Skip to main content

takumi_css/style/
stylesheets.rs

1use crate::style::unexpected_token;
2use std::{borrow::Cow, collections::HashMap, fmt, str::FromStr};
3
4use cssparser::{Parser, ParserInput, Token, match_ignore_ascii_case};
5use paste::paste;
6use serde::de::IgnoredAny;
7use smallvec::{SmallVec, smallvec};
8
9use crate::style::selector::{PropertyRule, StyleDeclarationParser};
10use crate::{
11  error::StyleDeclarationBlockParseError,
12  style::{CssInput, CssValueSeed, SizingContext, properties::*},
13};
14use cssparser::RuleBodyParser;
15#[path = "stylesheets_helpers.rs"]
16mod stylesheets_helpers;
17#[path = "stylesheets_mask.rs"]
18mod stylesheets_mask;
19#[path = "stylesheets_query.rs"]
20mod stylesheets_query;
21#[path = "stylesheets_vars.rs"]
22mod stylesheets_vars;
23
24use self::stylesheets_helpers::*;
25pub(crate) use self::stylesheets_mask::PropertyMask;
26use self::stylesheets_vars::apply_deferred_declaration;
27
28macro_rules! define_inherited_default {
29  ($parent:expr, $inherit:tt) => {
30    $parent.to_owned()
31  };
32  ($parent:expr) => {
33    Default::default()
34  };
35}
36
37type ParsedDeclarations = SmallVec<[StyleDeclaration; 8]>;
38
39#[derive(Debug, Clone, PartialEq, Eq)]
40struct DeferredDeclaration {
41  property: PropertyId,
42  specified_value: String,
43}
44
45#[derive(Clone, Copy)]
46struct InterpolationContext<'a> {
47  progress: f32,
48  sizing: &'a SizingContext,
49  current_color: Color,
50}
51
52fn interpolate_option_with_missing<T: Animatable + Clone>(
53  target: &mut Option<T>,
54  from: &Option<T>,
55  to: &Option<T>,
56  missing_from: T,
57  missing_to: T,
58  context: InterpolationContext<'_>,
59) {
60  *target = match (from, to) {
61    (Some(from), Some(to)) => {
62      let mut value = from.clone();
63      value.interpolate(
64        from,
65        to,
66        context.progress,
67        context.sizing,
68        context.current_color,
69      );
70      Some(value)
71    }
72    (Some(from), None) => {
73      let mut value = from.clone();
74      value.interpolate(
75        from,
76        &missing_to,
77        context.progress,
78        context.sizing,
79        context.current_color,
80      );
81      Some(value)
82    }
83    (None, Some(to)) => {
84      let mut value = missing_from.clone();
85      value.interpolate(
86        &missing_from,
87        to,
88        context.progress,
89        context.sizing,
90        context.current_color,
91      );
92      Some(value)
93    }
94    (None, None) => None,
95  };
96}
97
98macro_rules! push_expanded_declarations {
99  ($target:expr; $($declaration:expr),+ $(,)?) => {{
100    $(
101      $target.push($declaration);
102    )+
103  }};
104}
105
106macro_rules! push_axis_declarations {
107  ($target:expr, $value:expr, $first:ident, $second:ident) => {{
108    let value = $value;
109    push_expanded_declarations!(
110      $target;
111      StyleDeclaration::$first(value.x),
112      StyleDeclaration::$second(value.y),
113    );
114  }};
115}
116
117macro_rules! push_four_side_declarations {
118  ($target:expr, $values:expr, $top:ident, $right:ident, $bottom:ident, $left:ident) => {{
119    let values = $values;
120    push_expanded_declarations!(
121      $target;
122      StyleDeclaration::$top(values[0]),
123      StyleDeclaration::$right(values[1]),
124      StyleDeclaration::$bottom(values[2]),
125      StyleDeclaration::$left(values[3]),
126    );
127  }};
128}
129
130macro_rules! define_style {
131  (
132    longhands {
133      $(
134        $longhand:ident: $longhand_ty:ty
135          $(where inherit = $longhand_inherit:expr)?,
136      )*
137    }
138    // `name: type => (ltr_field, rtl_field)` — apply resolves to one of them.
139    transient_longhands {
140      $(
141        $transient:ident: $transient_ty:ty
142          => ($transient_ltr:ident, $transient_rtl:ident),
143      )*
144    }
145    shorthands {
146      $(
147        $shorthand:ident: $shorthand_ty:ty
148          $(where inherit = $shorthand_inherit:expr)?
149          => [$($target:ident),+ $(,)?]
150          |$value:ident, $target_var:ident|
151          $expand:block,
152      )*
153    }
154  ) => {
155    paste! {
156      #[repr(u8)]
157      #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
158      pub enum LonghandId {
159        $([<$longhand:camel>],)*
160        $([<$transient:camel>],)*
161      }
162
163      impl LonghandId {
164        const COUNT: usize = [$(Self::[<$longhand:camel>]),* $(, Self::[<$transient:camel>])*].len();
165        const ALL: [Self; Self::COUNT] = [
166          $(Self::[<$longhand:camel>],)*
167          $(Self::[<$transient:camel>],)*
168        ];
169
170        const fn index(self) -> usize {
171          self as usize
172        }
173      }
174
175      #[repr(u8)]
176      #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
177      pub enum ShorthandId {
178        $([<$shorthand:camel>],)*
179      }
180
181      impl LonghandId {
182        fn parse_declarations<'i>(
183          self,
184          input: &mut cssparser::Parser<'i, '_>,
185        ) -> ParseResult<'i, ParsedDeclarations> {
186          let state = input.state();
187          let keyword = input.try_parse(CssWideKeyword::from_css).ok();
188
189          if let Some(keyword) = keyword {
190            return Ok(smallvec![StyleDeclaration::CssWideKeyword(self, keyword)]);
191          }
192
193          input.reset(&state);
194          match self {
195            $(
196              Self::[<$longhand:camel>] => Ok(smallvec![StyleDeclaration::[<$longhand:camel>](
197                <$longhand_ty as FromCss>::from_css(input)?,
198              )]),
199            )*
200            $(
201              Self::[<$transient:camel>] => Ok(smallvec![StyleDeclaration::[<$transient:camel>](
202                <$transient_ty as FromCss>::from_css(input)?,
203              )]),
204            )*
205          }
206        }
207
208        fn parse_css_input_declarations<'de>(
209          self,
210          css_input: CssInput<'de>,
211        ) -> Result<ParsedDeclarations, CssInputParseError<'de>> {
212          if let Some(keyword) = parse_css_wide_keyword(&css_input) {
213            return Ok(smallvec![StyleDeclaration::CssWideKeyword(self, keyword)]);
214          }
215
216          match self {
217            $(
218              Self::[<$longhand:camel>] => Ok(smallvec![StyleDeclaration::[<$longhand:camel>](
219                parse_css_input_value(css_input)?,
220              )]),
221            )*
222            $(
223              Self::[<$transient:camel>] => Ok(smallvec![StyleDeclaration::[<$transient:camel>](
224                parse_css_input_value(css_input)?,
225              )]),
226            )*
227          }
228        }
229      }
230
231      impl ShorthandId {
232        fn parse_declarations<'i>(
233          self,
234          input: &mut cssparser::Parser<'i, '_>,
235        ) -> ParseResult<'i, ParsedDeclarations> {
236          match self {
237            $(
238              Self::[<$shorthand:camel>] => Ok(expand_shorthand(
239                <$shorthand_ty as FromCss>::from_css(input)?,
240                |$value, $target_var| {
241                  $expand
242                },
243              )),
244            )*
245          }
246        }
247
248        fn parse_css_input_declarations<'de>(
249          self,
250          css_input: CssInput<'de>,
251        ) -> Result<ParsedDeclarations, CssInputParseError<'de>> {
252          match self {
253            $(
254              Self::[<$shorthand:camel>] => {
255                if let Some(keyword) = parse_css_wide_keyword(&css_input) {
256                  let mut declarations = ParsedDeclarations::new();
257                  $(
258                    declarations.push(StyleDeclaration::CssWideKeyword(LonghandId::$target, keyword));
259                  )+
260                  return Ok(declarations);
261                }
262
263                Ok(expand_shorthand(
264                  parse_css_input_value::<$shorthand_ty>(css_input)?,
265                  |$value, $target_var| {
266                    $expand
267                  },
268                ))
269              }
270            )*
271          }
272        }
273      }
274
275      #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
276      pub enum PropertyId {
277        Ignored,
278        Custom,
279        Longhand(LonghandId),
280        Shorthand(ShorthandId),
281      }
282
283      impl PropertyId {
284        fn from_normalized_name(name: &str) -> Self {
285          match name {
286            $(stringify!($longhand) => Self::Longhand(LonghandId::[<$longhand:camel>]),)*
287            $(stringify!($transient) => Self::Longhand(LonghandId::[<$transient:camel>]),)*
288            $(stringify!($shorthand) => Self::Shorthand(ShorthandId::[<$shorthand:camel>]),)*
289            _ => Self::Ignored,
290          }
291        }
292
293        fn from_kebab_case(name: &str) -> Self {
294          PropertyId::from_name(name, normalize_kebab_property_name)
295        }
296
297        #[allow(dead_code)]
298        pub fn from_camel_case(name: &str) -> Self {
299          PropertyId::from_name(name, normalize_camel_property_name)
300        }
301
302        fn parse_declarations<'i>(
303          self,
304          name: &str,
305          input: &mut cssparser::Parser<'i, '_>,
306        ) -> ParseResult<'i, ParsedDeclarations> {
307          match self {
308            Self::Ignored => {
309              while input.next_including_whitespace_and_comments().is_ok() {}
310              Ok(ParsedDeclarations::new())
311            }
312            Self::Custom => {
313              let start = input.position();
314              while input.next_including_whitespace_and_comments().is_ok() {}
315              Ok(smallvec![StyleDeclaration::CustomProperty(
316                name.to_owned(),
317                input.slice_from(start).trim().to_owned(),
318              )])
319            }
320            Self::Shorthand(property) => property.parse_declarations(input),
321            Self::Longhand(property) => property.parse_declarations(input),
322          }
323        }
324
325        fn parse_css_input_declarations<'de>(
326          self,
327          css_input: CssInput<'de>,
328        ) -> Result<ParsedDeclarations, CssInputParseError<'de>> {
329          debug_assert!(
330            !matches!(self, Self::Custom),
331            "custom properties should be handled before parse_css_input_declarations",
332          );
333
334          let css_string = match &css_input {
335            CssInput::Str(value) => Some(value.as_ref()),
336            CssInput::Number(_) => None,
337            CssInput::Unexpected(_) => None,
338          };
339
340            if css_string.is_some_and(contains_var_function) {
341              return Ok(smallvec![StyleDeclaration::Deferred(DeferredDeclaration {
342                property: self,
343                specified_value: css_input.into_string(),
344              })]);
345            }
346
347          match self {
348            Self::Ignored => Ok(ParsedDeclarations::new()),
349            Self::Custom => Ok(ParsedDeclarations::new()),
350            Self::Shorthand(property) => property.parse_css_input_declarations(css_input),
351            Self::Longhand(property) => property.parse_css_input_declarations(css_input),
352          }
353        }
354
355        /// Longhands this property expands into (shorthand-expansion targets; unrelated to `!important`).
356        fn target_longhands(self) -> PropertyMask {
357          match self {
358            Self::Ignored | Self::Custom => PropertyMask::default(),
359            Self::Longhand(property) => [property].into_iter().collect(),
360            Self::Shorthand(property) => match property {
361              $(ShorthandId::[<$shorthand:camel>] => {
362                [$(LonghandId::$target),+].into_iter().collect()
363              })*
364            },
365          }
366        }
367      }
368
369      fn parse_style_declaration<'i>(
370        name: &str,
371        input: &mut cssparser::Parser<'i, '_>,
372      ) -> ParseResult<'i, StyleDeclarationBlock> {
373        let property = PropertyId::from_kebab_case(name);
374        let start = input.position();
375        // Detect var() up-front; otherwise a partial parse (e.g. `0 var(--y)`)
376        // would commit before deferral. See #712.
377        if !matches!(property, PropertyId::Ignored | PropertyId::Custom) {
378          let state = input.state();
379          while input.next_including_whitespace_and_comments().is_ok() {}
380          let specified_value = input.slice_from(start).trim();
381          if contains_var_function(specified_value) {
382            return Ok(StyleDeclarationBlock::from_parsed_declarations(
383              smallvec![StyleDeclaration::Deferred(DeferredDeclaration {
384                property,
385                specified_value: specified_value.to_owned(),
386              })],
387              false,
388            ));
389          }
390          input.reset(&state);
391        }
392
393        property.parse_declarations(name, input).map(|declarations| {
394          StyleDeclarationBlock::from_parsed_declarations(declarations, false)
395        })
396      }
397
398      /// Defines the style of an element.
399      #[derive(Debug, Default, Clone, PartialEq)]
400      pub struct Style {
401        pub declarations: StyleDeclarationBlock,
402      }
403
404      impl<'de> serde::Deserialize<'de> for Style {
405        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
406        where
407          D: serde::Deserializer<'de>,
408        {
409          struct StyleVisitor;
410
411          impl<'de> serde::de::Visitor<'de> for StyleVisitor {
412            type Value = Style;
413
414            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
415              formatter.write_str("a style object")
416            }
417
418            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
419            where
420              A: serde::de::MapAccess<'de>,
421            {
422              let mut style = Style::default();
423
424              while let Some(key) = map.next_key::<Cow<'de, str>>()? {
425                let property = PropertyId::from_camel_case(&key);
426                if matches!(property, PropertyId::Ignored) {
427                  map.next_value::<IgnoredAny>()?;
428                  continue;
429                }
430
431                let css_input = map.next_value_seed(CssValueSeed)?;
432                if matches!(property, PropertyId::Custom) {
433                  if !matches!(css_input, CssInput::Unexpected(_)) {
434                    style.declarations.push(
435                      StyleDeclaration::CustomProperty(key.into_owned(), css_input.into_string()),
436                      false,
437                    );
438                  }
439                } else {
440                  style
441                    .declarations
442                    .append_parsed_declarations(
443                      property
444                        .parse_css_input_declarations(css_input)
445                        .map_err(|error| error.into_serde_error(&key, property))?,
446                      false,
447                    );
448                }
449              }
450
451              Ok(style)
452            }
453          }
454
455          deserializer.deserialize_map(StyleVisitor)
456        }
457      }
458
459      impl<'de> serde::Deserialize<'de> for StyleDeclarationBlock {
460        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
461        where
462          D: serde::Deserializer<'de>,
463        {
464          Style::deserialize(deserializer).map(Into::into)
465        }
466      }
467
468      impl Style {
469        fn with_declarations(
470          mut self,
471          declarations: impl IntoIterator<Item = StyleDeclaration>,
472          important: bool,
473        ) -> Self {
474          for declaration in declarations {
475            self.declarations.push(declaration, important);
476          }
477          self
478        }
479
480        /// Returns a new style with one declaration appended in source order.
481        pub fn with(self, declaration: StyleDeclaration) -> Self {
482          self.with_declarations([declaration], false)
483        }
484
485        $(
486          /// Returns a new style with this shorthand expanded and appended in source order.
487          pub fn [<with_ $shorthand>](self, value: $shorthand_ty) -> Self {
488            self.with_declarations(
489              expand_shorthand(value, |$value, $target_var| {
490                $expand
491              }),
492              false,
493            )
494          }
495        )*
496
497        /// Returns a new style with one `!important` declaration appended in source order.
498        pub fn with_important(self, declaration: StyleDeclaration) -> Self {
499          self.with_declarations([declaration], true)
500        }
501
502        pub fn append_block(&mut self, declarations: StyleDeclarationBlock) {
503          self.declarations.append(declarations);
504        }
505
506        pub fn push(&mut self, declaration: StyleDeclaration, important: bool) {
507          self.declarations.push(declaration, important);
508        }
509
510        /// Collects resource URLs referenced by this style's declarations.
511        pub fn resource_urls(&self) -> impl Iterator<Item = &str> {
512          self.declarations.resource_urls()
513        }
514
515        pub fn inherit(self, parent: &ComputedStyle) -> ComputedStyle {
516          let mut style = ComputedStyle::from_parent(parent);
517          let mut declarations = ParsedDeclarations::new();
518
519          for declaration in self.declarations.declarations {
520            match declaration {
521              StyleDeclaration::CustomProperty(name, value) => {
522                style.custom_properties.insert(name, value);
523              }
524              declaration => declarations.push(declaration),
525            }
526          }
527
528          // Pre-resolve `direction` so logical-axis applies below see the
529          // final value even if `direction:` is declared later in the block.
530          for declaration in &declarations {
531            match declaration {
532              StyleDeclaration::Direction(d) => style.direction = *d,
533              StyleDeclaration::CssWideKeyword(LonghandId::Direction, keyword) => {
534                style.direction = match keyword {
535                  CssWideKeyword::Initial => Direction::default(),
536                  CssWideKeyword::Inherit | CssWideKeyword::Unset => parent.direction,
537                };
538              }
539              StyleDeclaration::Deferred(deferred)
540                if matches!(deferred.property, PropertyId::Longhand(LonghandId::Direction)) =>
541              {
542                apply_deferred_declaration(&mut style, Some(parent), deferred);
543              }
544              _ => {}
545            }
546          }
547
548          for declaration in declarations {
549            declaration.apply_with_parent(&mut style, parent);
550          }
551          style
552        }
553
554        pub fn merge_from(&mut self, other: Self) {
555          self.append_block(other.declarations);
556        }
557      }
558
559      impl From<StyleDeclarationBlock> for Style {
560        fn from(declarations: StyleDeclarationBlock) -> Self {
561          Self { declarations }
562        }
563      }
564
565      impl From<Style> for StyleDeclarationBlock {
566        fn from(style: Style) -> Self {
567          style.declarations
568        }
569      }
570
571      /// The computed style snapshot used during layout and rendering.
572      #[derive(Clone, Debug, Default)]
573      pub struct ComputedStyle {
574        pub custom_properties: HashMap<String, String>,
575        pub registered_custom_properties: HashMap<String, PropertyRule>,
576        $(pub $longhand: $longhand_ty,)*
577      }
578
579      /// A single specified declaration stored in a declaration block.
580      #[allow(private_interfaces)]
581      #[derive(Debug, Clone, PartialEq)]
582      pub enum StyleDeclaration {
583        $(
584          /// An explicit specified value for a non-shorthand property.
585          [<$longhand:camel>]($longhand_ty),
586        )*
587        $(
588          /// Logical-axis value, resolved to a physical side at apply time.
589          [<$transient:camel>]($transient_ty),
590        )*
591        /// A custom property declaration such as `--token: value`.
592        CustomProperty(String, String),
593        /// A property value that must be resolved after `var()` substitution.
594        Deferred(DeferredDeclaration),
595        /// A CSS-wide keyword targeting a longhand property.
596        CssWideKeyword(LonghandId, CssWideKeyword),
597      }
598
599      impl ComputedStyle {
600        pub fn from_parent(parent: &Self) -> Self {
601          Self {
602            custom_properties: if parent.custom_properties.is_empty() {
603              HashMap::new()
604            } else {
605              parent.custom_properties.clone()
606            },
607            registered_custom_properties: if parent.registered_custom_properties.is_empty() {
608              HashMap::new()
609            } else {
610              parent.registered_custom_properties.clone()
611            },
612            $($longhand: define_inherited_default!(parent.$longhand $(, $longhand_inherit)?),)*
613          }
614        }
615
616        pub fn make_computed_values(&mut self, sizing: &SizingContext) {
617          $(self.$longhand.make_computed(sizing);)*
618        }
619
620        pub(crate) fn apply_interpolated_properties(
621          &mut self,
622          from: &Self,
623          to: &Self,
624          animated_properties: &PropertyMask,
625          progress: f32,
626          sizing: &SizingContext,
627          current_color: Color,
628        ) {
629          let interpolation_context = InterpolationContext {
630            progress,
631            sizing,
632            current_color,
633          };
634
635          for property in animated_properties.iter() {
636            match property {
637              $(
638                LonghandId::[<$longhand:camel>] => {
639                  self.$longhand.interpolate(
640                    &from.$longhand,
641                    &to.$longhand,
642                    progress,
643                    sizing,
644                    current_color,
645                  );
646                }
647              )*
648              $(LonghandId::[<$transient:camel>] => {})*
649            }
650          }
651
652          // special cases
653          if animated_properties.contains(&LonghandId::FlexGrow) {
654            interpolate_option_with_missing(
655              &mut self.flex_grow,
656              &from.flex_grow,
657              &to.flex_grow,
658              FlexGrow(0.0),
659              FlexGrow(0.0),
660              interpolation_context,
661            );
662          }
663
664          if animated_properties.contains(&LonghandId::FlexShrink) {
665            interpolate_option_with_missing(
666              &mut self.flex_shrink,
667              &from.flex_shrink,
668              &to.flex_shrink,
669              FlexGrow(1.0),
670              FlexGrow(1.0),
671              interpolation_context,
672            );
673          }
674
675          if animated_properties.contains(&LonghandId::WebkitTextStrokeWidth) {
676            interpolate_option_with_missing(
677              &mut self.webkit_text_stroke_width,
678              &from.webkit_text_stroke_width,
679              &to.webkit_text_stroke_width,
680              Length::zero(),
681              Length::zero(),
682              interpolation_context,
683            );
684          }
685
686          if animated_properties.contains(&LonghandId::WebkitTextStrokeColor) {
687            interpolate_option_with_missing(
688              &mut self.webkit_text_stroke_color,
689              &from.webkit_text_stroke_color,
690              &to.webkit_text_stroke_color,
691              ColorInput::CurrentColor,
692              ColorInput::CurrentColor,
693              interpolation_context,
694            );
695          }
696
697          if animated_properties.contains(&LonghandId::WebkitTextFillColor) {
698            interpolate_option_with_missing(
699              &mut self.webkit_text_fill_color,
700              &from.webkit_text_fill_color,
701              &to.webkit_text_fill_color,
702              from.color,
703              to.color,
704              interpolation_context,
705            );
706          }
707        }
708      }
709
710      impl StyleDeclaration {
711        $(
712          /// Returns a declaration for this property.
713          pub fn $longhand(value: $longhand_ty) -> Self {
714            Self::[<$longhand:camel>](value)
715          }
716        )*
717        $(
718          /// Returns a declaration for this property.
719          pub fn $transient(value: $transient_ty) -> Self {
720            Self::[<$transient:camel>](value)
721          }
722        )*
723
724        pub fn longhand_id(&self) -> LonghandId {
725          match self {
726            $(Self::[<$longhand:camel>](..) => LonghandId::[<$longhand:camel>],)*
727            $(Self::[<$transient:camel>](..) => LonghandId::[<$transient:camel>],)*
728            Self::CustomProperty(..) | Self::Deferred(..) => {
729              unreachable!("custom and deferred declarations do not map to a single longhand")
730            }
731            Self::CssWideKeyword(id, _) => *id,
732          }
733        }
734
735        pub(crate) fn affected_longhands(&self) -> PropertyMask {
736          match self {
737            Self::CssWideKeyword(id, _) => [*id].into_iter().collect(),
738            Self::CustomProperty(..) => PropertyMask::default(),
739            Self::Deferred(deferred) => deferred.property.target_longhands(),
740            _ => [self.longhand_id()].into_iter().collect(),
741          }
742        }
743
744        pub fn apply_with_parent(
745          self,
746          style: &mut ComputedStyle,
747          parent: &ComputedStyle,
748        ) {
749          let is_rtl = style.direction == Direction::Rtl;
750          match self {
751            Self::CssWideKeyword(property, keyword) => {
752              match property {
753                $(
754                  LonghandId::[<$longhand:camel>] => {
755                    style.$longhand = match keyword {
756                      CssWideKeyword::Initial => Default::default(),
757                      CssWideKeyword::Inherit => parent.$longhand.to_owned(),
758                      CssWideKeyword::Unset => define_inherited_default!(parent.$longhand $(, $longhand_inherit)?),
759                    };
760                  }
761                )*
762                $(
763                  LonghandId::[<$transient:camel>] => {
764                    let target = if is_rtl { &mut style.$transient_rtl } else { &mut style.$transient_ltr };
765                    *target = match keyword {
766                      CssWideKeyword::Initial | CssWideKeyword::Unset => Default::default(),
767                      CssWideKeyword::Inherit => {
768                        if parent.direction == Direction::Rtl {
769                          parent.$transient_rtl.to_owned()
770                        } else {
771                          parent.$transient_ltr.to_owned()
772                        }
773                      }
774                    };
775                  }
776                )*
777              }
778            }
779            Self::CustomProperty(name, value) => {
780              style.custom_properties.insert(name, value);
781            }
782            Self::Deferred(deferred) => {
783              apply_deferred_declaration(style, Some(parent), &deferred);
784            }
785            $(Self::[<$longhand:camel>](value) => style.$longhand = value,)*
786            $(
787              Self::[<$transient:camel>](value) => {
788                if is_rtl { style.$transient_rtl = value } else { style.$transient_ltr = value }
789              }
790            )*
791          }
792        }
793
794        pub fn apply_to_computed(&self, style: &mut ComputedStyle) {
795          let is_rtl = style.direction == Direction::Rtl;
796          match self {
797            Self::CssWideKeyword(property, keyword) => match keyword {
798              CssWideKeyword::Initial => match property {
799                $(
800                  LonghandId::[<$longhand:camel>] => {
801                    style.$longhand = Default::default();
802                  }
803                )*
804                $(
805                  LonghandId::[<$transient:camel>] => {
806                    if is_rtl { style.$transient_rtl = Default::default() }
807                    else { style.$transient_ltr = Default::default() }
808                  }
809                )*
810              },
811              CssWideKeyword::Inherit | CssWideKeyword::Unset => {}
812            },
813            Self::CustomProperty(name, value) => {
814              style
815                .custom_properties
816                .insert(name.to_owned(), value.to_owned());
817            }
818            Self::Deferred(deferred) => apply_deferred_declaration(style, None, deferred),
819            $(Self::[<$longhand:camel>](value) => style.$longhand.clone_from(value),)*
820            $(
821              Self::[<$transient:camel>](value) => {
822                if is_rtl { style.$transient_rtl.clone_from(value) }
823                else { style.$transient_ltr.clone_from(value) }
824              }
825            )*
826          }
827        }
828
829        pub fn merge_into_ref(&self, style: &mut Style) {
830          style.declarations.push(self.to_owned(), false);
831        }
832
833      }
834
835      impl crate::style::properties::ToCss for StyleDeclaration {
836        fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
837          match self {
838            $(
839              Self::[<$longhand:camel>](value) => {
840                let name = stringify!($longhand).replace("_", "-");
841                if name.starts_with("webkit-") {
842                  dest.write_str("-")?;
843                }
844                dest.write_str(&name)?;
845                dest.write_str(": ")?;
846                value.to_css(dest)?;
847                dest.write_str(";")
848              }
849            )*
850            $(
851              Self::[<$transient:camel>](value) => {
852                let name = stringify!($transient).replace("_", "-");
853                dest.write_str(&name)?;
854                dest.write_str(": ")?;
855                value.to_css(dest)?;
856                dest.write_str(";")
857              }
858            )*
859            Self::CustomProperty(name, value) => {
860              write!(dest, "{}: {};", name, value)
861            }
862            Self::Deferred(deferred) => {
863              let name = match deferred.property {
864                PropertyId::Longhand(id) => format!("{:?}", id),
865                PropertyId::Shorthand(id) => format!("{:?}", id),
866                _ => return Ok(()),
867              };
868              write!(dest, "{}: {};", to_kebab_case(&name), deferred.specified_value)
869            }
870            Self::CssWideKeyword(id, keyword) => {
871              let name = format!("{:?}", id);
872              let keyword_str = match keyword {
873                CssWideKeyword::Initial => "initial",
874                CssWideKeyword::Inherit => "inherit",
875                CssWideKeyword::Unset => "unset",
876              };
877              write!(dest, "{}: {};", to_kebab_case(&name), keyword_str)
878            }
879          }
880        }
881      }
882
883    }
884  };
885}
886
887define_style! {
888  longhands {
889    box_sizing: BoxSizing,
890    opacity: PercentageNumber,
891    animation_name: AnimationNames,
892    animation_duration: AnimationDurations,
893    animation_delay: AnimationDurations,
894    animation_timing_function: AnimationTimingFunctions,
895    animation_iteration_count: AnimationIterationCounts,
896    animation_direction: AnimationDirections,
897    animation_fill_mode: AnimationFillModes,
898    animation_play_state: AnimationPlayStates,
899    display: Display,
900    width: Length,
901    height: Length,
902    max_width: Length,
903    max_height: Length,
904    min_width: Length,
905    min_height: Length,
906    aspect_ratio: AspectRatio,
907    padding_top: LengthDefaultsToZero,
908    padding_right: LengthDefaultsToZero,
909    padding_bottom: LengthDefaultsToZero,
910    padding_left: LengthDefaultsToZero,
911    margin_top: LengthDefaultsToZero,
912    margin_right: LengthDefaultsToZero,
913    margin_bottom: LengthDefaultsToZero,
914    margin_left: LengthDefaultsToZero,
915    top: Length,
916    right: Length,
917    bottom: Length,
918    left: Length,
919    flex_direction: FlexDirection,
920    justify_self: AlignItems,
921    justify_content: JustifyContent,
922    align_content: JustifyContent,
923    justify_items: AlignItems,
924    align_items: AlignItems,
925    align_self: AlignItems,
926    flex_wrap: FlexWrap,
927    flex_basis: Option<Length>,
928    order: Order,
929    z_index: ZIndex,
930    position: Position,
931    rotate: Option<Angle>,
932    scale: SpacePair<PercentageNumber>,
933    translate: SpacePair<Length>,
934    transform: Option<Transforms>,
935    transform_origin: TransformOrigin,
936    mask_image: Option<BackgroundImages>,
937    mask_size: BackgroundSizes,
938    mask_position: BackgroundPositions,
939    mask_repeat: BackgroundRepeats,
940    column_gap: LengthDefaultsToZero,
941    row_gap: LengthDefaultsToZero,
942    flex_grow: Option<FlexGrow>,
943    flex_shrink: Option<FlexGrow>,
944    border_top_left_radius: SpacePair<LengthDefaultsToZero>,
945    border_top_right_radius: SpacePair<LengthDefaultsToZero>,
946    border_bottom_right_radius: SpacePair<LengthDefaultsToZero>,
947    border_bottom_left_radius: SpacePair<LengthDefaultsToZero>,
948    border_top_width: Length,
949    border_right_width: Length,
950    border_bottom_width: Length,
951    border_left_width: Length,
952    border_top_style: BorderStyle,
953    border_right_style: BorderStyle,
954    border_bottom_style: BorderStyle,
955    border_left_style: BorderStyle,
956    border_top_color: ColorInput,
957    border_right_color: ColorInput,
958    border_bottom_color: ColorInput,
959    border_left_color: ColorInput,
960    outline_width: Length,
961    outline_style: BorderStyle,
962    outline_color: ColorInput,
963    outline_offset: Length,
964    object_fit: ObjectFit,
965    overflow_x: Overflow,
966    overflow_y: Overflow,
967    object_position: ObjectPosition,
968    background_image: Option<BackgroundImages>,
969    background_position: BackgroundPositions,
970    background_size: BackgroundSizes,
971    background_repeat: BackgroundRepeats,
972    background_blend_mode: BlendModes,
973    background_color: ColorDefaultsToTransparent,
974    background_clip: BackgroundClip,
975    box_shadow: Option<BoxShadows>,
976    grid_auto_columns: Option<GridTrackSizes>,
977    grid_auto_rows: Option<GridTrackSizes>,
978    grid_auto_flow: GridAutoFlow,
979    grid_row_start: GridPlacement,
980    grid_row_end: GridPlacement,
981    grid_column_start: GridPlacement,
982    grid_column_end: GridPlacement,
983    grid_template_columns: Option<GridTemplateComponents>,
984    grid_template_rows: Option<GridTemplateComponents>,
985    grid_template_areas: Option<GridTemplateAreas>,
986    text_overflow: TextOverflow,
987    text_fit: TextFit,
988    text_transform: TextTransform where inherit = true,
989    font_style: FontStyle where inherit = true,
990    font_stretch: FontStretch where inherit = true,
991    color: ColorInput where inherit = true,
992    filter: Filters,
993    backdrop_filter: Filters,
994    font_size: FontSize where inherit = true,
995    font_family: FontFamily where inherit = true,
996    line_height: LineHeight where inherit = true,
997    font_weight: FontWeight where inherit = true,
998    font_variation_settings: FontVariationSettings where inherit = true,
999    font_feature_settings: FontFeatureSettings where inherit = true,
1000    font_synthesis_weight: FontSynthesic where inherit = true,
1001    font_synthesis_style: FontSynthesic where inherit = true,
1002    line_clamp: Option<LineClamp> where inherit = true,
1003    text_align: TextAlign where inherit = true,
1004    webkit_text_stroke_width: Option<LengthDefaultsToZero> where inherit = true,
1005    webkit_text_stroke_color: Option<ColorInput> where inherit = true,
1006    webkit_text_fill_color: Option<ColorInput> where inherit = true,
1007    stroke_linejoin: LineJoin where inherit = true,
1008    text_shadow: Option<TextShadows> where inherit = true,
1009    text_decoration_line: Option<TextDecorationLines>,
1010    text_decoration_style: TextDecorationStyle,
1011    text_decoration_color: ColorInput,
1012    text_decoration_thickness: TextDecorationThickness,
1013    text_decoration_skip_ink: TextDecorationSkipInk where inherit = true,
1014    text_indent: TextIndent where inherit = true,
1015    letter_spacing: Length where inherit = true,
1016    word_spacing: Length where inherit = true,
1017    image_rendering: ImageScalingAlgorithm where inherit = true,
1018    overflow_wrap: OverflowWrap where inherit = true,
1019    word_break: WordBreak where inherit = true,
1020    clip_path: Option<BasicShape>,
1021    clip_rule: FillRule where inherit = true,
1022    white_space_collapse: WhiteSpaceCollapse where inherit = true,
1023    text_wrap_mode: TextWrapMode where inherit = true,
1024    text_wrap_style: TextWrapStyle where inherit = true,
1025    direction: Direction where inherit = true,
1026    float: Float,
1027    clear: Clear,
1028    isolation: Isolation,
1029    mix_blend_mode: BlendMode,
1030    visibility: Visibility where inherit = true,
1031    vertical_align: VerticalAlign,
1032    content: ContentValue,
1033  }
1034  transient_longhands {
1035    margin_inline_start: LengthDefaultsToZero => (margin_left, margin_right),
1036    margin_inline_end: LengthDefaultsToZero => (margin_right, margin_left),
1037    padding_inline_start: LengthDefaultsToZero => (padding_left, padding_right),
1038    padding_inline_end: LengthDefaultsToZero => (padding_right, padding_left),
1039  }
1040  shorthands {
1041    animation: Animations => [AnimationName, AnimationDuration, AnimationDelay, AnimationTimingFunction, AnimationIterationCount, AnimationDirection, AnimationFillMode, AnimationPlayState] |value, target| {
1042      target.push(StyleDeclaration::animation_duration(value.iter().map(|animation| animation.duration).collect()));
1043      target.push(StyleDeclaration::animation_delay(value.iter().map(|animation| animation.delay).collect()));
1044      target.push(StyleDeclaration::animation_timing_function(
1045        value
1046          .iter()
1047          .map(|animation| animation.timing_function)
1048          .collect(),
1049      ));
1050      target.push(StyleDeclaration::animation_iteration_count(
1051        value
1052          .iter()
1053          .map(|animation| animation.iteration_count)
1054          .collect(),
1055      ));
1056      target.push(StyleDeclaration::animation_direction(
1057        value.iter().map(|animation| animation.direction).collect(),
1058      ));
1059      target.push(StyleDeclaration::animation_fill_mode(
1060        value.iter().map(|animation| animation.fill_mode).collect(),
1061      ));
1062      target.push(StyleDeclaration::animation_play_state(
1063        value.iter().map(|animation| animation.play_state).collect(),
1064      ));
1065      target.push(StyleDeclaration::animation_name(value.into_iter().map(|animation| animation.name).collect()));
1066    },
1067    padding: Sides<LengthDefaultsToZero> => [PaddingTop, PaddingRight, PaddingBottom, PaddingLeft] |value, target| {
1068      push_four_side_declarations!(
1069        target,
1070        value.0,
1071        padding_top,
1072        padding_right,
1073        padding_bottom,
1074        padding_left
1075      );
1076    },
1077    padding_inline: SpacePair<LengthDefaultsToZero> => [PaddingInlineStart, PaddingInlineEnd] |value, target| {
1078      push_axis_declarations!(target, value, padding_inline_start, padding_inline_end);
1079    },
1080    padding_block: SpacePair<LengthDefaultsToZero> => [PaddingTop, PaddingBottom] |value, target| {
1081      push_axis_declarations!(target, value, padding_top, padding_bottom);
1082    },
1083    margin: Sides<LengthDefaultsToZero> => [MarginTop, MarginRight, MarginBottom, MarginLeft] |value, target| {
1084      push_four_side_declarations!(
1085        target,
1086        value.0,
1087        margin_top,
1088        margin_right,
1089        margin_bottom,
1090        margin_left
1091      );
1092    },
1093    margin_inline: SpacePair<LengthDefaultsToZero> => [MarginInlineStart, MarginInlineEnd] |value, target| {
1094      push_axis_declarations!(target, value, margin_inline_start, margin_inline_end);
1095    },
1096    margin_block: SpacePair<LengthDefaultsToZero> => [MarginTop, MarginBottom] |value, target| {
1097      push_axis_declarations!(target, value, margin_top, margin_bottom);
1098    },
1099    inset: Sides<Length> => [Top, Right, Bottom, Left] |value, target| {
1100      push_four_side_declarations!(target, value.0, top, right, bottom, left);
1101    },
1102    inset_inline: SpacePair<Length> => [Left, Right] |value, target| {
1103      push_axis_declarations!(target, value, left, right);
1104    },
1105    inset_block: SpacePair<Length> => [Top, Bottom] |value, target| {
1106      push_axis_declarations!(target, value, top, bottom);
1107    },
1108    mask: Backgrounds => [MaskImage, MaskPosition, MaskSize, MaskRepeat] |value, target| {
1109      target.push(StyleDeclaration::mask_position(
1110        value.iter().map(|background| background.position).collect(),
1111      ));
1112      target.push(StyleDeclaration::mask_size(
1113        value.iter().map(|background| background.size).collect(),
1114      ));
1115      target.push(StyleDeclaration::mask_repeat(
1116        value.iter().map(|background| background.repeat).collect(),
1117      ));
1118      target.push(StyleDeclaration::mask_image(Some(
1119        value
1120          .into_iter()
1121          .map(|background| background.image)
1122          .collect(),
1123      )));
1124    },
1125    gap: SpacePair<LengthDefaultsToZero> => [RowGap, ColumnGap] |value, target| {
1126      push_axis_declarations!(target, value, row_gap, column_gap);
1127    },
1128    flex_flow: FlexFlow => [FlexDirection, FlexWrap] |value, target| {
1129      target.push(StyleDeclaration::flex_direction(value.direction));
1130      target.push(StyleDeclaration::flex_wrap(value.wrap));
1131    },
1132    flex: Option<Flex> => [FlexGrow, FlexShrink, FlexBasis] |value, target| {
1133      target.push(StyleDeclaration::flex_grow(
1134        value.map(|value| FlexGrow(value.grow)),
1135      ));
1136      target.push(StyleDeclaration::flex_shrink(
1137        value.map(|value| FlexGrow(value.shrink)),
1138      ));
1139      target.push(StyleDeclaration::flex_basis(value.map(|value| value.basis)));
1140    },
1141    place_items: PlaceItems => [AlignItems, JustifyItems] |value, target| {
1142      target.push(StyleDeclaration::align_items(value.align));
1143      target.push(StyleDeclaration::justify_items(value.justify));
1144    },
1145    place_content: PlaceContent => [AlignContent, JustifyContent] |value, target| {
1146      target.push(StyleDeclaration::align_content(value.align));
1147      target.push(StyleDeclaration::justify_content(value.justify));
1148    },
1149    place_self: PlaceSelf => [AlignSelf, JustifySelf] |value, target| {
1150      target.push(StyleDeclaration::align_self(value.align));
1151      target.push(StyleDeclaration::justify_self(value.justify));
1152    },
1153    grid_column: GridLine => [GridColumnStart, GridColumnEnd] |value, target| {
1154      target.push(StyleDeclaration::grid_column_start(value.start));
1155      target.push(StyleDeclaration::grid_column_end(value.end));
1156    },
1157    grid_row: GridLine => [GridRowStart, GridRowEnd] |value, target| {
1158      target.push(StyleDeclaration::grid_row_start(value.start));
1159      target.push(StyleDeclaration::grid_row_end(value.end));
1160    },
1161    grid_area: GridArea => [GridRowStart, GridColumnStart, GridRowEnd, GridColumnEnd] |value, target| {
1162      target.push(StyleDeclaration::grid_row_start(value.row_start));
1163      target.push(StyleDeclaration::grid_column_start(value.column_start));
1164      target.push(StyleDeclaration::grid_row_end(value.row_end));
1165      target.push(StyleDeclaration::grid_column_end(value.column_end));
1166    },
1167    border_radius: BorderRadius => [BorderTopLeftRadius, BorderTopRightRadius, BorderBottomRightRadius, BorderBottomLeftRadius] |value, target| {
1168      push_four_side_declarations!(
1169        target,
1170        value.0.0,
1171        border_top_left_radius,
1172        border_top_right_radius,
1173        border_bottom_right_radius,
1174        border_bottom_left_radius
1175      );
1176    },
1177    border_width: Sides<Length> => [BorderTopWidth, BorderRightWidth, BorderBottomWidth, BorderLeftWidth] |value, target| {
1178      push_four_side_declarations!(
1179        target,
1180        value.0,
1181        border_top_width,
1182        border_right_width,
1183        border_bottom_width,
1184        border_left_width
1185      );
1186    },
1187    border_inline_width: SpacePair<Length> => [BorderLeftWidth, BorderRightWidth] |value, target| {
1188      push_axis_declarations!(
1189        target,
1190        value,
1191        border_left_width,
1192        border_right_width
1193      );
1194    },
1195    border_block_width: SpacePair<Length> => [BorderTopWidth, BorderBottomWidth] |value, target| {
1196      push_axis_declarations!(
1197        target,
1198        value,
1199        border_top_width,
1200        border_bottom_width
1201      );
1202    },
1203    border: Border => [BorderTopWidth, BorderRightWidth, BorderBottomWidth, BorderLeftWidth, BorderTopStyle, BorderRightStyle, BorderBottomStyle, BorderLeftStyle, BorderTopColor, BorderRightColor, BorderBottomColor, BorderLeftColor] |value, target| {
1204      target.push(StyleDeclaration::border_top_width(value.width));
1205      target.push(StyleDeclaration::border_right_width(value.width));
1206      target.push(StyleDeclaration::border_bottom_width(value.width));
1207      target.push(StyleDeclaration::border_left_width(value.width));
1208      target.push(StyleDeclaration::border_top_style(value.style));
1209      target.push(StyleDeclaration::border_right_style(value.style));
1210      target.push(StyleDeclaration::border_bottom_style(value.style));
1211      target.push(StyleDeclaration::border_left_style(value.style));
1212      target.push(StyleDeclaration::border_top_color(value.color));
1213      target.push(StyleDeclaration::border_right_color(value.color));
1214      target.push(StyleDeclaration::border_bottom_color(value.color));
1215      target.push(StyleDeclaration::border_left_color(value.color));
1216    },
1217    border_top: Border => [BorderTopWidth, BorderTopStyle, BorderTopColor] |value, target| {
1218      target.push(StyleDeclaration::border_top_width(value.width));
1219      target.push(StyleDeclaration::border_top_style(value.style));
1220      target.push(StyleDeclaration::border_top_color(value.color));
1221    },
1222    border_right: Border => [BorderRightWidth, BorderRightStyle, BorderRightColor] |value, target| {
1223      target.push(StyleDeclaration::border_right_width(value.width));
1224      target.push(StyleDeclaration::border_right_style(value.style));
1225      target.push(StyleDeclaration::border_right_color(value.color));
1226    },
1227    border_bottom: Border => [BorderBottomWidth, BorderBottomStyle, BorderBottomColor] |value, target| {
1228      target.push(StyleDeclaration::border_bottom_width(value.width));
1229      target.push(StyleDeclaration::border_bottom_style(value.style));
1230      target.push(StyleDeclaration::border_bottom_color(value.color));
1231    },
1232    border_left: Border => [BorderLeftWidth, BorderLeftStyle, BorderLeftColor] |value, target| {
1233      target.push(StyleDeclaration::border_left_width(value.width));
1234      target.push(StyleDeclaration::border_left_style(value.style));
1235      target.push(StyleDeclaration::border_left_color(value.color));
1236    },
1237    border_style: Sides<BorderStyle> => [BorderTopStyle, BorderRightStyle, BorderBottomStyle, BorderLeftStyle] |value, target| {
1238      push_four_side_declarations!(
1239        target,
1240        value.0,
1241        border_top_style,
1242        border_right_style,
1243        border_bottom_style,
1244        border_left_style
1245      );
1246    },
1247    border_color: Sides<ColorInput> => [BorderTopColor, BorderRightColor, BorderBottomColor, BorderLeftColor] |value, target| {
1248      push_four_side_declarations!(
1249        target,
1250        value.0,
1251        border_top_color,
1252        border_right_color,
1253        border_bottom_color,
1254        border_left_color
1255      );
1256    },
1257    outline: Border => [OutlineWidth, OutlineStyle, OutlineColor] |value, target| {
1258      target.push(StyleDeclaration::outline_width(value.width));
1259      target.push(StyleDeclaration::outline_style(value.style));
1260      target.push(StyleDeclaration::outline_color(value.color));
1261    },
1262    overflow: SpacePair<Overflow> => [OverflowX, OverflowY] |value, target| {
1263      push_axis_declarations!(target, value, overflow_x, overflow_y);
1264    },
1265    background: Backgrounds => [BackgroundImage, BackgroundPosition, BackgroundSize, BackgroundRepeat, BackgroundBlendMode, BackgroundColor, BackgroundClip] |value, target| {
1266      target.push(StyleDeclaration::background_position(
1267        value.iter().map(|background| background.position).collect(),
1268      ));
1269      target.push(StyleDeclaration::background_size(
1270        value.iter().map(|background| background.size).collect(),
1271      ));
1272      target.push(StyleDeclaration::background_repeat(
1273        value.iter().map(|background| background.repeat).collect(),
1274      ));
1275      target.push(StyleDeclaration::background_blend_mode(
1276        value
1277          .iter()
1278          .map(|background| background.blend_mode)
1279          .collect(),
1280      ));
1281      target.push(StyleDeclaration::background_color(
1282        value
1283          .iter()
1284          .filter_map(|background| background.color)
1285          .next_back()
1286          .unwrap_or_default(),
1287      ));
1288      target.push(StyleDeclaration::background_clip(
1289        value
1290          .last()
1291          .map(|background| background.clip)
1292          .unwrap_or_default(),
1293      ));
1294      target.push(StyleDeclaration::background_image(Some(
1295        value
1296          .into_iter()
1297          .map(|background| background.image)
1298          .collect(),
1299      )));
1300    },
1301    font_synthesis: FontSynthesis where inherit = true => [FontSynthesisWeight, FontSynthesisStyle] |value, target| {
1302      target.push(StyleDeclaration::font_synthesis_weight(value.weight));
1303      target.push(StyleDeclaration::font_synthesis_style(value.style));
1304    },
1305    webkit_text_stroke: Option<TextStroke> where inherit = true => [WebkitTextStrokeWidth, WebkitTextStrokeColor] |value, target| {
1306      target.push(StyleDeclaration::webkit_text_stroke_width(
1307        value.map(|value| value.width),
1308      ));
1309      target.push(StyleDeclaration::webkit_text_stroke_color(
1310        value.and_then(|value| value.color),
1311      ));
1312    },
1313    text_decoration: TextDecoration => [TextDecorationLine, TextDecorationStyle, TextDecorationColor, TextDecorationThickness] |value, target| {
1314      target.push(StyleDeclaration::text_decoration_line(Some(value.line)));
1315      target.push(StyleDeclaration::text_decoration_style(value.style));
1316      target.push(StyleDeclaration::text_decoration_color(value.color));
1317      target.push(StyleDeclaration::text_decoration_thickness(value.thickness));
1318    },
1319    white_space: WhiteSpace where inherit = true => [TextWrapMode, WhiteSpaceCollapse] |value, target| {
1320      target.push(StyleDeclaration::text_wrap_mode(value.text_wrap_mode));
1321      target.push(StyleDeclaration::white_space_collapse(
1322        value.white_space_collapse,
1323      ));
1324    },
1325    text_wrap: TextWrap where inherit = true => [TextWrapMode, TextWrapStyle] |value, target| {
1326      target.push(StyleDeclaration::text_wrap_mode(value.mode));
1327      target.push(StyleDeclaration::text_wrap_style(value.style));
1328    },
1329  }
1330}
1331
1332/// CSS-wide keywords that can target any longhand declaration.
1333#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1334pub(crate) enum CssWideKeyword {
1335  /// Reset the targeted longhand to its initial value.
1336  Initial,
1337  /// Inherit the targeted longhand from the parent computed style.
1338  Inherit,
1339  /// Apply CSS `unset` semantics to the targeted longhand.
1340  Unset,
1341}
1342
1343impl<'i> FromCss<'i> for CssWideKeyword {
1344  fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
1345    let location = input.current_source_location();
1346    let ident = input.expect_ident_cloned()?;
1347
1348    match_ignore_ascii_case! { ident.as_ref(),
1349      "initial" => Ok(Self::Initial),
1350      "inherit" => Ok(Self::Inherit),
1351      "unset" => Ok(Self::Unset),
1352      _ => Err(unexpected_token!(location, &Token::Ident(ident))),
1353    }
1354  }
1355
1356  const VALID_TOKENS: &'static [CssToken] = &[
1357    CssToken::Keyword("initial"),
1358    CssToken::Keyword("inherit"),
1359    CssToken::Keyword("unset"),
1360  ];
1361}
1362
1363#[derive(Debug, Clone, Default, PartialEq, Eq)]
1364pub struct DeclarationImportance {
1365  pub(crate) longhands: PropertyMask,
1366  pub custom_properties: SmallVec<[Box<str>; 1]>,
1367}
1368
1369impl DeclarationImportance {
1370  pub fn is_empty(&self) -> bool {
1371    self.custom_properties.is_empty() && self.longhands.iter().next().is_none()
1372  }
1373
1374  pub fn insert_declaration(&mut self, declaration: &StyleDeclaration) {
1375    self
1376      .longhands
1377      .extend(declaration.affected_longhands().iter());
1378
1379    if let StyleDeclaration::CustomProperty(name, _) = declaration {
1380      self.insert_custom_property(name);
1381    }
1382  }
1383
1384  pub fn append(&mut self, other: &mut Self) {
1385    self.longhands.append(&mut other.longhands);
1386
1387    for name in other.custom_properties.drain(..) {
1388      if self
1389        .custom_properties
1390        .iter()
1391        .all(|existing| existing != &name)
1392      {
1393        self.custom_properties.push(name);
1394      }
1395    }
1396  }
1397
1398  fn insert_custom_property(&mut self, name: &str) {
1399    if self
1400      .custom_properties
1401      .iter()
1402      .all(|existing| existing.as_ref() != name)
1403    {
1404      self.custom_properties.push(name.into());
1405    }
1406  }
1407}
1408
1409impl<T> From<T> for DeclarationImportance
1410where
1411  T: IntoIterator<Item = LonghandId>,
1412{
1413  fn from(value: T) -> Self {
1414    Self {
1415      longhands: value.into_iter().collect(),
1416      custom_properties: SmallVec::new(),
1417    }
1418  }
1419}
1420
1421/// Ordered specified declarations plus the set of important properties.
1422#[derive(Debug, Clone, Default, PartialEq)]
1423pub struct StyleDeclarationBlock {
1424  /// Ordered declarations in source order.
1425  pub declarations: SmallVec<[StyleDeclaration; 8]>,
1426  /// Properties that were marked with `!important`.
1427  pub importance: DeclarationImportance,
1428}
1429
1430impl StyleDeclarationBlock {
1431  fn from_parsed_declarations(declarations: ParsedDeclarations, important: bool) -> Self {
1432    let mut block = Self::default();
1433    block.append_parsed_declarations(declarations, important);
1434    block
1435  }
1436
1437  /// Appends a declaration and records whether it was important.
1438  pub fn push(&mut self, declaration: StyleDeclaration, important: bool) {
1439    if important {
1440      self.importance.insert_declaration(&declaration);
1441    }
1442    self.declarations.push(declaration);
1443  }
1444
1445  fn append_parsed_declarations(&mut self, declarations: ParsedDeclarations, important: bool) {
1446    for declaration in declarations {
1447      self.push(declaration, important);
1448    }
1449  }
1450
1451  pub fn append(&mut self, mut other: Self) {
1452    self.importance.append(&mut other.importance);
1453    self.declarations.extend(other.declarations);
1454  }
1455
1456  /// Iterates over the declarations in source order.
1457  pub fn iter(&self) -> std::slice::Iter<'_, StyleDeclaration> {
1458    self.declarations.iter()
1459  }
1460
1461  /// Collects resource URLs referenced by declarations in this block.
1462  pub fn resource_urls(&self) -> impl Iterator<Item = &str> {
1463    fn background_image_url(image: &BackgroundImage) -> Option<&str> {
1464      if let BackgroundImage::Url(url) = image {
1465        Some(url.as_ref())
1466      } else {
1467        None
1468      }
1469    }
1470
1471    self
1472      .iter()
1473      .flat_map(|declaration| -> Box<dyn Iterator<Item = &str> + '_> {
1474        match declaration {
1475          StyleDeclaration::BackgroundImage(Some(images))
1476          | StyleDeclaration::MaskImage(Some(images)) => {
1477            Box::new(images.iter().filter_map(background_image_url))
1478          }
1479          StyleDeclaration::Content(ContentValue::Items(items)) => {
1480            Box::new(items.iter().filter_map(|item| match item {
1481              ContentItem::Image(image) => background_image_url(image.as_ref()),
1482              _ => None,
1483            }))
1484          }
1485          _ => Box::new(std::iter::empty()),
1486        }
1487      })
1488  }
1489
1490  pub fn parse<'i>(name: &str, input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
1491    parse_style_declaration(name, input)
1492  }
1493}
1494
1495impl FromStr for StyleDeclarationBlock {
1496  type Err = StyleDeclarationBlockParseError;
1497
1498  fn from_str(input: &str) -> Result<Self, Self::Err> {
1499    let mut parser_input = ParserInput::new(input);
1500    let mut parser = Parser::new(&mut parser_input);
1501    let mut declaration_parser = StyleDeclarationParser;
1502    let mut block = Self::default();
1503
1504    for result in RuleBodyParser::new(&mut parser, &mut declaration_parser) {
1505      match result {
1506        Ok(declarations) => block.append(declarations),
1507        Err((error, context)) => {
1508          return Err(StyleDeclarationBlockParseError::InvalidDeclarationBlock {
1509            input: input.to_owned(),
1510            context: context.to_owned(),
1511            reason: format!("{error:?}"),
1512          });
1513        }
1514      }
1515    }
1516
1517    Ok(block)
1518  }
1519}
1520
1521pub(crate) fn to_kebab_case(s: &str) -> String {
1522  let mut result = String::new();
1523  for (i, c) in s.chars().enumerate() {
1524    if c.is_uppercase() {
1525      if i > 0 {
1526        result.push('-');
1527      }
1528      result.push(c.to_ascii_lowercase());
1529    } else {
1530      result.push(c);
1531    }
1532  }
1533  if result.starts_with("webkit-") {
1534    result.insert(0, '-');
1535  }
1536  result
1537}
1538
1539#[cfg(test)]
1540#[path = "stylesheets_tests.rs"]
1541mod tests;