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 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?,
198 )]),
199 )*
200 $(
201 Self::[<$transient:camel>] => Ok(smallvec?,
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?,
220 )]),
221 )*
222 $(
223 Self::[<$transient:camel>] => Ok(smallvec?,
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 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 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 #[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 pub fn with(self, declaration: StyleDeclaration) -> Self {
482 self.with_declarations([declaration], false)
483 }
484
485 $(
486 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 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 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 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 #[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 #[allow(private_interfaces)]
581 #[derive(Debug, Clone, PartialEq)]
582 pub enum StyleDeclaration {
583 $(
584 [<$longhand:camel>]($longhand_ty),
586 )*
587 $(
588 [<$transient:camel>]($transient_ty),
590 )*
591 CustomProperty(String, String),
593 Deferred(DeferredDeclaration),
595 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 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 pub fn $longhand(value: $longhand_ty) -> Self {
714 Self::[<$longhand:camel>](value)
715 }
716 )*
717 $(
718 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1334pub(crate) enum CssWideKeyword {
1335 Initial,
1337 Inherit,
1339 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#[derive(Debug, Clone, Default, PartialEq)]
1423pub struct StyleDeclarationBlock {
1424 pub declarations: SmallVec<[StyleDeclaration; 8]>,
1426 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 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 pub fn iter(&self) -> std::slice::Iter<'_, StyleDeclaration> {
1458 self.declarations.iter()
1459 }
1460
1461 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;