lightningcss/
declaration.rs

1//! CSS declarations.
2
3use std::borrow::Cow;
4use std::collections::HashMap;
5use std::ops::Range;
6
7use crate::context::{DeclarationContext, PropertyHandlerContext};
8use crate::error::{ParserError, PrinterError};
9use crate::parser::ParserOptions;
10use crate::printer::Printer;
11use crate::properties::box_shadow::BoxShadowHandler;
12use crate::properties::custom::{CustomProperty, CustomPropertyName};
13use crate::properties::masking::MaskHandler;
14use crate::properties::text::{Direction, UnicodeBidi};
15use crate::properties::{
16  align::AlignHandler,
17  animation::AnimationHandler,
18  background::BackgroundHandler,
19  border::BorderHandler,
20  contain::ContainerHandler,
21  display::DisplayHandler,
22  flex::FlexHandler,
23  font::FontHandler,
24  grid::GridHandler,
25  list::ListStyleHandler,
26  margin_padding::*,
27  outline::OutlineHandler,
28  overflow::OverflowHandler,
29  position::PositionHandler,
30  prefix_handler::{FallbackHandler, PrefixHandler},
31  size::SizeHandler,
32  text::TextDecorationHandler,
33  transform::TransformHandler,
34  transition::TransitionHandler,
35  ui::ColorSchemeHandler,
36};
37use crate::properties::{Property, PropertyId};
38use crate::traits::{PropertyHandler, ToCss};
39use crate::values::ident::DashedIdent;
40use crate::values::string::CowArcStr;
41#[cfg(feature = "visitor")]
42use crate::visitor::Visit;
43use cssparser::*;
44
45/// A CSS declaration block.
46///
47/// Properties are separated into a list of `!important` declararations,
48/// and a list of normal declarations. This reduces memory usage compared
49/// with storing a boolean along with each property.
50#[derive(Debug, PartialEq, Clone, Default)]
51#[cfg_attr(feature = "visitor", derive(Visit), visit(visit_declaration_block, PROPERTIES))]
52#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
53#[cfg_attr(
54  feature = "serde",
55  derive(serde::Serialize, serde::Deserialize),
56  serde(rename_all = "camelCase")
57)]
58#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
59pub struct DeclarationBlock<'i> {
60  /// A list of `!important` declarations in the block.
61  #[cfg_attr(feature = "serde", serde(borrow, default))]
62  pub important_declarations: Vec<Property<'i>>,
63  /// A list of normal declarations in the block.
64  #[cfg_attr(feature = "serde", serde(default))]
65  pub declarations: Vec<Property<'i>>,
66}
67
68impl<'i> DeclarationBlock<'i> {
69  /// Parses a declaration block from CSS syntax.
70  pub fn parse<'a, 'o, 't>(
71    input: &mut Parser<'i, 't>,
72    options: &'a ParserOptions<'o, 'i>,
73  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
74    let mut important_declarations = DeclarationList::new();
75    let mut declarations = DeclarationList::new();
76    let mut decl_parser = PropertyDeclarationParser {
77      important_declarations: &mut important_declarations,
78      declarations: &mut declarations,
79      options,
80    };
81    let mut parser = RuleBodyParser::new(input, &mut decl_parser);
82    while let Some(res) = parser.next() {
83      if let Err((err, _)) = res {
84        if options.error_recovery {
85          options.warn(err);
86          continue;
87        }
88        return Err(err);
89      }
90    }
91
92    Ok(DeclarationBlock {
93      important_declarations,
94      declarations,
95    })
96  }
97
98  /// Parses a declaration block from a string.
99  pub fn parse_string<'o>(
100    input: &'i str,
101    options: ParserOptions<'o, 'i>,
102  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
103    let mut input = ParserInput::new(input);
104    let mut parser = Parser::new(&mut input);
105    let result = Self::parse(&mut parser, &options)?;
106    parser.expect_exhausted()?;
107    Ok(result)
108  }
109
110  /// Returns an empty declaration block.
111  pub fn new() -> Self {
112    Self {
113      declarations: vec![],
114      important_declarations: vec![],
115    }
116  }
117
118  /// Returns the total number of declarations in the block.
119  pub fn len(&self) -> usize {
120    self.declarations.len() + self.important_declarations.len()
121  }
122}
123
124impl<'i> ToCss for DeclarationBlock<'i> {
125  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
126  where
127    W: std::fmt::Write,
128  {
129    let len = self.declarations.len() + self.important_declarations.len();
130    let mut i = 0;
131
132    macro_rules! write {
133      ($decls: expr, $important: literal) => {
134        for decl in &$decls {
135          decl.to_css(dest, $important)?;
136          if i != len - 1 {
137            dest.write_char(';')?;
138            dest.whitespace()?;
139          }
140          i += 1;
141        }
142      };
143    }
144
145    write!(self.declarations, false);
146    write!(self.important_declarations, true);
147    Ok(())
148  }
149}
150
151impl<'i> DeclarationBlock<'i> {
152  /// Writes the declarations to a CSS block, including starting and ending braces.
153  pub fn to_css_block<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
154  where
155    W: std::fmt::Write,
156  {
157    dest.whitespace()?;
158    dest.write_char('{')?;
159    dest.indent();
160
161    let mut i = 0;
162    let len = self.len();
163
164    macro_rules! write {
165      ($decls: expr, $important: literal) => {
166        for decl in &$decls {
167          dest.newline()?;
168          decl.to_css(dest, $important)?;
169          if i != len - 1 || !dest.minify {
170            dest.write_char(';')?;
171          }
172          i += 1;
173        }
174      };
175    }
176
177    write!(self.declarations, false);
178    write!(self.important_declarations, true);
179
180    dest.dedent();
181    dest.newline()?;
182    dest.write_char('}')
183  }
184}
185
186impl<'i> DeclarationBlock<'i> {
187  pub(crate) fn minify(
188    &mut self,
189    handler: &mut DeclarationHandler<'i>,
190    important_handler: &mut DeclarationHandler<'i>,
191    context: &mut PropertyHandlerContext<'i, '_>,
192  ) {
193    macro_rules! handle {
194      ($decls: expr, $handler: expr, $important: literal) => {
195        for decl in $decls.iter() {
196          context.is_important = $important;
197          let handled = $handler.handle_property(decl, context);
198
199          if !handled {
200            $handler.decls.push(decl.clone());
201          }
202        }
203      };
204    }
205
206    handle!(self.important_declarations, important_handler, true);
207    handle!(self.declarations, handler, false);
208
209    handler.finalize(context);
210    important_handler.finalize(context);
211    self.important_declarations = std::mem::take(&mut important_handler.decls);
212    self.declarations = std::mem::take(&mut handler.decls);
213  }
214
215  /// Returns whether the declaration block is empty.
216  pub fn is_empty(&self) -> bool {
217    return self.declarations.is_empty() && self.important_declarations.is_empty();
218  }
219
220  pub(crate) fn property_location<'t>(
221    &self,
222    input: &mut Parser<'i, 't>,
223    index: usize,
224  ) -> Result<(Range<SourceLocation>, Range<SourceLocation>), ParseError<'i, ParserError<'i>>> {
225    // Skip to the requested property index.
226    for _ in 0..index {
227      input.expect_ident()?;
228      input.expect_colon()?;
229      input.parse_until_after(Delimiter::Semicolon, |parser| {
230        while parser.next().is_ok() {}
231        Ok(())
232      })?;
233    }
234
235    // Get property name range.
236    input.skip_whitespace();
237    let key_start = input.current_source_location();
238    input.expect_ident()?;
239    let key_end = input.current_source_location();
240    let key_range = key_start..key_end;
241
242    input.expect_colon()?;
243    input.skip_whitespace();
244
245    // Get value range.
246    let val_start = input.current_source_location();
247    input.parse_until_before(Delimiter::Semicolon, |parser| {
248      while parser.next().is_ok() {}
249      Ok(())
250    })?;
251    let val_end = input.current_source_location();
252    let val_range = val_start..val_end;
253
254    Ok((key_range, val_range))
255  }
256}
257
258impl<'i> DeclarationBlock<'i> {
259  /// Returns an iterator over all properties in the declaration.
260  pub fn iter(&self) -> impl std::iter::DoubleEndedIterator<Item = (&Property<'i>, bool)> {
261    self
262      .declarations
263      .iter()
264      .map(|property| (property, false))
265      .chain(self.important_declarations.iter().map(|property| (property, true)))
266  }
267
268  /// Returns a mutable iterator over all properties in the declaration.
269  pub fn iter_mut(&mut self) -> impl std::iter::DoubleEndedIterator<Item = &mut Property<'i>> {
270    self.declarations.iter_mut().chain(self.important_declarations.iter_mut())
271  }
272
273  /// Returns the value for a given property id based on the properties in this declaration block.
274  ///
275  /// If the property is a shorthand, the result will be a combined value of all of the included
276  /// longhands, or `None` if some of the longhands are not declared. Otherwise, the value will be
277  /// either an explicitly declared longhand, or a value extracted from a shorthand property.
278  pub fn get<'a>(&'a self, property_id: &PropertyId) -> Option<(Cow<'a, Property<'i>>, bool)> {
279    if property_id.is_shorthand() {
280      if let Some((shorthand, important)) = property_id.shorthand_value(&self) {
281        return Some((Cow::Owned(shorthand), important));
282      }
283    } else {
284      for (property, important) in self.iter().rev() {
285        if property.property_id() == *property_id {
286          return Some((Cow::Borrowed(property), important));
287        }
288
289        if let Some(val) = property.longhand(&property_id) {
290          return Some((Cow::Owned(val), important));
291        }
292      }
293    }
294
295    None
296  }
297
298  /// Sets the value and importance for a given property, replacing any existing declarations.
299  ///
300  /// If the property already exists within the declaration block, it is updated in place. Otherwise,
301  /// a new declaration is appended. When updating a longhand property and a shorthand is defined which
302  /// includes the longhand, the shorthand will be updated rather than appending a new declaration.
303  pub fn set(&mut self, property: Property<'i>, important: bool) {
304    let property_id = property.property_id();
305    let declarations = if important {
306      // Remove any non-important properties with this id.
307      self.declarations.retain(|decl| decl.property_id() != property_id);
308      &mut self.important_declarations
309    } else {
310      // Remove any important properties with this id.
311      self.important_declarations.retain(|decl| decl.property_id() != property_id);
312      &mut self.declarations
313    };
314
315    let longhands = property_id.longhands().unwrap_or_else(|| vec![property.property_id()]);
316
317    for decl in declarations.iter_mut().rev() {
318      {
319        // If any of the longhands being set are in the same logical property group as any of the
320        // longhands in this property, but in a different category (i.e. logical or physical),
321        // then we cannot modify in place, and need to append a new property.
322        let id = decl.property_id();
323        let id_longhands = id.longhands().unwrap_or_else(|| vec![id]);
324        if longhands.iter().any(|longhand| {
325          let logical_group = longhand.logical_group();
326          let category = longhand.category();
327
328          logical_group.is_some()
329            && id_longhands.iter().any(|id_longhand| {
330              logical_group == id_longhand.logical_group() && category != id_longhand.category()
331            })
332        }) {
333          break;
334        }
335      }
336
337      if decl.property_id() == property_id {
338        *decl = property;
339        return;
340      }
341
342      // Update shorthand.
343      if decl.set_longhand(&property).is_ok() {
344        return;
345      }
346    }
347
348    declarations.push(property)
349  }
350
351  /// Removes all declarations of the given property id from the declaration block.
352  ///
353  /// When removing a longhand property and a shorthand is defined which includes the longhand,
354  /// the shorthand will be split apart into its component longhand properties, minus the property
355  /// to remove. When removing a shorthand, all included longhand properties are also removed.
356  pub fn remove(&mut self, property_id: &PropertyId) {
357    fn remove<'i, 'a>(declarations: &mut Vec<Property<'i>>, property_id: &PropertyId<'a>) {
358      let longhands = property_id.longhands().unwrap_or(vec![]);
359      let mut i = 0;
360      while i < declarations.len() {
361        let replacement = {
362          let property = &declarations[i];
363          let id = property.property_id();
364          if id == *property_id || longhands.contains(&id) {
365            // If the property matches the requested property id, or is a longhand
366            // property that is included in the requested shorthand, remove it.
367            None
368          } else if longhands.is_empty() && id.longhands().unwrap_or(vec![]).contains(&property_id) {
369            // If this is a shorthand property that includes the requested longhand,
370            // split it apart into its component longhands, excluding the requested one.
371            Some(
372              id.longhands()
373                .unwrap()
374                .iter()
375                .filter_map(|longhand| {
376                  if *longhand == *property_id {
377                    None
378                  } else {
379                    property.longhand(longhand)
380                  }
381                })
382                .collect::<Vec<Property>>(),
383            )
384          } else {
385            i += 1;
386            continue;
387          }
388        };
389
390        match replacement {
391          Some(properties) => {
392            let count = properties.len();
393            declarations.splice(i..i + 1, properties);
394            i += count;
395          }
396          None => {
397            declarations.remove(i);
398          }
399        }
400      }
401    }
402
403    remove(&mut self.declarations, property_id);
404    remove(&mut self.important_declarations, property_id);
405  }
406}
407
408struct PropertyDeclarationParser<'a, 'o, 'i> {
409  important_declarations: &'a mut Vec<Property<'i>>,
410  declarations: &'a mut Vec<Property<'i>>,
411  options: &'a ParserOptions<'o, 'i>,
412}
413
414/// Parse a declaration within {} block: `color: blue`
415impl<'a, 'o, 'i> cssparser::DeclarationParser<'i> for PropertyDeclarationParser<'a, 'o, 'i> {
416  type Declaration = ();
417  type Error = ParserError<'i>;
418
419  fn parse_value<'t>(
420    &mut self,
421    name: CowRcStr<'i>,
422    input: &mut cssparser::Parser<'i, 't>,
423  ) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {
424    parse_declaration(
425      name,
426      input,
427      &mut self.declarations,
428      &mut self.important_declarations,
429      &self.options,
430    )
431  }
432}
433
434/// Default methods reject all at rules.
435impl<'a, 'o, 'i> AtRuleParser<'i> for PropertyDeclarationParser<'a, 'o, 'i> {
436  type Prelude = ();
437  type AtRule = ();
438  type Error = ParserError<'i>;
439}
440
441impl<'a, 'o, 'i> QualifiedRuleParser<'i> for PropertyDeclarationParser<'a, 'o, 'i> {
442  type Prelude = ();
443  type QualifiedRule = ();
444  type Error = ParserError<'i>;
445}
446
447impl<'a, 'o, 'i> RuleBodyItemParser<'i, (), ParserError<'i>> for PropertyDeclarationParser<'a, 'o, 'i> {
448  fn parse_qualified(&self) -> bool {
449    false
450  }
451
452  fn parse_declarations(&self) -> bool {
453    true
454  }
455}
456
457pub(crate) fn parse_declaration<'i, 't>(
458  name: CowRcStr<'i>,
459  input: &mut cssparser::Parser<'i, 't>,
460  declarations: &mut DeclarationList<'i>,
461  important_declarations: &mut DeclarationList<'i>,
462  options: &ParserOptions<'_, 'i>,
463) -> Result<(), cssparser::ParseError<'i, ParserError<'i>>> {
464  // Stop if we hit a `{` token in a non-custom property to
465  // avoid ambiguity between nested rules and declarations.
466  // https://github.com/w3c/csswg-drafts/issues/9317
467  let property_id = PropertyId::from(CowArcStr::from(name));
468  let mut delimiters = Delimiter::Bang;
469  if !matches!(property_id, PropertyId::Custom(CustomPropertyName::Custom(..))) {
470    delimiters = delimiters | Delimiter::CurlyBracketBlock;
471  }
472  let property = input.parse_until_before(delimiters, |input| Property::parse(property_id, input, options))?;
473  let important = input
474    .try_parse(|input| {
475      input.expect_delim('!')?;
476      input.expect_ident_matching("important")
477    })
478    .is_ok();
479  input.expect_exhausted()?;
480  if important {
481    important_declarations.push(property);
482  } else {
483    declarations.push(property);
484  }
485  Ok(())
486}
487
488pub(crate) type DeclarationList<'i> = Vec<Property<'i>>;
489
490#[derive(Default)]
491pub(crate) struct DeclarationHandler<'i> {
492  background: BackgroundHandler<'i>,
493  border: BorderHandler<'i>,
494  outline: OutlineHandler,
495  flex: FlexHandler,
496  grid: GridHandler<'i>,
497  align: AlignHandler,
498  size: SizeHandler<'i>,
499  margin: MarginHandler<'i>,
500  padding: PaddingHandler<'i>,
501  scroll_margin: ScrollMarginHandler<'i>,
502  scroll_padding: ScrollPaddingHandler<'i>,
503  font: FontHandler<'i>,
504  text: TextDecorationHandler<'i>,
505  list: ListStyleHandler<'i>,
506  transition: TransitionHandler<'i>,
507  animation: AnimationHandler<'i>,
508  display: DisplayHandler<'i>,
509  position: PositionHandler,
510  inset: InsetHandler<'i>,
511  overflow: OverflowHandler,
512  transform: TransformHandler,
513  box_shadow: BoxShadowHandler,
514  mask: MaskHandler<'i>,
515  container: ContainerHandler<'i>,
516  color_scheme: ColorSchemeHandler,
517  fallback: FallbackHandler,
518  prefix: PrefixHandler,
519  direction: Option<Direction>,
520  unicode_bidi: Option<UnicodeBidi>,
521  custom_properties: HashMap<DashedIdent<'i>, usize>,
522  decls: DeclarationList<'i>,
523}
524
525impl<'i> DeclarationHandler<'i> {
526  pub fn handle_property(
527    &mut self,
528    property: &Property<'i>,
529    context: &mut PropertyHandlerContext<'i, '_>,
530  ) -> bool {
531    self.background.handle_property(property, &mut self.decls, context)
532      || self.border.handle_property(property, &mut self.decls, context)
533      || self.outline.handle_property(property, &mut self.decls, context)
534      || self.flex.handle_property(property, &mut self.decls, context)
535      || self.grid.handle_property(property, &mut self.decls, context)
536      || self.align.handle_property(property, &mut self.decls, context)
537      || self.size.handle_property(property, &mut self.decls, context)
538      || self.margin.handle_property(property, &mut self.decls, context)
539      || self.padding.handle_property(property, &mut self.decls, context)
540      || self.scroll_margin.handle_property(property, &mut self.decls, context)
541      || self.scroll_padding.handle_property(property, &mut self.decls, context)
542      || self.font.handle_property(property, &mut self.decls, context)
543      || self.text.handle_property(property, &mut self.decls, context)
544      || self.list.handle_property(property, &mut self.decls, context)
545      || self.transition.handle_property(property, &mut self.decls, context)
546      || self.animation.handle_property(property, &mut self.decls, context)
547      || self.display.handle_property(property, &mut self.decls, context)
548      || self.position.handle_property(property, &mut self.decls, context)
549      || self.inset.handle_property(property, &mut self.decls, context)
550      || self.overflow.handle_property(property, &mut self.decls, context)
551      || self.transform.handle_property(property, &mut self.decls, context)
552      || self.box_shadow.handle_property(property, &mut self.decls, context)
553      || self.mask.handle_property(property, &mut self.decls, context)
554      || self.container.handle_property(property, &mut self.decls, context)
555      || self.color_scheme.handle_property(property, &mut self.decls, context)
556      || self.fallback.handle_property(property, &mut self.decls, context)
557      || self.prefix.handle_property(property, &mut self.decls, context)
558      || self.handle_all(property)
559      || self.handle_custom_property(property, context)
560  }
561
562  fn handle_custom_property(
563    &mut self,
564    property: &Property<'i>,
565    context: &mut PropertyHandlerContext<'i, '_>,
566  ) -> bool {
567    if let Property::Custom(custom) = property {
568      if context.unused_symbols.contains(custom.name.as_ref()) {
569        return true;
570      }
571
572      if let CustomPropertyName::Custom(name) = &custom.name {
573        if let Some(index) = self.custom_properties.get(name) {
574          if self.decls[*index] == *property {
575            return true;
576          }
577          let mut custom = custom.clone();
578          self.add_conditional_fallbacks(&mut custom, context);
579          self.decls[*index] = Property::Custom(custom);
580        } else {
581          self.custom_properties.insert(name.clone(), self.decls.len());
582          let mut custom = custom.clone();
583          self.add_conditional_fallbacks(&mut custom, context);
584          self.decls.push(Property::Custom(custom));
585        }
586
587        return true;
588      }
589    }
590
591    false
592  }
593
594  fn handle_all(&mut self, property: &Property<'i>) -> bool {
595    // The `all` property resets all properies except `unicode-bidi`, `direction`, and custom properties.
596    // https://drafts.csswg.org/css-cascade-5/#all-shorthand
597    match property {
598      Property::UnicodeBidi(bidi) => {
599        self.unicode_bidi = Some(*bidi);
600        true
601      }
602      Property::Direction(direction) => {
603        self.direction = Some(*direction);
604        true
605      }
606      Property::All(keyword) => {
607        let mut handler = DeclarationHandler {
608          unicode_bidi: self.unicode_bidi.clone(),
609          direction: self.direction.clone(),
610          ..Default::default()
611        };
612        for (key, index) in self.custom_properties.drain() {
613          handler.custom_properties.insert(key, handler.decls.len());
614          handler.decls.push(self.decls[index].clone());
615        }
616        handler.decls.push(Property::All(keyword.clone()));
617        *self = handler;
618        true
619      }
620      _ => false,
621    }
622  }
623
624  fn add_conditional_fallbacks(
625    &self,
626    custom: &mut CustomProperty<'i>,
627    context: &mut PropertyHandlerContext<'i, '_>,
628  ) {
629    if context.context != DeclarationContext::Keyframes {
630      let fallbacks = custom.value.get_fallbacks(context.targets);
631      for (condition, fallback) in fallbacks {
632        context.add_conditional_property(
633          condition,
634          Property::Custom(CustomProperty {
635            name: custom.name.clone(),
636            value: fallback,
637          }),
638        );
639      }
640    }
641  }
642
643  pub fn finalize(&mut self, context: &mut PropertyHandlerContext<'i, '_>) {
644    if let Some(direction) = std::mem::take(&mut self.direction) {
645      self.decls.push(Property::Direction(direction));
646    }
647    if let Some(unicode_bidi) = std::mem::take(&mut self.unicode_bidi) {
648      self.decls.push(Property::UnicodeBidi(unicode_bidi));
649    }
650
651    self.background.finalize(&mut self.decls, context);
652    self.border.finalize(&mut self.decls, context);
653    self.outline.finalize(&mut self.decls, context);
654    self.flex.finalize(&mut self.decls, context);
655    self.grid.finalize(&mut self.decls, context);
656    self.align.finalize(&mut self.decls, context);
657    self.size.finalize(&mut self.decls, context);
658    self.margin.finalize(&mut self.decls, context);
659    self.padding.finalize(&mut self.decls, context);
660    self.scroll_margin.finalize(&mut self.decls, context);
661    self.scroll_padding.finalize(&mut self.decls, context);
662    self.font.finalize(&mut self.decls, context);
663    self.text.finalize(&mut self.decls, context);
664    self.list.finalize(&mut self.decls, context);
665    self.transition.finalize(&mut self.decls, context);
666    self.animation.finalize(&mut self.decls, context);
667    self.display.finalize(&mut self.decls, context);
668    self.position.finalize(&mut self.decls, context);
669    self.inset.finalize(&mut self.decls, context);
670    self.overflow.finalize(&mut self.decls, context);
671    self.transform.finalize(&mut self.decls, context);
672    self.box_shadow.finalize(&mut self.decls, context);
673    self.mask.finalize(&mut self.decls, context);
674    self.container.finalize(&mut self.decls, context);
675    self.color_scheme.finalize(&mut self.decls, context);
676    self.fallback.finalize(&mut self.decls, context);
677    self.prefix.finalize(&mut self.decls, context);
678    self.custom_properties.clear();
679  }
680}