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::typed_om::{
30    ToTyped, TypedValue, UnparsedSegment, UnparsedValue, VariableReferenceValue,
31};
32use crate::values::computed::{self, ToComputedValue};
33use crate::values::generics::calc::SortKey as AttrUnit;
34use crate::values::specified::NoCalcLength;
35use crate::values::specified::ParsedNamespace;
36use crate::{derives::*, Namespace, Prefix};
37use crate::{Atom, LocalName};
38use cssparser::{
39    CowRcStr, Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType,
40};
41use rustc_hash::FxHashMap;
42use selectors::parser::SelectorParseErrorKind;
43use servo_arc::Arc;
44use smallvec::SmallVec;
45use std::borrow::Cow;
46use std::collections::hash_map::Entry;
47use std::fmt::{self, Write};
48use std::ops::{Index, IndexMut};
49use std::{cmp, num};
50use style_traits::{CssString, CssWriter, ParseError, StyleParseErrorKind, ToCss};
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(NoCalcLength::LH) {
514            return Self::FONT_UNITS | Self::LH_UNITS;
515        }
516        if value.eq_ignore_ascii_case(NoCalcLength::EM)
517            || value.eq_ignore_ascii_case(NoCalcLength::EX)
518            || value.eq_ignore_ascii_case(NoCalcLength::CAP)
519            || value.eq_ignore_ascii_case(NoCalcLength::CH)
520            || value.eq_ignore_ascii_case(NoCalcLength::IC)
521        {
522            return Self::FONT_UNITS;
523        }
524        if value.eq_ignore_ascii_case(NoCalcLength::RLH) {
525            return Self::ROOT_FONT_UNITS | Self::ROOT_LH_UNITS;
526        }
527        if value.eq_ignore_ascii_case(NoCalcLength::REM)
528            || value.eq_ignore_ascii_case(NoCalcLength::REX)
529            || value.eq_ignore_ascii_case(NoCalcLength::RCH)
530            || value.eq_ignore_ascii_case(NoCalcLength::RCAP)
531            || value.eq_ignore_ascii_case(NoCalcLength::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    Invalid,
656    None,
657    RawString,
658    Type(SyntaxDescriptor),
659    Unit(AttrUnit),
660}
661
662#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
663struct AttributeData {
664    kind: AttributeType,
665    namespace: ParsedNamespace,
666}
667
668/// For a CSS string, the range, counted in bytes, that is attr()-tainted.
669#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem, ToComputedValue)]
670pub struct AttrTaintedRange {
671    /// Start of the range, counted in bytes. Inclusive.
672    start: usize,
673    /// End of the range, counted in bytes. Exclusive.
674    end: usize,
675}
676
677impl AttrTaintedRange {
678    /// Creates a range within a CSS string that is tainted by attr().
679    #[inline(always)]
680    pub fn new(start: usize, end: usize) -> Self {
681        debug_assert!(start <= end);
682        Self { start, end }
683    }
684}
685
686/// In CSS Values and Units, values produced by `attr()` are considered attr()-tainted, as are
687/// functions that contain an attr()-tainted value. Using an attr()-tainted value as or in a <url>
688/// makes a declaration invalid at computed-value time.
689/// https://drafts.csswg.org/css-values-5/#attr-security
690#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
691pub struct AttrTaint(SmallVec<[AttrTaintedRange; 1]>);
692
693impl AttrTaint {
694    /// For a CSS string, determine whether any `<url>` overlapping this `range`
695    /// is disallowed due to attr()-tainting.
696    #[inline(always)]
697    pub fn should_disallow_urls_in_range(&self, range: &AttrTaintedRange) -> bool {
698        self.0
699            .iter()
700            .any(|r| r.start <= range.end && r.end >= range.start)
701    }
702
703    /// Returns true if the attr()-tainted range contains no elements.
704    #[inline(always)]
705    pub fn is_empty(&self) -> bool {
706        self.0.is_empty()
707    }
708
709    #[inline(always)]
710    fn new_fully_tainted(end: usize) -> Self {
711        let mut taint = Self::default();
712        taint.push(0, end);
713        taint
714    }
715
716    #[inline(always)]
717    fn push(&mut self, start: usize, end: usize) {
718        self.0.push(AttrTaintedRange::new(start, end));
719    }
720}
721
722#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
723struct VariableFallback {
724    // NOTE(emilio): We don't track fallback end, because we rely on the missing closing
725    // parenthesis, if any, to be inserted, which means that we can rely on our end being
726    // reference.end - 1.
727    start: num::NonZeroUsize,
728    first_token_type: TokenSerializationType,
729    last_token_type: TokenSerializationType,
730}
731
732#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
733struct SubstitutionFunctionReference {
734    name: Name,
735    start: usize,
736    end: usize,
737    fallback: Option<VariableFallback>,
738    attribute_data: AttributeData,
739    prev_token_type: TokenSerializationType,
740    next_token_type: TokenSerializationType,
741    substitution_kind: SubstitutionFunctionKind,
742}
743
744/// A struct holding information about the external references to that a custom
745/// property value may have.
746#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
747struct References {
748    refs: Vec<SubstitutionFunctionReference>,
749    non_custom_references: NonCustomReferences,
750    any_env: bool,
751    any_var: bool,
752    any_attr: bool,
753}
754
755impl References {
756    fn has_references(&self) -> bool {
757        !self.refs.is_empty()
758    }
759
760    fn non_custom_references(&self, is_root_element: bool) -> NonCustomReferences {
761        let mut mask = NonCustomReferences::NON_ROOT_DEPENDENCIES;
762        if is_root_element {
763            mask |= NonCustomReferences::ROOT_DEPENDENCIES
764        }
765        self.non_custom_references & mask
766    }
767}
768
769impl VariableValue {
770    fn empty(url_data: &UrlExtraData) -> Self {
771        Self {
772            css: String::new(),
773            last_token_type: Default::default(),
774            first_token_type: Default::default(),
775            url_data: url_data.clone(),
776            references: Default::default(),
777        }
778    }
779
780    /// Create a new custom property without parsing if the CSS is known to be valid and contain no
781    /// references.
782    pub fn new(
783        css: String,
784        url_data: &UrlExtraData,
785        first_token_type: TokenSerializationType,
786        last_token_type: TokenSerializationType,
787    ) -> Self {
788        Self {
789            css,
790            url_data: url_data.clone(),
791            first_token_type,
792            last_token_type,
793            references: References::default(),
794        }
795    }
796
797    fn push<'i>(
798        &mut self,
799        css: &str,
800        css_first_token_type: TokenSerializationType,
801        css_last_token_type: TokenSerializationType,
802        attr_taint: Option<&mut AttrTaint>,
803    ) -> Result<(), ()> {
804        /// Prevent values from getting terribly big since you can use custom
805        /// properties exponentially.
806        ///
807        /// This number (2MB) is somewhat arbitrary, but silly enough that no
808        /// reasonable page should hit it. We could limit by number of total
809        /// substitutions, but that was very easy to work around in practice
810        /// (just choose a larger initial value and boom).
811        const MAX_VALUE_LENGTH_IN_BYTES: usize = 2 * 1024 * 1024;
812
813        if self.css.len() + css.len() > MAX_VALUE_LENGTH_IN_BYTES {
814            return Err(());
815        }
816
817        // This happens e.g. between two subsequent var() functions:
818        // `var(--a)var(--b)`.
819        //
820        // In that case, css_*_token_type is nonsensical.
821        if css.is_empty() {
822            return Ok(());
823        }
824
825        self.first_token_type.set_if_nothing(css_first_token_type);
826        // If self.first_token_type was nothing,
827        // self.last_token_type is also nothing and this will be false:
828        if self
829            .last_token_type
830            .needs_separator_when_before(css_first_token_type)
831        {
832            self.css.push_str("/**/")
833        }
834        let start = self.css.len();
835        self.css.push_str(css);
836        let end = self.css.len();
837        if let Some(taint) = attr_taint {
838            taint.push(start, end);
839        }
840        self.last_token_type = css_last_token_type;
841        Ok(())
842    }
843
844    /// Parse a custom property value.
845    pub fn parse<'i, 't>(
846        input: &mut Parser<'i, 't>,
847        namespaces: Option<&FxHashMap<Prefix, Namespace>>,
848        url_data: &UrlExtraData,
849    ) -> Result<Self, ParseError<'i>> {
850        let mut references = References::default();
851        let mut missing_closing_characters = String::new();
852        let start_position = input.position();
853        let (first_token_type, last_token_type) = parse_declaration_value(
854            input,
855            start_position,
856            namespaces,
857            &mut references,
858            &mut missing_closing_characters,
859        )?;
860        let mut css = input
861            .slice_from(start_position)
862            .trim_ascii_start()
863            .to_owned();
864        if !missing_closing_characters.is_empty() {
865            // Unescaped backslash at EOF in a quoted string is ignored.
866            if css.ends_with("\\")
867                && matches!(missing_closing_characters.as_bytes()[0], b'"' | b'\'')
868            {
869                css.pop();
870            }
871            css.push_str(&missing_closing_characters);
872        }
873
874        css.truncate(css.trim_ascii_end().len());
875        css.shrink_to_fit();
876        references.refs.shrink_to_fit();
877
878        Ok(Self {
879            css,
880            url_data: url_data.clone(),
881            first_token_type,
882            last_token_type,
883            references,
884        })
885    }
886
887    /// Returns whether this value is tainted by `attr()`.
888    pub fn is_attr_tainted(&self) -> bool {
889        self.references.any_attr
890    }
891
892    /// Create VariableValue from an int.
893    fn integer(number: i32, url_data: &UrlExtraData) -> Self {
894        Self::from_token(
895            Token::Number {
896                has_sign: false,
897                value: number as f32,
898                int_value: Some(number),
899            },
900            url_data,
901        )
902    }
903
904    /// Create VariableValue from an int.
905    fn ident(ident: &'static str, url_data: &UrlExtraData) -> Self {
906        Self::from_token(Token::Ident(ident.into()), url_data)
907    }
908
909    /// Create VariableValue from a float amount of CSS pixels.
910    fn pixels(number: f32, url_data: &UrlExtraData) -> Self {
911        // FIXME (https://github.com/servo/rust-cssparser/issues/266):
912        // No way to get TokenSerializationType::Dimension without creating
913        // Token object.
914        Self::from_token(
915            Token::Dimension {
916                has_sign: false,
917                value: number,
918                int_value: None,
919                unit: CowRcStr::from("px"),
920            },
921            url_data,
922        )
923    }
924
925    /// Create VariableValue from an integer amount of milliseconds.
926    fn int_ms(number: i32, url_data: &UrlExtraData) -> Self {
927        Self::from_token(
928            Token::Dimension {
929                has_sign: false,
930                value: number as f32,
931                int_value: Some(number),
932                unit: CowRcStr::from("ms"),
933            },
934            url_data,
935        )
936    }
937
938    /// Create VariableValue from an integer amount of CSS pixels.
939    fn int_pixels(number: i32, url_data: &UrlExtraData) -> Self {
940        Self::from_token(
941            Token::Dimension {
942                has_sign: false,
943                value: number as f32,
944                int_value: Some(number),
945                unit: CowRcStr::from("px"),
946            },
947            url_data,
948        )
949    }
950
951    fn from_token(token: Token, url_data: &UrlExtraData) -> Self {
952        let token_type = token.serialization_type();
953        let mut css = token.to_css_string();
954        css.shrink_to_fit();
955
956        VariableValue {
957            css,
958            url_data: url_data.clone(),
959            first_token_type: token_type,
960            last_token_type: token_type,
961            references: Default::default(),
962        }
963    }
964
965    /// Returns the raw CSS text from this VariableValue
966    pub fn css_text(&self) -> &str {
967        &self.css
968    }
969
970    /// Returns whether this variable value has any reference to the environment or other
971    /// variables.
972    pub fn has_references(&self) -> bool {
973        self.references.has_references()
974    }
975}
976
977/// <https://drafts.csswg.org/css-syntax-3/#typedef-declaration-value>
978fn parse_declaration_value<'i, 't>(
979    input: &mut Parser<'i, 't>,
980    input_start: SourcePosition,
981    namespaces: Option<&FxHashMap<Prefix, Namespace>>,
982    references: &mut References,
983    missing_closing_characters: &mut String,
984) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
985    input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
986        parse_declaration_value_block(
987            input,
988            input_start,
989            namespaces,
990            references,
991            missing_closing_characters,
992        )
993    })
994}
995
996/// Like parse_declaration_value, but accept `!` and `;` since they are only invalid at the top level.
997fn parse_declaration_value_block<'i, 't>(
998    input: &mut Parser<'i, 't>,
999    input_start: SourcePosition,
1000    namespaces: Option<&FxHashMap<Prefix, Namespace>>,
1001    references: &mut References,
1002    missing_closing_characters: &mut String,
1003) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
1004    let mut is_first = true;
1005    let mut first_token_type = TokenSerializationType::Nothing;
1006    let mut last_token_type = TokenSerializationType::Nothing;
1007    let mut prev_reference_index: Option<usize> = None;
1008    loop {
1009        let token_start = input.position();
1010        let Ok(token) = input.next_including_whitespace_and_comments() else {
1011            break;
1012        };
1013
1014        let prev_token_type = last_token_type;
1015        let serialization_type = token.serialization_type();
1016        last_token_type = serialization_type;
1017        if is_first {
1018            first_token_type = last_token_type;
1019            is_first = false;
1020        }
1021
1022        macro_rules! nested {
1023            ($closing:expr) => {{
1024                let mut inner_end_position = None;
1025                let result = input.parse_nested_block(|input| {
1026                    let result = parse_declaration_value_block(
1027                        input,
1028                        input_start,
1029                        namespaces,
1030                        references,
1031                        missing_closing_characters,
1032                    )?;
1033                    inner_end_position = Some(input.position());
1034                    Ok(result)
1035                })?;
1036                if inner_end_position.unwrap() == input.position() {
1037                    missing_closing_characters.push_str($closing);
1038                }
1039                result
1040            }};
1041        }
1042        if let Some(index) = prev_reference_index.take() {
1043            references.refs[index].next_token_type = serialization_type;
1044        }
1045        match *token {
1046            Token::Comment(_) => {
1047                let token_slice = input.slice_from(token_start);
1048                if !token_slice.ends_with("*/") {
1049                    missing_closing_characters.push_str(if token_slice.ends_with('*') {
1050                        "/"
1051                    } else {
1052                        "*/"
1053                    })
1054                }
1055            },
1056            Token::BadUrl(ref u) => {
1057                let e = StyleParseErrorKind::BadUrlInDeclarationValueBlock(u.clone());
1058                return Err(input.new_custom_error(e));
1059            },
1060            Token::BadString(ref s) => {
1061                let e = StyleParseErrorKind::BadStringInDeclarationValueBlock(s.clone());
1062                return Err(input.new_custom_error(e));
1063            },
1064            Token::CloseParenthesis => {
1065                let e = StyleParseErrorKind::UnbalancedCloseParenthesisInDeclarationValueBlock;
1066                return Err(input.new_custom_error(e));
1067            },
1068            Token::CloseSquareBracket => {
1069                let e = StyleParseErrorKind::UnbalancedCloseSquareBracketInDeclarationValueBlock;
1070                return Err(input.new_custom_error(e));
1071            },
1072            Token::CloseCurlyBracket => {
1073                let e = StyleParseErrorKind::UnbalancedCloseCurlyBracketInDeclarationValueBlock;
1074                return Err(input.new_custom_error(e));
1075            },
1076            Token::Function(ref name) => {
1077                let substitution_kind = match SubstitutionFunctionKind::from_ident(name).ok() {
1078                    Some(SubstitutionFunctionKind::Attr) => {
1079                        if static_prefs::pref!("layout.css.attr.enabled") {
1080                            Some(SubstitutionFunctionKind::Attr)
1081                        } else {
1082                            None
1083                        }
1084                    },
1085                    kind => kind,
1086                };
1087                if let Some(substitution_kind) = substitution_kind {
1088                    let our_ref_index = references.refs.len();
1089                    let mut input_end_position = None;
1090                    let fallback = input.parse_nested_block(|input| {
1091                        let mut namespace = ParsedNamespace::Known(Namespace::default());
1092                        if substitution_kind == SubstitutionFunctionKind::Attr {
1093                            if let Some(namespaces) = namespaces {
1094                                if let Ok(ns) = input
1095                                    .try_parse(|input| ParsedNamespace::parse(namespaces, input))
1096                                {
1097                                    namespace = ns;
1098                                    let prev = input.state();
1099                                    let next = match *input.next_including_whitespace()? {
1100                                        Token::Ident(_) => Ok(()),
1101                                        ref t => Err(prev
1102                                            .source_location()
1103                                            .new_unexpected_token_error(t.clone())),
1104                                    };
1105                                    input.reset(&prev);
1106                                    next?;
1107                                }
1108                            }
1109                        }
1110                        // TODO(emilio): For env() this should be <custom-ident> per spec, but no other browser does
1111                        // that, see https://github.com/w3c/csswg-drafts/issues/3262.
1112                        let name = input.expect_ident()?;
1113                        let name =
1114                            Atom::from(if substitution_kind == SubstitutionFunctionKind::Var {
1115                                match parse_name(name.as_ref()) {
1116                                    Ok(name) => name,
1117                                    Err(()) => {
1118                                        let name = name.clone();
1119                                        return Err(input.new_custom_error(
1120                                            SelectorParseErrorKind::UnexpectedIdent(name),
1121                                        ));
1122                                    },
1123                                }
1124                            } else {
1125                                name.as_ref()
1126                            });
1127
1128                        let attribute_kind = if substitution_kind == SubstitutionFunctionKind::Attr
1129                        {
1130                            parse_attr_type(input)
1131                        } else {
1132                            AttributeType::None
1133                        };
1134
1135                        // We want the order of the references to match source order. So we need to reserve our slot
1136                        // now, _before_ parsing our fallback. Note that we don't care if parsing fails after all, since
1137                        // if this fails we discard the whole result anyways.
1138                        let start = token_start.byte_index() - input_start.byte_index();
1139                        references.refs.push(SubstitutionFunctionReference {
1140                            name,
1141                            start,
1142                            // To be fixed up after parsing fallback and auto-closing via our_ref_index.
1143                            end: start,
1144                            prev_token_type,
1145                            // To be fixed up (if needed) on the next loop iteration via prev_reference_index.
1146                            next_token_type: TokenSerializationType::Nothing,
1147                            // To be fixed up after parsing fallback.
1148                            fallback: None,
1149                            attribute_data: AttributeData {
1150                                kind: attribute_kind,
1151                                namespace,
1152                            },
1153                            substitution_kind: substitution_kind.clone(),
1154                        });
1155
1156                        let mut fallback = None;
1157                        if input.try_parse(|input| input.expect_comma()).is_ok() {
1158                            input.skip_whitespace();
1159                            let fallback_start = num::NonZeroUsize::new(
1160                                input.position().byte_index() - input_start.byte_index(),
1161                            )
1162                            .unwrap();
1163                            // NOTE(emilio): Intentionally using parse_declaration_value rather than
1164                            // parse_declaration_value_block, since that's what parse_fallback used to do.
1165                            let (first, last) = parse_declaration_value(
1166                                input,
1167                                input_start,
1168                                namespaces,
1169                                references,
1170                                missing_closing_characters,
1171                            )?;
1172                            fallback = Some(VariableFallback {
1173                                start: fallback_start,
1174                                first_token_type: first,
1175                                last_token_type: last,
1176                            });
1177                            input_end_position = Some(input.position());
1178                        } else {
1179                            let state = input.state();
1180                            // We still need to consume the rest of the potentially-unclosed
1181                            // tokens, but make sure to not consume tokens that would otherwise be
1182                            // invalid, by calling reset().
1183                            parse_declaration_value_block(
1184                                input,
1185                                input_start,
1186                                namespaces,
1187                                references,
1188                                missing_closing_characters,
1189                            )?;
1190                            input_end_position = Some(input.position());
1191                            input.reset(&state);
1192                        }
1193                        Ok(fallback)
1194                    })?;
1195                    if input_end_position.unwrap() == input.position() {
1196                        missing_closing_characters.push_str(")");
1197                    }
1198                    prev_reference_index = Some(our_ref_index);
1199                    let reference = &mut references.refs[our_ref_index];
1200                    reference.end = input.position().byte_index() - input_start.byte_index()
1201                        + missing_closing_characters.len();
1202                    reference.fallback = fallback;
1203                    match substitution_kind {
1204                        SubstitutionFunctionKind::Var => references.any_var = true,
1205                        SubstitutionFunctionKind::Env => references.any_env = true,
1206                        SubstitutionFunctionKind::Attr => references.any_attr = true,
1207                    };
1208                } else {
1209                    nested!(")");
1210                }
1211            },
1212            Token::ParenthesisBlock => {
1213                nested!(")");
1214            },
1215            Token::CurlyBracketBlock => {
1216                nested!("}");
1217            },
1218            Token::SquareBracketBlock => {
1219                nested!("]");
1220            },
1221            Token::QuotedString(_) => {
1222                let token_slice = input.slice_from(token_start);
1223                let quote = &token_slice[..1];
1224                debug_assert!(matches!(quote, "\"" | "'"));
1225                if !(token_slice.ends_with(quote) && token_slice.len() > 1) {
1226                    missing_closing_characters.push_str(quote)
1227                }
1228            },
1229            Token::Ident(ref value)
1230            | Token::AtKeyword(ref value)
1231            | Token::Hash(ref value)
1232            | Token::IDHash(ref value)
1233            | Token::UnquotedUrl(ref value)
1234            | Token::Dimension {
1235                unit: ref value, ..
1236            } => {
1237                references
1238                    .non_custom_references
1239                    .insert(NonCustomReferences::from_unit(value));
1240                let is_unquoted_url = matches!(token, Token::UnquotedUrl(_));
1241                if value.ends_with("�") && input.slice_from(token_start).ends_with("\\") {
1242                    // Unescaped backslash at EOF in these contexts is interpreted as U+FFFD
1243                    // Check the value in case the final backslash was itself escaped.
1244                    // Serialize as escaped U+FFFD, which is also interpreted as U+FFFD.
1245                    // (Unescaped U+FFFD would also work, but removing the backslash is annoying.)
1246                    missing_closing_characters.push_str("�")
1247                }
1248                if is_unquoted_url && !input.slice_from(token_start).ends_with(")") {
1249                    missing_closing_characters.push_str(")");
1250                }
1251            },
1252            _ => {},
1253        };
1254    }
1255    Ok((first_token_type, last_token_type))
1256}
1257
1258/// Parse <attr-type> = type( <syntax> ) | raw-string | number | <attr-unit>.
1259/// https://drafts.csswg.org/css-values-5/#attr-notation
1260fn parse_attr_type<'i, 't>(input: &mut Parser<'i, 't>) -> AttributeType {
1261    input
1262        .try_parse(|input| {
1263            Ok(match input.next()? {
1264                Token::Function(ref name) if name.eq_ignore_ascii_case("type") => {
1265                    AttributeType::Type(
1266                        input.parse_nested_block(SyntaxDescriptor::from_css_parser)?,
1267                    )
1268                },
1269                Token::Ident(ref ident) => {
1270                    if ident.eq_ignore_ascii_case("raw-string") {
1271                        AttributeType::RawString
1272                    } else if let Ok(unit) = AttrUnit::from_ident(ident) {
1273                        AttributeType::Unit(unit)
1274                    } else {
1275                        AttributeType::Invalid
1276                    }
1277                },
1278                Token::Delim('%') => AttributeType::Unit(AttrUnit::Percentage),
1279                _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
1280            })
1281        })
1282        .unwrap_or(AttributeType::None)
1283}
1284
1285/// Attribute values may reference other substitution functions we may need to process.
1286/// See step 6: https://drafts.csswg.org/css-values-5/#attr-substitution
1287fn get_attr_value_for_cycle_resolution(
1288    name: &Atom,
1289    attribute_data: &AttributeData,
1290    url_data: &UrlExtraData,
1291    attribute_tracker: &mut AttributeTracker,
1292) -> Result<ComputedRegisteredValue, ()> {
1293    // If the attribute is not specified by the type() syntax, it
1294    // has no reference to traverse and we can stop processing.
1295    if !matches!(&attribute_data.kind, AttributeType::Type(_)) {
1296        return Err(());
1297    }
1298    #[cfg(feature = "gecko")]
1299    let local_name = LocalName::cast(name);
1300    #[cfg(feature = "servo")]
1301    let local_name = &LocalName::from(name.as_ref());
1302    let namespace = match attribute_data.namespace {
1303        ParsedNamespace::Known(ref ns) => ns,
1304        ParsedNamespace::Unknown => return Err(()),
1305    };
1306    let attr = attribute_tracker.query(local_name, namespace).ok_or(())?;
1307    let mut input = ParserInput::new(&attr);
1308    let mut parser = Parser::new(&mut input);
1309    // TODO(Bug 2021110): Support namespaced attributes in chained references.
1310    let value = VariableValue::parse(&mut parser, None, &url_data).map_err(|_| ())?;
1311    Ok(ComputedRegisteredValue::universal(Arc::new(value)))
1312}
1313
1314#[derive(Default)]
1315struct SeenSubstitutionFunctions<'a> {
1316    var: PrecomputedHashSet<&'a Name>,
1317    attr: PrecomputedHashSet<&'a Name>,
1318}
1319
1320/// A struct that takes care of encapsulating the cascade process for custom properties.
1321pub struct CustomPropertiesBuilder<'a, 'b: 'a> {
1322    seen: SeenSubstitutionFunctions<'a>,
1323    may_have_cycles: bool,
1324    has_color_scheme: bool,
1325    substitution_functions: ComputedSubstitutionFunctions,
1326    reverted: PrecomputedHashMap<&'a Name, (CascadePriority, RevertKind)>,
1327    stylist: &'a Stylist,
1328    computed_context: &'a mut computed::Context<'b>,
1329    references_from_non_custom_properties: NonCustomReferenceMap<Vec<Name>>,
1330}
1331
1332fn find_non_custom_references(
1333    registration: &PropertyDescriptors,
1334    value: &VariableValue,
1335    may_have_color_scheme: bool,
1336    is_root_element: bool,
1337    include_universal: bool,
1338) -> Option<NonCustomReferences> {
1339    let syntax = registration.syntax.as_ref()?;
1340    let dependent_types = syntax.dependent_types();
1341    let may_reference_length = dependent_types.intersects(DependentDataTypes::LENGTH)
1342        || (include_universal && syntax.is_universal());
1343    if may_reference_length {
1344        let value_dependencies = value.references.non_custom_references(is_root_element);
1345        if !value_dependencies.is_empty() {
1346            return Some(value_dependencies);
1347        }
1348    }
1349    if dependent_types.intersects(DependentDataTypes::COLOR) && may_have_color_scheme {
1350        // NOTE(emilio): We might want to add a NonCustomReferences::COLOR_SCHEME or something but
1351        // it's not really needed for correctness, so for now we use an Option for that to signal
1352        // that there might be a dependency.
1353        return Some(NonCustomReferences::empty());
1354    }
1355    None
1356}
1357
1358impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
1359    /// Create a new builder, inheriting from a given custom properties map.
1360    ///
1361    /// We expose this publicly mostly for @keyframe blocks.
1362    pub fn new_with_properties(
1363        stylist: &'a Stylist,
1364        custom_properties: ComputedCustomProperties,
1365        computed_context: &'a mut computed::Context<'b>,
1366    ) -> Self {
1367        Self {
1368            seen: SeenSubstitutionFunctions::default(),
1369            reverted: Default::default(),
1370            may_have_cycles: false,
1371            has_color_scheme: false,
1372            substitution_functions: ComputedSubstitutionFunctions::new(
1373                Some(custom_properties),
1374                None,
1375            ),
1376            stylist,
1377            computed_context,
1378            references_from_non_custom_properties: NonCustomReferenceMap::default(),
1379        }
1380    }
1381
1382    /// Create a new builder, inheriting from the right style given context.
1383    pub fn new(stylist: &'a Stylist, context: &'a mut computed::Context<'b>) -> Self {
1384        let is_root_element = context.is_root_element();
1385
1386        let inherited = context.inherited_custom_properties();
1387        let initial_values = stylist.get_custom_property_initial_values();
1388        let properties = ComputedCustomProperties {
1389            inherited: if is_root_element {
1390                debug_assert!(inherited.is_empty());
1391                initial_values.inherited.clone()
1392            } else {
1393                inherited.inherited.clone()
1394            },
1395            non_inherited: initial_values.non_inherited.clone(),
1396        };
1397
1398        // Reuse flags from computing registered custom properties initial values, such as
1399        // whether they depend on viewport units.
1400        context
1401            .style()
1402            .add_flags(stylist.get_custom_property_initial_values_flags());
1403        Self::new_with_properties(stylist, properties, context)
1404    }
1405
1406    /// Cascade a given custom property declaration.
1407    pub fn cascade(
1408        &mut self,
1409        declaration: &'a CustomDeclaration,
1410        priority: CascadePriority,
1411        attribute_tracker: &mut AttributeTracker,
1412    ) {
1413        let CustomDeclaration {
1414            ref name,
1415            ref value,
1416        } = *declaration;
1417
1418        if let Some(&(reverted_priority, revert_kind)) = self.reverted.get(&name) {
1419            if !reverted_priority.allows_when_reverted(&priority, revert_kind) {
1420                return;
1421            }
1422        }
1423
1424        if !(priority.flags() - self.computed_context.included_cascade_flags).is_empty() {
1425            return;
1426        }
1427
1428        let was_already_present = !self.seen.var.insert(name);
1429        if was_already_present {
1430            return;
1431        }
1432
1433        if !self.value_may_affect_style(name, value) {
1434            return;
1435        }
1436
1437        let kind = SubstitutionFunctionKind::Var;
1438        let map = &mut self.substitution_functions;
1439        let registration = self.stylist.get_custom_property_registration(&name);
1440        match value {
1441            CustomDeclarationValue::Unparsed(unparsed_value) => {
1442                // At this point of the cascade we're not guaranteed to have seen the color-scheme
1443                // declaration, so need to assume the worst. We could track all system color
1444                // keyword tokens + the light-dark() function, but that seems non-trivial /
1445                // probably overkill.
1446                let may_have_color_scheme = true;
1447                // Non-custom dependency is really relevant for registered custom properties
1448                // that require computed value of such dependencies.
1449                let has_dependency = unparsed_value.references.any_var
1450                    || unparsed_value.references.any_attr
1451                    || find_non_custom_references(
1452                        registration,
1453                        unparsed_value,
1454                        may_have_color_scheme,
1455                        self.computed_context.is_root_element(),
1456                        /* include_unregistered = */ false,
1457                    )
1458                    .is_some();
1459                // If the variable value has no references to other properties, perform
1460                // substitution here instead of forcing a full traversal in `substitute_all`
1461                // afterwards.
1462                if !has_dependency {
1463                    return substitute_references_if_needed_and_apply(
1464                        name,
1465                        kind,
1466                        unparsed_value,
1467                        &mut self.substitution_functions,
1468                        self.stylist,
1469                        self.computed_context,
1470                        attribute_tracker,
1471                    );
1472                }
1473                self.may_have_cycles = true;
1474                let value = ComputedRegisteredValue::universal(Arc::clone(unparsed_value));
1475                map.insert_var(registration, name, value);
1476            },
1477            CustomDeclarationValue::Parsed(parsed_value) => {
1478                let value = parsed_value.to_computed_value(&self.computed_context);
1479                map.insert_var(registration, name, value);
1480            },
1481            CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword.revert_kind() {
1482                Some(revert_kind) => {
1483                    self.seen.var.remove(name);
1484                    self.reverted.insert(name, (priority, revert_kind));
1485                },
1486                None => match keyword {
1487                    CSSWideKeyword::Initial => {
1488                        // For non-inherited custom properties, 'initial' was handled in value_may_affect_style.
1489                        debug_assert!(registration.inherits(), "Should've been handled earlier");
1490                        remove_and_insert_initial_value(name, registration, map);
1491                    },
1492                    CSSWideKeyword::Inherit => {
1493                        // For inherited custom properties, 'inherit' was handled in value_may_affect_style.
1494                        debug_assert!(!registration.inherits(), "Should've been handled earlier");
1495                        self.computed_context
1496                            .style()
1497                            .add_flags(ComputedValueFlags::INHERITS_RESET_STYLE);
1498                        if let Some(inherited_value) = self
1499                            .computed_context
1500                            .inherited_custom_properties()
1501                            .non_inherited
1502                            .get(name)
1503                        {
1504                            map.insert_var(registration, name, inherited_value.clone());
1505                        }
1506                    },
1507                    // handled in value_may_affect_style or in the revert_kind branch above.
1508                    CSSWideKeyword::Revert
1509                    | CSSWideKeyword::RevertLayer
1510                    | CSSWideKeyword::RevertRule
1511                    | CSSWideKeyword::Unset => unreachable!(),
1512                },
1513            },
1514        }
1515    }
1516
1517    /// Fast check to avoid calling maybe_note_non_custom_dependency in ~all cases.
1518    #[inline]
1519    pub fn might_have_non_custom_or_attr_dependency(
1520        id: LonghandId,
1521        decl: &PropertyDeclaration,
1522    ) -> bool {
1523        if id == LonghandId::ColorScheme {
1524            return true;
1525        }
1526        if let PropertyDeclaration::WithVariables(v) = decl {
1527            return matches!(id, LonghandId::LineHeight | LonghandId::FontSize)
1528                || v.value.variable_value.references.any_attr;
1529        }
1530        false
1531    }
1532
1533    /// Note a non-custom property with variable reference that may in turn depend on that property.
1534    /// e.g. `font-size` depending on a custom property that may be a registered property using `em`.
1535    pub fn maybe_note_non_custom_dependency(
1536        &mut self,
1537        id: LonghandId,
1538        decl: &'a PropertyDeclaration,
1539        attribute_tracker: &mut AttributeTracker,
1540    ) {
1541        debug_assert!(Self::might_have_non_custom_or_attr_dependency(id, decl));
1542        if id == LonghandId::ColorScheme {
1543            // If we might change the color-scheme, we need to defer computation of colors.
1544            self.has_color_scheme = true;
1545            return;
1546        }
1547
1548        let PropertyDeclaration::WithVariables(v) = decl else {
1549            return;
1550        };
1551        let value = &v.value.variable_value;
1552        let refs = &value.references;
1553
1554        if !refs.any_var && !refs.any_attr {
1555            return;
1556        }
1557
1558        // Attributes in non-custom properties may reference `var()` or `attr()` in their
1559        // values, which we need to track to support chained references and detect cycles.
1560        // Further processing occurs during `CustomPropertiesBuilder::build()`.
1561        if refs.any_attr {
1562            self.update_attributes_map(value, attribute_tracker);
1563            if !refs.any_var {
1564                return;
1565            }
1566        }
1567
1568        // With unit algebra in `calc()`, references aren't limited to `font-size`.
1569        // For example, `--foo: 100ex; font-weight: calc(var(--foo) / 1ex);`,
1570        // or `--foo: 1em; zoom: calc(var(--foo) * 30px / 2em);`
1571        let references = match id {
1572            LonghandId::FontSize => {
1573                if self.computed_context.is_root_element() {
1574                    NonCustomReferences::ROOT_FONT_UNITS
1575                } else {
1576                    NonCustomReferences::FONT_UNITS
1577                }
1578            },
1579            LonghandId::LineHeight => {
1580                if self.computed_context.is_root_element() {
1581                    NonCustomReferences::ROOT_LH_UNITS | NonCustomReferences::ROOT_FONT_UNITS
1582                } else {
1583                    NonCustomReferences::LH_UNITS | NonCustomReferences::FONT_UNITS
1584                }
1585            },
1586            _ => return,
1587        };
1588
1589        let variables: Vec<Atom> = refs
1590            .refs
1591            .iter()
1592            .filter_map(|reference| {
1593                if reference.substitution_kind != SubstitutionFunctionKind::Var {
1594                    return None;
1595                }
1596                let registration = self
1597                    .stylist
1598                    .get_custom_property_registration(&reference.name);
1599                if !registration
1600                    .syntax
1601                    .as_ref()?
1602                    .dependent_types()
1603                    .intersects(DependentDataTypes::LENGTH)
1604                {
1605                    return None;
1606                }
1607                Some(reference.name.clone())
1608            })
1609            .collect();
1610        references.for_each(|idx| {
1611            let entry = &mut self.references_from_non_custom_properties[idx];
1612            let was_none = entry.is_none();
1613            let v = entry.get_or_insert_with(|| variables.clone());
1614            if was_none {
1615                return;
1616            }
1617            v.extend(variables.iter().cloned());
1618        });
1619    }
1620
1621    fn value_may_affect_style(&self, name: &Name, value: &CustomDeclarationValue) -> bool {
1622        let registration = self.stylist.get_custom_property_registration(&name);
1623        match *value {
1624            CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit) => {
1625                // For inherited custom properties, explicit 'inherit' means we
1626                // can just use any existing value in the inherited
1627                // CustomPropertiesMap.
1628                if registration.inherits() {
1629                    return false;
1630                }
1631            },
1632            CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial) => {
1633                // For non-inherited custom properties, explicit 'initial' means
1634                // we can just use any initial value in the registration.
1635                if !registration.inherits() {
1636                    return false;
1637                }
1638            },
1639            CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Unset) => {
1640                // Explicit 'unset' means we can either just use any existing
1641                // value in the inherited CustomPropertiesMap or the initial
1642                // value in the registration.
1643                return false;
1644            },
1645            _ => {},
1646        }
1647
1648        let existing_value = self.substitution_functions.get_var(registration, &name);
1649        let Some(existing_value) = existing_value else {
1650            if matches!(
1651                value,
1652                CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial)
1653            ) {
1654                debug_assert!(registration.inherits(), "Should've been handled earlier");
1655                // The initial value of a custom property without a
1656                // guaranteed-invalid initial value is the same as it
1657                // not existing in the map.
1658                if registration.initial_value.is_none() {
1659                    return false;
1660                }
1661            }
1662            return true;
1663        };
1664        match value {
1665            CustomDeclarationValue::Unparsed(value) => {
1666                // Don't bother overwriting an existing value with the same
1667                // specified value.
1668                if let Some(existing_value) = existing_value.as_universal() {
1669                    return existing_value != value;
1670                }
1671            },
1672            CustomDeclarationValue::Parsed(..) => {
1673                // If the value has dependencies, self.computed_context might not yield the same
1674                // result as the eventual value.
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            },
1713        };
1714
1715        true
1716    }
1717
1718    /// For a given unparsed variable, update the attributes map with its attr references.
1719    pub fn update_attributes_map(
1720        &mut self,
1721        value: &'a VariableValue,
1722        attribute_tracker: &mut AttributeTracker,
1723    ) {
1724        let refs = &value.references;
1725        if !refs.any_attr {
1726            return;
1727        }
1728        self.may_have_cycles = true;
1729
1730        for next in &refs.refs {
1731            // Skip non-attrs and attributes we've already processed.
1732            if next.substitution_kind != SubstitutionFunctionKind::Attr
1733                || !self.seen.attr.insert(&next.name)
1734            {
1735                continue;
1736            }
1737            if let Ok(v) = get_attr_value_for_cycle_resolution(
1738                &next.name,
1739                &next.attribute_data,
1740                &value.url_data,
1741                attribute_tracker,
1742            ) {
1743                self.substitution_functions.insert_attr(&next.name, v);
1744            }
1745        }
1746    }
1747
1748    /// Computes the map of applicable custom properties, as well as
1749    /// longhand properties that are now considered invalid-at-compute time.
1750    /// The result is saved into the computed context.
1751    ///
1752    /// If there was any specified property or non-inherited custom property
1753    /// with an initial value, we've created a new map and now we
1754    /// need to remove any potential cycles (And marking non-custom
1755    /// properties), and wrap it in an arc.
1756    ///
1757    /// Some registered custom properties may require font-related properties
1758    /// be resolved to resolve. If these properties are not resolved at this time,
1759    /// `defer` should be set to `Yes`, which will leave such custom properties,
1760    /// and other properties referencing them, untouched. These properties are
1761    /// returned separately, to be resolved by `build_deferred` to fully resolve
1762    /// all custom properties after all necessary non-custom properties are resolved.
1763    pub fn build(
1764        mut self,
1765        defer: DeferFontRelativeCustomPropertyResolution,
1766        attribute_tracker: &mut AttributeTracker,
1767    ) -> Option<AllSubstitutionFunctions> {
1768        let mut deferred_substitution_functions = None;
1769        if self.may_have_cycles {
1770            if defer == DeferFontRelativeCustomPropertyResolution::Yes {
1771                deferred_substitution_functions = Some(AllSubstitutionFunctions::default());
1772            }
1773            let mut invalid_non_custom_properties = LonghandIdSet::default();
1774            substitute_all(
1775                &mut self.substitution_functions,
1776                deferred_substitution_functions.as_mut(),
1777                &mut invalid_non_custom_properties,
1778                self.has_color_scheme,
1779                &self.seen,
1780                &self.references_from_non_custom_properties,
1781                self.stylist,
1782                self.computed_context,
1783                attribute_tracker,
1784            );
1785            self.computed_context.builder.invalid_non_custom_properties =
1786                invalid_non_custom_properties;
1787        }
1788        self.substitution_functions
1789            .custom_properties
1790            .shrink_to_fit();
1791
1792        // Some pages apply a lot of redundant custom properties, see e.g.
1793        // bug 1758974 comment 5. Try to detect the case where the values
1794        // haven't really changed, and save some memory by reusing the inherited
1795        // map in that case.
1796        let initial_values = self.stylist.get_custom_property_initial_values();
1797        let custom_properties = self.substitution_functions.custom_properties;
1798        self.computed_context
1799            .builder
1800            .substitution_functions
1801            .custom_properties = ComputedCustomProperties {
1802            inherited: if self
1803                .computed_context
1804                .inherited_custom_properties()
1805                .inherited
1806                == custom_properties.inherited
1807            {
1808                self.computed_context
1809                    .inherited_custom_properties()
1810                    .inherited
1811                    .clone()
1812            } else {
1813                custom_properties.inherited
1814            },
1815            non_inherited: if initial_values.non_inherited == custom_properties.non_inherited {
1816                initial_values.non_inherited.clone()
1817            } else {
1818                custom_properties.non_inherited
1819            },
1820        };
1821        self.computed_context
1822            .builder
1823            .substitution_functions
1824            .attributes = self.substitution_functions.attributes;
1825
1826        deferred_substitution_functions
1827    }
1828
1829    /// Fully resolve all deferred custom properties and attributes, assuming that the
1830    /// incoming context has necessary properties resolved.
1831    pub fn build_deferred(
1832        deferred: AllSubstitutionFunctions,
1833        stylist: &Stylist,
1834        computed_context: &mut computed::Context,
1835        attribute_tracker: &mut AttributeTracker,
1836    ) {
1837        if deferred.is_empty() {
1838            return;
1839        }
1840        let mut map = std::mem::take(&mut computed_context.builder.substitution_functions);
1841        // Since `CustomPropertiesMap` preserves insertion order, we shouldn't have to worry about
1842        // resolving in a wrong order.
1843        for (name, kind, v) in deferred.iter() {
1844            let Some(v) = v.as_universal() else {
1845                unreachable!("Computing should have been deferred!")
1846            };
1847            substitute_references_if_needed_and_apply(
1848                name,
1849                kind,
1850                v,
1851                &mut map,
1852                stylist,
1853                computed_context,
1854                attribute_tracker,
1855            );
1856        }
1857        computed_context.builder.substitution_functions = map;
1858    }
1859}
1860
1861/// Resolve all custom properties to either substituted, invalid, or unset
1862/// (meaning we should use the inherited value).
1863///
1864/// It does cycle dependencies removal at the same time as substitution.
1865fn substitute_all(
1866    substitution_function_map: &mut ComputedSubstitutionFunctions,
1867    mut deferred_substituted_functions_map: Option<&mut AllSubstitutionFunctions>,
1868    invalid_non_custom_properties: &mut LonghandIdSet,
1869    has_color_scheme: bool,
1870    seen: &SeenSubstitutionFunctions,
1871    references_from_non_custom_properties: &NonCustomReferenceMap<Vec<Name>>,
1872    stylist: &Stylist,
1873    computed_context: &computed::Context,
1874    attr_tracker: &mut AttributeTracker,
1875) {
1876    // The cycle dependencies removal in this function is a variant
1877    // of Tarjan's algorithm. It is mostly based on the pseudo-code
1878    // listed in
1879    // https://en.wikipedia.org/w/index.php?
1880    // title=Tarjan%27s_strongly_connected_components_algorithm&oldid=801728495
1881
1882    #[derive(Clone, Eq, PartialEq, Debug)]
1883    enum VarType {
1884        Attr(Name),
1885        Custom(Name),
1886        NonCustom(SingleNonCustomReference),
1887    }
1888
1889    /// Struct recording necessary information for each variable.
1890    #[derive(Debug)]
1891    struct VarInfo {
1892        /// The name of the variable. It will be taken to save addref
1893        /// when the corresponding variable is popped from the stack.
1894        /// This also serves as a mark for whether the variable is
1895        /// currently in the stack below.
1896        var: Option<VarType>,
1897        /// If the variable is in a dependency cycle, lowlink represents
1898        /// a smaller index which corresponds to a variable in the same
1899        /// strong connected component, which is known to be accessible
1900        /// from this variable. It is not necessarily the root, though.
1901        lowlink: usize,
1902    }
1903
1904    #[derive(Debug, Default)]
1905    struct OrderIndexMap {
1906        /// The map from the custom property name to its order index.
1907        var: PrecomputedHashMap<Name, usize>,
1908        /// The map from the attribute name to its order index.
1909        attr: PrecomputedHashMap<Name, usize>,
1910    }
1911
1912    /// Context struct for traversing the variable graph, so that we can
1913    /// avoid referencing all the fields multiple times.
1914    struct Context<'a, 'b: 'a> {
1915        /// Number of variables visited. This is used as the order index
1916        /// when we visit a new unresolved variable.
1917        count: usize,
1918        /// The map from a substitution function name to its order index.
1919        index_map: OrderIndexMap,
1920        /// Mapping from a non-custom dependency to its order index.
1921        non_custom_index_map: NonCustomReferenceMap<usize>,
1922        /// Information of each variable indexed by the order index.
1923        var_info: SmallVec<[VarInfo; 5]>,
1924        /// The stack of order index of visited variables. It contains
1925        /// all unfinished strong connected components.
1926        stack: SmallVec<[usize; 5]>,
1927        /// References to non-custom properties in this strongly connected component.
1928        non_custom_references: NonCustomReferences,
1929        /// Whether the builder has seen a non-custom color-scheme reference.
1930        has_color_scheme: bool,
1931        /// Whether this strongly connected component contains any custom properties involving
1932        /// value computation.
1933        contains_computed_custom_property: bool,
1934        map: &'a mut ComputedSubstitutionFunctions,
1935        /// The stylist is used to get registered properties, and to resolve the environment to
1936        /// substitute `env()` variables.
1937        stylist: &'a Stylist,
1938        /// The computed context is used to get inherited custom
1939        /// properties  and compute registered custom properties.
1940        computed_context: &'a computed::Context<'b>,
1941        /// Longhand IDs that became invalid due to dependency cycle(s).
1942        invalid_non_custom_properties: &'a mut LonghandIdSet,
1943        /// Substitution functions that cannot yet be substituted. We store both custom
1944        /// properties (inherited and non-inherited) and attributes in the same map, since
1945        /// we need to make sure we iterate through them in the right order.
1946        deferred_substitution_functions: Option<&'a mut AllSubstitutionFunctions>,
1947    }
1948
1949    /// This function combines the traversal for cycle removal and value
1950    /// substitution. It returns either a signal None if this variable
1951    /// has been fully resolved (to either having no reference or being
1952    /// marked invalid), or the order index for the given name.
1953    ///
1954    /// When it returns, the variable corresponds to the name would be
1955    /// in one of the following states:
1956    /// * It is still in context.stack, which means it is part of an
1957    ///   potentially incomplete dependency circle.
1958    /// * It has been removed from the map.  It can be either that the
1959    ///   substitution failed, or it is inside a dependency circle.
1960    ///   When this function removes a variable from the map because
1961    ///   of dependency circle, it would put all variables in the same
1962    ///   strong connected component to the set together.
1963    /// * It doesn't have any reference, because either this variable
1964    ///   doesn't have reference at all in specified value, or it has
1965    ///   been completely resolved.
1966    /// * There is no such variable at all.
1967    fn traverse<'a, 'b>(
1968        var: VarType,
1969        non_custom_references: &NonCustomReferenceMap<Vec<Name>>,
1970        context: &mut Context<'a, 'b>,
1971        attribute_tracker: &mut AttributeTracker,
1972    ) -> Option<usize> {
1973        let kind = if matches!(var, VarType::Custom(_)) {
1974            SubstitutionFunctionKind::Var
1975        } else {
1976            SubstitutionFunctionKind::Attr
1977        };
1978        // Some shortcut checks.
1979        let value = match var {
1980            VarType::Custom(ref name) | VarType::Attr(ref name) => {
1981                let registration;
1982                let value;
1983                match kind {
1984                    SubstitutionFunctionKind::Var => {
1985                        registration = context.stylist.get_custom_property_registration(name);
1986                        value = context.map.get_var(registration, name)?.as_universal()?;
1987                    },
1988                    SubstitutionFunctionKind::Attr => {
1989                        // `attr()` is always treated as unregistered.
1990                        registration = PropertyDescriptors::unregistered();
1991                        value = context.map.get_attr(name)?.as_universal()?;
1992                    },
1993                    _ => unreachable!("Substitution kind must be var or attr for VarType::Custom."),
1994                }
1995                let is_var = matches!(kind, SubstitutionFunctionKind::Var);
1996                let is_attr = matches!(kind, SubstitutionFunctionKind::Attr);
1997                let is_root = context.computed_context.is_root_element();
1998                // We need to keep track of potential non-custom-references even on unregistered
1999                // properties for cycle-detection purposes.
2000                let non_custom_refs = find_non_custom_references(
2001                    registration,
2002                    value,
2003                    context.has_color_scheme,
2004                    is_root,
2005                    /* include_unregistered = */ true,
2006                );
2007                context.non_custom_references |= non_custom_refs.unwrap_or_default();
2008                let has_dependency = value.references.any_var
2009                    || value.references.any_attr
2010                    || non_custom_refs.is_some();
2011                // Nothing to resolve.
2012                if !has_dependency {
2013                    debug_assert!(!value.references.any_env, "Should've been handled earlier");
2014                    if is_attr || !registration.is_universal() {
2015                        // We might still need to compute the value if this is not an universal
2016                        // registration if we thought this had a dependency before but turned out
2017                        // not to be (due to has_color_scheme, for example). Note that if this was
2018                        // already computed we would've bailed out in the as_universal() check.
2019                        if is_var {
2020                            debug_assert!(
2021                                registration
2022                                    .syntax
2023                                    .as_ref()
2024                                    .unwrap()
2025                                    .dependent_types()
2026                                    .intersects(DependentDataTypes::COLOR),
2027                                "How did an unresolved value get here otherwise?",
2028                            );
2029                        }
2030                        let value = value.clone();
2031                        substitute_references_if_needed_and_apply(
2032                            name,
2033                            kind,
2034                            &value,
2035                            &mut context.map,
2036                            context.stylist,
2037                            context.computed_context,
2038                            attribute_tracker,
2039                        );
2040                    }
2041                    return None;
2042                }
2043
2044                // Has this variable been visited?
2045                let index_map = if is_var {
2046                    &mut context.index_map.var
2047                } else {
2048                    &mut context.index_map.attr
2049                };
2050                match index_map.entry(name.clone()) {
2051                    Entry::Occupied(entry) => {
2052                        return Some(*entry.get());
2053                    },
2054                    Entry::Vacant(entry) => {
2055                        entry.insert(context.count);
2056                    },
2057                }
2058                context.contains_computed_custom_property |= is_var && !registration.is_universal();
2059
2060                // Hold a strong reference to the value so that we don't
2061                // need to keep reference to context.map.
2062                Some(value.clone())
2063            },
2064            VarType::NonCustom(ref non_custom) => {
2065                let entry = &mut context.non_custom_index_map[*non_custom];
2066                if let Some(v) = entry {
2067                    return Some(*v);
2068                }
2069                *entry = Some(context.count);
2070                None
2071            },
2072        };
2073
2074        // Add new entry to the information table.
2075        let index = context.count;
2076        context.count += 1;
2077        debug_assert_eq!(index, context.var_info.len());
2078        context.var_info.push(VarInfo {
2079            var: Some(var.clone()),
2080            lowlink: index,
2081        });
2082        context.stack.push(index);
2083
2084        let mut self_ref = false;
2085        let mut lowlink = index;
2086        let visit_link = |var: VarType,
2087                          context: &mut Context,
2088                          lowlink: &mut usize,
2089                          self_ref: &mut bool,
2090                          attr_tracker: &mut AttributeTracker| {
2091            let next_index = match traverse(var, non_custom_references, context, attr_tracker) {
2092                Some(index) => index,
2093                // There is nothing to do if the next variable has been
2094                // fully resolved at this point.
2095                None => {
2096                    return;
2097                },
2098            };
2099            let next_info = &context.var_info[next_index];
2100            if next_index > index {
2101                // The next variable has a larger index than us, so it
2102                // must be inserted in the recursive call above. We want
2103                // to get its lowlink.
2104                *lowlink = cmp::min(*lowlink, next_info.lowlink);
2105            } else if next_index == index {
2106                *self_ref = true;
2107            } else if next_info.var.is_some() {
2108                // The next variable has a smaller order index and it is
2109                // in the stack, so we are at the same component.
2110                *lowlink = cmp::min(*lowlink, next_index);
2111            }
2112        };
2113        if let Some(ref v) = value.as_ref() {
2114            debug_assert!(
2115                matches!(var, VarType::Custom(_) | VarType::Attr(_)),
2116                "Non-custom property has references?"
2117            );
2118
2119            // Visit other custom properties...
2120            // FIXME: Maybe avoid visiting the same var twice if not needed?
2121            for next in &v.references.refs {
2122                if next.substitution_kind == SubstitutionFunctionKind::Env {
2123                    continue;
2124                }
2125
2126                let next_var = if next.substitution_kind == SubstitutionFunctionKind::Attr {
2127                    if context.map.get_attr(&next.name).is_none() {
2128                        let Ok(val) = get_attr_value_for_cycle_resolution(
2129                            &next.name,
2130                            &next.attribute_data,
2131                            &v.url_data,
2132                            attribute_tracker,
2133                        ) else {
2134                            continue;
2135                        };
2136                        context.map.insert_attr(&next.name, val);
2137                    }
2138                    VarType::Attr(next.name.clone())
2139                } else {
2140                    VarType::Custom(next.name.clone())
2141                };
2142
2143                visit_link(
2144                    next_var,
2145                    context,
2146                    &mut lowlink,
2147                    &mut self_ref,
2148                    attribute_tracker,
2149                );
2150            }
2151
2152            // ... Then non-custom properties.
2153            v.references.non_custom_references.for_each(|r| {
2154                visit_link(
2155                    VarType::NonCustom(r),
2156                    context,
2157                    &mut lowlink,
2158                    &mut self_ref,
2159                    attribute_tracker,
2160                );
2161            });
2162        } else if let VarType::NonCustom(non_custom) = var {
2163            let entry = &non_custom_references[non_custom];
2164            if let Some(deps) = entry.as_ref() {
2165                for d in deps {
2166                    // Visit any reference from this non-custom property to custom properties.
2167                    // TODO(bug2026785): non-custom should traverse unregistered vars and attrs.
2168                    visit_link(
2169                        VarType::Custom(d.clone()),
2170                        context,
2171                        &mut lowlink,
2172                        &mut self_ref,
2173                        attribute_tracker,
2174                    );
2175                }
2176            }
2177        }
2178
2179        context.var_info[index].lowlink = lowlink;
2180        if lowlink != index {
2181            // This variable is in a loop, but it is not the root of
2182            // this strong connected component. We simply return for
2183            // now, and the root would remove it from the map.
2184            //
2185            // This cannot be removed from the map here, because
2186            // otherwise the shortcut check at the beginning of this
2187            // function would return the wrong value.
2188            return Some(index);
2189        }
2190
2191        // This is the root of a strong-connected component.
2192        let mut in_loop = self_ref;
2193        let name;
2194
2195        let handle_variable_in_loop =
2196            |name: &Name, context: &mut Context<'a, 'b>, kind: SubstitutionFunctionKind| {
2197                if context.contains_computed_custom_property {
2198                    // These non-custom properties can't become invalid-at-compute-time from
2199                    // cyclic dependencies purely consisting of non-registered properties.
2200                    if context.non_custom_references.intersects(
2201                        NonCustomReferences::FONT_UNITS | NonCustomReferences::ROOT_FONT_UNITS,
2202                    ) {
2203                        context
2204                            .invalid_non_custom_properties
2205                            .insert(LonghandId::FontSize);
2206                    }
2207                    if context.non_custom_references.intersects(
2208                        NonCustomReferences::LH_UNITS | NonCustomReferences::ROOT_LH_UNITS,
2209                    ) {
2210                        context
2211                            .invalid_non_custom_properties
2212                            .insert(LonghandId::LineHeight);
2213                    }
2214                }
2215                // This variable is in loop. Resolve to invalid.
2216                handle_invalid_at_computed_value_time(
2217                    name,
2218                    kind,
2219                    &mut context.map,
2220                    context.computed_context,
2221                );
2222            };
2223        loop {
2224            let var_index = context
2225                .stack
2226                .pop()
2227                .expect("The current variable should still be in stack");
2228            let var_info = &mut context.var_info[var_index];
2229            // We should never visit the variable again, so it's safe
2230            // to take the name away, so that we don't do additional
2231            // reference count.
2232            let var_name = var_info
2233                .var
2234                .take()
2235                .expect("Variable should not be poped from stack twice");
2236            if var_index == index {
2237                name = match var_name {
2238                    VarType::Custom(name) | VarType::Attr(name) => name,
2239                    // At the root of this component, and it's a non-custom
2240                    // reference - we have nothing to substitute, so
2241                    // it's effectively resolved.
2242                    VarType::NonCustom(..) => return None,
2243                };
2244                break;
2245            }
2246            if let VarType::Custom(name) | VarType::Attr(name) = var_name {
2247                // Anything here is in a loop which can traverse to the
2248                // variable we are handling, so it's invalid at
2249                // computed-value time.
2250                handle_variable_in_loop(&name, context, kind);
2251            }
2252            in_loop = true;
2253        }
2254        // We've gotten to the root of this strongly connected component, so clear
2255        // whether or not it involved non-custom references.
2256        // It's fine to track it like this, because non-custom properties currently
2257        // being tracked can only participate in any loop only once.
2258        if in_loop {
2259            handle_variable_in_loop(&name, context, kind);
2260            context.non_custom_references = NonCustomReferences::default();
2261            return None;
2262        }
2263
2264        if let Some(ref v) = value {
2265            let registration = if kind == SubstitutionFunctionKind::Var {
2266                context.stylist.get_custom_property_registration(&name)
2267            } else {
2268                PropertyDescriptors::unregistered()
2269            };
2270
2271            let mut defer = false;
2272            if let Some(ref mut deferred) = context.deferred_substitution_functions {
2273                // We need to defer this property if it has a non-custom property dependency, or
2274                // any variable that it references is already deferred.
2275                defer = find_non_custom_references(
2276                    registration,
2277                    v,
2278                    context.has_color_scheme,
2279                    context.computed_context.is_root_element(),
2280                    /* include_unregistered = */ false,
2281                )
2282                .is_some()
2283                    || v.references.refs.iter().any(|reference| {
2284                        (reference.substitution_kind == SubstitutionFunctionKind::Var
2285                            && deferred
2286                                .get(&reference.name, SubstitutionFunctionKind::Var)
2287                                .is_some())
2288                            || reference.substitution_kind == SubstitutionFunctionKind::Attr
2289                    });
2290                if defer {
2291                    let value = ComputedRegisteredValue::universal(Arc::clone(v));
2292                    deferred.insert(&name, kind, value);
2293                    if kind == SubstitutionFunctionKind::Var {
2294                        context.map.remove_var(registration, &name);
2295                    } else {
2296                        context.map.remove_attr(&name);
2297                    }
2298                }
2299            }
2300
2301            // If there are no var or attr references we should already be computed and substituted by now.
2302            if !defer && (v.references.any_var || v.references.any_attr) {
2303                substitute_references_if_needed_and_apply(
2304                    &name,
2305                    kind,
2306                    v,
2307                    &mut context.map,
2308                    context.stylist,
2309                    context.computed_context,
2310                    attribute_tracker,
2311                );
2312            }
2313        }
2314        context.non_custom_references = NonCustomReferences::default();
2315
2316        // All resolved, so return the signal value.
2317        None
2318    }
2319
2320    let mut run = |make_var: fn(Name) -> VarType, seen: &PrecomputedHashSet<&Name>| {
2321        for name in seen {
2322            let mut context = Context {
2323                count: 0,
2324                index_map: OrderIndexMap::default(),
2325                non_custom_index_map: NonCustomReferenceMap::default(),
2326                stack: SmallVec::new(),
2327                var_info: SmallVec::new(),
2328                map: substitution_function_map,
2329                non_custom_references: NonCustomReferences::default(),
2330                has_color_scheme,
2331                stylist,
2332                computed_context,
2333                invalid_non_custom_properties,
2334                deferred_substitution_functions: deferred_substituted_functions_map.as_deref_mut(),
2335                contains_computed_custom_property: false,
2336            };
2337
2338            traverse(
2339                make_var((*name).clone()),
2340                references_from_non_custom_properties,
2341                &mut context,
2342                attr_tracker,
2343            );
2344        }
2345    };
2346
2347    // Note that `seen` doesn't contain names inherited from our parent, but
2348    // those can't have variable references (since we inherit the computed
2349    // variables) so we don't want to spend cycles traversing them anyway.
2350    run(VarType::Custom, &seen.var);
2351    // Traverse potentially untraversed chained references from `attr(type())`
2352    // in non-custom properties.
2353    run(VarType::Attr, &seen.attr);
2354}
2355
2356// See https://drafts.csswg.org/css-variables-2/#invalid-at-computed-value-time
2357fn handle_invalid_at_computed_value_time(
2358    name: &Name,
2359    kind: SubstitutionFunctionKind,
2360    substitution_functions: &mut ComputedSubstitutionFunctions,
2361    computed_context: &computed::Context,
2362) {
2363    if kind == SubstitutionFunctionKind::Attr {
2364        // Early return: `attr()` is always treated as unregistered.
2365        substitution_functions.remove_attr(name);
2366        return;
2367    }
2368
2369    let stylist = computed_context.style().stylist.unwrap();
2370    let registration = stylist.get_custom_property_registration(&name);
2371    if !registration.is_universal() {
2372        // For the root element, inherited maps are empty. We should just
2373        // use the initial value if any, rather than removing the name.
2374        if registration.inherits() && !computed_context.is_root_element() {
2375            let inherited = computed_context.inherited_custom_properties();
2376            if let Some(value) = inherited.get(registration, name) {
2377                substitution_functions.insert_var(registration, name, value.clone());
2378                return;
2379            }
2380        } else if let Some(ref initial_value) = registration.initial_value {
2381            if let Ok(initial_value) = compute_value(
2382                &initial_value.css,
2383                &initial_value.url_data,
2384                registration,
2385                computed_context,
2386                AttrTaint::default(),
2387            ) {
2388                substitution_functions.insert_var(registration, name, initial_value);
2389                return;
2390            }
2391        }
2392    }
2393    substitution_functions.remove_var(registration, name);
2394}
2395
2396/// Replace `var()`, `env()`, and `attr()` functions in a pre-existing variable value.
2397fn substitute_references_if_needed_and_apply(
2398    name: &Name,
2399    kind: SubstitutionFunctionKind,
2400    value: &Arc<VariableValue>,
2401    substitution_functions: &mut ComputedSubstitutionFunctions,
2402    stylist: &Stylist,
2403    computed_context: &computed::Context,
2404    attribute_tracker: &mut AttributeTracker,
2405) {
2406    debug_assert_ne!(kind, SubstitutionFunctionKind::Env);
2407    let is_var = matches!(kind, SubstitutionFunctionKind::Var);
2408    let registration = stylist.get_custom_property_registration(&name);
2409    if is_var && !value.has_references() && registration.is_universal() {
2410        // Trivial path: no references and no need to compute the value, just apply it directly.
2411        let computed_value = ComputedRegisteredValue::universal(Arc::clone(value));
2412        substitution_functions.insert_var(registration, name, computed_value);
2413        return;
2414    }
2415
2416    let inherited = computed_context.inherited_custom_properties();
2417    let url_data = &value.url_data;
2418    let substitution = match substitute_internal(
2419        value,
2420        substitution_functions,
2421        stylist,
2422        computed_context,
2423        attribute_tracker,
2424        None,
2425    ) {
2426        Ok(v) => v,
2427        Err(..) => {
2428            handle_invalid_at_computed_value_time(
2429                name,
2430                kind,
2431                substitution_functions,
2432                computed_context,
2433            );
2434            return;
2435        },
2436    };
2437
2438    // If variable fallback results in a wide keyword, deal with it now.
2439    if is_var {
2440        let css = &substitution.css;
2441        let css_wide_kw = {
2442            let mut input = ParserInput::new(&css);
2443            let mut input = Parser::new(&mut input);
2444            input.try_parse(CSSWideKeyword::parse)
2445        };
2446
2447        if let Ok(kw) = css_wide_kw {
2448            // TODO: It's unclear what this should do for revert / revert-layer, see
2449            // https://github.com/w3c/csswg-drafts/issues/9131. For now treating as unset
2450            // seems fine?
2451            match (
2452                kw,
2453                registration.inherits(),
2454                computed_context.is_root_element(),
2455            ) {
2456                (CSSWideKeyword::Initial, _, _)
2457                | (CSSWideKeyword::Revert, false, _)
2458                | (CSSWideKeyword::RevertLayer, false, _)
2459                | (CSSWideKeyword::RevertRule, false, _)
2460                | (CSSWideKeyword::Unset, false, _)
2461                | (CSSWideKeyword::Revert, true, true)
2462                | (CSSWideKeyword::RevertLayer, true, true)
2463                | (CSSWideKeyword::RevertRule, true, true)
2464                | (CSSWideKeyword::Unset, true, true)
2465                | (CSSWideKeyword::Inherit, _, true) => {
2466                    remove_and_insert_initial_value(name, registration, substitution_functions);
2467                },
2468                (CSSWideKeyword::Revert, true, false)
2469                | (CSSWideKeyword::RevertLayer, true, false)
2470                | (CSSWideKeyword::RevertRule, true, false)
2471                | (CSSWideKeyword::Inherit, _, false)
2472                | (CSSWideKeyword::Unset, true, false) => {
2473                    match inherited.get(registration, name) {
2474                        Some(value) => {
2475                            substitution_functions.insert_var(registration, name, value.clone());
2476                        },
2477                        None => {
2478                            substitution_functions.remove_var(registration, name);
2479                        },
2480                    };
2481                },
2482            }
2483            return;
2484        }
2485    }
2486
2487    match kind {
2488        SubstitutionFunctionKind::Var => {
2489            let value = match substitution.into_value(url_data, registration, computed_context) {
2490                Ok(v) => v,
2491                Err(()) => {
2492                    handle_invalid_at_computed_value_time(
2493                        name,
2494                        kind,
2495                        substitution_functions,
2496                        computed_context,
2497                    );
2498                    return;
2499                },
2500            };
2501            substitution_functions.insert_var(registration, name, value);
2502        },
2503        SubstitutionFunctionKind::Attr => {
2504            let mut value = ComputedRegisteredValue::universal(Arc::new(VariableValue::new(
2505                substitution.css.into_owned(),
2506                url_data,
2507                substitution.first_token_type,
2508                substitution.last_token_type,
2509            )));
2510            value.attr_tainted |= substitution.attr_tainted;
2511            substitution_functions.insert_attr(name, value);
2512        },
2513        SubstitutionFunctionKind::Env => unreachable!("Kind cannot be env."),
2514    }
2515}
2516
2517#[derive(Default, Debug)]
2518struct Substitution<'a> {
2519    css: Cow<'a, str>,
2520    first_token_type: TokenSerializationType,
2521    last_token_type: TokenSerializationType,
2522    attr_tainted: bool,
2523}
2524
2525impl<'a> Substitution<'a> {
2526    fn from_value(v: VariableValue, attr_tainted: bool) -> Self {
2527        Substitution {
2528            css: v.css.into(),
2529            first_token_type: v.first_token_type,
2530            last_token_type: v.last_token_type,
2531            attr_tainted,
2532        }
2533    }
2534
2535    fn into_value(
2536        self,
2537        url_data: &UrlExtraData,
2538        registration: &PropertyDescriptors,
2539        computed_context: &computed::Context,
2540    ) -> Result<ComputedRegisteredValue, ()> {
2541        if registration.is_universal() {
2542            let mut value = ComputedRegisteredValue::universal(Arc::new(VariableValue::new(
2543                self.css.into_owned(),
2544                url_data,
2545                self.first_token_type,
2546                self.last_token_type,
2547            )));
2548            value.attr_tainted |= self.attr_tainted;
2549            return Ok(value);
2550        }
2551        let taint = if self.attr_tainted {
2552            // Per spec: substitution value of an arbitrary substitution function is
2553            // attr()-tainted as a whole if any attr()-tainted values were involved
2554            // in creating that substitution value.
2555            // https://drafts.csswg.org/css-values-5/#attr-security
2556            AttrTaint::new_fully_tainted(self.css.len())
2557        } else {
2558            AttrTaint::default()
2559        };
2560        let mut v = compute_value(&self.css, url_data, registration, computed_context, taint)?;
2561        v.attr_tainted |= self.attr_tainted;
2562        Ok(v)
2563    }
2564
2565    fn new(
2566        css: Cow<'a, str>,
2567        first_token_type: TokenSerializationType,
2568        last_token_type: TokenSerializationType,
2569        attr_tainted: bool,
2570    ) -> Self {
2571        Self {
2572            css,
2573            first_token_type,
2574            last_token_type,
2575            attr_tainted,
2576        }
2577    }
2578}
2579
2580/// Result of var(), env(), and attr() substitution.
2581#[derive(Debug)]
2582pub struct SubstitutionResult<'a> {
2583    /// The resolved CSS string after substitution.
2584    pub css: Cow<'a, str>,
2585    /// Regions in the `css` string that are attr()-tainted, if any.
2586    pub attr_taint: AttrTaint,
2587}
2588
2589fn compute_value(
2590    css: &str,
2591    url_data: &UrlExtraData,
2592    registration: &PropertyDescriptors,
2593    computed_context: &computed::Context,
2594    attr_taint: AttrTaint,
2595) -> Result<ComputedRegisteredValue, ()> {
2596    debug_assert!(!registration.is_universal());
2597
2598    let mut input = ParserInput::new(&css);
2599    let mut input = Parser::new(&mut input);
2600
2601    SpecifiedRegisteredValue::compute(
2602        &mut input,
2603        registration,
2604        None,
2605        url_data,
2606        computed_context,
2607        AllowComputationallyDependent::Yes,
2608        attr_taint,
2609    )
2610}
2611
2612/// Removes the named registered custom property and inserts its uncomputed initial value.
2613fn remove_and_insert_initial_value(
2614    name: &Name,
2615    registration: &PropertyDescriptors,
2616    substitution_functions: &mut ComputedSubstitutionFunctions,
2617) {
2618    substitution_functions.remove_var(registration, name);
2619    if let Some(ref initial_value) = registration.initial_value {
2620        let value = ComputedRegisteredValue::universal(Arc::clone(initial_value));
2621        substitution_functions.insert_var(registration, name, value);
2622    }
2623}
2624
2625fn do_substitute_chunk<'a>(
2626    css: &'a str,
2627    start: usize,
2628    end: usize,
2629    first_token_type: TokenSerializationType,
2630    last_token_type: TokenSerializationType,
2631    url_data: &UrlExtraData,
2632    substitution_functions: &'a ComputedSubstitutionFunctions,
2633    stylist: &Stylist,
2634    computed_context: &computed::Context,
2635    references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>,
2636    attribute_tracker: &mut AttributeTracker,
2637    mut attr_taint: Option<&mut AttrTaint>,
2638) -> Result<Substitution<'a>, ()> {
2639    if start == end {
2640        // Empty string. Easy.
2641        return Ok(Substitution::default());
2642    }
2643    // Easy case: no references involved.
2644    if references
2645        .peek()
2646        .map_or(true, |reference| reference.end > end)
2647    {
2648        let result = &css[start..end];
2649        return Ok(Substitution::new(
2650            Cow::Borrowed(result),
2651            first_token_type,
2652            last_token_type,
2653            Default::default(),
2654        ));
2655    }
2656
2657    let mut substituted = ComputedValue::empty(url_data);
2658    let mut next_token_type = first_token_type;
2659    let mut cur_pos = start;
2660    let mut attr_tainted = false;
2661    while let Some(reference) = references.next_if(|reference| reference.end <= end) {
2662        if reference.start != cur_pos {
2663            substituted.push(
2664                &css[cur_pos..reference.start],
2665                next_token_type,
2666                reference.prev_token_type,
2667                /* attr_taint */ None,
2668            )?;
2669        }
2670
2671        let substitution = substitute_one_reference(
2672            css,
2673            url_data,
2674            substitution_functions,
2675            reference,
2676            stylist,
2677            computed_context,
2678            references,
2679            attribute_tracker,
2680        )?;
2681
2682        // Optimize the property: var(--...) case to avoid allocating at all.
2683        if reference.start == start && reference.end == end {
2684            if let Some(taint) = attr_taint.filter(|_| substitution.attr_tainted) {
2685                taint.push(start, end);
2686            }
2687            return Ok(substitution);
2688        }
2689
2690        substituted.push(
2691            &substitution.css,
2692            substitution.first_token_type,
2693            substitution.last_token_type,
2694            attr_taint
2695                .as_deref_mut()
2696                .filter(|_| substitution.attr_tainted),
2697        )?;
2698        attr_tainted |= substitution.attr_tainted;
2699        next_token_type = reference.next_token_type;
2700        cur_pos = reference.end;
2701    }
2702    // Push the rest of the value if needed.
2703    if cur_pos != end {
2704        substituted.push(
2705            &css[cur_pos..end],
2706            next_token_type,
2707            last_token_type,
2708            /* attr_taint */ None,
2709        )?;
2710    }
2711    Ok(Substitution::from_value(substituted, attr_tainted))
2712}
2713
2714fn quoted_css_string(src: &str) -> String {
2715    let mut dest = String::with_capacity(src.len() + 2);
2716    cssparser::serialize_string(src, &mut dest).unwrap();
2717    dest
2718}
2719
2720fn substitute_one_reference<'a>(
2721    css: &'a str,
2722    url_data: &UrlExtraData,
2723    substitution_functions: &'a ComputedSubstitutionFunctions,
2724    reference: &SubstitutionFunctionReference,
2725    stylist: &Stylist,
2726    computed_context: &computed::Context,
2727    references: &mut std::iter::Peekable<std::slice::Iter<SubstitutionFunctionReference>>,
2728    attribute_tracker: &mut AttributeTracker,
2729) -> Result<Substitution<'a>, ()> {
2730    let simple_attr_subst = |s: &str| {
2731        Some(Substitution::new(
2732            Cow::Owned(quoted_css_string(s)),
2733            TokenSerializationType::Nothing,
2734            TokenSerializationType::Nothing,
2735            /* attr_tainted */ true,
2736        ))
2737    };
2738    let substitution: Option<_> = match reference.substitution_kind {
2739        SubstitutionFunctionKind::Var => {
2740            let registration = stylist.get_custom_property_registration(&reference.name);
2741            substitution_functions
2742                .get_var(registration, &reference.name)
2743                .map(|v| Substitution::from_value(v.to_variable_value(), v.attr_tainted))
2744        },
2745        SubstitutionFunctionKind::Env => {
2746            let device = stylist.device();
2747            device
2748                .environment()
2749                .get(&reference.name, device, url_data)
2750                .map(|v| Substitution::from_value(v, /* attr_tainted */ false))
2751        },
2752        // https://drafts.csswg.org/css-values-5/#attr-substitution
2753        SubstitutionFunctionKind::Attr => {
2754            #[cfg(feature = "gecko")]
2755            let local_name = LocalName::cast(&reference.name);
2756            #[cfg(feature = "servo")]
2757            let local_name = LocalName::from(reference.name.as_ref());
2758            let namespace = match reference.attribute_data.namespace {
2759                ParsedNamespace::Known(ref ns) => Some(ns),
2760                ParsedNamespace::Unknown => None,
2761            };
2762            namespace
2763                .and_then(|namespace| attribute_tracker.query(&local_name, namespace))
2764                .map_or_else(
2765                    || {
2766                        // Special case when fallback and <attr-type> are omitted.
2767                        // See FAILURE: https://drafts.csswg.org/css-values-5/#attr-substitution
2768                        if reference.fallback.is_none()
2769                            && reference.attribute_data.kind == AttributeType::None
2770                        {
2771                            simple_attr_subst("")
2772                        } else {
2773                            None
2774                        }
2775                    },
2776                    |attr| {
2777                        let attr = if let AttributeType::Type(_) = &reference.attribute_data.kind {
2778                            // If we're evaluating a container query, we haven't run the cascade
2779                            // and populated substitution_functions.attributes, so we can't do the
2780                            // get_attr() lookup here.
2781                            // TODO: This means chained attr() references will not work reliably in
2782                            // container style queries:
2783                            // https://bugzilla.mozilla.org/show_bug.cgi?id=2028861
2784                            if computed_context.in_container_query {
2785                                attr
2786                            } else {
2787                                substitution_functions
2788                                    .get_attr(&reference.name)
2789                                    .map(|v| v.to_variable_value())?
2790                                    .css
2791                            }
2792                        } else {
2793                            attr
2794                        };
2795                        let mut input = ParserInput::new(&attr);
2796                        let mut parser = Parser::new(&mut input);
2797                        match &reference.attribute_data.kind {
2798                            AttributeType::Unit(unit) => {
2799                                let css = {
2800                                    // Verify that attribute data is a <number-token>.
2801                                    parser.expect_number().ok()?;
2802                                    let mut s = attr.clone();
2803                                    s.push_str(unit.as_ref());
2804                                    s
2805                                };
2806                                let serialization = match unit {
2807                                    AttrUnit::Number => TokenSerializationType::Number,
2808                                    AttrUnit::Percentage => TokenSerializationType::Percentage,
2809                                    _ => TokenSerializationType::Dimension,
2810                                };
2811                                let value =
2812                                    ComputedValue::new(css, url_data, serialization, serialization);
2813                                Some(Substitution::from_value(
2814                                    value, /* attr_tainted */ true,
2815                                ))
2816                            },
2817                            AttributeType::Type(syntax) => {
2818                                let value = SpecifiedRegisteredValue::parse(
2819                                    &mut parser,
2820                                    &syntax,
2821                                    url_data,
2822                                    None,
2823                                    AllowComputationallyDependent::Yes,
2824                                    AttrTaint::default(),
2825                                )
2826                                .ok()?;
2827                                let value = value.to_variable_value();
2828                                Some(Substitution::from_value(
2829                                    value, /* attr_tainted */ true,
2830                                ))
2831                            },
2832                            AttributeType::RawString | AttributeType::None => {
2833                                simple_attr_subst(&attr)
2834                            },
2835                            AttributeType::Invalid => None,
2836                        }
2837                    },
2838                )
2839        },
2840    };
2841
2842    if let Some(s) = substitution {
2843        // Skip references that are inside the outer variable (in fallback for example).
2844        while references
2845            .next_if(|next_ref| next_ref.end <= reference.end)
2846            .is_some()
2847        {}
2848        return Ok(s);
2849    }
2850
2851    let Some(ref fallback) = reference.fallback else {
2852        return Err(());
2853    };
2854
2855    do_substitute_chunk(
2856        css,
2857        fallback.start.get(),
2858        reference.end - 1, // Skip the closing parenthesis of the reference value.
2859        fallback.first_token_type,
2860        fallback.last_token_type,
2861        url_data,
2862        substitution_functions,
2863        stylist,
2864        computed_context,
2865        references,
2866        attribute_tracker,
2867        /* attr_taint */ None,
2868    )
2869}
2870
2871/// Replace `var()`, `env()`, and `attr()` functions. Return `Err(..)` for invalid at computed time.
2872fn substitute_internal<'a>(
2873    variable_value: &'a VariableValue,
2874    substitution_functions: &'a ComputedSubstitutionFunctions,
2875    stylist: &Stylist,
2876    computed_context: &computed::Context,
2877    attribute_tracker: &mut AttributeTracker,
2878    mut attr_taint: Option<&mut AttrTaint>,
2879) -> Result<Substitution<'a>, ()> {
2880    let mut refs = variable_value.references.refs.iter().peekable();
2881    do_substitute_chunk(
2882        &variable_value.css,
2883        /* start = */ 0,
2884        /* end = */ variable_value.css.len(),
2885        variable_value.first_token_type,
2886        variable_value.last_token_type,
2887        &variable_value.url_data,
2888        substitution_functions,
2889        stylist,
2890        computed_context,
2891        &mut refs,
2892        attribute_tracker,
2893        attr_taint.as_deref_mut(),
2894    )
2895}
2896
2897/// Replace var(), env(), and attr() functions, returning the resulting CSS string.
2898pub fn substitute<'a>(
2899    variable_value: &'a VariableValue,
2900    substitution_functions: &'a ComputedSubstitutionFunctions,
2901    stylist: &Stylist,
2902    computed_context: &computed::Context,
2903    attribute_tracker: &mut AttributeTracker,
2904) -> Result<SubstitutionResult<'a>, ()> {
2905    debug_assert!(variable_value.has_references());
2906    let mut attr_taint = AttrTaint::default();
2907    let v = substitute_internal(
2908        variable_value,
2909        substitution_functions,
2910        stylist,
2911        computed_context,
2912        attribute_tracker,
2913        Some(&mut attr_taint),
2914    )?;
2915    Ok(SubstitutionResult {
2916        css: v.css,
2917        attr_taint,
2918    })
2919}