Skip to main content

style/substitution/
var.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//! The `var()` arbitrary substitution function.
6//! https://drafts.csswg.org/css-variables-2/#funcdef-var
7
8use super::{
9    ArbitraryDeclarationValue, ParsedArgument, Substitution, SubstitutionArgument,
10    SubstitutionParserContext, SubstitutionReplacementContext, parse_optional_fallback,
11};
12use crate::custom_properties::{Name, VariableValue, parse_name};
13use crate::parser::Parse;
14use crate::{Atom, derives::*};
15use cssparser::{Delimiter, Parser};
16use style_traits::{ParseError, StyleParseErrorKind};
17
18/// A custom property name, without the leading `--`.
19#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
20pub struct CustomPropertyName(pub Name);
21
22impl Parse for CustomPropertyName {
23    fn parse(_: &crate::parser::ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
24        parse_name(input.expect_ident()?.as_ref())
25            .map(Atom::from)
26            .map(CustomPropertyName)
27            .map_err(|_| ParseError::custom(StyleParseErrorKind::UnexpectedIdent))
28    }
29}
30
31/// Data specific to a `var()` function.
32#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
33pub struct VarFunction {
34    /// The referenced custom property name.
35    pub name: SubstitutionArgument<CustomPropertyName>,
36    /// The fallback for this function, if any.
37    pub fallback: Option<ArbitraryDeclarationValue>,
38}
39
40impl VarFunction {
41    /// Parses the `var()` function arguments according to its argument grammar and syntax:
42    /// - <var-args> = var(<declaration-value>, <declaration-value>?)
43    /// - var() = var(<custom-property-name>, <declaration-value>?)
44    pub fn parse(
45        input: &mut Parser,
46        context: &mut SubstitutionParserContext,
47    ) -> Result<Self, ParseError> {
48        let name = SubstitutionArgument::try_parse(input, context, Delimiter::Comma)?;
49        let fallback = parse_optional_fallback(input, context)?;
50        Ok(Self { name, fallback })
51    }
52
53    /// Replaces the `var()` function with its substitution value.
54    /// https://drafts.csswg.org/css-variables-2/#replace-a-var-function
55    pub fn replace<'a>(
56        &'a self,
57        source: &'a VariableValue,
58        context: &mut SubstitutionReplacementContext<'a, '_, '_, '_>,
59    ) -> Result<Substitution<'a>, ()> {
60        let name = self.replace_name(source, context);
61        let mut substitution = name
62            .value
63            .and_then(|name| Self::resolve_custom_property(&name.0, context))
64            .ok_or(())
65            .or_else(|_| {
66                context.substitute_declaration_value(self.fallback.as_ref().ok_or(())?, source)
67            })?;
68        substitution.attr_tainted |= name.attr_tainted;
69        Ok(substitution)
70    }
71
72    fn resolve_custom_property<'a>(
73        name: &Name,
74        context: &mut SubstitutionReplacementContext<'a, '_, '_, '_>,
75    ) -> Option<Substitution<'a>> {
76        let registration = context.stylist.get_custom_property_registration(name);
77        match context.substitution_functions.get_var(registration, name) {
78            None => None,
79            // If the referenced value is itself still unresolved (i.e. it has references), we
80            // are substituting against a partially-resolved map -- this happens while applying
81            // a prioritary property in the middle of custom-property resolution. Resolve it
82            // recursively, guarding against cycles (which the resolver will also remove, but it
83            // may not have gotten to this variable yet).
84            Some(v) => match v.as_universal() {
85                Some(u) if u.has_references() => {
86                    if context.seen.contains(name) {
87                        // Cycle: the primary is guaranteed-invalid, so fall through to the
88                        // fallback (if any), like any other invalid primary.
89                        None
90                    } else {
91                        context.seen.push(name.clone());
92                        let result =
93                            context.substitute_variable_value(u, /* attr_taint */ None);
94                        context.seen.pop();
95                        match result {
96                            Ok(mut substitution) => {
97                                substitution.attr_tainted |= v.attr_tainted;
98                                Some(substitution)
99                            },
100                            // The primary couldn't be resolved (invalid); use the fallback.
101                            Err(()) => None,
102                        }
103                    }
104                },
105                _ => Some(Substitution::from_value(
106                    v.to_variable_value(),
107                    v.attr_tainted,
108                )),
109            },
110        }
111    }
112
113    /// Returns the name, substituting and parsing it first if it has references.
114    pub fn replace_name<'a>(
115        &'a self,
116        source: &'a VariableValue,
117        context: &mut SubstitutionReplacementContext<'a, '_, '_, '_>,
118    ) -> ParsedArgument<'a, CustomPropertyName> {
119        self.name.substitute_and_parse(source, context)
120    }
121
122    /// Calls `f` for each arbitrary declaration value nested in the function.
123    pub fn for_each_unparsed_value<F>(&self, mut f: F)
124    where
125        F: FnMut(&ArbitraryDeclarationValue),
126    {
127        if let SubstitutionArgument::Unparsed(arg) = &self.name {
128            f(arg);
129        }
130        if let Some(ref fallback) = self.fallback {
131            f(fallback);
132        }
133    }
134}