style: Add simple parsing and matching support for :has

Parsing is behind a config value `layout.css.has-selectors.enabled`. This
change does not support p:has(> a) combinators, but will handle them
gracefully, just not matching on them.

Differential Revision: https://phabricator.services.mozilla.com/D149515
This commit is contained in:
Tiaan Louw 2022-06-20 08:53:02 +00:00 committed by Martin Robinson
parent dcdf9f33d5
commit 3d0cf4dbf9
8 changed files with 85 additions and 10 deletions

View file

@ -261,6 +261,11 @@ pub trait Parser<'i> {
false
}
/// Whether to parse the :has pseudo-class.
fn parse_has(&self) -> bool {
false
}
/// Whether the given function name is an alias for the `:is()` function.
fn is_is_alias(&self, _name: &str) -> bool {
false
@ -1116,6 +1121,12 @@ pub enum Component<Impl: SelectorImpl> {
///
/// Same comment as above re. the argument.
Is(Box<[Selector<Impl>]>),
/// The `:has` pseudo-class.
///
/// https://drafts.csswg.org/selectors/#has-pseudo
///
/// Same comment as above re. the argument.
Has(Box<[Selector<Impl>]>),
/// An implementation-dependent pseudo-element selector.
PseudoElement(#[cfg_attr(feature = "shmem", shmem(field_bound))] Impl::PseudoElement),
@ -1589,11 +1600,12 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> {
write_affine(dest, a, b)?;
dest.write_char(')')
},
Is(ref list) | Where(ref list) | Negation(ref list) => {
Is(ref list) | Where(ref list) | Negation(ref list) | Has(ref list) => {
match *self {
Where(..) => dest.write_str(":where(")?,
Is(..) => dest.write_str(":is(")?,
Negation(..) => dest.write_str(":not(")?,
Has(..) => dest.write_str(":has(")?,
_ => unreachable!(),
}
serialize_selector_list(list.iter(), dest)?;
@ -2253,7 +2265,7 @@ where
Ok(empty)
}
fn parse_is_or_where<'i, 't, P, Impl>(
fn parse_is_where_has<'i, 't, P, Impl>(
parser: &P,
input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
@ -2295,8 +2307,9 @@ where
"nth-of-type" => return parse_nth_pseudo_class(parser, input, state, Component::NthOfType),
"nth-last-child" => return parse_nth_pseudo_class(parser, input, state, Component::NthLastChild),
"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, state, Component::Is),
"where" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, state, Component::Where),
"is" if parser.parse_is_and_where() => return parse_is_where_has(parser, input, state, Component::Is),
"where" if parser.parse_is_and_where() => return parse_is_where_has(parser, input, state, Component::Where),
"has" if parser.parse_has() => return parse_is_where_has(parser, input, state, Component::Has),
"host" => {
if !state.allows_tree_structural_pseudo_classes() {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
@ -2310,7 +2323,7 @@ where
}
if parser.parse_is_and_where() && parser.is_is_alias(&name) {
return parse_is_or_where(parser, input, state, Component::Is);
return parse_is_where_has(parser, input, state, Component::Is);
}
if !state.allows_custom_functional_pseudo_classes() {
@ -2684,6 +2697,10 @@ pub mod tests {
true
}
fn parse_has(&self) -> bool {
true
}
fn parse_part(&self) -> bool {
true
}