style/
custom_properties.rs

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