summaryrefslogtreecommitdiffstats
path: root/servo/components/style/error_reporting.rs
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/error_reporting.rs')
-rw-r--r--servo/components/style/error_reporting.rs454
1 files changed, 454 insertions, 0 deletions
diff --git a/servo/components/style/error_reporting.rs b/servo/components/style/error_reporting.rs
new file mode 100644
index 0000000000..6db4c18e27
--- /dev/null
+++ b/servo/components/style/error_reporting.rs
@@ -0,0 +1,454 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! Types used to report parsing errors.
+
+#![deny(missing_docs)]
+
+use crate::selector_parser::SelectorImpl;
+use crate::stylesheets::UrlExtraData;
+use cssparser::{BasicParseErrorKind, ParseErrorKind, SourceLocation, Token};
+use selectors::parser::{Component, RelativeSelector, Selector};
+use selectors::visitor::{SelectorListKind, SelectorVisitor};
+use selectors::SelectorList;
+use std::fmt;
+use style_traits::ParseError;
+
+/// Errors that can be encountered while parsing CSS.
+#[derive(Debug)]
+pub enum ContextualParseError<'a> {
+ /// A property declaration was not recognized.
+ UnsupportedPropertyDeclaration(&'a str, ParseError<'a>, &'a [SelectorList<SelectorImpl>]),
+ /// A property descriptor was not recognized.
+ UnsupportedPropertyDescriptor(&'a str, ParseError<'a>),
+ /// A font face descriptor was not recognized.
+ UnsupportedFontFaceDescriptor(&'a str, ParseError<'a>),
+ /// A font feature values descriptor was not recognized.
+ UnsupportedFontFeatureValuesDescriptor(&'a str, ParseError<'a>),
+ /// A font palette values descriptor was not recognized.
+ UnsupportedFontPaletteValuesDescriptor(&'a str, ParseError<'a>),
+ /// A keyframe rule was not valid.
+ InvalidKeyframeRule(&'a str, ParseError<'a>),
+ /// A font feature values rule was not valid.
+ InvalidFontFeatureValuesRule(&'a str, ParseError<'a>),
+ /// A keyframe property declaration was not recognized.
+ UnsupportedKeyframePropertyDeclaration(&'a str, ParseError<'a>),
+ /// A rule was invalid for some reason.
+ InvalidRule(&'a str, ParseError<'a>),
+ /// A rule was not recognized.
+ UnsupportedRule(&'a str, ParseError<'a>),
+ /// A viewport descriptor declaration was not recognized.
+ UnsupportedViewportDescriptorDeclaration(&'a str, ParseError<'a>),
+ /// A counter style descriptor declaration was not recognized.
+ UnsupportedCounterStyleDescriptorDeclaration(&'a str, ParseError<'a>),
+ /// A counter style rule had no symbols.
+ InvalidCounterStyleWithoutSymbols(String),
+ /// A counter style rule had less than two symbols.
+ InvalidCounterStyleNotEnoughSymbols(String),
+ /// A counter style rule did not have additive-symbols.
+ InvalidCounterStyleWithoutAdditiveSymbols,
+ /// A counter style rule had extends with symbols.
+ InvalidCounterStyleExtendsWithSymbols,
+ /// A counter style rule had extends with additive-symbols.
+ InvalidCounterStyleExtendsWithAdditiveSymbols,
+ /// A media rule was invalid for some reason.
+ InvalidMediaRule(&'a str, ParseError<'a>),
+ /// A value was not recognized.
+ UnsupportedValue(&'a str, ParseError<'a>),
+ /// A never-matching `:host` selector was found.
+ NeverMatchingHostSelector(String),
+}
+
+impl<'a> fmt::Display for ContextualParseError<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fn token_to_str(t: &Token, f: &mut fmt::Formatter) -> fmt::Result {
+ match *t {
+ Token::Ident(ref i) => write!(f, "identifier {}", i),
+ Token::AtKeyword(ref kw) => write!(f, "keyword @{}", kw),
+ Token::Hash(ref h) => write!(f, "hash #{}", h),
+ Token::IDHash(ref h) => write!(f, "id selector #{}", h),
+ Token::QuotedString(ref s) => write!(f, "quoted string \"{}\"", s),
+ Token::UnquotedUrl(ref u) => write!(f, "url {}", u),
+ Token::Delim(ref d) => write!(f, "delimiter {}", d),
+ Token::Number {
+ int_value: Some(i), ..
+ } => write!(f, "number {}", i),
+ Token::Number { value, .. } => write!(f, "number {}", value),
+ Token::Percentage {
+ int_value: Some(i), ..
+ } => write!(f, "percentage {}", i),
+ Token::Percentage { unit_value, .. } => {
+ write!(f, "percentage {}", unit_value * 100.)
+ },
+ Token::Dimension {
+ value, ref unit, ..
+ } => write!(f, "dimension {}{}", value, unit),
+ Token::WhiteSpace(_) => write!(f, "whitespace"),
+ Token::Comment(_) => write!(f, "comment"),
+ Token::Colon => write!(f, "colon (:)"),
+ Token::Semicolon => write!(f, "semicolon (;)"),
+ Token::Comma => write!(f, "comma (,)"),
+ Token::IncludeMatch => write!(f, "include match (~=)"),
+ Token::DashMatch => write!(f, "dash match (|=)"),
+ Token::PrefixMatch => write!(f, "prefix match (^=)"),
+ Token::SuffixMatch => write!(f, "suffix match ($=)"),
+ Token::SubstringMatch => write!(f, "substring match (*=)"),
+ Token::CDO => write!(f, "CDO (<!--)"),
+ Token::CDC => write!(f, "CDC (-->)"),
+ Token::Function(ref name) => write!(f, "function {}", name),
+ Token::ParenthesisBlock => write!(f, "parenthesis ("),
+ Token::SquareBracketBlock => write!(f, "square bracket ["),
+ Token::CurlyBracketBlock => write!(f, "curly bracket {{"),
+ Token::BadUrl(ref _u) => write!(f, "bad url parse error"),
+ Token::BadString(ref _s) => write!(f, "bad string parse error"),
+ Token::CloseParenthesis => write!(f, "unmatched close parenthesis"),
+ Token::CloseSquareBracket => write!(f, "unmatched close square bracket"),
+ Token::CloseCurlyBracket => write!(f, "unmatched close curly bracket"),
+ }
+ }
+
+ fn parse_error_to_str(err: &ParseError, f: &mut fmt::Formatter) -> fmt::Result {
+ match err.kind {
+ ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(ref t)) => {
+ write!(f, "found unexpected ")?;
+ token_to_str(t, f)
+ },
+ ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput) => {
+ write!(f, "unexpected end of input")
+ },
+ ParseErrorKind::Basic(BasicParseErrorKind::AtRuleInvalid(ref i)) => {
+ write!(f, "@ rule invalid: {}", i)
+ },
+ ParseErrorKind::Basic(BasicParseErrorKind::AtRuleBodyInvalid) => {
+ write!(f, "@ rule invalid")
+ },
+ ParseErrorKind::Basic(BasicParseErrorKind::QualifiedRuleInvalid) => {
+ write!(f, "qualified rule invalid")
+ },
+ ParseErrorKind::Custom(ref err) => write!(f, "{:?}", err),
+ }
+ }
+
+ match *self {
+ ContextualParseError::UnsupportedPropertyDeclaration(decl, ref err, _selectors) => {
+ write!(f, "Unsupported property declaration: '{}', ", decl)?;
+ parse_error_to_str(err, f)
+ },
+ ContextualParseError::UnsupportedPropertyDescriptor(decl, ref err) => {
+ write!(
+ f,
+ "Unsupported @property descriptor declaration: '{}', ",
+ decl
+ )?;
+ parse_error_to_str(err, f)
+ },
+ ContextualParseError::UnsupportedFontFaceDescriptor(decl, ref err) => {
+ write!(
+ f,
+ "Unsupported @font-face descriptor declaration: '{}', ",
+ decl
+ )?;
+ parse_error_to_str(err, f)
+ },
+ ContextualParseError::UnsupportedFontFeatureValuesDescriptor(decl, ref err) => {
+ write!(
+ f,
+ "Unsupported @font-feature-values descriptor declaration: '{}', ",
+ decl
+ )?;
+ parse_error_to_str(err, f)
+ },
+ ContextualParseError::UnsupportedFontPaletteValuesDescriptor(decl, ref err) => {
+ write!(
+ f,
+ "Unsupported @font-palette-values descriptor declaration: '{}', ",
+ decl
+ )?;
+ parse_error_to_str(err, f)
+ },
+ ContextualParseError::InvalidKeyframeRule(rule, ref err) => {
+ write!(f, "Invalid keyframe rule: '{}', ", rule)?;
+ parse_error_to_str(err, f)
+ },
+ ContextualParseError::InvalidFontFeatureValuesRule(rule, ref err) => {
+ write!(f, "Invalid font feature value rule: '{}', ", rule)?;
+ parse_error_to_str(err, f)
+ },
+ ContextualParseError::UnsupportedKeyframePropertyDeclaration(decl, ref err) => {
+ write!(f, "Unsupported keyframe property declaration: '{}', ", decl)?;
+ parse_error_to_str(err, f)
+ },
+ ContextualParseError::InvalidRule(rule, ref err) => {
+ write!(f, "Invalid rule: '{}', ", rule)?;
+ parse_error_to_str(err, f)
+ },
+ ContextualParseError::UnsupportedRule(rule, ref err) => {
+ write!(f, "Unsupported rule: '{}', ", rule)?;
+ parse_error_to_str(err, f)
+ },
+ ContextualParseError::UnsupportedViewportDescriptorDeclaration(decl, ref err) => {
+ write!(
+ f,
+ "Unsupported @viewport descriptor declaration: '{}', ",
+ decl
+ )?;
+ parse_error_to_str(err, f)
+ },
+ ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(decl, ref err) => {
+ write!(
+ f,
+ "Unsupported @counter-style descriptor declaration: '{}', ",
+ decl
+ )?;
+ parse_error_to_str(err, f)
+ },
+ ContextualParseError::InvalidCounterStyleWithoutSymbols(ref system) => write!(
+ f,
+ "Invalid @counter-style rule: 'system: {}' without 'symbols'",
+ system
+ ),
+ ContextualParseError::InvalidCounterStyleNotEnoughSymbols(ref system) => write!(
+ f,
+ "Invalid @counter-style rule: 'system: {}' less than two 'symbols'",
+ system
+ ),
+ ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols => write!(
+ f,
+ "Invalid @counter-style rule: 'system: additive' without 'additive-symbols'"
+ ),
+ ContextualParseError::InvalidCounterStyleExtendsWithSymbols => write!(
+ f,
+ "Invalid @counter-style rule: 'system: extends …' with 'symbols'"
+ ),
+ ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols => write!(
+ f,
+ "Invalid @counter-style rule: 'system: extends …' with 'additive-symbols'"
+ ),
+ ContextualParseError::InvalidMediaRule(media_rule, ref err) => {
+ write!(f, "Invalid media rule: {}, ", media_rule)?;
+ parse_error_to_str(err, f)
+ },
+ ContextualParseError::UnsupportedValue(_value, ref err) => parse_error_to_str(err, f),
+ ContextualParseError::NeverMatchingHostSelector(ref selector) => {
+ write!(f, ":host selector is not featureless: {}", selector)
+ },
+ }
+ }
+}
+
+/// A generic trait for an error reporter.
+pub trait ParseErrorReporter {
+ /// Called when the style engine detects an error.
+ ///
+ /// Returns the current input being parsed, the source location it was
+ /// reported from, and a message.
+ fn report_error(
+ &self,
+ url: &UrlExtraData,
+ location: SourceLocation,
+ error: ContextualParseError,
+ );
+}
+
+/// An error reporter that uses [the `log` crate](https://github.com/rust-lang-nursery/log)
+/// at `info` level.
+///
+/// This logging is silent by default, and can be enabled with a `RUST_LOG=style=info`
+/// environment variable.
+/// (See [`env_logger`](https://rust-lang-nursery.github.io/log/env_logger/).)
+#[cfg(feature = "servo")]
+pub struct RustLogReporter;
+
+#[cfg(feature = "servo")]
+impl ParseErrorReporter for RustLogReporter {
+ fn report_error(
+ &self,
+ url: &UrlExtraData,
+ location: SourceLocation,
+ error: ContextualParseError,
+ ) {
+ if log_enabled!(log::Level::Info) {
+ info!(
+ "Url:\t{}\n{}:{} {}",
+ url.as_str(),
+ location.line,
+ location.column,
+ error
+ )
+ }
+ }
+}
+
+/// Any warning a selector may generate.
+/// TODO(dshin): Bug 1860634 - Merge with never matching host selector warning, which is part of the rule parser.
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+pub enum SelectorWarningKind {
+ /// Relative Selector with not enough constraint, either outside or inside the selector. e.g. `*:has(.a)`, `.a:has(*)`.
+ /// May cause expensive invalidations for every element inserted and/or removed.
+ UnconstraintedRelativeSelector,
+}
+
+impl SelectorWarningKind {
+ /// Get all warnings for this selector.
+ pub fn from_selector(selector: &Selector<SelectorImpl>) -> Vec<Self> {
+ let mut result = vec![];
+ if UnconstrainedRelativeSelectorVisitor::has_warning(selector, 0, false) {
+ result.push(SelectorWarningKind::UnconstraintedRelativeSelector);
+ }
+ result
+ }
+}
+
+/// Per-compound state for finding unconstrained relative selectors.
+struct PerCompoundState {
+ /// Is there a relative selector in this compound?
+ relative_selector_found: bool,
+ /// Is this compound constrained in any way?
+ constrained: bool,
+ /// Nested below, or inside relative selector?
+ in_relative_selector: bool,
+}
+
+impl PerCompoundState {
+ fn new(in_relative_selector: bool) -> Self {
+ Self {
+ relative_selector_found: false,
+ constrained: false,
+ in_relative_selector,
+ }
+ }
+}
+
+/// Visitor to check if there's any unconstrained relative selector.
+struct UnconstrainedRelativeSelectorVisitor {
+ compound_state: PerCompoundState,
+}
+
+impl UnconstrainedRelativeSelectorVisitor {
+ fn new(in_relative_selector: bool) -> Self {
+ Self {
+ compound_state: PerCompoundState::new(in_relative_selector),
+ }
+ }
+
+ fn has_warning(
+ selector: &Selector<SelectorImpl>,
+ offset: usize,
+ in_relative_selector: bool,
+ ) -> bool {
+ let relative_selector = matches!(
+ selector.iter_raw_parse_order_from(0).next().unwrap(),
+ Component::RelativeSelectorAnchor
+ );
+ debug_assert!(
+ !relative_selector || offset == 0,
+ "Checking relative selector from non-rightmost?"
+ );
+ let mut visitor = Self::new(in_relative_selector);
+ let mut iter = if relative_selector {
+ selector.iter_skip_relative_selector_anchor()
+ } else {
+ selector.iter_from(offset)
+ };
+ loop {
+ visitor.compound_state = PerCompoundState::new(in_relative_selector);
+
+ for s in &mut iter {
+ s.visit(&mut visitor);
+ }
+
+ if (visitor.compound_state.relative_selector_found ||
+ visitor.compound_state.in_relative_selector) &&
+ !visitor.compound_state.constrained
+ {
+ return true;
+ }
+
+ if iter.next_sequence().is_none() {
+ break;
+ }
+ }
+ false
+ }
+}
+
+impl SelectorVisitor for UnconstrainedRelativeSelectorVisitor {
+ type Impl = SelectorImpl;
+
+ fn visit_simple_selector(&mut self, c: &Component<Self::Impl>) -> bool {
+ match c {
+ // Deferred to visit_selector_list
+ Component::Is(..) |
+ Component::Where(..) |
+ Component::Negation(..) |
+ Component::Has(..) => (),
+ Component::ExplicitUniversalType => (),
+ _ => self.compound_state.constrained |= true,
+ };
+ true
+ }
+
+ fn visit_selector_list(
+ &mut self,
+ _list_kind: SelectorListKind,
+ list: &[Selector<Self::Impl>],
+ ) -> bool {
+ let mut all_constrained = true;
+ for s in list {
+ let mut offset = 0;
+ // First, check the rightmost compound for constraint at this level.
+ if !self.compound_state.in_relative_selector {
+ let mut nested = Self::new(false);
+ let mut iter = s.iter();
+ loop {
+ for c in &mut iter {
+ c.visit(&mut nested);
+ offset += 1;
+ }
+
+ let c = iter.next_sequence();
+ offset += 1;
+ if c.map_or(true, |c| !c.is_pseudo_element()) {
+ break;
+ }
+ }
+ // Every single selector in the list must be constrained.
+ all_constrained &= nested.compound_state.constrained;
+ }
+
+ if offset >= s.len() {
+ continue;
+ }
+
+ // Then, recurse in to check at the deeper level.
+ if Self::has_warning(s, offset, self.compound_state.in_relative_selector) {
+ self.compound_state.constrained = false;
+ if !self.compound_state.in_relative_selector {
+ self.compound_state.relative_selector_found = true;
+ }
+ return false;
+ }
+ }
+ self.compound_state.constrained |= all_constrained;
+ true
+ }
+
+ fn visit_relative_selector_list(&mut self, list: &[RelativeSelector<Self::Impl>]) -> bool {
+ debug_assert!(
+ !self.compound_state.in_relative_selector,
+ "Nested relative selector"
+ );
+ self.compound_state.relative_selector_found = true;
+
+ for rs in list {
+ // If the inside is unconstrained, we are unconstrained no matter what.
+ if Self::has_warning(&rs.selector, 0, true) {
+ self.compound_state.constrained = false;
+ return false;
+ }
+ }
+ true
+ }
+}