servo: Merge #19817 - style: Track the visited-handling-mode on the MatchingContext (from emilio:matching-context-visited); r=nox
authorEmilio Cobos Álvarez <emilio@crisal.io>
Fri, 19 Jan 2018 12:50:00 -0600
changeset 454346 bcd8e6c6d25d37765ced83d6cf810766e1226961
parent 454345 5f76c1965c7cd7836dcb6b8f7221e84773d3db05
child 454347 23fd1f0f03b3eeca64ea87cf17158e2ed0a71d73
push id1648
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 12:45:47 +0000
treeherdermozilla-release@cbb9688c2eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnox
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
servo: Merge #19817 - style: Track the visited-handling-mode on the MatchingContext (from emilio:matching-context-visited); r=nox This fixes bugs where we're not passing the value around correctly, like from ::-moz-any. This is a fix for https://bugzilla.mozilla.org/show_bug.cgi?id=1431539. Source-Repo: https://github.com/servo/servo Source-Revision: 0d7d02fca772a407d4ffa1ebd1e03d60f385eb74
servo/components/layout_thread/dom_wrapper.rs
servo/components/script/dom/element.rs
servo/components/selectors/context.rs
servo/components/selectors/matching.rs
servo/components/selectors/tree.rs
servo/components/style/gecko/wrapper.rs
servo/components/style/invalidation/element/element_wrapper.rs
servo/components/style/stylist.rs
--- a/servo/components/layout_thread/dom_wrapper.rs
+++ b/servo/components/layout_thread/dom_wrapper.rs
@@ -708,17 +708,16 @@ impl<'le> ::selectors::Element for Servo
     ) -> bool {
         false
     }
 
     fn match_non_ts_pseudo_class<F>(
         &self,
         pseudo_class: &NonTSPseudoClass,
         _: &mut MatchingContext<Self::Impl>,
-        _: VisitedHandlingMode,
         _: &mut F,
     ) -> bool
     where
         F: FnMut(&Self, ElementSelectorFlags),
     {
         match *pseudo_class {
             // https://github.com/servo/servo/issues/8718
             NonTSPseudoClass::Link |
@@ -1216,17 +1215,16 @@ impl<'le> ::selectors::Element for Servo
             }
         }
     }
 
     fn match_non_ts_pseudo_class<F>(
         &self,
         _: &NonTSPseudoClass,
         _: &mut MatchingContext<Self::Impl>,
-        _: VisitedHandlingMode,
         _: &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/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -84,17 +84,16 @@ use html5ever::serialize::TraversalScope
 use js::jsapi::Heap;
 use js::jsval::JSVal;
 use net_traits::request::CorsSettings;
 use ref_filter_map::ref_filter_map;
 use script_layout_interface::message::ReflowGoal;
 use script_thread::ScriptThread;
 use selectors::Element as SelectorsElement;
 use selectors::attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity};
-use selectors::context::VisitedHandlingMode;
 use selectors::matching::{ElementSelectorFlags, MatchingContext};
 use selectors::sink::Push;
 use servo_arc::Arc;
 use servo_atoms::Atom;
 use std::borrow::Cow;
 use std::cell::{Cell, Ref};
 use std::default::Default;
 use std::fmt;
@@ -2631,17 +2630,16 @@ impl<'a> SelectorsElement for DomRoot<El
     fn get_namespace(&self) -> &Namespace {
         self.namespace()
     }
 
     fn match_non_ts_pseudo_class<F>(
         &self,
         pseudo_class: &NonTSPseudoClass,
         _: &mut MatchingContext<Self::Impl>,
-        _: VisitedHandlingMode,
         _: &mut F,
     ) -> bool
     where
         F: FnMut(&Self, ElementSelectorFlags),
     {
         match *pseudo_class {
             // https://github.com/servo/servo/issues/8718
             NonTSPseudoClass::Link |
--- a/servo/components/selectors/context.rs
+++ b/servo/components/selectors/context.rs
@@ -100,36 +100,37 @@ where
     Impl: SelectorImpl,
 {
     /// Input with the matching mode we should use when matching selectors.
     pub matching_mode: MatchingMode,
     /// Input with the bloom filter used to fast-reject selectors.
     pub bloom_filter: Option<&'a BloomFilter>,
     /// An optional cache to speed up nth-index-like selectors.
     pub nth_index_cache: Option<&'a mut NthIndexCache>,
-    /// Input that controls how matching for links is handled.
-    pub visited_handling: VisitedHandlingMode,
     /// The element which is going to match :scope pseudo-class. It can be
     /// either one :scope element, or the scoping element.
     ///
     /// Note that, although in theory there can be multiple :scope elements,
     /// in current specs, at most one is specified, and when there is one,
     /// scoping element is not relevant anymore, so we use a single field for
     /// them.
     ///
     /// When this is None, :scope will match the root element.
     ///
     /// See https://drafts.csswg.org/selectors-4/#scope-pseudo
     pub scope_element: Option<OpaqueElement>,
 
+    /// Controls how matching for links is handled.
+    visited_handling: VisitedHandlingMode,
+
     /// The current nesting level of selectors that we're matching.
     ///
-    /// FIXME(emilio): Move this somewhere else and make MatchingContext
-    /// immutable again.
-    pub nesting_level: usize,
+    /// FIXME(emilio): Consider putting the mutable stuff in a Cell, then make
+    /// MatchingContext immutable again.
+    nesting_level: usize,
 
     /// An optional hook function for checking whether a pseudo-element
     /// should match when matching_mode is ForStatelessPseudoElement.
     pub pseudo_element_matching_fn: Option<&'a Fn(&Impl::PseudoElement) -> bool>,
 
     /// Extra implementation-dependent matching data.
     pub extra_data: Impl::ExtraMatchingData,
 
@@ -176,20 +177,60 @@ where
             scope_element: None,
             nesting_level: 0,
             pseudo_element_matching_fn: None,
             extra_data: Default::default(),
             _impl: ::std::marker::PhantomData,
         }
     }
 
+    /// Whether we're matching a nested selector.
+    #[inline]
+    pub fn is_nested(&self) -> bool {
+        self.nesting_level != 0
+    }
+
     /// The quirks mode of the document.
     #[inline]
     pub fn quirks_mode(&self) -> QuirksMode {
         self.quirks_mode
     }
 
     /// The case-sensitivity for class and ID selectors
     #[inline]
     pub fn classes_and_ids_case_sensitivity(&self) -> CaseSensitivity {
         self.classes_and_ids_case_sensitivity
     }
+
+    /// Runs F with a deeper nesting level.
+    #[inline]
+    pub fn nest<F, R>(&mut self, f: F) -> R
+    where
+        F: FnOnce(&mut Self) -> R,
+    {
+        self.nesting_level += 1;
+        let result = f(self);
+        self.nesting_level -= 1;
+        result
+    }
+
+    #[inline]
+    pub fn visited_handling(&self) -> VisitedHandlingMode {
+        self.visited_handling
+    }
+
+    /// Runs F with a different VisitedHandlingMode.
+    #[inline]
+    pub fn with_visited_handling_mode<F, R>(
+        &mut self,
+        handling_mode: VisitedHandlingMode,
+        f: F,
+    ) -> R
+    where
+        F: FnOnce(&mut Self) -> R,
+    {
+        let original_handling_mode = self.visited_handling;
+        self.visited_handling = handling_mode;
+        let result = f(self);
+        self.visited_handling = original_handling_mode;
+        result
+    }
 }
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -55,18 +55,17 @@ impl ElementSelectorFlags {
                 ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS |
                 ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR)
     }
 }
 
 /// Holds per-compound-selector data.
 struct LocalMatchingContext<'a, 'b: 'a, Impl: SelectorImpl> {
     shared: &'a mut MatchingContext<'b, Impl>,
-    matches_hover_and_active_quirk: bool,
-    visited_handling: VisitedHandlingMode,
+    matches_hover_and_active_quirk: MatchesHoverAndActiveQuirk,
 }
 
 #[inline(always)]
 pub fn matches_selector_list<E>(
     selector_list: &SelectorList<E::Impl>,
     element: &E,
     context: &mut MatchingContext<E::Impl>,
 ) -> bool
@@ -166,16 +165,25 @@ fn may_match(hashes: &AncestorHashes, bf
 #[derive(Clone, Copy, Eq, PartialEq)]
 enum SelectorMatchingResult {
     Matched,
     NotMatchedAndRestartFromClosestLaterSibling,
     NotMatchedAndRestartFromClosestDescendant,
     NotMatchedGlobally,
 }
 
+/// Whether the :hover and :active quirk applies.
+///
+/// https://quirks.spec.whatwg.org/#the-active-and-hover-quirk
+#[derive(Clone, Copy, Debug, PartialEq)]
+enum MatchesHoverAndActiveQuirk {
+    Yes,
+    No,
+}
+
 /// 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
 /// 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).
@@ -231,21 +239,19 @@ pub fn matches_compound_selector_from<E>
 ) -> CompoundSelectorMatchingResult
 where
     E: Element
 {
     if cfg!(debug_assertions) && from_offset != 0 {
         selector.combinator_at_parse_order(from_offset - 1); // This asserts.
     }
 
-    let visited_handling = context.visited_handling;
     let mut local_context = LocalMatchingContext {
         shared: context,
-        visited_handling,
-        matches_hover_and_active_quirk: false,
+        matches_hover_and_active_quirk: MatchesHoverAndActiveQuirk::No,
     };
 
     for component in selector.iter_raw_parse_order_from(from_offset) {
         if matches!(*component, Component::Combinator(..)) {
             debug_assert_ne!(from_offset, 0, "Selector started with a combinator?");
             return CompoundSelectorMatchingResult::Matched {
                 next_combinator_offset: from_offset,
             }
@@ -275,17 +281,17 @@ pub fn matches_complex_selector<E, F>(
 ) -> bool
 where
     E: Element,
     F: FnMut(&E, ElementSelectorFlags),
 {
     // If this is the special pseudo-element mode, consume the ::pseudo-element
     // before proceeding, since the caller has already handled that part.
     if context.matching_mode == MatchingMode::ForStatelessPseudoElement &&
-        context.nesting_level == 0 {
+        !context.is_nested() {
         // Consume the pseudo.
         match *iter.next().unwrap() {
             Component::PseudoElement(ref pseudo) => {
                 if let Some(ref f) = context.pseudo_element_matching_fn {
                     if !f(pseudo) {
                         return false;
                     }
                 }
@@ -307,54 +313,52 @@ where
 
         // Advance to the non-pseudo-element part of the selector, but let the
         // context note that .
         if iter.next_sequence().is_none() {
             return true;
         }
     }
 
-    let visited_handling = context.visited_handling;
     let result = matches_complex_selector_internal(
         iter,
         element,
         context,
-        visited_handling,
         flags_setter,
         Rightmost::Yes,
     );
 
     match result {
         SelectorMatchingResult::Matched => true,
         _ => false
     }
 }
 
 #[inline]
 fn matches_hover_and_active_quirk<Impl: SelectorImpl>(
     selector_iter: &SelectorIter<Impl>,
     context: &MatchingContext<Impl>,
     rightmost: Rightmost,
-) -> bool {
+) -> MatchesHoverAndActiveQuirk {
     if context.quirks_mode() != QuirksMode::Quirks {
-        return false;
+        return MatchesHoverAndActiveQuirk::No;
     }
 
-    if context.nesting_level != 0 {
-        return false;
+    if context.is_nested() {
+        return MatchesHoverAndActiveQuirk::No;
     }
 
     // This compound selector had a pseudo-element to the right that we
     // intentionally skipped.
-    if matches!(rightmost, Rightmost::Yes) &&
+    if rightmost == Rightmost::Yes &&
         context.matching_mode == MatchingMode::ForStatelessPseudoElement {
-        return false;
+        return MatchesHoverAndActiveQuirk::No;
     }
 
-    selector_iter.clone().all(|simple| {
+    let all_match = selector_iter.clone().all(|simple| {
         match *simple {
             Component::LocalName(_) |
             Component::AttributeInNoNamespaceExists { .. } |
             Component::AttributeInNoNamespace { .. } |
             Component::AttributeOther(_) |
             Component::ID(_) |
             Component::Class(_) |
             Component::PseudoElement(_) |
@@ -370,19 +374,26 @@ fn matches_hover_and_active_quirk<Impl: 
             Component::FirstOfType |
             Component::LastOfType |
             Component::OnlyOfType => false,
             Component::NonTSPseudoClass(ref pseudo_class) => {
                 Impl::is_active_or_hover(pseudo_class)
             },
             _ => true,
         }
-    })
+    });
+
+    if all_match {
+        MatchesHoverAndActiveQuirk::Yes
+    } else {
+        MatchesHoverAndActiveQuirk::No
+    }
 }
 
+#[derive(Clone, Copy, PartialEq)]
 enum Rightmost {
     Yes,
     No,
 }
 
 #[inline(always)]
 fn next_element_for_combinator<E>(
     element: &E,
@@ -412,31 +423,29 @@ where
         }
     }
 }
 
 fn matches_complex_selector_internal<E, F>(
     mut selector_iter: SelectorIter<E::Impl>,
     element: &E,
     context: &mut MatchingContext<E::Impl>,
-    visited_handling: VisitedHandlingMode,
     flags_setter: &mut F,
     rightmost: Rightmost,
 ) -> SelectorMatchingResult
 where
     E: Element,
     F: FnMut(&E, ElementSelectorFlags),
 {
     debug!("Matching complex selector {:?} for {:?}", selector_iter, element);
 
     let matches_compound_selector = matches_compound_selector(
         &mut selector_iter,
         element,
         context,
-        visited_handling,
         flags_setter,
         rightmost
     );
 
     let combinator = selector_iter.next_sequence();
     if combinator.map_or(false, |c| c.is_sibling()) {
         flags_setter(element, ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS);
     }
@@ -462,37 +471,38 @@ where
             SelectorMatchingResult::NotMatchedGlobally
         }
     };
 
     let mut next_element = next_element_for_combinator(element, combinator);
 
     // Stop matching :visited as soon as we find a link, or a combinator for
     // something that isn't an ancestor.
-    let mut visited_handling =
-        if element.is_link() || combinator.is_sibling() {
-            VisitedHandlingMode::AllLinksUnvisited
-        } else {
-            visited_handling
-        };
+    let mut visited_handling = if element.is_link() || combinator.is_sibling() {
+        VisitedHandlingMode::AllLinksUnvisited
+    } else {
+        context.visited_handling()
+    };
 
     loop {
         let element = match next_element {
             None => return candidate_not_found,
             Some(next_element) => next_element,
         };
 
-        let result = matches_complex_selector_internal(
-            selector_iter.clone(),
-            &element,
-            context,
-            visited_handling,
-            flags_setter,
-            Rightmost::No,
-        );
+        let result =
+            context.with_visited_handling_mode(visited_handling, |context| {
+                matches_complex_selector_internal(
+                    selector_iter.clone(),
+                    &element,
+                    context,
+                    flags_setter,
+                    Rightmost::No,
+                )
+            });
 
         match (result, combinator) {
             // Return the status immediately.
             (SelectorMatchingResult::Matched, _) |
             (SelectorMatchingResult::NotMatchedGlobally, _) |
             (_, Combinator::NextSibling) => {
                 return result;
             }
@@ -516,22 +526,19 @@ where
             // NotMatchedAndRestartFromClosestLaterSibling or
             // NotMatchedAndRestartFromClosestDescendant, or the
             // Combinator::LaterSibling combinator and the status is
             // NotMatchedAndRestartFromClosestDescendant, we can continue to
             // matching on the next candidate element.
             _ => {},
         }
 
-        visited_handling =
-            if element.is_link() || combinator.is_sibling() {
-                VisitedHandlingMode::AllLinksUnvisited
-            } else {
-                visited_handling
-            };
+        if element.is_link() || combinator.is_sibling() {
+            visited_handling = VisitedHandlingMode::AllLinksUnvisited;
+        }
 
         next_element = next_element_for_combinator(&element, combinator);
     }
 }
 
 #[inline]
 fn matches_local_name<E>(
     element: &E,
@@ -549,17 +556,16 @@ where
 }
 
 /// Determines whether the given element matches the given compound selector.
 #[inline]
 fn matches_compound_selector<E, F>(
     selector_iter: &mut SelectorIter<E::Impl>,
     element: &E,
     context: &mut MatchingContext<E::Impl>,
-    visited_handling: VisitedHandlingMode,
     flags_setter: &mut F,
     rightmost: Rightmost,
 ) -> bool
 where
     E: Element,
     F: FnMut(&E, ElementSelectorFlags),
 {
     let matches_hover_and_active_quirk =
@@ -591,17 +597,16 @@ where
     let selector = match selector {
         Some(s) => s,
         None => return true,
     };
 
     let mut local_context =
         LocalMatchingContext {
             shared: context,
-            visited_handling,
             matches_hover_and_active_quirk,
         };
     iter::once(selector).chain(selector_iter).all(|simple| {
         matches_simple_selector(
             simple,
             element,
             &mut local_context,
             flags_setter,
@@ -618,27 +623,25 @@ fn matches_simple_selector<E, F>(
 ) -> bool
 where
     E: Element,
     F: FnMut(&E, ElementSelectorFlags),
 {
     match *selector {
         Component::Combinator(_) => unreachable!(),
         Component::Slotted(ref selector) => {
-            context.shared.nesting_level += 1;
-            let result =
+            context.shared.nest(|context| {
                 element.assigned_slot().is_some() &&
                 matches_complex_selector(
                     selector.iter(),
                     element,
-                    context.shared,
+                    context,
                     flags_setter,
-                );
-            context.shared.nesting_level -= 1;
-            result
+                )
+            })
         }
         Component::PseudoElement(ref pseudo) => {
             element.match_pseudo_element(pseudo, context.shared)
         }
         Component::LocalName(ref local_name) => {
             matches_local_name(element, local_name)
         }
         Component::ExplicitUniversalType |
@@ -709,27 +712,27 @@ where
                             case_sensitivity: case_sensitivity.to_unconditional(is_html),
                             expected_value: expected_value,
                         }
                     }
                 }
             )
         }
         Component::NonTSPseudoClass(ref pc) => {
-            if context.matches_hover_and_active_quirk &&
-               context.shared.nesting_level == 0 &&
+            if context.matches_hover_and_active_quirk == MatchesHoverAndActiveQuirk::Yes &&
+               !context.shared.is_nested() &&
                E::Impl::is_active_or_hover(pc) &&
-               !element.is_link() {
+               !element.is_link()
+            {
                 return false;
             }
 
             element.match_non_ts_pseudo_class(
                 pc,
                 &mut context.shared,
-                context.visited_handling,
                 flags_setter
             )
         }
         Component::FirstChild => {
             matches_first_child(element, flags_setter)
         }
         Component::LastChild => {
             matches_last_child(element, flags_setter)
@@ -769,27 +772,30 @@ where
         Component::LastOfType => {
             matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter)
         }
         Component::OnlyOfType => {
             matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter) &&
             matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter)
         }
         Component::Negation(ref negated) => {
-            context.shared.nesting_level += 1;
-            let result = !negated.iter().all(|ss| {
-                matches_simple_selector(
-                    ss,
-                    element,
-                    context,
-                    flags_setter,
-                )
-            });
-            context.shared.nesting_level -= 1;
-            result
+            context.shared.nest(|context| {
+                let mut local_context = LocalMatchingContext {
+                    matches_hover_and_active_quirk: MatchesHoverAndActiveQuirk::No,
+                    shared: context,
+                };
+                !negated.iter().all(|ss| {
+                    matches_simple_selector(
+                        ss,
+                        element,
+                        &mut local_context,
+                        flags_setter,
+                    )
+                })
+            })
         }
     }
 }
 
 #[inline(always)]
 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
--- a/servo/components/selectors/tree.rs
+++ b/servo/components/selectors/tree.rs
@@ -1,17 +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/. */
 
 //! Traits that nodes must implement. Breaks the otherwise-cyclic dependency
 //! between layout and style.
 
 use attr::{AttrSelectorOperation, NamespaceConstraint, CaseSensitivity};
-use context::VisitedHandlingMode;
 use matching::{ElementSelectorFlags, MatchingContext};
 use parser::SelectorImpl;
 use servo_arc::NonZeroPtrMut;
 use std::fmt::Debug;
 
 /// Opaque representation of an Element, for identity comparisons. We use
 /// NonZeroPtrMut to get the NonZero optimization.
 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
@@ -64,17 +63,16 @@ pub trait Element: Sized + Clone + Debug
                     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<Self::Impl>,
-        visited_handling: VisitedHandlingMode,
         flags_setter: &mut F,
     ) -> bool
     where
         F: FnMut(&Self, ElementSelectorFlags);
 
     fn match_pseudo_element(
         &self,
         pe: &<Self::Impl as SelectorImpl>::PseudoElement,
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -1992,17 +1992,16 @@ impl<'le> ::selectors::Element for Gecko
             WeakNamespace::new((*namespace_manager).mURIArray[self.namespace_id() as usize].mRawPtr)
         }
     }
 
     fn match_non_ts_pseudo_class<F>(
         &self,
         pseudo_class: &NonTSPseudoClass,
         context: &mut MatchingContext<Self::Impl>,
-        visited_handling: VisitedHandlingMode,
         flags_setter: &mut F,
     ) -> bool
     where
         F: FnMut(&Self, ElementSelectorFlags),
     {
         use selectors::matching::*;
         match *pseudo_class {
             NonTSPseudoClass::Focus |
@@ -2052,20 +2051,20 @@ impl<'le> ::selectors::Element for Gecko
             NonTSPseudoClass::MozAutofill |
             NonTSPseudoClass::Active |
             NonTSPseudoClass::Hover |
             NonTSPseudoClass::MozAutofillPreview => {
                 self.get_state().intersects(pseudo_class.state_flag())
             },
             NonTSPseudoClass::AnyLink => self.is_link(),
             NonTSPseudoClass::Link => {
-                self.is_link() && visited_handling.matches_unvisited()
+                self.is_link() && context.visited_handling().matches_unvisited()
             }
             NonTSPseudoClass::Visited => {
-                self.is_link() && visited_handling.matches_visited()
+                self.is_link() && context.visited_handling().matches_visited()
             }
             NonTSPseudoClass::MozFirstNode => {
                 flags_setter(self, ElementSelectorFlags::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
                     }
@@ -2114,22 +2113,21 @@ impl<'le> ::selectors::Element for Gecko
                 if context.extra_data.document_state.intersects(state_bit) {
                     return true;
                 }
 
                 self.document_state().contains(state_bit)
             }
             NonTSPseudoClass::MozPlaceholder => false,
             NonTSPseudoClass::MozAny(ref sels) => {
-                context.nesting_level += 1;
-                let result = sels.iter().any(|s| {
-                    matches_complex_selector(s.iter(), self, context, flags_setter)
-                });
-                context.nesting_level -= 1;
-                result
+                context.nest(|context| {
+                    sels.iter().any(|s| {
+                        matches_complex_selector(s.iter(), self, context, flags_setter)
+                    })
+                })
             }
             NonTSPseudoClass::Lang(ref lang_arg) => {
                 self.match_element_lang(None, lang_arg)
             }
             NonTSPseudoClass::MozLocaleDir(ref dir) => {
                 let state_bit = DocumentState::NS_DOCUMENT_STATE_RTL_LOCALE;
                 if context.extra_data.document_state.intersects(state_bit) {
                     // NOTE(emilio): We could still return false for
--- a/servo/components/style/invalidation/element/element_wrapper.rs
+++ b/servo/components/style/invalidation/element/element_wrapper.rs
@@ -6,17 +6,16 @@
 //! against a past state of the element.
 
 use {Atom, CaseSensitivityExt, LocalName, Namespace};
 use dom::TElement;
 use element_state::ElementState;
 use selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl, Snapshot, SnapshotMap, AttrValue};
 use selectors::{Element, OpaqueElement};
 use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
-use selectors::context::VisitedHandlingMode;
 use selectors::matching::{ElementSelectorFlags, MatchingContext};
 use std::cell::Cell;
 use std::fmt;
 
 /// In order to compute restyle hints, we perform a selector match against a
 /// list of partial selectors whose rightmost simple selector may be sensitive
 /// to the thing being changed. We do this matching twice, once for the element
 /// as it exists now and once for the element as it existed at the time of the
@@ -145,34 +144,32 @@ impl<'a, E> Element for ElementWrapper<'
     where E: TElement,
 {
     type Impl = SelectorImpl;
 
     fn match_non_ts_pseudo_class<F>(
         &self,
         pseudo_class: &NonTSPseudoClass,
         context: &mut MatchingContext<Self::Impl>,
-        visited_handling: VisitedHandlingMode,
         _setter: &mut F,
     ) -> bool
     where
          F: FnMut(&Self, ElementSelectorFlags),
     {
         // Some pseudo-classes need special handling to evaluate them against
         // the snapshot.
         match *pseudo_class {
             #[cfg(feature = "gecko")]
             NonTSPseudoClass::MozAny(ref selectors) => {
                 use selectors::matching::matches_complex_selector;
-                context.nesting_level += 1;
-                let result = selectors.iter().any(|s| {
-                    matches_complex_selector(s.iter(), self, context, _setter)
+                return context.nest(|context| {
+                    selectors.iter().any(|s| {
+                        matches_complex_selector(s.iter(), self, context, _setter)
+                    })
                 });
-                context.nesting_level -= 1;
-                return result
             }
 
             // :dir is implemented in terms of state flags, but which state flag
             // it maps to depends on the argument to :dir.  That means we can't
             // just add its state flags to the NonTSPseudoClass, because if we
             // added all of them there, and tested via intersects() here, we'd
             // get incorrect behavior for :not(:dir()) cases.
             //
@@ -194,20 +191,20 @@ impl<'a, E> Element for ElementWrapper<'
             }
 
             // For :link and :visited, we don't actually want to test the
             // element state directly.
             //
             // Instead, we use the `visited_handling` to determine if they
             // match.
             NonTSPseudoClass::Link => {
-                return self.is_link() && visited_handling.matches_unvisited()
+                return self.is_link() && context.visited_handling().matches_unvisited()
             }
             NonTSPseudoClass::Visited => {
-                return self.is_link() && visited_handling.matches_visited()
+                return self.is_link() && context.visited_handling().matches_visited()
             }
 
             #[cfg(feature = "gecko")]
             NonTSPseudoClass::MozTableBorderNonzero => {
                 if let Some(snapshot) = self.snapshot() {
                     if snapshot.has_other_pseudo_class_state() {
                         return snapshot.mIsTableBorderNonzero();
                     }
@@ -232,27 +229,25 @@ impl<'a, E> Element for ElementWrapper<'
             _ => {}
         }
 
         let flag = pseudo_class.state_flag();
         if flag.is_empty() {
             return self.element.match_non_ts_pseudo_class(
                 pseudo_class,
                 context,
-                visited_handling,
                 &mut |_, _| {},
             )
         }
         match self.snapshot().and_then(|s| s.state()) {
             Some(snapshot_state) => snapshot_state.intersects(flag),
             None => {
                 self.element.match_non_ts_pseudo_class(
                     pseudo_class,
                     context,
-                    visited_handling,
                     &mut |_, _| {},
                 )
             }
         }
     }
 
     fn match_pseudo_element(
         &self,
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -1241,17 +1241,17 @@ impl Stylist {
                 CascadeLevel::UANormal
             );
         }
 
         if pseudo_element.is_none() && !only_default_rules {
             // Step 2: Presentational hints.
             let length_before_preshints = applicable_declarations.len();
             element.synthesize_presentational_hints_for_legacy_attributes(
-                context.visited_handling,
+                context.visited_handling(),
                 applicable_declarations
             );
             if applicable_declarations.len() != length_before_preshints {
                 if cfg!(debug_assertions) {
                     for declaration in &applicable_declarations[length_before_preshints..] {
                         assert_eq!(declaration.level(), CascadeLevel::PresHints);
                     }
                 }