Skip to main content

style/
parser.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 context within which CSS code is parsed.
6
7use crate::context::QuirksMode;
8use crate::custom_properties::{AttrTaint, AttrTaintedRange};
9use crate::error_reporting::{ContextualParseError, ParseErrorReporter};
10use crate::stylesheets::{CssRuleType, CssRuleTypes, Namespaces, Origin, UrlExtraData};
11use crate::use_counters::UseCounters;
12use cssparser::{Parser, SourceLocation, UnicodeRange};
13use selectors::parser::ParseRelative;
14use std::borrow::Cow;
15use style_traits::{OneOrMoreSeparated, ParseError, ParsingMode, Separator};
16
17/// Nesting context for parsing rules.
18#[derive(Clone, Copy)]
19pub struct NestingContext {
20    /// All rule types we've nested into, if any.
21    pub rule_types: CssRuleTypes,
22    /// Whether or not parsing relative selector syntax should be allowed.
23    pub parse_relative: ParseRelative,
24}
25
26impl NestingContext {
27    fn parse_relative_for(rule_type: CssRuleType) -> ParseRelative {
28        match rule_type {
29            CssRuleType::Scope => ParseRelative::ForScope,
30            CssRuleType::Style => ParseRelative::ForNesting,
31            _ => ParseRelative::No,
32        }
33    }
34
35    /// Create a new nesting context.
36    pub fn new(rule_types: CssRuleTypes, parse_nested_rule_type: Option<CssRuleType>) -> Self {
37        Self {
38            rule_types,
39            parse_relative: parse_nested_rule_type
40                .map_or(ParseRelative::No, Self::parse_relative_for),
41        }
42    }
43
44    /// Create a new nesting context based on the given rule.
45    pub fn new_from_rule(rule_type: Option<CssRuleType>) -> Self {
46        Self {
47            rule_types: rule_type.map(CssRuleTypes::from).unwrap_or_default(),
48            parse_relative: rule_type
49                .map(Self::parse_relative_for)
50                .unwrap_or(ParseRelative::No),
51        }
52    }
53
54    /// Save the current nesting context.
55    pub fn save(&mut self, rule_type: CssRuleType) -> Self {
56        let old = *self;
57        self.rule_types.insert(rule_type);
58        let new_parse_relative = Self::parse_relative_for(rule_type);
59        if new_parse_relative != ParseRelative::No {
60            self.parse_relative = new_parse_relative;
61        }
62        old
63    }
64
65    /// Load the saved nesting context.
66    pub fn restore(&mut self, saved: Self) {
67        *self = saved;
68    }
69}
70
71/// The data that the parser needs from outside in order to parse a stylesheet.
72pub struct ParserContext<'a> {
73    /// The `Origin` of the stylesheet, whether it's a user, author or
74    /// user-agent stylesheet.
75    pub stylesheet_origin: Origin,
76    /// The extra data we need for resolving url values.
77    pub url_data: &'a UrlExtraData,
78    /// The mode to use when parsing.
79    pub parsing_mode: ParsingMode,
80    /// The quirks mode of this stylesheet.
81    pub quirks_mode: QuirksMode,
82    /// The active error reporter, or none if error reporting is disabled.
83    error_reporter: Option<&'a dyn ParseErrorReporter>,
84    /// The currently active namespaces.
85    pub namespaces: Cow<'a, Namespaces>,
86    /// The use counters we want to record while parsing style rules, if any.
87    pub use_counters: Option<&'a UseCounters>,
88    /// Current nesting context.
89    pub nesting_context: NestingContext,
90    /// The relevant regions in the input that have been tainted by attr() if relevant.
91    pub attr_tainted_regions: AttrTaint,
92}
93
94impl<'a> ParserContext<'a> {
95    /// Create a parser context.
96    #[inline]
97    pub fn new(
98        stylesheet_origin: Origin,
99        url_data: &'a UrlExtraData,
100        rule_type: Option<CssRuleType>,
101        parsing_mode: ParsingMode,
102        quirks_mode: QuirksMode,
103        namespaces: Cow<'a, Namespaces>,
104        error_reporter: Option<&'a dyn ParseErrorReporter>,
105        use_counters: Option<&'a UseCounters>,
106        attr_tainted_regions: AttrTaint,
107    ) -> Self {
108        Self {
109            stylesheet_origin,
110            url_data,
111            parsing_mode,
112            quirks_mode,
113            error_reporter,
114            namespaces,
115            use_counters,
116            nesting_context: NestingContext::new_from_rule(rule_type),
117            attr_tainted_regions,
118        }
119    }
120
121    /// Temporarily sets the rule_type and executes the callback function, returning its result.
122    pub fn nest_for_rule<R>(
123        &mut self,
124        rule_type: CssRuleType,
125        cb: impl FnOnce(&mut Self) -> R,
126    ) -> R {
127        let old = self.nesting_context.save(rule_type);
128        let r = cb(self);
129        self.nesting_context.restore(old);
130        r
131    }
132
133    /// Temporarily adds a parsing mode flag and executes the callback, returning its result.
134    pub fn with_parsing_mode<R>(&mut self, mode: ParsingMode, cb: impl FnOnce(&Self) -> R) -> R {
135        let old = self.parsing_mode;
136        self.parsing_mode |= mode;
137        let r = cb(self);
138        self.parsing_mode = old;
139        r
140    }
141
142    /// Whether we're in a @page rule.
143    #[inline]
144    pub fn in_page_rule(&self) -> bool {
145        self.nesting_context.rule_types.contains(CssRuleType::Page)
146    }
147
148    /// Returns whether !important declarations are forbidden.
149    #[inline]
150    pub fn allows_important_declarations(&self) -> bool {
151        !self
152            .nesting_context
153            .rule_types
154            .intersects(CssRuleTypes::IMPORTANT_FORBIDDEN)
155    }
156
157    /// Returns whether we can parse element-dependent values.
158    #[inline]
159    pub fn has_element_context(&self) -> bool {
160        if self
161            .nesting_context
162            .rule_types
163            .intersects(CssRuleTypes::WITHOUT_ELEMENT_CONTEXT)
164        {
165            return false;
166        }
167
168        if self
169            .parsing_mode
170            .intersects(ParsingMode::MEDIA_QUERY_CONDITION)
171        {
172            return false;
173        }
174
175        true
176    }
177
178    /// Get the rule type, which assumes that one is available.
179    pub fn rule_types(&self) -> CssRuleTypes {
180        self.nesting_context.rule_types
181    }
182
183    /// Returns whether CSS error reporting is enabled.
184    #[inline]
185    pub fn error_reporting_enabled(&self) -> bool {
186        self.error_reporter.is_some()
187    }
188
189    /// Record a CSS parse error with this context’s error reporting.
190    pub fn log_css_error(&self, location: SourceLocation, error: ContextualParseError) {
191        let error_reporter = match self.error_reporter {
192            Some(r) => r,
193            None => return,
194        };
195
196        error_reporter.report_error(self.url_data, location, error)
197    }
198
199    /// Whether we're in a user-agent stylesheet.
200    #[inline]
201    pub fn in_ua_sheet(&self) -> bool {
202        self.stylesheet_origin == Origin::UserAgent
203    }
204
205    /// Returns whether chrome-only rules should be parsed.
206    #[inline]
207    pub fn chrome_rules_enabled(&self) -> bool {
208        self.url_data.chrome_rules_enabled() || self.stylesheet_origin != Origin::Author
209    }
210
211    /// Whether the parsing mode allows units or functions that are not computationally independent.
212    #[inline]
213    pub fn allows_computational_dependence(&self) -> bool {
214        self.parsing_mode.allows_computational_dependence()
215    }
216
217    /// Whether any `<url>` over this `range` is disallowed due to attr()-tainting.
218    pub fn disallow_urls_in_range(&self, range: &AttrTaintedRange) -> bool {
219        self.attr_tainted_regions
220            .should_disallow_urls_in_range(range)
221    }
222}
223
224/// A trait to abstract parsing of a specified value given a `ParserContext` and
225/// CSS input.
226///
227/// This can be derived on keywords with `#[derive(Parse)]`.
228///
229/// The derive code understands the following attributes on each of the variants:
230///
231///  * `#[parse(aliases = "foo,bar")]` can be used to alias a value with another
232///    at parse-time.
233///
234///  * `#[parse(condition = "function")]` can be used to make the parsing of the
235///    value conditional on `function`, which needs to fulfill
236///    `fn(&ParserContext) -> bool`.
237///
238///  * `#[parse(parse_fn = "function")]` can be used to specify a function other than Parser::parse
239///    for a particular variant.
240pub trait Parse: Sized {
241    /// Parse a value of this type.
242    ///
243    /// Returns an error on failure.
244    fn parse<'i, 't>(
245        context: &ParserContext,
246        input: &mut Parser<'i, 't>,
247    ) -> Result<Self, ParseError<'i>>;
248}
249
250impl<T> Parse for Vec<T>
251where
252    T: Parse + OneOrMoreSeparated,
253    <T as OneOrMoreSeparated>::S: Separator,
254{
255    fn parse<'i, 't>(
256        context: &ParserContext,
257        input: &mut Parser<'i, 't>,
258    ) -> Result<Self, ParseError<'i>> {
259        <T as OneOrMoreSeparated>::S::parse(input, |i| T::parse(context, i))
260    }
261}
262
263impl<T> Parse for Box<T>
264where
265    T: Parse,
266{
267    fn parse<'i, 't>(
268        context: &ParserContext,
269        input: &mut Parser<'i, 't>,
270    ) -> Result<Self, ParseError<'i>> {
271        T::parse(context, input).map(Box::new)
272    }
273}
274
275impl Parse for crate::OwnedStr {
276    fn parse<'i, 't>(
277        _: &ParserContext,
278        input: &mut Parser<'i, 't>,
279    ) -> Result<Self, ParseError<'i>> {
280        Ok(input.expect_string()?.as_ref().to_owned().into())
281    }
282}
283
284impl Parse for UnicodeRange {
285    fn parse<'i, 't>(
286        _: &ParserContext,
287        input: &mut Parser<'i, 't>,
288    ) -> Result<Self, ParseError<'i>> {
289        Ok(UnicodeRange::parse(input)?)
290    }
291}