diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs index b9f4377f38d..fc970374e38 100644 --- a/components/selectors/matching.rs +++ b/components/selectors/matching.rs @@ -335,9 +335,9 @@ enum SelectorMatchingResult { /// Matches a selector, fast-rejecting against a bloom filter. /// -/// We accept an offset to allow consumers to represent and match against partial -/// selectors (indexed from the right). We use this API design, rather than -/// having the callers pass a SelectorIter, because creating a SelectorIter +/// We accept an offset to allow consumers to represent and match against +/// partial selectors (indexed from the right). We use this API design, rather +/// than having the callers pass a SelectorIter, because creating a SelectorIter /// requires dereferencing the selector to get the length, which adds an /// unncessary cache miss for cases when we can fast-reject with AncestorHashes /// (which the caller can store inline with the selector pointer). @@ -360,12 +360,74 @@ pub fn matches_selector(selector: &Selector, } let mut local_context = LocalMatchingContext::new(context, selector); - matches_complex_selector(&selector, offset, element, &mut local_context, flags_setter) + let iter = if offset == 0 { + selector.iter() + } else { + selector.iter_from(offset) + }; + matches_complex_selector(iter, element, &mut local_context, flags_setter) +} + +/// Whether a compound selector matched, and whether it was the rightmost +/// selector inside the complex selector. +pub enum CompoundSelectorMatchingResult { + /// The compound selector matched, and the next combinator offset is + /// `next_combinator_offset`. + /// + /// If the next combinator offset is zero, it means that it's the rightmost + /// selector. + Matched { next_combinator_offset: usize, }, + /// The selector didn't match. + NotMatched, +} + +/// Matches a compound selector belonging to `selector`, starting at offset +/// `from_offset`, matching left to right. +/// +/// Requires that `from_offset` points to a `Combinator`. +/// +/// NOTE(emilio): This doesn't allow to match in the leftmost sequence of the +/// complex selector, but it happens to be the case we don't need it. +pub fn matches_compound_selector( + selector: &Selector, + mut from_offset: usize, + context: &mut MatchingContext, + element: &E, +) -> CompoundSelectorMatchingResult +where + E: Element +{ + if cfg!(debug_assertions) { + selector.combinator_at(from_offset); // This asserts. + } + + let mut local_context = LocalMatchingContext::new(context, selector); + for component in selector.iter_raw_rev_from(from_offset - 1) { + if matches!(*component, Component::Combinator(..)) { + return CompoundSelectorMatchingResult::Matched { + next_combinator_offset: from_offset - 1, + } + } + + if !matches_simple_selector( + component, + element, + &mut local_context, + &RelevantLinkStatus::NotLooking, + &mut |_, _| {}) { + return CompoundSelectorMatchingResult::NotMatched; + } + + from_offset -= 1; + } + + return CompoundSelectorMatchingResult::Matched { + next_combinator_offset: 0, + } } /// Matches a complex selector. -pub fn matches_complex_selector(complex_selector: &Selector, - offset: usize, +pub fn matches_complex_selector(mut iter: SelectorIter, element: &E, mut context: &mut LocalMatchingContext, flags_setter: &mut F) @@ -373,12 +435,6 @@ pub fn matches_complex_selector(complex_selector: &Selector, where E: Element, F: FnMut(&E, ElementSelectorFlags), { - let mut iter = if offset == 0 { - complex_selector.iter() - } else { - complex_selector.iter_from(offset) - }; - if cfg!(debug_assertions) { if context.shared.matching_mode == MatchingMode::ForStatelessPseudoElement { assert!(iter.clone().any(|c| { diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index c2d64f18db1..0899858bd7d 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -449,18 +449,36 @@ impl Selector { } } - /// Returns an iterator over the entire sequence of simple selectors and combinators, - /// from right to left. + /// Returns the combinator at index `index`, or panics if the component is + /// not a combinator. + pub fn combinator_at(&self, index: usize) -> Combinator { + match self.0.slice[self.0.slice.len() - index] { + Component::Combinator(c) => c, + ref other => { + panic!("Not a combinator: {:?}, {:?}, index: {}", + other, self, index) + } + } + } + + /// Returns an iterator over the entire sequence of simple selectors and + /// combinators, from right to left. pub fn iter_raw(&self) -> Rev>> { self.iter_raw_rev().rev() } - /// Returns an iterator over the entire sequence of simple selectors and combinators, - /// from left to right. + /// Returns an iterator over the entire sequence of simple selectors and + /// combinators, from left to right. pub fn iter_raw_rev(&self) -> slice::Iter> { self.0.slice.iter() } + /// Returns an iterator over the sequence of simple selectors and + /// combinators after `offset`, from left to right. + pub fn iter_raw_rev_from(&self, offset: usize) -> slice::Iter> { + self.0.slice[(self.0.slice.len() - offset)..].iter() + } + /// Creates a Selector from a vec of Components. Used in tests. pub fn from_vec(vec: Vec>, specificity_and_flags: u32) -> Self { let header = HeaderWithLength::new(SpecificityAndFlags(specificity_and_flags), vec.len()); diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 17be81fe146..04e9a9888e6 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -1547,7 +1547,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { let old_value = context.within_functional_pseudo_class_argument; context.within_functional_pseudo_class_argument = true; let result = sels.iter().any(|s| { - matches_complex_selector(s, 0, self, context, flags_setter) + matches_complex_selector(s.iter(), self, context, flags_setter) }); context.within_functional_pseudo_class_argument = old_value; result