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