Skip to main content

style/
custom_properties.rs

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