Skip to main content

style/properties_and_values/
value.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Parsing for registered custom properties.
6
7use super::{
8    rule::Descriptors as PropertyDescriptors,
9    syntax::{
10        data_type::DataType, Component as SyntaxComponent, ComponentName, Descriptor, Multiplier,
11    },
12};
13use crate::custom_properties::{AttrTaint, ComputedValue as ComputedPropertyValue};
14use crate::derives::*;
15use crate::parser::{Parse, ParserContext};
16use crate::properties;
17use crate::properties::{CSSWideKeyword, CustomDeclarationValue};
18use crate::stylesheets::{CssRuleType, Origin, UrlExtraData};
19use crate::values::{
20    animated::{self, Animate, Procedure},
21    computed::{self, ToComputedValue},
22    specified, CustomIdent,
23};
24use crate::{Namespace, Prefix};
25use cssparser::{BasicParseErrorKind, ParseErrorKind, Parser as CSSParser, TokenSerializationType};
26use rustc_hash::FxHashMap;
27use selectors::matching::QuirksMode;
28use servo_arc::Arc;
29use smallvec::SmallVec;
30use std::fmt::{self, Write};
31use style_traits::{
32    owned_str::OwnedStr, CssWriter, ParseError as StyleParseError, ParsingMode,
33    PropertySyntaxParseError, StyleParseErrorKind, ToCss,
34};
35
36/// A single component of the computed value.
37pub type ComputedValueComponent = GenericValueComponent<
38    computed::Length,
39    computed::Number,
40    computed::Percentage,
41    computed::LengthPercentage,
42    computed::Color,
43    computed::Image,
44    computed::url::ComputedUrl,
45    computed::Integer,
46    computed::Angle,
47    computed::Time,
48    computed::Resolution,
49    computed::Transform,
50>;
51
52/// A single component of the specified value.
53pub type SpecifiedValueComponent = GenericValueComponent<
54    specified::Length,
55    specified::Number,
56    specified::Percentage,
57    specified::LengthPercentage,
58    specified::Color,
59    specified::Image,
60    specified::url::SpecifiedUrl,
61    specified::Integer,
62    specified::Angle,
63    specified::Time,
64    specified::Resolution,
65    specified::Transform,
66>;
67
68impl<L, N, P, LP, C, Image, U, Integer, A, T, R, Transform>
69    GenericValueComponent<L, N, P, LP, C, Image, U, Integer, A, T, R, Transform>
70{
71    fn serialization_types(&self) -> (TokenSerializationType, TokenSerializationType) {
72        let first_token_type = match self {
73            Self::Length(_) | Self::Angle(_) | Self::Time(_) | Self::Resolution(_) => {
74                TokenSerializationType::Dimension
75            },
76            Self::Number(_) | Self::Integer(_) => TokenSerializationType::Number,
77            Self::Percentage(_) | Self::LengthPercentage(_) => TokenSerializationType::Percentage,
78            Self::Color(_)
79            | Self::Image(_)
80            | Self::Url(_)
81            | Self::TransformFunction(_)
82            | Self::TransformList(_) => TokenSerializationType::Function,
83            Self::CustomIdent(_) => TokenSerializationType::Ident,
84            Self::String(_) => TokenSerializationType::Other,
85        };
86        let last_token_type = if first_token_type == TokenSerializationType::Function {
87            TokenSerializationType::Other
88        } else {
89            first_token_type
90        };
91        (first_token_type, last_token_type)
92    }
93}
94
95/// A generic enum used for both specified value components and computed value components.
96#[derive(
97    Animate, Clone, ToCss, ToComputedValue, ToResolvedValue, Debug, MallocSizeOf, PartialEq, ToShmem,
98)]
99#[animation(no_bound(Image, Url))]
100pub enum GenericValueComponent<
101    Length,
102    Number,
103    Percentage,
104    LengthPercentage,
105    Color,
106    Image,
107    Url,
108    Integer,
109    Angle,
110    Time,
111    Resolution,
112    TransformFunction,
113> {
114    /// A <length> value
115    Length(Length),
116    /// A <number> value
117    Number(Number),
118    /// A <percentage> value
119    Percentage(Percentage),
120    /// A <length-percentage> value
121    LengthPercentage(LengthPercentage),
122    /// A <color> value
123    Color(Color),
124    /// An <image> value
125    #[animation(error)]
126    Image(Image),
127    /// A <url> value
128    #[animation(error)]
129    Url(Url),
130    /// An <integer> value
131    Integer(Integer),
132    /// An <angle> value
133    Angle(Angle),
134    /// A <time> value
135    Time(Time),
136    /// A <resolution> value
137    Resolution(Resolution),
138    /// A <transform-function> value
139    /// TODO(bug 1884606): <transform-function> `none` should not interpolate.
140    TransformFunction(TransformFunction),
141    /// A <custom-ident> value
142    #[animation(error)]
143    CustomIdent(CustomIdent),
144    /// A <transform-list> value, equivalent to <transform-function>+
145    /// TODO(bug 1884606): <transform-list> `none` should not interpolate.
146    TransformList(ComponentList<Self>),
147    /// A <string> value
148    #[animation(error)]
149    String(OwnedStr),
150}
151
152/// A list of component values, including the list's multiplier.
153#[derive(Clone, ToComputedValue, ToResolvedValue, Debug, MallocSizeOf, PartialEq, ToShmem)]
154pub struct ComponentList<Component> {
155    /// Multiplier
156    pub multiplier: Multiplier,
157    /// The list of components contained.
158    pub components: crate::OwnedSlice<Component>,
159}
160
161impl<Component: Animate> Animate for ComponentList<Component> {
162    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
163        if self.multiplier != other.multiplier {
164            return Err(());
165        }
166        let components = animated::lists::by_computed_value::animate(
167            &self.components,
168            &other.components,
169            procedure,
170        )?;
171        Ok(Self {
172            multiplier: self.multiplier,
173            components,
174        })
175    }
176}
177
178impl<Component: ToCss> ToCss for ComponentList<Component> {
179    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
180    where
181        W: Write,
182    {
183        let mut iter = self.components.iter();
184        let Some(first) = iter.next() else {
185            return Ok(());
186        };
187        first.to_css(dest)?;
188
189        // The separator implied by the multiplier for this list.
190        let separator = match self.multiplier {
191            // <https://drafts.csswg.org/cssom-1/#serialize-a-whitespace-separated-list>
192            Multiplier::Space => " ",
193            // <https://drafts.csswg.org/cssom-1/#serialize-a-comma-separated-list>
194            Multiplier::Comma => ", ",
195        };
196        for component in iter {
197            dest.write_str(separator)?;
198            component.to_css(dest)?;
199        }
200        Ok(())
201    }
202}
203
204/// A struct for a single specified registered custom property value that includes its original URL
205/// data so the value can be uncomputed later.
206#[derive(Clone, Debug, MallocSizeOf, ToCss, ToComputedValue, ToResolvedValue, ToShmem)]
207pub struct Value<Component> {
208    /// The registered custom property value.
209    pub(crate) v: ValueInner<Component>,
210    /// The URL data of the registered custom property from before it was computed. This is
211    /// necessary to uncompute registered custom properties.
212    #[css(skip)]
213    url_data: UrlExtraData,
214    /// Flag indicating whether this value is tainted by an attr().
215    #[css(skip)]
216    pub attr_tainted: bool,
217}
218
219impl<Component: PartialEq> PartialEq for Value<Component> {
220    // Ignore the url_data field when comparing values for equality.
221    // attr_tainted is compared so the cascade doesn't treat a tainted
222    // value as equal to an untainted one, which could lose the taint.
223    fn eq(&self, other: &Self) -> bool {
224        self.v == other.v && self.attr_tainted == other.attr_tainted
225    }
226}
227
228impl<Component: Animate> Animate for Value<Component> {
229    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
230        let v = self.v.animate(&other.v, procedure)?;
231        Ok(Value {
232            v,
233            url_data: self.url_data.clone(),
234            attr_tainted: self.attr_tainted,
235        })
236    }
237}
238
239impl<Component> Value<Component> {
240    /// Creates a new registered custom property value.
241    pub fn new(v: ValueInner<Component>, url_data: UrlExtraData) -> Self {
242        Self {
243            v,
244            url_data,
245            attr_tainted: Default::default(),
246        }
247    }
248
249    /// Creates a new registered custom property value presumed to have universal syntax.
250    pub fn universal(var: Arc<ComputedPropertyValue>) -> Self {
251        let attr_tainted = var.is_attr_tainted();
252        let url_data = var.url_data.clone();
253        let v = ValueInner::Universal(var);
254        Self {
255            v,
256            url_data,
257            attr_tainted,
258        }
259    }
260}
261
262impl<L, N, P, LP, C, Image, U, Integer, A, T, R, Transform>
263    Value<GenericValueComponent<L, N, P, LP, C, Image, U, Integer, A, T, R, Transform>>
264where
265    Self: ToCss,
266{
267    fn serialization_types(&self) -> (TokenSerializationType, TokenSerializationType) {
268        match &self.v {
269            ValueInner::Component(component) => component.serialization_types(),
270            ValueInner::Universal(_) => unreachable!(),
271            ValueInner::List(list) => list
272                .components
273                .first()
274                .map_or(Default::default(), |f| f.serialization_types()),
275        }
276    }
277
278    /// Convert to an untyped variable value.
279    pub fn to_variable_value(&self) -> ComputedPropertyValue {
280        if let ValueInner::Universal(ref value) = self.v {
281            return (**value).clone();
282        }
283        let serialization_types = self.serialization_types();
284        ComputedPropertyValue::new(
285            self.to_css_string(),
286            &self.url_data,
287            serialization_types.0,
288            serialization_types.1,
289        )
290    }
291}
292
293/// A specified registered custom property value.
294#[derive(
295    Animate, ToComputedValue, ToResolvedValue, ToCss, Clone, Debug, MallocSizeOf, PartialEq, ToShmem,
296)]
297pub enum ValueInner<Component> {
298    /// A single specified component value whose syntax descriptor component did not have a
299    /// multiplier.
300    Component(Component),
301    /// A specified value whose syntax descriptor was the universal syntax definition.
302    #[animation(error)]
303    Universal(#[ignore_malloc_size_of = "Arc"] Arc<ComputedPropertyValue>),
304    /// A list of specified component values whose syntax descriptor component had a multiplier.
305    List(#[animation(field_bound)] ComponentList<Component>),
306}
307
308/// Specified custom property value.
309pub type SpecifiedValue = Value<SpecifiedValueComponent>;
310
311/// Computed custom property value.
312pub type ComputedValue = Value<ComputedValueComponent>;
313
314impl SpecifiedValue {
315    /// Convert a registered custom property to a Computed custom property value, given input and a
316    /// property registration.
317    pub fn compute<'i, 't>(
318        input: &mut CSSParser<'i, 't>,
319        registration: &PropertyDescriptors,
320        namespaces: Option<&FxHashMap<Prefix, Namespace>>,
321        url_data: &UrlExtraData,
322        context: &computed::Context,
323        allow_computationally_dependent: AllowComputationallyDependent,
324        attr_taint: AttrTaint,
325    ) -> Result<ComputedValue, ()> {
326        debug_assert!(!registration.is_universal(), "Shouldn't be needed");
327        let Some(ref syntax) = registration.syntax else {
328            return Err(());
329        };
330        let Ok(value) = Self::parse(
331            input,
332            syntax,
333            url_data,
334            namespaces,
335            allow_computationally_dependent,
336            attr_taint,
337        ) else {
338            return Err(());
339        };
340
341        Ok(value.to_computed_value(context))
342    }
343
344    /// Parse and validate a registered custom property value according to its syntax descriptor,
345    /// and check for computational independence.
346    pub fn parse<'i, 't>(
347        mut input: &mut CSSParser<'i, 't>,
348        syntax: &Descriptor,
349        url_data: &UrlExtraData,
350        namespaces: Option<&FxHashMap<Prefix, Namespace>>,
351        allow_computationally_dependent: AllowComputationallyDependent,
352        attr_taint: AttrTaint,
353    ) -> Result<Self, StyleParseError<'i>> {
354        if syntax.is_universal() {
355            let parsed = ComputedPropertyValue::parse(&mut input, namespaces, url_data)?;
356            return Ok(Self::new(
357                ValueInner::Universal(Arc::new(parsed)),
358                url_data.clone(),
359            ));
360        }
361
362        let mut values = SmallComponentVec::new();
363        let mut multiplier = None;
364        {
365            let mut parser = Parser::new(syntax, &mut values, &mut multiplier);
366            parser.parse(
367                &mut input,
368                url_data,
369                allow_computationally_dependent,
370                attr_taint,
371            )?;
372        }
373        let v = if let Some(multiplier) = multiplier {
374            ValueInner::List(ComponentList {
375                multiplier,
376                components: values.to_vec().into(),
377            })
378        } else {
379            ValueInner::Component(values[0].clone())
380        };
381        Ok(Self::new(v, url_data.clone()))
382    }
383}
384
385impl ComputedValue {
386    fn to_declared_value(&self) -> properties::CustomDeclarationValue {
387        if let ValueInner::Universal(ref var) = self.v {
388            return properties::CustomDeclarationValue::Unparsed(Arc::clone(var));
389        }
390        properties::CustomDeclarationValue::Parsed(Arc::new(ToComputedValue::from_computed_value(
391            self,
392        )))
393    }
394
395    /// Returns the contained variable value if it exists, otherwise `None`.
396    pub fn as_universal(&self) -> Option<&Arc<ComputedPropertyValue>> {
397        if let ValueInner::Universal(ref var) = self.v {
398            Some(var)
399        } else {
400            None
401        }
402    }
403}
404
405/// Whether the computed value parsing should allow computationaly dependent values like 3em or
406/// var(-foo).
407///
408/// https://drafts.css-houdini.org/css-properties-values-api-1/#computationally-independent
409pub enum AllowComputationallyDependent {
410    /// Only computationally independent values are allowed.
411    No,
412    /// Computationally independent and dependent values are allowed.
413    Yes,
414}
415
416type SmallComponentVec = SmallVec<[SpecifiedValueComponent; 1]>;
417
418struct Parser<'a> {
419    syntax: &'a Descriptor,
420    output: &'a mut SmallComponentVec,
421    output_multiplier: &'a mut Option<Multiplier>,
422}
423
424impl<'a> Parser<'a> {
425    fn new(
426        syntax: &'a Descriptor,
427        output: &'a mut SmallComponentVec,
428        output_multiplier: &'a mut Option<Multiplier>,
429    ) -> Self {
430        Self {
431            syntax,
432            output,
433            output_multiplier,
434        }
435    }
436
437    fn parse<'i, 't>(
438        &mut self,
439        input: &mut CSSParser<'i, 't>,
440        url_data: &UrlExtraData,
441        allow_computationally_dependent: AllowComputationallyDependent,
442        attr_taint: AttrTaint,
443    ) -> Result<(), StyleParseError<'i>> {
444        use self::AllowComputationallyDependent::*;
445        let parsing_mode = match allow_computationally_dependent {
446            No => ParsingMode::DISALLOW_COMPUTATIONALLY_DEPENDENT,
447            Yes => ParsingMode::DEFAULT,
448        };
449        let ref context = ParserContext::new(
450            Origin::Author,
451            url_data,
452            Some(CssRuleType::Style),
453            parsing_mode,
454            QuirksMode::NoQuirks,
455            /* namespaces = */ Default::default(),
456            None,
457            None,
458            attr_taint,
459        );
460        for component in self.syntax.components.iter() {
461            let result = input.try_parse(|input| {
462                input.parse_entirely(|input| {
463                    Self::parse_value(context, input, &component.unpremultiplied())
464                })
465            });
466            let Ok(values) = result else { continue };
467            self.output.extend(values);
468            *self.output_multiplier = component.multiplier();
469            break;
470        }
471        if self.output.is_empty() {
472            return Err(input.new_error(BasicParseErrorKind::EndOfInput));
473        }
474        Ok(())
475    }
476
477    fn parse_value<'i, 't>(
478        context: &ParserContext,
479        input: &mut CSSParser<'i, 't>,
480        component: &SyntaxComponent,
481    ) -> Result<SmallComponentVec, StyleParseError<'i>> {
482        let mut values = SmallComponentVec::new();
483        values.push(Self::parse_component_without_multiplier(
484            context, input, component,
485        )?);
486
487        if let Some(multiplier) = component.multiplier() {
488            loop {
489                let result = Self::expect_multiplier(input, &multiplier);
490                if Self::expect_multiplier_yielded_eof_error(&result) {
491                    break;
492                }
493                result?;
494                values.push(Self::parse_component_without_multiplier(
495                    context, input, component,
496                )?);
497            }
498        }
499        Ok(values)
500    }
501
502    fn parse_component_without_multiplier<'i, 't>(
503        context: &ParserContext,
504        input: &mut CSSParser<'i, 't>,
505        component: &SyntaxComponent,
506    ) -> Result<SpecifiedValueComponent, StyleParseError<'i>> {
507        let data_type = match component.name() {
508            ComponentName::DataType(ty) => ty,
509            ComponentName::Ident(ref name) => {
510                let ident = CustomIdent::parse(input, &[])?;
511                if ident != *name {
512                    return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
513                }
514                return Ok(SpecifiedValueComponent::CustomIdent(ident));
515            },
516        };
517
518        let value = match data_type {
519            DataType::Length => {
520                SpecifiedValueComponent::Length(specified::Length::parse(context, input)?)
521            },
522            DataType::Number => {
523                SpecifiedValueComponent::Number(specified::Number::parse(context, input)?)
524            },
525            DataType::Percentage => {
526                SpecifiedValueComponent::Percentage(specified::Percentage::parse(context, input)?)
527            },
528            DataType::LengthPercentage => SpecifiedValueComponent::LengthPercentage(
529                specified::LengthPercentage::parse(context, input)?,
530            ),
531            DataType::Color => {
532                SpecifiedValueComponent::Color(specified::Color::parse(context, input)?)
533            },
534            DataType::Image => {
535                SpecifiedValueComponent::Image(specified::Image::parse_forbid_none(context, input)?)
536            },
537            DataType::Url => {
538                SpecifiedValueComponent::Url(specified::url::SpecifiedUrl::parse(context, input)?)
539            },
540            DataType::Integer => {
541                SpecifiedValueComponent::Integer(specified::Integer::parse(context, input)?)
542            },
543            DataType::Angle => {
544                SpecifiedValueComponent::Angle(specified::Angle::parse(context, input)?)
545            },
546            DataType::Time => {
547                SpecifiedValueComponent::Time(specified::Time::parse(context, input)?)
548            },
549            DataType::Resolution => {
550                SpecifiedValueComponent::Resolution(specified::Resolution::parse(context, input)?)
551            },
552            DataType::TransformFunction => SpecifiedValueComponent::TransformFunction(
553                specified::Transform::parse(context, input)?,
554            ),
555            DataType::CustomIdent => {
556                let name = CustomIdent::parse(input, &[])?;
557                SpecifiedValueComponent::CustomIdent(name)
558            },
559            DataType::TransformList => {
560                let mut values = vec![];
561                let Some(multiplier) = component.unpremultiplied().multiplier() else {
562                    debug_assert!(false, "Unpremultiplied <transform-list> had no multiplier?");
563                    return Err(
564                        input.new_custom_error(StyleParseErrorKind::PropertySyntaxField(
565                            PropertySyntaxParseError::UnexpectedEOF,
566                        )),
567                    );
568                };
569                debug_assert_eq!(multiplier, Multiplier::Space);
570                loop {
571                    values.push(SpecifiedValueComponent::TransformFunction(
572                        specified::Transform::parse(context, input)?,
573                    ));
574                    let result = Self::expect_multiplier(input, &multiplier);
575                    if Self::expect_multiplier_yielded_eof_error(&result) {
576                        break;
577                    }
578                    result?;
579                }
580                let list = ComponentList {
581                    multiplier,
582                    components: values.into(),
583                };
584                SpecifiedValueComponent::TransformList(list)
585            },
586            DataType::String => {
587                let string = input.expect_string()?;
588                SpecifiedValueComponent::String(string.as_ref().to_owned().into())
589            },
590        };
591        Ok(value)
592    }
593
594    fn expect_multiplier_yielded_eof_error<'i>(result: &Result<(), StyleParseError<'i>>) -> bool {
595        matches!(
596            result,
597            Err(StyleParseError {
598                kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput),
599                ..
600            })
601        )
602    }
603
604    fn expect_multiplier<'i, 't>(
605        input: &mut CSSParser<'i, 't>,
606        multiplier: &Multiplier,
607    ) -> Result<(), StyleParseError<'i>> {
608        match multiplier {
609            Multiplier::Space => {
610                input.expect_whitespace()?;
611                if input.is_exhausted() {
612                    // If there was trailing whitespace, do not interpret it as a multiplier
613                    return Err(input.new_error(BasicParseErrorKind::EndOfInput));
614                }
615                Ok(())
616            },
617            Multiplier::Comma => Ok(input.expect_comma()?),
618        }
619    }
620}
621
622/// An animated value for custom property.
623#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
624pub struct CustomAnimatedValue {
625    /// The name of the custom property.
626    pub(crate) name: crate::custom_properties::Name,
627    /// The computed value of the custom property.
628    /// `None` represents the guaranteed-invalid value.
629    pub(crate) value: Option<ComputedValue>,
630}
631
632impl Animate for CustomAnimatedValue {
633    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
634        if self.name != other.name {
635            return Err(());
636        }
637        let value = self.value.animate(&other.value, procedure)?;
638        Ok(Self {
639            name: self.name.clone(),
640            value,
641        })
642    }
643}
644
645impl CustomAnimatedValue {
646    pub(crate) fn from_computed(
647        name: &crate::custom_properties::Name,
648        value: Option<&ComputedValue>,
649    ) -> Self {
650        Self {
651            name: name.clone(),
652            value: value.cloned(),
653        }
654    }
655
656    pub(crate) fn from_declaration(
657        declaration: &properties::CustomDeclaration,
658        context: &mut computed::Context,
659    ) -> Option<Self> {
660        let computed_value = match declaration.value {
661            properties::CustomDeclarationValue::Unparsed(ref value) => Some({
662                debug_assert!(
663                    context.builder.stylist.is_some(),
664                    "Need a Stylist to get property registration!"
665                );
666                let registration = context
667                    .builder
668                    .stylist
669                    .unwrap()
670                    .get_custom_property_registration(&declaration.name);
671                if registration.is_universal() {
672                    // FIXME: Do we need to perform substitution here somehow?
673                    ComputedValue::new(
674                        ValueInner::Universal(Arc::clone(value)),
675                        value.url_data.clone(),
676                    )
677                } else {
678                    let mut input = cssparser::ParserInput::new(&value.css);
679                    let mut input = CSSParser::new(&mut input);
680                    SpecifiedValue::compute(
681                        &mut input,
682                        registration,
683                        None,
684                        &value.url_data,
685                        context,
686                        AllowComputationallyDependent::Yes,
687                        /* attr_taint */ Default::default(),
688                    )
689                    .unwrap_or_else(|_| {
690                        ComputedValue::new(
691                            ValueInner::Universal(Arc::clone(value)),
692                            value.url_data.clone(),
693                        )
694                    })
695                }
696            }),
697            properties::CustomDeclarationValue::Parsed(ref v) => Some(v.to_computed_value(context)),
698            properties::CustomDeclarationValue::CSSWideKeyword(keyword) => {
699                let stylist = context.builder.stylist.unwrap();
700                let registration = stylist.get_custom_property_registration(&declaration.name);
701                match keyword {
702                    CSSWideKeyword::Initial => stylist
703                        .get_custom_property_initial_values()
704                        .get(registration, &declaration.name),
705                    CSSWideKeyword::Inherit => context
706                        .builder
707                        .inherited_custom_properties()
708                        .get(registration, &declaration.name),
709                    CSSWideKeyword::Unset => {
710                        if registration.inherits() {
711                            context
712                                .builder
713                                .inherited_custom_properties()
714                                .get(registration, &declaration.name)
715                        } else {
716                            stylist
717                                .get_custom_property_initial_values()
718                                .get(registration, &declaration.name)
719                        }
720                    },
721                    // FIXME(emilio, bug 1533327): I think revert (and
722                    // revert-layer) handling is not fine here, but what to
723                    // do instead?
724                    //
725                    // Seems we'd need the computed value as if it was
726                    // revert, somehow. Returning `None` seems fine for now...
727                    //
728                    // Note that once this is fixed, this method should be
729                    // able to return `Self` instead of Option<Self>`.
730                    CSSWideKeyword::Revert
731                    | CSSWideKeyword::RevertRule
732                    | CSSWideKeyword::RevertLayer => return None,
733                }
734                .cloned()
735            },
736        };
737        Some(Self {
738            name: declaration.name.clone(),
739            value: computed_value,
740        })
741    }
742
743    pub(crate) fn to_declaration(&self) -> properties::PropertyDeclaration {
744        properties::PropertyDeclaration::Custom(properties::CustomDeclaration {
745            name: self.name.clone(),
746            value: match &self.value {
747                Some(value) => value.to_declared_value(),
748                None => CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial),
749            },
750        })
751    }
752}