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