Skip to main content

style/
custom_properties.rs

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