Bug 1287951: stylo: Add support for computing style hints from Servo. r=heycam
authorEmilio Cobos Álvarez <ecoal95@gmail.com>
Mon, 18 Jul 2016 18:02:55 -0700
changeset 331283 5daa5c05033142561e0377f4b16b212fbbe8f811
parent 331282 607f7217e5ec8f910382992f752a03b5a67de36e
child 331284 b04110c70f1563175ca7689c05495f7a13b97b42
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam
bugs1287951
milestone50.0a1
Bug 1287951: stylo: Add support for computing style hints from Servo. r=heycam MozReview-Commit-ID: ALuJxcfAMuL
dom/events/EventStates.h
layout/base/ServoRestyleManager.cpp
layout/base/ServoRestyleManager.h
layout/style/ServoBindings.cpp
layout/style/ServoBindings.h
layout/style/ServoElementSnapshot.cpp
layout/style/ServoElementSnapshot.h
layout/style/ServoStyleSet.cpp
layout/style/ServoStyleSet.h
layout/style/moz.build
--- a/dom/events/EventStates.h
+++ b/dom/events/EventStates.h
@@ -7,27 +7,30 @@
 #ifndef mozilla_EventStates_h_
 #define mozilla_EventStates_h_
 
 #include "mozilla/Attributes.h"
 #include "nsDebug.h"
 
 namespace mozilla {
 
+#define NS_EVENT_STATE_HIGHEST_SERVO_BIT 6
+
 /**
  * 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.
  */
 class EventStates
 {
 public:
   typedef uint64_t InternalType;
+  typedef uint8_t ServoType;
 
   constexpr EventStates()
     : mStates(0)
   {
   }
 
   // NOTE: the ideal scenario would be to have the default constructor public
   // setting mStates to 0 and this constructor (without = 0) private.
@@ -150,16 +153,24 @@ public:
   }
 
   // We only need that method for inDOMUtils::GetContentState.
   // If inDOMUtils::GetContentState is removed, this method should be removed.
   InternalType GetInternalValue() const {
     return mStates;
   }
 
+  /**
+   * Method used to get the appropriate state representation for Servo.
+   */
+  ServoType ServoValue() const
+  {
+    return mStates & ((1 << (NS_EVENT_STATE_HIGHEST_SERVO_BIT + 1)) - 1);
+  }
+
 private:
   InternalType mStates;
 };
 
 } // namespace mozilla
 
 /**
  * The following macros are creating EventStates instance with different
@@ -195,20 +206,21 @@ private:
 #define NS_EVENT_STATE_ENABLED       NS_DEFINE_EVENT_STATE_MACRO(3)
 // Content is disabled.
 #define NS_EVENT_STATE_DISABLED      NS_DEFINE_EVENT_STATE_MACRO(4)
 // Content is checked.
 #define NS_EVENT_STATE_CHECKED       NS_DEFINE_EVENT_STATE_MACRO(5)
 // Content is in the indeterminate state.
 #define NS_EVENT_STATE_INDETERMINATE NS_DEFINE_EVENT_STATE_MACRO(6)
 
-#define NS_EVENT_STATE_HIGHEST_SERVO_BIT 6
-
 /*
  * Bits below here do not have Servo-related ordering constraints.
+ *
+ * Remember to change NS_EVENT_STATE_HIGHEST_SERVO_BIT at the top of the file if
+ * this changes!
  */
 
 // Drag is hovering over content.
 #define NS_EVENT_STATE_DRAGOVER      NS_DEFINE_EVENT_STATE_MACRO(7)
 // Content is URL's target (ref).
 #define NS_EVENT_STATE_URLTARGET     NS_DEFINE_EVENT_STATE_MACRO(8)
 // Content is required.
 #define NS_EVENT_STATE_REQUIRED      NS_DEFINE_EVENT_STATE_MACRO(9)
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -12,30 +12,35 @@ using namespace mozilla::dom;
 namespace mozilla {
 
 ServoRestyleManager::ServoRestyleManager(nsPresContext* aPresContext)
   : RestyleManagerBase(aPresContext)
 {
 }
 
 /* static */ void
-ServoRestyleManager::DirtyTree(nsIContent* aContent)
+ServoRestyleManager::DirtyTree(nsIContent* aContent, bool aIncludingRoot)
 {
-  if (aContent->IsDirtyForServo()) {
-    return;
+  if (aIncludingRoot) {
+    // XXX: This can in theory leave nodes not dirty, but in practice this is not
+    // a problem, at least for now, since right now element dirty implies
+    // descendants dirty. Remove this early return if this ever changes.
+    if (aContent->IsDirtyForServo()) {
+      return;
+    }
+
+    aContent->SetIsDirtyForServo();
   }
 
-  aContent->SetIsDirtyForServo();
-
   FlattenedChildIterator it(aContent);
 
   nsIContent* n = it.GetNextChild();
   bool hadChildren = bool(n);
-  for ( ; n; n = it.GetNextChild()) {
-    DirtyTree(n);
+  for (; n; n = it.GetNextChild()) {
+    DirtyTree(n, true);
   }
 
   if (hadChildren) {
     aContent->SetHasDirtyDescendantsForServo();
   }
 }
 
 void
@@ -43,36 +48,32 @@ ServoRestyleManager::PostRestyleEvent(El
                                       nsRestyleHint aRestyleHint,
                                       nsChangeHint aMinChangeHint)
 {
   if (MOZ_UNLIKELY(IsDisconnected()) ||
       MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) {
     return;
   }
 
-  if (aRestyleHint == 0 && !aMinChangeHint) {
-    // Nothing to do here
-    return;
+  if (aRestyleHint == 0 && !aMinChangeHint && !HasPendingRestyles()) {
+    return; // Nothing to do.
+  }
+
+  // Note that unlike in Servo, we don't mark elements as dirty until we process
+  // the restyle hints in ProcessPendingRestyles.
+  if (aRestyleHint || aMinChangeHint) {
+    ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
+    snapshot->AddExplicitRestyleHint(aRestyleHint);
+    snapshot->AddExplicitChangeHint(aMinChangeHint);
   }
 
   nsIPresShell* presShell = PresContext()->PresShell();
   if (!ObservingRefreshDriver()) {
-    SetObservingRefreshDriver(PresContext()->RefreshDriver()->
-        AddStyleFlushObserver(presShell));
-  }
-
-  // Propagate the IS_DIRTY flag down the tree.
-  DirtyTree(aElement);
-
-  // Propagate the HAS_DIRTY_DESCENDANTS flag to the root.
-  nsINode* cur = aElement;
-  while ((cur = cur->GetParentNode())) {
-    if (cur->HasDirtyDescendantsForServo())
-      break;
-    cur->SetHasDirtyDescendantsForServo();
+    SetObservingRefreshDriver(
+      PresContext()->RefreshDriver()->AddStyleFlushObserver(presShell));
   }
 
   presShell->GetDocument()->SetNeedStyleFlush();
 }
 
 void
 ServoRestyleManager::PostRestyleEventForLazyConstruction()
 {
@@ -132,32 +133,92 @@ ServoRestyleManager::RecreateStyleContex
     FlattenedChildIterator it(aContent);
     for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
       RecreateStyleContexts(n, primaryFrame->StyleContext(), aStyleSet);
     }
     aContent->UnsetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
   }
 }
 
+static void
+MarkParentsAsHavingDirtyDescendants(Element* aElement)
+{
+  nsINode* cur = aElement;
+  while ((cur = cur->GetParentNode())) {
+    if (cur->HasDirtyDescendantsForServo()) {
+      break;
+    }
+
+    cur->SetHasDirtyDescendantsForServo();
+  }
+}
+
+void
+ServoRestyleManager::NoteRestyleHint(Element* aElement, nsRestyleHint aHint)
+{
+  if (aHint & eRestyle_Self) {
+    aElement->SetIsDirtyForServo();
+    MarkParentsAsHavingDirtyDescendants(aElement);
+    // NB: Using Servo's style system marking the subtree as dirty is necessary
+    // so we inherit correctly the style structs.
+    aHint |= eRestyle_Subtree;
+  }
+
+  if (aHint & eRestyle_Subtree) {
+    DirtyTree(aElement, /* aIncludingRoot = */ false);
+    MarkParentsAsHavingDirtyDescendants(aElement);
+  }
+
+  if (aHint & eRestyle_LaterSiblings) {
+    for (nsINode* cur = aElement->GetNextSibling(); cur;
+         cur = cur->GetNextSibling()) {
+      if (cur->IsContent()) {
+        DirtyTree(cur->AsContent(), /* aIncludingRoot = */ true);
+      }
+    }
+  }
+
+  // TODO: Handle all other nsRestyleHint values.
+  if (aHint & ~(eRestyle_Self | eRestyle_Subtree | eRestyle_LaterSiblings)) {
+    NS_ERROR("stylo: Unhandled restyle hint");
+  }
+}
+
 void
 ServoRestyleManager::ProcessPendingRestyles()
 {
   if (!HasPendingRestyles()) {
     return;
   }
   ServoStyleSet* styleSet = StyleSet();
 
   nsIDocument* doc = PresContext()->Document();
 
   Element* root = doc->GetRootElement();
   if (root) {
+    for (auto iter = mModifiedElements.Iter(); !iter.Done(); iter.Next()) {
+      ServoElementSnapshot* snapshot = iter.UserData();
+      Element* element = iter.Key();
+
+      // TODO: avoid the ComputeRestyleHint call if we already have the highest
+      // explicit restyle hint?
+      nsRestyleHint hint = styleSet->ComputeRestyleHint(element, snapshot);
+      hint |= snapshot->ExplicitRestyleHint();
+
+      if (hint) {
+        NoteRestyleHint(element, hint);
+      }
+    }
+
     styleSet->RestyleSubtree(root, /* aForce = */ false);
     RecreateStyleContexts(root, nullptr, styleSet);
   }
 
+  mModifiedElements.Clear();
+
   // NB: we restyle from the root element, but the document also gets the
   // HAS_DIRTY_DESCENDANTS flag as part of the loop on PostRestyleEvent, and we
   // use that to check we have pending restyles.
   //
   // Thus, they need to get cleared here.
   MOZ_ASSERT(!doc->IsDirtyForServo());
   doc->UnsetFlags(NODE_HAS_DIRTY_DESCENDANTS_FOR_SERVO);
 
@@ -183,50 +244,84 @@ ServoRestyleManager::RestyleForRemove(El
                                       nsIContent* aOldChild,
                                       nsIContent* aFollowingSibling)
 {
   NS_ERROR("stylo: ServoRestyleManager::RestyleForRemove not implemented");
 }
 
 nsresult
 ServoRestyleManager::ContentStateChanged(nsIContent* aContent,
-                                         EventStates aStateMask)
+                                         EventStates aChangedBits)
 {
   if (!aContent->IsElement()) {
     return NS_OK;
   }
 
   Element* aElement = aContent->AsElement();
   nsChangeHint changeHint;
   nsRestyleHint restyleHint;
-  ContentStateChangedInternal(aElement, aStateMask, &changeHint, &restyleHint);
+
+  // NOTE: restyleHint here is effectively always 0, since that's what
+  // ServoStyleSet::HasStateDependentStyle returns. Servo computes on
+  // ProcessPendingRestyles using the ElementSnapshot, but in theory could
+  // compute it sequentially easily.
+  //
+  // Determine what's the best way to do it, and how much work do we save
+  // processing the restyle hint early (i.e., computing the style hint here
+  // sequentially, potentially saving the snapshot), vs lazily (snapshot
+  // approach).
+  //
+  // If we take the sequential approach we need to specialize Servo's restyle
+  // hints system a bit more, and mesure whether we save something storing the
+  // restyle hint in the table and deferring the dirtiness setting until
+  // ProcessPendingRestyles (that's a requirement if we store snapshots though),
+  // vs processing the restyle hint in-place, dirtying the nodes on
+  // PostRestyleEvent.
+  //
+  // If we definitely take the snapshot approach, we should take rid of
+  // HasStateDependentStyle, etc (though right now they're no-ops).
+  ContentStateChangedInternal(aElement, aChangedBits, &changeHint,
+                              &restyleHint);
+
+  EventStates previousState = aElement->StyleState() ^ aChangedBits;
+  ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
+  snapshot->AddState(previousState);
 
   PostRestyleEvent(aElement, restyleHint, changeHint);
   return NS_OK;
 }
 
 void
 ServoRestyleManager::AttributeWillChange(Element* aElement,
                                          int32_t aNameSpaceID,
-                                         nsIAtom* aAttribute,
-                                         int32_t aModType,
+                                         nsIAtom* aAttribute, int32_t aModType,
                                          const nsAttrValue* aNewValue)
 {
   NS_ERROR("stylo: ServoRestyleManager::AttributeWillChange not implemented");
 }
 
 void
-ServoRestyleManager::AttributeChanged(Element* aElement,
-                                      int32_t aNameSpaceID,
-                                      nsIAtom* aAttribute,
-                                      int32_t aModType,
+ServoRestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
+                                      nsIAtom* aAttribute, int32_t aModType,
                                       const nsAttrValue* aOldValue)
 {
   NS_ERROR("stylo: ServoRestyleManager::AttributeChanged not implemented");
 }
 
 nsresult
 ServoRestyleManager::ReparentStyleContext(nsIFrame* aFrame)
 {
   MOZ_CRASH("stylo: ServoRestyleManager::ReparentStyleContext not implemented");
 }
 
+ServoElementSnapshot*
+ServoRestyleManager::SnapshotForElement(Element* aElement)
+{
+  ServoElementSnapshot* snapshot = mModifiedElements.LookupOrAdd(aElement);
+  if (!snapshot->HasAny(
+        ServoElementSnapshot::Flags::HTMLElementInHTMLDocument)) {
+    snapshot->SetIsHTMLElementInHTMLDocument(
+      aElement->IsHTMLElement() && aElement->OwnerDoc()->IsHTMLDocument());
+  }
+  return snapshot;
+}
+
 } // namespace mozilla
--- a/layout/base/ServoRestyleManager.h
+++ b/layout/base/ServoRestyleManager.h
@@ -4,20 +4,22 @@
  * 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/. */
 
 #ifndef mozilla_ServoRestyleManager_h
 #define mozilla_ServoRestyleManager_h
 
 #include "mozilla/EventStates.h"
 #include "mozilla/RestyleManagerBase.h"
+#include "mozilla/ServoElementSnapshot.h"
 #include "nsChangeHint.h"
+#include "nsHashKeys.h"
+#include "nsINode.h"
 #include "nsISupportsImpl.h"
 #include "nsPresContext.h"
-#include "nsINode.h"
 
 namespace mozilla {
 namespace dom {
 class Element;
 } // namespace dom
 } // namespace mozilla
 class nsAttrValue;
 class nsIAtom;
@@ -55,54 +57,60 @@ public:
                         nsIContent* aFollowingSibling);
   nsresult ContentStateChanged(nsIContent* aContent,
                                EventStates aStateMask);
   void AttributeWillChange(dom::Element* aElement,
                            int32_t aNameSpaceID,
                            nsIAtom* aAttribute,
                            int32_t aModType,
                            const nsAttrValue* aNewValue);
-  void AttributeChanged(dom::Element* aElement,
-                        int32_t aNameSpaceID,
-                        nsIAtom* aAttribute,
-                        int32_t aModType,
+  void AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID,
+                        nsIAtom* aAttribute, int32_t aModType,
                         const nsAttrValue* aOldValue);
   nsresult ReparentStyleContext(nsIFrame* aFrame);
 
-  bool HasPendingRestyles() {
-    if (MOZ_UNLIKELY(IsDisconnected())) {
-      return false;
-    }
-
-    return PresContext()->PresShell()->GetDocument()->HasDirtyDescendantsForServo();
-  }
+  bool HasPendingRestyles() { return mModifiedElements.Count() != 0; }
 
 protected:
   ~ServoRestyleManager() {}
 
 private:
+  ServoElementSnapshot* SnapshotForElement(Element* aElement);
+
+  /**
+   * The element-to-element snapshot table to compute restyle hints.
+   */
+  nsClassHashtable<nsRefPtrHashKey<Element>, ServoElementSnapshot>
+    mModifiedElements;
+
   /**
    * Traverses a tree of content that Servo has just restyled, recreating style
    * contexts for their frames with the new style data.
    *
    * This is just static so ServoStyleSet can mark this class as friend, so we
    * can access to the GetContext method without making it available to everyone
    * else.
    */
   static void RecreateStyleContexts(nsIContent* aContent,
                                     nsStyleContext* aParentContext,
                                     ServoStyleSet* aStyleSet);
 
   /**
    * Propagates the IS_DIRTY flag down to the tree, setting
    * HAS_DIRTY_DESCENDANTS appropriately.
    */
-  static void DirtyTree(nsIContent* aContent);
+  static void DirtyTree(nsIContent* aContent, bool aIncludingRoot = true);
 
-  inline ServoStyleSet* StyleSet() const {
+  /**
+   * Marks the tree with the appropriate dirty flags for the given restyle hint.
+   */
+  static void NoteRestyleHint(Element* aElement, nsRestyleHint aRestyleHint);
+
+  inline ServoStyleSet* StyleSet() const
+  {
     MOZ_ASSERT(PresContext()->StyleSet()->IsServo(),
                "ServoRestyleManager should only be used with a Servo-flavored "
                "style backend");
     return PresContext()->StyleSet()->AsServo();
   }
 };
 
 } // namespace mozilla
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -1,33 +1,34 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #include "mozilla/ServoBindings.h"
 
+#include "StyleStructContext.h"
 #include "gfxFontFamilyList.h"
 #include "nsAttrValueInlines.h"
 #include "nsCSSRuleProcessor.h"
 #include "nsContentUtils.h"
 #include "nsDOMTokenList.h"
 #include "nsIDOMNode.h"
 #include "nsIDocument.h"
 #include "nsINode.h"
 #include "nsIPrincipal.h"
 #include "nsNameSpaceManager.h"
 #include "nsString.h"
 #include "nsStyleStruct.h"
+#include "nsStyleUtil.h"
 #include "nsTArray.h"
-#include "nsStyleUtil.h"
-#include "StyleStructContext.h"
 
 #include "mozilla/EventStates.h"
+#include "mozilla/ServoElementSnapshot.h"
 #include "mozilla/dom/Element.h"
 
 uint32_t
 Gecko_ChildrenCount(RawGeckoNode* aNode)
 {
   return aNode->GetChildCount();
 }
 
@@ -97,21 +98,20 @@ Gecko_GetNextSiblingElement(RawGeckoElem
 }
 
 RawGeckoElement*
 Gecko_GetDocumentElement(RawGeckoDocument* aDoc)
 {
   return aDoc->GetDocumentElement();
 }
 
-uint8_t
+EventStates::ServoType
 Gecko_ElementState(RawGeckoElement* aElement)
 {
-  return aElement->StyleState().GetInternalValue() &
-         ((1 << (NS_EVENT_STATE_HIGHEST_SERVO_BIT + 1)) - 1);
+  return aElement->StyleState().ServoValue();
 }
 
 bool
 Gecko_IsHTMLElementInHTMLDocument(RawGeckoElement* aElement)
 {
   return aElement->IsHTMLElement() && aElement->OwnerDoc()->IsHTMLDocument();
 }
 
@@ -179,19 +179,37 @@ Gecko_SetNodeFlags(RawGeckoNode* aNode, 
 }
 
 void
 Gecko_UnsetNodeFlags(RawGeckoNode* aNode, uint32_t aFlags)
 {
   aNode->UnsetFlags(aFlags);
 }
 
-template<class MatchFn>
-bool
-DoMatch(RawGeckoElement* aElement, nsIAtom* aNS, nsIAtom* aName, MatchFn aMatch)
+ServoDeclarationBlock*
+Gecko_GetServoDeclarationBlock(RawGeckoElement* aElement)
+{
+  const nsAttrValue* attr = aElement->GetParsedAttr(nsGkAtoms::style);
+  if (!attr || attr->Type() != nsAttrValue::eServoCSSDeclaration) {
+    return nullptr;
+  }
+  return attr->GetServoCSSDeclarationValue();
+}
+
+template <typename Implementor>
+static nsIAtom*
+AtomAttrValue(Implementor* aElement, nsIAtom* aName)
+{
+  const nsAttrValue* attr = aElement->GetParsedAttr(aName);
+  return attr ? attr->GetAtomValue() : nullptr;
+}
+
+template <typename Implementor, typename MatchFn>
+static bool
+DoMatch(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName, MatchFn aMatch)
 {
   if (aNS) {
     int32_t ns = nsContentUtils::NameSpaceManager()->GetNameSpaceID(aNS);
     NS_ENSURE_TRUE(ns != kNameSpaceID_Unknown, false);
     const nsAttrValue* value = aElement->GetParsedAttr(aName, ns);
     return value && aMatch(value);
   }
   // No namespace means any namespace - we have to check them all. :-(
@@ -213,104 +231,124 @@ DoMatch(RawGeckoElement* aElement, nsIAt
 // bug 1281935 lands.
 template<typename T>
 struct FakeRef {
   MOZ_IMPLICIT FakeRef(T* aPtr) : mPtr(aPtr) {}
   operator T*() const { return mPtr; }
   T* mPtr;
 };
 
-bool
-Gecko_HasAttr(RawGeckoElement* aElement, nsIAtom* aNS, nsIAtom* aName)
+template <typename Implementor>
+static bool
+HasAttr(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName)
 {
   auto match = [](const nsAttrValue* aValue) { return true; };
   return DoMatch(aElement, aNS, aName, match);
 }
 
-bool
-Gecko_AttrEquals(RawGeckoElement* aElement, nsIAtom* aNS, nsIAtom* aName,
-                 nsIAtom* aStr_, bool aIgnoreCase)
+template <typename Implementor>
+static bool
+AttrEquals(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName, nsIAtom* aStr_,
+           bool aIgnoreCase)
 {
   FakeRef<nsIAtom> aStr(aStr_);
   auto match = [aStr, aIgnoreCase](const nsAttrValue* aValue) {
     return aValue->Equals(aStr, aIgnoreCase ? eIgnoreCase : eCaseMatters);
   };
   return DoMatch(aElement, aNS, aName, match);
 }
 
-bool
-Gecko_AttrDashEquals(RawGeckoElement* aElement, nsIAtom* aNS, nsIAtom* aName,
-                     nsIAtom* aStr_)
+template <typename Implementor>
+static bool
+AttrDashEquals(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+               nsIAtom* aStr_)
 {
   FakeRef<nsIAtom> aStr(aStr_);
   auto match = [aStr](const nsAttrValue* aValue) {
     nsAutoString str;
     aValue->ToString(str);
     const nsDefaultStringComparator c;
     return nsStyleUtil::DashMatchCompare(str, nsDependentAtomString(aStr), c);
   };
   return DoMatch(aElement, aNS, aName, match);
 }
 
-bool
-Gecko_AttrIncludes(RawGeckoElement* aElement, nsIAtom* aNS, nsIAtom* aName,
-                   nsIAtom* aStr_)
+template <typename Implementor>
+static bool
+AttrIncludes(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+             nsIAtom* aStr_)
 {
   FakeRef<nsIAtom> aStr(aStr_);
   auto match = [aStr](const nsAttrValue* aValue) {
     nsAutoString str;
     aValue->ToString(str);
     const nsDefaultStringComparator c;
     return nsStyleUtil::ValueIncludes(str, nsDependentAtomString(aStr), c);
   };
   return DoMatch(aElement, aNS, aName, match);
 }
 
-bool
-Gecko_AttrHasSubstring(RawGeckoElement* aElement, nsIAtom* aNS, nsIAtom* aName,
-                       nsIAtom* aStr_)
+template <typename Implementor>
+static bool
+AttrHasSubstring(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+                 nsIAtom* aStr_)
 {
   FakeRef<nsIAtom> aStr(aStr_);
   auto match = [aStr](const nsAttrValue* aValue) {
     nsAutoString str;
     aValue->ToString(str);
     return FindInReadable(str, nsDependentAtomString(aStr));
   };
   return DoMatch(aElement, aNS, aName, match);
 }
 
-bool
-Gecko_AttrHasPrefix(RawGeckoElement* aElement, nsIAtom* aNS, nsIAtom* aName,
-                    nsIAtom* aStr_)
+template <typename Implementor>
+static bool
+AttrHasPrefix(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+              nsIAtom* aStr_)
 {
   FakeRef<nsIAtom> aStr(aStr_);
   auto match = [aStr](const nsAttrValue* aValue) {
     nsAutoString str;
     aValue->ToString(str);
     return StringBeginsWith(str, nsDependentAtomString(aStr));
   };
   return DoMatch(aElement, aNS, aName, match);
 }
 
-bool
-Gecko_AttrHasSuffix(RawGeckoElement* aElement, nsIAtom* aNS, nsIAtom* aName,
-                    nsIAtom* aStr_)
+template <typename Implementor>
+static bool
+AttrHasSuffix(Implementor* aElement, nsIAtom* aNS, nsIAtom* aName,
+              nsIAtom* aStr_)
 {
   FakeRef<nsIAtom> aStr(aStr_);
   auto match = [aStr](const nsAttrValue* aValue) {
     nsAutoString str;
     aValue->ToString(str);
     return StringEndsWith(str, nsDependentAtomString(aStr));
   };
   return DoMatch(aElement, aNS, aName, match);
 }
 
-uint32_t
-Gecko_ClassOrClassList(RawGeckoElement* aElement,
-                       nsIAtom** aClass, nsIAtom*** aClassList)
+/**
+ * Gets the class or class list (if any) of the implementor. The calling
+ * convention here is rather hairy, and is optimized for getting Servo the
+ * information it needs for hot calls.
+ *
+ * The return value indicates the number of classes. If zero, neither outparam
+ * is valid. If one, the class_ outparam is filled with the atom of the class.
+ * If two or more, the classList outparam is set to point to an array of atoms
+ * representing the class list.
+ *
+ * The array is borrowed and the atoms are not addrefed. These values can be
+ * invalidated by any DOM mutation. Use them in a tight scope.
+ */
+template <typename Implementor>
+static uint32_t
+ClassOrClassList(Implementor* aElement, nsIAtom** aClass, nsIAtom*** aClassList)
 {
   const nsAttrValue* attr = aElement->GetParsedAttr(nsGkAtoms::_class);
   if (!attr) {
     return 0;
   }
 
   // For class values with only whitespace, Gecko just stores a string. For the
   // purposes of the style system, there is no class in this case.
@@ -352,25 +390,65 @@ Gecko_ClassOrClassList(RawGeckoElement* 
   static_assert(alignof(nsCOMPtr<nsIAtom>) == alignof(nsIAtom*), "Bad simplification");
 
   nsCOMPtr<nsIAtom>* elements = atomArray->Elements();
   nsIAtom** rawElements = reinterpret_cast<nsIAtom**>(elements);
   *aClassList = rawElements;
   return atomArray->Length();
 }
 
-ServoDeclarationBlock*
-Gecko_GetServoDeclarationBlock(RawGeckoElement* aElement)
-{
-  const nsAttrValue* attr = aElement->GetParsedAttr(nsGkAtoms::style);
-  if (!attr || attr->Type() != nsAttrValue::eServoCSSDeclaration) {
-    return nullptr;
+#define SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS(prefix_, implementor_)      \
+  nsIAtom* prefix_##AtomAttrValue(implementor_* aElement, nsIAtom* aName)      \
+  {                                                                            \
+    return AtomAttrValue(aElement, aName);                                     \
+  }                                                                            \
+  bool prefix_##HasAttr(implementor_* aElement, nsIAtom* aNS, nsIAtom* aName)  \
+  {                                                                            \
+    return HasAttr(aElement, aNS, aName);                                      \
+  }                                                                            \
+  bool prefix_##AttrEquals(implementor_* aElement, nsIAtom* aNS,               \
+                           nsIAtom* aName, nsIAtom* aStr, bool aIgnoreCase)    \
+  {                                                                            \
+    return AttrEquals(aElement, aNS, aName, aStr, aIgnoreCase);                \
+  }                                                                            \
+  bool prefix_##AttrDashEquals(implementor_* aElement, nsIAtom* aNS,           \
+                               nsIAtom* aName, nsIAtom* aStr)                  \
+  {                                                                            \
+    return AttrDashEquals(aElement, aNS, aName, aStr);                         \
+  }                                                                            \
+  bool prefix_##AttrIncludes(implementor_* aElement, nsIAtom* aNS,             \
+                             nsIAtom* aName, nsIAtom* aStr)                    \
+  {                                                                            \
+    return AttrIncludes(aElement, aNS, aName, aStr);                           \
+  }                                                                            \
+  bool prefix_##AttrHasSubstring(implementor_* aElement, nsIAtom* aNS,         \
+                                 nsIAtom* aName, nsIAtom* aStr)                \
+  {                                                                            \
+    return AttrHasSubstring(aElement, aNS, aName, aStr);                       \
+  }                                                                            \
+  bool prefix_##AttrHasPrefix(implementor_* aElement, nsIAtom* aNS,            \
+                              nsIAtom* aName, nsIAtom* aStr)                   \
+  {                                                                            \
+    return AttrHasPrefix(aElement, aNS, aName, aStr);                          \
+  }                                                                            \
+  bool prefix_##AttrHasSuffix(implementor_* aElement, nsIAtom* aNS,            \
+                              nsIAtom* aName, nsIAtom* aStr)                   \
+  {                                                                            \
+    return AttrHasSuffix(aElement, aNS, aName, aStr);                          \
+  }                                                                            \
+  uint32_t prefix_##ClassOrClassList(implementor_* aElement, nsIAtom** aClass, \
+                                     nsIAtom*** aClassList)                    \
+  {                                                                            \
+    return ClassOrClassList(aElement, aClass, aClassList);                     \
   }
-  return attr->GetServoCSSDeclarationValue();
-}
+
+SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_, RawGeckoElement)
+SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_Snapshot, ServoElementSnapshot)
+
+#undef SERVO_IMPL_ELEMENT_ATTR_MATCHING_FUNCTIONS
 
 ServoNodeData*
 Gecko_GetNodeData(RawGeckoNode* aNode)
 {
   return aNode->GetServoNodeData();
 }
 
 void
@@ -804,16 +882,25 @@ Servo_ReleaseComputedValues(ServoCompute
 
 void
 Servo_Initialize()
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_Initialize in a "
             "non-MOZ_STYLO build");
 }
 
+// Restyle hints.
+nsRestyleHint
+Servo_ComputeRestyleHint(RawGeckoElement* element,
+                         ServoElementSnapshot* snapshot, RawServoStyleSet* set)
+{
+  MOZ_CRASH("stylo: shouldn't be calling Servo_ComputeRestyleHint in a "
+            "non-MOZ_STYLO build");
+}
+
 void
 Servo_RestyleDocument(RawGeckoDocument* doc, RawServoStyleSet* set)
 {
   MOZ_CRASH("stylo: shouldn't be calling Servo_RestyleDocument in a "
             "non-MOZ_STYLO build");
 }
 
 void Servo_RestyleSubtree(RawGeckoNode* node, RawServoStyleSet* set)
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -2,22 +2,24 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #ifndef mozilla_ServoBindings_h
 #define mozilla_ServoBindings_h
 
-#include "stdint.h"
+#include "mozilla/ServoElementSnapshot.h"
+#include "mozilla/css/SheetParsingMode.h"
+#include "nsChangeHint.h"
 #include "nsColor.h"
+#include "nsProxyRelease.h"
+#include "nsStyleCoord.h"
 #include "nsStyleStruct.h"
-#include "nsStyleCoord.h"
-#include "mozilla/css/SheetParsingMode.h"
-#include "nsProxyRelease.h"
+#include "stdint.h"
 
 /*
  * API for Servo to access Gecko data structures. This file must compile as valid
  * C code in order for the binding generator to parse it.
  *
  * Functions beginning with Gecko_ are implemented in Gecko and invoked from Servo.
  * Functions beginning with Servo_ are implemented in Servo and invoked from Gecko.
  */
@@ -31,16 +33,17 @@ struct nsFont;
 namespace mozilla {
   class FontFamilyList;
   enum FontFamilyType : uint32_t;
   namespace dom { class Element; }
 }
 using mozilla::FontFamilyList;
 using mozilla::FontFamilyType;
 using mozilla::dom::Element;
+using mozilla::ServoElementSnapshot;
 typedef mozilla::dom::Element RawGeckoElement;
 class nsIDocument;
 typedef nsIDocument RawGeckoDocument;
 struct ServoNodeData;
 struct ServoComputedValues;
 struct RawServoStyleSheet;
 struct RawServoStyleSet;
 class nsHTMLCSSStyleSheet;
@@ -99,39 +102,39 @@ bool Gecko_IsTextNode(RawGeckoNode* node
 bool Gecko_IsVisitedLink(RawGeckoElement* element);
 bool Gecko_IsUnvisitedLink(RawGeckoElement* element);
 bool Gecko_IsRootElement(RawGeckoElement* element);
 nsIAtom* Gecko_LocalName(RawGeckoElement* element);
 nsIAtom* Gecko_Namespace(RawGeckoElement* element);
 nsIAtom* Gecko_GetElementId(RawGeckoElement* element);
 
 // Attributes.
-bool Gecko_HasAttr(RawGeckoElement* element, nsIAtom* ns, nsIAtom* name);
-bool Gecko_AttrEquals(RawGeckoElement* element, nsIAtom* ns, nsIAtom* name, nsIAtom* str, bool ignoreCase);
-bool Gecko_AttrDashEquals(RawGeckoElement* element, nsIAtom* ns, nsIAtom* name, nsIAtom* str);
-bool Gecko_AttrIncludes(RawGeckoElement* element, nsIAtom* ns, nsIAtom* name, nsIAtom* str);
-bool Gecko_AttrHasSubstring(RawGeckoElement* element, nsIAtom* ns, nsIAtom* name, nsIAtom* str);
-bool Gecko_AttrHasPrefix(RawGeckoElement* element, nsIAtom* ns, nsIAtom* name, nsIAtom* str);
-bool Gecko_AttrHasSuffix(RawGeckoElement* element, nsIAtom* ns, nsIAtom* name, nsIAtom* str);
-
+#define SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS(prefix_, implementor_)   \
+  nsIAtom* prefix_##AtomAttrValue(implementor_* element, nsIAtom* attribute);  \
+  bool prefix_##HasAttr(implementor_* element, nsIAtom* ns, nsIAtom* name);    \
+  bool prefix_##AttrEquals(implementor_* element, nsIAtom* ns, nsIAtom* name,  \
+                           nsIAtom* str, bool ignoreCase);                     \
+  bool prefix_##AttrDashEquals(implementor_* element, nsIAtom* ns,             \
+                               nsIAtom* name, nsIAtom* str);                   \
+  bool prefix_##AttrIncludes(implementor_* element, nsIAtom* ns,               \
+                             nsIAtom* name, nsIAtom* str);                     \
+  bool prefix_##AttrHasSubstring(implementor_* element, nsIAtom* ns,           \
+                                 nsIAtom* name, nsIAtom* str);                 \
+  bool prefix_##AttrHasPrefix(implementor_* element, nsIAtom* ns,              \
+                              nsIAtom* name, nsIAtom* str);                    \
+  bool prefix_##AttrHasSuffix(implementor_* element, nsIAtom* ns,              \
+                              nsIAtom* name, nsIAtom* str);                    \
+  uint32_t prefix_##ClassOrClassList(implementor_* element, nsIAtom** class_,  \
+                                     nsIAtom*** classList);
 
-// Gets the class or class list (if any) of the Element.
-//
-// The calling convention here is rather hairy, and is optimized for getting
-// Servo the information it needs for hot calls.
-//
-// The return value indicates the number of classes. If zero, neither outparam
-// is valid. If one, the class_ outparam is filled with the atom of the class.
-// If two or more, the classList outparam is set to point to an array of atoms
-// representing the class list.
-//
-// The array is borrowed and the atoms are not addrefed. These values can be
-// invalidated by any DOM mutation. Use them in a tight scope.
-uint32_t Gecko_ClassOrClassList(RawGeckoElement* element,
-                                nsIAtom** class_, nsIAtom*** classList);
+SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_, RawGeckoElement)
+SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS(Gecko_Snapshot,
+                                              ServoElementSnapshot)
+
+#undef SERVO_DECLARE_ELEMENT_ATTR_MATCHING_FUNCTIONS
 
 // Style attributes.
 ServoDeclarationBlock* Gecko_GetServoDeclarationBlock(RawGeckoElement* element);
 
 // Node data.
 ServoNodeData* Gecko_GetNodeData(RawGeckoNode* node);
 void Gecko_SetNodeData(RawGeckoNode* node, ServoNodeData* data);
 void Servo_DropNodeData(ServoNodeData* data);
@@ -254,21 +257,28 @@ void Servo_ReleaseComputedValues(ServoCo
 
 // Initialize Servo components. Should be called exactly once at startup.
 void Servo_Initialize();
 
 // Restyle the given document or subtree.
 void Servo_RestyleDocument(RawGeckoDocument* doc, RawServoStyleSet* set);
 void Servo_RestyleSubtree(RawGeckoNode* node, RawServoStyleSet* set);
 
+// Restyle hints.
+nsRestyleHint Servo_ComputeRestyleHint(RawGeckoElement* element,
+                                       ServoElementSnapshot* snapshot,
+                                       RawServoStyleSet* set);
+
 // Style-struct management.
-#define STYLE_STRUCT(name, checkdata_cb) \
-struct nsStyle##name; \
-void Gecko_Construct_nsStyle##name(nsStyle##name* ptr); \
-void Gecko_CopyConstruct_nsStyle##name(nsStyle##name* ptr, const nsStyle##name* other); \
-void Gecko_Destroy_nsStyle##name(nsStyle##name* ptr); \
-const nsStyle##name* Servo_GetStyle##name(ServoComputedValues* computedValues);
+#define STYLE_STRUCT(name, checkdata_cb)                                       \
+  struct nsStyle##name;                                                        \
+  void Gecko_Construct_nsStyle##name(nsStyle##name* ptr);                      \
+  void Gecko_CopyConstruct_nsStyle##name(nsStyle##name* ptr,                   \
+                                         const nsStyle##name* other);          \
+  void Gecko_Destroy_nsStyle##name(nsStyle##name* ptr);                        \
+  const nsStyle##name* Servo_GetStyle##name(                                   \
+    ServoComputedValues* computedValues);
 #include "nsStyleStructList.h"
 #undef STYLE_STRUCT
 
 } // extern "C"
 
 #endif // mozilla_ServoBindings_h
new file mode 100644
--- /dev/null
+++ b/layout/style/ServoElementSnapshot.cpp
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "mozilla/ServoElementSnapshot.h"
+#include "mozilla/dom/Element.h"
+
+namespace mozilla {
+
+void
+ServoElementSnapshot::AddAttrs(Element* aElement)
+{
+  MOZ_ASSERT(aElement);
+
+  if (!HasAny(Flags::Attributes)) {
+    return;
+  }
+
+  uint32_t attrCount = aElement->GetAttrCount();
+  const nsAttrName* attrName;
+  for (uint32_t i = 0; i < attrCount; ++i) {
+    attrName = aElement->GetAttrNameAt(i);
+    const nsAttrValue* attrValue =
+      aElement->GetParsedAttr(attrName->LocalName(), attrName->NamespaceID());
+    mAttrs.AppendElement(ServoAttrSnapshot(*attrName, *attrValue));
+  }
+  mContains |= Flags::Attributes;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/style/ServoElementSnapshot.h
@@ -0,0 +1,180 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_ServoElementSnapshot_h
+#define mozilla_ServoElementSnapshot_h
+
+#include "mozilla/EventStates.h"
+#include "mozilla/TypedEnumBits.h"
+#include "nsAttrName.h"
+#include "nsAttrValue.h"
+#include "nsChangeHint.h"
+#include "nsIAtom.h"
+
+namespace mozilla {
+
+namespace dom {
+class Element;
+} // namespace dom
+
+/**
+ * A structure representing a single attribute name and value.
+ *
+ * This is pretty similar to the private nsAttrAndChildArray::InternalAttr.
+ */
+struct ServoAttrSnapshot
+{
+  nsAttrName mName;
+  nsAttrValue mValue;
+
+  ServoAttrSnapshot(const nsAttrName& aName, const nsAttrValue& aValue)
+    : mName(aName)
+    , mValue(aValue)
+  {
+  }
+};
+
+/**
+ * A bitflags enum class used to determine what data does a ServoElementSnapshot
+ * contains.
+ */
+enum class ServoElementSnapshotFlags : uint8_t
+{
+  State = 1 << 0,
+  Attributes = 1 << 1,
+  HTMLElementInHTMLDocument = 1 << 2,
+  All = State | Attributes | HTMLElementInHTMLDocument
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ServoElementSnapshotFlags)
+
+/**
+ * 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...
+ */
+class ServoElementSnapshot
+{
+  typedef dom::Element Element;
+  typedef EventStates::ServoType ServoStateType;
+
+public:
+  typedef ServoElementSnapshotFlags Flags;
+
+  /**
+   * Empty snapshot, with no data at all.
+   */
+  ServoElementSnapshot()
+    : mContains(Flags(0))
+    , mState(0)
+    , mExplicitRestyleHint(nsRestyleHint(0))
+    , mExplicitChangeHint(nsChangeHint(0))
+    , mIsHTMLElementInHTMLDocument(false)
+  {
+  }
+
+  bool HasAttrs() { return HasAny(Flags::Attributes); }
+
+  bool HasState() { return HasAny(Flags::State); }
+
+  /**
+   * Captures the given state (if not previously captured).
+   */
+  void AddState(EventStates aState)
+  {
+    if (!HasAny(Flags::State)) {
+      mState = aState.ServoValue();
+      mContains |= Flags::State;
+    }
+  }
+
+  /**
+   * Captures the given element attributes (if not previously captured).
+   */
+  void AddAttrs(Element* aElement);
+
+  void AddExplicitChangeHint(nsChangeHint aMinChangeHint)
+  {
+    mExplicitChangeHint |= aMinChangeHint;
+  }
+
+  void AddExplicitRestyleHint(nsRestyleHint aRestyleHint)
+  {
+    mExplicitRestyleHint |= aRestyleHint;
+  }
+
+  nsRestyleHint ExplicitRestyleHint() { return mExplicitRestyleHint; }
+
+  nsChangeHint ExplicitChangeHint() { return mExplicitChangeHint; }
+
+  /**
+   * Needed methods for attribute matching.
+   */
+  const nsAttrName* GetAttrNameAt(uint32_t aIndex) const
+  {
+    if (aIndex >= mAttrs.Length()) {
+      return nullptr;
+    }
+    return &mAttrs[aIndex].mName;
+  }
+
+  const nsAttrValue* GetParsedAttr(nsIAtom* aLocalName) const
+  {
+    return GetParsedAttr(aLocalName, kNameSpaceID_None);
+  }
+
+  const nsAttrValue* GetParsedAttr(nsIAtom* aLocalName,
+                                   int32_t aNamespaceID) const
+  {
+    uint32_t i, len = mAttrs.Length();
+    if (aNamespaceID == kNameSpaceID_None) {
+      // This should be the common case so lets make an optimized loop
+      for (i = 0; i < len; ++i) {
+        if (mAttrs[i].mName.Equals(aLocalName)) {
+          return &mAttrs[i].mValue;
+        }
+      }
+
+      return nullptr;
+    }
+
+    for (i = 0; i < len; ++i) {
+      if (mAttrs[i].mName.Equals(aLocalName, aNamespaceID)) {
+        return &mAttrs[i].mValue;
+      }
+    }
+
+    return nullptr;
+  }
+
+  void SetIsHTMLElementInHTMLDocument(bool aIs)
+  {
+    MOZ_ASSERT(!HasAny(Flags::HTMLElementInHTMLDocument),
+               "Only expected to be set once!");
+    mContains |= Flags::HTMLElementInHTMLDocument;
+    mIsHTMLElementInHTMLDocument = aIs;
+  }
+
+  bool HasAny(Flags aFlags) { return bool(mContains & aFlags); }
+
+private:
+  // TODO: Profile, a 1 or 2 element AutoTArray could be worth it, given we know
+  // we're dealing with attribute changes when we take snapshots of attributes,
+  // though it can be wasted space if we deal with a lot of state-only
+  // snapshots.
+  Flags mContains;
+  nsTArray<ServoAttrSnapshot> mAttrs;
+  ServoStateType mState;
+  nsRestyleHint mExplicitRestyleHint;
+  nsChangeHint mExplicitChangeHint;
+  bool mIsHTMLElementInHTMLDocument;
+};
+
+} // namespace mozilla
+
+#endif
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -376,16 +376,23 @@ ServoStyleSet::HasStateDependentStyle(do
                                       CSSPseudoElementType aPseudoType,
                                      dom::Element* aPseudoElement,
                                      EventStates aStateMask)
 {
   NS_ERROR("stylo: HasStateDependentStyle not implemented");
   return nsRestyleHint(0);
 }
 
+nsRestyleHint
+ServoStyleSet::ComputeRestyleHint(dom::Element* aElement,
+                                  ServoElementSnapshot* aSnapshot)
+{
+  return Servo_ComputeRestyleHint(aElement, aSnapshot, mRawSet.get());
+}
+
 void
 ServoStyleSet::RestyleSubtree(nsINode* aNode, bool aForce)
 {
   if (aForce) {
     MOZ_ASSERT(aNode->IsContent());
     ServoRestyleManager::DirtyTree(aNode->AsContent());
   }
 
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -6,21 +6,22 @@
 
 #ifndef mozilla_ServoStyleSet_h
 #define mozilla_ServoStyleSet_h
 
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/ServoBindingHelpers.h"
+#include "mozilla/ServoElementSnapshot.h"
 #include "mozilla/ServoStyleSheet.h"
 #include "mozilla/SheetType.h"
 #include "mozilla/UniquePtr.h"
+#include "nsCSSPseudoElements.h"
 #include "nsChangeHint.h"
-#include "nsCSSPseudoElements.h"
 #include "nsIAtom.h"
 #include "nsTArray.h"
 
 namespace mozilla {
 namespace dom {
 class Element;
 } // namespace dom
 class CSSStyleSheet;
@@ -108,20 +109,25 @@ public:
                           mozilla::CSSPseudoElementType aType,
                           nsStyleContext* aParentContext,
                           TreeMatchContext& aTreeMatchContext,
                           dom::Element* aPseudoElement = nullptr);
 
   // Test if style is dependent on content state
   nsRestyleHint HasStateDependentStyle(dom::Element* aElement,
                                        EventStates aStateMask);
-  nsRestyleHint HasStateDependentStyle(dom::Element* aElement,
-                                       mozilla::CSSPseudoElementType aPseudoType,
-                                       dom::Element* aPseudoElement,
-                                       EventStates aStateMask);
+  nsRestyleHint HasStateDependentStyle(
+    dom::Element* aElement, mozilla::CSSPseudoElementType aPseudoType,
+    dom::Element* aPseudoElement, EventStates aStateMask);
+
+  /**
+   * Computes a restyle hint given a element and a previous element snapshot.
+   */
+  nsRestyleHint ComputeRestyleHint(dom::Element* aElement,
+                                   ServoElementSnapshot* aSnapshot);
 
   /**
    * Restyles a whole subtree of nodes.
    *
    * The aForce parameter propagates the dirty bits down the subtree, and when
    * used aNode needs to be nsIContent.
    */
   void RestyleSubtree(nsINode* aNode, bool aForce);
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -88,16 +88,17 @@ EXPORTS.mozilla += [
     'CSSVariableValues.h',
     'HandleRefPtr.h',
     'IncrementalClearCOMRuleArray.h',
     'LayerAnimationInfo.h',
     'RuleNodeCacheConditions.h',
     'RuleProcessorCache.h',
     'ServoBindingHelpers.h',
     'ServoBindings.h',
+    'ServoElementSnapshot.h',
     'ServoStyleSet.h',
     'ServoStyleSheet.h',
     'SheetType.h',
     'StyleAnimationValue.h',
     'StyleBackendType.h',
     'StyleContextSource.h',
     'StyleSetHandle.h',
     'StyleSetHandleInlines.h',
@@ -185,16 +186,17 @@ UNIFIED_SOURCES += [
     'nsStyleSet.cpp',
     'nsStyleStruct.cpp',
     'nsStyleTransformMatrix.cpp',
     'nsStyleUtil.cpp',
     'nsTransitionManager.cpp',
     'RuleNodeCacheConditions.cpp',
     'RuleProcessorCache.cpp',
     'ServoBindings.cpp',
+    'ServoElementSnapshot.cpp',
     'ServoStyleSet.cpp',
     'ServoStyleSheet.cpp',
     'StyleAnimationValue.cpp',
     'StyleRule.cpp',
     'StyleSheet.cpp',
     'StyleSheetInfo.cpp',
     'SVGAttrAnimationRuleProcessor.cpp',
 ]