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