mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
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:
parent
dcdf9f33d5
commit
3d0cf4dbf9
8 changed files with 85 additions and 10 deletions
|
@ -695,7 +695,9 @@ where
|
|||
Component::Slotted(ref selector) | Component::Host(Some(ref selector)) => {
|
||||
selector.size_of(ops)
|
||||
},
|
||||
Component::Is(ref list) | Component::Where(ref list) => list.size_of(ops),
|
||||
Component::Is(ref list) |
|
||||
Component::Where(ref list) |
|
||||
Component::Has(ref list) => list.size_of(ops),
|
||||
Component::PseudoElement(ref pseudo) => (*pseudo).size_of(ops),
|
||||
Component::Combinator(..) |
|
||||
Component::ExplicitAnyNamespace |
|
||||
|
|
|
@ -332,12 +332,14 @@ where
|
|||
Component::NonTSPseudoClass(..) => {
|
||||
specificity.class_like_selectors += 1;
|
||||
},
|
||||
Component::Negation(ref list) | Component::Is(ref list) => {
|
||||
Component::Negation(ref list) |
|
||||
Component::Is(ref list) |
|
||||
Component::Has(ref list) => {
|
||||
// https://drafts.csswg.org/selectors/#specificity-rules:
|
||||
//
|
||||
// The specificity of an :is() pseudo-class is replaced by the
|
||||
// specificity of the most specific complex selector in its
|
||||
// selector list argument.
|
||||
// The specificity of an :is(), :not(), or :has() pseudo-class
|
||||
// is replaced by the specificity of the most specific complex
|
||||
// selector in its selector list argument.
|
||||
let mut max = 0;
|
||||
for selector in &**list {
|
||||
max = std::cmp::max(selector.specificity(), max);
|
||||
|
|
|
@ -326,6 +326,32 @@ where
|
|||
matches!(result, SelectorMatchingResult::Matched)
|
||||
}
|
||||
|
||||
/// Traverse all descendents of the given element and return true as soon as any of them match
|
||||
/// the given list of selectors.
|
||||
fn has_children_matching<E: Element>(
|
||||
selectors: &[Selector<E::Impl>],
|
||||
element: &E,
|
||||
context: &mut MatchingContext<E::Impl>,
|
||||
) -> bool {
|
||||
let mut current = element.first_element_child();
|
||||
|
||||
while let Some(el) = current {
|
||||
for selector in selectors {
|
||||
if matches_complex_selector(selector.iter(), &el, context) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if has_children_matching(selectors, &el, context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
current = el.next_sibling_element();
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Whether the :hover and :active quirk applies.
|
||||
///
|
||||
/// https://quirks.spec.whatwg.org/#the-active-and-hover-quirk
|
||||
|
@ -833,6 +859,9 @@ where
|
|||
}
|
||||
true
|
||||
}),
|
||||
Component::Has(ref list) => context.shared.nest(|context| {
|
||||
has_children_matching(list, element, context)
|
||||
}),
|
||||
Component::Combinator(_) => unsafe {
|
||||
debug_unreachable!("Shouldn't try to selector-match combinators")
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -60,6 +60,9 @@ pub trait Element: Sized + Clone + Debug {
|
|||
/// Skips non-element nodes
|
||||
fn next_sibling_element(&self) -> Option<Self>;
|
||||
|
||||
/// Skips non-element nodes
|
||||
fn first_element_child(&self) -> Option<Self>;
|
||||
|
||||
fn is_html_element_in_html_document(&self) -> bool;
|
||||
|
||||
fn has_local_name(&self, local_name: &<Self::Impl as SelectorImpl>::BorrowedLocalName) -> bool;
|
||||
|
|
|
@ -305,6 +305,11 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
|
|||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn parse_has(&self) -> bool {
|
||||
static_prefs::pref!("layout.css.has-selector.enabled")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn parse_part(&self) -> bool {
|
||||
true
|
||||
|
|
|
@ -1844,6 +1844,18 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
|||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn first_element_child(&self) -> Option<Self> {
|
||||
let mut child = self.as_node().first_child();
|
||||
while let Some(child_node) = child {
|
||||
if let Some(el) = child_node.as_element() {
|
||||
return Some(el);
|
||||
}
|
||||
child = child_node.next_sibling();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn set_selector_flags(&self, flags: ElementSelectorFlags) {
|
||||
debug_assert!(!flags.is_empty());
|
||||
self.set_flags(selector_flags_to_node_flags(flags));
|
||||
|
|
|
@ -285,6 +285,11 @@ where
|
|||
Some(Self::new(sibling, self.snapshot_map))
|
||||
}
|
||||
|
||||
fn first_element_child(&self) -> Option<Self> {
|
||||
let child = self.element.first_element_child()?;
|
||||
Some(Self::new(child, self.snapshot_map))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_html_element_in_html_document(&self) -> bool {
|
||||
self.element.is_html_element_in_html_document()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue