style/
custom_properties.rs

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