servo: Merge #15462 - Accumulate parent elements that need selector bits set on the ThreadLocalStyleContext (from bholley:accumulate_selector_flags); r=emilio
authorBobby Holley <bobbyholley@gmail.com>
Wed, 08 Feb 2017 19:33:27 -0800
changeset 481555 d4d5ff27ff7e9833e5413bb262a034baa4cf14d9
parent 481554 e2bb330d1566ffc993156466d0851b9ef102c590
child 481556 da9ba938f04950cc3f589d8330f33b96ff8ec947
push id44840
push userbmo:rchien@mozilla.com
push dateFri, 10 Feb 2017 01:03:57 +0000
reviewersemilio
milestone54.0a1
servo: Merge #15462 - Accumulate parent elements that need selector bits set on the ThreadLocalStyleContext (from bholley:accumulate_selector_flags); r=emilio Discussion and review in https://bugzilla.mozilla.org/show_bug.cgi?id=1336646 Source-Repo: https://github.com/servo/servo Source-Revision: cbfd4464270f8690b90b9b96c395523a3a39e2de
servo/Cargo.lock
servo/components/layout/query.rs
servo/components/script/dom/bindings/trace.rs
servo/components/script/dom/element.rs
servo/components/script/dom/node.rs
servo/components/script/layout_wrapper.rs
servo/components/script_layout_interface/wrapper_traits.rs
servo/components/selectors/Cargo.toml
servo/components/selectors/matching.rs
servo/components/selectors/tree.rs
servo/components/style/bloom.rs
servo/components/style/context.rs
servo/components/style/dom.rs
servo/components/style/gecko/wrapper.rs
servo/components/style/matching.rs
servo/components/style/parallel.rs
servo/components/style/restyle_hints.rs
servo/components/style/stylist.rs
servo/components/style/traversal.rs
servo/tests/unit/script/size_of.rs
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -929,17 +929,17 @@ dependencies = [
  "cssparser 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num_cpus 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "selectors 0.17.0",
+ "selectors 0.18.0",
  "servo_url 0.0.1",
  "style 0.0.1",
  "style_traits 0.0.1",
  "stylo_tests 0.0.1",
 ]
 
 [[package]]
 name = "getopts"
@@ -1350,17 +1350,17 @@ dependencies = [
  "ordered-float 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "profile_traits 0.0.1",
  "range 0.0.1",
  "rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "script_layout_interface 0.0.1",
  "script_traits 0.0.1",
- "selectors 0.17.0",
+ "selectors 0.18.0",
  "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "style 0.0.1",
@@ -1396,17 +1396,17 @@ dependencies = [
  "net_traits 0.0.1",
  "parking_lot 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "profile_traits 0.0.1",
  "rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "script 0.0.1",
  "script_layout_interface 0.0.1",
  "script_traits 0.0.1",
- "selectors 0.17.0",
+ "selectors 0.18.0",
  "serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "style 0.0.1",
  "webrender_traits 0.14.0 (git+https://github.com/servo/webrender)",
 ]
@@ -2287,17 +2287,17 @@ dependencies = [
  "profile_traits 0.0.1",
  "range 0.0.1",
  "ref_filter_map 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "ref_slice 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "script_layout_interface 0.0.1",
  "script_traits 0.0.1",
- "selectors 0.17.0",
+ "selectors 0.18.0",
  "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_atoms 0.0.1",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_rand 0.0.1",
  "servo_url 0.0.1",
  "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2331,17 +2331,17 @@ dependencies = [
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "plugins 0.0.1",
  "profile_traits 0.0.1",
  "range 0.0.1",
  "script_traits 0.0.1",
- "selectors 0.17.0",
+ "selectors 0.18.0",
  "servo_url 0.0.1",
  "style 0.0.1",
 ]
 
 [[package]]
 name = "script_tests"
 version = "0.0.1"
 dependencies = [
@@ -2381,17 +2381,17 @@ dependencies = [
  "style_traits 0.0.1",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "webvr_traits 0.0.1",
 ]
 
 [[package]]
 name = "selectors"
-version = "0.17.0"
+version = "0.18.0"
 dependencies = [
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2761,17 +2761,17 @@ dependencies = [
  "owning_ref 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "pdqsort 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
- "selectors 0.17.0",
+ "selectors 0.18.0",
  "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_atoms 0.0.1",
  "servo_config 0.0.1",
  "servo_url 0.0.1",
  "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "style_traits 0.0.1",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2787,17 +2787,17 @@ dependencies = [
  "cssparser 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "html5ever-atoms 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "owning_ref 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
- "selectors 0.17.0",
+ "selectors 0.18.0",
  "servo_atoms 0.0.1",
  "servo_config 0.0.1",
  "servo_url 0.0.1",
  "style 0.0.1",
  "style_traits 0.0.1",
 ]
 
 [[package]]
@@ -2824,17 +2824,17 @@ dependencies = [
  "env_logger 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "geckoservo 0.0.1",
  "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num_cpus 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "selectors 0.17.0",
+ "selectors 0.18.0",
  "servo_url 0.0.1",
  "style 0.0.1",
  "style_traits 0.0.1",
 ]
 
 [[package]]
 name = "syn"
 version = "0.10.7"
--- a/servo/components/layout/query.rs
+++ b/servo/components/layout/query.rs
@@ -711,24 +711,24 @@ pub fn process_resolved_style_request<'a
     if element.get_data().is_some() {
         return process_resolved_style_request_internal(node, pseudo, property, layout_root);
     }
 
     // However, the element may be in a display:none subtree. The style system
     // has a mechanism to give us that within a defined scope (after which point
     // it's cleared to maintained style system invariants).
     let mut tlc = ThreadLocalStyleContext::new(&context.style_context);
-    let context = StyleContext {
+    let mut context = StyleContext {
         shared: &context.style_context,
         thread_local: &mut tlc,
     };
     let mut result = None;
     let ensure = |el: N::ConcreteElement| el.as_node().initialize_data();
     let clear = |el: N::ConcreteElement| el.as_node().clear_data();
-    resolve_style(&context, element, &ensure, &clear, |_: &_| {
+    resolve_style(&mut context, element, &ensure, &clear, |_: &_| {
         let s = process_resolved_style_request_internal(node, pseudo, property, layout_root);
         result = Some(s);
     });
     result.unwrap()
 }
 
 /// The primary resolution logic, which assumes that the element is styled.
 fn process_resolved_style_request_internal<'a, N>(requested_node: N,
--- a/servo/components/script/dom/bindings/trace.rs
+++ b/servo/components/script/dom/bindings/trace.rs
@@ -71,16 +71,17 @@ use offscreen_gl_context::GLLimits;
 use parking_lot::RwLock;
 use profile_traits::mem::ProfilerChan as MemProfilerChan;
 use profile_traits::time::ProfilerChan as TimeProfilerChan;
 use script_layout_interface::OpaqueStyleAndLayoutData;
 use script_layout_interface::reporter::CSSErrorReporter;
 use script_layout_interface::rpc::LayoutRPC;
 use script_traits::{DocumentActivity, TimerEventId, TimerSource, TouchpadPressurePhase};
 use script_traits::{UntrustedNodeAddress, WindowSizeData, WindowSizeType};
+use selectors::matching::ElementSelectorFlags;
 use serde::{Deserialize, Serialize};
 use servo_atoms::Atom;
 use servo_url::ServoUrl;
 use smallvec::SmallVec;
 use std::cell::{Cell, RefCell, UnsafeCell};
 use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
 use std::hash::{BuildHasher, Hash};
 use std::ops::{Deref, DerefMut};
@@ -342,16 +343,17 @@ unsafe_no_jsmanaged_fields!(StorageType)
 unsafe_no_jsmanaged_fields!(CanvasGradientStop, LinearGradientStyle, RadialGradientStyle);
 unsafe_no_jsmanaged_fields!(LineCapStyle, LineJoinStyle, CompositionOrBlending);
 unsafe_no_jsmanaged_fields!(RepetitionStyle);
 unsafe_no_jsmanaged_fields!(WebGLError, GLLimits);
 unsafe_no_jsmanaged_fields!(TimeProfilerChan);
 unsafe_no_jsmanaged_fields!(MemProfilerChan);
 unsafe_no_jsmanaged_fields!(PseudoElement);
 unsafe_no_jsmanaged_fields!(Length);
+unsafe_no_jsmanaged_fields!(ElementSelectorFlags);
 unsafe_no_jsmanaged_fields!(ElementState);
 unsafe_no_jsmanaged_fields!(DOMString);
 unsafe_no_jsmanaged_fields!(Mime);
 unsafe_no_jsmanaged_fields!(AttrIdentifier);
 unsafe_no_jsmanaged_fields!(AttrValue);
 unsafe_no_jsmanaged_fields!(Snapshot);
 unsafe_no_jsmanaged_fields!(PendingRestyle);
 unsafe_no_jsmanaged_fields!(HttpsState);
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -78,42 +78,42 @@ use html5ever::serialize::TraversalScope
 use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode};
 use html5ever_atoms::{Prefix, LocalName, Namespace, QualName};
 use js::jsapi::{HandleValue, JSAutoCompartment};
 use net_traits::request::CorsSettings;
 use parking_lot::RwLock;
 use ref_filter_map::ref_filter_map;
 use script_layout_interface::message::ReflowQueryType;
 use script_thread::Runnable;
-use selectors::matching::{ElementFlags, MatchingReason, matches};
+use selectors::matching::{ElementSelectorFlags, matches};
 use selectors::matching::{HAS_EDGE_CHILD_SELECTOR, HAS_SLOW_SELECTOR, HAS_SLOW_SELECTOR_LATER_SIBLINGS};
 use selectors::parser::{AttrSelector, NamespaceConstraint};
 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;
 use std::fmt;
 use std::rc::Rc;
 use std::sync::Arc;
-use std::sync::atomic::{AtomicUsize, Ordering};
 use style::attr::{AttrValue, LengthOrPercentageOrAuto};
 use style::context::{QuirksMode, ReflowGoal};
 use style::element_state::*;
 use style::matching::{common_style_affecting_attributes, rare_style_affecting_attributes};
 use style::parser::ParserContextExtraData;
 use style::properties::{DeclaredValue, Importance};
 use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, parse_style_attribute};
 use style::properties::longhands::{background_image, border_spacing, font_family, font_size, overflow_x};
 use style::restyle_hints::RESTYLE_SELF;
 use style::rule_tree::CascadeLevel;
 use style::selector_parser::{NonTSPseudoClass, RestyleDamage, SelectorImpl, SelectorParser};
 use style::sink::Push;
 use style::stylist::ApplicableDeclarationBlock;
+use style::thread_state;
 use style::values::CSSFloat;
 use style::values::specified::{self, CSSColor, CSSRGBA};
 use stylesheet_loader::StylesheetOwner;
 
 // TODO: Update focus state when the top-level browsing context gains or loses system focus,
 // and when the element enters or leaves a browsing context container.
 // https://html.spec.whatwg.org/multipage/#selector-focus
 
@@ -126,17 +126,22 @@ pub struct Element {
     prefix: Option<DOMString>,
     attrs: DOMRefCell<Vec<JS<Attr>>>,
     id_attribute: DOMRefCell<Option<Atom>>,
     #[ignore_heap_size_of = "Arc"]
     style_attribute: DOMRefCell<Option<Arc<RwLock<PropertyDeclarationBlock>>>>,
     attr_list: MutNullableJS<NamedNodeMap>,
     class_list: MutNullableJS<DOMTokenList>,
     state: Cell<ElementState>,
-    atomic_flags: AtomicElementFlags,
+    /// These flags are set by the style system to indicate the that certain
+    /// operations may require restyling this element or its descendants. The
+    /// flags are not atomic, so the style system takes care of only set them
+    /// when it has exclusive access to the element.
+    #[ignore_heap_size_of = "bitflags defined in rust-selectors"]
+    selector_flags: Cell<ElementSelectorFlags>,
 }
 
 impl fmt::Debug for Element {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         try!(write!(f, "<{}", self.local_name));
         if let Some(ref id) = *self.id_attribute.borrow() {
             try!(write!(f, " id={}", id));
         }
@@ -214,17 +219,17 @@ impl Element {
             namespace: namespace,
             prefix: prefix,
             attrs: DOMRefCell::new(vec![]),
             id_attribute: DOMRefCell::new(None),
             style_attribute: DOMRefCell::new(None),
             attr_list: Default::default(),
             class_list: Default::default(),
             state: Cell::new(state),
-            atomic_flags: AtomicElementFlags::new(),
+            selector_flags: Cell::new(ElementSelectorFlags::empty()),
         }
     }
 
     pub fn new(local_name: LocalName,
                namespace: Namespace,
                prefix: Option<DOMString>,
                document: &Document) -> Root<Element> {
         Node::reflect_node(
@@ -346,17 +351,18 @@ pub trait LayoutElementHelpers {
     unsafe fn html_element_in_html_document_for_layout(&self) -> bool;
     fn id_attribute(&self) -> *const Option<Atom>;
     fn style_attribute(&self) -> *const Option<Arc<RwLock<PropertyDeclarationBlock>>>;
     fn local_name(&self) -> &LocalName;
     fn namespace(&self) -> &Namespace;
     fn get_checked_state_for_layout(&self) -> bool;
     fn get_indeterminate_state_for_layout(&self) -> bool;
     fn get_state_for_layout(&self) -> ElementState;
-    fn insert_atomic_flags(&self, flags: ElementFlags);
+    fn insert_selector_flags(&self, flags: ElementSelectorFlags);
+    fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool;
 }
 
 impl LayoutElementHelpers for LayoutJS<Element> {
     #[allow(unsafe_code)]
     #[inline]
     unsafe fn has_class_for_layout(&self, name: &Atom) -> bool {
         get_attr_for_layout(&*self.unsafe_get(), &ns!(), &local_name!("class")).map_or(false, |attr| {
             attr.value_tokens_forever().unwrap().iter().any(|atom| atom == name)
@@ -715,19 +721,29 @@ impl LayoutElementHelpers for LayoutJS<E
     fn get_state_for_layout(&self) -> ElementState {
         unsafe {
             (*self.unsafe_get()).state.get()
         }
     }
 
     #[inline]
     #[allow(unsafe_code)]
-    fn insert_atomic_flags(&self, flags: ElementFlags) {
+    fn insert_selector_flags(&self, flags: ElementSelectorFlags) {
+        debug_assert!(thread_state::get() == thread_state::LAYOUT);
         unsafe {
-            (*self.unsafe_get()).atomic_flags.insert(flags);
+            let f = &(*self.unsafe_get()).selector_flags;
+            f.set(f.get() | flags);
+        }
+    }
+
+    #[inline]
+    #[allow(unsafe_code)]
+    fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
+        unsafe {
+            (*self.unsafe_get()).selector_flags.get().contains(flags)
         }
     }
 }
 
 impl Element {
     pub fn html_element_in_html_document(&self) -> bool {
         self.namespace == ns!(html) && self.upcast::<Node>().is_in_html_doc()
     }
@@ -1968,17 +1984,17 @@ 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) => {
-                Ok(matches(&selectors.0, &Root::from_ref(self), None, MatchingReason::Other))
+                Ok(matches(&selectors.0, &Root::from_ref(self), None))
             }
         }
     }
 
     // https://dom.spec.whatwg.org/#dom-element-webkitmatchesselector
     fn WebkitMatchesSelector(&self, selectors: DOMString) -> Fallible<bool> {
         self.Matches(selectors)
     }
@@ -1986,17 +2002,18 @@ 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) {
-                        if matches(&selectors.0, &element, None, MatchingReason::Other) {
+                        if matches(&selectors.0, &element, None)
+                        {
                             return Ok(Some(element));
                         }
                     }
                 }
                 Ok(None)
             }
         }
     }
@@ -2226,17 +2243,17 @@ impl VirtualMethods for Element {
         doc.decrement_dom_count();
     }
 
     fn children_changed(&self, mutation: &ChildrenMutation) {
         if let Some(ref s) = self.super_type() {
             s.children_changed(mutation);
         }
 
-        let flags = self.atomic_flags.get();
+        let flags = self.selector_flags.get();
         if flags.intersects(HAS_SLOW_SELECTOR) {
             // All children of this node need to be restyled when any child changes.
             self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
         } else {
             if flags.intersects(HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
                 if let Some(next_child) = mutation.next_child() {
                     for child in next_child.inclusively_following_siblings() {
                         if child.is::<Element>() {
@@ -2739,34 +2756,16 @@ impl<'a> AttributeMutation<'a> {
     pub fn new_value<'b>(&self, attr: &'b Attr) -> Option<Ref<'b, AttrValue>> {
         match *self {
             AttributeMutation::Set(_) => Some(attr.value()),
             AttributeMutation::Removed => None,
         }
     }
 }
 
-/// Thread-safe wrapper for ElementFlags set during selector matching
-#[derive(JSTraceable, HeapSizeOf)]
-struct AtomicElementFlags(AtomicUsize);
-
-impl AtomicElementFlags {
-    fn new() -> Self {
-        AtomicElementFlags(AtomicUsize::new(0))
-    }
-
-    fn get(&self) -> ElementFlags {
-        ElementFlags::from_bits_truncate(self.0.load(Ordering::Relaxed) as u8)
-    }
-
-    fn insert(&self, flags: ElementFlags) {
-        self.0.fetch_or(flags.bits() as usize, Ordering::Relaxed);
-    }
-}
-
 /// A holder for an element's "tag name", which will be lazily
 /// resolved and cached. Should be reset when the document
 /// owner changes.
 #[derive(JSTraceable, HeapSizeOf)]
 struct TagName {
     ptr: DOMRefCell<Option<LocalName>>,
 }
 
--- a/servo/components/script/dom/node.rs
+++ b/servo/components/script/dom/node.rs
@@ -62,17 +62,17 @@ use js::jsapi::{JSContext, JSObject, JSR
 use libc::{self, c_void, uintptr_t};
 use msg::constellation_msg::PipelineId;
 use ref_slice::ref_slice;
 use script_layout_interface::{HTMLCanvasData, OpaqueStyleAndLayoutData, SVGSVGData};
 use script_layout_interface::{LayoutElementType, LayoutNodeType, TrustedNodeAddress};
 use script_layout_interface::message::Msg;
 use script_traits::DocumentActivity;
 use script_traits::UntrustedNodeAddress;
-use selectors::matching::{MatchingReason, matches};
+use selectors::matching::matches;
 use selectors::parser::SelectorList;
 use servo_url::ServoUrl;
 use std::borrow::ToOwned;
 use std::cell::{Cell, UnsafeCell};
 use std::cmp::max;
 use std::default::Default;
 use std::iter;
 use std::mem;
@@ -317,17 +317,17 @@ impl<'a> Iterator for QuerySelectorItera
     type Item = Root<Node>;
 
     fn next(&mut self) -> Option<Root<Node>> {
         let selectors = &self.selectors.0;
         // TODO(cgaebel): Is it worth it to build a bloom filter here
         // (instead of passing `None`)? Probably.
         self.iterator.by_ref().filter_map(|node| {
             if let Some(element) = Root::downcast(node) {
-                if matches(selectors, &element, None, MatchingReason::Other) {
+                if matches(selectors, &element, None) {
                     return Some(Root::upcast(element));
                 }
             }
             None
         }).next()
     }
 }
 
@@ -680,17 +680,17 @@ impl Node {
     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) => {
                 Ok(self.traverse_preorder().filter_map(Root::downcast).find(|element| {
-                    matches(&selectors.0, element, None, MatchingReason::Other)
+                    matches(&selectors.0, element, None)
                 }))
             }
         }
     }
 
     /// https://dom.spec.whatwg.org/#scope-match-a-selectors-string
     /// Get an iterator over all nodes which match a set of selectors
     /// Be careful not to do anything which may manipulate the DOM tree
--- a/servo/components/script/layout_wrapper.rs
+++ b/servo/components/script/layout_wrapper.rs
@@ -44,17 +44,17 @@ use gfx_traits::ByteIndex;
 use html5ever_atoms::{LocalName, Namespace};
 use msg::constellation_msg::PipelineId;
 use parking_lot::RwLock;
 use range::Range;
 use script_layout_interface::{HTMLCanvasData, LayoutNodeType, SVGSVGData, TrustedNodeAddress};
 use script_layout_interface::{OpaqueStyleAndLayoutData, PartialPersistentLayoutData};
 use script_layout_interface::wrapper_traits::{DangerousThreadSafeLayoutNode, GetLayoutData, LayoutNode};
 use script_layout_interface::wrapper_traits::{PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
-use selectors::matching::ElementFlags;
+use selectors::matching::ElementSelectorFlags;
 use selectors::parser::{AttrSelector, NamespaceConstraint};
 use servo_atoms::Atom;
 use servo_url::ServoUrl;
 use std::fmt;
 use std::fmt::Debug;
 use std::marker::PhantomData;
 use std::mem::transmute;
 use std::sync::Arc;
@@ -432,16 +432,24 @@ impl<'le> TElement for ServoLayoutElemen
                 psd
             })
         }
     }
 
     fn skip_root_and_item_based_display_fixup(&self) -> bool {
         false
     }
+
+    unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags) {
+        self.element.insert_selector_flags(flags);
+    }
+
+    fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
+        self.element.has_selector_flags(flags)
+    }
 }
 
 impl<'le> PartialEq for ServoLayoutElement<'le> {
     fn eq(&self, other: &Self) -> bool {
         self.as_node() == other.as_node()
     }
 }
 
@@ -660,20 +668,16 @@ impl<'le> ::selectors::Element for Servo
         }
     }
 
     fn is_html_element_in_html_document(&self) -> bool {
         unsafe {
             self.element.html_element_in_html_document_for_layout()
         }
     }
-
-    fn insert_flags(&self, flags: ElementFlags) {
-        self.element.insert_atomic_flags(flags);
-    }
 }
 
 #[derive(Copy, Clone, Debug)]
 pub struct ServoThreadSafeLayoutNode<'ln> {
     /// The wrapped node.
     node: ServoLayoutNode<'ln>,
 
     /// The pseudo-element type, with (optionally)
@@ -1004,16 +1008,20 @@ impl<'le> ThreadSafeLayoutElement for Se
             pseudo: pseudo,
         }
     }
 
     fn type_id(&self) -> Option<LayoutNodeType> {
         self.as_node().type_id()
     }
 
+    unsafe fn unsafe_get(self) -> ServoLayoutElement<'le> {
+        self.element
+    }
+
     fn get_attr<'a>(&'a self, namespace: &Namespace, name: &LocalName) -> Option<&'a str> {
         self.element.get_attr(namespace, name)
     }
 
     fn get_style_data(&self) -> Option<&AtomicRefCell<ElementData>> {
         self.element.get_data()
     }
 }
--- a/servo/components/script_layout_interface/wrapper_traits.rs
+++ b/servo/components/script_layout_interface/wrapper_traits.rs
@@ -308,16 +308,25 @@ pub trait ThreadSafeLayoutElement: Clone
     /// Creates a new `ThreadSafeLayoutElement` for the same `LayoutElement`
     /// with a different pseudo-element type.
     fn with_pseudo(&self, pseudo: PseudoElementType<Option<display::T>>) -> Self;
 
     /// Returns the type ID of this node.
     /// Returns `None` if this is a pseudo-element; otherwise, returns `Some`.
     fn type_id(&self) -> Option<LayoutNodeType>;
 
+    /// Returns access to the underlying TElement. This is breaks the abstraction
+    /// barrier of ThreadSafeLayout wrapper layer, and can lead to races if not used
+    /// carefully.
+    ///
+    /// We need this so that the functions defined on this trait can call
+    /// lazily_compute_pseudo_element_style, which operates on TElement.
+    unsafe fn unsafe_get(self) ->
+        <<Self::ConcreteThreadSafeLayoutNode as ThreadSafeLayoutNode>::ConcreteNode as TNode>::ConcreteElement;
+
     #[inline]
     fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&str>;
 
     fn get_style_data(&self) -> Option<&AtomicRefCell<ElementData>>;
 
     #[inline]
     fn get_pseudo_element_type(&self) -> PseudoElementType<Option<display::T>>;
 
@@ -408,17 +417,17 @@ pub trait ThreadSafeLayoutElement: Clone
                         if !self.get_style_data()
                                 .unwrap()
                                 .borrow()
                                 .styles().pseudos.contains_key(&style_pseudo) {
                             let mut data = self.get_style_data().unwrap().borrow_mut();
                             let new_style =
                                 context.stylist
                                        .lazily_compute_pseudo_element_style(
-                                           self,
+                                           unsafe { &self.unsafe_get() },
                                            &style_pseudo,
                                            &data.styles().primary.values,
                                            &context.default_computed_values);
                             data.styles_mut().pseudos
                                 .insert(style_pseudo.clone(), new_style.unwrap());
                         }
                     }
                 }
--- a/servo/components/selectors/Cargo.toml
+++ b/servo/components/selectors/Cargo.toml
@@ -1,12 +1,12 @@
 [package]
 
 name = "selectors"
-version = "0.17.0"
+version = "0.18.0" # Not yet published
 authors = ["Simon Sapin <simon.sapin@exyr.org>", "Alan Jeffrey <ajeffrey@mozilla.com>"]
 documentation = "https://docs.rs/selectors/"
 
 description = "CSS Selectors matching for Rust"
 repository = "https://github.com/servo/servo"
 readme = "README.md"
 keywords = ["css", "selectors"]
 license = "MPL-2.0"
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -2,37 +2,16 @@
  * 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 bloom::BloomFilter;
 use parser::{CaseSensitivity, Combinator, ComplexSelector, LocalName};
 use parser::{SimpleSelector, Selector, SelectorImpl};
 use std::borrow::Borrow;
 use tree::Element;
 
-/// The reason why we're doing selector matching.
-///
-/// If this is for styling, this will include the flags in the parent element.
-///
-/// This is done because Servo doesn't need those flags at all when it's not
-/// styling (e.g., when you're doing document.querySelector). For example, a
-/// slow selector in an API like querySelector doesn't imply that the parent
-/// could match it.
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub enum MatchingReason {
-    ForStyling,
-    Other,
-}
-
-impl MatchingReason {
-    #[inline]
-    fn for_styling(&self) -> bool {
-        *self == MatchingReason::ForStyling
-    }
-}
-
 // 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;
 
 bitflags! {
     /// Set of flags that determine the different kind of elements affected by
     /// the selector matching process.
@@ -79,71 +58,80 @@ bitflags! {
 
         /// Whether this element has effective transition styles. Computed
         /// externally.
         const AFFECTED_BY_TRANSITIONS = 1 << 10,
     }
 }
 
 bitflags! {
-    /// Set of flags that are set on the parent depending on whether a child
-    /// could potentially match a selector.
-    ///
-    /// These setters, in the case of Servo, must be atomic, due to the parallel
-    /// traversal.
-    pub flags ElementFlags: u8 {
-        /// When a child is added or removed from this element, all the children
+    /// Set of flags that are set on either the element or its parent (depending
+    /// on the flag) if the element could potentially match a selector.
+    pub flags ElementSelectorFlags: u8 {
+        /// When a child is added or removed from the parent, all the children
         /// must be restyled, because they may match :nth-last-child,
         /// :last-of-type, :nth-last-of-type, or :only-of-type.
         const HAS_SLOW_SELECTOR = 1 << 0,
 
-        /// When a child is added or removed from this element, any later
+        /// When a child is added or removed from the parent, any later
         /// children must be restyled, because they may match :nth-child,
         /// :first-of-type, or :nth-of-type.
         const HAS_SLOW_SELECTOR_LATER_SIBLINGS = 1 << 1,
 
-        /// When a child is added or removed from this element, the first and
+        /// When a child is added or removed from the parent, the first and
         /// last children must be restyled, because they may match :first-child,
         /// :last-child, or :only-child.
         const HAS_EDGE_CHILD_SELECTOR = 1 << 2,
 
         /// The element has an empty selector, so when a child is appended we
         /// might need to restyle the parent completely.
         const HAS_EMPTY_SELECTOR = 1 << 3,
     }
 }
 
+impl ElementSelectorFlags {
+    /// Returns the subset of flags that apply to the element.
+    pub fn for_self(self) -> ElementSelectorFlags {
+        self & (HAS_EMPTY_SELECTOR)
+    }
+
+    /// Returns the subset of flags that apply to the parent.
+    pub fn for_parent(self) -> ElementSelectorFlags {
+        self & (HAS_SLOW_SELECTOR | HAS_SLOW_SELECTOR_LATER_SIBLINGS | HAS_EDGE_CHILD_SELECTOR)
+    }
+}
+
 pub fn matches<E>(selector_list: &[Selector<E::Impl>],
                   element: &E,
-                  parent_bf: Option<&BloomFilter>,
-                  reason: MatchingReason)
+                  parent_bf: Option<&BloomFilter>)
                   -> bool
     where E: Element
 {
     selector_list.iter().any(|selector| {
         selector.pseudo_element.is_none() &&
-        matches_complex_selector(&*selector.complex_selector, element, parent_bf, &mut StyleRelations::empty(), reason)
+        matches_complex_selector(&*selector.complex_selector, element, parent_bf,
+                                 &mut StyleRelations::empty(), &mut ElementSelectorFlags::empty())
     })
 }
 
 /// Determines whether the given element matches the given complex selector.
 ///
 /// NB: If you add support for any new kinds of selectors to this routine, be sure to set
 /// `shareable` to false unless you are willing to update the style sharing logic. Otherwise things
 /// will almost certainly break as elements will start mistakenly sharing styles. (See
 /// `can_share_style_with` in `servo/components/style/matching.rs`.)
 pub fn matches_complex_selector<E>(selector: &ComplexSelector<E::Impl>,
                                    element: &E,
                                    parent_bf: Option<&BloomFilter>,
                                    relations: &mut StyleRelations,
-                                   reason: MatchingReason)
+                                   flags: &mut ElementSelectorFlags)
                                    -> bool
     where E: Element
 {
-    match matches_complex_selector_internal(selector, element, parent_bf, relations, reason) {
+    match matches_complex_selector_internal(selector, element, parent_bf, relations, flags) {
         SelectorMatchingResult::Matched => {
             match selector.next {
                 Some((_, Combinator::NextSibling)) |
                 Some((_, Combinator::LaterSibling)) => *relations |= AFFECTED_BY_SIBLINGS,
                 _ => {}
             }
 
             true
@@ -204,22 +192,22 @@ enum SelectorMatchingResult {
 
 /// Quickly figures out whether or not the complex selector is worth doing more
 /// work on. If the simple selectors don't match, or there's a child selector
 /// that does not appear in the bloom parent bloom filter, we can exit early.
 fn can_fast_reject<E>(mut selector: &ComplexSelector<E::Impl>,
                       element: &E,
                       parent_bf: Option<&BloomFilter>,
                       relations: &mut StyleRelations,
-                      reason: MatchingReason)
+                      flags: &mut ElementSelectorFlags)
                       -> Option<SelectorMatchingResult>
     where E: Element
 {
     if !selector.compound_selector.iter().all(|simple_selector| {
-      matches_simple_selector(simple_selector, element, parent_bf, relations, reason) }) {
+      matches_simple_selector(simple_selector, element, parent_bf, relations, flags) }) {
         return Some(SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling);
     }
 
     let bf: &BloomFilter = match parent_bf {
         None => return None,
         Some(ref bf) => bf,
     };
 
@@ -266,21 +254,21 @@ fn can_fast_reject<E>(mut selector: &Com
     // Can't fast reject.
     None
 }
 
 fn matches_complex_selector_internal<E>(selector: &ComplexSelector<E::Impl>,
                                          element: &E,
                                          parent_bf: Option<&BloomFilter>,
                                          relations: &mut StyleRelations,
-                                         reason: MatchingReason)
+                                         flags: &mut ElementSelectorFlags)
                                          -> SelectorMatchingResult
      where E: Element
 {
-    if let Some(result) = can_fast_reject(selector, element, parent_bf, relations, reason) {
+    if let Some(result) = can_fast_reject(selector, element, parent_bf, relations, flags) {
         return result;
     }
 
     match selector.next {
         None => SelectorMatchingResult::Matched,
         Some((ref next_selector, combinator)) => {
             let (siblings, candidate_not_found) = match combinator {
                 Combinator::Child => (false, SelectorMatchingResult::NotMatchedGlobally),
@@ -297,17 +285,17 @@ fn matches_complex_selector_internal<E>(
                 let element = match next_element {
                     None => return candidate_not_found,
                     Some(next_element) => next_element,
                 };
                 let result = matches_complex_selector_internal(&**next_selector,
                                                                 &element,
                                                                 parent_bf,
                                                                 relations,
-                                                                reason);
+                                                                flags);
                 match (result, combinator) {
                     // Return the status immediately.
                     (SelectorMatchingResult::Matched, _) => return result,
                     (SelectorMatchingResult::NotMatchedGlobally, _) => return result,
 
                     // Upgrade the failure status to
                     // NotMatchedAndRestartFromClosestDescendant.
                     (_, Combinator::Child) => return SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant,
@@ -341,17 +329,17 @@ fn matches_complex_selector_internal<E>(
 
 /// Determines whether the given element matches the given single selector.
 #[inline]
 fn matches_simple_selector<E>(
         selector: &SimpleSelector<E::Impl>,
         element: &E,
         parent_bf: Option<&BloomFilter>,
         relations: &mut StyleRelations,
-        reason: MatchingReason)
+        flags: &mut ElementSelectorFlags)
         -> bool
     where E: Element
 {
     macro_rules! relation_if {
         ($ex:expr, $flag:ident) => {
             if $ex {
                 *relations |= $flag;
                 true
@@ -424,95 +412,86 @@ fn matches_simple_selector<E>(
         SimpleSelector::AttrSuffixNeverMatch(..) => {
             false
         }
         SimpleSelector::NonTSPseudoClass(ref pc) => {
             relation_if!(element.match_non_ts_pseudo_class(pc),
                          AFFECTED_BY_STATE)
         }
         SimpleSelector::FirstChild => {
-            relation_if!(matches_first_child(element, reason), AFFECTED_BY_CHILD_INDEX)
+            relation_if!(matches_first_child(element, flags), AFFECTED_BY_CHILD_INDEX)
         }
         SimpleSelector::LastChild => {
-            relation_if!(matches_last_child(element, reason), AFFECTED_BY_CHILD_INDEX)
+            relation_if!(matches_last_child(element, flags), AFFECTED_BY_CHILD_INDEX)
         }
         SimpleSelector::OnlyChild => {
-            relation_if!(matches_first_child(element, reason) &&
-                         matches_last_child(element, reason), AFFECTED_BY_CHILD_INDEX)
+            relation_if!(matches_first_child(element, flags) &&
+                         matches_last_child(element, flags), AFFECTED_BY_CHILD_INDEX)
         }
         SimpleSelector::Root => {
             // We never share styles with an element with no parent, so no point
             // in creating a new StyleRelation.
             element.is_root()
         }
         SimpleSelector::Empty => {
-            if reason.for_styling() {
-                element.insert_flags(HAS_EMPTY_SELECTOR);
-            }
+            flags.insert(HAS_EMPTY_SELECTOR);
             relation_if!(element.is_empty(), AFFECTED_BY_EMPTY)
         }
         SimpleSelector::NthChild(a, b) => {
-            relation_if!(matches_generic_nth_child(element, a, b, false, false, reason),
+            relation_if!(matches_generic_nth_child(element, a, b, false, false, flags),
                          AFFECTED_BY_CHILD_INDEX)
         }
         SimpleSelector::NthLastChild(a, b) => {
-            relation_if!(matches_generic_nth_child(element, a, b, false, true, reason),
+            relation_if!(matches_generic_nth_child(element, a, b, false, true, flags),
                          AFFECTED_BY_CHILD_INDEX)
         }
         SimpleSelector::NthOfType(a, b) => {
-            relation_if!(matches_generic_nth_child(element, a, b, true, false, reason),
+            relation_if!(matches_generic_nth_child(element, a, b, true, false, flags),
                          AFFECTED_BY_CHILD_INDEX)
         }
         SimpleSelector::NthLastOfType(a, b) => {
-            relation_if!(matches_generic_nth_child(element, a, b, true, true, reason),
+            relation_if!(matches_generic_nth_child(element, a, b, true, true, flags),
                          AFFECTED_BY_CHILD_INDEX)
         }
         SimpleSelector::FirstOfType => {
-            relation_if!(matches_generic_nth_child(element, 0, 1, true, false, reason),
+            relation_if!(matches_generic_nth_child(element, 0, 1, true, false, flags),
                          AFFECTED_BY_CHILD_INDEX)
         }
         SimpleSelector::LastOfType => {
-            relation_if!(matches_generic_nth_child(element, 0, 1, true, true, reason),
+            relation_if!(matches_generic_nth_child(element, 0, 1, true, true, flags),
                          AFFECTED_BY_CHILD_INDEX)
         }
         SimpleSelector::OnlyOfType => {
-            relation_if!(matches_generic_nth_child(element, 0, 1, true, false, reason) &&
-                         matches_generic_nth_child(element, 0, 1, true, true, reason),
+            relation_if!(matches_generic_nth_child(element, 0, 1, true, false, flags) &&
+                         matches_generic_nth_child(element, 0, 1, true, true, flags),
                          AFFECTED_BY_CHILD_INDEX)
         }
         SimpleSelector::Negation(ref negated) => {
             !negated.iter().all(|s| {
-                matches_complex_selector(s, element, parent_bf, relations, reason)
+                matches_complex_selector(s, element, parent_bf, relations, flags)
             })
         }
     }
 }
 
 #[inline]
 fn matches_generic_nth_child<E>(element: &E,
                                 a: i32,
                                 b: i32,
                                 is_of_type: bool,
                                 is_from_end: bool,
-                                reason: MatchingReason) -> bool
+                                flags: &mut ElementSelectorFlags)
+                                -> bool
     where E: Element
 {
-    // Selectors Level 4 changed from Level 3:
-    // This can match without a parent element:
-    // https://drafts.csswg.org/selectors-4/#child-index
-
-    if reason.for_styling() {
-        if let Some(parent) = element.parent_element() {
-            parent.insert_flags(if is_from_end {
-                HAS_SLOW_SELECTOR
-            } else {
-                HAS_SLOW_SELECTOR_LATER_SIBLINGS
-            });
-        }
-    }
+    flags.insert(if is_from_end {
+        HAS_SLOW_SELECTOR
+    } else {
+        HAS_SLOW_SELECTOR_LATER_SIBLINGS
+    });
 
     let mut index = 1;
     let mut next_sibling = if is_from_end {
         element.next_sibling_element()
     } else {
         element.prev_sibling_element()
     };
 
@@ -541,33 +520,20 @@ fn matches_generic_nth_child<E>(element:
         b == index
     } else {
         (index - b) / a >= 0 &&
         (index - b) % a == 0
     }
 }
 
 #[inline]
-fn matches_first_child<E>(element: &E, reason: MatchingReason) -> bool where E: Element {
-    // Selectors Level 4 changed from Level 3:
-    // This can match without a parent element:
-    // https://drafts.csswg.org/selectors-4/#child-index
-    if reason.for_styling() {
-        if let Some(parent) = element.parent_element() {
-            parent.insert_flags(HAS_EDGE_CHILD_SELECTOR);
-        }
-    }
+fn matches_first_child<E>(element: &E, flags: &mut ElementSelectorFlags)
+                          -> bool where E: Element {
+    flags.insert(HAS_EDGE_CHILD_SELECTOR);
     element.prev_sibling_element().is_none()
 }
 
 #[inline]
-fn matches_last_child<E>(element: &E, reason: MatchingReason) -> bool where E: Element {
-    // Selectors Level 4 changed from Level 3:
-    // This can match without a parent element:
-    // https://drafts.csswg.org/selectors-4/#child-index
-    if reason.for_styling() {
-        if let Some(parent) = element.parent_element() {
-            parent.insert_flags(HAS_EDGE_CHILD_SELECTOR);
-        }
-    }
-
+fn matches_last_child<E>(element: &E, flags: &mut ElementSelectorFlags)
+                         -> bool where E: Element {
+    flags.insert(HAS_EDGE_CHILD_SELECTOR);
     element.next_sibling_element().is_none()
 }
--- a/servo/components/selectors/tree.rs
+++ b/servo/components/selectors/tree.rs
@@ -1,16 +1,15 @@
 /* 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 matching::ElementFlags;
 use parser::{AttrSelector, SelectorImpl};
 use std::ascii::AsciiExt;
 
 /// The definition of whitespace per CSS Selectors Level 3 ยง 4.
 pub static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C'];
 
 // Attribute matching routines. Consumers with simple implementations can implement
 // MatchAttrGeneric instead.
@@ -157,21 +156,9 @@ pub trait Element: MatchAttr + Sized {
     /// if the parent node is a `DocumentFragment`.
     fn is_root(&self) -> bool;
 
     // Ordinarily I wouldn't use callbacks like this, but the alternative is
     // really messy, since there is a `JSRef` and a `RefCell` involved. Maybe
     // in the future when we have associated types and/or a more convenient
     // JS GC story... --pcwalton
     fn each_class<F>(&self, callback: F) where F: FnMut(&<Self::Impl as SelectorImpl>::ClassName);
-
-    /// Add flags to the element. See the `ElementFlags` docs for details.
-    ///
-    /// This may be called while the element *or one of its children* is being
-    /// matched. Therefore the implementation must be thread-safe if children
-    /// may be matched in parallel.
-    fn insert_flags(&self, _flags: ElementFlags) {}
-
-    /// Clears the relevant ElementFlags. This is *not* called from
-    /// rust-selectors, but provided as part of the Element interface since it
-    /// makes sense.
-    fn clear_flags(&self) {}
 }
--- a/servo/components/style/bloom.rs
+++ b/servo/components/style/bloom.rs
@@ -61,17 +61,17 @@ impl<E: TElement> StyleBloom<E> {
 
     /// Return the bloom filter used properly by the `selectors` crate.
     pub fn filter(&self) -> &BloomFilter {
         &*self.filter
     }
 
     /// Push an element to the bloom filter, knowing that it's a child of the
     /// last element parent.
-    fn push(&mut self, element: E) {
+    pub fn push(&mut self, element: E) {
         if cfg!(debug_assertions) {
             if self.elements.is_empty() {
                 assert!(element.parent_element().is_none());
             }
         }
         element.insert_into_bloom_filter(&mut *self.filter);
         self.elements.push(unsafe { SendElement::new(element) });
     }
@@ -81,22 +81,30 @@ impl<E: TElement> StyleBloom<E> {
         let popped = self.elements.pop().map(|el| *el);
         if let Some(popped) = popped {
             popped.remove_from_bloom_filter(&mut self.filter);
         }
 
         popped
     }
 
-    fn clear(&mut self) {
+    /// Returns true if the bloom filter is empty.
+    pub fn is_empty(&self) -> bool {
+        self.elements.is_empty()
+    }
+
+
+    /// Clears the bloom filter.
+    pub fn clear(&mut self) {
         self.filter.clear();
         self.elements.clear();
     }
 
-    fn rebuild(&mut self, mut element: E) -> usize {
+    /// Rebuilds the bloom filter up to the parent of the given element.
+    pub fn rebuild(&mut self, mut element: E) -> usize {
         self.clear();
 
         while let Some(parent) = element.parent_element() {
             parent.insert_into_bloom_filter(&mut *self.filter);
             self.elements.push(unsafe { SendElement::new(parent) });
             element = parent;
         }
 
--- a/servo/components/style/context.rs
+++ b/servo/components/style/context.rs
@@ -4,29 +4,32 @@
 
 //! The context within which style is calculated.
 #![deny(missing_docs)]
 
 use animation::Animation;
 use app_units::Au;
 use bloom::StyleBloom;
 use data::ElementData;
-use dom::{OpaqueNode, TNode, TElement};
+use dom::{OpaqueNode, TNode, TElement, SendElement};
 use error_reporting::ParseErrorReporter;
 use euclid::Size2D;
 use matching::StyleSharingCandidateCache;
 use parking_lot::RwLock;
 use properties::ComputedValues;
+use selectors::matching::ElementSelectorFlags;
+use servo_config::opts;
 use std::collections::HashMap;
 use std::env;
 use std::fmt;
 use std::ops::Add;
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::Sender;
 use stylist::Stylist;
+use thread_state;
 use timer::Timer;
 
 /// This structure is used to create a local style context from a shared one.
 pub struct ThreadLocalStyleContextCreationInfo {
     new_animations_sender: Sender<Animation>,
 }
 
 impl ThreadLocalStyleContextCreationInfo {
@@ -155,46 +158,79 @@ lazy_static! {
             Err(_) => false,
         }
     };
 }
 
 impl TraversalStatistics {
     /// Returns whether statistics dumping is enabled.
     pub fn should_dump() -> bool {
-        *DUMP_STYLE_STATISTICS
+        *DUMP_STYLE_STATISTICS || opts::get().style_sharing_stats
+    }
+}
+
+/// A task to be run in sequential mode on the parent (non-worker) thread. This
+/// is used by the style system to queue up work which is not safe to do during
+/// the parallel traversal.
+pub enum SequentialTask<E: TElement> {
+    /// Sets selector flags. This is used when we need to set flags on an
+    /// element that we don't have exclusive access to (i.e. the parent).
+    SetSelectorFlags(SendElement<E>, ElementSelectorFlags),
+}
+
+impl<E: TElement> SequentialTask<E> {
+    /// Executes this task.
+    pub fn execute(self) {
+        use self::SequentialTask::*;
+        debug_assert!(thread_state::get() == thread_state::LAYOUT);
+        match self {
+            SetSelectorFlags(el, flags) => {
+                unsafe { el.set_selector_flags(flags) };
+            }
+        }
+    }
+
+    /// Creates a task to set the selector flags on an element.
+    pub fn set_selector_flags(el: E, flags: ElementSelectorFlags) -> Self {
+        use self::SequentialTask::*;
+        SetSelectorFlags(unsafe { SendElement::new(el) }, flags)
     }
 }
 
 /// A thread-local style context.
 ///
 /// This context contains data that needs to be used during restyling, but is
 /// not required to be unique among worker threads, so we create one per worker
 /// thread in order to be able to mutate it without locking.
 pub struct ThreadLocalStyleContext<E: TElement> {
     /// A cache to share style among siblings.
     pub style_sharing_candidate_cache: StyleSharingCandidateCache<E>,
     /// The bloom filter used to fast-reject selector-matching.
     pub bloom_filter: StyleBloom<E>,
     /// A channel on which new animations that have been triggered by style
     /// recalculation can be sent.
     pub new_animations_sender: Sender<Animation>,
+    /// A set of tasks to be run (on the parent thread) in sequential mode after
+    /// the rest of the styling is complete. This is useful for infrequently-needed
+    /// non-threadsafe operations.
+    pub tasks: Vec<SequentialTask<E>>,
     /// Statistics about the traversal.
     pub statistics: TraversalStatistics,
     /// Information related to the current element, non-None during processing.
     current_element_info: Option<CurrentElementInfo>,
 }
 
 impl<E: TElement> ThreadLocalStyleContext<E> {
     /// Creates a new `ThreadLocalStyleContext` from a shared one.
     pub fn new(shared: &SharedStyleContext) -> Self {
         ThreadLocalStyleContext {
             style_sharing_candidate_cache: StyleSharingCandidateCache::new(),
             bloom_filter: StyleBloom::new(),
             new_animations_sender: shared.local_context_creation_data.lock().unwrap().new_animations_sender.clone(),
+            tasks: Vec::new(),
             statistics: TraversalStatistics::default(),
             current_element_info: None,
         }
     }
 
     /// Notes when the style system starts traversing an element.
     pub fn begin_element(&mut self, element: E, data: &ElementData) {
         debug_assert!(self.current_element_info.is_none());
@@ -216,20 +252,25 @@ impl<E: TElement> ThreadLocalStyleContex
     /// the first time.
     ///
     /// Panics if called while no element is being traversed.
     pub fn is_initial_style(&self) -> bool {
         self.current_element_info.as_ref().unwrap().is_initial_style
     }
 }
 
-#[cfg(debug_assertions)]
 impl<E: TElement> Drop for ThreadLocalStyleContext<E> {
     fn drop(&mut self) {
         debug_assert!(self.current_element_info.is_none());
+
+        // Execute any enqueued sequential tasks.
+        debug_assert!(thread_state::get() == thread_state::LAYOUT);
+        for task in self.tasks.drain(..) {
+            task.execute();
+        }
     }
 }
 
 /// A `StyleContext` is just a simple container for a immutable reference to a
 /// shared style context, and a mutable reference to a local one.
 pub struct StyleContext<'a, E: TElement + 'a> {
     /// The shared style context reference.
     pub shared: &'a SharedStyleContext,
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -9,16 +9,17 @@
 
 use {Atom, Namespace, LocalName};
 use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
 use data::ElementData;
 use element_state::ElementState;
 use parking_lot::RwLock;
 use properties::{ComputedValues, PropertyDeclarationBlock};
 use selector_parser::{ElementExt, PreExistingComputedValues, PseudoElement};
+use selectors::matching::ElementSelectorFlags;
 use sink::Push;
 use std::fmt;
 use std::fmt::Debug;
 use std::ops::Deref;
 use std::sync::Arc;
 use stylist::ApplicableDeclarationBlock;
 
 pub use style_traits::UnsafeNode;
@@ -316,16 +317,29 @@ pub trait TElement : PartialEq + Debug +
     fn mutate_data(&self) -> Option<AtomicRefMut<ElementData>> {
         self.get_data().map(|x| x.borrow_mut())
     }
 
     /// Whether we should skip any root- or item-based display property
     /// blockification on this element.  (This function exists so that Gecko
     /// native anonymous content can opt out of this style fixup.)
     fn skip_root_and_item_based_display_fixup(&self) -> bool;
+
+    /// Sets selector flags, which indicate what kinds of selectors may have
+    /// matched on this element and therefore what kind of work may need to
+    /// be performed when DOM state changes.
+    ///
+    /// This is unsafe, like all the flag-setting methods, because it's only safe
+    /// to call with exclusive access to the element. When setting flags on the
+    /// parent during parallel traversal, we use SequentialTask to queue up the
+    /// set to run after the threads join.
+    unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags);
+
+    /// Returns true if the element has all the specified selector flags.
+    fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool;
 }
 
 /// TNode and TElement aren't Send because we want to be careful and explicit
 /// about our parallel traversal. However, there are certain situations
 /// (including but not limited to the traversal) where we need to send DOM
 /// objects to other threads.
 ///
 /// That's the reason why `SendNode` exists.
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -41,16 +41,17 @@ use gecko_bindings::structs::NODE_HAS_DI
 use gecko_bindings::structs::NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE;
 use parking_lot::RwLock;
 use parser::ParserContextExtraData;
 use properties::{ComputedValues, parse_style_attribute};
 use properties::PropertyDeclarationBlock;
 use rule_tree::CascadeLevel as ServoCascadeLevel;
 use selector_parser::{ElementExt, Snapshot};
 use selectors::Element;
+use selectors::matching::ElementSelectorFlags;
 use selectors::parser::{AttrSelector, NamespaceConstraint};
 use servo_url::ServoUrl;
 use sink::Push;
 use std::fmt;
 use std::ptr;
 use std::sync::Arc;
 use string_cache::{Atom, Namespace, WeakAtom, WeakNamespace};
 use stylist::ApplicableDeclarationBlock;
@@ -374,16 +375,39 @@ lazy_static! {
     /// A dummy base url in order to get it where we don't have any available.
     ///
     /// We need to get rid of this sooner than later.
     pub static ref DUMMY_BASE_URL: ServoUrl = {
         ServoUrl::parse("http://www.example.org").unwrap()
     };
 }
 
+/// Converts flags from the layout used by rust-selectors to the layout used
+/// by Gecko. We could align these and then do this without conditionals, but
+/// it's probably not worth the trouble.
+fn selector_flags_to_node_flags(flags: ElementSelectorFlags) -> u32 {
+    use gecko_bindings::structs::*;
+    use selectors::matching::*;
+    let mut gecko_flags = 0u32;
+    if flags.contains(HAS_SLOW_SELECTOR) {
+        gecko_flags |= NODE_HAS_SLOW_SELECTOR as u32;
+    }
+    if flags.contains(HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
+        gecko_flags |= NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS as u32;
+    }
+    if flags.contains(HAS_EDGE_CHILD_SELECTOR) {
+        gecko_flags |= NODE_HAS_EDGE_CHILD_SELECTOR as u32;
+    }
+    if flags.contains(HAS_EMPTY_SELECTOR) {
+        gecko_flags |= NODE_HAS_EMPTY_SELECTOR as u32;
+    }
+
+    gecko_flags
+}
+
 impl<'le> TElement for GeckoElement<'le> {
     type ConcreteNode = GeckoNode<'le>;
 
     fn as_node(&self) -> Self::ConcreteNode {
         unsafe { GeckoNode(&*(self.0 as *const _ as *const RawGeckoNode)) }
     }
 
     fn style_attribute(&self) -> Option<&Arc<RwLock<PropertyDeclarationBlock>>> {
@@ -471,16 +495,26 @@ impl<'le> TElement for GeckoElement<'le>
     fn skip_root_and_item_based_display_fixup(&self) -> bool {
         // We don't want to fix up display values of native anonymous content.
         // Additionally, we want to skip root-based display fixup for document
         // level native anonymous content subtree roots, since they're not
         // really roots from the style fixup perspective.  Checking that we
         // are NAC handles both cases.
         self.flags() & (NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE as u32) != 0
     }
+
+    unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags) {
+        debug_assert!(!flags.is_empty());
+        self.set_flags(selector_flags_to_node_flags(flags));
+    }
+
+    fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
+        let node_flags = selector_flags_to_node_flags(flags);
+        (self.flags() & node_flags) == node_flags
+    }
 }
 
 impl<'le> PartialEq for GeckoElement<'le> {
     fn eq(&self, other: &Self) -> bool {
         self.0 as *const _ == other.0 as *const _
     }
 }
 
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -7,27 +7,28 @@
 #![allow(unsafe_code)]
 #![deny(missing_docs)]
 
 use {Atom, LocalName};
 use animation::{self, Animation, PropertyAnimation};
 use atomic_refcell::AtomicRefMut;
 use cache::LRUCache;
 use cascade_info::CascadeInfo;
-use context::{SharedStyleContext, StyleContext};
+use context::{SequentialTask, SharedStyleContext, StyleContext};
 use data::{ComputedStyle, ElementData, ElementStyles, PseudoRuleNodes, PseudoStyles};
 use dom::{SendElement, TElement, TNode};
 use properties::{CascadeFlags, ComputedValues, SHAREABLE, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP, cascade};
 use properties::longhands::display::computed_value as display;
 use restyle_hints::{RESTYLE_STYLE_ATTRIBUTE, RestyleHint};
 use rule_tree::{CascadeLevel, StrongRuleNode};
 use selector_parser::{PseudoElement, RestyleDamage, SelectorImpl};
 use selectors::MatchAttr;
 use selectors::bloom::BloomFilter;
-use selectors::matching::{AFFECTED_BY_PSEUDO_ELEMENTS, AFFECTED_BY_STYLE_ATTRIBUTE, MatchingReason, StyleRelations};
+use selectors::matching::{AFFECTED_BY_PSEUDO_ELEMENTS, AFFECTED_BY_STYLE_ATTRIBUTE};
+use selectors::matching::{ElementSelectorFlags, StyleRelations};
 use servo_config::opts;
 use sink::ForgetfulSink;
 use std::collections::HashMap;
 use std::slice::IterMut;
 use std::sync::Arc;
 use stylist::ApplicableDeclarationBlock;
 
 /// Determines the amount of relations where we're going to share style.
@@ -572,60 +573,77 @@ fn compute_rule_node<E: TElement>(contex
 }
 
 impl<E: TElement> PrivateMatchMethods for E {}
 
 /// The public API that elements expose for selector matching.
 pub trait MatchMethods : TElement {
     /// Runs selector matching of this element, and returns the result.
     fn match_element(&self,
-                     context: &StyleContext<Self>,
-                     parent_bf: Option<&BloomFilter>)
+                     context: &mut StyleContext<Self>)
                      -> MatchResults
     {
         let mut applicable_declarations =
             Vec::<ApplicableDeclarationBlock>::with_capacity(16);
 
         let stylist = &context.shared.stylist;
         let style_attribute = self.style_attribute();
         let animation_rules = self.get_animation_rules(None);
+        let mut flags = ElementSelectorFlags::empty();
 
         // Compute the primary rule node.
         let mut primary_relations =
             stylist.push_applicable_declarations(self,
-                                                 parent_bf,
+                                                 Some(context.thread_local.bloom_filter.filter()),
                                                  style_attribute,
                                                  animation_rules,
                                                  None,
                                                  &mut applicable_declarations,
-                                                 MatchingReason::ForStyling);
+                                                 &mut flags);
         let primary_rule_node = compute_rule_node(context, &mut applicable_declarations);
 
         // Compute the pseudo rule nodes.
         let mut per_pseudo: PseudoRuleNodes = HashMap::with_hasher(Default::default());
         SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
             debug_assert!(applicable_declarations.is_empty());
             let pseudo_animation_rules = self.get_animation_rules(Some(&pseudo));
-            stylist.push_applicable_declarations(self, parent_bf, None,
-                                                 pseudo_animation_rules,
+            stylist.push_applicable_declarations(self,
+                                                 Some(context.thread_local.bloom_filter.filter()),
+                                                 None, pseudo_animation_rules,
                                                  Some(&pseudo),
                                                  &mut applicable_declarations,
-                                                 MatchingReason::ForStyling);
+                                                 &mut flags);
 
             if !applicable_declarations.is_empty() {
                 let rule_node = compute_rule_node(context, &mut applicable_declarations);
                 per_pseudo.insert(pseudo, rule_node);
             }
         });
 
         // If we have any pseudo elements, indicate so in the primary StyleRelations.
         if !per_pseudo.is_empty() {
             primary_relations |= AFFECTED_BY_PSEUDO_ELEMENTS;
         }
 
+        // Apply the selector flags.
+        let self_flags = flags.for_self();
+        if !self_flags.is_empty() {
+            unsafe { self.set_selector_flags(self_flags); }
+        }
+        let parent_flags = flags.for_parent();
+        if !parent_flags.is_empty() {
+            if let Some(p) = self.parent_element() {
+                // Avoid the overhead of the SequentialTask if the flags are already set.
+                if !p.has_selector_flags(parent_flags) {
+                    let task = SequentialTask::set_selector_flags(p, parent_flags);
+                    context.thread_local.tasks.push(task);
+                }
+            }
+        }
+
         MatchResults {
             primary: primary_rule_node,
             relations: primary_relations,
             per_pseudo: per_pseudo,
         }
     }
 
     /// Get the appropriate MatchResults from the current styles, to perform a
--- a/servo/components/style/parallel.rs
+++ b/servo/components/style/parallel.rs
@@ -21,17 +21,16 @@
 //! code in the parallel traversal.
 
 #![deny(missing_docs)]
 
 use context::TraversalStatistics;
 use dom::{OpaqueNode, SendNode, TElement, TNode};
 use rayon;
 use scoped_tls::ScopedTLS;
-use servo_config::opts;
 use std::borrow::Borrow;
 use traversal::{DomTraversal, PerLevelTraversalData, PreTraverseToken};
 
 /// The chunk size used to split the parallel traversal nodes.
 ///
 /// We send each `CHUNK_SIZE` nodes as a different work unit to the work queue.
 pub const CHUNK_SIZE: usize = 64;
 
@@ -70,17 +69,17 @@ pub fn traverse_dom<E, D>(traversal: &D,
 
     queue.install(|| {
         rayon::scope(|scope| {
             traverse_nodes(nodes, root, traversal_data, scope, traversal, &tls);
         });
     });
 
     // Dump statistics to stdout if requested.
-    if TraversalStatistics::should_dump() || opts::get().style_sharing_stats {
+    if TraversalStatistics::should_dump() {
         let slots = unsafe { tls.unsafe_get() };
         let aggregate = slots.iter().fold(TraversalStatistics::default(), |acc, t| {
             match *t.borrow() {
                 None => acc,
                 Some(ref cx) => &cx.borrow().statistics + &acc,
             }
         });
         println!("{}", aggregate);
--- a/servo/components/style/restyle_hints.rs
+++ b/servo/components/style/restyle_hints.rs
@@ -10,17 +10,17 @@ use Atom;
 use dom::TElement;
 use element_state::*;
 #[cfg(feature = "gecko")]
 use gecko_bindings::structs::nsRestyleHint;
 #[cfg(feature = "servo")]
 use heapsize::HeapSizeOf;
 use selector_parser::{AttrValue, NonTSPseudoClass, Snapshot, SelectorImpl};
 use selectors::{Element, MatchAttr};
-use selectors::matching::{MatchingReason, StyleRelations};
+use selectors::matching::{ElementSelectorFlags, StyleRelations};
 use selectors::matching::matches_complex_selector;
 use selectors::parser::{AttrSelector, Combinator, ComplexSelector, SimpleSelector};
 use std::clone::Clone;
 use std::sync::Arc;
 
 bitflags! {
     /// When the ElementState of an element (like IN_HOVER_STATE) changes,
     /// certain pseudo-classes (like :hover) may require us to restyle that
@@ -519,24 +519,26 @@ impl DependencySet {
         if hint.is_all() {
             return;
         }
         for dep in deps {
             debug_assert!((!state_changes.is_empty() && !dep.sensitivities.states.is_empty()) ||
                           (attrs_changed && dep.sensitivities.attrs),
                           "Testing a known ineffective dependency?");
             if (attrs_changed || state_changes.intersects(dep.sensitivities.states)) && !hint.intersects(dep.hint) {
+                // We can ignore the selector flags, since they would have already been set during
+                // original matching for any element that might change its matching behavior here.
                 let matched_then =
                     matches_complex_selector(&dep.selector, snapshot, None,
                                              &mut StyleRelations::empty(),
-                                             MatchingReason::Other);
+                                             &mut ElementSelectorFlags::empty());
                 let matches_now =
                     matches_complex_selector(&dep.selector, element, None,
                                              &mut StyleRelations::empty(),
-                                             MatchingReason::Other);
+                                             &mut ElementSelectorFlags::empty());
                 if matched_then != matches_now {
                     hint.insert(dep.hint);
                 }
                 if hint.is_all() {
                     break;
                 }
             }
         }
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -13,32 +13,33 @@ use error_reporting::StdoutErrorReporter
 use keyframes::KeyframesAnimation;
 use media_queries::Device;
 use parking_lot::RwLock;
 use pdqsort::sort_by;
 use properties::{self, CascadeFlags, ComputedValues, INHERIT_ALL};
 use properties::PropertyDeclarationBlock;
 use restyle_hints::{RestyleHint, DependencySet};
 use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource};
-use selector_parser::{ElementExt, SelectorImpl, PseudoElement, Snapshot};
+use selector_parser::{SelectorImpl, PseudoElement, Snapshot};
 use selectors::Element;
 use selectors::bloom::BloomFilter;
 use selectors::matching::{AFFECTED_BY_ANIMATIONS, AFFECTED_BY_TRANSITIONS};
 use selectors::matching::{AFFECTED_BY_STYLE_ATTRIBUTE, AFFECTED_BY_PRESENTATIONAL_HINTS};
-use selectors::matching::{MatchingReason, StyleRelations, matches_complex_selector};
+use selectors::matching::{ElementSelectorFlags, StyleRelations, matches_complex_selector};
 use selectors::parser::{Selector, SimpleSelector, LocalName as LocalNameSelector, ComplexSelector};
 use sink::Push;
 use smallvec::VecLike;
 use std::borrow::Borrow;
 use std::collections::HashMap;
 use std::fmt;
 use std::hash::Hash;
 use std::sync::Arc;
 use style_traits::viewport::ViewportConstraints;
 use stylesheets::{CssRule, Origin, StyleRule, Stylesheet, UserAgentStylesheets};
+use thread_state;
 use viewport::{self, MaybeNew, ViewportRule};
 
 pub use ::fnv::FnvHashMap;
 
 /// This structure holds all the selectors and device characteristics
 /// for a given document. The selectors are converted into `Rule`s
 /// (defined in rust-selectors), and introduced in a `SelectorMap`
 /// depending on the pseudo-element (see `PerPseudoElementSelectorMap`),
@@ -363,48 +364,71 @@ impl Stylist {
     /// Check the documentation on lazy pseudo-elements in
     /// docs/components/style.md
     pub fn lazily_compute_pseudo_element_style<E>(&self,
                                                   element: &E,
                                                   pseudo: &PseudoElement,
                                                   parent: &Arc<ComputedValues>,
                                                   default: &Arc<ComputedValues>)
                                                   -> Option<ComputedStyle>
-        where E: ElementExt +
+        where E: TElement +
                  fmt::Debug +
                  PresentationalHintsSynthetizer
     {
         debug_assert!(SelectorImpl::pseudo_element_cascade_type(pseudo).is_lazy());
         if self.pseudos_map.get(pseudo).is_none() {
             return None;
         }
 
         let mut declarations = vec![];
 
+        let mut flags = ElementSelectorFlags::empty();
         self.push_applicable_declarations(element,
                                           None,
                                           None,
                                           AnimationRules(None, None),
                                           Some(pseudo),
                                           &mut declarations,
-                                          MatchingReason::ForStyling);
+                                          &mut flags);
 
         let rule_node =
             self.rule_tree.insert_ordered_rules(
                 declarations.into_iter().map(|a| (a.source, a.level)));
 
         let computed =
             properties::cascade(self.device.au_viewport_size(),
                                 &rule_node,
                                 Some(&**parent),
                                 default,
                                 None,
                                 Box::new(StdoutErrorReporter),
                                 CascadeFlags::empty());
 
+        // Apply the selector flags. We should be in sequential mode already,
+        // so we can directly apply the parent flags.
+        if cfg!(feature = "servo") {
+            // Servo calls this function from the worker, but only for internal
+            // pseudos, so we should never generate selector flags here.
+            debug_assert!(flags.is_empty());
+        } else {
+            // Gecko calls this from sequential mode, so we can directly apply
+            // the flags.
+            debug_assert!(thread_state::get() == thread_state::LAYOUT);
+            let self_flags = flags.for_self();
+            if !self_flags.is_empty() {
+                unsafe { element.set_selector_flags(self_flags); }
+            }
+            let parent_flags = flags.for_parent();
+            if !parent_flags.is_empty() {
+                if let Some(p) = element.parent_element() {
+                    unsafe { p.set_selector_flags(parent_flags); }
+                }
+            }
+        }
+
         Some(ComputedStyle::new(rule_node, Arc::new(computed)))
     }
 
     /// Set a given device, which may change the styles that apply to the
     /// document.
     ///
     /// This means that we may need to rebuild style data even if the
     /// stylesheets haven't changed.
@@ -490,18 +514,18 @@ impl Stylist {
     pub fn push_applicable_declarations<E, V>(
                                         &self,
                                         element: &E,
                                         parent_bf: Option<&BloomFilter>,
                                         style_attribute: Option<&Arc<RwLock<PropertyDeclarationBlock>>>,
                                         animation_rules: AnimationRules,
                                         pseudo_element: Option<&PseudoElement>,
                                         applicable_declarations: &mut V,
-                                        reason: MatchingReason) -> StyleRelations
-        where E: ElementExt +
+                                        flags: &mut ElementSelectorFlags) -> StyleRelations
+        where E: TElement +
                  fmt::Debug +
                  PresentationalHintsSynthetizer,
               V: Push<ApplicableDeclarationBlock> + VecLike<ApplicableDeclarationBlock>
     {
         debug_assert!(!self.is_device_dirty);
         debug_assert!(style_attribute.is_none() || pseudo_element.is_none(),
                       "Style attributes do not apply to pseudo-elements");
         debug_assert!(pseudo_element.is_none() ||
@@ -516,17 +540,17 @@ impl Stylist {
         let mut relations = StyleRelations::empty();
 
         debug!("Determining if style is shareable: pseudo: {}", pseudo_element.is_some());
         // Step 1: Normal user-agent rules.
         map.user_agent.get_all_matching_rules(element,
                                               parent_bf,
                                               applicable_declarations,
                                               &mut relations,
-                                              reason,
+                                              flags,
                                               CascadeLevel::UANormal);
         debug!("UA normal: {:?}", relations);
 
         // Step 2: Presentational hints.
         let length_before_preshints = applicable_declarations.len();
         element.synthesize_presentational_hints_for_legacy_attributes(applicable_declarations);
         if applicable_declarations.len() != length_before_preshints {
             if cfg!(debug_assertions) {
@@ -540,24 +564,24 @@ impl Stylist {
         debug!("preshints: {:?}", relations);
 
         if element.matches_user_and_author_rules() {
             // Step 3: User and author normal rules.
             map.user.get_all_matching_rules(element,
                                             parent_bf,
                                             applicable_declarations,
                                             &mut relations,
-                                            reason,
+                                            flags,
                                             CascadeLevel::UserNormal);
             debug!("user normal: {:?}", relations);
             map.author.get_all_matching_rules(element,
                                               parent_bf,
                                               applicable_declarations,
                                               &mut relations,
-                                              reason,
+                                              flags,
                                               CascadeLevel::AuthorNormal);
             debug!("author normal: {:?}", relations);
 
             // Step 4: Normal style attributes.
             if let Some(sa) = style_attribute {
                 if sa.read().any_normal() {
                     relations |= AFFECTED_BY_STYLE_ATTRIBUTE;
                     Push::push(
@@ -581,17 +605,17 @@ impl Stylist {
             }
             debug!("animation: {:?}", relations);
 
             // Step 6: Author-supplied `!important` rules.
             map.author.get_all_matching_rules(element,
                                               parent_bf,
                                               applicable_declarations,
                                               &mut relations,
-                                              reason,
+                                              flags,
                                               CascadeLevel::AuthorImportant);
 
             debug!("author important: {:?}", relations);
 
             // Step 7: `!important` style attributes.
             if let Some(sa) = style_attribute {
                 if sa.read().any_important() {
                     relations |= AFFECTED_BY_STYLE_ATTRIBUTE;
@@ -604,30 +628,30 @@ impl Stylist {
 
             debug!("style attr important: {:?}", relations);
 
             // Step 8: User `!important` rules.
             map.user.get_all_matching_rules(element,
                                             parent_bf,
                                             applicable_declarations,
                                             &mut relations,
-                                            reason,
+                                            flags,
                                             CascadeLevel::UserImportant);
 
             debug!("user important: {:?}", relations);
         } else {
             debug!("skipping non-agent rules");
         }
 
         // Step 9: UA `!important` rules.
         map.user_agent.get_all_matching_rules(element,
                                               parent_bf,
                                               applicable_declarations,
                                               &mut relations,
-                                              reason,
+                                              flags,
                                               CascadeLevel::UAImportant);
 
         debug!("UA important: {:?}", relations);
 
         // Step 10: Transitions.
         // The transitions sheet (CSS transitions that are tied to CSS markup)
         if let Some(anim) = animation_rules.1 {
             relations |= AFFECTED_BY_TRANSITIONS;
@@ -658,36 +682,36 @@ impl Stylist {
     /// Whether two elements match the same not-common style-affecting attribute
     /// rules.
     ///
     /// This is used to test elements and candidates in the style-sharing
     /// candidate cache.
     pub fn match_same_not_common_style_affecting_attributes_rules<E>(&self,
                                                                      element: &E,
                                                                      candidate: &E) -> bool
-        where E: ElementExt,
+        where E: TElement,
     {
         use selectors::matching::StyleRelations;
         use selectors::matching::matches_complex_selector;
         // TODO(emilio): we can probably do better, the candidate should already
         // know what rules it matches. Also, we should only match until we find
         // a descendant combinator, the rest should be ok, since the parent is
         // the same.
         //
         // TODO(emilio): Use the bloom filter, since they contain the element's
         // ancestor chain and it's correct for the candidate too.
         for ref selector in self.non_common_style_affecting_attributes_selectors.iter() {
             let element_matches =
                 matches_complex_selector(&selector.complex_selector, element,
                                          None, &mut StyleRelations::empty(),
-                                         MatchingReason::Other);
+                                         &mut ElementSelectorFlags::empty());
             let candidate_matches =
                 matches_complex_selector(&selector.complex_selector, candidate,
                                          None, &mut StyleRelations::empty(),
-                                         MatchingReason::Other);
+                                         &mut ElementSelectorFlags::empty());
 
             if element_matches != candidate_matches {
                 return false;
             }
         }
 
         true
     }
@@ -699,35 +723,35 @@ impl Stylist {
     }
 
     /// Returns whether two elements match the same sibling-affecting rules.
     ///
     /// This is also for the style sharing candidate cache.
     pub fn match_same_sibling_affecting_rules<E>(&self,
                                                  element: &E,
                                                  candidate: &E) -> bool
-        where E: ElementExt,
+        where E: TElement,
     {
         use selectors::matching::StyleRelations;
         use selectors::matching::matches_complex_selector;
         // TODO(emilio): we can probably do better, the candidate should already
         // know what rules it matches.
         //
         // TODO(emilio): Use the bloom filter, since they contain the element's
         // ancestor chain and it's correct for the candidate too.
         for ref selector in self.sibling_affecting_selectors.iter() {
             let element_matches =
                 matches_complex_selector(&selector.complex_selector, element,
                                          None, &mut StyleRelations::empty(),
-                                         MatchingReason::Other);
+                                         &mut ElementSelectorFlags::empty());
 
             let candidate_matches =
                 matches_complex_selector(&selector.complex_selector, candidate,
                                          None, &mut StyleRelations::empty(),
-                                         MatchingReason::Other);
+                                         &mut ElementSelectorFlags::empty());
 
             if element_matches != candidate_matches {
                 debug!("match_same_sibling_affecting_rules: Failure due to {:?}",
                        selector.complex_selector);
                 return false;
             }
         }
 
@@ -855,17 +879,17 @@ impl SelectorMap {
     ///
     /// Extract matching rules as per element's ID, classes, tag name, etc..
     /// Sort the Rules at the end to maintain cascading order.
     pub fn get_all_matching_rules<E, V>(&self,
                                         element: &E,
                                         parent_bf: Option<&BloomFilter>,
                                         matching_rules_list: &mut V,
                                         relations: &mut StyleRelations,
-                                        reason: MatchingReason,
+                                        flags: &mut ElementSelectorFlags,
                                         cascade_level: CascadeLevel)
         where E: Element<Impl=SelectorImpl>,
               V: VecLike<ApplicableDeclarationBlock>
     {
         if self.empty {
             return
         }
 
@@ -873,51 +897,51 @@ impl SelectorMap {
         let init_len = matching_rules_list.len();
         if let Some(id) = element.get_id() {
             SelectorMap::get_matching_rules_from_hash(element,
                                                       parent_bf,
                                                       &self.id_hash,
                                                       &id,
                                                       matching_rules_list,
                                                       relations,
-                                                      reason,
+                                                      flags,
                                                       cascade_level)
         }
 
         element.each_class(|class| {
             SelectorMap::get_matching_rules_from_hash(element,
                                                       parent_bf,
                                                       &self.class_hash,
                                                       class,
                                                       matching_rules_list,
                                                       relations,
-                                                      reason,
+                                                      flags,
                                                       cascade_level);
         });
 
         let local_name_hash = if element.is_html_element_in_html_document() {
             &self.lower_local_name_hash
         } else {
             &self.local_name_hash
         };
         SelectorMap::get_matching_rules_from_hash(element,
                                                   parent_bf,
                                                   local_name_hash,
                                                   element.get_local_name(),
                                                   matching_rules_list,
                                                   relations,
-                                                  reason,
+                                                  flags,
                                                   cascade_level);
 
         SelectorMap::get_matching_rules(element,
                                         parent_bf,
                                         &self.other_rules,
                                         matching_rules_list,
                                         relations,
-                                        reason,
+                                        flags,
                                         cascade_level);
 
         // Sort only the rules we just added.
         sort_by_key(&mut matching_rules_list[init_len..],
                     |block| (block.specificity, block.source_order));
     }
 
     /// Append to `rule_list` all universal Rules (rules with selector `*|*`) in
@@ -958,56 +982,56 @@ impl SelectorMap {
 
     fn get_matching_rules_from_hash<E, Str, BorrowedStr: ?Sized, Vector>(
         element: &E,
         parent_bf: Option<&BloomFilter>,
         hash: &FnvHashMap<Str, Vec<Rule>>,
         key: &BorrowedStr,
         matching_rules: &mut Vector,
         relations: &mut StyleRelations,
-        reason: MatchingReason,
+        flags: &mut ElementSelectorFlags,
         cascade_level: CascadeLevel)
         where E: Element<Impl=SelectorImpl>,
               Str: Borrow<BorrowedStr> + Eq + Hash,
               BorrowedStr: Eq + Hash,
               Vector: VecLike<ApplicableDeclarationBlock>
     {
         if let Some(rules) = hash.get(key) {
             SelectorMap::get_matching_rules(element,
                                             parent_bf,
                                             rules,
                                             matching_rules,
                                             relations,
-                                            reason,
+                                            flags,
                                             cascade_level)
         }
     }
 
     /// Adds rules in `rules` that match `element` to the `matching_rules` list.
     fn get_matching_rules<E, V>(element: &E,
                                 parent_bf: Option<&BloomFilter>,
                                 rules: &[Rule],
                                 matching_rules: &mut V,
                                 relations: &mut StyleRelations,
-                                reason: MatchingReason,
+                                flags: &mut ElementSelectorFlags,
                                 cascade_level: CascadeLevel)
         where E: Element<Impl=SelectorImpl>,
               V: VecLike<ApplicableDeclarationBlock>
     {
         for rule in rules.iter() {
             let guard = rule.style_rule.read();
             let block = guard.block.read();
             let any_declaration_for_importance = if cascade_level.is_important() {
                 block.any_important()
             } else {
                 block.any_normal()
             };
             if any_declaration_for_importance &&
                matches_complex_selector(&*rule.selector, element, parent_bf,
-                                        relations, reason) {
+                                        relations, flags) {
                 matching_rules.push(
                     rule.to_applicable_declaration_block(cascade_level));
             }
         }
     }
 
     /// Insert rule into the correct hash.
     /// Order in which to try: id_hash, class_hash, local_name_hash, other_rules.
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -307,34 +307,44 @@ pub trait DomTraversal<E: TElement> : Sy
     /// NB: We do this check on runtime. We could guarantee correctness in this
     /// regard via the type system via a `TraversalDriver` trait for this trait,
     /// that could be one of two concrete types. It's not clear whether the
     /// potential code size impact of that is worth it.
     fn is_parallel(&self) -> bool;
 }
 
 /// Helper for the function below.
-fn resolve_style_internal<E, F>(context: &StyleContext<E>, element: E, ensure_data: &F)
+fn resolve_style_internal<E, F>(context: &mut StyleContext<E>, element: E, ensure_data: &F)
                                 -> Option<E>
     where E: TElement,
           F: Fn(E),
 {
     ensure_data(element);
     let mut data = element.mutate_data().unwrap();
     let mut display_none_root = None;
 
     // If the Element isn't styled, we need to compute its style.
     if data.get_styles().is_none() {
         // Compute the parent style if necessary.
-        if let Some(parent) = element.parent_element() {
-            display_none_root = resolve_style_internal(context, parent, ensure_data);
+        let parent = element.parent_element();
+        if let Some(p) = parent {
+            display_none_root = resolve_style_internal(context, p, ensure_data);
+        }
+
+        // Maintain the bloom filter. If it doesn't exist, we need to build it
+        // from scratch. Otherwise we just need to push the parent.
+        if context.thread_local.bloom_filter.is_empty() {
+            context.thread_local.bloom_filter.rebuild(element);
+        } else {
+            context.thread_local.bloom_filter.push(parent.unwrap());
+            context.thread_local.bloom_filter.assert_complete(element);
         }
 
         // Compute our style.
-        let match_results = element.match_element(context, None);
+        let match_results = element.match_element(context);
         let shareable = match_results.primary_is_shareable();
         element.cascade_node(context, &mut data, element.parent_element(),
                              match_results.primary,
                              match_results.per_pseudo,
                              shareable);
 
         // Conservatively mark us as having dirty descendants, since there might
         // be other unstyled siblings we miss when walking straight up the parent
@@ -350,23 +360,26 @@ fn resolve_style_internal<E, F>(context:
 
     return display_none_root
 }
 
 /// Manually resolve style by sequentially walking up the parent chain to the
 /// first styled Element, ignoring pending restyles. The resolved style is
 /// made available via a callback, and can be dropped by the time this function
 /// returns in the display:none subtree case.
-pub fn resolve_style<E, F, G, H>(context: &StyleContext<E>, element: E,
+pub fn resolve_style<E, F, G, H>(context: &mut StyleContext<E>, element: E,
                                  ensure_data: &F, clear_data: &G, callback: H)
     where E: TElement,
           F: Fn(E),
           G: Fn(E),
           H: FnOnce(&ElementStyles)
 {
+    // Clear the bloom filter, just in case the caller is reusing TLS.
+    context.thread_local.bloom_filter.clear();
+
     // Resolve styles up the tree.
     let display_none_root = resolve_style_internal(context, element, ensure_data);
 
     // Make them available for the scope of the callback. The callee may use the
     // argument, or perform any other processing that requires the styles to exist
     // on the Element.
     callback(element.borrow_data().unwrap().styles());
 
@@ -494,18 +507,17 @@ fn compute_style<E, D>(_traversal: &D,
                     // but it can change from unknown to known at this step.
                     traversal_data.current_dom_depth = Some(dom_depth);
 
                     context.thread_local.bloom_filter.assert_complete(element);
 
                     // Perform the CSS selector matching.
                     context.thread_local.statistics.elements_matched += 1;
 
-                    let filter = context.thread_local.bloom_filter.filter();
-                    Some(element.match_element(context, Some(filter)))
+                    Some(element.match_element(context))
                 }
             }
         }
         RestyleKind::CascadeWithReplacements(hint) => {
             Some(element.cascade_with_replacements(hint, context, &mut data))
         }
         RestyleKind::CascadeOnly => {
             // TODO(emilio): Stop doing this work, and teach cascade_node about
--- a/servo/tests/unit/script/size_of.rs
+++ b/servo/tests/unit/script/size_of.rs
@@ -26,19 +26,19 @@ macro_rules! sizeof_checker (
                         stringify!($t), old, new)
         }
     });
 );
 
 // Update the sizes here
 sizeof_checker!(size_event_target, EventTarget, 40);
 sizeof_checker!(size_node, Node, 152);
-sizeof_checker!(size_element, Element, 320);
-sizeof_checker!(size_htmlelement, HTMLElement, 336);
-sizeof_checker!(size_div, HTMLDivElement, 336);
-sizeof_checker!(size_span, HTMLSpanElement, 336);
+sizeof_checker!(size_element, Element, 312);
+sizeof_checker!(size_htmlelement, HTMLElement, 328);
+sizeof_checker!(size_div, HTMLDivElement, 328);
+sizeof_checker!(size_span, HTMLSpanElement, 328);
 sizeof_checker!(size_text, Text, 184);
 sizeof_checker!(size_characterdata, CharacterData, 184);
 sizeof_checker!(size_servothreadsafelayoutnode, ServoThreadSafeLayoutNode, 16);
 
 // We use these types in the parallel traversal. They should stay pointer-sized.
 sizeof_checker!(size_sendelement, SendElement, 8);
 sizeof_checker!(size_sendnode, SendNode, 8);