diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs index d8d4ad59946..f55833f7fb6 100644 --- a/components/selectors/matching.rs +++ b/components/selectors/matching.rs @@ -322,14 +322,13 @@ where }, } - // The only other parser-allowed Component in this sequence is a state - // class. We just don't match in that case. - if let Some(s) = iter.next() { - debug_assert!( - matches!(*s, Component::NonTSPseudoClass(..)), - "Someone messed up pseudo-element parsing" - ); - return false; + for component in &mut iter { + // The only other parser-allowed Components in this sequence are + // state pseudo-classes, or one of the other things that can contain + // them. + if !component.matches_for_stateless_pseudo_element() { + return false; + } } // Advance to the non-pseudo-element part of the selector. diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index 74f4a48de04..e2ad8e30caf 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -1054,6 +1054,47 @@ impl Component { } } + /// Whether this component is valid after a pseudo-element. Only intended + /// for sanity-checking. + pub fn maybe_allowed_after_pseudo_element(&self) -> bool { + match *self { + Component::NonTSPseudoClass(..) => true, + Component::Negation(ref components) => components.iter().all(|c| c.maybe_allowed_after_pseudo_element()), + Component::Is(ref selectors) | + Component::Where(ref selectors) => { + selectors.iter().all(|selector| { + selector.iter_raw_match_order().all(|c| c.maybe_allowed_after_pseudo_element()) + }) + }, + _ => false, + } + } + + /// Whether a given selector should match for stateless pseudo-elements. + /// + /// This is a bit subtle: Only selectors that return true in + /// `maybe_allowed_after_pseudo_element` should end up here, and + /// `NonTSPseudoClass` never matches (as it is a stateless pseudo after + /// all). + pub(crate) fn matches_for_stateless_pseudo_element(&self) -> bool { + debug_assert!( + self.maybe_allowed_after_pseudo_element(), + "Someone messed up pseudo-element parsing: {:?}", + *self + ); + match *self { + Component::Negation(ref components) => { + !components.iter().all(|c| c.matches_for_stateless_pseudo_element()) + }, + Component::Is(ref selectors) | Component::Where(ref selectors) => { + selectors.iter().any(|selector| { + selector.iter_raw_match_order().all(|c| c.matches_for_stateless_pseudo_element()) + }) + }, + _ => false, + } + } + pub fn visit(&self, visitor: &mut V) -> bool where V: SelectorVisitor, diff --git a/components/style/invalidation/element/invalidator.rs b/components/style/invalidation/element/invalidator.rs index 4f5d005c120..8b387bc0c04 100644 --- a/components/style/invalidation/element/invalidator.rs +++ b/components/style/invalidation/element/invalidator.rs @@ -871,23 +871,29 @@ where // This will usually be the very next component, except for // the fact that we store compound selectors the other way // around, so there could also be state pseudo-classes. - let pseudo_selector = next_invalidation + let pseudo = next_invalidation .dependency .selector .iter_raw_parse_order_from(next_invalidation.offset) - .skip_while(|c| matches!(**c, Component::NonTSPseudoClass(..))) + .flat_map(|c| { + if let Component::PseudoElement(ref pseudo) = *c { + return Some(pseudo); + } + + // TODO: Would be nice to make this a diagnostic_assert! of + // sorts. + debug_assert!( + c.maybe_allowed_after_pseudo_element(), + "Someone seriously messed up selector parsing: \ + {:?} at offset {:?}: {:?}", + next_invalidation.dependency, next_invalidation.offset, c, + ); + + None + }) .next() .unwrap(); - let pseudo = match *pseudo_selector { - Component::PseudoElement(ref pseudo) => pseudo, - _ => unreachable!( - "Someone seriously messed up selector parsing: \ - {:?} at offset {:?}: {:?}", - next_invalidation.dependency, next_invalidation.offset, pseudo_selector, - ), - }; - // FIXME(emilio): This is not ideal, and could not be // accurate if we ever have stateful element-backed eager // pseudos.