style: Allow :is() and :where() to have better error recovery.

Adjust is-where-parsing.html to work with both the new and old behavior,
and add a test for the new behavior.

Depends on D90049

Differential Revision: https://phabricator.services.mozilla.com/D90050
This commit is contained in:
Emilio Cobos Álvarez 2020-09-14 15:04:22 +00:00
parent 89c2e39dd9
commit 5980525139

View file

@ -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<Impl: SelectorImpl>(
#[shmem(field_bound)] pub SmallVec<[Selector<Impl>; 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<Impl: SelectorImpl> SelectorList<Impl> {
/// Parse a comma-separated list of Selectors.
/// <https://drafts.csswg.org/selectors/#grouping>
@ -341,26 +357,42 @@ impl<Impl: SelectorImpl> SelectorList<Impl> {
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<Self, ParseError<'i, P::Error>>
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<Impl: SelectorImpl> Debug for LocalName<Impl> {
}
}
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<Item = &'a Selector<Impl>>,
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()))
}