tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

custom_properties.rs (91156B)


      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 //! Support for [custom properties for cascading variables][custom].
      6 //!
      7 //! [custom]: https://drafts.csswg.org/css-variables/
      8 
      9 use crate::applicable_declarations::CascadePriority;
     10 use crate::custom_properties_map::CustomPropertiesMap;
     11 use crate::derives::*;
     12 use crate::dom::AttributeProvider;
     13 use crate::media_queries::Device;
     14 use crate::properties::{
     15    CSSWideKeyword, CustomDeclaration, CustomDeclarationValue, LonghandId, LonghandIdSet,
     16    PropertyDeclaration,
     17 };
     18 use crate::properties_and_values::{
     19    registry::PropertyRegistrationData,
     20    syntax::{data_type::DependentDataTypes, Descriptor},
     21    value::{
     22        AllowComputationallyDependent, ComputedValue as ComputedRegisteredValue,
     23        SpecifiedValue as SpecifiedRegisteredValue,
     24    },
     25 };
     26 use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet};
     27 use crate::stylesheets::UrlExtraData;
     28 use crate::stylist::Stylist;
     29 use crate::values::computed::{self, ToComputedValue};
     30 use crate::values::generics::calc::SortKey as AttrUnit;
     31 use crate::values::specified::FontRelativeLength;
     32 use crate::values::AtomIdent;
     33 use crate::Atom;
     34 use cssparser::{
     35    CowRcStr, Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType,
     36 };
     37 use selectors::parser::SelectorParseErrorKind;
     38 use servo_arc::Arc;
     39 use smallvec::SmallVec;
     40 use std::borrow::Cow;
     41 use std::collections::hash_map::Entry;
     42 use std::fmt::{self, Write};
     43 use std::ops::{Index, IndexMut};
     44 use std::{cmp, num};
     45 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
     46 
     47 /// The environment from which to get `env` function values.
     48 ///
     49 /// TODO(emilio): If this becomes a bit more complex we should probably move it
     50 /// to the `media_queries` module, or something.
     51 #[derive(Debug, MallocSizeOf)]
     52 pub struct CssEnvironment;
     53 
     54 type EnvironmentEvaluator = fn(device: &Device, url_data: &UrlExtraData) -> VariableValue;
     55 
     56 struct EnvironmentVariable {
     57    name: Atom,
     58    evaluator: EnvironmentEvaluator,
     59 }
     60 
     61 macro_rules! make_variable {
     62    ($name:expr, $evaluator:expr) => {{
     63        EnvironmentVariable {
     64            name: $name,
     65            evaluator: $evaluator,
     66        }
     67    }};
     68 }
     69 
     70 fn get_safearea_inset_top(device: &Device, url_data: &UrlExtraData) -> VariableValue {
     71    VariableValue::pixels(device.safe_area_insets().top, url_data)
     72 }
     73 
     74 fn get_safearea_inset_bottom(device: &Device, url_data: &UrlExtraData) -> VariableValue {
     75    VariableValue::pixels(device.safe_area_insets().bottom, url_data)
     76 }
     77 
     78 fn get_safearea_inset_left(device: &Device, url_data: &UrlExtraData) -> VariableValue {
     79    VariableValue::pixels(device.safe_area_insets().left, url_data)
     80 }
     81 
     82 fn get_safearea_inset_right(device: &Device, url_data: &UrlExtraData) -> VariableValue {
     83    VariableValue::pixels(device.safe_area_insets().right, url_data)
     84 }
     85 
     86 #[cfg(feature = "gecko")]
     87 fn get_content_preferred_color_scheme(device: &Device, url_data: &UrlExtraData) -> VariableValue {
     88    use crate::queries::values::PrefersColorScheme;
     89    let prefers_color_scheme = unsafe {
     90        crate::gecko_bindings::bindings::Gecko_MediaFeatures_PrefersColorScheme(
     91            device.document(),
     92            /* use_content = */ true,
     93        )
     94    };
     95    VariableValue::ident(
     96        match prefers_color_scheme {
     97            PrefersColorScheme::Light => "light",
     98            PrefersColorScheme::Dark => "dark",
     99        },
    100        url_data,
    101    )
    102 }
    103 
    104 #[cfg(feature = "servo")]
    105 fn get_content_preferred_color_scheme(_device: &Device, url_data: &UrlExtraData) -> VariableValue {
    106    // TODO: Add an implementation for Servo.
    107    VariableValue::ident("light", url_data)
    108 }
    109 
    110 fn get_scrollbar_inline_size(device: &Device, url_data: &UrlExtraData) -> VariableValue {
    111    VariableValue::pixels(device.scrollbar_inline_size().px(), url_data)
    112 }
    113 
    114 fn get_hairline(device: &Device, url_data: &UrlExtraData) -> VariableValue {
    115    VariableValue::pixels(
    116        app_units::Au(device.app_units_per_device_pixel()).to_f32_px(),
    117        url_data,
    118    )
    119 }
    120 
    121 static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [
    122    make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top),
    123    make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom),
    124    make_variable!(atom!("safe-area-inset-left"), get_safearea_inset_left),
    125    make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right),
    126 ];
    127 
    128 #[cfg(feature = "gecko")]
    129 macro_rules! lnf_int {
    130    ($id:ident) => {
    131        unsafe {
    132            crate::gecko_bindings::bindings::Gecko_GetLookAndFeelInt(
    133                crate::gecko_bindings::bindings::LookAndFeel_IntID::$id as i32,
    134            )
    135        }
    136    };
    137 }
    138 
    139 #[cfg(feature = "servo")]
    140 macro_rules! lnf_int {
    141    ($id:ident) => {
    142        // TODO: Add an implementation for Servo.
    143        0
    144    };
    145 }
    146 
    147 macro_rules! lnf_int_variable {
    148    ($atom:expr, $id:ident, $ctor:ident) => {{
    149        fn __eval(_: &Device, url_data: &UrlExtraData) -> VariableValue {
    150            VariableValue::$ctor(lnf_int!($id), url_data)
    151        }
    152        make_variable!($atom, __eval)
    153    }};
    154 }
    155 
    156 fn eval_gtk_csd_titlebar_radius(device: &Device, url_data: &UrlExtraData) -> VariableValue {
    157    let int_pixels = lnf_int!(TitlebarRadius);
    158    let unzoomed_scale =
    159        device.device_pixel_ratio_ignoring_full_zoom().get() / device.device_pixel_ratio().get();
    160    VariableValue::pixels(int_pixels as f32 * unzoomed_scale, url_data)
    161 }
    162 
    163 static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 9] = [
    164    make_variable!(
    165        atom!("-moz-gtk-csd-titlebar-radius"),
    166        eval_gtk_csd_titlebar_radius
    167    ),
    168    lnf_int_variable!(
    169        atom!("-moz-gtk-csd-tooltip-radius"),
    170        TooltipRadius,
    171        int_pixels
    172    ),
    173    lnf_int_variable!(
    174        atom!("-moz-gtk-csd-close-button-position"),
    175        GTKCSDCloseButtonPosition,
    176        integer
    177    ),
    178    lnf_int_variable!(
    179        atom!("-moz-gtk-csd-minimize-button-position"),
    180        GTKCSDMinimizeButtonPosition,
    181        integer
    182    ),
    183    lnf_int_variable!(
    184        atom!("-moz-gtk-csd-maximize-button-position"),
    185        GTKCSDMaximizeButtonPosition,
    186        integer
    187    ),
    188    lnf_int_variable!(
    189        atom!("-moz-overlay-scrollbar-fade-duration"),
    190        ScrollbarFadeDuration,
    191        int_ms
    192    ),
    193    make_variable!(
    194        atom!("-moz-content-preferred-color-scheme"),
    195        get_content_preferred_color_scheme
    196    ),
    197    make_variable!(atom!("scrollbar-inline-size"), get_scrollbar_inline_size),
    198    make_variable!(atom!("hairline"), get_hairline),
    199 ];
    200 
    201 impl CssEnvironment {
    202    #[inline]
    203    fn get(&self, name: &Atom, device: &Device, url_data: &UrlExtraData) -> Option<VariableValue> {
    204        if let Some(var) = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name) {
    205            return Some((var.evaluator)(device, url_data));
    206        }
    207        if !url_data.chrome_rules_enabled() {
    208            return None;
    209        }
    210        let var = CHROME_ENVIRONMENT_VARIABLES
    211            .iter()
    212            .find(|var| var.name == *name)?;
    213        Some((var.evaluator)(device, url_data))
    214    }
    215 }
    216 
    217 /// A custom property name is just an `Atom`.
    218 ///
    219 /// Note that this does not include the `--` prefix
    220 pub type Name = Atom;
    221 
    222 /// Parse a custom property name.
    223 ///
    224 /// <https://drafts.csswg.org/css-variables/#typedef-custom-property-name>
    225 pub fn parse_name(s: &str) -> Result<&str, ()> {
    226    if s.starts_with("--") && s.len() > 2 {
    227        Ok(&s[2..])
    228    } else {
    229        Err(())
    230    }
    231 }
    232 
    233 /// A value for a custom property is just a set of tokens.
    234 ///
    235 /// We preserve the original CSS for serialization, and also the variable
    236 /// references to other custom property names.
    237 #[derive(Clone, Debug, MallocSizeOf, ToShmem)]
    238 pub struct VariableValue {
    239    /// The raw CSS string.
    240    pub css: String,
    241 
    242    /// The url data of the stylesheet where this value came from.
    243    pub url_data: UrlExtraData,
    244 
    245    first_token_type: TokenSerializationType,
    246    last_token_type: TokenSerializationType,
    247 
    248    /// var(), env(), attr() or non-custom property (e.g. through `em`) references.
    249    references: References,
    250 }
    251 
    252 trivial_to_computed_value!(VariableValue);
    253 
    254 /// Given a potentially registered variable value turn it into a computed custom property value.
    255 pub fn compute_variable_value(
    256    value: &Arc<VariableValue>,
    257    registration: &PropertyRegistrationData,
    258    computed_context: &computed::Context,
    259 ) -> Option<ComputedRegisteredValue> {
    260    if registration.syntax.is_universal() {
    261        return Some(ComputedRegisteredValue::universal(Arc::clone(value)));
    262    }
    263    compute_value(&value.css, &value.url_data, registration, computed_context).ok()
    264 }
    265 
    266 // For all purposes, we want values to be considered equal if their css text is equal.
    267 impl PartialEq for VariableValue {
    268    fn eq(&self, other: &Self) -> bool {
    269        self.css == other.css
    270    }
    271 }
    272 
    273 impl Eq for VariableValue {}
    274 
    275 impl ToCss for SpecifiedValue {
    276    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    277    where
    278        W: Write,
    279    {
    280        dest.write_str(&self.css)
    281    }
    282 }
    283 
    284 /// A pair of separate CustomPropertiesMaps, split between custom properties
    285 /// that have the inherit flag set and those with the flag unset.
    286 #[repr(C)]
    287 #[derive(Clone, Debug, Default, PartialEq)]
    288 pub struct ComputedCustomProperties {
    289    /// Map for custom properties with inherit flag set, including non-registered
    290    /// ones.
    291    pub inherited: CustomPropertiesMap,
    292    /// Map for custom properties with inherit flag unset.
    293    pub non_inherited: CustomPropertiesMap,
    294 }
    295 
    296 impl ComputedCustomProperties {
    297    /// Return whether the inherited and non_inherited maps are none.
    298    pub fn is_empty(&self) -> bool {
    299        self.inherited.is_empty() && self.non_inherited.is_empty()
    300    }
    301 
    302    /// Return the name and value of the property at specified index, if any.
    303    pub fn property_at(&self, index: usize) -> Option<(&Name, &Option<ComputedRegisteredValue>)> {
    304        // Just expose the custom property items from custom_properties.inherited, followed
    305        // by custom property items from custom_properties.non_inherited.
    306        self.inherited
    307            .get_index(index)
    308            .or_else(|| self.non_inherited.get_index(index - self.inherited.len()))
    309    }
    310 
    311    /// Insert a custom property in the corresponding inherited/non_inherited
    312    /// map, depending on whether the inherit flag is set or unset.
    313    fn insert(
    314        &mut self,
    315        registration: &PropertyRegistrationData,
    316        name: &Name,
    317        value: ComputedRegisteredValue,
    318    ) {
    319        self.map_mut(registration).insert(name, value)
    320    }
    321 
    322    /// Remove a custom property from the corresponding inherited/non_inherited
    323    /// map, depending on whether the inherit flag is set or unset.
    324    fn remove(&mut self, registration: &PropertyRegistrationData, name: &Name) {
    325        self.map_mut(registration).remove(name);
    326    }
    327 
    328    /// Shrink the capacity of the inherited maps as much as possible.
    329    fn shrink_to_fit(&mut self) {
    330        self.inherited.shrink_to_fit();
    331        self.non_inherited.shrink_to_fit();
    332    }
    333 
    334    fn map_mut(&mut self, registration: &PropertyRegistrationData) -> &mut CustomPropertiesMap {
    335        if registration.inherits() {
    336            &mut self.inherited
    337        } else {
    338            &mut self.non_inherited
    339        }
    340    }
    341 
    342    /// Returns the relevant custom property value given a registration.
    343    pub fn get(
    344        &self,
    345        registration: &PropertyRegistrationData,
    346        name: &Name,
    347    ) -> Option<&ComputedRegisteredValue> {
    348        if registration.inherits() {
    349            self.inherited.get(name)
    350        } else {
    351            self.non_inherited.get(name)
    352        }
    353    }
    354 }
    355 
    356 /// Both specified and computed values are VariableValues, the difference is
    357 /// whether var() functions are expanded.
    358 pub type SpecifiedValue = VariableValue;
    359 /// Both specified and computed values are VariableValues, the difference is
    360 /// whether var() functions are expanded.
    361 pub type ComputedValue = VariableValue;
    362 
    363 /// Set of flags to non-custom references this custom property makes.
    364 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, MallocSizeOf, ToShmem)]
    365 struct NonCustomReferences(u8);
    366 
    367 bitflags! {
    368    impl NonCustomReferences: u8 {
    369        /// At least one custom property depends on font-relative units.
    370        const FONT_UNITS = 1 << 0;
    371        /// At least one custom property depends on root element's font-relative units.
    372        const ROOT_FONT_UNITS = 1 << 1;
    373        /// At least one custom property depends on line height units.
    374        const LH_UNITS = 1 << 2;
    375        /// At least one custom property depends on root element's line height units.
    376        const ROOT_LH_UNITS = 1 << 3;
    377        /// All dependencies not depending on the root element.
    378        const NON_ROOT_DEPENDENCIES = Self::FONT_UNITS.0 | Self::LH_UNITS.0;
    379        /// All dependencies depending on the root element.
    380        const ROOT_DEPENDENCIES = Self::ROOT_FONT_UNITS.0 | Self::ROOT_LH_UNITS.0;
    381    }
    382 }
    383 
    384 impl NonCustomReferences {
    385    fn for_each<F>(&self, mut f: F)
    386    where
    387        F: FnMut(SingleNonCustomReference),
    388    {
    389        for (_, r) in self.iter_names() {
    390            let single = match r {
    391                Self::FONT_UNITS => SingleNonCustomReference::FontUnits,
    392                Self::ROOT_FONT_UNITS => SingleNonCustomReference::RootFontUnits,
    393                Self::LH_UNITS => SingleNonCustomReference::LhUnits,
    394                Self::ROOT_LH_UNITS => SingleNonCustomReference::RootLhUnits,
    395                _ => unreachable!("Unexpected single bit value"),
    396            };
    397            f(single);
    398        }
    399    }
    400 
    401    fn from_unit(value: &CowRcStr) -> Self {
    402        // For registered properties, any reference to font-relative dimensions
    403        // make it dependent on font-related properties.
    404        // TODO(dshin): When we unit algebra gets implemented and handled -
    405        // Is it valid to say that `calc(1em / 2em * 3px)` triggers this?
    406        if value.eq_ignore_ascii_case(FontRelativeLength::LH) {
    407            return Self::FONT_UNITS | Self::LH_UNITS;
    408        }
    409        if value.eq_ignore_ascii_case(FontRelativeLength::EM)
    410            || value.eq_ignore_ascii_case(FontRelativeLength::EX)
    411            || value.eq_ignore_ascii_case(FontRelativeLength::CAP)
    412            || value.eq_ignore_ascii_case(FontRelativeLength::CH)
    413            || value.eq_ignore_ascii_case(FontRelativeLength::IC)
    414        {
    415            return Self::FONT_UNITS;
    416        }
    417        if value.eq_ignore_ascii_case(FontRelativeLength::RLH) {
    418            return Self::ROOT_FONT_UNITS | Self::ROOT_LH_UNITS;
    419        }
    420        if value.eq_ignore_ascii_case(FontRelativeLength::REM)
    421            || value.eq_ignore_ascii_case(FontRelativeLength::REX)
    422            || value.eq_ignore_ascii_case(FontRelativeLength::RCH)
    423            || value.eq_ignore_ascii_case(FontRelativeLength::RCAP)
    424            || value.eq_ignore_ascii_case(FontRelativeLength::RIC)
    425        {
    426            return Self::ROOT_FONT_UNITS;
    427        }
    428        Self::empty()
    429    }
    430 }
    431 
    432 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    433 enum SingleNonCustomReference {
    434    FontUnits = 0,
    435    RootFontUnits,
    436    LhUnits,
    437    RootLhUnits,
    438 }
    439 
    440 struct NonCustomReferenceMap<T>([Option<T>; 4]);
    441 
    442 impl<T> Default for NonCustomReferenceMap<T> {
    443    fn default() -> Self {
    444        NonCustomReferenceMap(Default::default())
    445    }
    446 }
    447 
    448 impl<T> Index<SingleNonCustomReference> for NonCustomReferenceMap<T> {
    449    type Output = Option<T>;
    450 
    451    fn index(&self, reference: SingleNonCustomReference) -> &Self::Output {
    452        &self.0[reference as usize]
    453    }
    454 }
    455 
    456 impl<T> IndexMut<SingleNonCustomReference> for NonCustomReferenceMap<T> {
    457    fn index_mut(&mut self, reference: SingleNonCustomReference) -> &mut Self::Output {
    458        &mut self.0[reference as usize]
    459    }
    460 }
    461 
    462 /// Whether to defer resolving custom properties referencing font relative units.
    463 #[derive(Clone, Copy, PartialEq, Eq)]
    464 #[allow(missing_docs)]
    465 pub enum DeferFontRelativeCustomPropertyResolution {
    466    Yes,
    467    No,
    468 }
    469 
    470 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, Parse)]
    471 enum SubstitutionFunctionKind {
    472    Var,
    473    Env,
    474    Attr,
    475 }
    476 
    477 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, Parse)]
    478 enum AttributeType {
    479    None,
    480    RawString,
    481    Type(Descriptor),
    482    Unit(AttrUnit),
    483 }
    484 
    485 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
    486 struct VariableFallback {
    487    start: num::NonZeroUsize,
    488    first_token_type: TokenSerializationType,
    489    last_token_type: TokenSerializationType,
    490 }
    491 
    492 #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
    493 struct SubstitutionFunctionReference {
    494    name: Name,
    495    start: usize,
    496    end: usize,
    497    fallback: Option<VariableFallback>,
    498    attribute_syntax: AttributeType,
    499    prev_token_type: TokenSerializationType,
    500    next_token_type: TokenSerializationType,
    501    substitution_kind: SubstitutionFunctionKind,
    502 }
    503 
    504 /// A struct holding information about the external references to that a custom
    505 /// property value may have.
    506 #[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
    507 struct References {
    508    refs: Vec<SubstitutionFunctionReference>,
    509    non_custom_references: NonCustomReferences,
    510    any_env: bool,
    511    any_var: bool,
    512    any_attr: bool,
    513 }
    514 
    515 impl References {
    516    fn has_references(&self) -> bool {
    517        !self.refs.is_empty()
    518    }
    519 
    520    fn non_custom_references(&self, is_root_element: bool) -> NonCustomReferences {
    521        let mut mask = NonCustomReferences::NON_ROOT_DEPENDENCIES;
    522        if is_root_element {
    523            mask |= NonCustomReferences::ROOT_DEPENDENCIES
    524        }
    525        self.non_custom_references & mask
    526    }
    527 }
    528 
    529 impl VariableValue {
    530    fn empty(url_data: &UrlExtraData) -> Self {
    531        Self {
    532            css: String::new(),
    533            last_token_type: Default::default(),
    534            first_token_type: Default::default(),
    535            url_data: url_data.clone(),
    536            references: Default::default(),
    537        }
    538    }
    539 
    540    /// Create a new custom property without parsing if the CSS is known to be valid and contain no
    541    /// references.
    542    pub fn new(
    543        css: String,
    544        url_data: &UrlExtraData,
    545        first_token_type: TokenSerializationType,
    546        last_token_type: TokenSerializationType,
    547    ) -> Self {
    548        Self {
    549            css,
    550            url_data: url_data.clone(),
    551            first_token_type,
    552            last_token_type,
    553            references: Default::default(),
    554        }
    555    }
    556 
    557    fn push<'i>(
    558        &mut self,
    559        css: &str,
    560        css_first_token_type: TokenSerializationType,
    561        css_last_token_type: TokenSerializationType,
    562    ) -> Result<(), ()> {
    563        /// Prevent values from getting terribly big since you can use custom
    564        /// properties exponentially.
    565        ///
    566        /// This number (2MB) is somewhat arbitrary, but silly enough that no
    567        /// reasonable page should hit it. We could limit by number of total
    568        /// substitutions, but that was very easy to work around in practice
    569        /// (just choose a larger initial value and boom).
    570        const MAX_VALUE_LENGTH_IN_BYTES: usize = 2 * 1024 * 1024;
    571 
    572        if self.css.len() + css.len() > MAX_VALUE_LENGTH_IN_BYTES {
    573            return Err(());
    574        }
    575 
    576        // This happens e.g. between two subsequent var() functions:
    577        // `var(--a)var(--b)`.
    578        //
    579        // In that case, css_*_token_type is nonsensical.
    580        if css.is_empty() {
    581            return Ok(());
    582        }
    583 
    584        self.first_token_type.set_if_nothing(css_first_token_type);
    585        // If self.first_token_type was nothing,
    586        // self.last_token_type is also nothing and this will be false:
    587        if self
    588            .last_token_type
    589            .needs_separator_when_before(css_first_token_type)
    590        {
    591            self.css.push_str("/**/")
    592        }
    593        self.css.push_str(css);
    594        self.last_token_type = css_last_token_type;
    595        Ok(())
    596    }
    597 
    598    /// Parse a custom property value.
    599    pub fn parse<'i, 't>(
    600        input: &mut Parser<'i, 't>,
    601        url_data: &UrlExtraData,
    602    ) -> Result<Self, ParseError<'i>> {
    603        let mut references = References::default();
    604        let mut missing_closing_characters = String::new();
    605        let start_position = input.position();
    606        let (first_token_type, last_token_type) = parse_declaration_value(
    607            input,
    608            start_position,
    609            &mut references,
    610            &mut missing_closing_characters,
    611        )?;
    612        let mut css = input
    613            .slice_from(start_position)
    614            .trim_ascii_start()
    615            .to_owned();
    616        if !missing_closing_characters.is_empty() {
    617            // Unescaped backslash at EOF in a quoted string is ignored.
    618            if css.ends_with("\\")
    619                && matches!(missing_closing_characters.as_bytes()[0], b'"' | b'\'')
    620            {
    621                css.pop();
    622            }
    623            css.push_str(&missing_closing_characters);
    624        }
    625 
    626        css.truncate(css.trim_ascii_end().len());
    627        css.shrink_to_fit();
    628        references.refs.shrink_to_fit();
    629 
    630        Ok(Self {
    631            css,
    632            url_data: url_data.clone(),
    633            first_token_type,
    634            last_token_type,
    635            references,
    636        })
    637    }
    638 
    639    /// Create VariableValue from an int.
    640    fn integer(number: i32, url_data: &UrlExtraData) -> Self {
    641        Self::from_token(
    642            Token::Number {
    643                has_sign: false,
    644                value: number as f32,
    645                int_value: Some(number),
    646            },
    647            url_data,
    648        )
    649    }
    650 
    651    /// Create VariableValue from an int.
    652    fn ident(ident: &'static str, url_data: &UrlExtraData) -> Self {
    653        Self::from_token(Token::Ident(ident.into()), url_data)
    654    }
    655 
    656    /// Create VariableValue from a float amount of CSS pixels.
    657    fn pixels(number: f32, url_data: &UrlExtraData) -> Self {
    658        // FIXME (https://github.com/servo/rust-cssparser/issues/266):
    659        // No way to get TokenSerializationType::Dimension without creating
    660        // Token object.
    661        Self::from_token(
    662            Token::Dimension {
    663                has_sign: false,
    664                value: number,
    665                int_value: None,
    666                unit: CowRcStr::from("px"),
    667            },
    668            url_data,
    669        )
    670    }
    671 
    672    /// Create VariableValue from an integer amount of milliseconds.
    673    fn int_ms(number: i32, url_data: &UrlExtraData) -> Self {
    674        Self::from_token(
    675            Token::Dimension {
    676                has_sign: false,
    677                value: number as f32,
    678                int_value: Some(number),
    679                unit: CowRcStr::from("ms"),
    680            },
    681            url_data,
    682        )
    683    }
    684 
    685    /// Create VariableValue from an integer amount of CSS pixels.
    686    fn int_pixels(number: i32, url_data: &UrlExtraData) -> Self {
    687        Self::from_token(
    688            Token::Dimension {
    689                has_sign: false,
    690                value: number as f32,
    691                int_value: Some(number),
    692                unit: CowRcStr::from("px"),
    693            },
    694            url_data,
    695        )
    696    }
    697 
    698    fn from_token(token: Token, url_data: &UrlExtraData) -> Self {
    699        let token_type = token.serialization_type();
    700        let mut css = token.to_css_string();
    701        css.shrink_to_fit();
    702 
    703        VariableValue {
    704            css,
    705            url_data: url_data.clone(),
    706            first_token_type: token_type,
    707            last_token_type: token_type,
    708            references: Default::default(),
    709        }
    710    }
    711 
    712    /// Returns the raw CSS text from this VariableValue
    713    pub fn css_text(&self) -> &str {
    714        &self.css
    715    }
    716 
    717    /// Returns whether this variable value has any reference to the environment or other
    718    /// variables.
    719    pub fn has_references(&self) -> bool {
    720        self.references.has_references()
    721    }
    722 }
    723 
    724 /// <https://drafts.csswg.org/css-syntax-3/#typedef-declaration-value>
    725 fn parse_declaration_value<'i, 't>(
    726    input: &mut Parser<'i, 't>,
    727    input_start: SourcePosition,
    728    references: &mut References,
    729    missing_closing_characters: &mut String,
    730 ) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
    731    input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
    732        parse_declaration_value_block(input, input_start, references, missing_closing_characters)
    733    })
    734 }
    735 
    736 /// Like parse_declaration_value, but accept `!` and `;` since they are only invalid at the top level.
    737 fn parse_declaration_value_block<'i, 't>(
    738    input: &mut Parser<'i, 't>,
    739    input_start: SourcePosition,
    740    references: &mut References,
    741    missing_closing_characters: &mut String,
    742 ) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
    743    let mut is_first = true;
    744    let mut first_token_type = TokenSerializationType::Nothing;
    745    let mut last_token_type = TokenSerializationType::Nothing;
    746    let mut prev_reference_index: Option<usize> = None;
    747    loop {
    748        let token_start = input.position();
    749        let Ok(token) = input.next_including_whitespace_and_comments() else {
    750            break;
    751        };
    752 
    753        let prev_token_type = last_token_type;
    754        let serialization_type = token.serialization_type();
    755        last_token_type = serialization_type;
    756        if is_first {
    757            first_token_type = last_token_type;
    758            is_first = false;
    759        }
    760 
    761        macro_rules! nested {
    762            () => {
    763                input.parse_nested_block(|input| {
    764                    parse_declaration_value_block(
    765                        input,
    766                        input_start,
    767                        references,
    768                        missing_closing_characters,
    769                    )
    770                })?
    771            };
    772        }
    773        macro_rules! check_closed {
    774            ($closing:expr) => {
    775                if !input.slice_from(token_start).ends_with($closing) {
    776                    missing_closing_characters.push_str($closing)
    777                }
    778            };
    779        }
    780        if let Some(index) = prev_reference_index.take() {
    781            references.refs[index].next_token_type = serialization_type;
    782        }
    783        match *token {
    784            Token::Comment(_) => {
    785                let token_slice = input.slice_from(token_start);
    786                if !token_slice.ends_with("*/") {
    787                    missing_closing_characters.push_str(if token_slice.ends_with('*') {
    788                        "/"
    789                    } else {
    790                        "*/"
    791                    })
    792                }
    793            },
    794            Token::BadUrl(ref u) => {
    795                let e = StyleParseErrorKind::BadUrlInDeclarationValueBlock(u.clone());
    796                return Err(input.new_custom_error(e));
    797            },
    798            Token::BadString(ref s) => {
    799                let e = StyleParseErrorKind::BadStringInDeclarationValueBlock(s.clone());
    800                return Err(input.new_custom_error(e));
    801            },
    802            Token::CloseParenthesis => {
    803                let e = StyleParseErrorKind::UnbalancedCloseParenthesisInDeclarationValueBlock;
    804                return Err(input.new_custom_error(e));
    805            },
    806            Token::CloseSquareBracket => {
    807                let e = StyleParseErrorKind::UnbalancedCloseSquareBracketInDeclarationValueBlock;
    808                return Err(input.new_custom_error(e));
    809            },
    810            Token::CloseCurlyBracket => {
    811                let e = StyleParseErrorKind::UnbalancedCloseCurlyBracketInDeclarationValueBlock;
    812                return Err(input.new_custom_error(e));
    813            },
    814            Token::Function(ref name) => {
    815                let substitution_kind = match SubstitutionFunctionKind::from_ident(name).ok() {
    816                    Some(SubstitutionFunctionKind::Attr) => {
    817                        if static_prefs::pref!("layout.css.attr.enabled") {
    818                            Some(SubstitutionFunctionKind::Attr)
    819                        } else {
    820                            None
    821                        }
    822                    },
    823                    kind => kind,
    824                };
    825                if let Some(substitution_kind) = substitution_kind {
    826                    let our_ref_index = references.refs.len();
    827                    let fallback = input.parse_nested_block(|input| {
    828                        // TODO(emilio): For env() this should be <custom-ident> per spec, but no other browser does
    829                        // that, see https://github.com/w3c/csswg-drafts/issues/3262.
    830                        let name = input.expect_ident()?;
    831                        let name =
    832                            Atom::from(if substitution_kind == SubstitutionFunctionKind::Var {
    833                                match parse_name(name.as_ref()) {
    834                                    Ok(name) => name,
    835                                    Err(()) => {
    836                                        let name = name.clone();
    837                                        return Err(input.new_custom_error(
    838                                            SelectorParseErrorKind::UnexpectedIdent(name),
    839                                        ));
    840                                    },
    841                                }
    842                            } else {
    843                                name.as_ref()
    844                            });
    845 
    846                        let attribute_syntax =
    847                            if substitution_kind == SubstitutionFunctionKind::Attr {
    848                                parse_attr_type(input)
    849                            } else {
    850                                AttributeType::None
    851                            };
    852 
    853                        // We want the order of the references to match source order. So we need to reserve our slot
    854                        // now, _before_ parsing our fallback. Note that we don't care if parsing fails after all, since
    855                        // if this fails we discard the whole result anyways.
    856                        let start = token_start.byte_index() - input_start.byte_index();
    857                        references.refs.push(SubstitutionFunctionReference {
    858                            name,
    859                            start,
    860                            // To be fixed up after parsing fallback and auto-closing via our_ref_index.
    861                            end: start,
    862                            prev_token_type,
    863                            // To be fixed up (if needed) on the next loop iteration via prev_reference_index.
    864                            next_token_type: TokenSerializationType::Nothing,
    865                            // To be fixed up after parsing fallback.
    866                            fallback: None,
    867                            attribute_syntax,
    868                            substitution_kind: substitution_kind.clone(),
    869                        });
    870 
    871                        let mut fallback = None;
    872                        if input.try_parse(|input| input.expect_comma()).is_ok() {
    873                            input.skip_whitespace();
    874                            let fallback_start = num::NonZeroUsize::new(
    875                                input.position().byte_index() - input_start.byte_index(),
    876                            )
    877                            .unwrap();
    878                            // NOTE(emilio): Intentionally using parse_declaration_value rather than
    879                            // parse_declaration_value_block, since that's what parse_fallback used to do.
    880                            let (first, last) = parse_declaration_value(
    881                                input,
    882                                input_start,
    883                                references,
    884                                missing_closing_characters,
    885                            )?;
    886                            fallback = Some(VariableFallback {
    887                                start: fallback_start,
    888                                first_token_type: first,
    889                                last_token_type: last,
    890                            });
    891                        } else {
    892                            let state = input.state();
    893                            // We still need to consume the rest of the potentially-unclosed
    894                            // tokens, but make sure to not consume tokens that would otherwise be
    895                            // invalid, by calling reset().
    896                            parse_declaration_value_block(
    897                                input,
    898                                input_start,
    899                                references,
    900                                missing_closing_characters,
    901                            )?;
    902                            input.reset(&state);
    903                        }
    904                        Ok(fallback)
    905                    })?;
    906                    check_closed!(")");
    907                    prev_reference_index = Some(our_ref_index);
    908                    let reference = &mut references.refs[our_ref_index];
    909                    reference.end = input.position().byte_index() - input_start.byte_index()
    910                        + missing_closing_characters.len();
    911                    reference.fallback = fallback;
    912                    match substitution_kind {
    913                        SubstitutionFunctionKind::Var => references.any_var = true,
    914                        SubstitutionFunctionKind::Env => references.any_env = true,
    915                        SubstitutionFunctionKind::Attr => references.any_attr = true,
    916                    };
    917                } else {
    918                    nested!();
    919                    check_closed!(")");
    920                }
    921            },
    922            Token::ParenthesisBlock => {
    923                nested!();
    924                check_closed!(")");
    925            },
    926            Token::CurlyBracketBlock => {
    927                nested!();
    928                check_closed!("}");
    929            },
    930            Token::SquareBracketBlock => {
    931                nested!();
    932                check_closed!("]");
    933            },
    934            Token::QuotedString(_) => {
    935                let token_slice = input.slice_from(token_start);
    936                let quote = &token_slice[..1];
    937                debug_assert!(matches!(quote, "\"" | "'"));
    938                if !(token_slice.ends_with(quote) && token_slice.len() > 1) {
    939                    missing_closing_characters.push_str(quote)
    940                }
    941            },
    942            Token::Ident(ref value)
    943            | Token::AtKeyword(ref value)
    944            | Token::Hash(ref value)
    945            | Token::IDHash(ref value)
    946            | Token::UnquotedUrl(ref value)
    947            | Token::Dimension {
    948                unit: ref value, ..
    949            } => {
    950                references
    951                    .non_custom_references
    952                    .insert(NonCustomReferences::from_unit(value));
    953                let is_unquoted_url = matches!(token, Token::UnquotedUrl(_));
    954                if value.ends_with("�") && input.slice_from(token_start).ends_with("\\") {
    955                    // Unescaped backslash at EOF in these contexts is interpreted as U+FFFD
    956                    // Check the value in case the final backslash was itself escaped.
    957                    // Serialize as escaped U+FFFD, which is also interpreted as U+FFFD.
    958                    // (Unescaped U+FFFD would also work, but removing the backslash is annoying.)
    959                    missing_closing_characters.push_str("�")
    960                }
    961                if is_unquoted_url {
    962                    check_closed!(")");
    963                }
    964            },
    965            _ => {},
    966        };
    967    }
    968    Ok((first_token_type, last_token_type))
    969 }
    970 
    971 /// Parse <attr-type> = type( <syntax> ) | raw-string | number | <attr-unit>.
    972 /// https://drafts.csswg.org/css-values-5/#attr-notation
    973 fn parse_attr_type<'i, 't>(input: &mut Parser<'i, 't>) -> AttributeType {
    974    input
    975        .try_parse(|input| {
    976            Ok(match input.next()? {
    977                Token::Function(ref name) if name.eq_ignore_ascii_case("type") => {
    978                    AttributeType::Type(input.parse_nested_block(Descriptor::from_css_parser)?)
    979                },
    980                Token::Ident(ref ident) => {
    981                    if ident.eq_ignore_ascii_case("raw-string") {
    982                        AttributeType::RawString
    983                    } else {
    984                        let unit = AttrUnit::from_ident(ident).map_err(|_| {
    985                            input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
    986                        })?;
    987                        AttributeType::Unit(unit)
    988                    }
    989                },
    990                Token::Delim('%') => AttributeType::Unit(AttrUnit::Percentage),
    991                _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
    992            })
    993        })
    994        .unwrap_or(AttributeType::None)
    995 }
    996 
    997 /// A struct that takes care of encapsulating the cascade process for custom properties.
    998 pub struct CustomPropertiesBuilder<'a, 'b: 'a> {
    999    seen: PrecomputedHashSet<&'a Name>,
   1000    may_have_cycles: bool,
   1001    has_color_scheme: bool,
   1002    custom_properties: ComputedCustomProperties,
   1003    reverted: PrecomputedHashMap<&'a Name, (CascadePriority, bool)>,
   1004    stylist: &'a Stylist,
   1005    computed_context: &'a mut computed::Context<'b>,
   1006    references_from_non_custom_properties: NonCustomReferenceMap<Vec<Name>>,
   1007 }
   1008 
   1009 fn find_non_custom_references(
   1010    registration: &PropertyRegistrationData,
   1011    value: &VariableValue,
   1012    may_have_color_scheme: bool,
   1013    is_root_element: bool,
   1014    include_universal: bool,
   1015 ) -> Option<NonCustomReferences> {
   1016    let dependent_types = registration.syntax.dependent_types();
   1017    let may_reference_length = dependent_types.intersects(DependentDataTypes::LENGTH)
   1018        || (include_universal && registration.syntax.is_universal());
   1019    if may_reference_length {
   1020        let value_dependencies = value.references.non_custom_references(is_root_element);
   1021        if !value_dependencies.is_empty() {
   1022            return Some(value_dependencies);
   1023        }
   1024    }
   1025    if dependent_types.intersects(DependentDataTypes::COLOR) && may_have_color_scheme {
   1026        // NOTE(emilio): We might want to add a NonCustomReferences::COLOR_SCHEME or something but
   1027        // it's not really needed for correctness, so for now we use an Option for that to signal
   1028        // that there might be a dependencies.
   1029        return Some(NonCustomReferences::empty());
   1030    }
   1031    None
   1032 }
   1033 
   1034 impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
   1035    /// Create a new builder, inheriting from a given custom properties map.
   1036    ///
   1037    /// We expose this publicly mostly for @keyframe blocks.
   1038    pub fn new_with_properties(
   1039        stylist: &'a Stylist,
   1040        custom_properties: ComputedCustomProperties,
   1041        computed_context: &'a mut computed::Context<'b>,
   1042    ) -> Self {
   1043        Self {
   1044            seen: PrecomputedHashSet::default(),
   1045            reverted: Default::default(),
   1046            may_have_cycles: false,
   1047            has_color_scheme: false,
   1048            custom_properties,
   1049            stylist,
   1050            computed_context,
   1051            references_from_non_custom_properties: NonCustomReferenceMap::default(),
   1052        }
   1053    }
   1054 
   1055    /// Create a new builder, inheriting from the right style given context.
   1056    pub fn new(stylist: &'a Stylist, context: &'a mut computed::Context<'b>) -> Self {
   1057        let is_root_element = context.is_root_element();
   1058 
   1059        let inherited = context.inherited_custom_properties();
   1060        let initial_values = stylist.get_custom_property_initial_values();
   1061        let properties = ComputedCustomProperties {
   1062            inherited: if is_root_element {
   1063                debug_assert!(inherited.is_empty());
   1064                initial_values.inherited.clone()
   1065            } else {
   1066                inherited.inherited.clone()
   1067            },
   1068            non_inherited: initial_values.non_inherited.clone(),
   1069        };
   1070 
   1071        // Reuse flags from computing registered custom properties initial values, such as
   1072        // whether they depend on viewport units.
   1073        context
   1074            .style()
   1075            .add_flags(stylist.get_custom_property_initial_values_flags());
   1076        Self::new_with_properties(stylist, properties, context)
   1077    }
   1078 
   1079    /// Cascade a given custom property declaration.
   1080    pub fn cascade(
   1081        &mut self,
   1082        declaration: &'a CustomDeclaration,
   1083        priority: CascadePriority,
   1084        attr_provider: &dyn AttributeProvider,
   1085    ) {
   1086        let CustomDeclaration {
   1087            ref name,
   1088            ref value,
   1089        } = *declaration;
   1090 
   1091        if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&name) {
   1092            if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) {
   1093                return;
   1094            }
   1095        }
   1096 
   1097        let was_already_present = !self.seen.insert(name);
   1098        if was_already_present {
   1099            return;
   1100        }
   1101 
   1102        if !self.value_may_affect_style(name, value) {
   1103            return;
   1104        }
   1105 
   1106        let map = &mut self.custom_properties;
   1107        let registration = self.stylist.get_custom_property_registration(&name);
   1108        match value {
   1109            CustomDeclarationValue::Unparsed(unparsed_value) => {
   1110                // At this point of the cascade we're not guaranteed to have seen the color-scheme
   1111                // declaration, so need to assume the worst. We could track all system color
   1112                // keyword tokens + the light-dark() function, but that seems non-trivial /
   1113                // probably overkill.
   1114                let may_have_color_scheme = true;
   1115                // Non-custom dependency is really relevant for registered custom properties
   1116                // that require computed value of such dependencies.
   1117                let has_dependency = unparsed_value.references.any_var
   1118                    || unparsed_value.references.any_attr
   1119                    || find_non_custom_references(
   1120                        registration,
   1121                        unparsed_value,
   1122                        may_have_color_scheme,
   1123                        self.computed_context.is_root_element(),
   1124                        /* include_unregistered = */ false,
   1125                    )
   1126                    .is_some();
   1127                // If the variable value has no references to other properties, perform
   1128                // substitution here instead of forcing a full traversal in `substitute_all`
   1129                // afterwards.
   1130                if !has_dependency {
   1131                    return substitute_references_if_needed_and_apply(
   1132                        name,
   1133                        unparsed_value,
   1134                        map,
   1135                        self.stylist,
   1136                        self.computed_context,
   1137                        attr_provider,
   1138                    );
   1139                }
   1140                self.may_have_cycles = true;
   1141                let value = ComputedRegisteredValue::universal(Arc::clone(unparsed_value));
   1142                map.insert(registration, name, value);
   1143            },
   1144            CustomDeclarationValue::Parsed(parsed_value) => {
   1145                let value = parsed_value.to_computed_value(&self.computed_context);
   1146                map.insert(registration, name, value);
   1147            },
   1148            CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword {
   1149                CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => {
   1150                    let origin_revert = matches!(keyword, CSSWideKeyword::Revert);
   1151                    self.seen.remove(name);
   1152                    self.reverted.insert(name, (priority, origin_revert));
   1153                },
   1154                CSSWideKeyword::Initial => {
   1155                    // For non-inherited custom properties, 'initial' was handled in value_may_affect_style.
   1156                    debug_assert!(registration.inherits(), "Should've been handled earlier");
   1157                    remove_and_insert_initial_value(name, registration, map);
   1158                },
   1159                CSSWideKeyword::Inherit => {
   1160                    // For inherited custom properties, 'inherit' was handled in value_may_affect_style.
   1161                    debug_assert!(!registration.inherits(), "Should've been handled earlier");
   1162                    if let Some(inherited_value) = self
   1163                        .computed_context
   1164                        .inherited_custom_properties()
   1165                        .non_inherited
   1166                        .get(name)
   1167                    {
   1168                        map.insert(registration, name, inherited_value.clone());
   1169                    }
   1170                },
   1171                // handled in value_may_affect_style
   1172                CSSWideKeyword::Unset => unreachable!(),
   1173            },
   1174        }
   1175    }
   1176 
   1177    /// Fast check to avoid calling maybe_note_non_custom_dependency in ~all cases.
   1178    #[inline]
   1179    pub fn might_have_non_custom_dependency(id: LonghandId, decl: &PropertyDeclaration) -> bool {
   1180        if id == LonghandId::ColorScheme {
   1181            return true;
   1182        }
   1183        if matches!(id, LonghandId::LineHeight | LonghandId::FontSize) {
   1184            return matches!(decl, PropertyDeclaration::WithVariables(..));
   1185        }
   1186        false
   1187    }
   1188 
   1189    /// Note a non-custom property with variable reference that may in turn depend on that property.
   1190    /// e.g. `font-size` depending on a custom property that may be a registered property using `em`.
   1191    pub fn maybe_note_non_custom_dependency(&mut self, id: LonghandId, decl: &PropertyDeclaration) {
   1192        debug_assert!(Self::might_have_non_custom_dependency(id, decl));
   1193        if id == LonghandId::ColorScheme {
   1194            // If we might change the color-scheme, we need to defer computation of colors.
   1195            self.has_color_scheme = true;
   1196            return;
   1197        }
   1198 
   1199        let refs = match decl {
   1200            PropertyDeclaration::WithVariables(ref v) => &v.value.variable_value.references,
   1201            _ => return,
   1202        };
   1203 
   1204        if !refs.any_var && !refs.any_attr {
   1205            return;
   1206        }
   1207 
   1208        // With unit algebra in `calc()`, references aren't limited to `font-size`.
   1209        // For example, `--foo: 100ex; font-weight: calc(var(--foo) / 1ex);`,
   1210        // or `--foo: 1em; zoom: calc(var(--foo) * 30px / 2em);`
   1211        let references = match id {
   1212            LonghandId::FontSize => {
   1213                if self.computed_context.is_root_element() {
   1214                    NonCustomReferences::ROOT_FONT_UNITS
   1215                } else {
   1216                    NonCustomReferences::FONT_UNITS
   1217                }
   1218            },
   1219            LonghandId::LineHeight => {
   1220                if self.computed_context.is_root_element() {
   1221                    NonCustomReferences::ROOT_LH_UNITS | NonCustomReferences::ROOT_FONT_UNITS
   1222                } else {
   1223                    NonCustomReferences::LH_UNITS | NonCustomReferences::FONT_UNITS
   1224                }
   1225            },
   1226            _ => return,
   1227        };
   1228 
   1229        let variables: Vec<Atom> = refs
   1230            .refs
   1231            .iter()
   1232            .filter_map(|reference| {
   1233                if reference.substitution_kind != SubstitutionFunctionKind::Var {
   1234                    return None;
   1235                }
   1236                let registration = self
   1237                    .stylist
   1238                    .get_custom_property_registration(&reference.name);
   1239                if !registration
   1240                    .syntax
   1241                    .dependent_types()
   1242                    .intersects(DependentDataTypes::LENGTH)
   1243                {
   1244                    return None;
   1245                }
   1246                Some(reference.name.clone())
   1247            })
   1248            .collect();
   1249        references.for_each(|idx| {
   1250            let entry = &mut self.references_from_non_custom_properties[idx];
   1251            let was_none = entry.is_none();
   1252            let v = entry.get_or_insert_with(|| variables.clone());
   1253            if was_none {
   1254                return;
   1255            }
   1256            v.extend(variables.iter().cloned());
   1257        });
   1258    }
   1259 
   1260    fn value_may_affect_style(&self, name: &Name, value: &CustomDeclarationValue) -> bool {
   1261        let registration = self.stylist.get_custom_property_registration(&name);
   1262        match *value {
   1263            CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit) => {
   1264                // For inherited custom properties, explicit 'inherit' means we
   1265                // can just use any existing value in the inherited
   1266                // CustomPropertiesMap.
   1267                if registration.inherits() {
   1268                    return false;
   1269                }
   1270            },
   1271            CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial) => {
   1272                // For non-inherited custom properties, explicit 'initial' means
   1273                // we can just use any initial value in the registration.
   1274                if !registration.inherits() {
   1275                    return false;
   1276                }
   1277            },
   1278            CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Unset) => {
   1279                // Explicit 'unset' means we can either just use any existing
   1280                // value in the inherited CustomPropertiesMap or the initial
   1281                // value in the registration.
   1282                return false;
   1283            },
   1284            _ => {},
   1285        }
   1286 
   1287        let existing_value = self.custom_properties.get(registration, &name);
   1288        let existing_value = match existing_value {
   1289            None => {
   1290                if matches!(
   1291                    value,
   1292                    CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial)
   1293                ) {
   1294                    debug_assert!(registration.inherits(), "Should've been handled earlier");
   1295                    // The initial value of a custom property without a
   1296                    // guaranteed-invalid initial value is the same as it
   1297                    // not existing in the map.
   1298                    if registration.initial_value.is_none() {
   1299                        return false;
   1300                    }
   1301                }
   1302                return true;
   1303            },
   1304            Some(v) => v,
   1305        };
   1306        let computed_value = match value {
   1307            CustomDeclarationValue::Unparsed(value) => {
   1308                // Don't bother overwriting an existing value with the same
   1309                // specified value.
   1310                if let Some(existing_value) = existing_value.as_universal() {
   1311                    return existing_value != value;
   1312                }
   1313                if !registration.syntax.is_universal() {
   1314                    compute_value(
   1315                        &value.css,
   1316                        &value.url_data,
   1317                        registration,
   1318                        self.computed_context,
   1319                    )
   1320                    .ok()
   1321                } else {
   1322                    None
   1323                }
   1324            },
   1325            CustomDeclarationValue::Parsed(value) => {
   1326                Some(value.to_computed_value(&self.computed_context))
   1327            },
   1328            CustomDeclarationValue::CSSWideKeyword(kw) => {
   1329                match kw {
   1330                    CSSWideKeyword::Inherit => {
   1331                        debug_assert!(!registration.inherits(), "Should've been handled earlier");
   1332                        // existing_value is the registered initial value.
   1333                        // Don't bother adding it to self.custom_properties.non_inherited
   1334                        // if the key is also absent from self.inherited.non_inherited.
   1335                        if self
   1336                            .computed_context
   1337                            .inherited_custom_properties()
   1338                            .non_inherited
   1339                            .get(name)
   1340                            .is_none()
   1341                        {
   1342                            return false;
   1343                        }
   1344                    },
   1345                    CSSWideKeyword::Initial => {
   1346                        debug_assert!(registration.inherits(), "Should've been handled earlier");
   1347                        // Don't bother overwriting an existing value with the initial value specified in
   1348                        // the registration.
   1349                        if let Some(initial_value) = self
   1350                            .stylist
   1351                            .get_custom_property_initial_values()
   1352                            .get(registration, name)
   1353                        {
   1354                            return existing_value != initial_value;
   1355                        }
   1356                    },
   1357                    CSSWideKeyword::Unset => {
   1358                        debug_assert!(false, "Should've been handled earlier");
   1359                    },
   1360                    CSSWideKeyword::Revert | CSSWideKeyword::RevertLayer => {},
   1361                }
   1362                None
   1363            },
   1364        };
   1365 
   1366        if let Some(value) = computed_value {
   1367            return existing_value.v != value.v;
   1368        }
   1369 
   1370        true
   1371    }
   1372 
   1373    /// Computes the map of applicable custom properties, as well as
   1374    /// longhand properties that are now considered invalid-at-compute time.
   1375    /// The result is saved into the computed context.
   1376    ///
   1377    /// If there was any specified property or non-inherited custom property
   1378    /// with an initial value, we've created a new map and now we
   1379    /// need to remove any potential cycles (And marking non-custom
   1380    /// properties), and wrap it in an arc.
   1381    ///
   1382    /// Some registered custom properties may require font-related properties
   1383    /// be resolved to resolve. If these properties are not resolved at this time,
   1384    /// `defer` should be set to `Yes`, which will leave such custom properties,
   1385    /// and other properties referencing them, untouched. These properties are
   1386    /// returned separately, to be resolved by `build_deferred` to fully resolve
   1387    /// all custom properties after all necessary non-custom properties are resolved.
   1388    pub fn build(
   1389        mut self,
   1390        defer: DeferFontRelativeCustomPropertyResolution,
   1391        attr_provider: &dyn AttributeProvider,
   1392    ) -> Option<CustomPropertiesMap> {
   1393        let mut deferred_custom_properties = None;
   1394        if self.may_have_cycles {
   1395            if defer == DeferFontRelativeCustomPropertyResolution::Yes {
   1396                deferred_custom_properties = Some(CustomPropertiesMap::default());
   1397            }
   1398            let mut invalid_non_custom_properties = LonghandIdSet::default();
   1399            substitute_all(
   1400                &mut self.custom_properties,
   1401                deferred_custom_properties.as_mut(),
   1402                &mut invalid_non_custom_properties,
   1403                self.has_color_scheme,
   1404                &self.seen,
   1405                &self.references_from_non_custom_properties,
   1406                self.stylist,
   1407                self.computed_context,
   1408                attr_provider,
   1409            );
   1410            self.computed_context.builder.invalid_non_custom_properties =
   1411                invalid_non_custom_properties;
   1412        }
   1413 
   1414        self.custom_properties.shrink_to_fit();
   1415 
   1416        // Some pages apply a lot of redundant custom properties, see e.g.
   1417        // bug 1758974 comment 5. Try to detect the case where the values
   1418        // haven't really changed, and save some memory by reusing the inherited
   1419        // map in that case.
   1420        let initial_values = self.stylist.get_custom_property_initial_values();
   1421        self.computed_context.builder.custom_properties = ComputedCustomProperties {
   1422            inherited: if self
   1423                .computed_context
   1424                .inherited_custom_properties()
   1425                .inherited
   1426                == self.custom_properties.inherited
   1427            {
   1428                self.computed_context
   1429                    .inherited_custom_properties()
   1430                    .inherited
   1431                    .clone()
   1432            } else {
   1433                self.custom_properties.inherited
   1434            },
   1435            non_inherited: if initial_values.non_inherited == self.custom_properties.non_inherited {
   1436                initial_values.non_inherited.clone()
   1437            } else {
   1438                self.custom_properties.non_inherited
   1439            },
   1440        };
   1441 
   1442        deferred_custom_properties
   1443    }
   1444 
   1445    /// Fully resolve all deferred custom properties, assuming that the incoming context
   1446    /// has necessary properties resolved.
   1447    pub fn build_deferred(
   1448        deferred: CustomPropertiesMap,
   1449        stylist: &Stylist,
   1450        computed_context: &mut computed::Context,
   1451        attr_provider: &dyn AttributeProvider,
   1452    ) {
   1453        if deferred.is_empty() {
   1454            return;
   1455        }
   1456        let mut custom_properties = std::mem::take(&mut computed_context.builder.custom_properties);
   1457        // Since `CustomPropertiesMap` preserves insertion order, we shouldn't have to worry about
   1458        // resolving in a wrong order.
   1459        for (k, v) in deferred.iter() {
   1460            let Some(v) = v else { continue };
   1461            let Some(v) = v.as_universal() else {
   1462                unreachable!("Computing should have been deferred!")
   1463            };
   1464            substitute_references_if_needed_and_apply(
   1465                k,
   1466                v,
   1467                &mut custom_properties,
   1468                stylist,
   1469                computed_context,
   1470                attr_provider,
   1471            );
   1472        }
   1473        computed_context.builder.custom_properties = custom_properties;
   1474    }
   1475 }
   1476 
   1477 /// Resolve all custom properties to either substituted, invalid, or unset
   1478 /// (meaning we should use the inherited value).
   1479 ///
   1480 /// It does cycle dependencies removal at the same time as substitution.
   1481 fn substitute_all(
   1482    custom_properties_map: &mut ComputedCustomProperties,
   1483    mut deferred_properties_map: Option<&mut CustomPropertiesMap>,
   1484    invalid_non_custom_properties: &mut LonghandIdSet,
   1485    has_color_scheme: bool,
   1486    seen: &PrecomputedHashSet<&Name>,
   1487    references_from_non_custom_properties: &NonCustomReferenceMap<Vec<Name>>,
   1488    stylist: &Stylist,
   1489    computed_context: &computed::Context,
   1490    attr_provider: &dyn AttributeProvider,
   1491 ) {
   1492    // The cycle dependencies removal in this function is a variant
   1493    // of Tarjan's algorithm. It is mostly based on the pseudo-code
   1494    // listed in
   1495    // https://en.wikipedia.org/w/index.php?
   1496    // title=Tarjan%27s_strongly_connected_components_algorithm&oldid=801728495
   1497 
   1498    #[derive(Clone, Eq, PartialEq, Debug)]
   1499    enum VarType {
   1500        Custom(Name),
   1501        NonCustom(SingleNonCustomReference),
   1502    }
   1503 
   1504    /// Struct recording necessary information for each variable.
   1505    #[derive(Debug)]
   1506    struct VarInfo {
   1507        /// The name of the variable. It will be taken to save addref
   1508        /// when the corresponding variable is popped from the stack.
   1509        /// This also serves as a mark for whether the variable is
   1510        /// currently in the stack below.
   1511        var: Option<VarType>,
   1512        /// If the variable is in a dependency cycle, lowlink represents
   1513        /// a smaller index which corresponds to a variable in the same
   1514        /// strong connected component, which is known to be accessible
   1515        /// from this variable. It is not necessarily the root, though.
   1516        lowlink: usize,
   1517    }
   1518    /// Context struct for traversing the variable graph, so that we can
   1519    /// avoid referencing all the fields multiple times.
   1520    struct Context<'a, 'b: 'a> {
   1521        /// Number of variables visited. This is used as the order index
   1522        /// when we visit a new unresolved variable.
   1523        count: usize,
   1524        /// The map from custom property name to its order index.
   1525        index_map: PrecomputedHashMap<Name, usize>,
   1526        /// Mapping from a non-custom dependency to its order index.
   1527        non_custom_index_map: NonCustomReferenceMap<usize>,
   1528        /// Information of each variable indexed by the order index.
   1529        var_info: SmallVec<[VarInfo; 5]>,
   1530        /// The stack of order index of visited variables. It contains
   1531        /// all unfinished strong connected components.
   1532        stack: SmallVec<[usize; 5]>,
   1533        /// References to non-custom properties in this strongly connected component.
   1534        non_custom_references: NonCustomReferences,
   1535        /// Whether the builder has seen a non-custom color-scheme reference.
   1536        has_color_scheme: bool,
   1537        /// Whether this strongly connected component contains any custom properties involving
   1538        /// value computation.
   1539        contains_computed_custom_property: bool,
   1540        map: &'a mut ComputedCustomProperties,
   1541        /// The stylist is used to get registered properties, and to resolve the environment to
   1542        /// substitute `env()` variables.
   1543        stylist: &'a Stylist,
   1544        /// The computed context is used to get inherited custom
   1545        /// properties  and compute registered custom properties.
   1546        computed_context: &'a computed::Context<'b>,
   1547        /// Longhand IDs that became invalid due to dependency cycle(s).
   1548        invalid_non_custom_properties: &'a mut LonghandIdSet,
   1549        /// Properties that cannot yet be substituted. Note we store both inherited and
   1550        /// non-inherited properties in the same map, since we need to make sure we iterate through
   1551        /// them in the right order.
   1552        deferred_properties: Option<&'a mut CustomPropertiesMap>,
   1553    }
   1554 
   1555    /// This function combines the traversal for cycle removal and value
   1556    /// substitution. It returns either a signal None if this variable
   1557    /// has been fully resolved (to either having no reference or being
   1558    /// marked invalid), or the order index for the given name.
   1559    ///
   1560    /// When it returns, the variable corresponds to the name would be
   1561    /// in one of the following states:
   1562    /// * It is still in context.stack, which means it is part of an
   1563    ///   potentially incomplete dependency circle.
   1564    /// * It has been removed from the map.  It can be either that the
   1565    ///   substitution failed, or it is inside a dependency circle.
   1566    ///   When this function removes a variable from the map because
   1567    ///   of dependency circle, it would put all variables in the same
   1568    ///   strong connected component to the set together.
   1569    /// * It doesn't have any reference, because either this variable
   1570    ///   doesn't have reference at all in specified value, or it has
   1571    ///   been completely resolved.
   1572    /// * There is no such variable at all.
   1573    fn traverse<'a, 'b>(
   1574        var: VarType,
   1575        non_custom_references: &NonCustomReferenceMap<Vec<Name>>,
   1576        context: &mut Context<'a, 'b>,
   1577        attr_provider: &dyn AttributeProvider,
   1578    ) -> Option<usize> {
   1579        // Some shortcut checks.
   1580        let value = match var {
   1581            VarType::Custom(ref name) => {
   1582                let registration = context.stylist.get_custom_property_registration(name);
   1583                let value = context.map.get(registration, name)?.as_universal()?;
   1584                let is_root = context.computed_context.is_root_element();
   1585                // We need to keep track of potential non-custom-references even on unregistered
   1586                // properties for cycle-detection purposes.
   1587                let non_custom_refs = find_non_custom_references(
   1588                    registration,
   1589                    value,
   1590                    context.has_color_scheme,
   1591                    is_root,
   1592                    /* include_unregistered = */ true,
   1593                );
   1594                context.non_custom_references |= non_custom_refs.unwrap_or_default();
   1595                let has_dependency = value.references.any_var
   1596                    || value.references.any_attr
   1597                    || non_custom_refs.is_some();
   1598                // Nothing to resolve.
   1599                if !has_dependency {
   1600                    debug_assert!(!value.references.any_env, "Should've been handled earlier");
   1601                    if !registration.syntax.is_universal() {
   1602                        // We might still need to compute the value if this is not an universal
   1603                        // registration if we thought this had a dependency before but turned out
   1604                        // not to be (due to has_color_scheme, for example). Note that if this was
   1605                        // already computed we would've bailed out in the as_universal() check.
   1606                        debug_assert!(
   1607                            registration
   1608                                .syntax
   1609                                .dependent_types()
   1610                                .intersects(DependentDataTypes::COLOR),
   1611                            "How did an unresolved value get here otherwise?",
   1612                        );
   1613                        let value = value.clone();
   1614                        substitute_references_if_needed_and_apply(
   1615                            name,
   1616                            &value,
   1617                            &mut context.map,
   1618                            context.stylist,
   1619                            context.computed_context,
   1620                            attr_provider,
   1621                        );
   1622                    }
   1623                    return None;
   1624                }
   1625 
   1626                // Has this variable been visited?
   1627                match context.index_map.entry(name.clone()) {
   1628                    Entry::Occupied(entry) => {
   1629                        return Some(*entry.get());
   1630                    },
   1631                    Entry::Vacant(entry) => {
   1632                        entry.insert(context.count);
   1633                    },
   1634                }
   1635                context.contains_computed_custom_property |= !registration.syntax.is_universal();
   1636 
   1637                // Hold a strong reference to the value so that we don't
   1638                // need to keep reference to context.map.
   1639                Some(value.clone())
   1640            },
   1641            VarType::NonCustom(ref non_custom) => {
   1642                let entry = &mut context.non_custom_index_map[*non_custom];
   1643                if let Some(v) = entry {
   1644                    return Some(*v);
   1645                }
   1646                *entry = Some(context.count);
   1647                None
   1648            },
   1649        };
   1650 
   1651        // Add new entry to the information table.
   1652        let index = context.count;
   1653        context.count += 1;
   1654        debug_assert_eq!(index, context.var_info.len());
   1655        context.var_info.push(VarInfo {
   1656            var: Some(var.clone()),
   1657            lowlink: index,
   1658        });
   1659        context.stack.push(index);
   1660 
   1661        let mut self_ref = false;
   1662        let mut lowlink = index;
   1663        let visit_link =
   1664            |var: VarType, context: &mut Context, lowlink: &mut usize, self_ref: &mut bool| {
   1665                let next_index = match traverse(var, non_custom_references, context, attr_provider)
   1666                {
   1667                    Some(index) => index,
   1668                    // There is nothing to do if the next variable has been
   1669                    // fully resolved at this point.
   1670                    None => {
   1671                        return;
   1672                    },
   1673                };
   1674                let next_info = &context.var_info[next_index];
   1675                if next_index > index {
   1676                    // The next variable has a larger index than us, so it
   1677                    // must be inserted in the recursive call above. We want
   1678                    // to get its lowlink.
   1679                    *lowlink = cmp::min(*lowlink, next_info.lowlink);
   1680                } else if next_index == index {
   1681                    *self_ref = true;
   1682                } else if next_info.var.is_some() {
   1683                    // The next variable has a smaller order index and it is
   1684                    // in the stack, so we are at the same component.
   1685                    *lowlink = cmp::min(*lowlink, next_index);
   1686                }
   1687            };
   1688        if let Some(ref v) = value.as_ref() {
   1689            debug_assert!(
   1690                matches!(var, VarType::Custom(_)),
   1691                "Non-custom property has references?"
   1692            );
   1693 
   1694            // Visit other custom properties...
   1695            // FIXME: Maybe avoid visiting the same var twice if not needed?
   1696            for next in &v.references.refs {
   1697                if next.substitution_kind != SubstitutionFunctionKind::Var {
   1698                    continue;
   1699                }
   1700                visit_link(
   1701                    VarType::Custom(next.name.clone()),
   1702                    context,
   1703                    &mut lowlink,
   1704                    &mut self_ref,
   1705                );
   1706            }
   1707 
   1708            // ... Then non-custom properties.
   1709            v.references.non_custom_references.for_each(|r| {
   1710                visit_link(VarType::NonCustom(r), context, &mut lowlink, &mut self_ref);
   1711            });
   1712        } else if let VarType::NonCustom(non_custom) = var {
   1713            let entry = &non_custom_references[non_custom];
   1714            if let Some(deps) = entry.as_ref() {
   1715                for d in deps {
   1716                    // Visit any reference from this non-custom property to custom properties.
   1717                    visit_link(
   1718                        VarType::Custom(d.clone()),
   1719                        context,
   1720                        &mut lowlink,
   1721                        &mut self_ref,
   1722                    );
   1723                }
   1724            }
   1725        }
   1726 
   1727        context.var_info[index].lowlink = lowlink;
   1728        if lowlink != index {
   1729            // This variable is in a loop, but it is not the root of
   1730            // this strong connected component. We simply return for
   1731            // now, and the root would remove it from the map.
   1732            //
   1733            // This cannot be removed from the map here, because
   1734            // otherwise the shortcut check at the beginning of this
   1735            // function would return the wrong value.
   1736            return Some(index);
   1737        }
   1738 
   1739        // This is the root of a strong-connected component.
   1740        let mut in_loop = self_ref;
   1741        let name;
   1742 
   1743        let handle_variable_in_loop = |name: &Name, context: &mut Context<'a, 'b>| {
   1744            if context.contains_computed_custom_property {
   1745                // These non-custom properties can't become invalid-at-compute-time from
   1746                // cyclic dependencies purely consisting of non-registered properties.
   1747                if context.non_custom_references.intersects(
   1748                    NonCustomReferences::FONT_UNITS | NonCustomReferences::ROOT_FONT_UNITS,
   1749                ) {
   1750                    context
   1751                        .invalid_non_custom_properties
   1752                        .insert(LonghandId::FontSize);
   1753                }
   1754                if context
   1755                    .non_custom_references
   1756                    .intersects(NonCustomReferences::LH_UNITS | NonCustomReferences::ROOT_LH_UNITS)
   1757                {
   1758                    context
   1759                        .invalid_non_custom_properties
   1760                        .insert(LonghandId::LineHeight);
   1761                }
   1762            }
   1763            // This variable is in loop. Resolve to invalid.
   1764            handle_invalid_at_computed_value_time(name, context.map, context.computed_context);
   1765        };
   1766        loop {
   1767            let var_index = context
   1768                .stack
   1769                .pop()
   1770                .expect("The current variable should still be in stack");
   1771            let var_info = &mut context.var_info[var_index];
   1772            // We should never visit the variable again, so it's safe
   1773            // to take the name away, so that we don't do additional
   1774            // reference count.
   1775            let var_name = var_info
   1776                .var
   1777                .take()
   1778                .expect("Variable should not be poped from stack twice");
   1779            if var_index == index {
   1780                name = match var_name {
   1781                    VarType::Custom(name) => name,
   1782                    // At the root of this component, and it's a non-custom
   1783                    // reference - we have nothing to substitute, so
   1784                    // it's effectively resolved.
   1785                    VarType::NonCustom(..) => return None,
   1786                };
   1787                break;
   1788            }
   1789            if let VarType::Custom(name) = var_name {
   1790                // Anything here is in a loop which can traverse to the
   1791                // variable we are handling, so it's invalid at
   1792                // computed-value time.
   1793                handle_variable_in_loop(&name, context);
   1794            }
   1795            in_loop = true;
   1796        }
   1797        // We've gotten to the root of this strongly connected component, so clear
   1798        // whether or not it involved non-custom references.
   1799        // It's fine to track it like this, because non-custom properties currently
   1800        // being tracked can only participate in any loop only once.
   1801        if in_loop {
   1802            handle_variable_in_loop(&name, context);
   1803            context.non_custom_references = NonCustomReferences::default();
   1804            return None;
   1805        }
   1806 
   1807        if let Some(ref v) = value {
   1808            let registration = context.stylist.get_custom_property_registration(&name);
   1809 
   1810            let mut defer = false;
   1811            if let Some(ref mut deferred) = context.deferred_properties {
   1812                // We need to defer this property if it has a non-custom property dependency, or
   1813                // any variable that it references is already deferred.
   1814                defer = find_non_custom_references(
   1815                    registration,
   1816                    v,
   1817                    context.has_color_scheme,
   1818                    context.computed_context.is_root_element(),
   1819                    /* include_unregistered = */ false,
   1820                )
   1821                .is_some()
   1822                    || v.references.refs.iter().any(|reference| {
   1823                        (reference.substitution_kind == SubstitutionFunctionKind::Var
   1824                            && deferred.get(&reference.name).is_some())
   1825                            || reference.substitution_kind == SubstitutionFunctionKind::Attr
   1826                    });
   1827 
   1828                if defer {
   1829                    let value = ComputedRegisteredValue::universal(Arc::clone(v));
   1830                    deferred.insert(&name, value);
   1831                    context.map.remove(registration, &name);
   1832                }
   1833            }
   1834 
   1835            // If there are no var references we should already be computed and substituted by now.
   1836            if !defer && (v.references.any_var || v.references.any_attr) {
   1837                substitute_references_if_needed_and_apply(
   1838                    &name,
   1839                    v,
   1840                    &mut context.map,
   1841                    context.stylist,
   1842                    context.computed_context,
   1843                    attr_provider,
   1844                );
   1845            }
   1846        }
   1847        context.non_custom_references = NonCustomReferences::default();
   1848 
   1849        // All resolved, so return the signal value.
   1850        None
   1851    }
   1852 
   1853    // Note that `seen` doesn't contain names inherited from our parent, but
   1854    // those can't have variable references (since we inherit the computed
   1855    // variables) so we don't want to spend cycles traversing them anyway.
   1856    for name in seen {
   1857        let mut context = Context {
   1858            count: 0,
   1859            index_map: PrecomputedHashMap::default(),
   1860            non_custom_index_map: NonCustomReferenceMap::default(),
   1861            stack: SmallVec::new(),
   1862            var_info: SmallVec::new(),
   1863            map: custom_properties_map,
   1864            non_custom_references: NonCustomReferences::default(),
   1865            has_color_scheme,
   1866            stylist,
   1867            computed_context,
   1868            invalid_non_custom_properties,
   1869            deferred_properties: deferred_properties_map.as_deref_mut(),
   1870            contains_computed_custom_property: false,
   1871        };
   1872        traverse(
   1873            VarType::Custom((*name).clone()),
   1874            references_from_non_custom_properties,
   1875            &mut context,
   1876            attr_provider,
   1877        );
   1878    }
   1879 }
   1880 
   1881 // See https://drafts.csswg.org/css-variables-2/#invalid-at-computed-value-time
   1882 fn handle_invalid_at_computed_value_time(
   1883    name: &Name,
   1884    custom_properties: &mut ComputedCustomProperties,
   1885    computed_context: &computed::Context,
   1886 ) {
   1887    let stylist = computed_context.style().stylist.unwrap();
   1888    let registration = stylist.get_custom_property_registration(&name);
   1889    if !registration.syntax.is_universal() {
   1890        // For the root element, inherited maps are empty. We should just
   1891        // use the initial value if any, rather than removing the name.
   1892        if registration.inherits() && !computed_context.is_root_element() {
   1893            let inherited = computed_context.inherited_custom_properties();
   1894            if let Some(value) = inherited.get(registration, name) {
   1895                custom_properties.insert(registration, name, value.clone());
   1896                return;
   1897            }
   1898        } else if let Some(ref initial_value) = registration.initial_value {
   1899            if let Ok(initial_value) = compute_value(
   1900                &initial_value.css,
   1901                &initial_value.url_data,
   1902                registration,
   1903                computed_context,
   1904            ) {
   1905                custom_properties.insert(registration, name, initial_value);
   1906                return;
   1907            }
   1908        }
   1909    }
   1910    custom_properties.remove(registration, name);
   1911 }
   1912 
   1913 /// Replace `var()`, `env()`, and `attr()` functions in a pre-existing variable value.
   1914 fn substitute_references_if_needed_and_apply(
   1915    name: &Name,
   1916    value: &Arc<VariableValue>,
   1917    custom_properties: &mut ComputedCustomProperties,
   1918    stylist: &Stylist,
   1919    computed_context: &computed::Context,
   1920    attr_provider: &dyn AttributeProvider,
   1921 ) {
   1922    let registration = stylist.get_custom_property_registration(&name);
   1923    if !value.has_references() && registration.syntax.is_universal() {
   1924        // Trivial path: no references and no need to compute the value, just apply it directly.
   1925        let computed_value = ComputedRegisteredValue::universal(Arc::clone(value));
   1926        custom_properties.insert(registration, name, computed_value);
   1927        return;
   1928    }
   1929 
   1930    let inherited = computed_context.inherited_custom_properties();
   1931    let url_data = &value.url_data;
   1932    let substitution = match substitute_internal(
   1933        value,
   1934        custom_properties,
   1935        stylist,
   1936        computed_context,
   1937        attr_provider,
   1938    ) {
   1939        Ok(v) => v,
   1940        Err(..) => {
   1941            handle_invalid_at_computed_value_time(name, custom_properties, computed_context);
   1942            return;
   1943        },
   1944    };
   1945 
   1946    // If variable fallback results in a wide keyword, deal with it now.
   1947    {
   1948        let css = &substitution.css;
   1949        let css_wide_kw = {
   1950            let mut input = ParserInput::new(&css);
   1951            let mut input = Parser::new(&mut input);
   1952            input.try_parse(CSSWideKeyword::parse)
   1953        };
   1954 
   1955        if let Ok(kw) = css_wide_kw {
   1956            // TODO: It's unclear what this should do for revert / revert-layer, see
   1957            // https://github.com/w3c/csswg-drafts/issues/9131. For now treating as unset
   1958            // seems fine?
   1959            match (
   1960                kw,
   1961                registration.inherits(),
   1962                computed_context.is_root_element(),
   1963            ) {
   1964                (CSSWideKeyword::Initial, _, _)
   1965                | (CSSWideKeyword::Revert, false, _)
   1966                | (CSSWideKeyword::RevertLayer, false, _)
   1967                | (CSSWideKeyword::Unset, false, _)
   1968                | (CSSWideKeyword::Revert, true, true)
   1969                | (CSSWideKeyword::RevertLayer, true, true)
   1970                | (CSSWideKeyword::Unset, true, true)
   1971                | (CSSWideKeyword::Inherit, _, true) => {
   1972                    remove_and_insert_initial_value(name, registration, custom_properties);
   1973                },
   1974                (CSSWideKeyword::Revert, true, false)
   1975                | (CSSWideKeyword::RevertLayer, true, false)
   1976                | (CSSWideKeyword::Inherit, _, false)
   1977                | (CSSWideKeyword::Unset, true, false) => {
   1978                    match inherited.get(registration, name) {
   1979                        Some(value) => {
   1980                            custom_properties.insert(registration, name, value.clone());
   1981                        },
   1982                        None => {
   1983                            custom_properties.remove(registration, name);
   1984                        },
   1985                    };
   1986                },
   1987            }
   1988            return;
   1989        }
   1990    }
   1991 
   1992    let value = match substitution.into_value(url_data, registration, computed_context) {
   1993        Ok(v) => v,
   1994        Err(()) => {
   1995            handle_invalid_at_computed_value_time(name, custom_properties, computed_context);
   1996            return;
   1997        },
   1998    };
   1999 
   2000    custom_properties.insert(registration, name, value);
   2001 }
   2002 
   2003 #[derive(Default)]
   2004 struct Substitution<'a> {
   2005    css: Cow<'a, str>,
   2006    first_token_type: TokenSerializationType,
   2007    last_token_type: TokenSerializationType,
   2008 }
   2009 
   2010 impl<'a> Substitution<'a> {
   2011    fn from_value(v: VariableValue) -> Self {
   2012        Substitution {
   2013            css: v.css.into(),
   2014            first_token_type: v.first_token_type,
   2015            last_token_type: v.last_token_type,
   2016        }
   2017    }
   2018 
   2019    fn into_value(
   2020        self,
   2021        url_data: &UrlExtraData,
   2022        registration: &PropertyRegistrationData,
   2023        computed_context: &computed::Context,
   2024    ) -> Result<ComputedRegisteredValue, ()> {
   2025        if registration.syntax.is_universal() {
   2026            return Ok(ComputedRegisteredValue::universal(Arc::new(
   2027                VariableValue {
   2028                    css: self.css.into_owned(),
   2029                    first_token_type: self.first_token_type,
   2030                    last_token_type: self.last_token_type,
   2031                    url_data: url_data.clone(),
   2032                    references: Default::default(),
   2033                },
   2034            )));
   2035        }
   2036        compute_value(&self.css, url_data, registration, computed_context)
   2037    }
   2038 
   2039    fn new(
   2040        css: Cow<'a, str>,
   2041        first_token_type: TokenSerializationType,
   2042        last_token_type: TokenSerializationType,
   2043    ) -> Self {
   2044        Self {
   2045            css,
   2046            first_token_type,
   2047            last_token_type,
   2048        }
   2049    }
   2050 }
   2051 
   2052 fn compute_value(
   2053    css: &str,
   2054    url_data: &UrlExtraData,
   2055    registration: &PropertyRegistrationData,
   2056    computed_context: &computed::Context,
   2057 ) -> Result<ComputedRegisteredValue, ()> {
   2058    debug_assert!(!registration.syntax.is_universal());
   2059 
   2060    let mut input = ParserInput::new(&css);
   2061    let mut input = Parser::new(&mut input);
   2062 
   2063    SpecifiedRegisteredValue::compute(
   2064        &mut input,
   2065        registration,
   2066        url_data,
   2067        computed_context,
   2068        AllowComputationallyDependent::Yes,
   2069    )
   2070 }
   2071 
   2072 /// Removes the named registered custom property and inserts its uncomputed initial value.
   2073 fn remove_and_insert_initial_value(
   2074    name: &Name,
   2075    registration: &PropertyRegistrationData,
   2076    custom_properties: &mut ComputedCustomProperties,
   2077 ) {
   2078    custom_properties.remove(registration, name);
   2079    if let Some(ref initial_value) = registration.initial_value {
   2080        let value = ComputedRegisteredValue::universal(Arc::clone(initial_value));
   2081        custom_properties.insert(registration, name, value);
   2082    }
   2083 }
   2084 
   2085 fn do_substitute_chunk<'a>(
   2086    css: &'a str,
   2087    start: usize,
   2088    end: usize,
   2089    first_token_type: TokenSerializationType,
   2090    last_token_type: TokenSerializationType,
   2091    url_data: &UrlExtraData,
   2092    custom_properties: &'a ComputedCustomProperties,
   2093    stylist: &Stylist,
   2094    computed_context: &computed::Context,
   2095    references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>,
   2096    attr_provider: &dyn AttributeProvider,
   2097 ) -> Result<Substitution<'a>, ()> {
   2098    if start == end {
   2099        // Empty string. Easy.
   2100        return Ok(Substitution::default());
   2101    }
   2102    // Easy case: no references involved.
   2103    if references
   2104        .peek()
   2105        .map_or(true, |reference| reference.end > end)
   2106    {
   2107        let result = &css[start..end];
   2108        return Ok(Substitution::new(
   2109            Cow::Borrowed(result),
   2110            first_token_type,
   2111            last_token_type,
   2112        ));
   2113    }
   2114 
   2115    let mut substituted = ComputedValue::empty(url_data);
   2116    let mut next_token_type = first_token_type;
   2117    let mut cur_pos = start;
   2118    while let Some(reference) = references.next_if(|reference| reference.end <= end) {
   2119        if reference.start != cur_pos {
   2120            substituted.push(
   2121                &css[cur_pos..reference.start],
   2122                next_token_type,
   2123                reference.prev_token_type,
   2124            )?;
   2125        }
   2126 
   2127        let substitution = substitute_one_reference(
   2128            css,
   2129            url_data,
   2130            custom_properties,
   2131            reference,
   2132            stylist,
   2133            computed_context,
   2134            references,
   2135            attr_provider,
   2136        )?;
   2137 
   2138        // Optimize the property: var(--...) case to avoid allocating at all.
   2139        if reference.start == start && reference.end == end {
   2140            return Ok(substitution);
   2141        }
   2142 
   2143        substituted.push(
   2144            &substitution.css,
   2145            substitution.first_token_type,
   2146            substitution.last_token_type,
   2147        )?;
   2148        next_token_type = reference.next_token_type;
   2149        cur_pos = reference.end;
   2150    }
   2151    // Push the rest of the value if needed.
   2152    if cur_pos != end {
   2153        substituted.push(&css[cur_pos..end], next_token_type, last_token_type)?;
   2154    }
   2155    Ok(Substitution::from_value(substituted))
   2156 }
   2157 
   2158 fn quoted_css_string(src: &str) -> String {
   2159    let mut dest = String::with_capacity(src.len() + 2);
   2160    cssparser::serialize_string(src, &mut dest).unwrap();
   2161    dest
   2162 }
   2163 
   2164 fn substitute_one_reference<'a>(
   2165    css: &'a str,
   2166    url_data: &UrlExtraData,
   2167    custom_properties: &'a ComputedCustomProperties,
   2168    reference: &SubstitutionFunctionReference,
   2169    stylist: &Stylist,
   2170    computed_context: &computed::Context,
   2171    references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>,
   2172    attr_provider: &dyn AttributeProvider,
   2173 ) -> Result<Substitution<'a>, ()> {
   2174    let simple_subst = |s: &str| {
   2175        Some(Substitution::new(
   2176            Cow::Owned(quoted_css_string(s)),
   2177            TokenSerializationType::Nothing,
   2178            TokenSerializationType::Nothing,
   2179        ))
   2180    };
   2181    let substitution: Option<_> = match reference.substitution_kind {
   2182        SubstitutionFunctionKind::Var => {
   2183            let registration = stylist.get_custom_property_registration(&reference.name);
   2184            custom_properties
   2185                .get(registration, &reference.name)
   2186                .map(|v| Substitution::from_value(v.to_variable_value()))
   2187        },
   2188        SubstitutionFunctionKind::Env => {
   2189            let device = stylist.device();
   2190            device
   2191                .environment()
   2192                .get(&reference.name, device, url_data)
   2193                .map(Substitution::from_value)
   2194        },
   2195        // https://drafts.csswg.org/css-values-5/#attr-substitution
   2196        SubstitutionFunctionKind::Attr => attr_provider
   2197            .get_attr(AtomIdent::cast(&reference.name))
   2198            .map_or_else(
   2199                || {
   2200                    // Special case when fallback and <attr-type> are omitted.
   2201                    // See FAILURE: https://drafts.csswg.org/css-values-5/#attr-substitution
   2202                    if reference.fallback.is_none()
   2203                        && reference.attribute_syntax == AttributeType::None
   2204                    {
   2205                        simple_subst("")
   2206                    } else {
   2207                        None
   2208                    }
   2209                },
   2210                |attr| {
   2211                    let mut input = ParserInput::new(&attr);
   2212                    let mut parser = Parser::new(&mut input);
   2213                    match &reference.attribute_syntax {
   2214                        AttributeType::Unit(unit) => {
   2215                            let css = {
   2216                                // Verify that attribute data is a <number-token>.
   2217                                parser.expect_number().ok()?;
   2218                                let mut s = attr.clone();
   2219                                s.push_str(unit.as_ref());
   2220                                s
   2221                            };
   2222                            let serialization = match unit {
   2223                                AttrUnit::Number => TokenSerializationType::Number,
   2224                                AttrUnit::Percentage => TokenSerializationType::Percentage,
   2225                                _ => TokenSerializationType::Dimension,
   2226                            };
   2227                            let value =
   2228                                ComputedValue::new(css, url_data, serialization, serialization);
   2229                            Some(Substitution::from_value(value))
   2230                        },
   2231                        AttributeType::Type(syntax) => {
   2232                            let value = SpecifiedRegisteredValue::parse(
   2233                                &mut parser,
   2234                                syntax,
   2235                                url_data,
   2236                                AllowComputationallyDependent::Yes,
   2237                            )
   2238                            .ok()?;
   2239                            Some(Substitution::from_value(value.to_variable_value()))
   2240                        },
   2241                        AttributeType::RawString | AttributeType::None => simple_subst(&attr),
   2242                    }
   2243                },
   2244            ),
   2245    };
   2246 
   2247    if let Some(s) = substitution {
   2248        while references
   2249            .next_if(|next_ref| next_ref.end <= reference.end)
   2250            .is_some()
   2251        {}
   2252 
   2253        return Ok(s);
   2254    }
   2255 
   2256    let Some(ref fallback) = reference.fallback else {
   2257        return Err(());
   2258    };
   2259 
   2260    do_substitute_chunk(
   2261        css,
   2262        fallback.start.get(),
   2263        reference.end - 1, // Skip the closing parenthesis of the reference value.
   2264        fallback.first_token_type,
   2265        fallback.last_token_type,
   2266        url_data,
   2267        custom_properties,
   2268        stylist,
   2269        computed_context,
   2270        references,
   2271        attr_provider,
   2272    )
   2273 }
   2274 
   2275 /// Replace `var()`, `env()`, and `attr()` functions. Return `Err(..)` for invalid at computed time.
   2276 fn substitute_internal<'a>(
   2277    variable_value: &'a VariableValue,
   2278    custom_properties: &'a ComputedCustomProperties,
   2279    stylist: &Stylist,
   2280    computed_context: &computed::Context,
   2281    attr_provider: &dyn AttributeProvider,
   2282 ) -> Result<Substitution<'a>, ()> {
   2283    let mut refs = variable_value.references.refs.iter().peekable();
   2284    do_substitute_chunk(
   2285        &variable_value.css,
   2286        /* start = */ 0,
   2287        /* end = */ variable_value.css.len(),
   2288        variable_value.first_token_type,
   2289        variable_value.last_token_type,
   2290        &variable_value.url_data,
   2291        custom_properties,
   2292        stylist,
   2293        computed_context,
   2294        &mut refs,
   2295        attr_provider,
   2296    )
   2297 }
   2298 
   2299 /// Replace var(), env(), and attr() functions, returning the resulting CSS string.
   2300 pub fn substitute<'a>(
   2301    variable_value: &'a VariableValue,
   2302    custom_properties: &'a ComputedCustomProperties,
   2303    stylist: &Stylist,
   2304    computed_context: &computed::Context,
   2305    attr_provider: &dyn AttributeProvider,
   2306 ) -> Result<Cow<'a, str>, ()> {
   2307    debug_assert!(variable_value.has_references());
   2308    let v = substitute_internal(
   2309        variable_value,
   2310        custom_properties,
   2311        stylist,
   2312        computed_context,
   2313        attr_provider,
   2314    )?;
   2315    Ok(v.css)
   2316 }