//! CSS declarations. use std::borrow::Cow; use std::ops::Range; use crate::context::{DeclarationContext, PropertyHandlerContext}; use crate::error::{ParserError, PrinterError, PrinterErrorKind}; use crate::parser::ParserOptions; use crate::printer::Printer; use crate::properties::box_shadow::BoxShadowHandler; use crate::properties::custom::{CustomProperty, CustomPropertyName}; use crate::properties::masking::MaskHandler; use crate::properties::text::{Direction, UnicodeBidi}; use crate::properties::{ align::AlignHandler, animation::AnimationHandler, background::BackgroundHandler, border::BorderHandler, contain::ContainerHandler, display::DisplayHandler, flex::FlexHandler, font::FontHandler, grid::GridHandler, list::ListStyleHandler, margin_padding::*, outline::OutlineHandler, overflow::OverflowHandler, position::PositionHandler, prefix_handler::{FallbackHandler, PrefixHandler}, size::SizeHandler, text::TextDecorationHandler, transform::TransformHandler, transition::TransitionHandler, ui::ColorSchemeHandler, }; use crate::properties::{Property, PropertyId}; use crate::selector::SelectorList; use crate::traits::{PropertyHandler, ToCss}; use crate::values::ident::DashedIdent; use crate::values::string::CowArcStr; #[cfg(feature = "visitor")] use crate::visitor::Visit; use cssparser::*; use indexmap::IndexMap; use smallvec::SmallVec; /// A CSS declaration block. /// /// Properties are separated into a list of `!important` declararations, /// and a list of normal declarations. This reduces memory usage compared /// with storing a boolean along with each property. #[derive(Debug, PartialEq, Clone, Default)] #[cfg_attr(feature = "visitor", derive(Visit), visit(visit_declaration_block, PROPERTIES))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "camelCase") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub struct DeclarationBlock<'i> { /// A list of `!important` declarations in the block. #[cfg_attr(feature = "serde", serde(borrow, default))] pub important_declarations: Vec>, /// A list of normal declarations in the block. #[cfg_attr(feature = "serde", serde(default))] pub declarations: Vec>, } impl<'i> DeclarationBlock<'i> { /// Parses a declaration block from CSS syntax. pub fn parse<'a, 'o, 't>( input: &mut Parser<'i, 't>, options: &'a ParserOptions<'o, 'i>, ) -> Result>> { let mut important_declarations = DeclarationList::new(); let mut declarations = DeclarationList::new(); let mut decl_parser = PropertyDeclarationParser { important_declarations: &mut important_declarations, declarations: &mut declarations, options, }; let mut parser = RuleBodyParser::new(input, &mut decl_parser); while let Some(res) = parser.next() { if let Err((err, _)) = res { if options.error_recovery { options.warn(err); continue; } return Err(err); } } Ok(DeclarationBlock { important_declarations, declarations, }) } /// Parses a declaration block from a string. pub fn parse_string<'o>( input: &'i str, options: ParserOptions<'o, 'i>, ) -> Result>> { let mut input = ParserInput::new(input); let mut parser = Parser::new(&mut input); let result = Self::parse(&mut parser, &options)?; parser.expect_exhausted()?; Ok(result) } /// Returns an empty declaration block. pub fn new() -> Self { Self { declarations: vec![], important_declarations: vec![], } } /// Returns the total number of declarations in the block. pub fn len(&self) -> usize { self.declarations.len() + self.important_declarations.len() } } impl<'i> ToCss for DeclarationBlock<'i> { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { let len = self.declarations.len() + self.important_declarations.len(); let mut i = 0; macro_rules! write { ($decls: expr, $important: literal) => { for decl in &$decls { decl.to_css(dest, $important)?; if i != len - 1 { dest.write_char(';')?; dest.whitespace()?; } i += 1; } }; } write!(self.declarations, false); write!(self.important_declarations, true); Ok(()) } } impl<'i> DeclarationBlock<'i> { /// Writes the declarations to a CSS block, including starting and ending braces. pub fn to_css_block(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { dest.whitespace()?; dest.write_char('{')?; dest.indent(); dest.newline()?; self.to_css_declarations(dest, false, &parcel_selectors::SelectorList(SmallVec::new()), 0)?; dest.dedent(); dest.newline()?; dest.write_char('}') } pub(crate) fn has_printable_declarations(&self) -> bool { if self.len() > 1 { return true; } if self.declarations.len() == 1 { !matches!(self.declarations[0], crate::properties::Property::Composes(_)) } else if self.important_declarations.len() == 1 { !matches!(self.important_declarations[0], crate::properties::Property::Composes(_)) } else { false } } /// Writes the declarations to a CSS declaration block. pub fn to_css_declarations( &self, dest: &mut Printer, has_nested_rules: bool, selectors: &SelectorList, source_index: u32, ) -> Result<(), PrinterError> where W: std::fmt::Write, { let mut i = 0; let len = self.len(); macro_rules! write { ($decls: expr, $important: literal) => { for decl in &$decls { // The CSS modules `composes` property is handled specially, and omitted during printing. // We need to add the classes it references to the list for the selectors in this rule. if let crate::properties::Property::Composes(composes) = &decl { if dest.is_nested() && dest.css_module.is_some() { return Err(dest.error(PrinterErrorKind::InvalidComposesNesting, composes.loc)); } if let Some(css_module) = &mut dest.css_module { css_module .handle_composes(&selectors, &composes, source_index) .map_err(|e| dest.error(e, composes.loc))?; continue; } } if i > 0 { dest.newline()?; } decl.to_css(dest, $important)?; if i != len - 1 || !dest.minify || has_nested_rules { dest.write_char(';')?; } i += 1; } }; } write!(self.declarations, false); write!(self.important_declarations, true); Ok(()) } } impl<'i> DeclarationBlock<'i> { pub(crate) fn minify( &mut self, handler: &mut DeclarationHandler<'i>, important_handler: &mut DeclarationHandler<'i>, context: &mut PropertyHandlerContext<'i, '_>, ) { macro_rules! handle { ($decls: expr, $handler: expr, $important: literal) => { for decl in $decls.iter() { context.is_important = $important; let handled = $handler.handle_property(decl, context); if !handled { $handler.decls.push(decl.clone()); } } }; } handle!(self.important_declarations, important_handler, true); handle!(self.declarations, handler, false); handler.finalize(context); important_handler.finalize(context); self.important_declarations = std::mem::take(&mut important_handler.decls); self.declarations = std::mem::take(&mut handler.decls); } /// Returns whether the declaration block is empty. pub fn is_empty(&self) -> bool { return self.declarations.is_empty() && self.important_declarations.is_empty(); } pub(crate) fn property_location<'t>( &self, input: &mut Parser<'i, 't>, index: usize, ) -> Result<(Range, Range), ParseError<'i, ParserError<'i>>> { // Skip to the requested property index. for _ in 0..index { input.expect_ident()?; input.expect_colon()?; input.parse_until_after(Delimiter::Semicolon, |parser| { while parser.next().is_ok() {} Ok(()) })?; } // Get property name range. input.skip_whitespace(); let key_start = input.current_source_location(); input.expect_ident()?; let key_end = input.current_source_location(); let key_range = key_start..key_end; input.expect_colon()?; input.skip_whitespace(); // Get value range. let val_start = input.current_source_location(); input.parse_until_before(Delimiter::Semicolon, |parser| { while parser.next().is_ok() {} Ok(()) })?; let val_end = input.current_source_location(); let val_range = val_start..val_end; Ok((key_range, val_range)) } } impl<'i> DeclarationBlock<'i> { /// Returns an iterator over all properties in the declaration. pub fn iter(&self) -> impl std::iter::DoubleEndedIterator, bool)> { self .declarations .iter() .map(|property| (property, false)) .chain(self.important_declarations.iter().map(|property| (property, true))) } /// Returns a mutable iterator over all properties in the declaration. pub fn iter_mut(&mut self) -> impl std::iter::DoubleEndedIterator> { self.declarations.iter_mut().chain(self.important_declarations.iter_mut()) } /// Returns the value for a given property id based on the properties in this declaration block. /// /// If the property is a shorthand, the result will be a combined value of all of the included /// longhands, or `None` if some of the longhands are not declared. Otherwise, the value will be /// either an explicitly declared longhand, or a value extracted from a shorthand property. pub fn get<'a>(&'a self, property_id: &PropertyId) -> Option<(Cow<'a, Property<'i>>, bool)> { if property_id.is_shorthand() { if let Some((shorthand, important)) = property_id.shorthand_value(&self) { return Some((Cow::Owned(shorthand), important)); } } else { for (property, important) in self.iter().rev() { if property.property_id() == *property_id { return Some((Cow::Borrowed(property), important)); } if let Some(val) = property.longhand(&property_id) { return Some((Cow::Owned(val), important)); } } } None } /// Sets the value and importance for a given property, replacing any existing declarations. /// /// If the property already exists within the declaration block, it is updated in place. Otherwise, /// a new declaration is appended. When updating a longhand property and a shorthand is defined which /// includes the longhand, the shorthand will be updated rather than appending a new declaration. pub fn set(&mut self, property: Property<'i>, important: bool) { let property_id = property.property_id(); let declarations = if important { // Remove any non-important properties with this id. self.declarations.retain(|decl| decl.property_id() != property_id); &mut self.important_declarations } else { // Remove any important properties with this id. self.important_declarations.retain(|decl| decl.property_id() != property_id); &mut self.declarations }; let longhands = property_id.longhands().unwrap_or_else(|| vec![property.property_id()]); for decl in declarations.iter_mut().rev() { { // If any of the longhands being set are in the same logical property group as any of the // longhands in this property, but in a different category (i.e. logical or physical), // then we cannot modify in place, and need to append a new property. let id = decl.property_id(); let id_longhands = id.longhands().unwrap_or_else(|| vec![id]); if longhands.iter().any(|longhand| { let logical_group = longhand.logical_group(); let category = longhand.category(); logical_group.is_some() && id_longhands.iter().any(|id_longhand| { logical_group == id_longhand.logical_group() && category != id_longhand.category() }) }) { break; } } if decl.property_id() == property_id { *decl = property; return; } // Update shorthand. if decl.set_longhand(&property).is_ok() { return; } } declarations.push(property) } /// Removes all declarations of the given property id from the declaration block. /// /// When removing a longhand property and a shorthand is defined which includes the longhand, /// the shorthand will be split apart into its component longhand properties, minus the property /// to remove. When removing a shorthand, all included longhand properties are also removed. pub fn remove(&mut self, property_id: &PropertyId) { fn remove<'i, 'a>(declarations: &mut Vec>, property_id: &PropertyId<'a>) { let longhands = property_id.longhands().unwrap_or(vec![]); let mut i = 0; while i < declarations.len() { let replacement = { let property = &declarations[i]; let id = property.property_id(); if id == *property_id || longhands.contains(&id) { // If the property matches the requested property id, or is a longhand // property that is included in the requested shorthand, remove it. None } else if longhands.is_empty() && id.longhands().unwrap_or(vec![]).contains(&property_id) { // If this is a shorthand property that includes the requested longhand, // split it apart into its component longhands, excluding the requested one. Some( id.longhands() .unwrap() .iter() .filter_map(|longhand| { if *longhand == *property_id { None } else { property.longhand(longhand) } }) .collect::>(), ) } else { i += 1; continue; } }; match replacement { Some(properties) => { let count = properties.len(); declarations.splice(i..i + 1, properties); i += count; } None => { declarations.remove(i); } } } } remove(&mut self.declarations, property_id); remove(&mut self.important_declarations, property_id); } } struct PropertyDeclarationParser<'a, 'o, 'i> { important_declarations: &'a mut Vec>, declarations: &'a mut Vec>, options: &'a ParserOptions<'o, 'i>, } /// Parse a declaration within {} block: `color: blue` impl<'a, 'o, 'i> cssparser::DeclarationParser<'i> for PropertyDeclarationParser<'a, 'o, 'i> { type Declaration = (); type Error = ParserError<'i>; fn parse_value<'t>( &mut self, name: CowRcStr<'i>, input: &mut cssparser::Parser<'i, 't>, ) -> Result> { parse_declaration( name, input, &mut self.declarations, &mut self.important_declarations, &self.options, ) } } /// Default methods reject all at rules. impl<'a, 'o, 'i> AtRuleParser<'i> for PropertyDeclarationParser<'a, 'o, 'i> { type Prelude = (); type AtRule = (); type Error = ParserError<'i>; } impl<'a, 'o, 'i> QualifiedRuleParser<'i> for PropertyDeclarationParser<'a, 'o, 'i> { type Prelude = (); type QualifiedRule = (); type Error = ParserError<'i>; } impl<'a, 'o, 'i> RuleBodyItemParser<'i, (), ParserError<'i>> for PropertyDeclarationParser<'a, 'o, 'i> { fn parse_qualified(&self) -> bool { false } fn parse_declarations(&self) -> bool { true } } pub(crate) fn parse_declaration<'i, 't>( name: CowRcStr<'i>, input: &mut cssparser::Parser<'i, 't>, declarations: &mut DeclarationList<'i>, important_declarations: &mut DeclarationList<'i>, options: &ParserOptions<'_, 'i>, ) -> Result<(), cssparser::ParseError<'i, ParserError<'i>>> { // Stop if we hit a `{` token in a non-custom property to // avoid ambiguity between nested rules and declarations. // https://github.com/w3c/csswg-drafts/issues/9317 let property_id = PropertyId::from(CowArcStr::from(name)); let mut delimiters = Delimiter::Bang; if !matches!(property_id, PropertyId::Custom(CustomPropertyName::Custom(..))) { delimiters = delimiters | Delimiter::CurlyBracketBlock; } let property = input.parse_until_before(delimiters, |input| Property::parse(property_id, input, options))?; let important = input .try_parse(|input| { input.expect_delim('!')?; input.expect_ident_matching("important") }) .is_ok(); input.expect_exhausted()?; if important { important_declarations.push(property); } else { declarations.push(property); } Ok(()) } pub(crate) type DeclarationList<'i> = Vec>; #[derive(Default)] pub(crate) struct DeclarationHandler<'i> { background: BackgroundHandler<'i>, border: BorderHandler<'i>, outline: OutlineHandler, flex: FlexHandler, grid: GridHandler<'i>, align: AlignHandler, size: SizeHandler, margin: MarginHandler<'i>, padding: PaddingHandler<'i>, scroll_margin: ScrollMarginHandler<'i>, scroll_padding: ScrollPaddingHandler<'i>, font: FontHandler<'i>, text: TextDecorationHandler<'i>, list: ListStyleHandler<'i>, transition: TransitionHandler<'i>, animation: AnimationHandler<'i>, display: DisplayHandler<'i>, position: PositionHandler, inset: InsetHandler<'i>, overflow: OverflowHandler, transform: TransformHandler, box_shadow: BoxShadowHandler, mask: MaskHandler<'i>, container: ContainerHandler<'i>, color_scheme: ColorSchemeHandler, fallback: FallbackHandler, prefix: PrefixHandler, direction: Option, unicode_bidi: Option, custom_properties: IndexMap, usize>, decls: DeclarationList<'i>, } impl<'i> DeclarationHandler<'i> { pub fn handle_property( &mut self, property: &Property<'i>, context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { self.background.handle_property(property, &mut self.decls, context) || self.border.handle_property(property, &mut self.decls, context) || self.outline.handle_property(property, &mut self.decls, context) || self.flex.handle_property(property, &mut self.decls, context) || self.grid.handle_property(property, &mut self.decls, context) || self.align.handle_property(property, &mut self.decls, context) || self.size.handle_property(property, &mut self.decls, context) || self.margin.handle_property(property, &mut self.decls, context) || self.padding.handle_property(property, &mut self.decls, context) || self.scroll_margin.handle_property(property, &mut self.decls, context) || self.scroll_padding.handle_property(property, &mut self.decls, context) || self.font.handle_property(property, &mut self.decls, context) || self.text.handle_property(property, &mut self.decls, context) || self.list.handle_property(property, &mut self.decls, context) || self.transition.handle_property(property, &mut self.decls, context) || self.animation.handle_property(property, &mut self.decls, context) || self.display.handle_property(property, &mut self.decls, context) || self.position.handle_property(property, &mut self.decls, context) || self.inset.handle_property(property, &mut self.decls, context) || self.overflow.handle_property(property, &mut self.decls, context) || self.transform.handle_property(property, &mut self.decls, context) || self.box_shadow.handle_property(property, &mut self.decls, context) || self.mask.handle_property(property, &mut self.decls, context) || self.container.handle_property(property, &mut self.decls, context) || self.color_scheme.handle_property(property, &mut self.decls, context) || self.fallback.handle_property(property, &mut self.decls, context) || self.prefix.handle_property(property, &mut self.decls, context) || self.handle_all(property) || self.handle_custom_property(property, context) } fn handle_custom_property( &mut self, property: &Property<'i>, context: &mut PropertyHandlerContext<'i, '_>, ) -> bool { if let Property::Custom(custom) = property { if context.unused_symbols.contains(custom.name.as_ref()) { return true; } if let CustomPropertyName::Custom(name) = &custom.name { if let Some(index) = self.custom_properties.get(name) { if self.decls[*index] == *property { return true; } let mut custom = custom.clone(); self.add_conditional_fallbacks(&mut custom, context); self.decls[*index] = Property::Custom(custom); } else { self.custom_properties.insert(name.clone(), self.decls.len()); let mut custom = custom.clone(); self.add_conditional_fallbacks(&mut custom, context); self.decls.push(Property::Custom(custom)); } return true; } } false } fn handle_all(&mut self, property: &Property<'i>) -> bool { // The `all` property resets all properies except `unicode-bidi`, `direction`, and custom properties. // https://drafts.csswg.org/css-cascade-5/#all-shorthand match property { Property::UnicodeBidi(bidi) => { self.unicode_bidi = Some(*bidi); true } Property::Direction(direction) => { self.direction = Some(*direction); true } Property::All(keyword) => { let mut handler = DeclarationHandler { unicode_bidi: self.unicode_bidi.clone(), direction: self.direction.clone(), ..Default::default() }; for (key, index) in self.custom_properties.drain(..) { handler.custom_properties.insert(key, handler.decls.len()); handler.decls.push(self.decls[index].clone()); } handler.decls.push(Property::All(keyword.clone())); *self = handler; true } _ => false, } } fn add_conditional_fallbacks( &self, custom: &mut CustomProperty<'i>, context: &mut PropertyHandlerContext<'i, '_>, ) { if context.context != DeclarationContext::Keyframes { let fallbacks = custom.value.get_fallbacks(context.targets); for (condition, fallback) in fallbacks { context.add_conditional_property( condition, Property::Custom(CustomProperty { name: custom.name.clone(), value: fallback, }), ); } } } pub fn finalize(&mut self, context: &mut PropertyHandlerContext<'i, '_>) { if let Some(direction) = std::mem::take(&mut self.direction) { self.decls.push(Property::Direction(direction)); } if let Some(unicode_bidi) = std::mem::take(&mut self.unicode_bidi) { self.decls.push(Property::UnicodeBidi(unicode_bidi)); } self.background.finalize(&mut self.decls, context); self.border.finalize(&mut self.decls, context); self.outline.finalize(&mut self.decls, context); self.flex.finalize(&mut self.decls, context); self.grid.finalize(&mut self.decls, context); self.align.finalize(&mut self.decls, context); self.size.finalize(&mut self.decls, context); self.margin.finalize(&mut self.decls, context); self.padding.finalize(&mut self.decls, context); self.scroll_margin.finalize(&mut self.decls, context); self.scroll_padding.finalize(&mut self.decls, context); self.font.finalize(&mut self.decls, context); self.text.finalize(&mut self.decls, context); self.list.finalize(&mut self.decls, context); self.transition.finalize(&mut self.decls, context); self.animation.finalize(&mut self.decls, context); self.display.finalize(&mut self.decls, context); self.position.finalize(&mut self.decls, context); self.inset.finalize(&mut self.decls, context); self.overflow.finalize(&mut self.decls, context); self.transform.finalize(&mut self.decls, context); self.box_shadow.finalize(&mut self.decls, context); self.mask.finalize(&mut self.decls, context); self.container.finalize(&mut self.decls, context); self.color_scheme.finalize(&mut self.decls, context); self.fallback.finalize(&mut self.decls, context); self.prefix.finalize(&mut self.decls, context); self.custom_properties.clear(); } }