mirror of
https://github.com/servo/servo.git
synced 2025-08-05 21:50:18 +01:00
style: :has
relative selector matching, with no caching/filtering
Differential Revision: https://phabricator.services.mozilla.com/D172019
This commit is contained in:
parent
a68766197f
commit
3756e3b027
4 changed files with 334 additions and 64 deletions
|
@ -695,9 +695,8 @@ where
|
||||||
Component::Slotted(ref selector) | Component::Host(Some(ref selector)) => {
|
Component::Slotted(ref selector) | Component::Host(Some(ref selector)) => {
|
||||||
selector.size_of(ops)
|
selector.size_of(ops)
|
||||||
},
|
},
|
||||||
Component::Is(ref list) | Component::Where(ref list) | Component::Has(ref list) => {
|
Component::Is(ref list) | Component::Where(ref list) => list.size_of(ops),
|
||||||
list.size_of(ops)
|
Component::Has(ref relative_selectors) => relative_selectors.size_of(ops),
|
||||||
},
|
|
||||||
Component::NthOf(ref nth_of_data) => nth_of_data.size_of(ops),
|
Component::NthOf(ref nth_of_data) => nth_of_data.size_of(ops),
|
||||||
Component::PseudoElement(ref pseudo) => (*pseudo).size_of(ops),
|
Component::PseudoElement(ref pseudo) => (*pseudo).size_of(ops),
|
||||||
Component::Combinator(..) |
|
Component::Combinator(..) |
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
//! is non-trivial. This module encapsulates those details and presents an
|
//! is non-trivial. This module encapsulates those details and presents an
|
||||||
//! easy-to-use API for the parser.
|
//! easy-to-use API for the parser.
|
||||||
|
|
||||||
use crate::parser::{Combinator, Component, Selector, SelectorImpl};
|
use crate::parser::{Combinator, Component, RelativeSelector, Selector, SelectorImpl};
|
||||||
use crate::sink::Push;
|
use crate::sink::Push;
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use derive_more::{Add, AddAssign};
|
use derive_more::{Add, AddAssign};
|
||||||
|
@ -337,25 +337,29 @@ where
|
||||||
// specificity of a regular pseudo-class with that of its
|
// specificity of a regular pseudo-class with that of its
|
||||||
// selector argument S.
|
// selector argument S.
|
||||||
specificity.class_like_selectors += 1;
|
specificity.class_like_selectors += 1;
|
||||||
let sf = selector_list_specificity_and_flags(nth_of_data.selectors());
|
let sf = selector_list_specificity_and_flags(nth_of_data.selectors().iter());
|
||||||
*specificity += Specificity::from(sf.specificity);
|
*specificity += Specificity::from(sf.specificity);
|
||||||
flags.insert(sf.flags);
|
flags.insert(sf.flags);
|
||||||
},
|
},
|
||||||
Component::Where(ref list) |
|
|
||||||
Component::Negation(ref list) |
|
|
||||||
Component::Is(ref list) |
|
|
||||||
Component::Has(ref list) => {
|
|
||||||
// https://drafts.csswg.org/selectors/#specificity-rules:
|
// https://drafts.csswg.org/selectors/#specificity-rules:
|
||||||
//
|
//
|
||||||
// The specificity of an :is(), :not(), or :has() pseudo-class
|
// The specificity of an :is(), :not(), or :has() pseudo-class
|
||||||
// is replaced by the specificity of the most specific complex
|
// is replaced by the specificity of the most specific complex
|
||||||
// selector in its selector list argument.
|
// selector in its selector list argument.
|
||||||
let sf = selector_list_specificity_and_flags(list);
|
Component::Where(ref list) |
|
||||||
|
Component::Negation(ref list) |
|
||||||
|
Component::Is(ref list) => {
|
||||||
|
let sf = selector_list_specificity_and_flags(list.iter());
|
||||||
if !matches!(*simple_selector, Component::Where(..)) {
|
if !matches!(*simple_selector, Component::Where(..)) {
|
||||||
*specificity += Specificity::from(sf.specificity);
|
*specificity += Specificity::from(sf.specificity);
|
||||||
}
|
}
|
||||||
flags.insert(sf.flags);
|
flags.insert(sf.flags);
|
||||||
},
|
},
|
||||||
|
Component::Has(ref relative_selectors) => {
|
||||||
|
let sf = relative_selector_list_specificity_and_flags(relative_selectors);
|
||||||
|
*specificity += Specificity::from(sf.specificity);
|
||||||
|
flags.insert(sf.flags);
|
||||||
|
},
|
||||||
Component::ExplicitUniversalType |
|
Component::ExplicitUniversalType |
|
||||||
Component::ExplicitAnyNamespace |
|
Component::ExplicitAnyNamespace |
|
||||||
Component::ExplicitNoNamespace |
|
Component::ExplicitNoNamespace |
|
||||||
|
@ -379,12 +383,13 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds the maximum specificity of elements in the list and returns it.
|
/// Finds the maximum specificity of elements in the list and returns it.
|
||||||
pub(crate) fn selector_list_specificity_and_flags<Impl: SelectorImpl>(
|
pub(crate) fn selector_list_specificity_and_flags<'a, Impl: SelectorImpl>(
|
||||||
list: &[Selector<Impl>],
|
itr: impl Iterator<Item = &'a Selector<Impl>>,
|
||||||
) -> SpecificityAndFlags {
|
) -> SpecificityAndFlags
|
||||||
|
{
|
||||||
let mut specificity = 0;
|
let mut specificity = 0;
|
||||||
let mut flags = SelectorFlags::empty();
|
let mut flags = SelectorFlags::empty();
|
||||||
for selector in list.iter() {
|
for selector in itr {
|
||||||
specificity = std::cmp::max(specificity, selector.specificity());
|
specificity = std::cmp::max(specificity, selector.specificity());
|
||||||
if selector.has_parent_selector() {
|
if selector.has_parent_selector() {
|
||||||
flags.insert(SelectorFlags::HAS_PARENT);
|
flags.insert(SelectorFlags::HAS_PARENT);
|
||||||
|
@ -392,3 +397,9 @@ pub(crate) fn selector_list_specificity_and_flags<Impl: SelectorImpl>(
|
||||||
}
|
}
|
||||||
SpecificityAndFlags { specificity, flags }
|
SpecificityAndFlags { specificity, flags }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn relative_selector_list_specificity_and_flags<Impl: SelectorImpl>(
|
||||||
|
list: &[RelativeSelector<Impl>],
|
||||||
|
) -> SpecificityAndFlags {
|
||||||
|
selector_list_specificity_and_flags(list.iter().map(|rel| &rel.selector))
|
||||||
|
}
|
||||||
|
|
|
@ -7,8 +7,12 @@ use crate::attr::{
|
||||||
ParsedCaseSensitivity,
|
ParsedCaseSensitivity,
|
||||||
};
|
};
|
||||||
use crate::bloom::{BloomFilter, BLOOM_HASH_MASK};
|
use crate::bloom::{BloomFilter, BLOOM_HASH_MASK};
|
||||||
use crate::parser::{AncestorHashes, Combinator, Component, LocalName, NthSelectorData};
|
use crate::parser::{
|
||||||
use crate::parser::{NonTSPseudoClass, Selector, SelectorImpl, SelectorIter, SelectorList};
|
AncestorHashes, Combinator, Component, LocalName, NthSelectorData, RelativeSelectorMatchHint,
|
||||||
|
};
|
||||||
|
use crate::parser::{
|
||||||
|
NonTSPseudoClass, RelativeSelector, Selector, SelectorImpl, SelectorIter, SelectorList,
|
||||||
|
};
|
||||||
use crate::tree::Element;
|
use crate::tree::Element;
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use debug_unreachable::debug_unreachable;
|
use debug_unreachable::debug_unreachable;
|
||||||
|
@ -353,23 +357,63 @@ pub fn list_matches_complex_selector<E: Element>(
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Traverse all descendents of the given element and return true as soon as any of them match
|
/// Matches a relative selector in a list of relative selectors.
|
||||||
/// the given list of selectors.
|
fn matches_relative_selectors<E: Element>(
|
||||||
fn has_children_matching<E: Element>(
|
selectors: &[RelativeSelector<E::Impl>],
|
||||||
selectors: &[Selector<E::Impl>],
|
element: &E,
|
||||||
|
context: &mut MatchingContext<E::Impl>,
|
||||||
|
) -> bool {
|
||||||
|
for RelativeSelector {
|
||||||
|
match_hint,
|
||||||
|
selector,
|
||||||
|
} in selectors.iter()
|
||||||
|
{
|
||||||
|
let (traverse_subtree, traverse_siblings, mut next_element) = match match_hint {
|
||||||
|
RelativeSelectorMatchHint::InChild => (false, true, element.first_element_child()),
|
||||||
|
RelativeSelectorMatchHint::InSubtree => (true, true, element.first_element_child()),
|
||||||
|
RelativeSelectorMatchHint::InSibling => (false, true, element.next_sibling_element()),
|
||||||
|
RelativeSelectorMatchHint::InSiblingSubtree => {
|
||||||
|
(true, true, element.next_sibling_element())
|
||||||
|
},
|
||||||
|
RelativeSelectorMatchHint::InNextSibling => {
|
||||||
|
(false, false, element.next_sibling_element())
|
||||||
|
},
|
||||||
|
RelativeSelectorMatchHint::InNextSiblingSubtree => {
|
||||||
|
(true, false, element.next_sibling_element())
|
||||||
|
},
|
||||||
|
};
|
||||||
|
while let Some(el) = next_element {
|
||||||
|
// TODO(dshin): `:has()` matching can get expensive when determining style changes.
|
||||||
|
// We'll need caching/filtering here, which is tracked in bug 1822177.
|
||||||
|
if matches_complex_selector(selector.iter(), &el, context) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if traverse_subtree && matches_relative_selector_subtree(selector, &el, context) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if !traverse_siblings {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
next_element = el.next_sibling_element();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches_relative_selector_subtree<E: Element>(
|
||||||
|
selector: &Selector<E::Impl>,
|
||||||
element: &E,
|
element: &E,
|
||||||
context: &mut MatchingContext<E::Impl>,
|
context: &mut MatchingContext<E::Impl>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut current = element.first_element_child();
|
let mut current = element.first_element_child();
|
||||||
|
|
||||||
while let Some(el) = current {
|
while let Some(el) = current {
|
||||||
for selector in selectors {
|
|
||||||
if matches_complex_selector(selector.iter(), &el, context) {
|
if matches_complex_selector(selector.iter(), &el, context) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if has_children_matching(selectors, &el, context) {
|
if matches_relative_selector_subtree(selector, &el, context) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -844,24 +888,10 @@ where
|
||||||
Component::Negation(ref list) => context
|
Component::Negation(ref list) => context
|
||||||
.shared
|
.shared
|
||||||
.nest_for_negation(|context| !list_matches_complex_selector(list, element, context)),
|
.nest_for_negation(|context| !list_matches_complex_selector(list, element, context)),
|
||||||
Component::Has(ref list) => context
|
Component::Has(ref relative_selectors) => context
|
||||||
.shared
|
.shared
|
||||||
.nest_for_relative_selector(element.opaque(), |context| {
|
.nest_for_relative_selector(element.opaque(), |context| {
|
||||||
if cfg!(debug_assertions) {
|
matches_relative_selectors(relative_selectors, element, context)
|
||||||
for selector in list.iter() {
|
|
||||||
let mut selector_iter = selector.iter_raw_parse_order_from(0);
|
|
||||||
assert!(
|
|
||||||
matches!(selector_iter.next().unwrap(), Component::RelativeSelectorAnchor),
|
|
||||||
"Relative selector does not start with RelativeSelectorAnchor"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
selector_iter.next().unwrap().is_combinator(),
|
|
||||||
"Relative combinator does not exist"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO(dshin): Proper matching for sibling relative combinators.
|
|
||||||
has_children_matching(list, element, context)
|
|
||||||
}),
|
}),
|
||||||
Component::Combinator(_) => unsafe {
|
Component::Combinator(_) => unsafe {
|
||||||
debug_unreachable!("Shouldn't try to selector-match combinators")
|
debug_unreachable!("Shouldn't try to selector-match combinators")
|
||||||
|
|
|
@ -7,8 +7,8 @@ use crate::attr::{NamespaceConstraint, ParsedAttrSelectorOperation};
|
||||||
use crate::attr::{ParsedCaseSensitivity, SELECTOR_WHITESPACE};
|
use crate::attr::{ParsedCaseSensitivity, SELECTOR_WHITESPACE};
|
||||||
use crate::bloom::BLOOM_HASH_MASK;
|
use crate::bloom::BLOOM_HASH_MASK;
|
||||||
use crate::builder::{
|
use crate::builder::{
|
||||||
selector_list_specificity_and_flags, SelectorBuilder, SelectorFlags, Specificity,
|
relative_selector_list_specificity_and_flags, selector_list_specificity_and_flags,
|
||||||
SpecificityAndFlags,
|
SelectorBuilder, SelectorFlags, Specificity, SpecificityAndFlags,
|
||||||
};
|
};
|
||||||
use crate::context::QuirksMode;
|
use crate::context::QuirksMode;
|
||||||
use crate::sink::Push;
|
use crate::sink::Push;
|
||||||
|
@ -742,6 +742,30 @@ impl<Impl: SelectorImpl> Selector<Impl> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Same as `iter()`, but skips `RelativeSelectorAnchor` and its associated combinator.
|
||||||
|
#[inline]
|
||||||
|
pub fn iter_skip_relative_selector_anchor(&self) -> SelectorIter<Impl> {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
let mut selector_iter = self.iter_raw_parse_order_from(0);
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
selector_iter.next().unwrap(),
|
||||||
|
Component::RelativeSelectorAnchor
|
||||||
|
),
|
||||||
|
"Relative selector does not start with RelativeSelectorAnchor"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
selector_iter.next().unwrap().is_combinator(),
|
||||||
|
"Relative combinator does not exist"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectorIter {
|
||||||
|
iter: self.0.slice[..self.len() - 2].iter(),
|
||||||
|
next_combinator: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether this selector is a featureless :host selector, with no
|
/// Whether this selector is a featureless :host selector, with no
|
||||||
/// combinators to the left, and optionally has a pseudo-element to the
|
/// combinators to the left, and optionally has a pseudo-element to the
|
||||||
/// right.
|
/// right.
|
||||||
|
@ -846,7 +870,7 @@ impl<Impl: SelectorImpl> Selector<Impl> {
|
||||||
let flags = self.flags() - SelectorFlags::HAS_PARENT;
|
let flags = self.flags() - SelectorFlags::HAS_PARENT;
|
||||||
let mut specificity = Specificity::from(self.specificity());
|
let mut specificity = Specificity::from(self.specificity());
|
||||||
let parent_specificity =
|
let parent_specificity =
|
||||||
Specificity::from(selector_list_specificity_and_flags(parent).specificity());
|
Specificity::from(selector_list_specificity_and_flags(parent.iter()).specificity());
|
||||||
|
|
||||||
// The specificity at this point will be wrong, we replace it by the correct one after the
|
// The specificity at this point will be wrong, we replace it by the correct one after the
|
||||||
// fact.
|
// fact.
|
||||||
|
@ -879,8 +903,40 @@ impl<Impl: SelectorImpl> Selector<Impl> {
|
||||||
}
|
}
|
||||||
|
|
||||||
*specificity += Specificity::from(
|
*specificity += Specificity::from(
|
||||||
selector_list_specificity_and_flags(&result).specificity -
|
selector_list_specificity_and_flags(result.iter()).specificity -
|
||||||
selector_list_specificity_and_flags(orig).specificity,
|
selector_list_specificity_and_flags(orig.iter()).specificity,
|
||||||
|
);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_parent_on_relative_selector_list<Impl: SelectorImpl>(
|
||||||
|
orig: &[RelativeSelector<Impl>],
|
||||||
|
parent: &[Selector<Impl>],
|
||||||
|
specificity: &mut Specificity,
|
||||||
|
) -> Vec<RelativeSelector<Impl>> {
|
||||||
|
let mut any = false;
|
||||||
|
|
||||||
|
let result = orig
|
||||||
|
.iter()
|
||||||
|
.map(|s| {
|
||||||
|
if !s.selector.has_parent_selector() {
|
||||||
|
return s.clone();
|
||||||
|
}
|
||||||
|
any = true;
|
||||||
|
RelativeSelector {
|
||||||
|
match_hint: s.match_hint,
|
||||||
|
selector: s.selector.replace_parent_selector(parent).into_owned(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !any {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
*specificity += Specificity::from(
|
||||||
|
relative_selector_list_specificity_and_flags(&result).specificity -
|
||||||
|
relative_selector_list_specificity_and_flags(orig).specificity,
|
||||||
);
|
);
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
@ -956,15 +1012,12 @@ impl<Impl: SelectorImpl> Selector<Impl> {
|
||||||
.into_boxed_slice(),
|
.into_boxed_slice(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
Has(ref selectors) => {
|
Has(ref selectors) => Has(replace_parent_on_relative_selector_list(
|
||||||
Has(replace_parent_on_selector_list(
|
|
||||||
selectors,
|
selectors,
|
||||||
parent,
|
parent,
|
||||||
&mut specificity,
|
&mut specificity,
|
||||||
/* with_specificity = */ true,
|
|
||||||
)
|
)
|
||||||
.into_boxed_slice())
|
.into_boxed_slice()),
|
||||||
},
|
|
||||||
|
|
||||||
Host(Some(ref selector)) => Host(Some(replace_parent_on_selector(
|
Host(Some(ref selector)) => Host(Some(replace_parent_on_selector(
|
||||||
selector,
|
selector,
|
||||||
|
@ -1139,6 +1192,29 @@ impl<'a, Impl: SelectorImpl> fmt::Debug for SelectorIter<'a, Impl> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An iterator over all combinators in a selector. Does not traverse selectors within psuedoclasses.
|
||||||
|
struct CombinatorIter<'a, Impl: 'a + SelectorImpl>(SelectorIter<'a, Impl>);
|
||||||
|
impl<'a, Impl: 'a + SelectorImpl> CombinatorIter<'a, Impl> {
|
||||||
|
fn new(inner: SelectorIter<'a, Impl>) -> Self {
|
||||||
|
let mut result = CombinatorIter(inner);
|
||||||
|
result.consume_non_combinators();
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consume_non_combinators(&mut self) {
|
||||||
|
while self.0.next().is_some() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Impl: SelectorImpl> Iterator for CombinatorIter<'a, Impl> {
|
||||||
|
type Item = Combinator;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let result = self.0.next_sequence();
|
||||||
|
self.consume_non_combinators();
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An iterator over all simple selectors belonging to ancestors.
|
/// An iterator over all simple selectors belonging to ancestors.
|
||||||
struct AncestorIter<'a, Impl: 'a + SelectorImpl>(SelectorIter<'a, Impl>);
|
struct AncestorIter<'a, Impl: 'a + SelectorImpl>(SelectorIter<'a, Impl>);
|
||||||
impl<'a, Impl: 'a + SelectorImpl> AncestorIter<'a, Impl> {
|
impl<'a, Impl: 'a + SelectorImpl> AncestorIter<'a, Impl> {
|
||||||
|
@ -1387,6 +1463,140 @@ impl<Impl: SelectorImpl> NthOfSelectorData<Impl> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Flag indicating where a given relative selector's match would be contained.
|
||||||
|
#[derive(Clone, Copy, Eq, PartialEq, ToShmem)]
|
||||||
|
pub enum RelativeSelectorMatchHint {
|
||||||
|
/// Within this element's subtree.
|
||||||
|
InSubtree,
|
||||||
|
/// Within this element's direct children.
|
||||||
|
InChild,
|
||||||
|
/// This element's next sibling.
|
||||||
|
InNextSibling,
|
||||||
|
/// Within this element's next sibling's subtree.
|
||||||
|
InNextSiblingSubtree,
|
||||||
|
/// Within this element's subsequent siblings.
|
||||||
|
InSibling,
|
||||||
|
/// Across this element's subsequent siblings and their subtrees.
|
||||||
|
InSiblingSubtree,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Storage for a relative selector.
|
||||||
|
#[derive(Clone, Eq, PartialEq, ToShmem)]
|
||||||
|
#[shmem(no_bounds)]
|
||||||
|
pub struct RelativeSelector<Impl: SelectorImpl> {
|
||||||
|
/// Match space constraining hint.
|
||||||
|
pub match_hint: RelativeSelectorMatchHint,
|
||||||
|
/// The selector. Guaranteed to contain `RelativeSelectorAnchor` and the relative combinator in parse order.
|
||||||
|
#[shmem(field_bound)]
|
||||||
|
pub selector: Selector<Impl>,
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
/// Composition of combinators in a given selector, not traversing selectors of pseudoclasses.
|
||||||
|
struct CombinatorComposition: u8 {
|
||||||
|
const DESCENDANTS = 1 << 0;
|
||||||
|
const SIBLINGS = 1 << 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CombinatorComposition {
|
||||||
|
fn for_relative_selector<Impl: SelectorImpl>(inner_selector: &Selector<Impl>) -> Self {
|
||||||
|
let mut result = CombinatorComposition::empty();
|
||||||
|
for combinator in CombinatorIter::new(inner_selector.iter_skip_relative_selector_anchor()) {
|
||||||
|
match combinator {
|
||||||
|
Combinator::Descendant | Combinator::Child => {
|
||||||
|
result.insert(CombinatorComposition::DESCENDANTS);
|
||||||
|
},
|
||||||
|
Combinator::NextSibling | Combinator::LaterSibling => {
|
||||||
|
result.insert(CombinatorComposition::SIBLINGS);
|
||||||
|
},
|
||||||
|
Combinator::Part | Combinator::PseudoElement | Combinator::SlotAssignment => {
|
||||||
|
continue
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if result.is_all() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Impl: SelectorImpl> RelativeSelector<Impl> {
|
||||||
|
fn from_selector_list(selector_list: SelectorList<Impl>) -> Box<[Self]> {
|
||||||
|
let vec: Vec<Self> = selector_list
|
||||||
|
.0
|
||||||
|
.into_iter()
|
||||||
|
.map(|selector| {
|
||||||
|
// It's more efficient to keep track of all this during the parse time, but that seems like a lot of special
|
||||||
|
// case handling for what it's worth.
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
let relative_selector_anchor = selector.iter_raw_parse_order_from(0).next();
|
||||||
|
debug_assert!(
|
||||||
|
relative_selector_anchor.is_some(),
|
||||||
|
"Relative selector is empty"
|
||||||
|
);
|
||||||
|
debug_assert!(
|
||||||
|
matches!(
|
||||||
|
relative_selector_anchor.unwrap(),
|
||||||
|
Component::RelativeSelectorAnchor
|
||||||
|
),
|
||||||
|
"Relative selector anchor is missing"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Leave a hint for narrowing down the search space when we're matching.
|
||||||
|
let match_hint = match selector.combinator_at_parse_order(1) {
|
||||||
|
Combinator::Descendant => RelativeSelectorMatchHint::InSubtree,
|
||||||
|
Combinator::Child => {
|
||||||
|
let composition = CombinatorComposition::for_relative_selector(&selector);
|
||||||
|
if composition.is_empty() || composition == CombinatorComposition::SIBLINGS
|
||||||
|
{
|
||||||
|
RelativeSelectorMatchHint::InChild
|
||||||
|
} else {
|
||||||
|
// Technically, for any composition that consists of child combinators only,
|
||||||
|
// the search space is depth-constrained, but it's probably not worth optimizing for.
|
||||||
|
RelativeSelectorMatchHint::InSubtree
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Combinator::NextSibling => {
|
||||||
|
let composition = CombinatorComposition::for_relative_selector(&selector);
|
||||||
|
if composition.is_empty() {
|
||||||
|
RelativeSelectorMatchHint::InNextSibling
|
||||||
|
} else if composition == CombinatorComposition::SIBLINGS {
|
||||||
|
RelativeSelectorMatchHint::InSibling
|
||||||
|
} else if composition == CombinatorComposition::DESCENDANTS {
|
||||||
|
// Match won't cross multiple siblings.
|
||||||
|
RelativeSelectorMatchHint::InNextSiblingSubtree
|
||||||
|
} else {
|
||||||
|
RelativeSelectorMatchHint::InSiblingSubtree
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Combinator::LaterSibling => {
|
||||||
|
let composition = CombinatorComposition::for_relative_selector(&selector);
|
||||||
|
if composition.is_empty() || composition == CombinatorComposition::SIBLINGS
|
||||||
|
{
|
||||||
|
RelativeSelectorMatchHint::InSibling
|
||||||
|
} else {
|
||||||
|
// Even if the match may not cross multiple siblings, we have to look until
|
||||||
|
// we find a match anyway.
|
||||||
|
RelativeSelectorMatchHint::InSiblingSubtree
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Combinator::Part | Combinator::PseudoElement | Combinator::SlotAssignment => {
|
||||||
|
debug_assert!(false, "Unexpected relative combinator");
|
||||||
|
RelativeSelectorMatchHint::InSubtree
|
||||||
|
},
|
||||||
|
};
|
||||||
|
RelativeSelector {
|
||||||
|
match_hint,
|
||||||
|
selector,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
vec.into_boxed_slice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A CSS simple selector or combinator. We store both in the same enum for
|
/// A CSS simple selector or combinator. We store both in the same enum for
|
||||||
/// optimal packing and cache performance, see [1].
|
/// optimal packing and cache performance, see [1].
|
||||||
///
|
///
|
||||||
|
@ -1479,7 +1689,7 @@ pub enum Component<Impl: SelectorImpl> {
|
||||||
/// https://drafts.csswg.org/selectors/#has-pseudo
|
/// https://drafts.csswg.org/selectors/#has-pseudo
|
||||||
///
|
///
|
||||||
/// Same comment as above re. the argument.
|
/// Same comment as above re. the argument.
|
||||||
Has(Box<[Selector<Impl>]>),
|
Has(Box<[RelativeSelector<Impl>]>),
|
||||||
/// An implementation-dependent pseudo-element selector.
|
/// An implementation-dependent pseudo-element selector.
|
||||||
PseudoElement(#[cfg_attr(feature = "shmem", shmem(field_bound))] Impl::PseudoElement),
|
PseudoElement(#[cfg_attr(feature = "shmem", shmem(field_bound))] Impl::PseudoElement),
|
||||||
|
|
||||||
|
@ -1997,17 +2207,28 @@ impl<Impl: SelectorImpl> ToCss for Component<Impl> {
|
||||||
serialize_selector_list(nth_of_data.selectors().iter(), dest)?;
|
serialize_selector_list(nth_of_data.selectors().iter(), dest)?;
|
||||||
dest.write_char(')')
|
dest.write_char(')')
|
||||||
},
|
},
|
||||||
Is(ref list) | Where(ref list) | Negation(ref list) | Has(ref list) => {
|
Is(ref list) | Where(ref list) | Negation(ref list) => {
|
||||||
match *self {
|
match *self {
|
||||||
Where(..) => dest.write_str(":where(")?,
|
Where(..) => dest.write_str(":where(")?,
|
||||||
Is(..) => dest.write_str(":is(")?,
|
Is(..) => dest.write_str(":is(")?,
|
||||||
Negation(..) => dest.write_str(":not(")?,
|
Negation(..) => dest.write_str(":not(")?,
|
||||||
Has(..) => dest.write_str(":has(")?,
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
serialize_selector_list(list.iter(), dest)?;
|
serialize_selector_list(list.iter(), dest)?;
|
||||||
dest.write_str(")")
|
dest.write_str(")")
|
||||||
},
|
},
|
||||||
|
Has(ref list) => {
|
||||||
|
dest.write_str(":has(")?;
|
||||||
|
let mut first = true;
|
||||||
|
for RelativeSelector { ref selector, .. } in list.iter() {
|
||||||
|
if !first {
|
||||||
|
dest.write_str(", ")?;
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
selector.to_css(dest)?;
|
||||||
|
}
|
||||||
|
dest.write_str(")")
|
||||||
|
},
|
||||||
NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest),
|
NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest),
|
||||||
RelativeSelectorAnchor => Ok(()),
|
RelativeSelectorAnchor => Ok(()),
|
||||||
}
|
}
|
||||||
|
@ -2721,7 +2942,7 @@ where
|
||||||
ForgivingParsing::No,
|
ForgivingParsing::No,
|
||||||
ParseRelative::Yes,
|
ParseRelative::Yes,
|
||||||
)?;
|
)?;
|
||||||
Ok(Component::Has(inner.0.into_vec().into_boxed_slice()))
|
Ok(Component::Has(RelativeSelector::from_selector_list(inner)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_functional_pseudo_class<'i, 't, P, Impl>(
|
fn parse_functional_pseudo_class<'i, 't, P, Impl>(
|
||||||
|
@ -3250,7 +3471,6 @@ pub mod tests {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn default_namespace(&self) -> Option<DummyAtom> {
|
fn default_namespace(&self) -> Option<DummyAtom> {
|
||||||
self.default_ns.clone()
|
self.default_ns.clone()
|
||||||
}
|
}
|
||||||
|
@ -3824,9 +4044,19 @@ pub mod tests {
|
||||||
let parent = parse(".bar, div .baz").unwrap();
|
let parent = parse(".bar, div .baz").unwrap();
|
||||||
let child = parse("#foo &.bar").unwrap();
|
let child = parse("#foo &.bar").unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
SelectorList::from_vec(vec![child.0[0].replace_parent_selector(&parent.0).into_owned()]),
|
SelectorList::from_vec(vec![child.0[0]
|
||||||
|
.replace_parent_selector(&parent.0)
|
||||||
|
.into_owned()]),
|
||||||
parse("#foo :is(.bar, div .baz).bar").unwrap()
|
parse("#foo :is(.bar, div .baz).bar").unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let has_child = parse("#foo:has(&.bar)").unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
SelectorList::from_vec(vec![has_child.0[0]
|
||||||
|
.replace_parent_selector(&parent.0)
|
||||||
|
.into_owned()]),
|
||||||
|
parse("#foo:has(:is(.bar, div .baz).bar)").unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue