Skip to main content

style/substitution/
mod.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//! Arbitrary substitution functions.
6//! https://drafts.csswg.org/css-values-5/#arbitrary-substitution-function
7
8pub mod attr;
9pub mod env;
10pub mod var;
11
12use crate::custom_properties::{
13    ComputedSubstitutionFunctions, ComputedValue, Name, VariableValue, compute_value,
14};
15use crate::derives::*;
16use crate::dom::AttributeTracker;
17use crate::parser::{Parse, ParserContext};
18use crate::properties::PropertyIdRef;
19use crate::properties_and_values::{
20    rule::Descriptors as PropertyDescriptors, value::ComputedValue as ComputedRegisteredValue,
21};
22use crate::stylesheets::{Origin, UrlExtraData};
23use crate::stylist::Stylist;
24use crate::values::computed;
25use crate::values::specified::NoCalcLength;
26use cssparser::{
27    CowRcStr, Delimiter, Delimiters, Parser, SourcePosition, Token, TokenSerializationType,
28};
29use servo_arc::Arc;
30use smallvec::SmallVec;
31use std::borrow::Cow;
32use std::num;
33use strum_macros::VariantNames;
34use style_traits::{ParseError, ParsingMode, StyleParseErrorKind};
35
36pub use self::attr::AttrFunction;
37pub use self::env::EnvFunction;
38pub use self::var::VarFunction;
39
40/// Substitution function name.
41#[derive(Copy, Clone, Debug, MallocSizeOf, Hash, Eq, PartialEq, ToShmem, Parse, VariantNames)]
42#[strum(serialize_all = "kebab-case")]
43pub enum SubstitutionFunctionKind {
44    /// CSS variable / custom property
45    Var,
46    /// Environment variable
47    Env,
48    /// DOM attribute
49    Attr,
50}
51
52/// Context for parsing an arbitrary substitution function.
53pub struct SubstitutionParserContext<'a> {
54    /// Original parser context.
55    pub parser_context: &'a ParserContext<'a>,
56    /// The start of the value being parsed. Positions are recorded relative to this.
57    pub input_start: SourcePosition,
58    /// Closing characters implied by EOF, to be appended to the value's serialization.
59    pub missing_closing_characters: String,
60}
61
62impl<'a> SubstitutionParserContext<'a> {
63    /// Creates a parser context for a value starting at `input_start`.
64    pub fn new(parser_context: &'a ParserContext<'a>, input_start: SourcePosition) -> Self {
65        Self {
66            parser_context,
67            input_start,
68            missing_closing_characters: String::new(),
69        }
70    }
71}
72
73/// An arbitrary `<declaration-value>` nested inside of a substitution function.
74#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
75pub struct ArbitraryDeclarationValue {
76    /// The start of the value in the referencing value's CSS, counted in bytes.
77    pub start: num::NonZeroUsize,
78    /// The end of the value in the referencing value's CSS, counted in bytes.
79    pub end: num::NonZeroUsize,
80    first_token_type: TokenSerializationType,
81    last_token_type: TokenSerializationType,
82    /// References from this value.
83    pub references: References,
84}
85
86impl ArbitraryDeclarationValue {
87    fn parse_internal(
88        input: &mut Parser,
89        context: &mut SubstitutionParserContext,
90        additional_delimiters: Delimiters,
91    ) -> Result<Self, ParseError> {
92        let start = num::NonZeroUsize::new(
93            input.position().byte_index() - context.input_start.byte_index(),
94        )
95        .unwrap();
96        let mut references = References::default();
97        // NOTE(emilio): Intentionally using parse_declaration_value rather than
98        // parse_declaration_value_block, since that's what parse_fallback used to do.
99        let (first_token_type, last_token_type) =
100            parse_declaration_value(input, context, &mut references, additional_delimiters)?;
101        let end = num::NonZeroUsize::new(
102            input.position().byte_index() - context.input_start.byte_index()
103                + context.missing_closing_characters.len(),
104        )
105        .unwrap();
106        Ok(Self {
107            start,
108            end,
109            first_token_type,
110            last_token_type,
111            references,
112        })
113    }
114
115    /// Parses a free-form `<declaration-value>` production, which may be surrounded by curly
116    /// braces. Also returns the source text of the value, excluding any curly braces. If parsing
117    /// a legacy non-strict production, an unbraced value may also contain commas and {} blocks.
118    /// https://drafts.csswg.org/css-values-5/#component-function-commas
119    /// https://drafts.csswg.org/css-values-5/#non-strict-free-form-production
120    fn parse_free_form<'i>(
121        input: &mut Parser<'i>,
122        context: &mut SubstitutionParserContext,
123        additional_delimiters: Delimiters,
124        is_non_strict: bool,
125    ) -> Result<(Self, &'i str), ParseError> {
126        if input
127            .try_parse(|input| input.expect_curly_bracket_block())
128            .is_ok()
129        {
130            let mut inner_end_position = None;
131            let result = input.parse_nested_block(|input| {
132                input.skip_whitespace();
133                let start_position = input.position();
134                let value = Self::parse_internal(input, context, Delimiter::None)?;
135                let end_position = input.position();
136                inner_end_position = Some(end_position);
137                Ok((value, input.slice(start_position..end_position)))
138            })?;
139            if inner_end_position.unwrap() == input.position() {
140                context.missing_closing_characters.push('}');
141            }
142            return Ok(result);
143        }
144        input.skip_whitespace();
145        let start_position = input.position();
146        let delimiters = if is_non_strict {
147            additional_delimiters
148        } else {
149            additional_delimiters | Delimiter::Comma | Delimiter::CurlyBracketBlock
150        };
151        let value = Self::parse_internal(input, context, delimiters)?;
152        Ok((value, input.slice_from(start_position)))
153    }
154
155    fn is_empty(&self) -> bool {
156        self.first_token_type == TokenSerializationType::Nothing
157    }
158}
159
160fn parse_optional_fallback(
161    input: &mut Parser,
162    context: &mut SubstitutionParserContext,
163) -> Result<Option<ArbitraryDeclarationValue>, ParseError> {
164    if input.try_parse(|input| input.expect_comma()).is_ok() {
165        let (fallback, _) = ArbitraryDeclarationValue::parse_free_form(
166            input,
167            context,
168            Delimiter::None,
169            /* is_non_strict */ true,
170        )?;
171        Ok(Some(fallback))
172    } else {
173        let state = input.state();
174        // We still need to consume the rest of the potentially-unclosed
175        // tokens, but make sure to not consume tokens that would otherwise be
176        // invalid, by calling reset().
177        let mut ignored_references = References::default();
178        parse_declaration_value_block(input, context, &mut ignored_references)?;
179        input.reset(&state);
180        Ok(None)
181    }
182}
183
184/// <https://drafts.csswg.org/css-syntax-3/#typedef-declaration-value>
185pub fn parse_declaration_value(
186    input: &mut Parser,
187    context: &mut SubstitutionParserContext,
188    references: &mut References,
189    additional_delimiters: Delimiters,
190) -> Result<(TokenSerializationType, TokenSerializationType), ParseError> {
191    input.parse_until_before(
192        Delimiter::Bang | Delimiter::Semicolon | additional_delimiters,
193        |input| parse_declaration_value_block(input, context, references),
194    )
195}
196
197/// Like parse_declaration_value, but accept `!` and `;` since they are only invalid at the top level.
198fn parse_declaration_value_block(
199    input: &mut Parser,
200    context: &mut SubstitutionParserContext,
201    references: &mut References,
202) -> Result<(TokenSerializationType, TokenSerializationType), ParseError> {
203    let mut is_first = true;
204    let mut first_token_type = TokenSerializationType::Nothing;
205    let mut last_token_type = TokenSerializationType::Nothing;
206    let mut prev_reference_index: Option<usize> = None;
207    loop {
208        let token_start = input.position();
209        let Ok(token) = input.next_including_whitespace_and_comments() else {
210            break;
211        };
212
213        let prev_token_type = last_token_type;
214        let serialization_type = token.serialization_type();
215        last_token_type = serialization_type;
216        if is_first {
217            first_token_type = last_token_type;
218            is_first = false;
219        }
220
221        macro_rules! nested {
222            ($closing:expr) => {{
223                let mut inner_end_position = None;
224                let result = input.parse_nested_block(|input| {
225                    let result = parse_declaration_value_block(input, context, references)?;
226                    inner_end_position = Some(input.position());
227                    Ok(result)
228                })?;
229                if inner_end_position.unwrap() == input.position() {
230                    context.missing_closing_characters.push_str($closing);
231                }
232                result
233            }};
234        }
235        if let Some(index) = prev_reference_index.take() {
236            references.refs[index].next_token_type = serialization_type;
237        }
238        match *token {
239            Token::Comment(_) => {
240                let token_slice = input.slice_from(token_start);
241                if !token_slice.ends_with("*/") {
242                    context
243                        .missing_closing_characters
244                        .push_str(if token_slice.ends_with('*') {
245                            "/"
246                        } else {
247                            "*/"
248                        })
249                }
250            },
251            Token::BadUrl(..) => {
252                let e = StyleParseErrorKind::BadUrlInDeclarationValueBlock;
253                return Err(ParseError::custom(e));
254            },
255            Token::BadString(..) => {
256                let e = StyleParseErrorKind::BadStringInDeclarationValueBlock;
257                return Err(ParseError::custom(e));
258            },
259            Token::CloseParenthesis => {
260                let e = StyleParseErrorKind::UnbalancedCloseParenthesisInDeclarationValueBlock;
261                return Err(ParseError::custom(e));
262            },
263            Token::CloseSquareBracket => {
264                let e = StyleParseErrorKind::UnbalancedCloseSquareBracketInDeclarationValueBlock;
265                return Err(ParseError::custom(e));
266            },
267            Token::CloseCurlyBracket => {
268                let e = StyleParseErrorKind::UnbalancedCloseCurlyBracketInDeclarationValueBlock;
269                return Err(ParseError::custom(e));
270            },
271            Token::Function(ref name) => {
272                let substitution_kind = SubstitutionFunctionKind::from_ident(name).ok();
273                if let Some(substitution_kind) = substitution_kind {
274                    let start = token_start.byte_index() - context.input_start.byte_index();
275                    let mut input_end_position = None;
276                    let function = input.parse_nested_block(|input| {
277                        let function =
278                            SubstitutionFunction::parse(substitution_kind, input, context)?;
279                        input.expect_exhausted()?;
280                        input_end_position = Some(input.position());
281                        Ok(function)
282                    })?;
283                    if input_end_position.unwrap() == input.position() {
284                        context.missing_closing_characters.push(')');
285                    }
286                    let end = input.position().byte_index() - context.input_start.byte_index()
287                        + context.missing_closing_characters.len();
288                    references.flags |= function.reference_flags();
289                    // Bubble up flags from our nested values, so we know what we might reference
290                    // from the outer scope.
291                    function.for_each_unparsed_value(|value| {
292                        references.flags |= value.references.flags
293                    });
294                    prev_reference_index = Some(references.refs.len());
295                    references.refs.push(SubstitutionFunctionReference {
296                        start,
297                        end,
298                        prev_token_type,
299                        // To be fixed up (if needed) on the next loop iteration via prev_reference_index.
300                        next_token_type: TokenSerializationType::Nothing,
301                        function,
302                    });
303                } else {
304                    nested!(")");
305                }
306            },
307            Token::ParenthesisBlock => {
308                nested!(")");
309            },
310            Token::CurlyBracketBlock => {
311                nested!("}");
312            },
313            Token::SquareBracketBlock => {
314                nested!("]");
315            },
316            Token::QuotedString(_) => {
317                let token_slice = input.slice_from(token_start);
318                let quote = &token_slice[..1];
319                debug_assert!(matches!(quote, "\"" | "'"));
320                if !(token_slice.ends_with(quote) && token_slice.len() > 1) {
321                    context.missing_closing_characters.push_str(quote)
322                }
323            },
324            Token::Ident(ref value)
325            | Token::AtKeyword(ref value)
326            | Token::Hash(ref value)
327            | Token::IDHash(ref value)
328            | Token::UnquotedUrl(ref value)
329            | Token::Dimension {
330                unit: ref value, ..
331            } => {
332                references.flags.insert(ReferenceFlags::from_unit(value));
333                let is_unquoted_url = matches!(token, Token::UnquotedUrl(_));
334                if value.ends_with("�") && input.slice_from(token_start).ends_with("\\") {
335                    // Unescaped backslash at EOF in these contexts is interpreted as U+FFFD
336                    // Check the value in case the final backslash was itself escaped.
337                    // Serialize as escaped U+FFFD, which is also interpreted as U+FFFD.
338                    // (Unescaped U+FFFD would also work, but removing the backslash is annoying.)
339                    context.missing_closing_characters.push('�')
340                }
341                if is_unquoted_url && !input.slice_from(token_start).ends_with(")") {
342                    context.missing_closing_characters.push(')');
343                }
344            },
345            _ => {},
346        };
347    }
348    Ok((first_token_type, last_token_type))
349}
350
351/// An argument of a substitution function that is parsed as `T` at substitution time.
352#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
353pub enum SubstitutionArgument<T> {
354    /// The argument in its `<declaration-value>` form with nested substitution functions.
355    Unparsed(ArbitraryDeclarationValue),
356    /// The argument successfully parsed as `T` at parse time.
357    Parsed(T),
358    /// The argument is a valid `<declaration-value>` and contains no nested substitution
359    /// functions, but is not a valid `T`.
360    Invalid,
361}
362
363impl<T: Parse> SubstitutionArgument<T> {
364    /// Parses the argument as a `<declaration-value>`. If it has no nested substitution
365    /// functions, it is also parsed right away, giving `Parsed` or `Invalid`; otherwise
366    /// it is kept as `Unparsed`, to be parsed at substitution time.
367    pub fn try_parse(
368        input: &mut Parser,
369        context: &mut SubstitutionParserContext,
370        delimiters: Delimiters,
371    ) -> Result<Self, ParseError> {
372        let (value, css) = ArbitraryDeclarationValue::parse_free_form(
373            input, context, delimiters, /* is_non_strict */ false,
374        )?;
375        if value.is_empty() {
376            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
377        }
378        if !value.references.has_references() {
379            let parser_context = context.parser_context;
380            if let Ok(result) =
381                Parser::new(css).parse_entirely(|input| T::parse(parser_context, input))
382            {
383                return Ok(Self::Parsed(result));
384            }
385            return Ok(Self::Invalid);
386        }
387        Ok(Self::Unparsed(value))
388    }
389}
390
391impl<T: Clone + Parse> SubstitutionArgument<T> {
392    /// Returns the argument, first substituting its nested references and
393    /// parsing it into `T` via `parse` if it is `Unparsed`.
394    pub fn substitute_and_parse<'a>(
395        &'a self,
396        source: &'a VariableValue,
397        context: &mut SubstitutionReplacementContext<'a, '_, '_, '_>,
398    ) -> ParsedArgument<'a, T> {
399        let arg = match self {
400            Self::Unparsed(arg) => arg,
401            Self::Parsed(value) => {
402                return ParsedArgument {
403                    value: Some(Cow::Borrowed(value)),
404                    attr_tainted: false,
405                };
406            },
407            Self::Invalid => {
408                return ParsedArgument {
409                    value: None,
410                    attr_tainted: false,
411                };
412            },
413        };
414        let Ok(substitution) = context.substitute_declaration_value(arg, source) else {
415            return ParsedArgument {
416                value: None,
417                attr_tainted: false,
418            };
419        };
420        let parser_context = context.parser_context(&source.url_data);
421        ParsedArgument {
422            value: Parser::new(&substitution.css)
423                .parse_entirely(|input| T::parse(&parser_context, input))
424                .ok()
425                .map(Cow::Owned),
426            attr_tainted: substitution.attr_tainted,
427        }
428    }
429}
430
431/// The result of substituting and parsing a `SubstitutionArgument`.
432pub struct ParsedArgument<'a, T: Clone> {
433    /// The parsed argument, or `None` if it failed to substitute or parse.
434    pub value: Option<Cow<'a, T>>,
435    /// Whether any attr()-tainted values were involved in the substitution.
436    pub attr_tainted: bool,
437}
438
439/// A substitution function along with the data specific to it.
440#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
441pub enum SubstitutionFunction {
442    /// A `var()` reference.
443    Var(VarFunction),
444    /// An `env()` reference.
445    Env(EnvFunction),
446    /// An `attr()` reference.
447    Attr(AttrFunction),
448}
449
450impl SubstitutionFunction {
451    /// Parses the arguments of a substitution function of the given `kind`, with `input`
452    /// positioned inside its nested block.
453    pub fn parse(
454        kind: SubstitutionFunctionKind,
455        input: &mut Parser,
456        context: &mut SubstitutionParserContext,
457    ) -> Result<Self, ParseError> {
458        Ok(match kind {
459            SubstitutionFunctionKind::Var => Self::Var(VarFunction::parse(input, context)?),
460            SubstitutionFunctionKind::Env => Self::Env(EnvFunction::parse(input, context)?),
461            SubstitutionFunctionKind::Attr => Self::Attr(AttrFunction::parse(input, context)?),
462        })
463    }
464
465    /// The reference flags to record for a reference to this function.
466    #[inline]
467    pub fn reference_flags(&self) -> ReferenceFlags {
468        match self {
469            Self::Var(..) => ReferenceFlags::VAR,
470            Self::Env(..) => ReferenceFlags::ENV,
471            Self::Attr(..) => ReferenceFlags::ATTR,
472        }
473    }
474
475    fn replace<'a>(
476        &'a self,
477        source: &'a VariableValue,
478        context: &mut SubstitutionReplacementContext<'a, '_, '_, '_>,
479    ) -> Result<Substitution<'a>, ()> {
480        match self {
481            Self::Var(function) => function.replace(source, context),
482            Self::Env(function) => function.replace(source, context),
483            Self::Attr(function) => function.replace(source, context),
484        }
485    }
486
487    /// Calls `f` for each arbitrary declaration value nested in the function.
488    #[inline]
489    pub fn for_each_unparsed_value<F>(&self, f: F)
490    where
491        F: FnMut(&ArbitraryDeclarationValue),
492    {
493        match self {
494            Self::Var(function) => function.for_each_unparsed_value(f),
495            Self::Env(function) => function.for_each_unparsed_value(f),
496            Self::Attr(function) => function.for_each_unparsed_value(f),
497        }
498    }
499}
500
501/// A reference to a substitution function like env() / var() / attr().
502#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
503pub struct SubstitutionFunctionReference {
504    /// The start of the function in the referencing value's CSS, counted in bytes.
505    pub start: usize,
506    /// The end of the function in the referencing value's CSS, counted in bytes.
507    pub end: usize,
508    /// The serialization type of the token before the function.
509    pub prev_token_type: TokenSerializationType,
510    /// The serialization type of the token after the function.
511    pub next_token_type: TokenSerializationType,
512    /// Function-specific data.
513    pub function: SubstitutionFunction,
514}
515
516/// Set of flags to references this custom property makes.
517#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, MallocSizeOf, ToShmem)]
518pub struct ReferenceFlags(u8);
519
520bitflags! {
521    impl ReferenceFlags : u8 {
522        /// At least one custom property depends on font-relative units.
523        const FONT_UNITS = 1 << 0;
524        /// At least one custom property depends on root element's font-relative units.
525        const ROOT_FONT_UNITS = 1 << 1;
526        /// At least one custom property depends on line height units.
527        const LH_UNITS = 1 << 2;
528        /// At least one custom property depends on root element's line height units.
529        const ROOT_LH_UNITS = 1 << 3;
530        /// The value depends on the used color-scheme (e.g. a registered `<color>` property).
531        const COLOR_SCHEME = 1 << 4;
532        /// All dependencies not depending on the root element.
533        const NON_ROOT_DEPENDENCIES = Self::FONT_UNITS.0 | Self::LH_UNITS.0;
534        /// All dependencies depending on the root element.
535        const ROOT_DEPENDENCIES = Self::ROOT_FONT_UNITS.0 | Self::ROOT_LH_UNITS.0;
536        /// All non-custom dependencies
537        const NON_CUSTOM = Self::NON_ROOT_DEPENDENCIES.0 | Self::ROOT_DEPENDENCIES.0;
538        /// At least one attr() reference.
539        const ATTR = 1 << 5;
540        /// At least one env() reference.
541        const ENV = 1 << 6;
542        /// At least one var() reference.
543        const VAR = 1 << 7;
544    }
545}
546
547impl ReferenceFlags {
548    fn from_unit(value: &CowRcStr) -> Self {
549        // For registered properties, any reference to font-relative dimensions
550        // make it dependent on font-related properties.
551        // TODO(dshin): When we unit algebra gets implemented and handled -
552        // Is it valid to say that `calc(1em / 2em * 3px)` triggers this?
553        if value.eq_ignore_ascii_case(NoCalcLength::LH) {
554            return Self::FONT_UNITS | Self::LH_UNITS;
555        }
556        if value.eq_ignore_ascii_case(NoCalcLength::EM)
557            || value.eq_ignore_ascii_case(NoCalcLength::EX)
558            || value.eq_ignore_ascii_case(NoCalcLength::CAP)
559            || value.eq_ignore_ascii_case(NoCalcLength::CH)
560            || value.eq_ignore_ascii_case(NoCalcLength::IC)
561        {
562            return Self::FONT_UNITS;
563        }
564        if value.eq_ignore_ascii_case(NoCalcLength::RLH) {
565            return Self::ROOT_FONT_UNITS | Self::ROOT_LH_UNITS;
566        }
567        if value.eq_ignore_ascii_case(NoCalcLength::REM)
568            || value.eq_ignore_ascii_case(NoCalcLength::REX)
569            || value.eq_ignore_ascii_case(NoCalcLength::RCH)
570            || value.eq_ignore_ascii_case(NoCalcLength::RCAP)
571            || value.eq_ignore_ascii_case(NoCalcLength::RIC)
572        {
573            return Self::ROOT_FONT_UNITS;
574        }
575        Self::empty()
576    }
577}
578
579/// A struct holding information about the external references to that a custom property value may
580/// have.
581#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
582pub struct References {
583    /// The actual list of references that we end up with.
584    pub refs: Vec<SubstitutionFunctionReference>,
585    /// Various flags about the kind of references we hold. Note that these include data about our
586    /// nested references.
587    ///
588    /// TODO(emilio): Do we need to distinguish between own and nested flags?
589    pub flags: ReferenceFlags,
590}
591
592impl References {
593    /// Whether there are any substitution function references.
594    pub fn has_references(&self) -> bool {
595        !self.refs.is_empty()
596    }
597
598    pub(crate) fn non_custom_references(&self, is_root_element: bool) -> ReferenceFlags {
599        let mut mask = ReferenceFlags::NON_ROOT_DEPENDENCIES;
600        if is_root_element {
601            mask |= ReferenceFlags::ROOT_DEPENDENCIES
602        }
603        self.flags & mask
604    }
605}
606
607/// For a CSS string, the range, counted in bytes, that is attr()-tainted.
608#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem, ToComputedValue)]
609pub struct AttrTaintedRange {
610    /// Start of the range, counted in bytes. Inclusive.
611    start: usize,
612    /// End of the range, counted in bytes. Exclusive.
613    end: usize,
614}
615
616impl AttrTaintedRange {
617    /// Creates a range within a CSS string that is tainted by attr().
618    #[inline(always)]
619    pub fn new(start: usize, end: usize) -> Self {
620        debug_assert!(start <= end);
621        Self { start, end }
622    }
623}
624
625/// In CSS Values and Units, values produced by `attr()` are considered attr()-tainted, as are
626/// functions that contain an attr()-tainted value. Using an attr()-tainted value as or in a <url>
627/// makes a declaration invalid at computed-value time.
628/// https://drafts.csswg.org/css-values-5/#attr-security
629#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq, ToShmem)]
630pub struct AttrTaint(SmallVec<[AttrTaintedRange; 1]>);
631
632impl AttrTaint {
633    /// For a CSS string, determine whether any `<url>` overlapping this `range`
634    /// is disallowed due to attr()-tainting.
635    #[inline(always)]
636    pub fn should_disallow_urls_in_range(&self, range: &AttrTaintedRange) -> bool {
637        self.0
638            .iter()
639            .any(|r| r.start <= range.end && r.end >= range.start)
640    }
641
642    /// Returns true if the attr()-tainted range contains no elements.
643    #[inline(always)]
644    pub fn is_empty(&self) -> bool {
645        self.0.is_empty()
646    }
647
648    #[inline(always)]
649    fn new_fully_tainted(end: usize) -> Self {
650        let mut taint = Self::default();
651        taint.push(0, end);
652        taint
653    }
654
655    /// Marks the range [start, end) as tainted.
656    #[inline(always)]
657    pub fn push(&mut self, start: usize, end: usize) {
658        self.0.push(AttrTaintedRange::new(start, end));
659    }
660}
661
662/// The result of substituting a value or a single substitution function.
663#[derive(Default, Debug)]
664pub struct Substitution<'a> {
665    /// The substituted CSS.
666    pub css: Cow<'a, str>,
667    /// The serialization type of the first token in `css`.
668    pub first_token_type: TokenSerializationType,
669    /// The serialization type of the last token in `css`.
670    pub last_token_type: TokenSerializationType,
671    /// Whether any attr()-tainted values were involved in the substitution.
672    pub attr_tainted: bool,
673}
674
675impl<'a> Substitution<'a> {
676    /// Creates a substitution from a variable value.
677    pub fn from_value(v: VariableValue, attr_tainted: bool) -> Self {
678        Substitution {
679            css: v.css.into(),
680            first_token_type: v.first_token_type,
681            last_token_type: v.last_token_type,
682            attr_tainted,
683        }
684    }
685
686    /// Computes the substitution against the registration of a custom property.
687    pub fn into_value(
688        self,
689        url_data: &UrlExtraData,
690        registration: &PropertyDescriptors,
691        computed_context: &computed::Context,
692        property_id: Option<PropertyIdRef>,
693    ) -> Result<ComputedRegisteredValue, ()> {
694        if registration.is_universal() {
695            let mut value = ComputedRegisteredValue::universal(Arc::new(VariableValue::new(
696                self.css.into_owned(),
697                url_data,
698                self.first_token_type,
699                self.last_token_type,
700            )));
701            value.attr_tainted |= self.attr_tainted;
702            return Ok(value);
703        }
704        let taint = if self.attr_tainted {
705            // Per spec: substitution value of an arbitrary substitution function is
706            // attr()-tainted as a whole if any attr()-tainted values were involved
707            // in creating that substitution value.
708            // https://drafts.csswg.org/css-values-5/#attr-security
709            AttrTaint::new_fully_tainted(self.css.len())
710        } else {
711            AttrTaint::default()
712        };
713        let mut v = compute_value(
714            &self.css,
715            url_data,
716            registration,
717            computed_context,
718            taint,
719            property_id,
720        )?;
721        v.attr_tainted |= self.attr_tainted;
722        Ok(v)
723    }
724
725    /// Creates a substitution from its parts.
726    pub fn new(
727        css: Cow<'a, str>,
728        first_token_type: TokenSerializationType,
729        last_token_type: TokenSerializationType,
730        attr_tainted: bool,
731    ) -> Self {
732        Self {
733            css,
734            first_token_type,
735            last_token_type,
736            attr_tainted,
737        }
738    }
739}
740
741/// Context for replacing an arbitrary substitution function with its value.
742pub struct SubstitutionReplacementContext<'a, 'b, 'c, 't> {
743    /// The property the value is being substituted for, if any.
744    pub property_id: Option<PropertyIdRef<'b>>,
745    /// The custom properties and attributes to substitute from.
746    pub substitution_functions: &'a ComputedSubstitutionFunctions,
747    /// The stylist.
748    pub stylist: &'b Stylist,
749    /// The computed context.
750    pub computed_context: &'b computed::Context<'c>,
751    /// Tracks the attributes queried by `attr()`.
752    pub attribute_tracker: &'b mut AttributeTracker<'t>,
753    /// The custom properties currently being resolved, for cycle detection.
754    pub seen: SmallVec<[Name; 8]>,
755}
756
757impl<'a, 'b, 'c, 't> SubstitutionReplacementContext<'a, 'b, 'c, 't> {
758    /// Creates a new context for substitution function replacement.
759    pub fn new(
760        property_id: Option<PropertyIdRef<'b>>,
761        substitution_functions: &'a ComputedSubstitutionFunctions,
762        stylist: &'b Stylist,
763        computed_context: &'b computed::Context<'c>,
764        attribute_tracker: &'b mut AttributeTracker<'t>,
765    ) -> Self {
766        Self {
767            property_id,
768            substitution_functions,
769            stylist,
770            computed_context,
771            attribute_tracker,
772            seen: SmallVec::new(),
773        }
774    }
775
776    /// Substitutes the references in a variable value. Returns `Err(..)` for invalid at computed
777    /// time.
778    pub fn substitute_variable_value(
779        &mut self,
780        variable_value: &'a VariableValue,
781        attr_taint: Option<&mut AttrTaint>,
782    ) -> Result<Substitution<'a>, ()> {
783        self.substitute_chunk(
784            variable_value,
785            /* start = */ 0,
786            /* end = */ variable_value.css.len(),
787            variable_value.first_token_type,
788            variable_value.last_token_type,
789            &variable_value.references.refs,
790            attr_taint,
791        )
792    }
793
794    /// Substitutes one of a function's nested values, which is part of `source`.
795    pub fn substitute_declaration_value(
796        &mut self,
797        value: &'a ArbitraryDeclarationValue,
798        source: &'a VariableValue,
799    ) -> Result<Substitution<'a>, ()> {
800        self.substitute_chunk(
801            source,
802            value.start.get(),
803            value.end.get(),
804            value.first_token_type,
805            value.last_token_type,
806            &value.references.refs,
807            /* attr_taint */ None,
808        )
809    }
810
811    fn substitute_chunk(
812        &mut self,
813        source: &'a VariableValue,
814        start: usize,
815        end: usize,
816        first_token_type: TokenSerializationType,
817        last_token_type: TokenSerializationType,
818        references: &'a [SubstitutionFunctionReference],
819        mut attr_taint: Option<&mut AttrTaint>,
820    ) -> Result<Substitution<'a>, ()> {
821        if start == end {
822            // Empty string. Easy.
823            return Ok(Substitution::default());
824        }
825        let css = &source.css;
826        // Easy case: no references involved.
827        if references.is_empty() {
828            let result = &css[start..end];
829            return Ok(Substitution::new(
830                Cow::Borrowed(result),
831                first_token_type,
832                last_token_type,
833                Default::default(),
834            ));
835        }
836
837        let mut substituted = ComputedValue::empty(&source.url_data);
838        let mut next_token_type = first_token_type;
839        let mut cur_pos = start;
840        let mut attr_tainted = false;
841        for reference in references {
842            if reference.start != cur_pos {
843                substituted.push(
844                    &css[cur_pos..reference.start],
845                    next_token_type,
846                    reference.prev_token_type,
847                    /* attr_taint */ None,
848                )?;
849            }
850
851            let substitution = reference.function.replace(source, self)?;
852
853            // Optimize the property: var(--...) case to avoid allocating at all.
854            if reference.start == start && reference.end == end {
855                if let Some(taint) = attr_taint.filter(|_| substitution.attr_tainted) {
856                    taint.push(start, substitution.css.len());
857                }
858                return Ok(substitution);
859            }
860
861            substituted.push(
862                &substitution.css,
863                substitution.first_token_type,
864                substitution.last_token_type,
865                attr_taint
866                    .as_deref_mut()
867                    .filter(|_| substitution.attr_tainted),
868            )?;
869            attr_tainted |= substitution.attr_tainted;
870            next_token_type = reference.next_token_type;
871            cur_pos = reference.end;
872        }
873        // Push the rest of the value if needed.
874        if cur_pos != end {
875            substituted.push(
876                &css[cur_pos..end],
877                next_token_type,
878                last_token_type,
879                /* attr_taint */ None,
880            )?;
881        }
882        Ok(Substitution::from_value(substituted, attr_tainted))
883    }
884
885    /// Build the context to use when parsing values during substitution.
886    pub fn parser_context(&self, url_data: &'b UrlExtraData) -> ParserContext<'b> {
887        ParserContext::new_with_property_id(
888            Origin::Author,
889            url_data,
890            None,
891            ParsingMode::DEFAULT,
892            self.computed_context.quirks_mode,
893            /* namespaces = */ Default::default(),
894            None,
895            None,
896            AttrTaint::default(),
897            self.property_id,
898        )
899    }
900}
901
902/// Result of var(), env(), and attr() substitution.
903#[derive(Debug)]
904pub struct SubstitutionResult<'a> {
905    /// The resolved CSS string after substitution.
906    pub css: Cow<'a, str>,
907    /// Regions in the `css` string that are attr()-tainted, if any.
908    pub attr_taint: AttrTaint,
909}
910
911/// Replace var(), env(), and attr() functions, returning the resulting CSS string.
912pub fn substitute<'a>(
913    variable_value: &'a VariableValue,
914    property_id: Option<PropertyIdRef>,
915    substitution_functions: &'a ComputedSubstitutionFunctions,
916    stylist: &Stylist,
917    computed_context: &computed::Context,
918    attribute_tracker: &mut AttributeTracker,
919) -> Result<SubstitutionResult<'a>, ()> {
920    debug_assert!(variable_value.has_references());
921    let mut attr_taint = AttrTaint::default();
922    let v = SubstitutionReplacementContext::new(
923        property_id,
924        substitution_functions,
925        stylist,
926        computed_context,
927        attribute_tracker,
928    )
929    .substitute_variable_value(variable_value, Some(&mut attr_taint))?;
930    Ok(SubstitutionResult {
931        css: v.css,
932        attr_taint,
933    })
934}