diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index da87b9bb08f..4416461f46e 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -251,6 +251,11 @@ pub trait Parser<'i> { false } + /// The error recovery that selector lists inside :is() and :where() have. + fn is_and_where_error_recovery(&self) -> ParseErrorRecovery { + ParseErrorRecovery::IgnoreInvalidSelector + } + /// Whether the given function name is an alias for the `:is()` function. fn is_is_alias(&self, _name: &str) -> bool { false @@ -329,6 +334,17 @@ pub struct SelectorList( #[shmem(field_bound)] pub SmallVec<[Selector; 1]>, ); +/// How to treat invalid selectors in a selector list. +pub enum ParseErrorRecovery { + /// Discard the entire selector list, this is the default behavior for + /// almost all of CSS. + DiscardList, + /// Ignore invalid selectors, potentially creating an empty selector list. + /// + /// This is the error recovery mode of :is() and :where() + IgnoreInvalidSelector, +} + impl SelectorList { /// Parse a comma-separated list of Selectors. /// @@ -341,26 +357,42 @@ impl SelectorList { where P: Parser<'i, Impl = Impl>, { - Self::parse_with_state(parser, input, SelectorParsingState::empty()) + Self::parse_with_state(parser, input, SelectorParsingState::empty(), ParseErrorRecovery::DiscardList) } + #[inline] fn parse_with_state<'i, 't, P>( parser: &P, input: &mut CssParser<'i, 't>, state: SelectorParsingState, + recovery: ParseErrorRecovery, ) -> Result> where P: Parser<'i, Impl = Impl>, { let mut values = SmallVec::new(); loop { - values.push(input.parse_until_before(Delimiter::Comma, |input| { + let selector = input.parse_until_before(Delimiter::Comma, |input| { parse_selector(parser, input, state) - })?); - match input.next() { - Err(_) => return Ok(SelectorList(values)), - Ok(&Token::Comma) => continue, - Ok(_) => unreachable!(), + }); + + let was_ok = selector.is_ok(); + match selector { + Ok(selector) => values.push(selector), + Err(err) => match recovery { + ParseErrorRecovery::DiscardList => return Err(err), + ParseErrorRecovery::IgnoreInvalidSelector => {}, + }, + } + + loop { + match input.next() { + Err(_) => return Ok(SelectorList(values)), + Ok(&Token::Comma) => break, + Ok(_) => { + debug_assert!(!was_ok, "Shouldn't have got a selector if getting here"); + } + } } } } @@ -1251,18 +1283,18 @@ impl Debug for LocalName { } } -fn serialize_selector_list<'a, Impl, I, W>(mut iter: I, dest: &mut W) -> fmt::Result +fn serialize_selector_list<'a, Impl, I, W>(iter: I, dest: &mut W) -> fmt::Result where Impl: SelectorImpl, I: Iterator>, W: fmt::Write, { - let first = iter - .next() - .expect("Empty SelectorList, should contain at least one selector"); - first.to_css(dest)?; + let mut first = true; for selector in iter { - dest.write_str(", ")?; + if !first { + dest.write_str(", ")?; + } + first = false; selector.to_css(dest)?; } Ok(()) @@ -2266,6 +2298,7 @@ where parser, input, state | SelectorParsingState::DISALLOW_PSEUDOS, + parser.is_and_where_error_recovery(), )?; Ok(component(inner.0.into_vec().into_boxed_slice())) } @@ -2658,6 +2691,10 @@ pub mod tests { true } + fn is_and_where_error_recovery(&self) -> ParseErrorRecovery { + ParseErrorRecovery::DiscardList + } + fn parse_part(&self) -> bool { true }