servo: Merge #12469 - style: Rewrite the restyle hints code to allow different kinds of element snapshots (from emilio:stylo); r=bholley
authorEmilio Cobos Álvarez <ecoal95@gmail.com>
Thu, 21 Jul 2016 16:54:34 -0500
changeset 339345 6bbba3a67511db235ec2509ba9ffe13f2ee0fcb1
parent 339344 a3a43f2ec02cad0f8381d772a6891a0f43d39f8a
child 339346 658ef2031b62490c4665a4b19bb624272460cf03
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
servo: Merge #12469 - style: Rewrite the restyle hints code to allow different kinds of element snapshots (from emilio:stylo); r=bholley <!-- Please describe your changes on the following line: --> --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors <!-- Either: --> - [x] These changes do not require tests because refactoring. <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> This is a rewrite for how style interfaces with its consumers in order to allow different representations for an element snapshot. This also changes the requirements of an element snapshot, requiring them to only implement MatchAttr, instead of MatchAttrGeneric. This is important for stylo since implementing MatchAttrGeneric is way more difficult for us given the atom limitations. This also allows for more performant implementations in the Gecko side of things. I don't want to get this merged just yet, mainly because the stylo part is not implemented, but I'd like early feedback from @bholley and/or @heycam: How do you see this approach? I don't think we'll have much problem to implement MatchAttr for our element snapshots, but... worth checking. r? @heycam Source-Repo: https://github.com/servo/servo Source-Revision: 1e0321f7dde5f33f7d26bbd4f088622fa3660477
servo/components/script/dom/bindings/trace.rs
servo/components/script/dom/document.rs
servo/components/script/layout_wrapper.rs
servo/components/style/attr.rs
servo/components/style/dom.rs
servo/components/style/restyle_hints.rs
servo/components/style/selector_impl.rs
servo/components/style/selector_matching.rs
servo/components/style/servo_selector_impl.rs
servo/ports/geckolib/gecko_bindings/bindings.rs
servo/ports/geckolib/gecko_bindings/structs_debug.rs
servo/ports/geckolib/gecko_bindings/structs_release.rs
servo/ports/geckolib/gecko_bindings/tools/regen.py
servo/ports/geckolib/glue.rs
servo/ports/geckolib/lib.rs
servo/ports/geckolib/snapshot.rs
servo/ports/geckolib/snapshot_helpers.rs
servo/ports/geckolib/wrapper.rs
--- a/servo/components/script/dom/bindings/trace.rs
+++ b/servo/components/script/dom/bindings/trace.rs
@@ -84,18 +84,17 @@ use std::rc::Rc;
 use std::sync::Arc;
 use std::sync::atomic::{AtomicBool, AtomicUsize};
 use std::sync::mpsc::{Receiver, Sender};
 use std::time::SystemTime;
 use string_cache::{Atom, Namespace, QualName};
 use style::attr::{AttrIdentifier, AttrValue, LengthOrPercentageOrAuto};
 use style::element_state::*;
 use style::properties::PropertyDeclarationBlock;
-use style::restyle_hints::ElementSnapshot;
-use style::selector_impl::PseudoElement;
+use style::selector_impl::{PseudoElement, ElementSnapshot};
 use style::values::specified::Length;
 use url::Origin as UrlOrigin;
 use url::Url;
 use uuid::Uuid;
 use webrender_traits::{WebGLBufferId, WebGLError, WebGLFramebufferId, WebGLProgramId};
 use webrender_traits::{WebGLRenderbufferId, WebGLShaderId, WebGLTextureId};
 
 /// A trait to allow tracing (only) DOM objects.
--- a/servo/components/script/dom/document.rs
+++ b/servo/components/script/dom/document.rs
@@ -117,17 +117,17 @@ use std::default::Default;
 use std::iter::once;
 use std::mem;
 use std::ptr;
 use std::rc::Rc;
 use std::sync::Arc;
 use string_cache::{Atom, QualName};
 use style::attr::AttrValue;
 use style::context::ReflowGoal;
-use style::restyle_hints::ElementSnapshot;
+use style::selector_impl::ElementSnapshot;
 use style::str::{split_html_space_chars, str_join};
 use style::stylesheets::Stylesheet;
 use time;
 use url::Url;
 use url::percent_encoding::percent_decode;
 use util::prefs::PREFS;
 
 #[derive(JSTraceable, PartialEq, HeapSizeOf)]
@@ -1846,25 +1846,31 @@ impl Document {
     }
 
     pub fn get_element_by_id(&self, id: &Atom) -> Option<Root<Element>> {
         self.id_map.borrow().get(&id).map(|ref elements| Root::from_ref(&*(*elements)[0]))
     }
 
     pub fn element_state_will_change(&self, el: &Element) {
         let mut map = self.modified_elements.borrow_mut();
-        let snapshot = map.entry(JS::from_ref(el)).or_insert(ElementSnapshot::new());
+        let snapshot = map.entry(JS::from_ref(el))
+                          .or_insert_with(|| {
+                              ElementSnapshot::new(el.html_element_in_html_document())
+                          });
         if snapshot.state.is_none() {
             snapshot.state = Some(el.state());
         }
     }
 
     pub fn element_attr_will_change(&self, el: &Element) {
         let mut map = self.modified_elements.borrow_mut();
-        let mut snapshot = map.entry(JS::from_ref(el)).or_insert(ElementSnapshot::new());
+        let mut snapshot = map.entry(JS::from_ref(el))
+                              .or_insert_with(|| {
+                                  ElementSnapshot::new(el.html_element_in_html_document())
+                              });
         if snapshot.attrs.is_none() {
             let attrs = el.attrs()
                           .iter()
                           .map(|attr| (attr.identifier().clone(), attr.value().clone()))
                           .collect();
             snapshot.attrs = Some(attrs);
         }
     }
--- a/servo/components/script/layout_wrapper.rs
+++ b/servo/components/script/layout_wrapper.rs
@@ -55,18 +55,17 @@ use string_cache::{Atom, BorrowedAtom, B
 use style::attr::AttrValue;
 use style::computed_values::display;
 use style::context::SharedStyleContext;
 use style::data::PrivateStyleData;
 use style::dom::{PresentationalHintsSynthetizer, OpaqueNode, TDocument, TElement, TNode, UnsafeNode};
 use style::element_state::*;
 use style::properties::{PropertyDeclaration, PropertyDeclarationBlock};
 use style::refcell::{Ref, RefCell, RefMut};
-use style::restyle_hints::ElementSnapshot;
-use style::selector_impl::{NonTSPseudoClass, ServoSelectorImpl};
+use style::selector_impl::{ElementSnapshot, NonTSPseudoClass, ServoSelectorImpl};
 use style::sink::Push;
 use style::str::is_whitespace;
 use url::Url;
 
 #[derive(Copy, Clone)]
 pub struct ServoLayoutNode<'a> {
     /// The wrapped node.
     node: LayoutJS<Node>,
--- a/servo/components/style/attr.rs
+++ b/servo/components/style/attr.rs
@@ -24,17 +24,17 @@ const UNSIGNED_LONG_MAX: u32 = 214748364
 #[derive(Clone, Copy, Debug, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum LengthOrPercentageOrAuto {
     Auto,
     Percentage(f32),
     Length(Au),
 }
 
-#[derive(PartialEq, Clone)]
+#[derive(PartialEq, Clone, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum AttrValue {
     String(String),
     TokenList(String, Vec<Atom>),
     UInt(String, u32),
     Int(String, i32),
     Double(String, f64),
     Atom(Atom),
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -6,17 +6,17 @@
 
 #![allow(unsafe_code)]
 
 use context::SharedStyleContext;
 use data::PrivateStyleData;
 use element_state::ElementState;
 use properties::{ComputedValues, PropertyDeclaration, PropertyDeclarationBlock};
 use refcell::{Ref, RefMut};
-use restyle_hints::{ElementSnapshot, RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint};
+use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint};
 use selector_impl::{ElementExt, SelectorImplExt};
 use selectors::Element;
 use selectors::matching::DeclarationBlock;
 use sink::Push;
 use std::ops::BitOr;
 use std::sync::Arc;
 use string_cache::{Atom, Namespace};
 
@@ -187,17 +187,18 @@ pub trait TNode : Sized + Copy + Clone {
 pub trait TDocument : Sized + Copy + Clone {
     type ConcreteNode: TNode<ConcreteElement = Self::ConcreteElement, ConcreteDocument = Self>;
     type ConcreteElement: TElement<ConcreteNode = Self::ConcreteNode, ConcreteDocument = Self>;
 
     fn as_node(&self) -> Self::ConcreteNode;
 
     fn root_node(&self) -> Option<Self::ConcreteNode>;
 
-    fn drain_modified_elements(&self) -> Vec<(Self::ConcreteElement, ElementSnapshot)>;
+    fn drain_modified_elements(&self) -> Vec<(Self::ConcreteElement,
+                                              <Self::ConcreteElement as ElementExt>::Snapshot)>;
 }
 
 pub trait PresentationalHintsSynthetizer {
     fn synthesize_presentational_hints_for_legacy_attributes<V>(&self, hints: &mut V)
         where V: Push<DeclarationBlock<Vec<PropertyDeclaration>>>;
 }
 
 pub trait TElement : Sized + Copy + Clone + ElementExt + PresentationalHintsSynthetizer {
--- a/servo/components/style/restyle_hints.rs
+++ b/servo/components/style/restyle_hints.rs
@@ -1,248 +1,266 @@
 /* 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/. */
 
 //! Restyle hints: an optimization to avoid unnecessarily matching selectors.
 
-use attr::{AttrIdentifier, AttrValue};
 use element_state::*;
-use selector_impl::{SelectorImplExt, TheSelectorImpl, AttrString};
+use selector_impl::{ElementExt, SelectorImplExt, TheSelectorImpl, AttrString};
 use selectors::matching::matches_compound_selector;
 use selectors::parser::{AttrSelector, Combinator, CompoundSelector, SelectorImpl, SimpleSelector};
-use selectors::{Element, MatchAttrGeneric};
-#[cfg(feature = "gecko")] use selectors::MatchAttr;
+use selectors::{Element, MatchAttr};
 use std::clone::Clone;
 use std::sync::Arc;
-use string_cache::{Atom, BorrowedAtom, BorrowedNamespace, Namespace};
+use string_cache::{Atom, BorrowedAtom, BorrowedNamespace};
 
 /// When the ElementState of an element (like IN_HOVER_STATE) changes, certain
 /// pseudo-classes (like :hover) may require us to restyle that element, its
 /// siblings, and/or its descendants. Similarly, when various attributes of an
-/// element change, we may also need to restyle things with id, class, and attribute
-/// selectors. Doing this conservatively is expensive, and so we use RestyleHints to
-/// short-circuit work we know is unnecessary.
-
+/// element change, we may also need to restyle things with id, class, and
+/// attribute selectors. Doing this conservatively is expensive, and so we use
+/// RestyleHints to short-circuit work we know is unnecessary.
 bitflags! {
     pub flags RestyleHint: u8 {
         #[doc = "Rerun selector matching on the element."]
         const RESTYLE_SELF = 0x01,
         #[doc = "Rerun selector matching on all of the element's descendants."]
         // NB: In Gecko, we have RESTYLE_SUBTREE which is inclusive of self, but heycam isn't aware
         // of a good reason for that.
         const RESTYLE_DESCENDANTS = 0x02,
         #[doc = "Rerun selector matching on all later siblings of the element and all of their descendants."]
         const RESTYLE_LATER_SIBLINGS = 0x04,
     }
 }
 
-/// In order to compute restyle hints, we perform a selector match against a list of partial
-/// selectors whose rightmost simple selector may be sensitive to the thing being changed. We
-/// do this matching twice, once for the element as it exists now and once for the element as it
-/// existed at the time of the last restyle. If the results of the selector match differ, that means
-/// that the given partial selector is sensitive to the change, and we compute a restyle hint
-/// based on its combinator.
+/// In order to compute restyle hints, we perform a selector match against a
+/// list of partial selectors whose rightmost simple selector may be sensitive
+/// to the thing being changed. We do this matching twice, once for the element
+/// as it exists now and once for the element as it existed at the time of the
+/// last restyle. If the results of the selector match differ, that means that
+/// the given partial selector is sensitive to the change, and we compute a
+/// restyle hint based on its combinator.
 ///
-/// In order to run selector matching against the old element state, we generate a wrapper for
-/// the element which claims to have the old state. This is the ElementWrapper logic below.
+/// In order to run selector matching against the old element state, we generate
+/// a wrapper for the element which claims to have the old state. This is the
+/// ElementWrapper logic below.
 ///
-/// Gecko does this differently for element states, and passes a mask called mStateMask, which
-/// indicates the states that need to be ignored during selector matching. This saves an ElementWrapper
-/// allocation and an additional selector match call at the expense of additional complexity inside
-/// the selector matching logic. This only works for boolean states though, so we still need to
-/// take the ElementWrapper approach for attribute-dependent style. So we do it the same both ways for
-/// now to reduce complexity, but it's worth measuring the performance impact (if any) of the
-/// mStateMask approach.
+/// Gecko does this differently for element states, and passes a mask called
+/// mStateMask, which indicates the states that need to be ignored during
+/// selector matching. This saves an ElementWrapper allocation and an additional
+/// selector match call at the expense of additional complexity inside the
+/// selector matching logic. This only works for boolean states though, so we
+/// still need to take the ElementWrapper approach for attribute-dependent
+/// style. So we do it the same both ways for now to reduce complexity, but it's
+/// worth measuring the performance impact (if any) of the mStateMask approach.
+pub trait ElementSnapshot : Sized + MatchAttr {
+    /// The state of the snapshot, if any.
+    fn state(&self) -> Option<ElementState>;
 
-#[derive(Clone)]
-#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub struct ElementSnapshot {
-    pub state: Option<ElementState>,
-    pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>,
+    /// If this snapshot contains attribute information.
+    fn has_attrs(&self) -> bool;
+
+    /// The ID attribute per this snapshot. Should only be called if
+    /// `has_attrs()` returns true.
+    fn id_attr(&self) -> Option<Atom>;
+
+    /// Whether this snapshot contains the class `name`. Should only be called
+    /// if `has_attrs()` returns true.
+    fn has_class(&self, name: &Atom) -> bool;
+
+    /// A callback that should be called for each class of the snapshot. Should
+    /// only be called if `has_attrs()` returns true.
+    fn each_class<F>(&self, F)
+        where F: FnMut(&Atom);
 }
 
-impl ElementSnapshot {
-    pub fn new() -> ElementSnapshot {
-        EMPTY_SNAPSHOT.clone()
+struct ElementWrapper<'a, E>
+    where E: ElementExt
+{
+    element: E,
+    snapshot: Option<&'a E::Snapshot>,
+}
+
+impl<'a, E> ElementWrapper<'a, E>
+    where E: ElementExt
+{
+    pub fn new(el: E) -> ElementWrapper<'a, E> {
+        ElementWrapper { element: el, snapshot: None }
     }
 
-    // Gets an attribute matching |namespace| and |name|, if any. Panics if |attrs| is None.
-    pub fn get_attr(&self, namespace: &Namespace, name: &Atom) -> Option<&AttrValue> {
-        self.attrs.as_ref().unwrap().iter()
-                  .find(|&&(ref ident, _)| ident.local_name == *name && ident.namespace == *namespace)
-                  .map(|&(_, ref v)| v)
-    }
-
-    // Gets an attribute matching |name| if any, ignoring namespace. Panics if |attrs| is None.
-    pub fn get_attr_ignore_ns(&self, name: &Atom) -> Option<&AttrValue> {
-        self.attrs.as_ref().unwrap().iter()
-                  .find(|&&(ref ident, _)| ident.local_name == *name)
-                  .map(|&(_, ref v)| v)
+    pub fn new_with_snapshot(el: E, snapshot: &'a E::Snapshot) -> ElementWrapper<'a, E> {
+        ElementWrapper { element: el, snapshot: Some(snapshot) }
     }
 }
 
-static EMPTY_SNAPSHOT: ElementSnapshot = ElementSnapshot { state: None, attrs: None };
+impl<'a, E> MatchAttr for ElementWrapper<'a, E>
+    where E: ElementExt<AttrString=AttrString>,
+{
+    type AttrString = E::AttrString;
+
+    fn match_attr_has(&self, attr: &AttrSelector) -> bool {
+        match self.snapshot {
+            Some(snapshot) if snapshot.has_attrs()
+                => snapshot.match_attr_has(attr),
+            _   => self.element.match_attr_has(attr)
+        }
+    }
 
-// FIXME(bholley): This implementation isn't going to work for geckolib, because
-// it's fundamentally based on get_attr/match_attr, which we don't want to support
-// that configuration due to the overhead of converting between UTF-16 and UTF-8.
-// We'll need to figure something out when we start using restyle hints with
-// geckolib, but in the mean time we can just use the trait parameters to
-// specialize it to the Servo configuration.
-struct ElementWrapper<'a, E>
-    where E: Element<AttrString=AttrString>,
-          E::Impl: SelectorImplExt {
-    element: E,
-    snapshot: &'a ElementSnapshot,
-}
+    fn match_attr_equals(&self,
+                         attr: &AttrSelector,
+                         value: &Self::AttrString) -> bool {
+        match self.snapshot {
+            Some(snapshot) if snapshot.has_attrs()
+                => snapshot.match_attr_equals(attr, value),
+            _   => self.element.match_attr_equals(attr, value)
+        }
+    }
 
-impl<'a, E> ElementWrapper<'a, E>
-    where E: Element<AttrString=AttrString>,
-          E::Impl: SelectorImplExt {
-    pub fn new(el: E) -> ElementWrapper<'a, E> {
-        ElementWrapper { element: el, snapshot: &EMPTY_SNAPSHOT }
+    fn match_attr_equals_ignore_ascii_case(&self,
+                                           attr: &AttrSelector,
+                                           value: &Self::AttrString) -> bool {
+        match self.snapshot {
+            Some(snapshot) if snapshot.has_attrs()
+                => snapshot.match_attr_equals_ignore_ascii_case(attr, value),
+            _   => self.element.match_attr_equals_ignore_ascii_case(attr, value)
+        }
     }
 
-    pub fn new_with_snapshot(el: E, snapshot: &'a ElementSnapshot) -> ElementWrapper<'a, E> {
-        ElementWrapper { element: el, snapshot: snapshot }
+    fn match_attr_includes(&self,
+                           attr: &AttrSelector,
+                           value: &Self::AttrString) -> bool {
+        match self.snapshot {
+            Some(snapshot) if snapshot.has_attrs()
+                => snapshot.match_attr_includes(attr, value),
+            _   => self.element.match_attr_includes(attr, value)
+        }
     }
-}
+
+    fn match_attr_dash(&self,
+                       attr: &AttrSelector,
+                       value: &Self::AttrString) -> bool {
+        match self.snapshot {
+            Some(snapshot) if snapshot.has_attrs()
+                => snapshot.match_attr_dash(attr, value),
+            _   => self.element.match_attr_dash(attr, value)
+        }
+    }
 
-#[cfg(not(feature = "gecko"))]
-impl<'a, E> MatchAttrGeneric for ElementWrapper<'a, E>
-    where E: Element<AttrString=AttrString>,
-          E: MatchAttrGeneric,
-          E::Impl: SelectorImplExt {
-    fn match_attr<F>(&self, attr: &AttrSelector, test: F) -> bool
-                    where F: Fn(&str) -> bool {
-        use selectors::parser::NamespaceConstraint;
-        match self.snapshot.attrs {
-            Some(_) => {
-                let html = self.is_html_element_in_html_document();
-                let local_name = if html { &attr.lower_name } else { &attr.name };
-                match attr.namespace {
-                    NamespaceConstraint::Specific(ref ns) => self.snapshot.get_attr(ns, local_name),
-                    NamespaceConstraint::Any => self.snapshot.get_attr_ignore_ns(local_name),
-                }.map_or(false, |v| test(v))
-            },
-            None => self.element.match_attr(attr, test)
+    fn match_attr_prefix(&self,
+                         attr: &AttrSelector,
+                         value: &Self::AttrString) -> bool {
+        match self.snapshot {
+            Some(snapshot) if snapshot.has_attrs()
+                => snapshot.match_attr_prefix(attr, value),
+            _   => self.element.match_attr_prefix(attr, value)
+        }
+    }
+
+    fn match_attr_substring(&self,
+                            attr: &AttrSelector,
+                            value: &Self::AttrString) -> bool {
+        match self.snapshot {
+            Some(snapshot) if snapshot.has_attrs()
+                => snapshot.match_attr_substring(attr, value),
+            _   => self.element.match_attr_substring(attr, value)
+        }
+    }
+
+    fn match_attr_suffix(&self,
+                         attr: &AttrSelector,
+                         value: &Self::AttrString) -> bool {
+        match self.snapshot {
+            Some(snapshot) if snapshot.has_attrs()
+                => snapshot.match_attr_suffix(attr, value),
+            _   => self.element.match_attr_suffix(attr, value)
         }
     }
 }
 
-#[cfg(feature = "gecko")]
-impl<'a, E> MatchAttr for ElementWrapper<'a, E>
-    where E: Element<AttrString=AttrString>,
-          E::Impl: SelectorImplExt {
-    type AttrString = AttrString;
-
-    fn match_attr_has(&self, _attr: &AttrSelector) -> bool {
-        panic!("Not implemented for Gecko - this system will need to be redesigned");
-    }
-
-    fn match_attr_equals(&self, _attr: &AttrSelector, _value: &Self::AttrString) -> bool {
-        panic!("Not implemented for Gecko - this system will need to be redesigned");
-    }
-
-    fn match_attr_equals_ignore_ascii_case(&self, _attr: &AttrSelector, _value: &Self::AttrString) -> bool {
-        panic!("Not implemented for Gecko - this system will need to be redesigned");
-    }
-
-    fn match_attr_includes(&self, _attr: &AttrSelector, _value: &Self::AttrString) -> bool {
-        panic!("Not implemented for Gecko - this system will need to be redesigned");
-    }
-
-    fn match_attr_dash(&self, _attr: &AttrSelector, _value: &Self::AttrString) -> bool {
-        panic!("Not implemented for Gecko - this system will need to be redesigned");
-    }
-
-    fn match_attr_prefix(&self, _attr: &AttrSelector, _value: &Self::AttrString) -> bool {
-        panic!("Not implemented for Gecko - this system will need to be redesigned");
-    }
-
-    fn match_attr_substring(&self, _attr: &AttrSelector, _value: &Self::AttrString) -> bool {
-        panic!("Not implemented for Gecko - this system will need to be redesigned");
-    }
-
-    fn match_attr_suffix(&self, _attr: &AttrSelector, _value: &Self::AttrString) -> bool {
-        panic!("Not implemented for Gecko - this system will need to be redesigned");
-    }
-}
-
 impl<'a, E> Element for ElementWrapper<'a, E>
-    where E: Element<AttrString=AttrString>,
-          E: MatchAttrGeneric,
-          E::Impl: SelectorImplExt {
+    where E: ElementExt<AttrString=AttrString>,
+          E::Impl: SelectorImplExt<AttrString=AttrString> {
     type Impl = E::Impl;
 
     fn match_non_ts_pseudo_class(&self,
                                  pseudo_class: <Self::Impl as SelectorImpl>::NonTSPseudoClass) -> bool {
         let flag = Self::Impl::pseudo_class_state_flag(&pseudo_class);
         if flag == ElementState::empty() {
             self.element.match_non_ts_pseudo_class(pseudo_class)
         } else {
-            match self.snapshot.state {
-                Some(s) => s.contains(flag),
-                None => self.element.match_non_ts_pseudo_class(pseudo_class)
+            match self.snapshot.and_then(|s| s.state()) {
+                Some(snapshot_state) => snapshot_state.contains(flag),
+                _   => self.element.match_non_ts_pseudo_class(pseudo_class)
             }
         }
     }
 
     fn parent_element(&self) -> Option<Self> {
         self.element.parent_element().map(ElementWrapper::new)
     }
+
     fn first_child_element(&self) -> Option<Self> {
         self.element.first_child_element().map(ElementWrapper::new)
     }
+
     fn last_child_element(&self) -> Option<Self> {
         self.element.last_child_element().map(ElementWrapper::new)
     }
+
     fn prev_sibling_element(&self) -> Option<Self> {
         self.element.prev_sibling_element().map(ElementWrapper::new)
     }
+
     fn next_sibling_element(&self) -> Option<Self> {
         self.element.next_sibling_element().map(ElementWrapper::new)
     }
+
     fn is_html_element_in_html_document(&self) -> bool {
         self.element.is_html_element_in_html_document()
     }
+
     fn get_local_name(&self) -> BorrowedAtom {
         self.element.get_local_name()
     }
+
     fn get_namespace(&self) -> BorrowedNamespace {
         self.element.get_namespace()
     }
+
     fn get_id(&self) -> Option<Atom> {
-        match self.snapshot.attrs {
-            Some(_) => self.snapshot.get_attr(&ns!(), &atom!("id")).map(|value| value.as_atom().clone()),
-            None => self.element.get_id(),
+        match self.snapshot {
+            Some(snapshot) if snapshot.has_attrs()
+                => snapshot.id_attr(),
+            _   => self.element.get_id()
         }
     }
+
     fn has_class(&self, name: &Atom) -> bool {
-        match self.snapshot.attrs {
-            Some(_) => self.snapshot.get_attr(&ns!(), &atom!("class"))
-                                    .map_or(false, |v| { v.as_tokens().iter().any(|atom| atom == name) }),
-            None => self.element.has_class(name),
+        match self.snapshot {
+            Some(snapshot) if snapshot.has_attrs()
+                => snapshot.has_class(name),
+            _   => self.element.has_class(name)
         }
     }
+
     fn is_empty(&self) -> bool {
         self.element.is_empty()
     }
+
     fn is_root(&self) -> bool {
         self.element.is_root()
     }
-    fn each_class<F>(&self, mut callback: F) where F: FnMut(&Atom) {
-        match self.snapshot.attrs {
-            Some(_) => {
-                if let Some(v) = self.snapshot.get_attr(&ns!(), &atom!("class")) {
-                    for c in v.as_tokens() { callback(c) }
-                }
-            }
-            None => self.element.each_class(callback),
+
+    fn each_class<F>(&self, callback: F)
+        where F: FnMut(&Atom) {
+        match self.snapshot {
+            Some(snapshot) if snapshot.has_attrs()
+                => snapshot.each_class(callback),
+            _   => self.element.each_class(callback)
         }
     }
 }
 
 fn selector_to_state(sel: &SimpleSelector<TheSelectorImpl>) -> ElementState {
     match *sel {
         SimpleSelector::NonTSPseudoClass(ref pc) => TheSelectorImpl::pseudo_class_state_flag(pc),
         _ => ElementState::empty(),
@@ -291,34 +309,34 @@ impl Sensitivities {
     fn new() -> Sensitivities {
         Sensitivities {
             states: ElementState::empty(),
             attrs: false,
         }
     }
 }
 
-// Mapping between (partial) CompoundSelectors (and the combinator to their right)
-// and the states and attributes they depend on.
-//
-// In general, for all selectors in all applicable stylesheets of the form:
-//
-// |a _ b _ c _ d _ e|
-//
-// Where:
-//   * |b| and |d| are simple selectors that depend on state (like :hover) or
-//     attributes (like [attr...], .foo, or #foo).
-//   * |a|, |c|, and |e| are arbitrary simple selectors that do not depend on
-//     state or attributes.
-//
-// We generate a Dependency for both |a _ b:X _| and |a _ b:X _ c _ d:Y _|, even
-// though those selectors may not appear on their own in any stylesheet. This allows
-// us to quickly scan through the dependency sites of all style rules and determine the
-// maximum effect that a given state or attribute change may have on the style of
-// elements in the document.
+/// Mapping between (partial) CompoundSelectors (and the combinator to their
+/// right) and the states and attributes they depend on.
+///
+/// In general, for all selectors in all applicable stylesheets of the form:
+///
+/// |a _ b _ c _ d _ e|
+///
+/// Where:
+///   * |b| and |d| are simple selectors that depend on state (like :hover) or
+///     attributes (like [attr...], .foo, or #foo).
+///   * |a|, |c|, and |e| are arbitrary simple selectors that do not depend on
+///     state or attributes.
+///
+/// We generate a Dependency for both |a _ b:X _| and |a _ b:X _ c _ d:Y _|,
+/// even though those selectors may not appear on their own in any stylesheet.
+/// This allows us to quickly scan through the dependency sites of all style
+/// rules and determine the maximum effect that a given state or attribute
+/// change may have on the style of elements in the document.
 #[derive(Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 struct Dependency {
     selector: Arc<CompoundSelector<TheSelectorImpl>>,
     combinator: Option<Combinator>,
     sensitivities: Sensitivities,
 }
 
@@ -363,21 +381,24 @@ impl DependencySet {
     }
 
     pub fn clear(&mut self) {
         self.deps.clear();
     }
 }
 
 impl DependencySet {
-    pub fn compute_hint<E>(&self, el: &E, snapshot: &ElementSnapshot, current_state: ElementState)
-                          -> RestyleHint
-                          where E: Element<Impl=TheSelectorImpl, AttrString=AttrString> + Clone + MatchAttrGeneric {
-        let state_changes = snapshot.state.map_or(ElementState::empty(), |old_state| current_state ^ old_state);
-        let attrs_changed = snapshot.attrs.is_some();
+    pub fn compute_hint<E>(&self, el: &E,
+                           snapshot: &E::Snapshot,
+                           current_state: ElementState)
+                           -> RestyleHint
+    where E: ElementExt + Clone
+    {
+        let state_changes = snapshot.state().map_or_else(ElementState::empty, |old_state| current_state ^ old_state);
+        let attrs_changed = snapshot.has_attrs();
         let mut hint = RestyleHint::empty();
         for dep in &self.deps {
             if state_changes.intersects(dep.sensitivities.states) || (attrs_changed && dep.sensitivities.attrs) {
                 let old_el: ElementWrapper<E> = ElementWrapper::new_with_snapshot(el.clone(), snapshot);
                 let matched_then = matches_compound_selector(&*dep.selector, &old_el, None, &mut false);
                 let matches_now = matches_compound_selector(&*dep.selector, el, None, &mut false);
                 if matched_then != matches_now {
                     hint.insert(combinator_to_restyle_hint(dep.combinator));
--- a/servo/components/style/selector_impl.rs
+++ b/servo/components/style/selector_impl.rs
@@ -1,30 +1,34 @@
 /* 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/. */
 
 //! The pseudo-classes and pseudo-elements supported by the style system.
 
 use element_state::ElementState;
+use restyle_hints;
 use selectors::Element;
 use selectors::parser::SelectorImpl;
 use std::fmt::Debug;
 use stylesheets::Stylesheet;
 
 pub type AttrString = <TheSelectorImpl as SelectorImpl>::AttrString;
 
 #[cfg(feature = "servo")]
-pub use servo_selector_impl::ServoSelectorImpl;
+pub use servo_selector_impl::*;
 
 #[cfg(feature = "servo")]
-pub use servo_selector_impl::{ServoSelectorImpl as TheSelectorImpl, PseudoElement, NonTSPseudoClass};
+pub use servo_selector_impl::{ServoSelectorImpl as TheSelectorImpl, ServoElementSnapshot as ElementSnapshot};
 
 #[cfg(feature = "gecko")]
-pub use gecko_selector_impl::{GeckoSelectorImpl as TheSelectorImpl, PseudoElement, NonTSPseudoClass};
+pub use gecko_selector_impl::*;
+
+#[cfg(feature = "gecko")]
+pub use gecko_selector_impl::{GeckoSelectorImpl as TheSelectorImpl};
 
 /// This function determines if a pseudo-element is eagerly cascaded or not.
 ///
 /// Eagerly cascaded pseudo-elements are "normal" pseudo-elements (i.e.
 /// `::before` and `::after`). They inherit styles normally as another
 /// selector would do, and they're part of the cascade.
 ///
 /// Lazy pseudo-elements are affected by selector matching, but they're only
@@ -64,16 +68,18 @@ impl PseudoElementCascadeType {
 
     #[inline]
     pub fn is_precomputed(&self) -> bool {
         *self == PseudoElementCascadeType::Precomputed
     }
 }
 
 pub trait ElementExt: Element<Impl=TheSelectorImpl, AttrString=<TheSelectorImpl as SelectorImpl>::AttrString> {
+    type Snapshot: restyle_hints::ElementSnapshot<AttrString = Self::AttrString> + 'static;
+
     fn is_link(&self) -> bool;
 }
 
 // NB: The `Clone` trait is here for convenience due to:
 // https://github.com/rust-lang/rust/issues/26925
 pub trait SelectorImplExt : SelectorImpl + Clone + Debug + Sized + 'static {
     fn pseudo_element_cascade_type(pseudo: &Self::PseudoElement) -> PseudoElementCascadeType;
 
--- a/servo/components/style/selector_matching.rs
+++ b/servo/components/style/selector_matching.rs
@@ -5,22 +5,22 @@
 //! Selector matching.
 
 use dom::PresentationalHintsSynthetizer;
 use element_state::*;
 use error_reporting::StdoutErrorReporter;
 use keyframes::KeyframesAnimation;
 use media_queries::{Device, MediaType};
 use properties::{self, PropertyDeclaration, PropertyDeclarationBlock, ComputedValues};
-use restyle_hints::{ElementSnapshot, RestyleHint, DependencySet};
-use selector_impl::{SelectorImplExt, TheSelectorImpl, PseudoElement, AttrString};
+use restyle_hints::{RestyleHint, DependencySet};
+use selector_impl::{ElementExt, SelectorImplExt, TheSelectorImpl, PseudoElement, AttrString};
+use selectors::Element;
 use selectors::bloom::BloomFilter;
 use selectors::matching::DeclarationBlock as GenericDeclarationBlock;
 use selectors::matching::{Rule, SelectorMap};
-use selectors::{Element, MatchAttrGeneric};
 use sink::Push;
 use smallvec::VecLike;
 use std::collections::HashMap;
 use std::hash::BuildHasherDefault;
 use std::sync::Arc;
 use string_cache::Atom;
 use style_traits::viewport::ViewportConstraints;
 use stylesheets::{CSSRule, CSSRuleIteratorExt, Origin, Stylesheet};
@@ -397,25 +397,25 @@ impl Stylist {
     }
 
     #[inline]
     pub fn animations(&self) -> &HashMap<Atom, KeyframesAnimation> {
         &self.animations
     }
 
     pub fn compute_restyle_hint<E>(&self, element: &E,
-                                   snapshot: &ElementSnapshot,
+                                   snapshot: &E::Snapshot,
                                    // NB: We need to pass current_state as an argument because
                                    // selectors::Element doesn't provide access to ElementState
                                    // directly, and computing it from the ElementState would be
                                    // more expensive than getting it directly from the caller.
                                    current_state: ElementState)
                                    -> RestyleHint
-                                   where E: Element<Impl=TheSelectorImpl, AttrString=AttrString>
-                                        + Clone + MatchAttrGeneric {
+        where E: ElementExt + Clone
+    {
         self.state_deps.compute_hint(element, snapshot, current_state)
     }
 }
 
 
 /// Map that contains the CSS rules for a given origin.
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 struct PerOriginSelectorMap {
--- a/servo/components/style/servo_selector_impl.rs
+++ b/servo/components/style/servo_selector_impl.rs
@@ -1,19 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+use attr::{AttrIdentifier, AttrValue};
 use element_state::ElementState;
 use error_reporting::StdoutErrorReporter;
 use parser::ParserContextExtraData;
+use restyle_hints::ElementSnapshot;
 use selector_impl::{SelectorImplExt, ElementExt, PseudoElementCascadeType, TheSelectorImpl};
-use selectors::Element;
-use selectors::parser::{ParserContext, SelectorImpl};
+use selectors::parser::{AttrSelector, ParserContext, SelectorImpl};
+use selectors::{Element, MatchAttrGeneric};
 use std::process;
+use string_cache::{Atom, Namespace};
 use stylesheets::{Stylesheet, Origin};
 use url::Url;
 use util::opts;
 use util::resource_files::read_resource_file;
 
 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum PseudoElement {
@@ -184,17 +187,94 @@ impl SelectorImplExt for ServoSelectorIm
     }
 
     #[inline]
     fn get_quirks_mode_stylesheet() -> Option<&'static Stylesheet> {
         Some(&*QUIRKS_MODE_STYLESHEET)
     }
 }
 
+/// Servo's version of an element snapshot.
+#[derive(Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub struct ServoElementSnapshot {
+    pub state: Option<ElementState>,
+    pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>,
+    pub is_html_element_in_html_document: bool,
+}
+
+impl ServoElementSnapshot {
+    pub fn new(is_html_element_in_html_document: bool) -> Self {
+        ServoElementSnapshot {
+            state: None,
+            attrs: None,
+            is_html_element_in_html_document: is_html_element_in_html_document,
+        }
+    }
+
+    fn get_attr(&self, namespace: &Namespace, name: &Atom) -> Option<&AttrValue> {
+        self.attrs.as_ref().unwrap().iter()
+            .find(|&&(ref ident, _)| ident.local_name == *name &&
+                                     ident.namespace == *namespace)
+            .map(|&(_, ref v)| v)
+    }
+
+    fn get_attr_ignore_ns(&self, name: &Atom) -> Option<&AttrValue> {
+        self.attrs.as_ref().unwrap().iter()
+                  .find(|&&(ref ident, _)| ident.local_name == *name)
+                  .map(|&(_, ref v)| v)
+    }
+}
+
+impl ElementSnapshot for ServoElementSnapshot {
+    fn state(&self) -> Option<ElementState> {
+        self.state.clone()
+    }
+
+    fn has_attrs(&self) -> bool {
+        self.attrs.is_some()
+    }
+
+    fn id_attr(&self) -> Option<Atom> {
+        self.get_attr(&ns!(), &atom!("id")).map(|v| v.as_atom().clone())
+    }
+
+    fn has_class(&self, name: &Atom) -> bool {
+        self.get_attr(&ns!(), &atom!("class"))
+            .map_or(false, |v| v.as_tokens().iter().any(|atom| atom == name))
+    }
+
+    fn each_class<F>(&self, mut callback: F)
+        where F: FnMut(&Atom)
+    {
+        if let Some(v) = self.get_attr(&ns!(), &atom!("class")) {
+            for class in v.as_tokens() {
+                callback(class);
+            }
+        }
+    }
+}
+
+impl MatchAttrGeneric for ServoElementSnapshot {
+    fn match_attr<F>(&self, attr: &AttrSelector, test: F) -> bool
+        where F: Fn(&str) -> bool
+    {
+        use selectors::parser::NamespaceConstraint;
+        let html = self.is_html_element_in_html_document;
+        let local_name = if html { &attr.lower_name } else { &attr.name };
+        match attr.namespace {
+            NamespaceConstraint::Specific(ref ns) => self.get_attr(ns, local_name),
+            NamespaceConstraint::Any => self.get_attr_ignore_ns(local_name),
+        }.map_or(false, |v| test(v))
+    }
+}
+
 impl<E: Element<Impl=TheSelectorImpl, AttrString=String>> ElementExt for E {
+    type Snapshot = ServoElementSnapshot;
+
     fn is_link(&self) -> bool {
         self.match_non_ts_pseudo_class(NonTSPseudoClass::AnyLink)
     }
 }
 
 lazy_static! {
     pub static ref USER_OR_USER_AGENT_STYLESHEETS: Vec<Stylesheet> = {
         let mut stylesheets = vec!();
--- a/servo/ports/geckolib/gecko_bindings/bindings.rs
+++ b/servo/ports/geckolib/gecko_bindings/bindings.rs
@@ -128,16 +128,18 @@ unsafe impl Send for nsStyleUnit {}
 unsafe impl Sync for nsStyleUnit {}
 impl HeapSizeOf for nsStyleUnit { fn heap_size_of_children(&self) -> usize { 0 } }
 use structs::nsStyleUnion;
 unsafe impl Send for nsStyleUnion {}
 unsafe impl Sync for nsStyleUnion {}
 impl HeapSizeOf for nsStyleUnion { fn heap_size_of_children(&self) -> usize { 0 } }
 use structs::nsStyleCoord_CalcValue as CalcValue;
 use structs::nsStyleCoord_Calc as Calc;
+use structs::nsRestyleHint;
+use structs::ServoElementSnapshot;
 use structs::SheetParsingMode;
 use structs::nsMainThreadPtrHandle;
 use structs::nsMainThreadPtrHolder;
 use structs::nscolor;
 use structs::nsFont;
 use structs::FontFamilyList;
 use structs::FontFamilyType;
 use structs::nsIAtom;
@@ -180,16 +182,18 @@ extern "C" {
     pub fn Gecko_IsLink(element: *mut RawGeckoElement) -> bool;
     pub fn Gecko_IsTextNode(node: *mut RawGeckoNode) -> bool;
     pub fn Gecko_IsVisitedLink(element: *mut RawGeckoElement) -> bool;
     pub fn Gecko_IsUnvisitedLink(element: *mut RawGeckoElement) -> bool;
     pub fn Gecko_IsRootElement(element: *mut RawGeckoElement) -> bool;
     pub fn Gecko_LocalName(element: *mut RawGeckoElement) -> *mut nsIAtom;
     pub fn Gecko_Namespace(element: *mut RawGeckoElement) -> *mut nsIAtom;
     pub fn Gecko_GetElementId(element: *mut RawGeckoElement) -> *mut nsIAtom;
+    pub fn Gecko_AtomAttrValue(element: *mut RawGeckoElement,
+                               attribute: *mut nsIAtom) -> *mut nsIAtom;
     pub fn Gecko_HasAttr(element: *mut RawGeckoElement, ns: *mut nsIAtom,
                          name: *mut nsIAtom) -> bool;
     pub fn Gecko_AttrEquals(element: *mut RawGeckoElement, ns: *mut nsIAtom,
                             name: *mut nsIAtom, str: *mut nsIAtom,
                             ignoreCase: bool) -> bool;
     pub fn Gecko_AttrDashEquals(element: *mut RawGeckoElement,
                                 ns: *mut nsIAtom, name: *mut nsIAtom,
                                 str: *mut nsIAtom) -> bool;
@@ -202,16 +206,46 @@ extern "C" {
                                ns: *mut nsIAtom, name: *mut nsIAtom,
                                str: *mut nsIAtom) -> bool;
     pub fn Gecko_AttrHasSuffix(element: *mut RawGeckoElement,
                                ns: *mut nsIAtom, name: *mut nsIAtom,
                                str: *mut nsIAtom) -> bool;
     pub fn Gecko_ClassOrClassList(element: *mut RawGeckoElement,
                                   class_: *mut *mut nsIAtom,
                                   classList: *mut *mut *mut nsIAtom) -> u32;
+    pub fn Gecko_SnapshotAtomAttrValue(element: *mut ServoElementSnapshot,
+                                       attribute: *mut nsIAtom)
+     -> *mut nsIAtom;
+    pub fn Gecko_SnapshotHasAttr(element: *mut ServoElementSnapshot,
+                                 ns: *mut nsIAtom, name: *mut nsIAtom)
+     -> bool;
+    pub fn Gecko_SnapshotAttrEquals(element: *mut ServoElementSnapshot,
+                                    ns: *mut nsIAtom, name: *mut nsIAtom,
+                                    str: *mut nsIAtom, ignoreCase: bool)
+     -> bool;
+    pub fn Gecko_SnapshotAttrDashEquals(element: *mut ServoElementSnapshot,
+                                        ns: *mut nsIAtom, name: *mut nsIAtom,
+                                        str: *mut nsIAtom) -> bool;
+    pub fn Gecko_SnapshotAttrIncludes(element: *mut ServoElementSnapshot,
+                                      ns: *mut nsIAtom, name: *mut nsIAtom,
+                                      str: *mut nsIAtom) -> bool;
+    pub fn Gecko_SnapshotAttrHasSubstring(element: *mut ServoElementSnapshot,
+                                          ns: *mut nsIAtom,
+                                          name: *mut nsIAtom,
+                                          str: *mut nsIAtom) -> bool;
+    pub fn Gecko_SnapshotAttrHasPrefix(element: *mut ServoElementSnapshot,
+                                       ns: *mut nsIAtom, name: *mut nsIAtom,
+                                       str: *mut nsIAtom) -> bool;
+    pub fn Gecko_SnapshotAttrHasSuffix(element: *mut ServoElementSnapshot,
+                                       ns: *mut nsIAtom, name: *mut nsIAtom,
+                                       str: *mut nsIAtom) -> bool;
+    pub fn Gecko_SnapshotClassOrClassList(element: *mut ServoElementSnapshot,
+                                          class_: *mut *mut nsIAtom,
+                                          classList: *mut *mut *mut nsIAtom)
+     -> u32;
     pub fn Gecko_GetServoDeclarationBlock(element: *mut RawGeckoElement)
      -> *mut ServoDeclarationBlock;
     pub fn Gecko_GetNodeData(node: *mut RawGeckoNode) -> *mut ServoNodeData;
     pub fn Gecko_SetNodeData(node: *mut RawGeckoNode,
                              data: *mut ServoNodeData);
     pub fn Servo_DropNodeData(data: *mut ServoNodeData);
     pub fn Gecko_Atomize(aString: *const ::std::os::raw::c_char, aLength: u32)
      -> *mut nsIAtom;
@@ -326,16 +360,20 @@ extern "C" {
      -> *mut ServoComputedValues;
     pub fn Servo_AddRefComputedValues(arg1: *mut ServoComputedValues);
     pub fn Servo_ReleaseComputedValues(arg1: *mut ServoComputedValues);
     pub fn Servo_Initialize();
     pub fn Servo_RestyleDocument(doc: *mut RawGeckoDocument,
                                  set: *mut RawServoStyleSet);
     pub fn Servo_RestyleSubtree(node: *mut RawGeckoNode,
                                 set: *mut RawServoStyleSet);
+    pub fn Servo_ComputeRestyleHint(element: *mut RawGeckoElement,
+                                    snapshot: *mut ServoElementSnapshot,
+                                    set: *mut RawServoStyleSet)
+     -> nsRestyleHint;
     pub fn Servo_StyleWorkerThreadCount() -> u32;
     pub fn Gecko_Construct_nsStyleFont(ptr: *mut nsStyleFont);
     pub fn Gecko_CopyConstruct_nsStyleFont(ptr: *mut nsStyleFont,
                                            other: *const nsStyleFont);
     pub fn Gecko_Destroy_nsStyleFont(ptr: *mut nsStyleFont);
     pub fn Servo_GetStyleFont(computedValues: *mut ServoComputedValues)
      -> *const nsStyleFont;
     pub fn Gecko_Construct_nsStyleColor(ptr: *mut nsStyleColor);
--- a/servo/ports/geckolib/gecko_bindings/structs_debug.rs
+++ b/servo/ports/geckolib/gecko_bindings/structs_debug.rs
@@ -183,20 +183,30 @@ pub const NS_ERROR_MODULE_DOM_ANIM: ::st
 pub const NS_ERROR_MODULE_DOM_PUSH: ::std::os::raw::c_uint = 40;
 pub const NS_ERROR_MODULE_GENERAL: ::std::os::raw::c_uint = 51;
 pub const NS_ERROR_SEVERITY_SUCCESS: ::std::os::raw::c_uint = 0;
 pub const NS_ERROR_SEVERITY_ERROR: ::std::os::raw::c_uint = 1;
 pub const NS_ERROR_MODULE_BASE_OFFSET: ::std::os::raw::c_uint = 69;
 pub const MOZ_STRING_WITH_OBSOLETE_API: ::std::os::raw::c_uint = 1;
 pub const NSID_LENGTH: ::std::os::raw::c_uint = 39;
 pub const NS_NUMBER_OF_FLAGS_IN_REFCNT: ::std::os::raw::c_uint = 2;
+pub const _STL_PAIR_H: ::std::os::raw::c_uint = 1;
+pub const _GLIBCXX_UTILITY: ::std::os::raw::c_uint = 1;
+pub const __cpp_lib_tuple_element_t: ::std::os::raw::c_uint = 201402;
+pub const __cpp_lib_tuples_by_type: ::std::os::raw::c_uint = 201304;
+pub const __cpp_lib_exchange_function: ::std::os::raw::c_uint = 201304;
+pub const __cpp_lib_integer_sequence: ::std::os::raw::c_uint = 201304;
+pub const NS_EVENT_STATE_HIGHEST_SERVO_BIT: ::std::os::raw::c_uint = 6;
 pub const DOM_USER_DATA: ::std::os::raw::c_uint = 1;
 pub const SMIL_MAPPED_ATTR_ANIMVAL: ::std::os::raw::c_uint = 2;
 pub const TWIPS_PER_POINT_INT: ::std::os::raw::c_uint = 20;
 pub const POINTS_PER_INCH_INT: ::std::os::raw::c_uint = 72;
+pub const NS_ATTRNAME_NODEINFO_BIT: ::std::os::raw::c_uint = 1;
+pub const NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM: ::std::os::raw::c_uint = 12;
+pub const NS_ATTRVALUE_INTEGERTYPE_BITS: ::std::os::raw::c_uint = 4;
 pub const NS_FONT_VARIANT_NORMAL: ::std::os::raw::c_uint = 0;
 pub const NS_FONT_VARIANT_SMALL_CAPS: ::std::os::raw::c_uint = 1;
 pub const NS_CORNER_TOP_LEFT_X: ::std::os::raw::c_uint = 0;
 pub const NS_CORNER_TOP_LEFT_Y: ::std::os::raw::c_uint = 1;
 pub const NS_CORNER_TOP_RIGHT_X: ::std::os::raw::c_uint = 2;
 pub const NS_CORNER_TOP_RIGHT_Y: ::std::os::raw::c_uint = 3;
 pub const NS_CORNER_BOTTOM_RIGHT_X: ::std::os::raw::c_uint = 4;
 pub const NS_CORNER_BOTTOM_RIGHT_Y: ::std::os::raw::c_uint = 5;
@@ -2720,16 +2730,22 @@ pub struct nsIExpandedPrincipal {
 #[repr(C)]
 pub struct _vftable_nsIExpandedPrincipal {
     pub _base: _vftable_nsISupports,
 }
 impl ::std::clone::Clone for nsIExpandedPrincipal {
     fn clone(&self) -> Self { *self }
 }
 #[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct _Make_integer_sequence<_Tp, _ISeq> {
+    pub _phantom0: ::std::marker::PhantomData<_Tp>,
+    pub _phantom1: ::std::marker::PhantomData<_ISeq>,
+}
+#[repr(C)]
 #[derive(Debug, Copy)]
 pub struct nsIURI {
     pub _base: nsISupports,
 }
 #[repr(C)]
 pub struct _vftable_nsIURI {
     pub _base: _vftable_nsISupports,
 }
@@ -2762,16 +2778,36 @@ pub enum nsIRequest_nsIRequest_h_unnamed
     VALIDATE_ONCE_PER_SESSION = 8192,
     LOAD_ANONYMOUS = 16384,
     LOAD_FRESH_CONNECTION = 32768,
 }
 impl ::std::clone::Clone for nsIRequest {
     fn clone(&self) -> Self { *self }
 }
 /**
+ * EventStates is the class used to represent the event states of nsIContent
+ * instances. These states are calculated by IntrinsicState() and
+ * ContentStatesChanged() has to be called when one of them changes thus
+ * informing the layout/style engine of the change.
+ * Event states are associated with pseudo-classes.
+ */
+#[repr(C)]
+#[derive(Debug, Copy)]
+pub struct EventStates {
+    pub mStates: ::std::os::raw::c_ulong,
+}
+impl ::std::clone::Clone for EventStates {
+    fn clone(&self) -> Self { *self }
+}
+#[test]
+fn bindgen_test_layout_EventStates() {
+    assert_eq!(::std::mem::size_of::<EventStates>() , 8usize);
+    assert_eq!(::std::mem::align_of::<EventStates>() , 8usize);
+}
+/**
  * Enum defining the mode in which a sheet is to be parsed.  This is
  * usually, but not always, the same as the cascade level at which the
  * sheet will apply (see nsStyleSet.h).  Most of the Loader APIs only
  * support loading of author sheets.
  *
  * Author sheets are the normal case: styles embedded in or linked
  * from HTML pages.  They are also the most restricted.
  *
@@ -2881,17 +2917,17 @@ impl ::std::clone::Clone for nsMutationG
 #[test]
 fn bindgen_test_layout_nsMutationGuard() {
     assert_eq!(::std::mem::size_of::<nsMutationGuard>() , 8usize);
     assert_eq!(::std::mem::align_of::<nsMutationGuard>() , 8usize);
 }
 extern "C" {
     #[link_name = "_ZN15nsMutationGuard11sGenerationE"]
     pub static mut nsMutationGuard_consts_sGeneration:
-               ::std::os::raw::c_ulonglong;
+               ::std::os::raw::c_ulong;
 }
 pub type Float = f32;
 #[repr(i8)]
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub enum SurfaceType {
     DATA = 0,
     D2D1_BITMAP = 1,
     D2D1_DRAWTARGET = 2,
@@ -3257,16 +3293,269 @@ pub enum FillMode {
 pub enum PlaybackDirection {
     Normal = 0,
     Reverse = 1,
     Alternate = 2,
     Alternate_reverse = 3,
     EndGuard_ = 4,
 }
 pub type NativeType = AnimationEffectReadOnly;
+#[repr(C)]
+#[derive(Debug)]
+pub struct nsAttrName {
+    pub mBits: usize,
+}
+#[test]
+fn bindgen_test_layout_nsAttrName() {
+    assert_eq!(::std::mem::size_of::<nsAttrName>() , 8usize);
+    assert_eq!(::std::mem::align_of::<nsAttrName>() , 8usize);
+}
+pub type nscolor = u32;
+#[repr(i8)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum nsHexColorType { NoAlpha = 0, AllowAlpha = 1, }
+pub enum nsStyledElementNotElementCSSInlineStyle { }
+pub enum MiscContainer { }
+pub enum ServoDeclarationBlock { }
+pub enum Declaration { }
+/**
+ * A class used to construct a nsString from a nsStringBuffer (we might
+ * want to move this to nsString at some point).
+ *
+ * WARNING: Note that nsCheapString doesn't take an explicit length -- it
+ * assumes the string is maximally large, given the nsStringBuffer's storage
+ * size.  This means the given string buffer *must* be sized exactly correctly
+ * for the string it contains (including one byte for a null terminator).  If
+ * it has any unused storage space, then that will result in bogus characters
+ * at the end of our nsCheapString.
+ */
+#[repr(C)]
+#[derive(Debug)]
+pub struct nsCheapString {
+    pub _base: nsString,
+}
+#[test]
+fn bindgen_test_layout_nsCheapString() {
+    assert_eq!(::std::mem::size_of::<nsCheapString>() , 16usize);
+    assert_eq!(::std::mem::align_of::<nsCheapString>() , 8usize);
+}
+#[repr(C)]
+#[derive(Debug)]
+pub struct nsAttrValue {
+    pub mBits: usize,
+}
+pub const eSVGTypesBegin: nsAttrValue_ValueType =
+    nsAttrValue_ValueType::eSVGAngle;
+pub const eSVGTypesEnd: nsAttrValue_ValueType =
+    nsAttrValue_ValueType::eSVGViewBox;
+#[repr(u32)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum nsAttrValue_ValueType {
+    eString = 0,
+    eAtom = 2,
+    eInteger = 3,
+    eColor = 7,
+    eEnum = 11,
+    ePercent = 15,
+    eGeckoCSSDeclaration = 16,
+    eServoCSSDeclaration = 17,
+    eURL = 18,
+    eImage = 19,
+    eAtomArray = 20,
+    eDoubleValue = 21,
+    eIntMarginValue = 22,
+    eSVGAngle = 23,
+    eSVGIntegerPair = 24,
+    eSVGLength = 25,
+    eSVGLengthList = 26,
+    eSVGNumberList = 27,
+    eSVGNumberPair = 28,
+    eSVGPathData = 29,
+    eSVGPointList = 30,
+    eSVGPreserveAspectRatio = 31,
+    eSVGStringList = 32,
+    eSVGTransformList = 33,
+    eSVGViewBox = 34,
+}
+/**
+   * Structure for a mapping from int (enum) values to strings.  When you use
+   * it you generally create an array of them.
+   * Instantiate like this:
+   * EnumTable myTable[] = {
+   *   { "string1", 1 },
+   *   { "string2", 2 },
+   *   { 0 }
+   * }
+   */
+#[repr(C)]
+#[derive(Debug, Copy)]
+pub struct nsAttrValue_EnumTable {
+    /** The string the value maps to */
+    pub tag: *const ::std::os::raw::c_char,
+    /** The enum value that maps to this string */
+    pub value: i16,
+}
+impl ::std::clone::Clone for nsAttrValue_EnumTable {
+    fn clone(&self) -> Self { *self }
+}
+#[test]
+fn bindgen_test_layout_nsAttrValue_EnumTable() {
+    assert_eq!(::std::mem::size_of::<nsAttrValue_EnumTable>() , 16usize);
+    assert_eq!(::std::mem::align_of::<nsAttrValue_EnumTable>() , 8usize);
+}
+#[repr(u32)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum nsAttrValue_ValueBaseType {
+    eStringBase = 0,
+    eOtherBase = 1,
+    eAtomBase = 2,
+    eIntegerBase = 3,
+}
+#[test]
+fn bindgen_test_layout_nsAttrValue() {
+    assert_eq!(::std::mem::size_of::<nsAttrValue>() , 8usize);
+    assert_eq!(::std::mem::align_of::<nsAttrValue>() , 8usize);
+}
+extern "C" {
+    #[link_name = "_ZN11nsAttrValue15sEnumTableArrayE"]
+    pub static mut nsAttrValue_consts_sEnumTableArray:
+               nsTArray<*const nsAttrValue_EnumTable>;
+}
+pub enum nsCSSSelector { }
+#[repr(u32)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum nsChangeHint {
+    nsChangeHint_RepaintFrame = 1,
+    nsChangeHint_NeedReflow = 2,
+    nsChangeHint_ClearAncestorIntrinsics = 4,
+    nsChangeHint_ClearDescendantIntrinsics = 8,
+    nsChangeHint_NeedDirtyReflow = 16,
+    nsChangeHint_SyncFrameView = 32,
+    nsChangeHint_UpdateCursor = 64,
+    nsChangeHint_UpdateEffects = 128,
+    nsChangeHint_UpdateOpacityLayer = 256,
+    nsChangeHint_UpdateTransformLayer = 512,
+    nsChangeHint_ReconstructFrame = 1024,
+    nsChangeHint_UpdateOverflow = 2048,
+    nsChangeHint_UpdateSubtreeOverflow = 4096,
+    nsChangeHint_UpdatePostTransformOverflow = 8192,
+    nsChangeHint_UpdateParentOverflow = 16384,
+    nsChangeHint_ChildrenOnlyTransform = 32768,
+    nsChangeHint_RecomputePosition = 65536,
+    nsChangeHint_UpdateContainingBlock = 131072,
+    nsChangeHint_BorderStyleNoneChange = 262144,
+    nsChangeHint_UpdateTextPath = 524288,
+    nsChangeHint_SchedulePaint = 1048576,
+    nsChangeHint_NeutralChange = 2097152,
+    nsChangeHint_InvalidateRenderingObservers = 4194304,
+    nsChangeHint_ReflowChangesSizeOrPosition = 8388608,
+    nsChangeHint_UpdateComputedBSize = 16777216,
+    nsChangeHint_UpdateUsesOpacity = 33554432,
+    nsChangeHint_UpdateBackgroundPosition = 67108864,
+}
+pub type nsChangeHint_size_t = ::std::os::raw::c_int;
+/**
+ * |nsRestyleHint| is a bitfield for the result of
+ * |HasStateDependentStyle| and |HasAttributeDependentStyle|.  When no
+ * restyling is necessary, use |nsRestyleHint(0)|.
+ *
+ * Without eRestyle_Force or eRestyle_ForceDescendants, the restyling process
+ * can stop processing at a frame when it detects no style changes and it is
+ * known that the styles of the subtree beneath it will not change, leaving
+ * the old style context on the frame.  eRestyle_Force can be used to skip this
+ * optimization on a frame, and to force its new style context to be used.
+ *
+ * Similarly, eRestyle_ForceDescendants will cause the frame and all of its
+ * descendants to be traversed and for the new style contexts that are created
+ * to be set on the frames.
+ *
+ * NOTE: When adding new restyle hints, please also add them to
+ * RestyleManager::RestyleHintToString.
+ */
+#[repr(u32)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum nsRestyleHint {
+    eRestyle_Self = 1,
+    eRestyle_SomeDescendants = 2,
+    eRestyle_Subtree = 4,
+    eRestyle_LaterSiblings = 8,
+    eRestyle_CSSTransitions = 16,
+    eRestyle_CSSAnimations = 32,
+    eRestyle_SVGAttrAnimations = 64,
+    eRestyle_StyleAttribute = 128,
+    eRestyle_StyleAttribute_Animations = 256,
+    eRestyle_Force = 512,
+    eRestyle_ForceDescendants = 1024,
+    eRestyle_AllHintsWithAnimations = 368,
+}
+pub type nsRestyleHint_size_t = ::std::os::raw::c_int;
+/**
+ * Additional data used in conjunction with an nsRestyleHint to control the
+ * restyle process.
+ */
+#[repr(C)]
+#[derive(Debug)]
+pub struct RestyleHintData {
+    pub mSelectorsForDescendants: nsTArray<*mut nsCSSSelector>,
+}
+#[test]
+fn bindgen_test_layout_RestyleHintData() {
+    assert_eq!(::std::mem::size_of::<RestyleHintData>() , 8usize);
+    assert_eq!(::std::mem::align_of::<RestyleHintData>() , 8usize);
+}
+/**
+ * A structure representing a single attribute name and value.
+ *
+ * This is pretty similar to the private nsAttrAndChildArray::InternalAttr.
+ */
+#[repr(C)]
+#[derive(Debug)]
+pub struct ServoAttrSnapshot {
+    pub mName: nsAttrName,
+    pub mValue: nsAttrValue,
+}
+#[test]
+fn bindgen_test_layout_ServoAttrSnapshot() {
+    assert_eq!(::std::mem::size_of::<ServoAttrSnapshot>() , 16usize);
+    assert_eq!(::std::mem::align_of::<ServoAttrSnapshot>() , 8usize);
+}
+/**
+ * A bitflags enum class used to determine what data does a ServoElementSnapshot
+ * contains.
+ */
+#[repr(i8)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum ServoElementSnapshotFlags {
+    State = 1,
+    Attributes = 2,
+    HTMLElementInHTMLDocument = 4,
+    All = 7,
+}
+/**
+ * This class holds all non-tree-structural state of an element that might be
+ * used for selector matching eventually.
+ *
+ * This means the attributes, and the element state, such as :hover, :active,
+ * etc...
+ */
+#[repr(C)]
+#[derive(Debug)]
+pub struct ServoElementSnapshot {
+    pub mContains: ServoElementSnapshotFlags,
+    pub mAttrs: nsTArray<ServoAttrSnapshot>,
+    pub mState: ::std::os::raw::c_uchar,
+    pub mExplicitRestyleHint: nsRestyleHint,
+    pub mExplicitChangeHint: nsChangeHint,
+    pub mIsHTMLElementInHTMLDocument: bool,
+}
+#[test]
+fn bindgen_test_layout_ServoElementSnapshot() {
+    assert_eq!(::std::mem::size_of::<ServoElementSnapshot>() , 32usize);
+    assert_eq!(::std::mem::align_of::<ServoElementSnapshot>() , 8usize);
+}
 pub enum ErrorReporter { }
 #[repr(u32)]
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub enum nsCSSTokenType {
     eCSSToken_Whitespace = 0,
     eCSSToken_Comment = 1,
     eCSSToken_Ident = 2,
     eCSSToken_Function = 3,
@@ -4131,20 +4420,16 @@ pub enum nsCSSPropertyLogicalGroup {
     eCSSPropertyLogicalGroup_Margin = 3,
     eCSSPropertyLogicalGroup_MaxSize = 4,
     eCSSPropertyLogicalGroup_Offset = 5,
     eCSSPropertyLogicalGroup_Padding = 6,
     eCSSPropertyLogicalGroup_MinSize = 7,
     eCSSPropertyLogicalGroup_Size = 8,
     eCSSPropertyLogicalGroup_COUNT = 9,
 }
-pub type nscolor = u32;
-#[repr(i8)]
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum nsHexColorType { NoAlpha = 0, AllowAlpha = 1, }
 /**
  * Class to safely handle main-thread-only pointers off the main thread.
  *
  * Classes like XPCWrappedJS are main-thread-only, which means that it is
  * forbidden to call methods on instances of these classes off the main thread.
  * For various reasons (see bug 771074), this restriction recently began to
  * apply to AddRef/Release as well.
  *
--- a/servo/ports/geckolib/gecko_bindings/structs_release.rs
+++ b/servo/ports/geckolib/gecko_bindings/structs_release.rs
@@ -183,20 +183,30 @@ pub const NS_ERROR_MODULE_DOM_ANIM: ::st
 pub const NS_ERROR_MODULE_DOM_PUSH: ::std::os::raw::c_uint = 40;
 pub const NS_ERROR_MODULE_GENERAL: ::std::os::raw::c_uint = 51;
 pub const NS_ERROR_SEVERITY_SUCCESS: ::std::os::raw::c_uint = 0;
 pub const NS_ERROR_SEVERITY_ERROR: ::std::os::raw::c_uint = 1;
 pub const NS_ERROR_MODULE_BASE_OFFSET: ::std::os::raw::c_uint = 69;
 pub const MOZ_STRING_WITH_OBSOLETE_API: ::std::os::raw::c_uint = 1;
 pub const NSID_LENGTH: ::std::os::raw::c_uint = 39;
 pub const NS_NUMBER_OF_FLAGS_IN_REFCNT: ::std::os::raw::c_uint = 2;
+pub const _STL_PAIR_H: ::std::os::raw::c_uint = 1;
+pub const _GLIBCXX_UTILITY: ::std::os::raw::c_uint = 1;
+pub const __cpp_lib_tuple_element_t: ::std::os::raw::c_uint = 201402;
+pub const __cpp_lib_tuples_by_type: ::std::os::raw::c_uint = 201304;
+pub const __cpp_lib_exchange_function: ::std::os::raw::c_uint = 201304;
+pub const __cpp_lib_integer_sequence: ::std::os::raw::c_uint = 201304;
+pub const NS_EVENT_STATE_HIGHEST_SERVO_BIT: ::std::os::raw::c_uint = 6;
 pub const DOM_USER_DATA: ::std::os::raw::c_uint = 1;
 pub const SMIL_MAPPED_ATTR_ANIMVAL: ::std::os::raw::c_uint = 2;
 pub const TWIPS_PER_POINT_INT: ::std::os::raw::c_uint = 20;
 pub const POINTS_PER_INCH_INT: ::std::os::raw::c_uint = 72;
+pub const NS_ATTRNAME_NODEINFO_BIT: ::std::os::raw::c_uint = 1;
+pub const NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM: ::std::os::raw::c_uint = 12;
+pub const NS_ATTRVALUE_INTEGERTYPE_BITS: ::std::os::raw::c_uint = 4;
 pub const NS_FONT_VARIANT_NORMAL: ::std::os::raw::c_uint = 0;
 pub const NS_FONT_VARIANT_SMALL_CAPS: ::std::os::raw::c_uint = 1;
 pub const NS_CORNER_TOP_LEFT_X: ::std::os::raw::c_uint = 0;
 pub const NS_CORNER_TOP_LEFT_Y: ::std::os::raw::c_uint = 1;
 pub const NS_CORNER_TOP_RIGHT_X: ::std::os::raw::c_uint = 2;
 pub const NS_CORNER_TOP_RIGHT_Y: ::std::os::raw::c_uint = 3;
 pub const NS_CORNER_BOTTOM_RIGHT_X: ::std::os::raw::c_uint = 4;
 pub const NS_CORNER_BOTTOM_RIGHT_Y: ::std::os::raw::c_uint = 5;
@@ -2699,16 +2709,22 @@ pub struct nsIExpandedPrincipal {
 #[repr(C)]
 pub struct _vftable_nsIExpandedPrincipal {
     pub _base: _vftable_nsISupports,
 }
 impl ::std::clone::Clone for nsIExpandedPrincipal {
     fn clone(&self) -> Self { *self }
 }
 #[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct _Make_integer_sequence<_Tp, _ISeq> {
+    pub _phantom0: ::std::marker::PhantomData<_Tp>,
+    pub _phantom1: ::std::marker::PhantomData<_ISeq>,
+}
+#[repr(C)]
 #[derive(Debug, Copy)]
 pub struct nsIURI {
     pub _base: nsISupports,
 }
 #[repr(C)]
 pub struct _vftable_nsIURI {
     pub _base: _vftable_nsISupports,
 }
@@ -2741,16 +2757,36 @@ pub enum nsIRequest_nsIRequest_h_unnamed
     VALIDATE_ONCE_PER_SESSION = 8192,
     LOAD_ANONYMOUS = 16384,
     LOAD_FRESH_CONNECTION = 32768,
 }
 impl ::std::clone::Clone for nsIRequest {
     fn clone(&self) -> Self { *self }
 }
 /**
+ * EventStates is the class used to represent the event states of nsIContent
+ * instances. These states are calculated by IntrinsicState() and
+ * ContentStatesChanged() has to be called when one of them changes thus
+ * informing the layout/style engine of the change.
+ * Event states are associated with pseudo-classes.
+ */
+#[repr(C)]
+#[derive(Debug, Copy)]
+pub struct EventStates {
+    pub mStates: ::std::os::raw::c_ulong,
+}
+impl ::std::clone::Clone for EventStates {
+    fn clone(&self) -> Self { *self }
+}
+#[test]
+fn bindgen_test_layout_EventStates() {
+    assert_eq!(::std::mem::size_of::<EventStates>() , 8usize);
+    assert_eq!(::std::mem::align_of::<EventStates>() , 8usize);
+}
+/**
  * Enum defining the mode in which a sheet is to be parsed.  This is
  * usually, but not always, the same as the cascade level at which the
  * sheet will apply (see nsStyleSet.h).  Most of the Loader APIs only
  * support loading of author sheets.
  *
  * Author sheets are the normal case: styles embedded in or linked
  * from HTML pages.  They are also the most restricted.
  *
@@ -2860,17 +2896,17 @@ impl ::std::clone::Clone for nsMutationG
 #[test]
 fn bindgen_test_layout_nsMutationGuard() {
     assert_eq!(::std::mem::size_of::<nsMutationGuard>() , 8usize);
     assert_eq!(::std::mem::align_of::<nsMutationGuard>() , 8usize);
 }
 extern "C" {
     #[link_name = "_ZN15nsMutationGuard11sGenerationE"]
     pub static mut nsMutationGuard_consts_sGeneration:
-               ::std::os::raw::c_ulonglong;
+               ::std::os::raw::c_ulong;
 }
 pub type Float = f32;
 #[repr(i8)]
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub enum SurfaceType {
     DATA = 0,
     D2D1_BITMAP = 1,
     D2D1_DRAWTARGET = 2,
@@ -3236,16 +3272,269 @@ pub enum FillMode {
 pub enum PlaybackDirection {
     Normal = 0,
     Reverse = 1,
     Alternate = 2,
     Alternate_reverse = 3,
     EndGuard_ = 4,
 }
 pub type NativeType = AnimationEffectReadOnly;
+#[repr(C)]
+#[derive(Debug)]
+pub struct nsAttrName {
+    pub mBits: usize,
+}
+#[test]
+fn bindgen_test_layout_nsAttrName() {
+    assert_eq!(::std::mem::size_of::<nsAttrName>() , 8usize);
+    assert_eq!(::std::mem::align_of::<nsAttrName>() , 8usize);
+}
+pub type nscolor = u32;
+#[repr(i8)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum nsHexColorType { NoAlpha = 0, AllowAlpha = 1, }
+pub enum nsStyledElementNotElementCSSInlineStyle { }
+pub enum MiscContainer { }
+pub enum ServoDeclarationBlock { }
+pub enum Declaration { }
+/**
+ * A class used to construct a nsString from a nsStringBuffer (we might
+ * want to move this to nsString at some point).
+ *
+ * WARNING: Note that nsCheapString doesn't take an explicit length -- it
+ * assumes the string is maximally large, given the nsStringBuffer's storage
+ * size.  This means the given string buffer *must* be sized exactly correctly
+ * for the string it contains (including one byte for a null terminator).  If
+ * it has any unused storage space, then that will result in bogus characters
+ * at the end of our nsCheapString.
+ */
+#[repr(C)]
+#[derive(Debug)]
+pub struct nsCheapString {
+    pub _base: nsString,
+}
+#[test]
+fn bindgen_test_layout_nsCheapString() {
+    assert_eq!(::std::mem::size_of::<nsCheapString>() , 16usize);
+    assert_eq!(::std::mem::align_of::<nsCheapString>() , 8usize);
+}
+#[repr(C)]
+#[derive(Debug)]
+pub struct nsAttrValue {
+    pub mBits: usize,
+}
+pub const eSVGTypesBegin: nsAttrValue_ValueType =
+    nsAttrValue_ValueType::eSVGAngle;
+pub const eSVGTypesEnd: nsAttrValue_ValueType =
+    nsAttrValue_ValueType::eSVGViewBox;
+#[repr(u32)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum nsAttrValue_ValueType {
+    eString = 0,
+    eAtom = 2,
+    eInteger = 3,
+    eColor = 7,
+    eEnum = 11,
+    ePercent = 15,
+    eGeckoCSSDeclaration = 16,
+    eServoCSSDeclaration = 17,
+    eURL = 18,
+    eImage = 19,
+    eAtomArray = 20,
+    eDoubleValue = 21,
+    eIntMarginValue = 22,
+    eSVGAngle = 23,
+    eSVGIntegerPair = 24,
+    eSVGLength = 25,
+    eSVGLengthList = 26,
+    eSVGNumberList = 27,
+    eSVGNumberPair = 28,
+    eSVGPathData = 29,
+    eSVGPointList = 30,
+    eSVGPreserveAspectRatio = 31,
+    eSVGStringList = 32,
+    eSVGTransformList = 33,
+    eSVGViewBox = 34,
+}
+/**
+   * Structure for a mapping from int (enum) values to strings.  When you use
+   * it you generally create an array of them.
+   * Instantiate like this:
+   * EnumTable myTable[] = {
+   *   { "string1", 1 },
+   *   { "string2", 2 },
+   *   { 0 }
+   * }
+   */
+#[repr(C)]
+#[derive(Debug, Copy)]
+pub struct nsAttrValue_EnumTable {
+    /** The string the value maps to */
+    pub tag: *const ::std::os::raw::c_char,
+    /** The enum value that maps to this string */
+    pub value: i16,
+}
+impl ::std::clone::Clone for nsAttrValue_EnumTable {
+    fn clone(&self) -> Self { *self }
+}
+#[test]
+fn bindgen_test_layout_nsAttrValue_EnumTable() {
+    assert_eq!(::std::mem::size_of::<nsAttrValue_EnumTable>() , 16usize);
+    assert_eq!(::std::mem::align_of::<nsAttrValue_EnumTable>() , 8usize);
+}
+#[repr(u32)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum nsAttrValue_ValueBaseType {
+    eStringBase = 0,
+    eOtherBase = 1,
+    eAtomBase = 2,
+    eIntegerBase = 3,
+}
+#[test]
+fn bindgen_test_layout_nsAttrValue() {
+    assert_eq!(::std::mem::size_of::<nsAttrValue>() , 8usize);
+    assert_eq!(::std::mem::align_of::<nsAttrValue>() , 8usize);
+}
+extern "C" {
+    #[link_name = "_ZN11nsAttrValue15sEnumTableArrayE"]
+    pub static mut nsAttrValue_consts_sEnumTableArray:
+               nsTArray<*const nsAttrValue_EnumTable>;
+}
+pub enum nsCSSSelector { }
+#[repr(u32)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum nsChangeHint {
+    nsChangeHint_RepaintFrame = 1,
+    nsChangeHint_NeedReflow = 2,
+    nsChangeHint_ClearAncestorIntrinsics = 4,
+    nsChangeHint_ClearDescendantIntrinsics = 8,
+    nsChangeHint_NeedDirtyReflow = 16,
+    nsChangeHint_SyncFrameView = 32,
+    nsChangeHint_UpdateCursor = 64,
+    nsChangeHint_UpdateEffects = 128,
+    nsChangeHint_UpdateOpacityLayer = 256,
+    nsChangeHint_UpdateTransformLayer = 512,
+    nsChangeHint_ReconstructFrame = 1024,
+    nsChangeHint_UpdateOverflow = 2048,
+    nsChangeHint_UpdateSubtreeOverflow = 4096,
+    nsChangeHint_UpdatePostTransformOverflow = 8192,
+    nsChangeHint_UpdateParentOverflow = 16384,
+    nsChangeHint_ChildrenOnlyTransform = 32768,
+    nsChangeHint_RecomputePosition = 65536,
+    nsChangeHint_UpdateContainingBlock = 131072,
+    nsChangeHint_BorderStyleNoneChange = 262144,
+    nsChangeHint_UpdateTextPath = 524288,
+    nsChangeHint_SchedulePaint = 1048576,
+    nsChangeHint_NeutralChange = 2097152,
+    nsChangeHint_InvalidateRenderingObservers = 4194304,
+    nsChangeHint_ReflowChangesSizeOrPosition = 8388608,
+    nsChangeHint_UpdateComputedBSize = 16777216,
+    nsChangeHint_UpdateUsesOpacity = 33554432,
+    nsChangeHint_UpdateBackgroundPosition = 67108864,
+}
+pub type nsChangeHint_size_t = ::std::os::raw::c_int;
+/**
+ * |nsRestyleHint| is a bitfield for the result of
+ * |HasStateDependentStyle| and |HasAttributeDependentStyle|.  When no
+ * restyling is necessary, use |nsRestyleHint(0)|.
+ *
+ * Without eRestyle_Force or eRestyle_ForceDescendants, the restyling process
+ * can stop processing at a frame when it detects no style changes and it is
+ * known that the styles of the subtree beneath it will not change, leaving
+ * the old style context on the frame.  eRestyle_Force can be used to skip this
+ * optimization on a frame, and to force its new style context to be used.
+ *
+ * Similarly, eRestyle_ForceDescendants will cause the frame and all of its
+ * descendants to be traversed and for the new style contexts that are created
+ * to be set on the frames.
+ *
+ * NOTE: When adding new restyle hints, please also add them to
+ * RestyleManager::RestyleHintToString.
+ */
+#[repr(u32)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum nsRestyleHint {
+    eRestyle_Self = 1,
+    eRestyle_SomeDescendants = 2,
+    eRestyle_Subtree = 4,
+    eRestyle_LaterSiblings = 8,
+    eRestyle_CSSTransitions = 16,
+    eRestyle_CSSAnimations = 32,
+    eRestyle_SVGAttrAnimations = 64,
+    eRestyle_StyleAttribute = 128,
+    eRestyle_StyleAttribute_Animations = 256,
+    eRestyle_Force = 512,
+    eRestyle_ForceDescendants = 1024,
+    eRestyle_AllHintsWithAnimations = 368,
+}
+pub type nsRestyleHint_size_t = ::std::os::raw::c_int;
+/**
+ * Additional data used in conjunction with an nsRestyleHint to control the
+ * restyle process.
+ */
+#[repr(C)]
+#[derive(Debug)]
+pub struct RestyleHintData {
+    pub mSelectorsForDescendants: nsTArray<*mut nsCSSSelector>,
+}
+#[test]
+fn bindgen_test_layout_RestyleHintData() {
+    assert_eq!(::std::mem::size_of::<RestyleHintData>() , 8usize);
+    assert_eq!(::std::mem::align_of::<RestyleHintData>() , 8usize);
+}
+/**
+ * A structure representing a single attribute name and value.
+ *
+ * This is pretty similar to the private nsAttrAndChildArray::InternalAttr.
+ */
+#[repr(C)]
+#[derive(Debug)]
+pub struct ServoAttrSnapshot {
+    pub mName: nsAttrName,
+    pub mValue: nsAttrValue,
+}
+#[test]
+fn bindgen_test_layout_ServoAttrSnapshot() {
+    assert_eq!(::std::mem::size_of::<ServoAttrSnapshot>() , 16usize);
+    assert_eq!(::std::mem::align_of::<ServoAttrSnapshot>() , 8usize);
+}
+/**
+ * A bitflags enum class used to determine what data does a ServoElementSnapshot
+ * contains.
+ */
+#[repr(i8)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum ServoElementSnapshotFlags {
+    State = 1,
+    Attributes = 2,
+    HTMLElementInHTMLDocument = 4,
+    All = 7,
+}
+/**
+ * This class holds all non-tree-structural state of an element that might be
+ * used for selector matching eventually.
+ *
+ * This means the attributes, and the element state, such as :hover, :active,
+ * etc...
+ */
+#[repr(C)]
+#[derive(Debug)]
+pub struct ServoElementSnapshot {
+    pub mContains: ServoElementSnapshotFlags,
+    pub mAttrs: nsTArray<ServoAttrSnapshot>,
+    pub mState: ::std::os::raw::c_uchar,
+    pub mExplicitRestyleHint: nsRestyleHint,
+    pub mExplicitChangeHint: nsChangeHint,
+    pub mIsHTMLElementInHTMLDocument: bool,
+}
+#[test]
+fn bindgen_test_layout_ServoElementSnapshot() {
+    assert_eq!(::std::mem::size_of::<ServoElementSnapshot>() , 32usize);
+    assert_eq!(::std::mem::align_of::<ServoElementSnapshot>() , 8usize);
+}
 pub enum ErrorReporter { }
 #[repr(u32)]
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub enum nsCSSTokenType {
     eCSSToken_Whitespace = 0,
     eCSSToken_Comment = 1,
     eCSSToken_Ident = 2,
     eCSSToken_Function = 3,
@@ -4110,20 +4399,16 @@ pub enum nsCSSPropertyLogicalGroup {
     eCSSPropertyLogicalGroup_Margin = 3,
     eCSSPropertyLogicalGroup_MaxSize = 4,
     eCSSPropertyLogicalGroup_Offset = 5,
     eCSSPropertyLogicalGroup_Padding = 6,
     eCSSPropertyLogicalGroup_MinSize = 7,
     eCSSPropertyLogicalGroup_Size = 8,
     eCSSPropertyLogicalGroup_COUNT = 9,
 }
-pub type nscolor = u32;
-#[repr(i8)]
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum nsHexColorType { NoAlpha = 0, AllowAlpha = 1, }
 /**
  * Class to safely handle main-thread-only pointers off the main thread.
  *
  * Classes like XPCWrappedJS are main-thread-only, which means that it is
  * forbidden to call methods on instances of these classes off the main thread.
  * For various reasons (see bug 771074), this restriction recently began to
  * apply to AddRef/Release as well.
  *
--- a/servo/ports/geckolib/gecko_bindings/tools/regen.py
+++ b/servo/ports/geckolib/gecko_bindings/tools/regen.py
@@ -40,16 +40,17 @@ COMPILATION_TARGETS = {
     "structs": {
         "test": True,
         "flags": [
             "-ignore-functions",
         ],
         "includes": [
             "{}/dist/include/nsThemeConstants.h",
             "{}/dist/include/mozilla/dom/AnimationEffectReadOnlyBinding.h",
+            "{}/dist/include/mozilla/ServoElementSnapshot.h",
         ],
         "files": [
             "{}/dist/include/nsStyleStruct.h",
         ],
         "build_kinds": {
             "debug": {
                 "flags": [
                     "-DDEBUG=1",
@@ -69,16 +70,18 @@ COMPILATION_TARGETS = {
             "PLDHashTable.h", "nsColor.h", "nsStyleStruct.h", "nsStyleCoord.h",
             "RefPtr.h", "nsISupportsImpl.h", "gfxFontConstants.h",
             "gfxFontFamilyList.h", "gfxFontFeatures.h", "imgRequestProxy.h",
             "nsIRequest.h", "imgIRequest.h", "CounterStyleManager.h",
             "nsStyleConsts.h", "nsCSSValue.h", "SheetType.h", "nsIPrincipal.h",
             "nsDataHashtable.h", "nsCSSScanner.h", "nsTArray",
             "pair", "SheetParsingMode.h", "StaticPtr.h", "nsProxyRelease.h",
             "mozilla/dom/AnimationEffectReadOnlyBinding.h",
+            "nsChangeHint.h", "ServoElementSnapshot.h",
+            "EventStates.h", "nsAttrValue.h", "nsAttrName.h",
             "/Types.h",   # <- Disallow UnionTypes.h
             "/utility",   # <- Disallow xutility
             "nsINode.h",  # <- For `NodeFlags`.
         ],
         "blacklist": [
             "IsDestructibleFallbackImpl", "IsDestructibleFallback",
             "ProxyReleaseEvent", "FallibleTArray", "nsTArray_Impl",
             "__is_tuple_like_impl", "tuple_size", "tuple",
@@ -118,17 +121,17 @@ COMPILATION_TARGETS = {
             "nsStylePosition", "nsStyleTextReset", "nsStyleDisplay",
             "nsStyleContent", "nsStyleUIReset", "nsStyleTable",
             "nsStyleMargin", "nsStylePadding", "nsStyleBorder",
             "nsStyleOutline", "nsStyleXUL", "nsStyleSVGReset", "nsStyleColumn",
             "nsStyleEffects", "nsStyleImage", "nsStyleGradient",
             "nsStyleCoord", "nsStyleGradientStop", "nsStyleImageLayers",
             "nsStyleImageLayers::Layer", "nsStyleImageLayers::LayerType",
             "nsStyleUnit", "nsStyleUnion", "nsStyleCoord::CalcValue",
-            "nsStyleCoord::Calc",
+            "nsStyleCoord::Calc", "nsRestyleHint", "ServoElementSnapshot",
 
             "SheetParsingMode", "nsMainThreadPtrHandle",
             "nsMainThreadPtrHolder", "nscolor", "nsFont", "FontFamilyList",
             "FontFamilyType", "nsIAtom",
         ],
         "void_types": [
             "nsINode", "nsIDocument", "nsIPrincipal", "nsIURI",
         ],
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -8,17 +8,20 @@ use app_units::Au;
 use data::{NUM_THREADS, PerDocumentStyleData};
 use env_logger;
 use euclid::Size2D;
 use gecko_bindings::bindings::{RawGeckoDocument, RawGeckoElement, RawGeckoNode};
 use gecko_bindings::bindings::{RawServoStyleSet, RawServoStyleSheet, ServoComputedValues};
 use gecko_bindings::bindings::{ServoDeclarationBlock, ServoNodeData, ThreadSafePrincipalHolder};
 use gecko_bindings::bindings::{ThreadSafeURIHolder, nsHTMLCSSStyleSheet};
 use gecko_bindings::ptr::{GeckoArcPrincipal, GeckoArcURI};
+use gecko_bindings::structs::ServoElementSnapshot;
+use gecko_bindings::structs::nsRestyleHint;
 use gecko_bindings::structs::{SheetParsingMode, nsIAtom};
+use snapshot::GeckoElementSnapshot;
 use std::mem::transmute;
 use std::ptr;
 use std::slice;
 use std::str::from_utf8_unchecked;
 use std::sync::{Arc, Mutex};
 use style::arc_ptr_eq;
 use style::context::{LocalStyleContextCreationInfo, ReflowGoal, SharedStyleContext};
 use style::dom::{TDocument, TElement, TNode};
@@ -434,8 +437,27 @@ pub extern "C" fn Servo_CSSSupports(prop
     let base_url = &*DUMMY_BASE_URL;
     let extra_data = ParserContextExtraData::default();
 
     match parse_one_declaration(&property, &value, &base_url, Box::new(StdoutErrorReporter), extra_data) {
         Ok(decls) => !decls.is_empty(),
         Err(()) => false,
     }
 }
+
+#[no_mangle]
+pub extern "C" fn Servo_ComputeRestyleHint(element: *mut RawGeckoElement,
+                                           snapshot: *mut ServoElementSnapshot,
+                                           raw_data: *mut RawServoStyleSet) -> nsRestyleHint {
+    let per_doc_data = unsafe { &mut *(raw_data as *mut PerDocumentStyleData) };
+    let snapshot = unsafe { GeckoElementSnapshot::from_raw(snapshot) };
+    let element = unsafe { GeckoElement::from_raw(element) };
+
+    // NB: This involves an FFI call, we can get rid of it easily if needed.
+    let current_state = element.get_state();
+
+    let hint = per_doc_data.stylist
+                           .compute_restyle_hint(&element, &snapshot,
+                                                 current_state);
+
+    // NB: Binary representations match.
+    unsafe { transmute(hint.bits() as u32) }
+}
--- a/servo/ports/geckolib/lib.rs
+++ b/servo/ports/geckolib/lib.rs
@@ -17,16 +17,18 @@ extern crate selectors;
 extern crate string_cache;
 extern crate style;
 extern crate style_traits;
 extern crate url;
 extern crate util;
 
 mod context;
 mod data;
+mod snapshot;
+mod snapshot_helpers;
 #[allow(non_snake_case)]
 pub mod glue;
 mod traversal;
 mod wrapper;
 
 // FIXME(bholley): This should probably go away once we harmonize the allocators.
 #[no_mangle]
 pub extern "C" fn je_malloc_usable_size(_: *const ::libc::c_void) -> ::libc::size_t { 0 }
new file mode 100644
--- /dev/null
+++ b/servo/ports/geckolib/snapshot.rs
@@ -0,0 +1,149 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+use gecko_bindings::bindings;
+use gecko_bindings::structs::ServoElementSnapshot;
+use gecko_bindings::structs::ServoElementSnapshotFlags as Flags;
+use selectors::parser::AttrSelector;
+use snapshot_helpers;
+use string_cache::Atom;
+use style::element_state::ElementState;
+use style::restyle_hints::ElementSnapshot;
+use wrapper::AttrSelectorHelpers;
+
+// NB: This is sound, in some sense, because during computation of restyle hints
+// the snapshot is kept alive by the modified elements table.
+#[derive(Debug)]
+pub struct GeckoElementSnapshot(*mut ServoElementSnapshot);
+
+impl GeckoElementSnapshot {
+    #[inline]
+    pub unsafe fn from_raw(raw: *mut ServoElementSnapshot) -> Self {
+        GeckoElementSnapshot(raw)
+    }
+
+    #[inline]
+    fn is_html_element_in_html_document(&self) -> bool {
+        unsafe { (*self.0).mIsHTMLElementInHTMLDocument }
+    }
+
+    #[inline]
+    fn has_any(&self, flags: Flags) -> bool {
+        unsafe { ((*self.0).mContains as u8 & flags as u8) != 0 }
+    }
+}
+
+impl ::selectors::MatchAttr for GeckoElementSnapshot {
+    type AttrString = Atom;
+
+    fn match_attr_has(&self, attr: &AttrSelector) -> bool {
+        unsafe {
+            bindings::Gecko_SnapshotHasAttr(self.0,
+                                            attr.ns_or_null(),
+                                            attr.select_name(self.is_html_element_in_html_document()))
+        }
+    }
+
+    fn match_attr_equals(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool {
+        unsafe {
+            bindings::Gecko_SnapshotAttrEquals(self.0,
+                                               attr.ns_or_null(),
+                                               attr.select_name(self.is_html_element_in_html_document()),
+                                               value.as_ptr(),
+                                               /* ignoreCase = */ false)
+        }
+    }
+
+    fn match_attr_equals_ignore_ascii_case(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool {
+        unsafe {
+            bindings::Gecko_SnapshotAttrEquals(self.0,
+                                               attr.ns_or_null(),
+                                               attr.select_name(self.is_html_element_in_html_document()),
+                                               value.as_ptr(),
+                                               /* ignoreCase = */ true)
+        }
+    }
+    fn match_attr_includes(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool {
+        unsafe {
+            bindings::Gecko_SnapshotAttrIncludes(self.0,
+                                                 attr.ns_or_null(),
+                                                 attr.select_name(self.is_html_element_in_html_document()),
+                                                 value.as_ptr())
+        }
+    }
+    fn match_attr_dash(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool {
+        unsafe {
+            bindings::Gecko_SnapshotAttrDashEquals(self.0,
+                                                   attr.ns_or_null(),
+                                                   attr.select_name(self.is_html_element_in_html_document()),
+                                                   value.as_ptr())
+        }
+    }
+    fn match_attr_prefix(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool {
+        unsafe {
+            bindings::Gecko_SnapshotAttrHasPrefix(self.0,
+                                                  attr.ns_or_null(),
+                                                  attr.select_name(self.is_html_element_in_html_document()),
+                                                  value.as_ptr())
+        }
+    }
+    fn match_attr_substring(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool {
+        unsafe {
+            bindings::Gecko_SnapshotAttrHasSubstring(self.0,
+                                                     attr.ns_or_null(),
+                                                     attr.select_name(self.is_html_element_in_html_document()),
+                                                     value.as_ptr())
+        }
+    }
+    fn match_attr_suffix(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool {
+        unsafe {
+            bindings::Gecko_SnapshotAttrHasSuffix(self.0,
+                                                  attr.ns_or_null(),
+                                                  attr.select_name(self.is_html_element_in_html_document()),
+                                                  value.as_ptr())
+        }
+    }
+}
+
+impl ElementSnapshot for GeckoElementSnapshot {
+    fn state(&self) -> Option<ElementState> {
+        if self.has_any(Flags::State) {
+            Some(ElementState::from_bits_truncate(unsafe { (*self.0).mState as u16 }))
+        } else {
+            None
+        }
+    }
+
+    #[inline]
+    fn has_attrs(&self) -> bool {
+        self.has_any(Flags::Attributes)
+    }
+
+    fn id_attr(&self) -> Option<Atom> {
+        let ptr = unsafe {
+            bindings::Gecko_SnapshotAtomAttrValue(self.0,
+                                                  atom!("id").as_ptr())
+        };
+
+        if ptr.is_null() {
+            None
+        } else {
+            Some(Atom::from(ptr))
+        }
+    }
+
+    // TODO: share logic with Element::{has_class, each_class}?
+    fn has_class(&self, name: &Atom) -> bool {
+        snapshot_helpers::has_class(self.0,
+                                    name,
+                                    bindings::Gecko_SnapshotClassOrClassList)
+    }
+
+    fn each_class<F>(&self, callback: F)
+        where F: FnMut(&Atom)
+    {
+        snapshot_helpers::each_class(self.0,
+                                     callback,
+                                     bindings::Gecko_SnapshotClassOrClassList)
+    }
+}
new file mode 100644
--- /dev/null
+++ b/servo/ports/geckolib/snapshot_helpers.rs
@@ -0,0 +1,53 @@
+/* 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/. */
+
+//! Element an snapshot common logic.
+
+use gecko_bindings::structs::nsIAtom;
+use std::{ptr, slice};
+use string_cache::Atom;
+
+pub type ClassOrClassList<T> = unsafe extern fn (T, *mut *mut nsIAtom, *mut *mut *mut nsIAtom) -> u32;
+
+pub fn has_class<T>(item: T,
+                    name: &Atom,
+                    getter: ClassOrClassList<T>) -> bool
+{
+    unsafe {
+        let mut class: *mut nsIAtom = ptr::null_mut();
+        let mut list: *mut *mut nsIAtom = ptr::null_mut();
+        let length = getter(item, &mut class, &mut list);
+        match length {
+            0 => false,
+            1 => name.as_ptr() == class,
+            n => {
+                let classes = slice::from_raw_parts(list, n as usize);
+                classes.iter().any(|ptr| name.as_ptr() == *ptr)
+            }
+        }
+    }
+}
+
+
+pub fn each_class<F, T>(item: T,
+                        mut callback: F,
+                        getter: ClassOrClassList<T>)
+    where F: FnMut(&Atom)
+{
+    unsafe {
+        let mut class: *mut nsIAtom = ptr::null_mut();
+        let mut list: *mut *mut nsIAtom = ptr::null_mut();
+        let length = getter(item, &mut class, &mut list);
+        match length {
+            0 => {}
+            1 => Atom::with(class, &mut callback),
+            n => {
+                let classes = slice::from_raw_parts(list, n as usize);
+                for c in classes {
+                    Atom::with(*c, &mut callback)
+                }
+            }
+        }
+    }
+}
--- a/servo/ports/geckolib/wrapper.rs
+++ b/servo/ports/geckolib/wrapper.rs
@@ -2,62 +2,56 @@
  * 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/. */
 
 #![allow(unsafe_code)]
 
 use gecko_bindings::bindings;
 use gecko_bindings::bindings::Gecko_ChildrenCount;
 use gecko_bindings::bindings::Gecko_ClassOrClassList;
-use gecko_bindings::bindings::Gecko_GetElementId;
 use gecko_bindings::bindings::Gecko_GetNodeData;
 use gecko_bindings::bindings::ServoNodeData;
 use gecko_bindings::bindings::{Gecko_ElementState, Gecko_GetDocumentElement};
 use gecko_bindings::bindings::{Gecko_GetFirstChild, Gecko_GetFirstChildElement};
 use gecko_bindings::bindings::{Gecko_GetLastChild, Gecko_GetLastChildElement};
 use gecko_bindings::bindings::{Gecko_GetNextSibling, Gecko_GetNextSiblingElement};
 use gecko_bindings::bindings::{Gecko_GetNodeFlags, Gecko_SetNodeFlags, Gecko_UnsetNodeFlags};
 use gecko_bindings::bindings::{Gecko_GetParentElement, Gecko_GetParentNode};
 use gecko_bindings::bindings::{Gecko_GetPrevSibling, Gecko_GetPrevSiblingElement};
 use gecko_bindings::bindings::{Gecko_GetServoDeclarationBlock, Gecko_IsHTMLElementInHTMLDocument};
 use gecko_bindings::bindings::{Gecko_IsLink, Gecko_IsRootElement, Gecko_IsTextNode};
 use gecko_bindings::bindings::{Gecko_IsUnvisitedLink, Gecko_IsVisitedLink};
-#[allow(unused_imports)] // Used in commented-out code.
 use gecko_bindings::bindings::{Gecko_LocalName, Gecko_Namespace, Gecko_NodeIsElement, Gecko_SetNodeData};
 use gecko_bindings::bindings::{RawGeckoDocument, RawGeckoElement, RawGeckoNode};
 use gecko_bindings::structs::nsIAtom;
 use gecko_bindings::structs::{NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO, NODE_IS_DIRTY_FOR_SERVO};
 use glue::GeckoDeclarationBlock;
 use libc::uintptr_t;
 use selectors::Element;
 use selectors::matching::DeclarationBlock;
 use selectors::parser::{AttrSelector, NamespaceConstraint};
+use snapshot::GeckoElementSnapshot;
+use snapshot_helpers;
 use std::marker::PhantomData;
 use std::ops::BitOr;
 use std::ptr;
-use std::slice;
 use std::sync::Arc;
 use string_cache::{Atom, BorrowedAtom, BorrowedNamespace, Namespace};
 use style::data::PrivateStyleData;
 use style::dom::{OpaqueNode, PresentationalHintsSynthetizer};
 use style::dom::{TDocument, TElement, TNode, TRestyleDamage, UnsafeNode};
 use style::element_state::ElementState;
-#[allow(unused_imports)] // Used in commented-out code.
 use style::error_reporting::StdoutErrorReporter;
 use style::gecko_selector_impl::{GeckoSelectorImpl, NonTSPseudoClass};
-#[allow(unused_imports)] // Used in commented-out code.
 use style::parser::ParserContextExtraData;
-#[allow(unused_imports)] // Used in commented-out code.
 use style::properties::{ComputedValues, parse_style_attribute};
 use style::properties::{PropertyDeclaration, PropertyDeclarationBlock};
 use style::refcell::{Ref, RefCell, RefMut};
-use style::restyle_hints::ElementSnapshot;
 use style::selector_impl::ElementExt;
 use style::sink::Push;
-#[allow(unused_imports)] // Used in commented-out code.
 use url::Url;
 
 pub type NonOpaqueStyleData = *mut RefCell<PrivateStyleData>;
 
 // Important: We don't currently refcount the DOM, because the wrapper lifetime
 // magic guarantees that our LayoutFoo references won't outlive the root, and
 // we don't mutate any of the references on the Gecko side during restyle. We
 // could implement refcounting if need be (at a potentially non-trivial
@@ -319,17 +313,17 @@ impl<'ld> TDocument for GeckoDocument<'l
     }
 
     fn root_node(&self) -> Option<GeckoNode<'ld>> {
         unsafe {
             Gecko_GetDocumentElement(self.document).as_ref().map(|el| GeckoElement::from_ref(el).as_node())
         }
     }
 
-    fn drain_modified_elements(&self) -> Vec<(GeckoElement<'ld>, ElementSnapshot)> {
+    fn drain_modified_elements(&self) -> Vec<(GeckoElement<'ld>, GeckoElementSnapshot)> {
         unimplemented!()
         /*
         let elements =  unsafe { self.document.drain_modified_elements() };
         elements.into_iter().map(|(el, snapshot)| (ServoLayoutElement::from_layout_js(el), snapshot)).collect()*/
     }
 }
 
 #[derive(Clone, Copy)]
@@ -492,156 +486,140 @@ impl<'le> ::selectors::Element for Gecko
             },
             NonTSPseudoClass::ReadOnly => {
                 !self.get_state().contains(pseudo_class.state_flag())
             }
         }
     }
 
     fn get_id(&self) -> Option<Atom> {
-        unsafe {
-            let ptr = Gecko_GetElementId(self.element);
-            if ptr.is_null() {
-                None
-            } else {
-                Some(Atom::from(ptr))
-            }
+        let ptr = unsafe {
+            bindings::Gecko_AtomAttrValue(self.element,
+                                          atom!("id").as_ptr())
+        };
+
+        if ptr.is_null() {
+            None
+        } else {
+            Some(Atom::from(ptr))
         }
     }
 
     fn has_class(&self, name: &Atom) -> bool {
-        unsafe {
-            let mut class: *mut nsIAtom = ptr::null_mut();
-            let mut list: *mut *mut nsIAtom = ptr::null_mut();
-            let length = Gecko_ClassOrClassList(self.element, &mut class, &mut list);
-            match length {
-                0 => false,
-                1 => name.as_ptr() == class,
-                n => {
-                    let classes = slice::from_raw_parts(list, n as usize);
-                    classes.iter().any(|ptr| name.as_ptr() == *ptr)
-                }
-            }
-        }
+        snapshot_helpers::has_class(self.element,
+                                    name,
+                                    Gecko_ClassOrClassList)
     }
 
-    fn each_class<F>(&self, mut callback: F) where F: FnMut(&Atom) {
-        unsafe {
-            let mut class: *mut nsIAtom = ptr::null_mut();
-            let mut list: *mut *mut nsIAtom = ptr::null_mut();
-            let length = Gecko_ClassOrClassList(self.element, &mut class, &mut list);
-            match length {
-                0 => {}
-                1 => Atom::with(class, &mut callback),
-                n => {
-                    let classes = slice::from_raw_parts(list, n as usize);
-                    for c in classes {
-                        Atom::with(*c, &mut callback)
-                    }
-                }
-            }
-        }
+    fn each_class<F>(&self, callback: F)
+        where F: FnMut(&Atom)
+    {
+        snapshot_helpers::each_class(self.element,
+                                     callback,
+                                     Gecko_ClassOrClassList)
     }
 
     fn is_html_element_in_html_document(&self) -> bool {
         unsafe {
             Gecko_IsHTMLElementInHTMLDocument(self.element)
         }
     }
 }
 
-trait AttrSelectorHelpers {
+pub trait AttrSelectorHelpers {
     fn ns_or_null(&self) -> *mut nsIAtom;
-    fn select_name<'le>(&self, el: &GeckoElement<'le>) -> *mut nsIAtom;
+    fn select_name(&self, is_html_element_in_html_document: bool) -> *mut nsIAtom;
 }
 
 impl AttrSelectorHelpers for AttrSelector {
     fn ns_or_null(&self) -> *mut nsIAtom {
         match self.namespace {
             NamespaceConstraint::Any => ptr::null_mut(),
             NamespaceConstraint::Specific(ref ns) => ns.0.as_ptr(),
         }
     }
 
-    fn select_name<'le>(&self, el: &GeckoElement<'le>) -> *mut nsIAtom {
-        if el.is_html_element_in_html_document() {
+    fn select_name(&self, is_html_element_in_html_document: bool) -> *mut nsIAtom {
+        if is_html_element_in_html_document {
             self.lower_name.as_ptr()
         } else {
             self.name.as_ptr()
         }
-
     }
 }
 
 impl<'le> ::selectors::MatchAttr for GeckoElement<'le> {
     type AttrString = Atom;
     fn match_attr_has(&self, attr: &AttrSelector) -> bool {
         unsafe {
             bindings::Gecko_HasAttr(self.element,
                                     attr.ns_or_null(),
-                                    attr.select_name(self))
+                                    attr.select_name(self.is_html_element_in_html_document()))
         }
     }
     fn match_attr_equals(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool {
         unsafe {
             bindings::Gecko_AttrEquals(self.element,
                                        attr.ns_or_null(),
-                                       attr.select_name(self),
+                                       attr.select_name(self.is_html_element_in_html_document()),
                                        value.as_ptr(),
                                        /* ignoreCase = */ false)
         }
     }
     fn match_attr_equals_ignore_ascii_case(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool {
         unsafe {
             bindings::Gecko_AttrEquals(self.element,
                                        attr.ns_or_null(),
-                                       attr.select_name(self),
+                                       attr.select_name(self.is_html_element_in_html_document()),
                                        value.as_ptr(),
                                        /* ignoreCase = */ false)
         }
     }
     fn match_attr_includes(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool {
         unsafe {
             bindings::Gecko_AttrIncludes(self.element,
                                          attr.ns_or_null(),
-                                         attr.select_name(self),
+                                         attr.select_name(self.is_html_element_in_html_document()),
                                          value.as_ptr())
         }
     }
     fn match_attr_dash(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool {
         unsafe {
             bindings::Gecko_AttrDashEquals(self.element,
                                            attr.ns_or_null(),
-                                           attr.select_name(self),
+                                           attr.select_name(self.is_html_element_in_html_document()),
                                            value.as_ptr())
         }
     }
     fn match_attr_prefix(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool {
         unsafe {
             bindings::Gecko_AttrHasPrefix(self.element,
                                           attr.ns_or_null(),
-                                          attr.select_name(self),
+                                          attr.select_name(self.is_html_element_in_html_document()),
                                           value.as_ptr())
         }
     }
     fn match_attr_substring(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool {
         unsafe {
             bindings::Gecko_AttrHasSubstring(self.element,
                                              attr.ns_or_null(),
-                                             attr.select_name(self),
+                                             attr.select_name(self.is_html_element_in_html_document()),
                                              value.as_ptr())
         }
     }
     fn match_attr_suffix(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool {
         unsafe {
             bindings::Gecko_AttrHasSuffix(self.element,
                                           attr.ns_or_null(),
-                                          attr.select_name(self),
+                                          attr.select_name(self.is_html_element_in_html_document()),
                                           value.as_ptr())
         }
     }
 }
 
 impl<'le> ElementExt for GeckoElement<'le> {
+    type Snapshot = GeckoElementSnapshot;
+
+    #[inline]
     fn is_link(&self) -> bool {
         self.match_non_ts_pseudo_class(NonTSPseudoClass::AnyLink)
     }
 }