Skip to main content

style/
error_reporting.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//! Types used to report parsing errors.
6
7#![deny(missing_docs)]
8
9use crate::selector_parser::SelectorImpl;
10use crate::stylesheets::UrlExtraData;
11use cssparser::{BasicParseErrorKind, ParseErrorKind, SourceLocation};
12use selectors::SelectorList;
13use selectors::parser::{Combinator, Component, RelativeSelector, Selector};
14use selectors::visitor::{SelectorListKind, SelectorVisitor};
15use std::fmt;
16use style_traits::ParseError;
17
18/// Errors that can be encountered while parsing CSS.
19#[derive(Debug)]
20pub enum ContextualParseError<'a> {
21    /// A property declaration was not recognized.
22    UnsupportedPropertyDeclaration(&'a str, ParseError, &'a [SelectorList<SelectorImpl>]),
23    /// A property descriptor was not recognized.
24    UnsupportedPropertyDescriptor(&'a str, ParseError),
25    /// A font face descriptor was not recognized.
26    UnsupportedFontFaceDescriptor(&'a str, ParseError),
27    /// A font feature values descriptor was not recognized.
28    UnsupportedFontFeatureValuesDescriptor(&'a str, ParseError),
29    /// A font palette values descriptor was not recognized.
30    UnsupportedFontPaletteValuesDescriptor(&'a str, ParseError),
31    /// A keyframe rule was not valid.
32    InvalidKeyframeRule(&'a str, ParseError),
33    /// A font feature values rule was not valid.
34    InvalidFontFeatureValuesRule(&'a str, ParseError),
35    /// A rule was invalid for some reason.
36    InvalidRule(&'a str, ParseError),
37    /// A rule was not recognized.
38    UnsupportedRule(&'a str, ParseError),
39    /// A viewport descriptor declaration was not recognized.
40    UnsupportedViewportDescriptorDeclaration(&'a str, ParseError),
41    /// A counter style descriptor declaration was not recognized.
42    UnsupportedCounterStyleDescriptorDeclaration(&'a str, ParseError),
43    /// A counter style rule had no symbols.
44    InvalidCounterStyleWithoutSymbols(String),
45    /// A counter style rule had less than two symbols.
46    InvalidCounterStyleNotEnoughSymbols(String),
47    /// A counter style rule did not have additive-symbols.
48    InvalidCounterStyleWithoutAdditiveSymbols,
49    /// A counter style rule had extends with symbols.
50    InvalidCounterStyleExtendsWithSymbols,
51    /// A counter style rule had extends with additive-symbols.
52    InvalidCounterStyleExtendsWithAdditiveSymbols,
53    /// A media rule was invalid for some reason.
54    InvalidMediaRule(&'a str, ParseError),
55    /// A value was not recognized.
56    UnsupportedValue(&'a str, ParseError),
57    /// A never-matching `:host` selector was found.
58    NeverMatchingHostSelector(String),
59    /// A view-transition declaration was not recognized.
60    UnsupportedViewTransitionDescriptor(&'a str, ParseError),
61}
62
63impl<'a> fmt::Display for ContextualParseError<'a> {
64    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65        fn parse_error_to_str(err: &ParseError, f: &mut fmt::Formatter) -> fmt::Result {
66            match err.kind {
67                ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken) => {
68                    write!(f, "found unexpected token")
69                },
70                ParseErrorKind::Basic(BasicParseErrorKind::TooManyNestedBlocks) => {
71                    write!(f, "too many nested blocks")
72                },
73                ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput) => {
74                    write!(f, "unexpected end of input")
75                },
76                ParseErrorKind::Basic(BasicParseErrorKind::AtRuleInvalid) => {
77                    write!(f, "@ rule invalid")
78                },
79                ParseErrorKind::Basic(BasicParseErrorKind::AtRuleBodyInvalid) => {
80                    write!(f, "@ rule invalid")
81                },
82                ParseErrorKind::Basic(BasicParseErrorKind::QualifiedRuleInvalid) => {
83                    write!(f, "qualified rule invalid")
84                },
85                ParseErrorKind::Custom(ref err) => write!(f, "{:?}", err),
86            }
87        }
88
89        match *self {
90            ContextualParseError::UnsupportedPropertyDeclaration(decl, ref err, _selectors) => {
91                write!(f, "Unsupported property declaration: '{}', ", decl)?;
92                parse_error_to_str(err, f)
93            },
94            ContextualParseError::UnsupportedPropertyDescriptor(decl, ref err) => {
95                write!(
96                    f,
97                    "Unsupported @property descriptor declaration: '{}', ",
98                    decl
99                )?;
100                parse_error_to_str(err, f)
101            },
102            ContextualParseError::UnsupportedFontFaceDescriptor(decl, ref err) => {
103                write!(
104                    f,
105                    "Unsupported @font-face descriptor declaration: '{}', ",
106                    decl
107                )?;
108                parse_error_to_str(err, f)
109            },
110            ContextualParseError::UnsupportedFontFeatureValuesDescriptor(decl, ref err) => {
111                write!(
112                    f,
113                    "Unsupported @font-feature-values descriptor declaration: '{}', ",
114                    decl
115                )?;
116                parse_error_to_str(err, f)
117            },
118            ContextualParseError::UnsupportedFontPaletteValuesDescriptor(decl, ref err) => {
119                write!(
120                    f,
121                    "Unsupported @font-palette-values descriptor declaration: '{}', ",
122                    decl
123                )?;
124                parse_error_to_str(err, f)
125            },
126            ContextualParseError::InvalidKeyframeRule(rule, ref err) => {
127                write!(f, "Invalid keyframe rule: '{}', ", rule)?;
128                parse_error_to_str(err, f)
129            },
130            ContextualParseError::InvalidFontFeatureValuesRule(rule, ref err) => {
131                write!(f, "Invalid font feature value rule: '{}', ", rule)?;
132                parse_error_to_str(err, f)
133            },
134            ContextualParseError::InvalidRule(rule, ref err) => {
135                write!(f, "Invalid rule: '{}', ", rule)?;
136                parse_error_to_str(err, f)
137            },
138            ContextualParseError::UnsupportedRule(rule, ref err) => {
139                write!(f, "Unsupported rule: '{}', ", rule)?;
140                parse_error_to_str(err, f)
141            },
142            ContextualParseError::UnsupportedViewportDescriptorDeclaration(decl, ref err) => {
143                write!(
144                    f,
145                    "Unsupported @viewport descriptor declaration: '{}', ",
146                    decl
147                )?;
148                parse_error_to_str(err, f)
149            },
150            ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(decl, ref err) => {
151                write!(
152                    f,
153                    "Unsupported @counter-style descriptor declaration: '{}', ",
154                    decl
155                )?;
156                parse_error_to_str(err, f)
157            },
158            ContextualParseError::InvalidCounterStyleWithoutSymbols(ref system) => write!(
159                f,
160                "Invalid @counter-style rule: 'system: {}' without 'symbols'",
161                system
162            ),
163            ContextualParseError::InvalidCounterStyleNotEnoughSymbols(ref system) => write!(
164                f,
165                "Invalid @counter-style rule: 'system: {}' less than two 'symbols'",
166                system
167            ),
168            ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols => write!(
169                f,
170                "Invalid @counter-style rule: 'system: additive' without 'additive-symbols'"
171            ),
172            ContextualParseError::InvalidCounterStyleExtendsWithSymbols => write!(
173                f,
174                "Invalid @counter-style rule: 'system: extends …' with 'symbols'"
175            ),
176            ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols => write!(
177                f,
178                "Invalid @counter-style rule: 'system: extends …' with 'additive-symbols'"
179            ),
180            ContextualParseError::InvalidMediaRule(media_rule, ref err) => {
181                write!(f, "Invalid media rule: {}, ", media_rule)?;
182                parse_error_to_str(err, f)
183            },
184            ContextualParseError::UnsupportedValue(_value, ref err) => parse_error_to_str(err, f),
185            ContextualParseError::NeverMatchingHostSelector(ref selector) => {
186                write!(f, ":host selector is not featureless: {}", selector)
187            },
188            ContextualParseError::UnsupportedViewTransitionDescriptor(decl, ref err) => {
189                write!(
190                    f,
191                    "Unsupported @view-transition descriptor declaration: '{}', ",
192                    decl
193                )?;
194                parse_error_to_str(err, f)
195            },
196        }
197    }
198}
199
200/// A generic trait for an error reporter.
201pub trait ParseErrorReporter {
202    /// Called when the style engine detects an error.
203    ///
204    /// Returns the current input being parsed, an approximate source location
205    /// for the error, and a message.
206    fn report_error(
207        &self,
208        url: &UrlExtraData,
209        location: SourceLocation,
210        error: ContextualParseError,
211    );
212}
213
214/// An error reporter that uses [the `log` crate](https://github.com/rust-lang-nursery/log)
215/// at `info` level.
216///
217/// This logging is silent by default, and can be enabled with a `RUST_LOG=style=info`
218/// environment variable.
219/// (See [`env_logger`](https://rust-lang-nursery.github.io/log/env_logger/).)
220#[cfg(feature = "servo")]
221pub struct RustLogReporter;
222
223#[cfg(feature = "servo")]
224impl ParseErrorReporter for RustLogReporter {
225    fn report_error(
226        &self,
227        url: &UrlExtraData,
228        location: SourceLocation,
229        error: ContextualParseError,
230    ) {
231        if log_enabled!(log::Level::Info) {
232            info!(
233                "Url:\t{}\n{}:{} {}",
234                url.as_str(),
235                location.line,
236                location.column,
237                error
238            )
239        }
240    }
241}
242
243/// Any warning a selector may generate.
244/// TODO(dshin): Bug 1860634 - Merge with never matching host selector warning, which is part of the rule parser.
245#[repr(u8)]
246#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
247pub enum SelectorWarningKind {
248    /// Relative Selector with not enough constraint, either outside or inside the selector. e.g. `*:has(.a)`, `.a:has(*)`.
249    /// May cause expensive invalidations for every element inserted and/or removed.
250    UnconstraintedRelativeSelector,
251    /// `:scope` can have 3 meanings, but in all cases, the relationship is defined strictly by an ancestor-descendant
252    /// relationship. This means that any presence of sibling selectors to its right would make it never match.
253    SiblingCombinatorAfterScopeSelector,
254}
255
256impl SelectorWarningKind {
257    /// Get all warnings for this selector.
258    pub fn from_selector(selector: &Selector<SelectorImpl>) -> Vec<Self> {
259        let mut result = vec![];
260        if UnconstrainedRelativeSelectorVisitor::has_warning(selector, 0, false) {
261            result.push(SelectorWarningKind::UnconstraintedRelativeSelector);
262        }
263        if SiblingCombinatorAfterScopeSelectorVisitor::has_warning(selector) {
264            result.push(SelectorWarningKind::SiblingCombinatorAfterScopeSelector);
265        }
266        result
267    }
268}
269
270/// Per-compound state for finding unconstrained relative selectors.
271struct PerCompoundState {
272    /// Is there a relative selector in this compound?
273    relative_selector_found: bool,
274    /// Is this compound constrained in any way?
275    constrained: bool,
276    /// Nested below, or inside relative selector?
277    in_relative_selector: bool,
278}
279
280impl PerCompoundState {
281    fn new(in_relative_selector: bool) -> Self {
282        Self {
283            relative_selector_found: false,
284            constrained: false,
285            in_relative_selector,
286        }
287    }
288}
289
290/// Visitor to check if there's any unconstrained relative selector.
291struct UnconstrainedRelativeSelectorVisitor {
292    compound_state: PerCompoundState,
293}
294
295impl UnconstrainedRelativeSelectorVisitor {
296    fn new(in_relative_selector: bool) -> Self {
297        Self {
298            compound_state: PerCompoundState::new(in_relative_selector),
299        }
300    }
301
302    fn has_warning(
303        selector: &Selector<SelectorImpl>,
304        offset: usize,
305        in_relative_selector: bool,
306    ) -> bool {
307        let relative_selector = matches!(
308            selector.iter_raw_parse_order_from(0).next().unwrap(),
309            Component::RelativeSelectorAnchor
310        );
311        debug_assert!(
312            !relative_selector || offset == 0,
313            "Checking relative selector from non-rightmost?"
314        );
315        let mut visitor = Self::new(in_relative_selector);
316        let mut iter = if relative_selector {
317            selector.iter_skip_relative_selector_anchor()
318        } else {
319            selector.iter_from(offset)
320        };
321        loop {
322            visitor.compound_state = PerCompoundState::new(in_relative_selector);
323
324            for s in &mut iter {
325                s.visit(&mut visitor);
326            }
327
328            if (visitor.compound_state.relative_selector_found
329                || visitor.compound_state.in_relative_selector)
330                && !visitor.compound_state.constrained
331            {
332                return true;
333            }
334
335            if iter.next_sequence().is_none() {
336                break;
337            }
338        }
339        false
340    }
341}
342
343impl SelectorVisitor for UnconstrainedRelativeSelectorVisitor {
344    type Impl = SelectorImpl;
345
346    fn visit_simple_selector(&mut self, c: &Component<Self::Impl>) -> bool {
347        match c {
348            // Deferred to visit_selector_list
349            Component::Is(..)
350            | Component::Where(..)
351            | Component::Negation(..)
352            | Component::Has(..) => (),
353            Component::ExplicitUniversalType => (),
354            _ => self.compound_state.constrained |= true,
355        };
356        true
357    }
358
359    fn visit_selector_list(
360        &mut self,
361        _list_kind: SelectorListKind,
362        list: &[Selector<Self::Impl>],
363    ) -> bool {
364        let mut all_constrained = true;
365        for s in list {
366            let mut offset = 0;
367            // First, check the rightmost compound for constraint at this level.
368            if !self.compound_state.in_relative_selector {
369                let mut nested = Self::new(false);
370                let mut iter = s.iter();
371                loop {
372                    for c in &mut iter {
373                        c.visit(&mut nested);
374                        offset += 1;
375                    }
376
377                    let c = iter.next_sequence();
378                    offset += 1;
379                    if c.is_none_or(|c| !c.is_pseudo_element()) {
380                        break;
381                    }
382                }
383                // Every single selector in the list must be constrained.
384                all_constrained &= nested.compound_state.constrained;
385            }
386
387            if offset >= s.len() {
388                continue;
389            }
390
391            // Then, recurse in to check at the deeper level.
392            if Self::has_warning(s, offset, self.compound_state.in_relative_selector) {
393                self.compound_state.constrained = false;
394                if !self.compound_state.in_relative_selector {
395                    self.compound_state.relative_selector_found = true;
396                }
397                return false;
398            }
399        }
400        self.compound_state.constrained |= all_constrained;
401        true
402    }
403
404    fn visit_relative_selector_list(&mut self, list: &[RelativeSelector<Self::Impl>]) -> bool {
405        debug_assert!(
406            !self.compound_state.in_relative_selector,
407            "Nested relative selector"
408        );
409        self.compound_state.relative_selector_found = true;
410
411        for rs in list {
412            // If the inside is unconstrained, we are unconstrained no matter what.
413            if Self::has_warning(&rs.selector, 0, true) {
414                self.compound_state.constrained = false;
415                return false;
416            }
417        }
418        true
419    }
420}
421
422struct SiblingCombinatorAfterScopeSelectorVisitor {
423    right_combinator_is_sibling: bool,
424    found: bool,
425}
426
427impl SiblingCombinatorAfterScopeSelectorVisitor {
428    fn new(right_combinator_is_sibling: bool) -> Self {
429        Self {
430            right_combinator_is_sibling,
431            found: false,
432        }
433    }
434    fn has_warning(selector: &Selector<SelectorImpl>) -> bool {
435        if !selector.has_scope_selector() {
436            return false;
437        }
438        let visitor = SiblingCombinatorAfterScopeSelectorVisitor::new(false);
439        visitor.find_never_matching_scope_selector(selector)
440    }
441
442    fn find_never_matching_scope_selector(mut self, selector: &Selector<SelectorImpl>) -> bool {
443        selector.visit(&mut self);
444        self.found
445    }
446}
447
448impl SelectorVisitor for SiblingCombinatorAfterScopeSelectorVisitor {
449    type Impl = SelectorImpl;
450
451    fn visit_simple_selector(&mut self, c: &Component<Self::Impl>) -> bool {
452        if !matches!(c, Component::Scope | Component::ImplicitScope) {
453            return true;
454        }
455        // e.g. `:scope ~ .a` will never match.
456        if self.right_combinator_is_sibling {
457            self.found = true;
458        }
459        true
460    }
461
462    fn visit_selector_list(
463        &mut self,
464        _list_kind: SelectorListKind,
465        list: &[Selector<Self::Impl>],
466    ) -> bool {
467        for s in list {
468            let list_visitor = Self::new(self.right_combinator_is_sibling);
469            self.found |= list_visitor.find_never_matching_scope_selector(s);
470        }
471        true
472    }
473
474    fn visit_complex_selector(&mut self, combinator_to_right: Option<Combinator>) -> bool {
475        if let Some(c) = combinator_to_right {
476            // Subject compounds' state is determined by the outer visitor. e.g: When there's `:is(.a .b) ~ .c`,
477            // the inner visitor is assumed to be constructed with right_combinator_is_sibling == true.
478            self.right_combinator_is_sibling = c.is_sibling();
479        }
480        true
481    }
482
483    // It's harder to discern if use of :scope <sibling-combinator> is invalid - at least for now, defer.
484}