Bug 1287951: stylo: Add basic support for taking element Snapshots draft
authorEmilio Cobos Álvarez <ecoal95@gmail.com>
Mon, 18 Jul 2016 18:02:55 -0700
changeset 389716 d3fd6e6ee4d97b41059b0342b655a3826c0a81fc
parent 389550 5a91e5b49be3c1ba401b057e90c92d7488e3647d
child 389717 a50c58bbb152abe1425c2678dbcbeccf6d5db6b9
push id23495
push userbmo:ealvarez@mozilla.com
push dateTue, 19 Jul 2016 22:31:45 +0000
bugs1287951
milestone50.0a1
Bug 1287951: stylo: Add basic support for taking element Snapshots MozReview-Commit-ID: Hzqj8mgEyIT
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.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,23 @@ 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 +205,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);
+    DirtyTree(n, true);
   }
 
   if (hadChildren) {
     aContent->SetHasDirtyDescendantsForServo();
   }
 }
 
 void
@@ -43,39 +48,49 @@ 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;
-  }
+  // NOTE: We defer the processing of restyle/change hints until
+  // ProcessPendingRestyles.
+  //
+  // This snapshot effectively stores nothing (at least yet), except the
+  // restyle/change hint.
+  //
+  // TODO: we could do some space optimisations here it seems, though I don't
+  // expect this path to be really hot.
+  bool needsRestyle = false;
 
-  nsIPresShell* presShell = PresContext()->PresShell();
-  if (!ObservingRefreshDriver()) {
-    SetObservingRefreshDriver(PresContext()->RefreshDriver()->
-        AddStyleFlushObserver(presShell));
+  if (aRestyleHint || aMinChangeHint) {
+    AddElementSnapshot(aElement, ServoElementSnapshot::Flags::No,
+                       aRestyleHint, aMinChangeHint);
+    needsRestyle = true;
+  } else {
+    // Note that we could have been called just after adding an element to the
+    // table, for example.
+    //
+    // Other way to structure this would be to duplicate the logic in
+    // AddElementSnapshot, at the (very minor) cost of possibly doing things
+    // twice.
+    needsRestyle = HasPendingRestyles();
   }
 
-  // Propagate the IS_DIRTY flag down the tree.
-  DirtyTree(aElement);
+  if (needsRestyle) {
+    nsIPresShell* presShell = PresContext()->PresShell();
+    if (!ObservingRefreshDriver()) {
+      SetObservingRefreshDriver(PresContext()->RefreshDriver()->
+          AddStyleFlushObserver(presShell));
+    }
 
-  // Propagate the HAS_DIRTY_DESCENDANTS flag to the root.
-  nsINode* cur = aElement;
-  while ((cur = cur->GetParentNode())) {
-    if (cur->HasDirtyDescendantsForServo())
-      break;
-    cur->SetHasDirtyDescendantsForServo();
+    presShell->GetDocument()->SetNeedStyleFlush();
   }
-
-  presShell->GetDocument()->SetNeedStyleFlush();
 }
 
 void
 ServoRestyleManager::PostRestyleEventForLazyConstruction()
 {
   NS_ERROR("stylo: ServoRestyleManager::PostRestyleEventForLazyConstruction not implemented");
 }
 
@@ -132,32 +147,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);
+
+    // XXX Self must imply Subtree, at least for Servo, because of style
+    // structs inheritance. Would that be taken care by the SetStyleContext
+    // call?
+    aHint |= eRestyle_Subtree;
+    // MarkParentsAsHavingDirtyDescendants(aElement);
+  }
+
+  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: detect restyle for animations/transitions/etc, and act properly.
+  //
+  // The cascade levels there are going to be fun, if we keep the actual
+  // mechanism.
+}
+
 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 this if we already have the highest restyle hint in the
+      // subtree.
+      nsRestyleHint hint = styleSet->ComputeRestyleHint(element, snapshot);
+      hint = hint | snapshot->RestyleHint();
+      // nsRestyleHint hint = eRestyle_Self;
+      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);
 
@@ -192,18 +267,36 @@ ServoRestyleManager::ContentStateChanged
 {
   if (!aContent->IsElement()) {
     return NS_OK;
   }
 
   Element* aElement = aContent->AsElement();
   nsChangeHint changeHint;
   nsRestyleHint 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 in
+  // ServoStyleSet), vs lazily (snapshot approach), and if we definitely take
+  // the second approach, take rid of HasStateDependentStyle, etc.
+  //
+  // Also, profile 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.
   ContentStateChangedInternal(aElement, aStateMask, &changeHint, &restyleHint);
 
+  AddElementSnapshot(aElement, ServoElementSnapshot::Flags::State,
+                     restyleHint, changeHint);
+
   PostRestyleEvent(aElement, restyleHint, changeHint);
   return NS_OK;
 }
 
 void
 ServoRestyleManager::AttributeWillChange(Element* aElement,
                                          int32_t aNameSpaceID,
                                          nsIAtom* aAttribute,
@@ -224,9 +317,31 @@ ServoRestyleManager::AttributeChanged(El
 }
 
 nsresult
 ServoRestyleManager::ReparentStyleContext(nsIFrame* aFrame)
 {
   MOZ_CRASH("stylo: ServoRestyleManager::ReparentStyleContext not implemented");
 }
 
+void
+ServoRestyleManager::AddElementSnapshot(Element* aElement,
+                                        ServoElementSnapshot::Flags aWhatToCapture)
+{
+  AddElementSnapshot(aElement,
+                     aWhatToCapture,
+                     nsRestyleHint(0),
+                     nsChangeHint(0));
+}
+
+void
+ServoRestyleManager::AddElementSnapshot(Element* aElement,
+                                        ServoElementSnapshot::Flags aWhatToCapture,
+                                        nsRestyleHint aRestyleHint,
+                                        nsChangeHint aMinChangeHint)
+{
+  ServoElementSnapshot* existingSnapshot = mModifiedElements.LookupOrAdd(aElement);
+  MOZ_ASSERT(existingSnapshot);
+
+  existingSnapshot->Add(aElement, aWhatToCapture, aRestyleHint, aMinChangeHint);
+}
+
 } // 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 "nsISupportsImpl.h"
 #include "nsPresContext.h"
 #include "nsINode.h"
+#include "nsHashKeys.h"
 
 namespace mozilla {
 namespace dom {
 class Element;
 } // namespace dom
 } // namespace mozilla
 class nsAttrValue;
 class nsIAtom;
@@ -63,48 +65,62 @@ public:
   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();
+    // XXX IsEmpty isn't public here, consider implementing it in
+    // nsClassHashTable?
+    return mModifiedElements.Count() != 0;
   }
 
 protected:
   ~ServoRestyleManager() {}
 
 private:
   /**
+   * 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);
+
+  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();
   }
+
+  void AddElementSnapshot(Element* aElement,
+                          ServoElementSnapshot::Flags aWhatToCapture);
+
+  void AddElementSnapshot(Element* aElement,
+                          ServoElementSnapshot::Flags aWhatToCapture,
+                          nsRestyleHint aRestyleHint,
+                          nsChangeHint aMinChangeHint);
 };
 
 } // namespace mozilla
 
 #endif // mozilla_ServoRestyleManager_h
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -97,21 +97,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();
 }
 
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -7,16 +7,17 @@
 #ifndef mozilla_ServoBindings_h
 #define mozilla_ServoBindings_h
 
 #include "stdint.h"
 #include "nsColor.h"
 #include "nsStyleStruct.h"
 #include "mozilla/css/SheetParsingMode.h"
 #include "nsProxyRelease.h"
+#include "nsChangeHint.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.
  */
@@ -45,16 +46,17 @@ struct RawServoStyleSet;
 class nsHTMLCSSStyleSheet;
 struct nsStyleList;
 struct nsStyleImage;
 struct nsStyleGradientStop;
 class nsStyleGradient;
 class nsStyleCoord;
 struct nsStyleDisplay;
 struct ServoDeclarationBlock;
+class ServoElementSnapshot;
 
 #define NS_DECL_THREADSAFE_FFI_REFCOUNTING(class_, name_)                     \
   void Gecko_AddRef##name_##ArbitraryThread(class_* aPtr);                    \
   void Gecko_Release##name_##ArbitraryThread(class_* aPtr);
 #define NS_IMPL_THREADSAFE_FFI_REFCOUNTING(class_, name_)                     \
   static_assert(class_::HasThreadSafeRefCnt::value,                           \
                 "NS_DECL_THREADSAFE_FFI_REFCOUNTING can only be used with "   \
                 "classes that have thread-safe refcounting");                 \
@@ -245,16 +247,22 @@ 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);
+
+uint32_t Servo_StyleWorkerThreadCount();
+
 // 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);
 #include "nsStyleStructList.h"
new file mode 100644
--- /dev/null
+++ b/layout/style/ServoElementSnapshot.cpp
@@ -0,0 +1,65 @@
+/* -*- 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 {
+
+ServoElementSnapshot::ServoElementSnapshot(Element* aElement,
+                                           Flags aWhatToCapture)
+  : mContains(Flags(0))
+{
+  MOZ_ASSERT(aWhatToCapture & Flags::All,
+             "Huh, nothing to snapshot?");
+
+  Add(aElement, aWhatToCapture, nsRestyleHint(0), nsChangeHint(0));
+
+  MOZ_ASSERT(mContains == aWhatToCapture, "What happened here?");
+}
+
+void ServoElementSnapshot::Add(Element* aElement,
+                               Flags aWhatToCapture,
+                               nsRestyleHint aRestyleHint,
+                               nsChangeHint aMinChangeHint)
+{
+  if (aWhatToCapture & Flags::State) {
+    AddState(aElement);
+  }
+
+  if (aWhatToCapture & Flags::Attributes) {
+    AddAttrs(aElement);
+  }
+
+  mExplicitChangeHint |= aMinChangeHint;
+  mExplicitRestyleHint |= aRestyleHint;
+}
+
+void
+ServoElementSnapshot::AddState(Element* aElement)
+{
+  MOZ_ASSERT(aElement);
+  if (!HasAny(Flags::State)) {
+    mState = aElement->StyleState().ServoValue();
+    mContains |= Flags::State;
+  }
+}
+
+void
+ServoElementSnapshot::AddAttrs(Element* aElement)
+{
+  MOZ_ASSERT(aElement);
+  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,175 @@
+/* -*- 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 "nsAttrName.h"
+#include "nsAttrValue.h"
+#include "nsChangeHint.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;
+
+  explicit ServoAttrSnapshot(const nsAttrName& aName,
+                             const nsAttrValue& aValue)
+    : mName(aName)
+    , mValue(aValue)
+  {}
+};
+
+/**
+ * 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 mozilla::dom::Element Element;
+  typedef EventStates::ServoType ServoStateType;
+public:
+  /**
+   * A bitflags enum class used to determine what data does a ServoElementSnapshot
+   * contain, if either only State, only Attributes, or everything.
+   */
+  class Flags {
+  public:
+    typedef uint8_t InternalType;
+
+    enum InternalTypeEnum : InternalType {
+      No = 0, // XXX None is a macro on Linux.
+      State = 1 << 0,
+      Attributes = 1 << 1,
+      All = State | Attributes
+    };
+
+    /* implicit */
+    Flags(enum InternalTypeEnum aFlags)
+      : mInternal(aFlags)
+    {}
+
+    explicit Flags(InternalType aFlags)
+      : mInternal(aFlags)
+    {}
+
+    Flags
+    operator |(Flags aOther) {
+      return Flags(mInternal | aOther.mInternal);
+    }
+
+    Flags&
+    operator |=(Flags aOther) {
+      mInternal |= aOther.mInternal;
+      return *this;
+    }
+
+    Flags
+    operator &(Flags aOther) {
+      return Flags(mInternal & aOther.mInternal);
+    }
+
+    operator bool() {
+      return mInternal != 0;
+    }
+
+    // FIXME: These are lame.
+    Flags
+    operator |(enum InternalTypeEnum aOther) {
+      return Flags(mInternal | aOther);
+    }
+
+    Flags&
+    operator |=(enum InternalTypeEnum aOther) {
+      mInternal |= aOther;
+      return *this;
+    }
+
+    Flags
+    operator &(enum InternalTypeEnum aOther) {
+      return Flags(mInternal & aOther);
+    }
+
+  private:
+    InternalType mInternal;
+  };
+
+  explicit ServoElementSnapshot(Element* aElement,
+                                Flags aWhatToCapture);
+
+  /**
+   * Empty snapshot, with no data at all.
+   */
+  explicit ServoElementSnapshot()
+    : mContains(0)
+    , mState(0)
+    , mExplicitRestyleHint(nsRestyleHint(0))
+    , mExplicitChangeHint(nsChangeHint(0))
+  {}
+
+  bool HasAttrs() {
+    return HasAny(Flags::Attributes);
+  }
+
+  bool HasState() {
+    return HasAny(Flags::State);
+  }
+
+  void Add(Element* aElement,
+           Flags aWhatToCapture,
+           nsRestyleHint aRestyleHint,
+           nsChangeHint aMinChangeHint);
+
+  /**
+   * Captures the given element state (if not previously captured).
+   *
+   * Equivalent to call Add(aElement, Flags::State).
+   */
+  void AddState(Element* aElement);
+
+  /**
+   * Captures the given element attributes (if not previously captured).
+   *
+   * Equivalent to call Add(aElement, Flags::Attributes).
+   */
+  void AddAttrs(Element* aElement);
+
+  nsRestyleHint RestyleHint() { return mExplicitRestyleHint; }
+
+  nsChangeHint ChangeHint() { return mExplicitChangeHint; }
+
+private:
+  bool HasAny(Flags aFlags) {
+    return mContains & aFlags;
+  }
+
+  // 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;
+};
+
+} // namespace mozilla
+#endif
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -6,16 +6,17 @@
 
 #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 "nsChangeHint.h"
 #include "nsCSSPseudoElements.h"
 #include "nsIAtom.h"
 #include "nsTArray.h"
 
@@ -114,16 +115,25 @@ public:
   nsRestyleHint HasStateDependentStyle(dom::Element* aElement,
                                        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) {
+    // Not yet.
+    return eRestyle_Self;
+  }
+
+  /**
    * 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);
 
 private:
--- 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',
 ]