style: Fix parsing of :is() and :where() to account for constraints from parent selectors.

Differential Revision: https://phabricator.services.mozilla.com/D75856
This commit is contained in:
Emilio Cobos Álvarez 2020-05-20 12:16:22 +00:00
parent 7cbc963fc7
commit a457a2261b

View file

@ -104,41 +104,59 @@ bitflags! {
/// disallowed. If this flag is set, `AFTER_PSEUDO_ELEMENT` must be set /// disallowed. If this flag is set, `AFTER_PSEUDO_ELEMENT` must be set
/// as well. /// as well.
const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 4; const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 4;
/// Whether we are after any of the pseudo-like things. /// Whether we are after any of the pseudo-like things.
const AFTER_PSEUDO = Self::AFTER_PART.bits | Self::AFTER_SLOTTED.bits | Self::AFTER_PSEUDO_ELEMENT.bits; const AFTER_PSEUDO = Self::AFTER_PART.bits | Self::AFTER_SLOTTED.bits | Self::AFTER_PSEUDO_ELEMENT.bits;
/// Whether we explicitly disallow combinators.
const DISALLOW_COMBINATORS = 1 << 5;
/// Whether we explicitly disallow pseudo-element-like things.
const DISALLOW_PSEUDOS = 1 << 6;
} }
} }
impl SelectorParsingState { impl SelectorParsingState {
#[inline] #[inline]
fn allows_functional_pseudo_classes(self) -> bool { fn allows_pseudos(self) -> bool {
!self.intersects(SelectorParsingState::AFTER_PSEUDO) // NOTE(emilio): We allow pseudos after ::part and such.
!self.intersects(Self::AFTER_PSEUDO_ELEMENT | Self::DISALLOW_PSEUDOS)
} }
#[inline] #[inline]
fn allows_slotted(self) -> bool { fn allows_slotted(self) -> bool {
!self.intersects(SelectorParsingState::AFTER_PSEUDO) !self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS)
} }
// TODO(emilio): Should we allow other ::part()s after ::part()?
//
// See https://github.com/w3c/csswg-drafts/issues/3841
#[inline] #[inline]
fn allows_part(self) -> bool { fn allows_part(self) -> bool {
!self.intersects(SelectorParsingState::AFTER_PSEUDO) !self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS)
}
// TODO(emilio): Maybe some of these should be allowed, but this gets us on
// the safe side for now, matching previous behavior. Gotta be careful with
// the ones like :-moz-any, which allow nested selectors but don't carry the
// state, and so on.
#[inline]
fn allows_custom_functional_pseudo_classes(self) -> bool {
!self.intersects(Self::AFTER_PSEUDO)
} }
#[inline] #[inline]
fn allows_non_functional_pseudo_classes(self) -> bool { fn allows_non_functional_pseudo_classes(self) -> bool {
!self.intersects( !self.intersects(
SelectorParsingState::AFTER_SLOTTED | Self::AFTER_SLOTTED | Self::AFTER_NON_STATEFUL_PSEUDO_ELEMENT,
SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT,
) )
} }
#[inline] #[inline]
fn allows_tree_structural_pseudo_classes(self) -> bool { fn allows_tree_structural_pseudo_classes(self) -> bool {
!self.intersects(SelectorParsingState::AFTER_PSEUDO) !self.intersects(Self::AFTER_PSEUDO)
}
#[inline]
fn allows_combinators(self) -> bool {
!self.intersects(Self::DISALLOW_COMBINATORS)
} }
} }
@ -146,7 +164,6 @@ pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum SelectorParseErrorKind<'i> { pub enum SelectorParseErrorKind<'i> {
PseudoElementInComplexSelector,
NoQualifiedNameInAttributeSelector(Token<'i>), NoQualifiedNameInAttributeSelector(Token<'i>),
EmptySelector, EmptySelector,
DanglingCombinator, DanglingCombinator,
@ -321,6 +338,17 @@ impl<Impl: SelectorImpl> SelectorList<Impl> {
parser: &P, parser: &P,
input: &mut CssParser<'i, 't>, input: &mut CssParser<'i, 't>,
) -> Result<Self, ParseError<'i, P::Error>> ) -> Result<Self, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl = Impl>,
{
Self::parse_with_state(parser, input, SelectorParsingState::empty())
}
fn parse_with_state<'i, 't, P>(
parser: &P,
input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
) -> Result<Self, ParseError<'i, P::Error>>
where where
P: Parser<'i, Impl = Impl>, P: Parser<'i, Impl = Impl>,
{ {
@ -328,7 +356,7 @@ impl<Impl: SelectorImpl> SelectorList<Impl> {
loop { loop {
values.push( values.push(
input input
.parse_until_before(Delimiter::Comma, |input| parse_selector(parser, input))?, .parse_until_before(Delimiter::Comma, |input| parse_selector(parser, input, state))?,
); );
match input.next() { match input.next() {
Err(_) => return Ok(SelectorList(values)), Err(_) => return Ok(SelectorList(values)),
@ -344,30 +372,17 @@ impl<Impl: SelectorImpl> SelectorList<Impl> {
} }
} }
/// Parses one compound selector suitable for nested stuff like ::-moz-any, etc. /// Parses one compound selector suitable for nested stuff like :-moz-any, etc.
fn parse_inner_compound_selector<'i, 't, P, Impl>( fn parse_inner_compound_selector<'i, 't, P, Impl>(
parser: &P, parser: &P,
input: &mut CssParser<'i, 't>, input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
) -> Result<Selector<Impl>, ParseError<'i, P::Error>> ) -> Result<Selector<Impl>, ParseError<'i, P::Error>>
where where
P: Parser<'i, Impl = Impl>, P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl, Impl: SelectorImpl,
{ {
let location = input.current_source_location(); parse_selector(parser, input, state | SelectorParsingState::DISALLOW_PSEUDOS | SelectorParsingState::DISALLOW_COMBINATORS)
let selector = parse_selector(parser, input)?;
// Ensure they're actually all compound selectors without pseudo-elements.
if selector.has_pseudo_element() {
return Err(
location.new_custom_error(SelectorParseErrorKind::PseudoElementInComplexSelector)
);
}
if selector.iter_raw_match_order().any(|s| s.is_combinator()) {
return Err(location.new_custom_error(SelectorParseErrorKind::NonCompoundSelector));
}
Ok(selector)
} }
/// Parse a comma separated list of compound selectors. /// Parse a comma separated list of compound selectors.
@ -380,7 +395,7 @@ where
Impl: SelectorImpl, Impl: SelectorImpl,
{ {
input input
.parse_comma_separated(|input| parse_inner_compound_selector(parser, input)) .parse_comma_separated(|input| parse_inner_compound_selector(parser, input, SelectorParsingState::empty()))
.map(|selectors| selectors.into_boxed_slice()) .map(|selectors| selectors.into_boxed_slice())
} }
@ -1542,6 +1557,7 @@ fn display_to_css_identifier<T: Display, W: fmt::Write>(x: &T, dest: &mut W) ->
fn parse_selector<'i, 't, P, Impl>( fn parse_selector<'i, 't, P, Impl>(
parser: &P, parser: &P,
input: &mut CssParser<'i, 't>, input: &mut CssParser<'i, 't>,
mut state: SelectorParsingState,
) -> Result<Selector<Impl>, ParseError<'i, P::Error>> ) -> Result<Selector<Impl>, ParseError<'i, P::Error>>
where where
P: Parser<'i, Impl = Impl>, P: Parser<'i, Impl = Impl>,
@ -1554,16 +1570,14 @@ where
let mut part = false; let mut part = false;
'outer_loop: loop { 'outer_loop: loop {
// Parse a sequence of simple selectors. // Parse a sequence of simple selectors.
let state = match parse_compound_selector(parser, input, &mut builder)? { let empty = parse_compound_selector(parser, &mut state, input, &mut builder)?;
Some(state) => state, if empty {
None => { return Err(input.new_custom_error(if builder.has_combinators() {
return Err(input.new_custom_error(if builder.has_combinators() { SelectorParseErrorKind::DanglingCombinator
SelectorParseErrorKind::DanglingCombinator } else {
} else { SelectorParseErrorKind::EmptySelector
SelectorParseErrorKind::EmptySelector }));
})); }
},
};
if state.intersects(SelectorParsingState::AFTER_PSEUDO) { if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
has_pseudo_element = state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT); has_pseudo_element = state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
@ -1604,6 +1618,11 @@ where
}, },
} }
} }
if !state.allows_combinators() {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
builder.push_combinator(combinator); builder.push_combinator(combinator);
} }
@ -1620,7 +1639,7 @@ impl<Impl: SelectorImpl> Selector<Impl> {
where where
P: Parser<'i, Impl = Impl>, P: Parser<'i, Impl = Impl>,
{ {
parse_selector(parser, input) parse_selector(parser, input, SelectorParsingState::empty())
} }
} }
@ -1630,6 +1649,7 @@ impl<Impl: SelectorImpl> Selector<Impl> {
fn parse_type_selector<'i, 't, P, Impl, S>( fn parse_type_selector<'i, 't, P, Impl, S>(
parser: &P, parser: &P,
input: &mut CssParser<'i, 't>, input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
sink: &mut S, sink: &mut S,
) -> Result<bool, ParseError<'i, P::Error>> ) -> Result<bool, ParseError<'i, P::Error>>
where where
@ -1644,6 +1664,9 @@ where
}) | }) |
Ok(OptionalQName::None(_)) => Ok(false), Ok(OptionalQName::None(_)) => Ok(false),
Ok(OptionalQName::Some(namespace, local_name)) => { Ok(OptionalQName::Some(namespace, local_name)) => {
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
match namespace { match namespace {
QNamePrefix::ImplicitAnyNamespace => {}, QNamePrefix::ImplicitAnyNamespace => {},
QNamePrefix::ImplicitDefaultNamespace(url) => { QNamePrefix::ImplicitDefaultNamespace(url) => {
@ -2015,11 +2038,14 @@ fn parse_attribute_flags<'i, 't>(
fn parse_negation<'i, 't, P, Impl>( fn parse_negation<'i, 't, P, Impl>(
parser: &P, parser: &P,
input: &mut CssParser<'i, 't>, input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
) -> Result<Component<Impl>, ParseError<'i, P::Error>> ) -> Result<Component<Impl>, ParseError<'i, P::Error>>
where where
P: Parser<'i, Impl = Impl>, P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl, Impl: SelectorImpl,
{ {
let state = state | SelectorParsingState::INSIDE_NEGATION;
// We use a sequence because a type selector may be represented as two Components. // We use a sequence because a type selector may be represented as two Components.
let mut sequence = SmallVec::<[Component<Impl>; 2]>::new(); let mut sequence = SmallVec::<[Component<Impl>; 2]>::new();
@ -2027,7 +2053,7 @@ where
// Get exactly one simple selector. The parse logic in the caller will verify // Get exactly one simple selector. The parse logic in the caller will verify
// that there are no trailing tokens after we're done. // that there are no trailing tokens after we're done.
let is_type_sel = match parse_type_selector(parser, input, &mut sequence) { let is_type_sel = match parse_type_selector(parser, input, state, &mut sequence) {
Ok(result) => result, Ok(result) => result,
Err(ParseError { Err(ParseError {
kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput), kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput),
@ -2036,7 +2062,7 @@ where
Err(e) => return Err(e.into()), Err(e) => return Err(e.into()),
}; };
if !is_type_sel { if !is_type_sel {
match parse_one_simple_selector(parser, input, SelectorParsingState::INSIDE_NEGATION)? { match parse_one_simple_selector(parser, input, state)? {
Some(SimpleSelectorParseResult::SimpleSelector(s)) => { Some(SimpleSelectorParseResult::SimpleSelector(s)) => {
sequence.push(s); sequence.push(s);
}, },
@ -2063,12 +2089,13 @@ where
/// | [ HASH | class | attrib | pseudo | negation ]+ /// | [ HASH | class | attrib | pseudo | negation ]+
/// ///
/// `Err(())` means invalid selector. /// `Err(())` means invalid selector.
/// `Ok(None)` is an empty selector /// `Ok(true)` is an empty selector
fn parse_compound_selector<'i, 't, P, Impl>( fn parse_compound_selector<'i, 't, P, Impl>(
parser: &P, parser: &P,
state: &mut SelectorParsingState,
input: &mut CssParser<'i, 't>, input: &mut CssParser<'i, 't>,
builder: &mut SelectorBuilder<Impl>, builder: &mut SelectorBuilder<Impl>,
) -> Result<Option<SelectorParsingState>, ParseError<'i, P::Error>> ) -> Result<bool, ParseError<'i, P::Error>>
where where
P: Parser<'i, Impl = Impl>, P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl, Impl: SelectorImpl,
@ -2076,13 +2103,12 @@ where
input.skip_whitespace(); input.skip_whitespace();
let mut empty = true; let mut empty = true;
if parse_type_selector(parser, input, builder)? { if parse_type_selector(parser, input, *state, builder)? {
empty = false; empty = false;
} }
let mut state = SelectorParsingState::empty();
loop { loop {
let result = match parse_one_simple_selector(parser, input, state)? { let result = match parse_one_simple_selector(parser, input, *state)? {
None => break, None => break,
Some(result) => result, Some(result) => result,
}; };
@ -2141,17 +2167,13 @@ where
}, },
} }
} }
if empty { Ok(empty)
// An empty selector is invalid.
Ok(None)
} else {
Ok(Some(state))
}
} }
fn parse_is_or_where<'i, 't, P, Impl>( fn parse_is_or_where<'i, 't, P, Impl>(
parser: &P, parser: &P,
input: &mut CssParser<'i, 't>, input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
component: impl FnOnce(Box<[Selector<Impl>]>) -> Component<Impl>, component: impl FnOnce(Box<[Selector<Impl>]>) -> Component<Impl>,
) -> Result<Component<Impl>, ParseError<'i, P::Error>> ) -> Result<Component<Impl>, ParseError<'i, P::Error>>
where where
@ -2159,15 +2181,12 @@ where
Impl: SelectorImpl, Impl: SelectorImpl,
{ {
debug_assert!(parser.parse_is_and_where()); debug_assert!(parser.parse_is_and_where());
let inner = SelectorList::parse(parser, input)?;
// https://drafts.csswg.org/selectors/#matches-pseudo: // https://drafts.csswg.org/selectors/#matches-pseudo:
// //
// Pseudo-elements cannot be represented by the matches-any // Pseudo-elements cannot be represented by the matches-any
// pseudo-class; they are not valid within :is(). // pseudo-class; they are not valid within :is().
// //
if inner.0.iter().any(|i| i.has_pseudo_element()) { let inner = SelectorList::parse_with_state(parser, input, state | SelectorParsingState::DISALLOW_PSEUDOS)?;
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidPseudoElementInsideWhere));
}
Ok(component(inner.0.into_vec().into_boxed_slice())) Ok(component(inner.0.into_vec().into_boxed_slice()))
} }
@ -2181,40 +2200,46 @@ where
P: Parser<'i, Impl = Impl>, P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl, Impl: SelectorImpl,
{ {
if !state.allows_functional_pseudo_classes() {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
debug_assert!(state.allows_tree_structural_pseudo_classes());
match_ignore_ascii_case! { &name, match_ignore_ascii_case! { &name,
"nth-child" => return Ok(parse_nth_pseudo_class(input, Component::NthChild)?), "nth-child" => return parse_nth_pseudo_class(parser, input, state, Component::NthChild),
"nth-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthOfType)?), "nth-of-type" => return parse_nth_pseudo_class(parser, input, state, Component::NthOfType),
"nth-last-child" => return Ok(parse_nth_pseudo_class(input, Component::NthLastChild)?), "nth-last-child" => return parse_nth_pseudo_class(parser, input, state, Component::NthLastChild),
"nth-last-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthLastOfType)?), "nth-last-of-type" => return parse_nth_pseudo_class(parser, input, state, Component::NthLastOfType),
"is" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, Component::Is), "is" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, state, Component::Is),
"where" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, Component::Where), "where" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, state, Component::Where),
"host" => return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input)?))), "host" => return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input, state)?))),
"not" => { "not" => {
if state.intersects(SelectorParsingState::INSIDE_NEGATION) { if state.intersects(SelectorParsingState::INSIDE_NEGATION) {
return Err(input.new_custom_error( return Err(input.new_custom_error(
SelectorParseErrorKind::UnexpectedIdent("not".into()) SelectorParseErrorKind::UnexpectedIdent("not".into())
)); ));
} }
debug_assert!(state.is_empty()); return parse_negation(parser, input, state)
return parse_negation(parser, input)
}, },
_ => {} _ => {}
} }
if !state.allows_custom_functional_pseudo_classes() {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
P::parse_non_ts_functional_pseudo_class(parser, name, input).map(Component::NonTSPseudoClass) P::parse_non_ts_functional_pseudo_class(parser, name, input).map(Component::NonTSPseudoClass)
} }
fn parse_nth_pseudo_class<'i, 't, Impl, F>( fn parse_nth_pseudo_class<'i, 't, P, Impl, F>(
_: &P,
input: &mut CssParser<'i, 't>, input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
selector: F, selector: F,
) -> Result<Component<Impl>, BasicParseError<'i>> ) -> Result<Component<Impl>, ParseError<'i, P::Error>>
where where
P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl, Impl: SelectorImpl,
F: FnOnce(i32, i32) -> Component<Impl>, F: FnOnce(i32, i32) -> Component<Impl>,
{ {
if !state.allows_tree_structural_pseudo_classes() {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
let (a, b) = parse_nth(input)?; let (a, b) = parse_nth(input)?;
Ok(selector(a, b)) Ok(selector(a, b))
} }
@ -2299,7 +2324,7 @@ where
}; };
let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(&name); let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(&name);
if is_pseudo_element { if is_pseudo_element {
if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT) { if !state.allows_pseudos() {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
} }
let pseudo_element = if is_functional { let pseudo_element = if is_functional {
@ -2326,7 +2351,7 @@ where
); );
} }
let selector = input.parse_nested_block(|input| { let selector = input.parse_nested_block(|input| {
parse_inner_compound_selector(parser, input) parse_inner_compound_selector(parser, input, state)
})?; })?;
return Ok(Some(SimpleSelectorParseResult::SlottedPseudo(selector))); return Ok(Some(SimpleSelectorParseResult::SlottedPseudo(selector)));
} }