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::custom_properties_map::{CustomPropertiesMap, OwnMap};
10use crate::device::Device;
11use crate::dom::AttributeTracker;
12use crate::properties::{CSSWideKeyword, PrioritaryPropertyId};
13use crate::properties_and_values::{
14    rule::Descriptors as PropertyDescriptors,
15    syntax::Descriptor as SyntaxDescriptor,
16    value::{
17        AllowComputationallyDependent, ComputedValue as ComputedRegisteredValue,
18        SpecifiedValue as SpecifiedRegisteredValue,
19    },
20};
21use crate::stylesheets::UrlExtraData;
22use crate::stylist::Stylist;
23use crate::typed_om::{
24    ToTyped, TypedValue, UnparsedSegment, UnparsedValue, VariableReferenceValue,
25};
26use crate::values::computed;
27use crate::values::generics::calc::SortKey as AttrUnit;
28use crate::values::specified::{param::LinkParamValueOrNone, NoCalcLength, ParsedNamespace};
29use crate::{derives::*, Namespace, Prefix};
30use crate::{Atom, LocalName};
31use cssparser::{
32    CowRcStr, Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType,
33};
34use rustc_hash::FxHashMap;
35use selectors::parser::SelectorParseErrorKind;
36use servo_arc::Arc;
37use smallvec::SmallVec;
38use std::borrow::Cow;
39use std::fmt::{self, Write};
40use std::num;
41use std::ops::{Index, IndexMut};
42use style_traits::{CssString, CssWriter, ParseError, StyleParseErrorKind, ToCss};
43use thin_vec::ThinVec;
44
45/// The environment from which to get `env` function values.
46///
47/// TODO(emilio): If this becomes a bit more complex we should probably move it
48/// to the `media_queries` module, or something.
49#[derive(Debug, MallocSizeOf)]
50pub struct CssEnvironment;
51
52type EnvironmentEvaluator = fn(device: &Device, url_data: &UrlExtraData) -> VariableValue;
53
54struct EnvironmentVariable {
55    name: Atom,
56    evaluator: EnvironmentEvaluator,
57}
58
59macro_rules! make_variable {
60    ($name:expr, $evaluator:expr) => {{
61        EnvironmentVariable {
62            name: $name,
63            evaluator: $evaluator,
64        }
65    }};
66}
67
68fn get_safearea_inset_top(device: &Device, url_data: &UrlExtraData) -> VariableValue {
69    VariableValue::pixels(device.safe_area_insets().top, url_data)
70}
71
72fn get_safearea_inset_bottom(device: &Device, url_data: &UrlExtraData) -> VariableValue {
73    VariableValue::pixels(device.safe_area_insets().bottom, url_data)
74}
75
76fn get_safearea_inset_left(device: &Device, url_data: &UrlExtraData) -> VariableValue {
77    VariableValue::pixels(device.safe_area_insets().left, url_data)
78}
79
80fn get_safearea_inset_right(device: &Device, url_data: &UrlExtraData) -> VariableValue {
81    VariableValue::pixels(device.safe_area_insets().right, url_data)
82}
83
84#[cfg(feature = "gecko")]
85fn get_content_preferred_color_scheme(device: &Device, url_data: &UrlExtraData) -> VariableValue {
86    use crate::queries::values::PrefersColorScheme;
87    let prefers_color_scheme = unsafe {
88        crate::gecko_bindings::bindings::Gecko_MediaFeatures_PrefersColorScheme(
89            device.document(),
90            /* use_content = */ true,
91        )
92    };
93    VariableValue::ident(
94        match prefers_color_scheme {
95            PrefersColorScheme::Light => "light",
96            PrefersColorScheme::Dark => "dark",
97        },
98        url_data,
99    )
100}
101
102#[cfg(feature = "servo")]
103fn get_content_preferred_color_scheme(_device: &Device, url_data: &UrlExtraData) -> VariableValue {
104    // TODO: Add an implementation for Servo.
105    VariableValue::ident("light", url_data)
106}
107
108fn get_scrollbar_inline_size(device: &Device, url_data: &UrlExtraData) -> VariableValue {
109    VariableValue::pixels(device.scrollbar_inline_size().px(), url_data)
110}
111
112fn get_hairline(device: &Device, url_data: &UrlExtraData) -> VariableValue {
113    VariableValue::pixels(
114        app_units::Au(device.app_units_per_device_pixel()).to_f32_px(),
115        url_data,
116    )
117}
118
119static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [
120    make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top),
121    make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom),
122    make_variable!(atom!("safe-area-inset-left"), get_safearea_inset_left),
123    make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right),
124];
125
126#[cfg(feature = "gecko")]
127macro_rules! lnf_int {
128    ($id:ident) => {
129        unsafe {
130            crate::gecko_bindings::bindings::Gecko_GetLookAndFeelInt(
131                crate::gecko_bindings::bindings::LookAndFeel_IntID::$id as i32,
132            )
133        }
134    };
135}
136
137#[cfg(feature = "servo")]
138macro_rules! lnf_int {
139    ($id:ident) => {
140        // TODO: Add an implementation for Servo.
141        0
142    };
143}
144
145macro_rules! lnf_int_variable {
146    ($atom:expr, $id:ident, $ctor:ident) => {{
147        fn __eval(_: &Device, url_data: &UrlExtraData) -> VariableValue {
148            VariableValue::$ctor(lnf_int!($id), url_data)
149        }
150        make_variable!($atom, __eval)
151    }};
152}
153
154fn eval_gtk_csd_titlebar_radius(device: &Device, url_data: &UrlExtraData) -> VariableValue {
155    let int_pixels = lnf_int!(TitlebarRadius);
156    let unzoomed_scale =
157        device.device_pixel_ratio_ignoring_full_zoom().get() / device.device_pixel_ratio().get();
158    VariableValue::pixels(int_pixels as f32 * unzoomed_scale, url_data)
159}
160
161static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 9] = [
162    make_variable!(
163        atom!("-moz-gtk-csd-titlebar-radius"),
164        eval_gtk_csd_titlebar_radius
165    ),
166    lnf_int_variable!(
167        atom!("-moz-gtk-csd-tooltip-radius"),
168        TooltipRadius,
169        int_pixels
170    ),
171    lnf_int_variable!(
172        atom!("-moz-gtk-csd-close-button-position"),
173        GTKCSDCloseButtonPosition,
174        integer
175    ),
176    lnf_int_variable!(
177        atom!("-moz-gtk-csd-minimize-button-position"),
178        GTKCSDMinimizeButtonPosition,
179        integer
180    ),
181    lnf_int_variable!(
182        atom!("-moz-gtk-csd-maximize-button-position"),
183        GTKCSDMaximizeButtonPosition,
184        integer
185    ),
186    lnf_int_variable!(
187        atom!("-moz-overlay-scrollbar-fade-duration"),
188        ScrollbarFadeDuration,
189        int_ms
190    ),
191    make_variable!(
192        atom!("-moz-content-preferred-color-scheme"),
193        get_content_preferred_color_scheme
194    ),
195    make_variable!(atom!("scrollbar-inline-size"), get_scrollbar_inline_size),
196    make_variable!(atom!("hairline"), get_hairline),
197];
198
199impl CssEnvironment {
200    /// Get an env() variable, either custom or not.
201    #[inline]
202    pub fn get(
203        &self,
204        name: &Atom,
205        device: &Device,
206        url_data: &UrlExtraData,
207    ) -> Option<VariableValue> {
208        #[cfg(feature = "gecko")]
209        let is_link_parameter = name.as_slice().starts_with(&[b'-' as u16, b'-' as u16]);
210        #[cfg(feature = "servo")]
211        let is_link_parameter = name.starts_with("--");
212        if is_link_parameter {
213            let param = device
214                .link_parameters()?
215                .0
216                .iter()
217                .find(|p| p.name.0 == *name)?;
218            if let LinkParamValueOrNone::Specified(val) = &param.value {
219                let mut input = cssparser::ParserInput::new(val.as_ref());
220                let mut parser = cssparser::Parser::new(&mut input);
221
222                // need to carry around full variable value https://bugzilla.mozilla.org/show_bug.cgi?id=2028998
223                return VariableValue::parse(&mut parser, None, url_data).ok();
224            }
225            return None;
226        }
227
228        if let Some(var) = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name) {
229            return Some((var.evaluator)(device, url_data));
230        }
231        if !url_data.chrome_rules_enabled() {
232            return None;
233        }
234        let var = CHROME_ENVIRONMENT_VARIABLES
235            .iter()
236            .find(|var| var.name == *name)?;
237        Some((var.evaluator)(device, url_data))
238    }
239}
240
241/// A custom property name is just an `Atom`.
242///
243/// Note that this does not include the `--` prefix
244pub type Name = Atom;
245
246/// Parse a custom property name.
247///
248/// <https://drafts.csswg.org/css-variables/#typedef-custom-property-name>
249pub fn parse_name(s: &str) -> Result<&str, ()> {
250    if s.starts_with("--") && s.len() > 2 {
251        Ok(&s[2..])
252    } else {
253        Err(())
254    }
255}
256
257/// A value for a custom property is just a set of tokens.
258///
259/// We preserve the original CSS for serialization, and also the variable
260/// references to other custom property names.
261#[derive(Clone, Debug, MallocSizeOf, ToShmem)]
262pub struct VariableValue {
263    /// The raw CSS string.
264    pub css: String,
265
266    /// The url data of the stylesheet where this value came from.
267    pub url_data: UrlExtraData,
268
269    first_token_type: TokenSerializationType,
270    last_token_type: TokenSerializationType,
271
272    /// var(), env(), attr() or non-custom property (e.g. through `em`) references.
273    pub references: References,
274}
275
276trivial_to_computed_value!(VariableValue);
277
278/// Given a potentially registered variable value turn it into a computed custom property value.
279pub(crate) fn compute_variable_value(
280    value: &Arc<VariableValue>,
281    registration: &PropertyDescriptors,
282    computed_context: &computed::Context,
283) -> Option<ComputedRegisteredValue> {
284    if registration.is_universal() {
285        return Some(ComputedRegisteredValue::universal(Arc::clone(value)));
286    }
287    compute_value(
288        &value.css,
289        &value.url_data,
290        registration,
291        computed_context,
292        AttrTaint::default(),
293    )
294    .ok()
295}
296
297// For all purposes, we want values to be considered equal if their css text is equal.
298impl PartialEq for VariableValue {
299    fn eq(&self, other: &Self) -> bool {
300        self.css == other.css
301    }
302}
303
304impl Eq for VariableValue {}
305
306impl ToCss for SpecifiedValue {
307    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
308    where
309        W: Write,
310    {
311        dest.write_str(&self.css)
312    }
313}
314
315impl ToTyped for SpecifiedValue {
316    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
317        let unparsed_value = reify_variable_value(self)?;
318        dest.push(TypedValue::Unparsed(unparsed_value));
319        Ok(())
320    }
321}
322
323fn reify_variable_value(value: &VariableValue) -> Result<UnparsedValue, ()> {
324    let mut reference_index = 0;
325    reify_variable_value_range(
326        &value.css,
327        &value.references.refs,
328        &mut reference_index,
329        0,
330        value.css.len(),
331    )
332}
333
334/// Reify a slice of the CSS string into UnparsedSegment entries.
335///
336/// References are stored in source order, with outer substitution functions
337/// inserted before references in their fallback. The shared `reference_index`
338/// relies on this ordering to recurse into fallbacks without reprocessing
339/// nested referecences.
340fn reify_variable_value_range(
341    css: &str,
342    references: &[SubstitutionFunctionReference],
343    reference_index: &mut usize,
344    start: usize,
345    end: usize,
346) -> Result<UnparsedValue, ()> {
347    debug_assert!(start <= end);
348    debug_assert!(end <= css.len());
349
350    let mut values = ThinVec::new();
351    let mut cur_pos = start;
352
353    while *reference_index < references.len() {
354        let reference = &references[*reference_index];
355
356        if reference.start >= end {
357            break;
358        }
359
360        debug_assert!(reference.start >= cur_pos);
361        debug_assert!(reference.start <= reference.end);
362        debug_assert!(reference.end <= css.len());
363
364        if cur_pos < reference.start {
365            values.push(UnparsedSegment::String(CssString::from(
366                &css[cur_pos..reference.start],
367            )));
368        }
369
370        *reference_index += 1;
371
372        if reference.substitution_kind != SubstitutionFunctionKind::Var {
373            return Err(());
374        }
375
376        let (fallback, has_fallback) = if let Some(fallback) = &reference.fallback {
377            debug_assert!(fallback.start.get() <= reference.end - 1);
378
379            (
380                reify_variable_value_range(
381                    css,
382                    references,
383                    reference_index,
384                    fallback.start.get(),
385                    reference.end - 1, // Skip the closing ')'.
386                )?,
387                true,
388            )
389        } else {
390            (ThinVec::new(), false)
391        };
392
393        values.push(UnparsedSegment::VariableReference(VariableReferenceValue {
394            variable: CssString::from(format!("--{}", reference.name)),
395            fallback,
396            has_fallback,
397        }));
398
399        cur_pos = reference.end;
400    }
401
402    if cur_pos < end {
403        values.push(UnparsedSegment::String(CssString::from(&css[cur_pos..end])));
404    }
405
406    Ok(values)
407}
408
409/// A pair of separate CustomPropertiesMaps, split between custom properties
410/// that have the inherit flag set and those with the flag unset.
411#[repr(C)]
412#[derive(Clone, Debug, Default, PartialEq)]
413pub struct ComputedCustomProperties {
414    /// Map for custom properties with inherit flag set, including non-registered
415    /// ones.
416    pub inherited: CustomPropertiesMap,
417    /// Map for custom properties with inherit flag unset.
418    pub non_inherited: CustomPropertiesMap,
419}
420
421impl ComputedCustomProperties {
422    /// Return whether the inherited and non_inherited maps are none.
423    pub fn is_empty(&self) -> bool {
424        self.inherited.is_empty() && self.non_inherited.is_empty()
425    }
426
427    /// Return the name and value of the property at specified index, if any.
428    pub fn property_at(&self, index: usize) -> Option<(&Name, &Option<ComputedRegisteredValue>)> {
429        // Just expose the custom property items from custom_properties.inherited, followed
430        // by custom property items from custom_properties.non_inherited.
431        self.inherited
432            .get_index(index)
433            .or_else(|| self.non_inherited.get_index(index - self.inherited.len()))
434    }
435
436    /// Insert a custom property in the corresponding inherited/non_inherited
437    /// map, depending on whether the inherit flag is set or unset.
438    pub fn insert(
439        &mut self,
440        registration: &PropertyDescriptors,
441        name: &Name,
442        value: ComputedRegisteredValue,
443    ) {
444        self.map_mut(registration).insert(name, value)
445    }
446
447    /// Remove a custom property from the corresponding inherited/non_inherited
448    /// map, depending on whether the inherit flag is set or unset.
449    pub fn remove(&mut self, registration: &PropertyDescriptors, name: &Name) {
450        self.map_mut(registration).remove(name);
451    }
452
453    /// Shrink the capacity of the inherited maps as much as possible.
454    pub fn shrink_to_fit(&mut self) {
455        self.inherited.shrink_to_fit();
456        self.non_inherited.shrink_to_fit();
457    }
458
459    fn map_mut(&mut self, registration: &PropertyDescriptors) -> &mut CustomPropertiesMap {
460        if registration.inherits() {
461            &mut self.inherited
462        } else {
463            &mut self.non_inherited
464        }
465    }
466
467    /// Returns the relevant custom property value given a registration.
468    pub fn get(
469        &self,
470        registration: &PropertyDescriptors,
471        name: &Name,
472    ) -> Option<&ComputedRegisteredValue> {
473        if registration.inherits() {
474            self.inherited.get(name)
475        } else {
476            self.non_inherited.get(name)
477        }
478    }
479}
480
481/// Both specified and computed values are VariableValues, the difference is
482/// whether var() functions are expanded.
483pub type SpecifiedValue = VariableValue;
484/// Both specified and computed values are VariableValues, the difference is
485/// whether var() functions are expanded.
486pub type ComputedValue = VariableValue;
487
488/// Set of flags to references this custom property makes.
489#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, MallocSizeOf, ToShmem)]
490pub struct ReferenceFlags(u8);
491
492bitflags! {
493    impl ReferenceFlags : u8 {
494        /// At least one custom property depends on font-relative units.
495        const FONT_UNITS = 1 << 0;
496        /// At least one custom property depends on root element's font-relative units.
497        const ROOT_FONT_UNITS = 1 << 1;
498        /// At least one custom property depends on line height units.
499        const LH_UNITS = 1 << 2;
500        /// At least one custom property depends on root element's line height units.
501        const ROOT_LH_UNITS = 1 << 3;
502        /// The value depends on the used color-scheme (e.g. a registered `<color>` property).
503        const COLOR_SCHEME = 1 << 4;
504        /// All dependencies not depending on the root element.
505        const NON_ROOT_DEPENDENCIES = Self::FONT_UNITS.0 | Self::LH_UNITS.0;
506        /// All dependencies depending on the root element.
507        const ROOT_DEPENDENCIES = Self::ROOT_FONT_UNITS.0 | Self::ROOT_LH_UNITS.0;
508        /// All non-custom dependencies
509        const NON_CUSTOM = Self::NON_ROOT_DEPENDENCIES.0 | Self::ROOT_DEPENDENCIES.0;
510        /// At least one attr() reference.
511        const ATTR = 1 << 5;
512        /// At least one env() reference.
513        const ENV = 1 << 6;
514        /// At least one var() reference.
515        const VAR = 1 << 7;
516    }
517}
518
519impl ReferenceFlags {
520    /// Iterate for each non custom
521    pub fn for_each_non_custom<F>(mut self, is_root_element: bool, mut f: F)
522    where
523        F: FnMut(SingleNonCustomReference),
524    {
525        // On the root element, rem etc refer to the root's own dependencies.
526        if is_root_element {
527            if self.intersects(Self::ROOT_FONT_UNITS) {
528                self.remove(Self::ROOT_FONT_UNITS);
529                self |= Self::FONT_UNITS;
530            }
531            if self.intersects(Self::ROOT_LH_UNITS) {
532                self.remove(Self::ROOT_FONT_UNITS);
533                self |= Self::LH_UNITS;
534            }
535        }
536
537        for (_, r) in self.iter_names() {
538            let single = match r {
539                Self::FONT_UNITS => SingleNonCustomReference::FontUnits,
540                Self::LH_UNITS => SingleNonCustomReference::LhUnits,
541                Self::COLOR_SCHEME => SingleNonCustomReference::ColorScheme,
542                Self::ROOT_FONT_UNITS
543                | Self::ROOT_LH_UNITS
544                | Self::VAR
545                | Self::ENV
546                | Self::ATTR => continue,
547                _ => unreachable!("Unexpected single bit value"),
548            };
549            f(single);
550        }
551    }
552
553    fn from_unit(value: &CowRcStr) -> Self {
554        // For registered properties, any reference to font-relative dimensions
555        // make it dependent on font-related properties.
556        // TODO(dshin): When we unit algebra gets implemented and handled -
557        // Is it valid to say that `calc(1em / 2em * 3px)` triggers this?
558        if value.eq_ignore_ascii_case(NoCalcLength::LH) {
559            return Self::FONT_UNITS | Self::LH_UNITS;
560        }
561        if value.eq_ignore_ascii_case(NoCalcLength::EM)
562            || value.eq_ignore_ascii_case(NoCalcLength::EX)
563            || value.eq_ignore_ascii_case(NoCalcLength::CAP)
564            || value.eq_ignore_ascii_case(NoCalcLength::CH)
565            || value.eq_ignore_ascii_case(NoCalcLength::IC)
566        {
567            return Self::FONT_UNITS;
568        }
569        if value.eq_ignore_ascii_case(NoCalcLength::RLH) {
570            return Self::ROOT_FONT_UNITS | Self::ROOT_LH_UNITS;
571        }
572        if value.eq_ignore_ascii_case(NoCalcLength::REM)
573            || value.eq_ignore_ascii_case(NoCalcLength::REX)
574            || value.eq_ignore_ascii_case(NoCalcLength::RCH)
575            || value.eq_ignore_ascii_case(NoCalcLength::RCAP)
576            || value.eq_ignore_ascii_case(NoCalcLength::RIC)
577        {
578            return Self::ROOT_FONT_UNITS;
579        }
580        Self::empty()
581    }
582}
583
584/// A non-custom reference that participates in cycle resolution.
585/// TODO(emilio): This should probably eventually become just PrioritaryPropertyId.
586#[derive(Clone, Copy, Debug, Eq, PartialEq)]
587#[allow(missing_docs)]
588pub enum SingleNonCustomReference {
589    FontUnits = 0,
590    LhUnits,
591    ColorScheme,
592}
593
594impl SingleNonCustomReference {
595    /// Returns a prioritary id for this reference.
596    pub fn to_prioritary_id(self) -> PrioritaryPropertyId {
597        match self {
598            Self::FontUnits => PrioritaryPropertyId::FontSize,
599            Self::LhUnits => PrioritaryPropertyId::LineHeight,
600            Self::ColorScheme => PrioritaryPropertyId::ColorScheme,
601        }
602    }
603}
604
605/// A map from NonCustomReferenceMap to a T.
606pub struct NonCustomReferenceMap<T>([Option<T>; 3]);
607
608impl<T> Default for NonCustomReferenceMap<T> {
609    fn default() -> Self {
610        NonCustomReferenceMap(Default::default())
611    }
612}
613
614impl<T> Index<SingleNonCustomReference> for NonCustomReferenceMap<T> {
615    type Output = Option<T>;
616
617    fn index(&self, reference: SingleNonCustomReference) -> &Self::Output {
618        &self.0[reference as usize]
619    }
620}
621
622impl<T> IndexMut<SingleNonCustomReference> for NonCustomReferenceMap<T> {
623    fn index_mut(&mut self, reference: SingleNonCustomReference) -> &mut Self::Output {
624        &mut self.0[reference as usize]
625    }
626}
627
628/// Substitution function source: var, env, attr.
629#[derive(Copy, Clone, Debug, MallocSizeOf, Hash, Eq, PartialEq, ToShmem, Parse)]
630pub enum SubstitutionFunctionKind {
631    /// CSS variable / custom property
632    Var,
633    /// Environment variable
634    Env,
635    /// DOM attribute
636    Attr,
637}
638
639/// A wrapper map that encapsulates both the custom properties and attributes
640/// for a given element.
641#[repr(C)]
642#[derive(Clone, Debug, Default, PartialEq)]
643pub struct ComputedSubstitutionFunctions {
644    /// The applicable custom properties (includes inherited and non-inherited).
645    pub custom_properties: ComputedCustomProperties,
646    /// The applicable DOM attributes.
647    pub attributes: OwnMap,
648}
649
650impl ComputedSubstitutionFunctions {
651    /// Creates a substitution function map from optional custom properties
652    /// and DOM attributes.
653    #[inline(always)]
654    pub fn new(
655        custom_properties: Option<ComputedCustomProperties>,
656        attributes: Option<OwnMap>,
657    ) -> Self {
658        Self {
659            custom_properties: custom_properties.unwrap_or_default(),
660            attributes: attributes.unwrap_or_default(),
661        }
662    }
663
664    #[inline(always)]
665    pub(crate) fn insert_var(
666        &mut self,
667        registration: &PropertyDescriptors,
668        name: &Name,
669        value: ComputedRegisteredValue,
670    ) {
671        self.custom_properties.insert(registration, name, value);
672    }
673
674    #[inline(always)]
675    pub(crate) fn insert_attr(&mut self, name: &Name, value: ComputedRegisteredValue) {
676        self.attributes.insert(name.clone(), Some(value));
677    }
678
679    #[inline(always)]
680    pub(crate) fn remove_var(&mut self, registration: &PropertyDescriptors, name: &Name) {
681        self.custom_properties.remove(registration, name);
682    }
683
684    #[inline(always)]
685    pub(crate) fn remove_attr(&mut self, name: &Name) {
686        self.attributes.insert(name.clone(), None);
687    }
688
689    #[inline(always)]
690    pub(crate) fn get_var(
691        &self,
692        registration: &PropertyDescriptors,
693        name: &Name,
694    ) -> Option<&ComputedRegisteredValue> {
695        self.custom_properties.get(registration, name)
696    }
697
698    #[inline(always)]
699    pub(crate) fn get_attr(&self, name: &Name) -> Option<&ComputedRegisteredValue> {
700        self.attributes.get(name).and_then(|p| p.as_ref())
701    }
702}
703
704#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem, Parse)]
705enum AttributeType {
706    Invalid,
707    None,
708    RawString,
709    Type(SyntaxDescriptor),
710    Unit(AttrUnit),
711}
712
713/// Data specific to an attr() call like type and namespace.
714#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
715pub struct AttributeData {
716    kind: AttributeType,
717    namespace: ParsedNamespace,
718}
719
720/// For a CSS string, the range, counted in bytes, that is attr()-tainted.
721#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem, ToComputedValue)]
722pub struct AttrTaintedRange {
723    /// Start of the range, counted in bytes. Inclusive.
724    start: usize,
725    /// End of the range, counted in bytes. Exclusive.
726    end: usize,
727}
728
729impl AttrTaintedRange {
730    /// Creates a range within a CSS string that is tainted by attr().
731    #[inline(always)]
732    pub fn new(start: usize, end: usize) -> Self {
733        debug_assert!(start <= end);
734        Self { start, end }
735    }
736}
737
738/// In CSS Values and Units, values produced by `attr()` are considered attr()-tainted, as are
739/// functions that contain an attr()-tainted value. Using an attr()-tainted value as or in a <url>
740/// makes a declaration invalid at computed-value time.
741/// https://drafts.csswg.org/css-values-5/#attr-security
742#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
743pub struct AttrTaint(SmallVec<[AttrTaintedRange; 1]>);
744
745impl AttrTaint {
746    /// For a CSS string, determine whether any `<url>` overlapping this `range`
747    /// is disallowed due to attr()-tainting.
748    #[inline(always)]
749    pub fn should_disallow_urls_in_range(&self, range: &AttrTaintedRange) -> bool {
750        self.0
751            .iter()
752            .any(|r| r.start <= range.end && r.end >= range.start)
753    }
754
755    /// Returns true if the attr()-tainted range contains no elements.
756    #[inline(always)]
757    pub fn is_empty(&self) -> bool {
758        self.0.is_empty()
759    }
760
761    #[inline(always)]
762    fn new_fully_tainted(end: usize) -> Self {
763        let mut taint = Self::default();
764        taint.push(0, end);
765        taint
766    }
767
768    #[inline(always)]
769    fn push(&mut self, start: usize, end: usize) {
770        self.0.push(AttrTaintedRange::new(start, end));
771    }
772}
773
774/// The fallback of a particular value.
775#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
776pub struct VariableFallback {
777    // NOTE(emilio): We don't track fallback end, because we rely on the missing closing
778    // parenthesis, if any, to be inserted, which means that we can rely on our end being
779    // reference.end - 1.
780    start: num::NonZeroUsize,
781    first_token_type: TokenSerializationType,
782    last_token_type: TokenSerializationType,
783    /// References from this fallback value.
784    pub references: References,
785}
786
787/// A reference to a substitution function like env() / var() / attr().
788#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
789pub struct SubstitutionFunctionReference {
790    /// The identifier for this substitution function (the first argument).
791    pub name: Name,
792    start: usize,
793    end: usize,
794    /// The fallback for this function, if any.
795    pub fallback: Option<VariableFallback>,
796    /// attr() specific data, only relevant if substitution_kind is `Attr`..
797    pub attribute_data: AttributeData,
798    prev_token_type: TokenSerializationType,
799    next_token_type: TokenSerializationType,
800    /// The kind of substitution function we are.
801    pub substitution_kind: SubstitutionFunctionKind,
802}
803
804impl SubstitutionFunctionReference {
805    /// Whether we're an attr(... type(...)) reference.
806    pub fn is_attr_with_type(&self) -> bool {
807        self.substitution_kind == SubstitutionFunctionKind::Attr
808            && matches!(self.attribute_data.kind, AttributeType::Type(..))
809    }
810}
811
812/// A struct holding information about the external references to that a custom property value may
813/// have.
814#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
815pub struct References {
816    /// The actual list of references that we end up with.
817    pub refs: Vec<SubstitutionFunctionReference>,
818    /// Various flags about the kind of references we hold. Note that these include data about our
819    /// nested references.
820    ///
821    /// TODO(emilio): Do we need to distinguish between own and nested flags?
822    pub flags: ReferenceFlags,
823}
824
825impl References {
826    fn has_references(&self) -> bool {
827        !self.refs.is_empty()
828    }
829
830    pub(crate) fn non_custom_references(&self, is_root_element: bool) -> ReferenceFlags {
831        let mut mask = ReferenceFlags::NON_ROOT_DEPENDENCIES;
832        if is_root_element {
833            mask |= ReferenceFlags::ROOT_DEPENDENCIES
834        }
835        self.flags & mask
836    }
837}
838
839impl VariableValue {
840    fn empty(url_data: &UrlExtraData) -> Self {
841        Self {
842            css: String::new(),
843            last_token_type: Default::default(),
844            first_token_type: Default::default(),
845            url_data: url_data.clone(),
846            references: Default::default(),
847        }
848    }
849
850    /// Create a new custom property without parsing if the CSS is known to be valid and contain no
851    /// references.
852    pub fn new(
853        css: String,
854        url_data: &UrlExtraData,
855        first_token_type: TokenSerializationType,
856        last_token_type: TokenSerializationType,
857    ) -> Self {
858        Self {
859            css,
860            url_data: url_data.clone(),
861            first_token_type,
862            last_token_type,
863            references: References::default(),
864        }
865    }
866
867    fn push<'i>(
868        &mut self,
869        css: &str,
870        css_first_token_type: TokenSerializationType,
871        css_last_token_type: TokenSerializationType,
872        attr_taint: Option<&mut AttrTaint>,
873    ) -> Result<(), ()> {
874        /// Prevent values from getting terribly big since you can use custom
875        /// properties exponentially.
876        ///
877        /// This number (2MB) is somewhat arbitrary, but silly enough that no
878        /// reasonable page should hit it. We could limit by number of total
879        /// substitutions, but that was very easy to work around in practice
880        /// (just choose a larger initial value and boom).
881        const MAX_VALUE_LENGTH_IN_BYTES: usize = 2 * 1024 * 1024;
882
883        if self.css.len() + css.len() > MAX_VALUE_LENGTH_IN_BYTES {
884            return Err(());
885        }
886
887        // This happens e.g. between two subsequent var() functions:
888        // `var(--a)var(--b)`.
889        //
890        // In that case, css_*_token_type is nonsensical.
891        if css.is_empty() {
892            return Ok(());
893        }
894
895        self.first_token_type.set_if_nothing(css_first_token_type);
896        // If self.first_token_type was nothing,
897        // self.last_token_type is also nothing and this will be false:
898        if self
899            .last_token_type
900            .needs_separator_when_before(css_first_token_type)
901        {
902            self.css.push_str("/**/")
903        }
904        let start = self.css.len();
905        self.css.push_str(css);
906        let end = self.css.len();
907        if let Some(taint) = attr_taint {
908            taint.push(start, end);
909        }
910        self.last_token_type = css_last_token_type;
911        Ok(())
912    }
913
914    /// Parse a custom property value.
915    pub fn parse<'i, 't>(
916        input: &mut Parser<'i, 't>,
917        namespaces: Option<&FxHashMap<Prefix, Namespace>>,
918        url_data: &UrlExtraData,
919    ) -> Result<Self, ParseError<'i>> {
920        let mut references = References::default();
921        let mut missing_closing_characters = String::new();
922        let start_position = input.position();
923        let (first_token_type, last_token_type) = parse_declaration_value(
924            input,
925            start_position,
926            namespaces,
927            &mut references,
928            &mut missing_closing_characters,
929        )?;
930        let mut css = input
931            .slice_from(start_position)
932            .trim_ascii_start()
933            .to_owned();
934        if !missing_closing_characters.is_empty() {
935            // Unescaped backslash at EOF in a quoted string is ignored.
936            if css.ends_with("\\")
937                && matches!(missing_closing_characters.as_bytes()[0], b'"' | b'\'')
938            {
939                css.pop();
940            }
941            css.push_str(&missing_closing_characters);
942        }
943
944        css.truncate(css.trim_ascii_end().len());
945        css.shrink_to_fit();
946        references.refs.shrink_to_fit();
947
948        Ok(Self {
949            css,
950            url_data: url_data.clone(),
951            first_token_type,
952            last_token_type,
953            references,
954        })
955    }
956
957    /// Returns whether this value is tainted by `attr()`.
958    pub fn is_attr_tainted(&self) -> bool {
959        self.references.flags.intersects(ReferenceFlags::ATTR)
960    }
961
962    /// Create VariableValue from an int.
963    fn integer(number: i32, url_data: &UrlExtraData) -> Self {
964        Self::from_token(
965            Token::Number {
966                has_sign: false,
967                value: number as f32,
968                int_value: Some(number),
969            },
970            url_data,
971        )
972    }
973
974    /// Create VariableValue from an int.
975    fn ident(ident: &'static str, url_data: &UrlExtraData) -> Self {
976        Self::from_token(Token::Ident(ident.into()), url_data)
977    }
978
979    /// Create VariableValue from a float amount of CSS pixels.
980    fn pixels(number: f32, url_data: &UrlExtraData) -> Self {
981        // FIXME (https://github.com/servo/rust-cssparser/issues/266):
982        // No way to get TokenSerializationType::Dimension without creating
983        // Token object.
984        Self::from_token(
985            Token::Dimension {
986                has_sign: false,
987                value: number,
988                int_value: None,
989                unit: CowRcStr::from("px"),
990            },
991            url_data,
992        )
993    }
994
995    /// Create VariableValue from an integer amount of milliseconds.
996    fn int_ms(number: i32, url_data: &UrlExtraData) -> Self {
997        Self::from_token(
998            Token::Dimension {
999                has_sign: false,
1000                value: number as f32,
1001                int_value: Some(number),
1002                unit: CowRcStr::from("ms"),
1003            },
1004            url_data,
1005        )
1006    }
1007
1008    /// Create VariableValue from an integer amount of CSS pixels.
1009    fn int_pixels(number: i32, url_data: &UrlExtraData) -> Self {
1010        Self::from_token(
1011            Token::Dimension {
1012                has_sign: false,
1013                value: number as f32,
1014                int_value: Some(number),
1015                unit: CowRcStr::from("px"),
1016            },
1017            url_data,
1018        )
1019    }
1020
1021    fn from_token(token: Token, url_data: &UrlExtraData) -> Self {
1022        let token_type = token.serialization_type();
1023        let mut css = token.to_css_string();
1024        css.shrink_to_fit();
1025
1026        VariableValue {
1027            css,
1028            url_data: url_data.clone(),
1029            first_token_type: token_type,
1030            last_token_type: token_type,
1031            references: Default::default(),
1032        }
1033    }
1034
1035    /// Returns the raw CSS text from this VariableValue
1036    pub fn css_text(&self) -> &str {
1037        &self.css
1038    }
1039
1040    /// Returns whether this variable value has any reference to the environment or other
1041    /// variables.
1042    pub fn has_references(&self) -> bool {
1043        self.references.has_references()
1044    }
1045}
1046
1047/// <https://drafts.csswg.org/css-syntax-3/#typedef-declaration-value>
1048fn parse_declaration_value<'i, 't>(
1049    input: &mut Parser<'i, 't>,
1050    input_start: SourcePosition,
1051    namespaces: Option<&FxHashMap<Prefix, Namespace>>,
1052    references: &mut References,
1053    missing_closing_characters: &mut String,
1054) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
1055    input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
1056        parse_declaration_value_block(
1057            input,
1058            input_start,
1059            namespaces,
1060            references,
1061            missing_closing_characters,
1062        )
1063    })
1064}
1065
1066/// Like parse_declaration_value, but accept `!` and `;` since they are only invalid at the top level.
1067fn parse_declaration_value_block<'i, 't>(
1068    input: &mut Parser<'i, 't>,
1069    input_start: SourcePosition,
1070    namespaces: Option<&FxHashMap<Prefix, Namespace>>,
1071    references: &mut References,
1072    missing_closing_characters: &mut String,
1073) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
1074    let mut is_first = true;
1075    let mut first_token_type = TokenSerializationType::Nothing;
1076    let mut last_token_type = TokenSerializationType::Nothing;
1077    let mut prev_reference_index: Option<usize> = None;
1078    loop {
1079        let token_start = input.position();
1080        let Ok(token) = input.next_including_whitespace_and_comments() else {
1081            break;
1082        };
1083
1084        let prev_token_type = last_token_type;
1085        let serialization_type = token.serialization_type();
1086        last_token_type = serialization_type;
1087        if is_first {
1088            first_token_type = last_token_type;
1089            is_first = false;
1090        }
1091
1092        macro_rules! nested {
1093            ($closing:expr) => {{
1094                let mut inner_end_position = None;
1095                let result = input.parse_nested_block(|input| {
1096                    let result = parse_declaration_value_block(
1097                        input,
1098                        input_start,
1099                        namespaces,
1100                        references,
1101                        missing_closing_characters,
1102                    )?;
1103                    inner_end_position = Some(input.position());
1104                    Ok(result)
1105                })?;
1106                if inner_end_position.unwrap() == input.position() {
1107                    missing_closing_characters.push_str($closing);
1108                }
1109                result
1110            }};
1111        }
1112        if let Some(index) = prev_reference_index.take() {
1113            references.refs[index].next_token_type = serialization_type;
1114        }
1115        match *token {
1116            Token::Comment(_) => {
1117                let token_slice = input.slice_from(token_start);
1118                if !token_slice.ends_with("*/") {
1119                    missing_closing_characters.push_str(if token_slice.ends_with('*') {
1120                        "/"
1121                    } else {
1122                        "*/"
1123                    })
1124                }
1125            },
1126            Token::BadUrl(ref u) => {
1127                let e = StyleParseErrorKind::BadUrlInDeclarationValueBlock(u.clone());
1128                return Err(input.new_custom_error(e));
1129            },
1130            Token::BadString(ref s) => {
1131                let e = StyleParseErrorKind::BadStringInDeclarationValueBlock(s.clone());
1132                return Err(input.new_custom_error(e));
1133            },
1134            Token::CloseParenthesis => {
1135                let e = StyleParseErrorKind::UnbalancedCloseParenthesisInDeclarationValueBlock;
1136                return Err(input.new_custom_error(e));
1137            },
1138            Token::CloseSquareBracket => {
1139                let e = StyleParseErrorKind::UnbalancedCloseSquareBracketInDeclarationValueBlock;
1140                return Err(input.new_custom_error(e));
1141            },
1142            Token::CloseCurlyBracket => {
1143                let e = StyleParseErrorKind::UnbalancedCloseCurlyBracketInDeclarationValueBlock;
1144                return Err(input.new_custom_error(e));
1145            },
1146            Token::Function(ref name) => {
1147                let substitution_kind = match SubstitutionFunctionKind::from_ident(name).ok() {
1148                    Some(SubstitutionFunctionKind::Attr) => {
1149                        if static_prefs::pref!("layout.css.attr.enabled") {
1150                            Some(SubstitutionFunctionKind::Attr)
1151                        } else {
1152                            None
1153                        }
1154                    },
1155                    kind => kind,
1156                };
1157                if let Some(substitution_kind) = substitution_kind {
1158                    let our_ref_index = references.refs.len();
1159                    let mut input_end_position = None;
1160                    let fallback = input.parse_nested_block(|input| {
1161                        let mut namespace = ParsedNamespace::Known(Namespace::default());
1162                        if substitution_kind == SubstitutionFunctionKind::Attr {
1163                            if let Some(namespaces) = namespaces {
1164                                if let Ok(ns) = input
1165                                    .try_parse(|input| ParsedNamespace::parse(namespaces, input))
1166                                {
1167                                    namespace = ns;
1168                                    let prev = input.state();
1169                                    let next = match *input.next_including_whitespace()? {
1170                                        Token::Ident(_) => Ok(()),
1171                                        ref t => Err(prev
1172                                            .source_location()
1173                                            .new_unexpected_token_error(t.clone())),
1174                                    };
1175                                    input.reset(&prev);
1176                                    next?;
1177                                }
1178                            }
1179                        }
1180                        // TODO(emilio): For env() this should be <custom-ident> per spec, but no other browser does
1181                        // that, see https://github.com/w3c/csswg-drafts/issues/3262.
1182                        let name = input.expect_ident()?;
1183                        let name =
1184                            Atom::from(if substitution_kind == SubstitutionFunctionKind::Var {
1185                                match parse_name(name.as_ref()) {
1186                                    Ok(name) => name,
1187                                    Err(()) => {
1188                                        let name = name.clone();
1189                                        return Err(input.new_custom_error(
1190                                            SelectorParseErrorKind::UnexpectedIdent(name),
1191                                        ));
1192                                    },
1193                                }
1194                            } else {
1195                                name.as_ref()
1196                            });
1197
1198                        let attribute_kind = if substitution_kind == SubstitutionFunctionKind::Attr
1199                        {
1200                            parse_attr_type(input)
1201                        } else {
1202                            AttributeType::None
1203                        };
1204
1205                        // We want the order of the references to match source order. So we need to reserve our slot
1206                        // now, _before_ parsing our fallback. Note that we don't care if parsing fails after all, since
1207                        // if this fails we discard the whole result anyways.
1208                        let start = token_start.byte_index() - input_start.byte_index();
1209                        references.refs.push(SubstitutionFunctionReference {
1210                            name,
1211                            start,
1212                            // To be fixed up after parsing fallback and auto-closing via our_ref_index.
1213                            end: start,
1214                            prev_token_type,
1215                            // To be fixed up (if needed) on the next loop iteration via prev_reference_index.
1216                            next_token_type: TokenSerializationType::Nothing,
1217                            // To be fixed up after parsing fallback.
1218                            fallback: None,
1219                            attribute_data: AttributeData {
1220                                kind: attribute_kind,
1221                                namespace,
1222                            },
1223                            substitution_kind: substitution_kind.clone(),
1224                        });
1225
1226                        let mut fallback = None;
1227                        if input.try_parse(|input| input.expect_comma()).is_ok() {
1228                            input.skip_whitespace();
1229                            let fallback_start = num::NonZeroUsize::new(
1230                                input.position().byte_index() - input_start.byte_index(),
1231                            )
1232                            .unwrap();
1233                            let mut references = References::default();
1234                            // NOTE(emilio): Intentionally using parse_declaration_value rather than
1235                            // parse_declaration_value_block, since that's what parse_fallback used to do.
1236                            let (first, last) = parse_declaration_value(
1237                                input,
1238                                input_start,
1239                                namespaces,
1240                                &mut references,
1241                                missing_closing_characters,
1242                            )?;
1243                            fallback = Some(VariableFallback {
1244                                start: fallback_start,
1245                                first_token_type: first,
1246                                last_token_type: last,
1247                                references,
1248                            });
1249                            input_end_position = Some(input.position());
1250                        } else {
1251                            let state = input.state();
1252                            // We still need to consume the rest of the potentially-unclosed
1253                            // tokens, but make sure to not consume tokens that would otherwise be
1254                            // invalid, by calling reset().
1255                            parse_declaration_value_block(
1256                                input,
1257                                input_start,
1258                                namespaces,
1259                                references,
1260                                missing_closing_characters,
1261                            )?;
1262                            input_end_position = Some(input.position());
1263                            input.reset(&state);
1264                        }
1265                        Ok(fallback)
1266                    })?;
1267                    if input_end_position.unwrap() == input.position() {
1268                        missing_closing_characters.push_str(")");
1269                    }
1270                    prev_reference_index = Some(our_ref_index);
1271                    let reference = &mut references.refs[our_ref_index];
1272                    reference.end = input.position().byte_index() - input_start.byte_index()
1273                        + missing_closing_characters.len();
1274                    reference.fallback = fallback;
1275                    references.flags |= match substitution_kind {
1276                        SubstitutionFunctionKind::Var => ReferenceFlags::VAR,
1277                        SubstitutionFunctionKind::Env => ReferenceFlags::ENV,
1278                        SubstitutionFunctionKind::Attr => ReferenceFlags::ATTR,
1279                    };
1280                    // Bubble up flags from our fallback, so we know what we might reference from the outer scope.
1281                    if let Some(ref fb) = reference.fallback {
1282                        references.flags |= fb.references.flags;
1283                    }
1284                } else {
1285                    nested!(")");
1286                }
1287            },
1288            Token::ParenthesisBlock => {
1289                nested!(")");
1290            },
1291            Token::CurlyBracketBlock => {
1292                nested!("}");
1293            },
1294            Token::SquareBracketBlock => {
1295                nested!("]");
1296            },
1297            Token::QuotedString(_) => {
1298                let token_slice = input.slice_from(token_start);
1299                let quote = &token_slice[..1];
1300                debug_assert!(matches!(quote, "\"" | "'"));
1301                if !(token_slice.ends_with(quote) && token_slice.len() > 1) {
1302                    missing_closing_characters.push_str(quote)
1303                }
1304            },
1305            Token::Ident(ref value)
1306            | Token::AtKeyword(ref value)
1307            | Token::Hash(ref value)
1308            | Token::IDHash(ref value)
1309            | Token::UnquotedUrl(ref value)
1310            | Token::Dimension {
1311                unit: ref value, ..
1312            } => {
1313                references.flags.insert(ReferenceFlags::from_unit(value));
1314                let is_unquoted_url = matches!(token, Token::UnquotedUrl(_));
1315                if value.ends_with("�") && input.slice_from(token_start).ends_with("\\") {
1316                    // Unescaped backslash at EOF in these contexts is interpreted as U+FFFD
1317                    // Check the value in case the final backslash was itself escaped.
1318                    // Serialize as escaped U+FFFD, which is also interpreted as U+FFFD.
1319                    // (Unescaped U+FFFD would also work, but removing the backslash is annoying.)
1320                    missing_closing_characters.push_str("�")
1321                }
1322                if is_unquoted_url && !input.slice_from(token_start).ends_with(")") {
1323                    missing_closing_characters.push_str(")");
1324                }
1325            },
1326            _ => {},
1327        };
1328    }
1329    Ok((first_token_type, last_token_type))
1330}
1331
1332/// Parse <attr-type> = type( <syntax> ) | raw-string | number | <attr-unit>.
1333/// https://drafts.csswg.org/css-values-5/#attr-notation
1334fn parse_attr_type<'i, 't>(input: &mut Parser<'i, 't>) -> AttributeType {
1335    input
1336        .try_parse(|input| {
1337            Ok(match input.next()? {
1338                Token::Function(ref name) if name.eq_ignore_ascii_case("type") => {
1339                    AttributeType::Type(
1340                        input.parse_nested_block(SyntaxDescriptor::from_css_parser)?,
1341                    )
1342                },
1343                Token::Ident(ref ident) => {
1344                    if ident.eq_ignore_ascii_case("raw-string") {
1345                        AttributeType::RawString
1346                    } else if let Ok(unit) = AttrUnit::from_ident(ident) {
1347                        AttributeType::Unit(unit)
1348                    } else {
1349                        AttributeType::Invalid
1350                    }
1351                },
1352                Token::Delim('%') => AttributeType::Unit(AttrUnit::Percentage),
1353                _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
1354            })
1355        })
1356        .unwrap_or(AttributeType::None)
1357}
1358
1359/// Attribute values may reference other substitution functions we may need to process.
1360/// See step 6: https://drafts.csswg.org/css-values-5/#attr-substitution
1361pub fn get_attr_value_for_cycle_resolution(
1362    name: &Atom,
1363    attribute_data: &AttributeData,
1364    url_data: &UrlExtraData,
1365    attribute_tracker: &mut AttributeTracker,
1366) -> Result<ComputedRegisteredValue, ()> {
1367    #[cfg(feature = "gecko")]
1368    let local_name = LocalName::cast(name);
1369    #[cfg(feature = "servo")]
1370    let local_name = &LocalName::from(name.as_ref());
1371    let namespace = match attribute_data.namespace {
1372        ParsedNamespace::Known(ref ns) => ns,
1373        ParsedNamespace::Unknown => return Err(()),
1374    };
1375    let attr = attribute_tracker.query(local_name, namespace).ok_or(())?;
1376    let mut input = ParserInput::new(&attr);
1377    let mut parser = Parser::new(&mut input);
1378    // TODO(Bug 2021110): Support namespaced attributes in chained references.
1379    let value = VariableValue::parse(&mut parser, None, &url_data).map_err(|_| ())?;
1380    Ok(ComputedRegisteredValue::universal(Arc::new(value)))
1381}
1382
1383/// See https://drafts.csswg.org/css-variables-2/#invalid-at-computed-value-time
1384pub fn handle_invalid_at_computed_value_time(
1385    name: &Name,
1386    registration: &PropertyDescriptors,
1387    context: &mut computed::Context,
1388) {
1389    if !registration.is_universal() {
1390        // For the root element, inherited maps are empty. We should just
1391        // use the initial value if any, rather than removing the name.
1392        if registration.inherits() && !context.builder.is_root_element {
1393            let inherited = context.builder.inherited_style.custom_properties();
1394            if let Some(value) = inherited.get(registration, name) {
1395                context.builder.substitution_functions.insert_var(
1396                    registration,
1397                    name,
1398                    value.clone(),
1399                );
1400                return;
1401            }
1402        } else if let Some(ref initial_value) = registration.initial_value {
1403            if let Ok(initial_value) = compute_value(
1404                &initial_value.css,
1405                &initial_value.url_data,
1406                registration,
1407                context,
1408                AttrTaint::default(),
1409            ) {
1410                context.builder.substitution_functions.insert_var(
1411                    registration,
1412                    name,
1413                    initial_value,
1414                );
1415                return;
1416            }
1417        }
1418    }
1419    context
1420        .builder
1421        .substitution_functions
1422        .remove_var(registration, name);
1423}
1424
1425/// Replace `var()`, `env()`, and `attr()` functions in a pre-existing variable value.
1426pub fn substitute_references_if_needed_and_apply(
1427    name: &Name,
1428    kind: SubstitutionFunctionKind,
1429    value: &Arc<VariableValue>,
1430    stylist: &Stylist,
1431    context: &mut computed::Context,
1432    attribute_tracker: &mut AttributeTracker,
1433) {
1434    debug_assert_ne!(kind, SubstitutionFunctionKind::Env);
1435    let is_var = matches!(kind, SubstitutionFunctionKind::Var);
1436    let registration = stylist.get_custom_property_registration(&name);
1437    if is_var && !value.has_references() && registration.is_universal() {
1438        // Trivial path: no references and no need to compute the value, just apply it directly.
1439        let computed_value = ComputedRegisteredValue::universal(Arc::clone(value));
1440        context
1441            .builder
1442            .substitution_functions
1443            .insert_var(registration, name, computed_value);
1444        return;
1445    }
1446
1447    let url_data = &value.url_data;
1448    let substitution = substitute_internal(
1449        value,
1450        &context.builder.substitution_functions,
1451        stylist,
1452        context,
1453        attribute_tracker,
1454        &mut SmallVec::new(),
1455        None,
1456    );
1457
1458    let Ok(substitution) = substitution else {
1459        if is_var {
1460            handle_invalid_at_computed_value_time(name, registration, context);
1461        } else {
1462            context.builder.substitution_functions.remove_attr(name);
1463        }
1464        return;
1465    };
1466
1467    // If variable fallback results in a wide keyword, deal with it now.
1468    let inherited = context.builder.inherited_style.custom_properties();
1469    if is_var {
1470        let css = &substitution.css;
1471        let css_wide_kw = {
1472            let mut input = ParserInput::new(&css);
1473            let mut input = Parser::new(&mut input);
1474            input.try_parse(CSSWideKeyword::parse)
1475        };
1476
1477        if let Ok(kw) = css_wide_kw {
1478            // TODO: It's unclear what this should do for revert / revert-layer, see
1479            // https://github.com/w3c/csswg-drafts/issues/9131. For now treating as unset
1480            // seems fine?
1481            match (kw, registration.inherits(), context.is_root_element()) {
1482                (CSSWideKeyword::Initial, _, _)
1483                | (CSSWideKeyword::Revert, false, _)
1484                | (CSSWideKeyword::RevertLayer, false, _)
1485                | (CSSWideKeyword::RevertRule, false, _)
1486                | (CSSWideKeyword::Unset, false, _)
1487                | (CSSWideKeyword::Revert, true, true)
1488                | (CSSWideKeyword::RevertLayer, true, true)
1489                | (CSSWideKeyword::RevertRule, true, true)
1490                | (CSSWideKeyword::Unset, true, true)
1491                | (CSSWideKeyword::Inherit, _, true) => {
1492                    remove_and_insert_initial_value(
1493                        name,
1494                        registration,
1495                        &mut context.builder.substitution_functions,
1496                    );
1497                },
1498                (CSSWideKeyword::Revert, true, false)
1499                | (CSSWideKeyword::RevertLayer, true, false)
1500                | (CSSWideKeyword::RevertRule, true, false)
1501                | (CSSWideKeyword::Inherit, _, false)
1502                | (CSSWideKeyword::Unset, true, false) => {
1503                    match inherited.get(registration, name) {
1504                        Some(value) => {
1505                            context.builder.substitution_functions.insert_var(
1506                                registration,
1507                                name,
1508                                value.clone(),
1509                            );
1510                        },
1511                        None => {
1512                            context
1513                                .builder
1514                                .substitution_functions
1515                                .remove_var(registration, name);
1516                        },
1517                    };
1518                },
1519            }
1520            return;
1521        }
1522    }
1523
1524    match kind {
1525        SubstitutionFunctionKind::Var => {
1526            let value = match substitution.into_value(url_data, registration, context) {
1527                Ok(v) => v,
1528                Err(()) => {
1529                    handle_invalid_at_computed_value_time(name, registration, context);
1530                    return;
1531                },
1532            };
1533            context
1534                .builder
1535                .substitution_functions
1536                .insert_var(registration, name, value);
1537        },
1538        SubstitutionFunctionKind::Attr => {
1539            let mut value = ComputedRegisteredValue::universal(Arc::new(VariableValue::new(
1540                substitution.css.into_owned(),
1541                url_data,
1542                substitution.first_token_type,
1543                substitution.last_token_type,
1544            )));
1545            value.attr_tainted |= substitution.attr_tainted;
1546            context
1547                .builder
1548                .substitution_functions
1549                .insert_attr(name, value);
1550        },
1551        SubstitutionFunctionKind::Env => unreachable!("Kind cannot be env."),
1552    }
1553}
1554
1555#[derive(Default, Debug)]
1556struct Substitution<'a> {
1557    css: Cow<'a, str>,
1558    first_token_type: TokenSerializationType,
1559    last_token_type: TokenSerializationType,
1560    attr_tainted: bool,
1561}
1562
1563impl<'a> Substitution<'a> {
1564    fn from_value(v: VariableValue, attr_tainted: bool) -> Self {
1565        Substitution {
1566            css: v.css.into(),
1567            first_token_type: v.first_token_type,
1568            last_token_type: v.last_token_type,
1569            attr_tainted,
1570        }
1571    }
1572
1573    fn into_value(
1574        self,
1575        url_data: &UrlExtraData,
1576        registration: &PropertyDescriptors,
1577        computed_context: &computed::Context,
1578    ) -> Result<ComputedRegisteredValue, ()> {
1579        if registration.is_universal() {
1580            let mut value = ComputedRegisteredValue::universal(Arc::new(VariableValue::new(
1581                self.css.into_owned(),
1582                url_data,
1583                self.first_token_type,
1584                self.last_token_type,
1585            )));
1586            value.attr_tainted |= self.attr_tainted;
1587            return Ok(value);
1588        }
1589        let taint = if self.attr_tainted {
1590            // Per spec: substitution value of an arbitrary substitution function is
1591            // attr()-tainted as a whole if any attr()-tainted values were involved
1592            // in creating that substitution value.
1593            // https://drafts.csswg.org/css-values-5/#attr-security
1594            AttrTaint::new_fully_tainted(self.css.len())
1595        } else {
1596            AttrTaint::default()
1597        };
1598        let mut v = compute_value(&self.css, url_data, registration, computed_context, taint)?;
1599        v.attr_tainted |= self.attr_tainted;
1600        Ok(v)
1601    }
1602
1603    fn new(
1604        css: Cow<'a, str>,
1605        first_token_type: TokenSerializationType,
1606        last_token_type: TokenSerializationType,
1607        attr_tainted: bool,
1608    ) -> Self {
1609        Self {
1610            css,
1611            first_token_type,
1612            last_token_type,
1613            attr_tainted,
1614        }
1615    }
1616}
1617
1618/// Result of var(), env(), and attr() substitution.
1619#[derive(Debug)]
1620pub struct SubstitutionResult<'a> {
1621    /// The resolved CSS string after substitution.
1622    pub css: Cow<'a, str>,
1623    /// Regions in the `css` string that are attr()-tainted, if any.
1624    pub attr_taint: AttrTaint,
1625}
1626
1627fn compute_value(
1628    css: &str,
1629    url_data: &UrlExtraData,
1630    registration: &PropertyDescriptors,
1631    computed_context: &computed::Context,
1632    attr_taint: AttrTaint,
1633) -> Result<ComputedRegisteredValue, ()> {
1634    debug_assert!(!registration.is_universal());
1635
1636    let mut input = ParserInput::new(&css);
1637    let mut input = Parser::new(&mut input);
1638
1639    SpecifiedRegisteredValue::compute(
1640        &mut input,
1641        registration,
1642        None,
1643        url_data,
1644        computed_context,
1645        AllowComputationallyDependent::Yes,
1646        attr_taint,
1647    )
1648}
1649
1650/// Removes the named registered custom property and inserts its uncomputed initial value.
1651pub(crate) fn remove_and_insert_initial_value(
1652    name: &Name,
1653    registration: &PropertyDescriptors,
1654    substitution_functions: &mut ComputedSubstitutionFunctions,
1655) {
1656    substitution_functions.remove_var(registration, name);
1657    if let Some(ref initial_value) = registration.initial_value {
1658        let value = ComputedRegisteredValue::universal(Arc::clone(initial_value));
1659        substitution_functions.insert_var(registration, name, value);
1660    }
1661}
1662
1663fn do_substitute_chunk<'a>(
1664    css: &'a str,
1665    start: usize,
1666    end: usize,
1667    first_token_type: TokenSerializationType,
1668    last_token_type: TokenSerializationType,
1669    url_data: &UrlExtraData,
1670    substitution_functions: &'a ComputedSubstitutionFunctions,
1671    stylist: &Stylist,
1672    computed_context: &computed::Context,
1673    references: &'a [SubstitutionFunctionReference],
1674    attribute_tracker: &mut AttributeTracker,
1675    seen: &mut SmallVec<[&'a Name; 8]>,
1676    mut attr_taint: Option<&mut AttrTaint>,
1677) -> Result<Substitution<'a>, ()> {
1678    if start == end {
1679        // Empty string. Easy.
1680        return Ok(Substitution::default());
1681    }
1682    // Easy case: no references involved.
1683    if references.is_empty() {
1684        let result = &css[start..end];
1685        return Ok(Substitution::new(
1686            Cow::Borrowed(result),
1687            first_token_type,
1688            last_token_type,
1689            Default::default(),
1690        ));
1691    }
1692
1693    let mut substituted = ComputedValue::empty(url_data);
1694    let mut next_token_type = first_token_type;
1695    let mut cur_pos = start;
1696    let mut attr_tainted = false;
1697    let mut references = references.iter();
1698    while let Some(reference) = references.next() {
1699        if reference.start != cur_pos {
1700            substituted.push(
1701                &css[cur_pos..reference.start],
1702                next_token_type,
1703                reference.prev_token_type,
1704                /* attr_taint */ None,
1705            )?;
1706        }
1707
1708        let substitution = substitute_one_reference(
1709            css,
1710            url_data,
1711            substitution_functions,
1712            reference,
1713            stylist,
1714            computed_context,
1715            attribute_tracker,
1716            seen,
1717        )?;
1718
1719        // Optimize the property: var(--...) case to avoid allocating at all.
1720        if reference.start == start && reference.end == end {
1721            if let Some(taint) = attr_taint.filter(|_| substitution.attr_tainted) {
1722                taint.push(start, end);
1723            }
1724            return Ok(substitution);
1725        }
1726
1727        substituted.push(
1728            &substitution.css,
1729            substitution.first_token_type,
1730            substitution.last_token_type,
1731            attr_taint
1732                .as_deref_mut()
1733                .filter(|_| substitution.attr_tainted),
1734        )?;
1735        attr_tainted |= substitution.attr_tainted;
1736        next_token_type = reference.next_token_type;
1737        cur_pos = reference.end;
1738    }
1739    // Push the rest of the value if needed.
1740    if cur_pos != end {
1741        substituted.push(
1742            &css[cur_pos..end],
1743            next_token_type,
1744            last_token_type,
1745            /* attr_taint */ None,
1746        )?;
1747    }
1748    Ok(Substitution::from_value(substituted, attr_tainted))
1749}
1750
1751fn quoted_css_string(src: &str) -> String {
1752    let mut dest = String::with_capacity(src.len() + 2);
1753    cssparser::serialize_string(src, &mut dest).unwrap();
1754    dest
1755}
1756
1757fn substitute_one_reference<'a>(
1758    css: &'a str,
1759    url_data: &UrlExtraData,
1760    substitution_functions: &'a ComputedSubstitutionFunctions,
1761    reference: &'a SubstitutionFunctionReference,
1762    stylist: &Stylist,
1763    computed_context: &computed::Context,
1764    attribute_tracker: &mut AttributeTracker,
1765    seen: &mut SmallVec<[&'a Name; 8]>,
1766) -> Result<Substitution<'a>, ()> {
1767    let simple_attr_subst = |s: &str| {
1768        Some(Substitution::new(
1769            Cow::Owned(quoted_css_string(s)),
1770            TokenSerializationType::Nothing,
1771            TokenSerializationType::Nothing,
1772            /* attr_tainted */ true,
1773        ))
1774    };
1775    let substitution: Option<_> = match reference.substitution_kind {
1776        SubstitutionFunctionKind::Var => {
1777            let registration = stylist.get_custom_property_registration(&reference.name);
1778            match substitution_functions.get_var(registration, &reference.name) {
1779                None => None,
1780                // If the referenced value is itself still unresolved (i.e. it has references), we
1781                // are substituting against a partially-resolved map -- this happens while applying
1782                // a prioritary property in the middle of custom-property resolution. Resolve it
1783                // recursively, guarding against cycles (which the resolver will also remove, but it
1784                // may not have gotten to this variable yet).
1785                Some(v) => match v.as_universal() {
1786                    Some(u) if u.has_references() => {
1787                        if seen.contains(&&reference.name) {
1788                            // Cycle: the primary is guaranteed-invalid, so fall through to the
1789                            // fallback (if any), like any other invalid primary.
1790                            None
1791                        } else {
1792                            seen.push(&reference.name);
1793                            let result = substitute_internal(
1794                                u,
1795                                substitution_functions,
1796                                stylist,
1797                                computed_context,
1798                                attribute_tracker,
1799                                seen,
1800                                /* attr_taint */ None,
1801                            );
1802                            seen.pop();
1803                            match result {
1804                                Ok(mut substitution) => {
1805                                    substitution.attr_tainted |= v.attr_tainted;
1806                                    Some(substitution)
1807                                },
1808                                // The primary couldn't be resolved (invalid); use the fallback.
1809                                Err(()) => None,
1810                            }
1811                        }
1812                    },
1813                    _ => Some(Substitution::from_value(
1814                        v.to_variable_value(),
1815                        v.attr_tainted,
1816                    )),
1817                },
1818            }
1819        },
1820        SubstitutionFunctionKind::Env => {
1821            let device = stylist.device();
1822            device
1823                .environment()
1824                .get(&reference.name, device, url_data)
1825                .map(|v| Substitution::from_value(v, /* attr_tainted */ false))
1826        },
1827        // https://drafts.csswg.org/css-values-5/#attr-substitution
1828        SubstitutionFunctionKind::Attr => {
1829            #[cfg(feature = "gecko")]
1830            let local_name = LocalName::cast(&reference.name);
1831            #[cfg(feature = "servo")]
1832            let local_name = LocalName::from(reference.name.as_ref());
1833            let namespace = match reference.attribute_data.namespace {
1834                ParsedNamespace::Known(ref ns) => Some(ns),
1835                ParsedNamespace::Unknown => None,
1836            };
1837            namespace
1838                .and_then(|namespace| attribute_tracker.query(&local_name, namespace))
1839                .map_or_else(
1840                    || {
1841                        // Special case when fallback and <attr-type> are omitted.
1842                        // See FAILURE: https://drafts.csswg.org/css-values-5/#attr-substitution
1843                        if reference.fallback.is_none()
1844                            && reference.attribute_data.kind == AttributeType::None
1845                        {
1846                            simple_attr_subst("")
1847                        } else {
1848                            None
1849                        }
1850                    },
1851                    |attr| {
1852                        let attr = if let AttributeType::Type(_) = &reference.attribute_data.kind {
1853                            // If we're evaluating a container query, we haven't run the cascade
1854                            // and populated substitution_functions.attributes, so we can't do the
1855                            // get_attr() lookup here.
1856                            // TODO: This means chained attr() references will not work reliably in
1857                            // container style queries:
1858                            // https://bugzilla.mozilla.org/show_bug.cgi?id=2028861
1859                            if computed_context.in_container_query {
1860                                attr
1861                            } else {
1862                                substitution_functions
1863                                    .get_attr(&reference.name)
1864                                    .map(|v| v.to_variable_value())?
1865                                    .css
1866                            }
1867                        } else {
1868                            attr
1869                        };
1870                        let mut input = ParserInput::new(&attr);
1871                        let mut parser = Parser::new(&mut input);
1872                        match &reference.attribute_data.kind {
1873                            AttributeType::Unit(unit) => {
1874                                let css = {
1875                                    // Verify that attribute data is a <number-token>.
1876                                    parser.expect_number().ok()?;
1877                                    let mut s = attr.clone();
1878                                    s.push_str(unit.as_ref());
1879                                    s
1880                                };
1881                                let serialization = match unit {
1882                                    AttrUnit::Number => TokenSerializationType::Number,
1883                                    AttrUnit::Percentage => TokenSerializationType::Percentage,
1884                                    _ => TokenSerializationType::Dimension,
1885                                };
1886                                let value =
1887                                    ComputedValue::new(css, url_data, serialization, serialization);
1888                                Some(Substitution::from_value(
1889                                    value, /* attr_tainted */ true,
1890                                ))
1891                            },
1892                            AttributeType::Type(syntax) => {
1893                                let value = SpecifiedRegisteredValue::parse(
1894                                    &mut parser,
1895                                    &syntax,
1896                                    url_data,
1897                                    None,
1898                                    AllowComputationallyDependent::Yes,
1899                                    AttrTaint::default(),
1900                                )
1901                                .ok()?;
1902                                let value = value.to_variable_value();
1903                                Some(Substitution::from_value(
1904                                    value, /* attr_tainted */ true,
1905                                ))
1906                            },
1907                            AttributeType::RawString | AttributeType::None => {
1908                                simple_attr_subst(&attr)
1909                            },
1910                            AttributeType::Invalid => None,
1911                        }
1912                    },
1913                )
1914        },
1915    };
1916
1917    if let Some(s) = substitution {
1918        return Ok(s);
1919    }
1920
1921    let Some(ref fallback) = reference.fallback else {
1922        return Err(());
1923    };
1924
1925    do_substitute_chunk(
1926        css,
1927        fallback.start.get(),
1928        reference.end - 1, // Skip the closing parenthesis of the reference value.
1929        fallback.first_token_type,
1930        fallback.last_token_type,
1931        url_data,
1932        substitution_functions,
1933        stylist,
1934        computed_context,
1935        &fallback.references.refs,
1936        attribute_tracker,
1937        seen,
1938        /* attr_taint */ None,
1939    )
1940}
1941
1942/// Replace `var()`, `env()`, and `attr()` functions. Return `Err(..)` for invalid at computed time.
1943fn substitute_internal<'a>(
1944    variable_value: &'a VariableValue,
1945    substitution_functions: &'a ComputedSubstitutionFunctions,
1946    stylist: &Stylist,
1947    computed_context: &computed::Context,
1948    attribute_tracker: &mut AttributeTracker,
1949    seen: &mut SmallVec<[&'a Name; 8]>,
1950    mut attr_taint: Option<&mut AttrTaint>,
1951) -> Result<Substitution<'a>, ()> {
1952    do_substitute_chunk(
1953        &variable_value.css,
1954        /* start = */ 0,
1955        /* end = */ variable_value.css.len(),
1956        variable_value.first_token_type,
1957        variable_value.last_token_type,
1958        &variable_value.url_data,
1959        substitution_functions,
1960        stylist,
1961        computed_context,
1962        &variable_value.references.refs,
1963        attribute_tracker,
1964        seen,
1965        attr_taint.as_deref_mut(),
1966    )
1967}
1968
1969/// Replace var(), env(), and attr() functions, returning the resulting CSS string.
1970pub fn substitute<'a>(
1971    variable_value: &'a VariableValue,
1972    substitution_functions: &'a ComputedSubstitutionFunctions,
1973    stylist: &Stylist,
1974    computed_context: &computed::Context,
1975    attribute_tracker: &mut AttributeTracker,
1976) -> Result<SubstitutionResult<'a>, ()> {
1977    debug_assert!(variable_value.has_references());
1978    let mut attr_taint = AttrTaint::default();
1979    let v = substitute_internal(
1980        variable_value,
1981        substitution_functions,
1982        stylist,
1983        computed_context,
1984        attribute_tracker,
1985        &mut SmallVec::new(),
1986        Some(&mut attr_taint),
1987    )?;
1988    Ok(SubstitutionResult {
1989        css: v.css,
1990        attr_taint,
1991    })
1992}