Bug 1355724 - stylo: Support :hover and :active quirk r?bholley draft
authorNazım Can Altınova <canaltinova@gmail.com>
Sat, 10 Jun 2017 20:43:05 +0300
changeset 592136 a3e11f1d3a1e88f5c0cf54de8f4bceffcf64320a
parent 592116 91dc9525c422f11041da33b008b14a8117ed9a40
child 592137 f3f5da19c638977e40767bd9a5a6eb3e60becad6
push id63288
push userbmo:canaltinova@gmail.com
push dateSat, 10 Jun 2017 18:00:20 +0000
reviewersbholley
bugs1355724
milestone55.0a1
Bug 1355724 - stylo: Support :hover and :active quirk r?bholley MozReview-Commit-ID: 3s6ygE10cHg
servo/components/script/dom/document.rs
servo/components/script/dom/element.rs
servo/components/script/dom/node.rs
servo/components/script/layout_wrapper.rs
servo/components/selectors/matching.rs
servo/components/selectors/parser.rs
servo/components/selectors/tree.rs
servo/components/style/context.rs
servo/components/style/gecko/selector_parser.rs
servo/components/style/gecko/wrapper.rs
servo/components/style/matching.rs
servo/components/style/restyle_hints.rs
servo/components/style/servo/selector_parser.rs
servo/components/style/stylist.rs
--- a/servo/components/script/dom/document.rs
+++ b/servo/components/script/dom/document.rs
@@ -204,16 +204,17 @@ pub struct Document {
     location: MutNullableJS<Location>,
     content_type: DOMString,
     last_modified: Option<String>,
     encoding: Cell<EncodingRef>,
     has_browsing_context: bool,
     is_html_document: bool,
     activity: Cell<DocumentActivity>,
     url: DOMRefCell<ServoUrl>,
+    #[ignore_heap_size_of = "defined in selectors"]
     quirks_mode: Cell<QuirksMode>,
     /// Caches for the getElement methods
     id_map: DOMRefCell<HashMap<Atom, Vec<JS<Element>>>>,
     tag_map: DOMRefCell<HashMap<LocalName, JS<HTMLCollection>>>,
     tagns_map: DOMRefCell<HashMap<QualName, JS<HTMLCollection>>>,
     classes_map: DOMRefCell<HashMap<Vec<Atom>, JS<HTMLCollection>>>,
     images: MutNullableJS<HTMLCollection>,
     embeds: MutNullableJS<HTMLCollection>,
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -81,17 +81,17 @@ use html5ever::serialize::SerializeOpts;
 use html5ever::serialize::TraversalScope;
 use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode};
 use js::jsapi::{HandleValue, JSAutoCompartment};
 use net_traits::request::CorsSettings;
 use ref_filter_map::ref_filter_map;
 use script_layout_interface::message::ReflowQueryType;
 use script_thread::Runnable;
 use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
-use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
+use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, MatchingMode};
 use selectors::matching::{HAS_EDGE_CHILD_SELECTOR, HAS_SLOW_SELECTOR, HAS_SLOW_SELECTOR_LATER_SIBLINGS};
 use selectors::matching::{RelevantLinkStatus, matches_selector_list};
 use servo_atoms::Atom;
 use std::ascii::AsciiExt;
 use std::borrow::Cow;
 use std::cell::{Cell, Ref};
 use std::convert::TryFrom;
 use std::default::Default;
@@ -2058,17 +2058,19 @@ impl ElementMethods for Element {
         self.upcast::<Node>().remove_self();
     }
 
     // https://dom.spec.whatwg.org/#dom-element-matches
     fn Matches(&self, selectors: DOMString) -> Fallible<bool> {
         match SelectorParser::parse_author_origin_no_namespace(&selectors) {
             Err(_) => Err(Error::Syntax),
             Ok(selectors) => {
-                let mut ctx = MatchingContext::new(MatchingMode::Normal, None);
+                let quirks_mode = document_from_node(self).quirks_mode();
+                let mut ctx = MatchingContext::new(MatchingMode::Normal, None,
+                                                   quirks_mode);
                 Ok(matches_selector_list(&selectors, &Root::from_ref(self), &mut ctx))
             }
         }
     }
 
     // https://dom.spec.whatwg.org/#dom-element-webkitmatchesselector
     fn WebkitMatchesSelector(&self, selectors: DOMString) -> Fallible<bool> {
         self.Matches(selectors)
@@ -2077,17 +2079,19 @@ impl ElementMethods for Element {
     // https://dom.spec.whatwg.org/#dom-element-closest
     fn Closest(&self, selectors: DOMString) -> Fallible<Option<Root<Element>>> {
         match SelectorParser::parse_author_origin_no_namespace(&selectors) {
             Err(_) => Err(Error::Syntax),
             Ok(selectors) => {
                 let root = self.upcast::<Node>();
                 for element in root.inclusive_ancestors() {
                     if let Some(element) = Root::downcast::<Element>(element) {
-                        let mut ctx = MatchingContext::new(MatchingMode::Normal, None);
+                        let quirks_mode = document_from_node(self).quirks_mode();
+                        let mut ctx = MatchingContext::new(MatchingMode::Normal, None,
+                                                           quirks_mode);
                         if matches_selector_list(&selectors, &element, &mut ctx) {
                             return Ok(Some(element));
                         }
                     }
                 }
                 Ok(None)
             }
         }
@@ -2427,17 +2431,17 @@ impl<'a> ::selectors::Element for Root<E
     }
 
     fn get_namespace(&self) -> &Namespace {
         self.namespace()
     }
 
     fn match_non_ts_pseudo_class<F>(&self,
                                     pseudo_class: &NonTSPseudoClass,
-                                    _: &mut MatchingContext,
+                                    _: &mut LocalMatchingContext<Self::Impl>,
                                     _: &RelevantLinkStatus,
                                     _: &mut F)
                                     -> bool
         where F: FnMut(&Self, ElementSelectorFlags),
     {
         match *pseudo_class {
             // https://github.com/servo/servo/issues/8718
             NonTSPseudoClass::Link |
--- a/servo/components/script/dom/node.rs
+++ b/servo/components/script/dom/node.rs
@@ -342,21 +342,21 @@ impl<'a> QuerySelectorIterator {
 }
 
 impl<'a> Iterator for QuerySelectorIterator {
     type Item = Root<Node>;
 
     fn next(&mut self) -> Option<Root<Node>> {
         let selectors = &self.selectors;
 
-        // TODO(cgaebel): Is it worth it to build a bloom filter here
-        // (instead of passing `None`)? Probably.
-        let mut ctx = MatchingContext::new(MatchingMode::Normal, None);
-
         self.iterator.by_ref().filter_map(|node| {
+            // TODO(cgaebel): Is it worth it to build a bloom filter here
+            // (instead of passing `None`)? Probably.
+            let mut ctx = MatchingContext::new(MatchingMode::Normal, None,
+                node.owner_doc().quirks_mode());
             if let Some(element) = Root::downcast(node) {
                 if matches_selector_list(selectors, &element, &mut ctx) {
                     return Some(Root::upcast(element));
                 }
             }
             None
         }).next()
     }
@@ -715,17 +715,18 @@ impl Node {
     // https://dom.spec.whatwg.org/#dom-parentnode-queryselector
     pub fn query_selector(&self, selectors: DOMString) -> Fallible<Option<Root<Element>>> {
         // Step 1.
         match SelectorParser::parse_author_origin_no_namespace(&selectors) {
             // Step 2.
             Err(_) => Err(Error::Syntax),
             // Step 3.
             Ok(selectors) => {
-                let mut ctx = MatchingContext::new(MatchingMode::Normal, None);
+                let mut ctx = MatchingContext::new(MatchingMode::Normal, None,
+                                                   self.owner_doc().quirks_mode());
                 Ok(self.traverse_preorder().filter_map(Root::downcast).find(|element| {
                     matches_selector_list(&selectors, element, &mut ctx)
                 }))
             }
         }
     }
 
     /// https://dom.spec.whatwg.org/#scope-match-a-selectors-string
--- a/servo/components/script/layout_wrapper.rs
+++ b/servo/components/script/layout_wrapper.rs
@@ -45,17 +45,18 @@ use gfx_traits::ByteIndex;
 use html5ever::{LocalName, Namespace};
 use msg::constellation_msg::{BrowsingContextId, PipelineId};
 use range::Range;
 use script_layout_interface::{HTMLCanvasData, LayoutNodeType, SVGSVGData, TrustedNodeAddress};
 use script_layout_interface::{OpaqueStyleAndLayoutData, StyleData};
 use script_layout_interface::wrapper_traits::{DangerousThreadSafeLayoutNode, GetLayoutData, LayoutNode};
 use script_layout_interface::wrapper_traits::{PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
 use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
-use selectors::matching::{ElementSelectorFlags, MatchingContext, RelevantLinkStatus, VisitedHandlingMode};
+use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, RelevantLinkStatus};
+use selectors::matching::VisitedHandlingMode;
 use servo_atoms::Atom;
 use servo_url::ServoUrl;
 use std::fmt;
 use std::fmt::Debug;
 use std::hash::{Hash, Hasher};
 use std::marker::PhantomData;
 use std::mem::transmute;
 use std::sync::atomic::Ordering;
@@ -716,17 +717,17 @@ impl<'le> ::selectors::Element for Servo
                             _context: &mut MatchingContext)
                             -> bool
     {
         false
     }
 
     fn match_non_ts_pseudo_class<F>(&self,
                                     pseudo_class: &NonTSPseudoClass,
-                                    _: &mut MatchingContext,
+                                    _: &mut LocalMatchingContext<Self::Impl>,
                                     _: &RelevantLinkStatus,
                                     _: &mut F)
                                     -> bool
         where F: FnMut(&Self, ElementSelectorFlags),
     {
         match *pseudo_class {
             // https://github.com/servo/servo/issues/8718
             NonTSPseudoClass::Link |
@@ -1227,17 +1228,17 @@ impl<'le> ::selectors::Element for Servo
                 };
                 values.iter().any(|v| v.eval_selector(operation))
             }
         }
     }
 
     fn match_non_ts_pseudo_class<F>(&self,
                                     _: &NonTSPseudoClass,
-                                    _: &mut MatchingContext,
+                                    _: &mut LocalMatchingContext<Self::Impl>,
                                     _: &RelevantLinkStatus,
                                     _: &mut F)
                                     -> bool
         where F: FnMut(&Self, ElementSelectorFlags),
     {
         // NB: This could maybe be implemented
         warn!("ServoThreadSafeLayoutElement::match_non_ts_pseudo_class called");
         false
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use attr::{ParsedAttrSelectorOperation, AttrSelectorOperation, NamespaceConstraint};
 use bloom::BloomFilter;
 use parser::{AncestorHashes, Combinator, Component, LocalName};
-use parser::{Selector, SelectorIter, SelectorList};
+use parser::{Selector, SelectorImpl, SelectorIter, SelectorList};
 use std::borrow::Borrow;
 use tree::Element;
 
 // The bloom filter for descendant CSS selectors will have a <1% false
 // positive rate until it has this many selectors in it, then it will
 // rapidly increase.
 pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: usize = 4096;
 
@@ -95,16 +95,29 @@ pub enum VisitedHandlingMode {
     /// All links are matched as if they are unvisted.
     AllLinksUnvisited,
     /// A element's "relevant link" is the element being matched if it is a link
     /// or the nearest ancestor link. The relevant link is matched as though it
     /// is visited, and all other links are matched as if they are unvisited.
     RelevantLinkVisited,
 }
 
+/// Which quirks mode is this document in.
+///
+/// See: https://quirks.spec.whatwg.org/
+#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)]
+pub enum QuirksMode {
+    /// Quirks mode.
+    Quirks,
+    /// Limited quirks mode.
+    LimitedQuirks,
+    /// No quirks mode.
+    NoQuirks,
+}
+
 /// Data associated with the matching process for a element.  This context is
 /// used across many selectors for an element, so it's not appropriate for
 /// transient data that applies to only a single selector.
 #[derive(Clone)]
 pub struct MatchingContext<'a> {
     /// Output that records certains relations between elements noticed during
     /// matching (and also extended after matching).
     pub relations: StyleRelations,
@@ -114,49 +127,141 @@ pub struct MatchingContext<'a> {
     pub bloom_filter: Option<&'a BloomFilter>,
     /// Input that controls how matching for links is handled.
     pub visited_handling: VisitedHandlingMode,
     /// Output that records whether we encountered a "relevant link" while
     /// matching _any_ selector for this element. (This differs from
     /// `RelevantLinkStatus` which tracks the status for the _current_ selector
     /// only.)
     pub relevant_link_found: bool,
+    /// The quirks mode of the document.
+    pub quirks_mode: QuirksMode,
 }
 
 impl<'a> MatchingContext<'a> {
     /// Constructs a new `MatchingContext`.
     pub fn new(matching_mode: MatchingMode,
-               bloom_filter: Option<&'a BloomFilter>)
+               bloom_filter: Option<&'a BloomFilter>,
+               quirks_mode: QuirksMode)
                -> Self
     {
         Self {
             relations: StyleRelations::empty(),
             matching_mode: matching_mode,
             bloom_filter: bloom_filter,
             visited_handling: VisitedHandlingMode::AllLinksUnvisited,
             relevant_link_found: false,
+            quirks_mode: quirks_mode,
         }
     }
 
     /// Constructs a new `MatchingContext` for use in visited matching.
     pub fn new_for_visited(matching_mode: MatchingMode,
                            bloom_filter: Option<&'a BloomFilter>,
-                           visited_handling: VisitedHandlingMode)
+                           visited_handling: VisitedHandlingMode,
+                           quirks_mode: QuirksMode)
                            -> Self
     {
         Self {
             relations: StyleRelations::empty(),
             matching_mode: matching_mode,
             bloom_filter: bloom_filter,
             visited_handling: visited_handling,
             relevant_link_found: false,
+            quirks_mode: quirks_mode,
         }
     }
 }
 
+/// Holds per-element data alongside a pointer to MatchingContext.
+pub struct LocalMatchingContext<'a, 'b: 'a, Impl: SelectorImpl> {
+    /// Shared `MatchingContext`.
+    pub shared: &'a mut MatchingContext<'b>,
+    /// A reference to the base selector we're matching against.
+    pub selector: &'a Selector<Impl>,
+    /// The offset of the current compound selector being matched, kept up to date by
+    /// the callees when the iterator is advanced. This, in conjunction with the selector
+    /// reference above, allows callees to synthesize an iterator for the current compound
+    /// selector on-demand. This is necessary because the primary iterator may already have
+    /// been advanced partway through the current compound selector, and the callee may need
+    /// the whole thing.
+    offset: usize,
+    /// Holds a bool flag to see if LocalMatchingContext is within a functional
+    /// pseudo class argument. This is used for pseudo classes like
+    /// `:-moz-any` or `:not`. If this flag is true, :active and :hover
+    /// quirk shouldn't match.
+    pub within_functional_pseudo_class_argument: bool,
+}
+
+impl<'a, 'b, Impl> LocalMatchingContext<'a, 'b, Impl>
+    where Impl: SelectorImpl
+{
+    /// Constructs a new `LocalMatchingContext`.
+    pub fn new(shared: &'a mut MatchingContext<'b>,
+               selector: &'a Selector<Impl>) -> Self {
+        Self {
+            shared: shared,
+            selector: selector,
+            offset: 0,
+            within_functional_pseudo_class_argument: false,
+        }
+    }
+
+    /// Updates offset of Selector to show new compound selector.
+    /// To be able to correctly re-synthesize main SelectorIter.
+    pub fn note_next_sequence(&mut self, selector_iter: &SelectorIter<Impl>) {
+        if let QuirksMode::Quirks = self.shared.quirks_mode {
+            self.offset = self.selector.len() - selector_iter.selector_length();
+        }
+    }
+
+    /// Returns true if current compound selector matches :active and :hover quirk.
+    /// https://quirks.spec.whatwg.org/#the-active-and-hover-quirk
+    pub fn active_hover_quirk_matches(&mut self) -> bool {
+        if self.shared.quirks_mode != QuirksMode::Quirks ||
+           self.within_functional_pseudo_class_argument {
+            return false;
+        }
+
+        let mut iter = if self.offset == 0 {
+            self.selector.iter()
+        } else {
+            self.selector.iter_from(self.offset)
+        };
+
+        return iter.all(|simple| {
+            match *simple {
+                Component::LocalName(_) |
+                Component::AttributeInNoNamespaceExists { .. } |
+                Component::AttributeInNoNamespace { .. } |
+                Component::AttributeOther(_) |
+                Component::ID(_) |
+                Component::Class(_) |
+                Component::PseudoElement(_) |
+                Component::Negation(_) |
+                Component::FirstChild |
+                Component::LastChild |
+                Component::OnlyChild |
+                Component::Empty |
+                Component::NthChild(_, _) |
+                Component::NthLastChild(_, _) |
+                Component::NthOfType(_, _) |
+                Component::NthLastOfType(_, _) |
+                Component::FirstOfType |
+                Component::LastOfType |
+                Component::OnlyOfType => false,
+                Component::NonTSPseudoClass(ref pseudo_class) => {
+                    Impl::is_active_or_hover(pseudo_class)
+                },
+                _ => true,
+            }
+        });
+    }
+}
+
 pub fn matches_selector_list<E>(selector_list: &SelectorList<E::Impl>,
                                 element: &E,
                                 context: &mut MatchingContext)
                                 -> bool
     where E: Element
 {
     selector_list.0.iter().any(|selector_and_hashes| {
         matches_selector(&selector_and_hashes.selector,
@@ -356,56 +461,59 @@ pub fn matches_selector<E, F>(selector: 
 {
     // Use the bloom filter to fast-reject.
     if let Some(filter) = context.bloom_filter {
         if !may_match::<E>(hashes, filter) {
             return false;
         }
     }
 
-    matches_complex_selector(selector, offset, element, context, flags_setter)
+    let mut local_context = LocalMatchingContext::new(context, selector);
+    matches_complex_selector(&selector, offset, element, &mut local_context, flags_setter)
 }
 
 /// Matches a complex selector.
 pub fn matches_complex_selector<E, F>(complex_selector: &Selector<E::Impl>,
                                       offset: usize,
                                       element: &E,
-                                      context: &mut MatchingContext,
+                                      mut context: &mut LocalMatchingContext<E::Impl>,
                                       flags_setter: &mut F)
                                       -> bool
     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.matching_mode == MatchingMode::ForStatelessPseudoElement {
+        if context.shared.matching_mode == MatchingMode::ForStatelessPseudoElement {
             assert!(iter.clone().any(|c| {
                 matches!(*c, Component::PseudoElement(..))
             }));
         }
     }
 
-    if context.matching_mode == MatchingMode::ForStatelessPseudoElement {
+    if context.shared.matching_mode == MatchingMode::ForStatelessPseudoElement {
         match *iter.next().unwrap() {
             // Stateful pseudo, just don't match.
             Component::NonTSPseudoClass(..) => return false,
             Component::PseudoElement(..) => {
                 // Pseudo, just eat the whole sequence.
                 let next = iter.next();
                 debug_assert!(next.is_none(),
                               "Someone messed up pseudo-element parsing?");
 
                 if iter.next_sequence().is_none() {
                     return true;
                 }
+                // Inform the context that the we've advanced to the next compound selector.
+                context.note_next_sequence(&mut iter);
             }
             _ => panic!("Used MatchingMode::ForStatelessPseudoElement in a non-pseudo selector"),
         }
     }
 
     match matches_complex_selector_internal(iter,
                                             element,
                                             context,
@@ -413,33 +521,35 @@ pub fn matches_complex_selector<E, F>(co
                                             flags_setter) {
         SelectorMatchingResult::Matched => true,
         _ => false
     }
 }
 
 fn matches_complex_selector_internal<E, F>(mut selector_iter: SelectorIter<E::Impl>,
                                            element: &E,
-                                           context: &mut MatchingContext,
+                                           context: &mut LocalMatchingContext<E::Impl>,
                                            relevant_link: &mut RelevantLinkStatus,
                                            flags_setter: &mut F)
                                            -> SelectorMatchingResult
      where E: Element,
            F: FnMut(&E, ElementSelectorFlags),
 {
-    *relevant_link = relevant_link.examine_potential_link(element, context);
+    *relevant_link = relevant_link.examine_potential_link(element, &mut context.shared);
 
     let matches_all_simple_selectors = selector_iter.all(|simple| {
         matches_simple_selector(simple, element, context, &relevant_link, flags_setter)
     });
 
     debug!("Matching for {:?}, simple selector {:?}, relevant link {:?}",
            element, selector_iter, relevant_link);
 
     let combinator = selector_iter.next_sequence();
+    // Inform the context that the we've advanced to the next compound selector.
+    context.note_next_sequence(&mut selector_iter);
     let siblings = combinator.map_or(false, |c| c.is_sibling());
     if siblings {
         flags_setter(element, HAS_SLOW_SELECTOR_LATER_SIBLINGS);
     }
 
     if !matches_all_simple_selectors {
         return SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling;
     }
@@ -512,27 +622,27 @@ fn matches_complex_selector_internal<E, 
     }
 }
 
 /// Determines whether the given element matches the given single selector.
 #[inline]
 fn matches_simple_selector<E, F>(
         selector: &Component<E::Impl>,
         element: &E,
-        context: &mut MatchingContext,
+        context: &mut LocalMatchingContext<E::Impl>,
         relevant_link: &RelevantLinkStatus,
         flags_setter: &mut F)
         -> bool
     where E: Element,
           F: FnMut(&E, ElementSelectorFlags),
 {
     match *selector {
         Component::Combinator(_) => unreachable!(),
         Component::PseudoElement(ref pseudo) => {
-            element.match_pseudo_element(pseudo, context)
+            element.match_pseudo_element(pseudo, context.shared)
         }
         Component::LocalName(LocalName { ref name, ref lower_name }) => {
             let is_html = element.is_html_element_in_html_document();
             element.get_local_name() == select_name(is_html, name, lower_name).borrow()
         }
         Component::ExplicitUniversalType |
         Component::ExplicitAnyNamespace => {
             true
@@ -646,17 +756,22 @@ fn matches_simple_selector<E, F>(
         Component::LastOfType => {
             matches_generic_nth_child(element, 0, 1, true, true, flags_setter)
         }
         Component::OnlyOfType => {
             matches_generic_nth_child(element, 0, 1, true, false, flags_setter) &&
             matches_generic_nth_child(element, 0, 1, true, true, flags_setter)
         }
         Component::Negation(ref negated) => {
-            !negated.iter().all(|ss| matches_simple_selector(ss, element, context, relevant_link, flags_setter))
+            let old_value = context.within_functional_pseudo_class_argument;
+            context.within_functional_pseudo_class_argument = true;
+            let result = !negated.iter().all(|ss| matches_simple_selector(ss, element, context,
+                                                             relevant_link, flags_setter));
+            context.within_functional_pseudo_class_argument = old_value;
+            result
         }
     }
 }
 
 fn select_name<'a, T>(is_html: bool, local_name: &'a T, local_name_lower: &'a T) -> &'a T {
     if is_html {
         local_name_lower
     } else {
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -98,16 +98,21 @@ macro_rules! with_all_bounds {
             type BorrowedLocalName: ?Sized + Eq;
 
             /// non tree-structural pseudo-classes
             /// (see: https://drafts.csswg.org/selectors/#structural-pseudos)
             type NonTSPseudoClass: $($CommonBounds)* + Sized + ToCss + SelectorMethods<Impl = Self>;
 
             /// pseudo-elements
             type PseudoElement: $($CommonBounds)* + PseudoElement<Impl = Self>;
+
+            /// Returns whether the selector matches conditions for the :active and
+            /// :hover quirk.
+            #[inline]
+            fn is_active_or_hover(pseudo_class: &Self::NonTSPseudoClass) -> bool;
         }
     }
 }
 
 macro_rules! with_bounds {
     ( [ $( $CommonBounds: tt )* ] [ $( $FromStr: tt )* ]) => {
         with_all_bounds! {
             [$($CommonBounds)* + $($FromStr)* + Display]
@@ -422,17 +427,17 @@ impl<Impl: SelectorImpl> Selector<Impl> 
         SelectorIter {
             iter: self.iter_raw(),
             next_combinator: None,
         }
     }
 
     pub fn iter_from(&self, offset: usize) -> SelectorIter<Impl> {
         // Note: selectors are stored left-to-right but logical order is right-to-left.
-        let iter = self.0.slice[..(self.0.slice.len() - offset)].iter().rev();
+        let iter = self.0.slice[..(self.len() - offset)].iter().rev();
         SelectorIter {
             iter: iter,
             next_combinator: None,
         }
     }
 
     /// Returns an iterator over the entire sequence of simple selectors and combinators,
     /// from right to left.
@@ -446,30 +451,40 @@ impl<Impl: SelectorImpl> Selector<Impl> 
         self.0.slice.iter()
     }
 
     /// Creates a Selector from a vec of Components. Used in tests.
     pub fn from_vec(vec: Vec<Component<Impl>>, specificity_and_flags: u32) -> Self {
         let header = HeaderWithLength::new(SpecificityAndFlags(specificity_and_flags), vec.len());
         Selector(Arc::into_thin(Arc::from_header_and_iter(header, vec.into_iter())))
     }
+
+    /// Returns count of simple selectors and combinators in the Selector.
+    pub fn len(&self) -> usize {
+        self.0.slice.len()
+    }
 }
 
 #[derive(Clone)]
 pub struct SelectorIter<'a, Impl: 'a + SelectorImpl> {
     iter: Rev<slice::Iter<'a, Component<Impl>>>,
     next_combinator: Option<Combinator>,
 }
 
 impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> {
     /// Prepares this iterator to point to the next sequence to the left,
     /// returning the combinator if the sequence was found.
     pub fn next_sequence(&mut self) -> Option<Combinator> {
         self.next_combinator.take()
     }
+
+    /// Returns remaining count of the simple selectors and combinators in the Selector.
+    pub fn selector_length(&self) -> usize {
+        self.iter.len()
+    }
 }
 
 impl<'a, Impl: SelectorImpl> Iterator for SelectorIter<'a, Impl> {
     type Item = &'a Component<Impl>;
     fn next(&mut self) -> Option<Self::Item> {
         debug_assert!(self.next_combinator.is_none(),
                       "You should call next_sequence!");
         match self.iter.next() {
--- a/servo/components/selectors/tree.rs
+++ b/servo/components/selectors/tree.rs
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Traits that nodes must implement. Breaks the otherwise-cyclic dependency
 //! between layout and style.
 
 use attr::{AttrSelectorOperation, NamespaceConstraint};
-use matching::{ElementSelectorFlags, MatchingContext, RelevantLinkStatus};
+use matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, RelevantLinkStatus};
 use parser::SelectorImpl;
 use std::fmt::Debug;
 
 pub trait Element: Sized + Debug {
     type Impl: SelectorImpl;
 
     fn parent_element(&self) -> Option<Self>;
 
@@ -45,17 +45,17 @@ pub trait Element: Sized + Debug {
     fn attr_matches(&self,
                     ns: &NamespaceConstraint<&<Self::Impl as SelectorImpl>::NamespaceUrl>,
                     local_name: &<Self::Impl as SelectorImpl>::LocalName,
                     operation: &AttrSelectorOperation<&<Self::Impl as SelectorImpl>::AttrValue>)
                     -> bool;
 
     fn match_non_ts_pseudo_class<F>(&self,
                                     pc: &<Self::Impl as SelectorImpl>::NonTSPseudoClass,
-                                    context: &mut MatchingContext,
+                                    context: &mut LocalMatchingContext<Self::Impl>,
                                     relevant_link: &RelevantLinkStatus,
                                     flags_setter: &mut F) -> bool
         where F: FnMut(&Self, ElementSelectorFlags);
 
     fn match_pseudo_element(&self,
                             pe: &<Self::Impl as SelectorImpl>::PseudoElement,
                             context: &mut MatchingContext)
                             -> bool;
--- a/servo/components/style/context.rs
+++ b/servo/components/style/context.rs
@@ -28,46 +28,34 @@ use std::ops::Add;
 #[cfg(feature = "servo")] use std::sync::mpsc::Sender;
 use stylearc::Arc;
 use stylist::Stylist;
 use thread_state;
 use time;
 use timer::Timer;
 use traversal::{DomTraversal, TraversalFlags};
 
+pub use selectors::matching::QuirksMode;
+
 /// This structure is used to create a local style context from a shared one.
 #[cfg(feature = "servo")]
 pub struct ThreadLocalStyleContextCreationInfo {
     new_animations_sender: Sender<Animation>,
 }
 
 #[cfg(feature = "servo")]
 impl ThreadLocalStyleContextCreationInfo {
     /// Trivially constructs a `ThreadLocalStyleContextCreationInfo`.
     pub fn new(animations_sender: Sender<Animation>) -> Self {
         ThreadLocalStyleContextCreationInfo {
             new_animations_sender: animations_sender,
         }
     }
 }
 
-/// Which quirks mode is this document in.
-///
-/// See: https://quirks.spec.whatwg.org/
-#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub enum QuirksMode {
-    /// Quirks mode.
-    Quirks,
-    /// Limited quirks mode.
-    LimitedQuirks,
-    /// No quirks mode.
-    NoQuirks,
-}
-
 /// A global options structure for the style system. We use this instead of
 /// opts to abstract across Gecko and Servo.
 #[derive(Clone)]
 pub struct StyleSystemOptions {
     /// Whether the style sharing cache is disabled.
     pub disable_style_sharing_cache: bool,
     /// Whether we should dump statistics about the style system.
     pub dump_style_statistics: bool,
--- a/servo/components/style/gecko/selector_parser.rs
+++ b/servo/components/style/gecko/selector_parser.rs
@@ -232,16 +232,22 @@ impl ::selectors::SelectorImpl for Selec
     type LocalName = Atom;
     type NamespacePrefix = Atom;
     type NamespaceUrl = Namespace;
     type BorrowedNamespaceUrl = WeakNamespace;
     type BorrowedLocalName = WeakAtom;
 
     type PseudoElement = PseudoElement;
     type NonTSPseudoClass = NonTSPseudoClass;
+
+    #[inline]
+    fn is_active_or_hover(pseudo_class: &Self::NonTSPseudoClass) -> bool {
+        matches!(*pseudo_class, NonTSPseudoClass::Active |
+                                NonTSPseudoClass::Hover)
+    }
 }
 
 impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
     type Impl = SelectorImpl;
     type Error = StyleParseError<'i>;
 
     fn parse_non_ts_pseudo_class(&self, name: Cow<'i, str>)
                                  -> Result<NonTSPseudoClass, ParseError<'i>> {
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -67,17 +67,17 @@ use media_queries::Device;
 use properties::{ComputedValues, parse_style_attribute};
 use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock};
 use properties::animated_properties::{AnimationValue, AnimationValueMap, TransitionProperty};
 use properties::style_structs::Font;
 use rule_tree::CascadeLevel as ServoCascadeLevel;
 use selector_parser::{AttrValue, ElementExt, PseudoClassStringArg};
 use selectors::Element;
 use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator, CaseSensitivity, NamespaceConstraint};
-use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
+use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext};
 use selectors::matching::{RelevantLinkStatus, VisitedHandlingMode};
 use shared_lock::Locked;
 use sink::Push;
 use smallvec::VecLike;
 use std::cell::RefCell;
 use std::collections::HashMap;
 use std::fmt;
 use std::hash::{Hash, Hasher};
@@ -1419,28 +1419,25 @@ impl<'le> ::selectors::Element for Gecko
     fn get_namespace(&self) -> &WeakNamespace {
         unsafe {
             WeakNamespace::new(Gecko_Namespace(self.0))
         }
     }
 
     fn match_non_ts_pseudo_class<F>(&self,
                                     pseudo_class: &NonTSPseudoClass,
-                                    context: &mut MatchingContext,
+                                    context: &mut LocalMatchingContext<Self::Impl>,
                                     relevant_link: &RelevantLinkStatus,
                                     flags_setter: &mut F)
                                     -> bool
         where F: FnMut(&Self, ElementSelectorFlags),
     {
         use selectors::matching::*;
         match *pseudo_class {
-            NonTSPseudoClass::AnyLink |
-            NonTSPseudoClass::Active |
             NonTSPseudoClass::Focus |
-            NonTSPseudoClass::Hover |
             NonTSPseudoClass::Enabled |
             NonTSPseudoClass::Disabled |
             NonTSPseudoClass::Checked |
             NonTSPseudoClass::Fullscreen |
             NonTSPseudoClass::Indeterminate |
             NonTSPseudoClass::PlaceholderShown |
             NonTSPseudoClass::Target |
             NonTSPseudoClass::Valid |
@@ -1472,22 +1469,31 @@ impl<'le> ::selectors::Element for Gecko
             NonTSPseudoClass::Default |
             NonTSPseudoClass::MozSubmitInvalid |
             NonTSPseudoClass::MozUIInvalid |
             NonTSPseudoClass::MozMeterOptimum |
             NonTSPseudoClass::MozMeterSubOptimum |
             NonTSPseudoClass::MozMeterSubSubOptimum |
             NonTSPseudoClass::MozAutofill |
             NonTSPseudoClass::MozAutofillPreview => {
-                // NB: It's important to use `intersect` instead of `contains`
-                // here, to handle `:any-link` correctly.
+                /// FIXME: This can/should probably be contains() now that any-link
+                // (which depends in multiple bits) is handled in its own case below.
                 self.get_state().intersects(pseudo_class.state_flag())
             },
-            NonTSPseudoClass::Link => relevant_link.is_unvisited(self, context),
-            NonTSPseudoClass::Visited => relevant_link.is_visited(self, context),
+            NonTSPseudoClass::AnyLink => self.is_link(),
+            NonTSPseudoClass::Link => relevant_link.is_unvisited(self, context.shared),
+            NonTSPseudoClass::Visited => relevant_link.is_visited(self, context.shared),
+            NonTSPseudoClass::Active |
+            NonTSPseudoClass::Hover => {
+                if context.active_hover_quirk_matches() && !self.is_link() {
+                    false
+                } else {
+                    self.get_state().contains(pseudo_class.state_flag())
+                }
+            },
             NonTSPseudoClass::MozFirstNode => {
                 flags_setter(self, HAS_EDGE_CHILD_SELECTOR);
                 let mut elem = self.as_node();
                 while let Some(prev) = elem.prev_sibling() {
                     if prev.contains_non_whitespace_content() {
                         return false
                     }
                     elem = prev;
@@ -1517,19 +1523,23 @@ impl<'le> ::selectors::Element for Gecko
             NonTSPseudoClass::MozNativeAnonymous => unsafe {
                 Gecko_MatchesElement(pseudo_class.to_gecko_pseudoclasstype().unwrap(), self.0)
             },
             NonTSPseudoClass::MozIsHTML => {
                 self.is_html_element_in_html_document()
             }
             NonTSPseudoClass::MozPlaceholder => false,
             NonTSPseudoClass::MozAny(ref sels) => {
-                sels.iter().any(|s| {
+                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)
-                })
+                });
+                context.within_functional_pseudo_class_argument = old_value;
+                result
             }
             NonTSPseudoClass::Lang(ref lang_arg) => {
                 self.match_element_lang(None, lang_arg)
             }
             NonTSPseudoClass::MozSystemMetric(ref s) |
             NonTSPseudoClass::MozLocaleDir(ref s) |
             NonTSPseudoClass::MozEmptyExceptChildrenWithLocalname(ref s) |
             NonTSPseudoClass::Dir(ref s) => {
@@ -1558,21 +1568,17 @@ impl<'le> ::selectors::Element for Gecko
         match self.implemented_pseudo_element() {
             Some(ref pseudo) => *pseudo == pseudo_element.canonical(),
             None => false,
         }
     }
 
     #[inline]
     fn is_link(&self) -> bool {
-        let mut context = MatchingContext::new(MatchingMode::Normal, None);
-        self.match_non_ts_pseudo_class(&NonTSPseudoClass::AnyLink,
-                                       &mut context,
-                                       &RelevantLinkStatus::default(),
-                                       &mut |_, _| {})
+        self.get_state().intersects(NonTSPseudoClass::AnyLink.state_flag())
     }
 
     fn get_id(&self) -> Option<Atom> {
         if !self.has_id() {
             return None;
         }
 
         let ptr = unsafe {
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -1016,17 +1016,18 @@ pub trait MatchMethods : TElement {
         } else {
             RuleInclusion::All
         };
 
         let bloom_filter = context.thread_local.bloom_filter.filter();
         let mut matching_context =
             MatchingContext::new_for_visited(MatchingMode::Normal,
                                              Some(bloom_filter),
-                                             visited_handling);
+                                             visited_handling,
+                                             context.shared.quirks_mode);
 
         {
             let smil_override = data.get_smil_override();
             let animation_rules = if self.may_have_animations() {
                 data.get_animation_rules()
             } else {
                 AnimationRules(None, None)
             };
@@ -1112,17 +1113,18 @@ pub trait MatchMethods : TElement {
             RuleInclusion::All
         };
 
         let bloom_filter = context.thread_local.bloom_filter.filter();
 
         let mut matching_context =
             MatchingContext::new_for_visited(MatchingMode::ForStatelessPseudoElement,
                                              Some(bloom_filter),
-                                             visited_handling);
+                                             visited_handling,
+                                             context.shared.quirks_mode);
 
         // Compute rule nodes for eagerly-cascaded pseudo-elements.
         let mut matches_different_pseudos = false;
         SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
             // For pseudo-elements, we only try to match visited rules if there
             // are also unvisited rules.  (This matches Gecko's behavior.)
             if visited_handling == VisitedHandlingMode::RelevantLinkVisited &&
                !data.styles().pseudos.has(&pseudo) {
--- a/servo/components/style/restyle_hints.rs
+++ b/servo/components/style/restyle_hints.rs
@@ -15,17 +15,17 @@ use element_state::*;
 #[cfg(feature = "gecko")]
 use gecko_bindings::structs::nsRestyleHint;
 #[cfg(feature = "servo")]
 use heapsize::HeapSizeOf;
 use selector_map::{SelectorMap, SelectorMapEntry};
 use selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl, Snapshot, SnapshotMap, AttrValue};
 use selectors::Element;
 use selectors::attr::{AttrSelectorOperation, NamespaceConstraint};
-use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
+use selectors::matching::{ElementSelectorFlags, LocalMatchingContext, MatchingContext, MatchingMode};
 use selectors::matching::{RelevantLinkStatus, VisitedHandlingMode, matches_selector};
 use selectors::parser::{AncestorHashes, Combinator, Component};
 use selectors::parser::{Selector, SelectorAndHashes, SelectorIter, SelectorMethods};
 use selectors::visitor::SelectorVisitor;
 use smallvec::SmallVec;
 use std::cell::Cell;
 use std::clone::Clone;
 use std::cmp;
@@ -659,17 +659,17 @@ fn dir_selector_to_state(s: &[u16]) -> E
 
 impl<'a, E> Element for ElementWrapper<'a, E>
     where E: TElement,
 {
     type Impl = SelectorImpl;
 
     fn match_non_ts_pseudo_class<F>(&self,
                                     pseudo_class: &NonTSPseudoClass,
-                                    context: &mut MatchingContext,
+                                    context: &mut LocalMatchingContext<Self::Impl>,
                                     relevant_link: &RelevantLinkStatus,
                                     _setter: &mut F)
                                     -> bool
         where F: FnMut(&Self, ElementSelectorFlags),
     {
         // Some pseudo-classes need special handling to evaluate them against
         // the snapshot.
         match *pseudo_class {
@@ -702,20 +702,20 @@ impl<'a, E> Element for ElementWrapper<'
                 };
                 return state.contains(selector_flag);
             }
 
             // For :link and :visited, we don't actually want to test the element
             // state directly.  Instead, we use the `relevant_link` to determine if
             // they match.
             NonTSPseudoClass::Link => {
-                return relevant_link.is_unvisited(self, context);
+                return relevant_link.is_unvisited(self, context.shared);
             }
             NonTSPseudoClass::Visited => {
-                return relevant_link.is_visited(self, context);
+                return relevant_link.is_visited(self, context.shared);
             }
 
             #[cfg(feature = "gecko")]
             NonTSPseudoClass::MozTableBorderNonzero => {
                 if let Some(snapshot) = self.snapshot() {
                     if snapshot.has_other_pseudo_class_state() {
                         return snapshot.mIsTableBorderNonzero();
                     }
@@ -762,21 +762,17 @@ impl<'a, E> Element for ElementWrapper<'
                             pseudo_element: &PseudoElement,
                             context: &mut MatchingContext)
                             -> bool
     {
         self.element.match_pseudo_element(pseudo_element, context)
     }
 
     fn is_link(&self) -> bool {
-        let mut context = MatchingContext::new(MatchingMode::Normal, None);
-        self.match_non_ts_pseudo_class(&NonTSPseudoClass::AnyLink,
-                                       &mut context,
-                                       &RelevantLinkStatus::default(),
-                                       &mut |_, _| {})
+        self.element.is_link()
     }
 
     fn parent_element(&self) -> Option<Self> {
         self.element.parent_element()
             .map(|e| ElementWrapper::new(e, self.snapshot_map))
     }
 
     fn first_child_element(&self) -> Option<Self> {
@@ -1197,27 +1193,29 @@ impl DependencySet {
             // filter, and as such we may fast-reject selectors incorrectly.
             //
             // We may be able to improve this if we record as we go down the
             // tree whether any parent had a snapshot, and whether those
             // snapshots were taken due to an element class/id change, but it's
             // not clear we _need_ it right now.
             let mut then_context =
                 MatchingContext::new_for_visited(MatchingMode::Normal, None,
-                                                 VisitedHandlingMode::AllLinksUnvisited);
+                                                 VisitedHandlingMode::AllLinksUnvisited,
+                                                 shared_context.quirks_mode);
             let matched_then =
                 matches_selector(&dep.selector,
                                  dep.selector_offset,
                                  &dep.hashes,
                                  &snapshot_el,
                                  &mut then_context,
                                  &mut |_, _| {});
             let mut now_context =
                 MatchingContext::new_for_visited(MatchingMode::Normal, bloom_filter,
-                                                 VisitedHandlingMode::AllLinksUnvisited);
+                                                 VisitedHandlingMode::AllLinksUnvisited,
+                                                 shared_context.quirks_mode);
             let matches_now =
                 matches_selector(&dep.selector,
                                  dep.selector_offset,
                                  &dep.hashes,
                                  el,
                                  &mut now_context,
                                  &mut |_, _| {});
 
--- a/servo/components/style/servo/selector_parser.rs
+++ b/servo/components/style/servo/selector_parser.rs
@@ -294,16 +294,22 @@ impl ::selectors::SelectorImpl for Selec
     type AttrValue = String;
     type Identifier = Atom;
     type ClassName = Atom;
     type LocalName = LocalName;
     type NamespacePrefix = Prefix;
     type NamespaceUrl = Namespace;
     type BorrowedLocalName = LocalName;
     type BorrowedNamespaceUrl = Namespace;
+
+    #[inline]
+    fn is_active_or_hover(pseudo_class: &Self::NonTSPseudoClass) -> bool {
+        matches!(*pseudo_class, NonTSPseudoClass::Active |
+                                NonTSPseudoClass::Hover)
+    }
 }
 
 impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
     type Impl = SelectorImpl;
     type Error = StyleParseError<'i>;
 
     fn parse_non_ts_pseudo_class(&self, name: Cow<'i, str>)
                                  -> Result<NonTSPseudoClass, ParseError<'i>> {
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -83,16 +83,17 @@ pub struct Stylist {
 
     /// Viewport constraints based on the current device.
     viewport_constraints: Option<ViewportConstraints>,
 
     /// Effective media query results cached from the last rebuild.
     effective_media_query_results: EffectiveMediaQueryResults,
 
     /// If true, the quirks-mode stylesheet is applied.
+    #[cfg_attr(feature = "servo", ignore_heap_size_of = "defined in selectors")]
     quirks_mode: QuirksMode,
 
     /// If true, the device has changed, and the stylist needs to be updated.
     is_device_dirty: bool,
 
     /// If true, the stylist is in a cleared state (e.g. just-constructed, or
     /// had clear() called on it with no following rebuild()).
     is_cleared: bool,
@@ -730,17 +731,19 @@ impl Stylist {
                     unsafe { p.set_selector_flags(parent_flags); }
                 }
             }
         };
 
         // Bug 1364242: We need to add visited support for lazy pseudos
         let mut declarations = ApplicableDeclarationList::new();
         let mut matching_context =
-            MatchingContext::new(MatchingMode::ForStatelessPseudoElement, None);
+            MatchingContext::new(MatchingMode::ForStatelessPseudoElement,
+                                 None,
+                                 self.quirks_mode);
         self.push_applicable_declarations(element,
                                           Some(&pseudo),
                                           None,
                                           None,
                                           AnimationRules(None, None),
                                           rule_inclusion,
                                           &mut declarations,
                                           &mut matching_context,
@@ -922,17 +925,17 @@ impl Stylist {
     /// treating us as an XBL stylesheet-only stylist.
     pub fn push_applicable_declarations_as_xbl_only_stylist<E, V>(&self,
                                                                   element: &E,
                                                                   applicable_declarations: &mut V)
         where E: TElement,
               V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock>,
     {
         let mut matching_context =
-            MatchingContext::new(MatchingMode::Normal, None);
+            MatchingContext::new(MatchingMode::Normal, None, self.quirks_mode);
         let mut dummy_flag_setter = |_: &E, _: ElementSelectorFlags| {};
 
         self.element_map.author.get_all_matching_rules(element,
                                                        element,
                                                        applicable_declarations,
                                                        &mut matching_context,
                                                        &mut dummy_flag_setter,
                                                        CascadeLevel::XBL);
@@ -1151,17 +1154,17 @@ impl Stylist {
                                               flags_setter: &mut F)
                                               -> BitVec
         where E: TElement,
               F: FnMut(&E, ElementSelectorFlags),
     {
         // NB: `MatchingMode` doesn't really matter, given we don't share style
         // between pseudos.
         let mut matching_context =
-            MatchingContext::new(MatchingMode::Normal, bloom);
+            MatchingContext::new(MatchingMode::Normal, bloom, self.quirks_mode);
 
         // Note that, by the time we're revalidating, we're guaranteed that the
         // candidate and the entry have the same id, classes, and local name.
         // This means we're guaranteed to get the same rulehash buckets for all
         // the lookups, which means that the bitvecs are comparable. We verify
         // this in the caller by asserting that the bitvecs are same-length.
         let mut results = BitVec::new();
         self.selectors_for_cache_revalidation.lookup(*element, &mut |selector_and_hashes| {