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