From 90a2687545b8c06fc311ba250cd7db7858b5b297 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Wed, 17 May 2023 00:28:18 +0200 Subject: [PATCH] style: Add a CSS error to the console when using non-featureless :host selectors (which would never match by definition). Differential Revision: https://phabricator.services.mozilla.com/D111610 --- components/selectors/parser.rs | 9 ++++- components/style/error_reporting.rs | 5 +++ components/style/stylesheets/rule_parser.rs | 40 +++++++++++++++++++-- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index 3c728116e97..0b8d7d2331e 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -859,7 +859,7 @@ impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> { #[inline] pub(crate) fn is_featureless_host_selector(&mut self) -> bool { self.selector_length() > 0 && - self.all(|component| matches!(*component, Component::Host(..))) && + self.all(|component| component.is_host()) && self.next_sequence().is_none() } @@ -1123,10 +1123,17 @@ pub enum Component { impl Component { /// Returns true if this is a combinator. + #[inline] pub fn is_combinator(&self) -> bool { matches!(*self, Component::Combinator(_)) } + /// Returns true if this is a :host() selector. + #[inline] + pub fn is_host(&self) -> bool { + matches!(*self, Component::Host(..)) + } + /// Returns the value as a combinator if applicable, None otherwise. pub fn as_combinator(&self) -> Option { match *self { diff --git a/components/style/error_reporting.rs b/components/style/error_reporting.rs index 6e63b0ad68a..eebb0901309 100644 --- a/components/style/error_reporting.rs +++ b/components/style/error_reporting.rs @@ -54,6 +54,8 @@ pub enum ContextualParseError<'a> { 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> { @@ -210,6 +212,9 @@ impl<'a> fmt::Display for ContextualParseError<'a> { 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) + } } } } diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 524d54d9c33..914af2cfb66 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -53,7 +53,7 @@ pub struct TopLevelRuleParser<'a> { /// This won't contain any namespaces, and only nested parsers created with /// `ParserContext::new_with_rule_type` will. pub context: ParserContext<'a>, - /// The current stajkj/te of the parser. + /// The current state of the parser. pub state: State, /// Whether we have tried to parse was invalid due to being in the wrong /// place (e.g. an @import rule was found while in the `Body` state). Reset @@ -587,6 +587,38 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { } } +#[inline(never)] +fn check_for_useless_selector( + input: &mut Parser, + context: &ParserContext, + selectors: &SelectorList, +) { + use cssparser::ToCss; + + 'selector_loop: for selector in selectors.0.iter() { + let mut current = selector.iter(); + loop { + let mut found_host = false; + let mut found_non_host = false; + for component in &mut current { + if component.is_host() { + found_host = true; + } else { + found_non_host = true; + } + if found_host && found_non_host { + let location = input.current_source_location(); + context.log_css_error(location, ContextualParseError::NeverMatchingHostSelector(selector.to_css_string())); + continue 'selector_loop; + } + } + if current.next_sequence().is_none() { + break; + } + } + } +} + impl<'a, 'b, 'i> QualifiedRuleParser<'i> for NestedRuleParser<'a, 'b> { type Prelude = SelectorList; type QualifiedRule = CssRule; @@ -601,7 +633,11 @@ impl<'a, 'b, 'i> QualifiedRuleParser<'i> for NestedRuleParser<'a, 'b> { namespaces: self.namespaces, url_data: Some(self.context.url_data), }; - SelectorList::parse(&selector_parser, input) + let selectors = SelectorList::parse(&selector_parser, input)?; + if self.context.error_reporting_enabled() { + check_for_useless_selector(input, &self.context, &selectors); + } + Ok(selectors) } fn parse_block<'t>(