Bug 1355343: Take all the snapshots into account. r=bholley
authorEmilio Cobos Álvarez <emilio@crisal.io>
Sun, 07 May 2017 16:36:47 +0200
changeset 357644 2150351429b5c9515589facd9f2aefb1e4640842
parent 357643 eaf4e461619e685ef1eefaf31464ad0f1c133448
child 357645 fbe4a19a42e2239a9cf119873509bda67942407f
push id31797
push usercbook@mozilla.com
push dateThu, 11 May 2017 10:44:37 +0000
treeherdermozilla-central@86754a7acc0e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
bugs1355343
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1355343: Take all the snapshots into account. r=bholley I've chosen this approach mainly because there's no other good way to guarantee the model is correct than holding the snapshots alive until a style refresh. What I tried before this (storing them in a sort of "immutable element data") is a pain, since we call into style from the frame constructor and other content notifications, which makes keeping track of which snapshots should be cleared an which shouldn't an insane task. Ideally we'd have a single entry-point for style, but that's not the case right now, and changing that requires pretty non-trivial changes to the frame constructor. MozReview-Commit-ID: FF1KWZv2iBM
dom/base/ElementInlines.h
layout/base/ServoRestyleManager.cpp
layout/base/ServoRestyleManager.h
layout/base/nsIPresShell.h
layout/base/nsIPresShellInlines.h
layout/style/ServoBindingList.h
layout/style/ServoBindings.cpp
layout/style/ServoBindings.h
layout/style/ServoElementSnapshotTable.h
layout/style/ServoStyleSet.cpp
layout/style/ServoStyleSet.h
layout/style/moz.build
layout/style/nsTransitionManager.cpp
--- a/dom/base/ElementInlines.h
+++ b/dom/base/ElementInlines.h
@@ -61,18 +61,17 @@ Element::NoteDirtyDescendantsForServo()
 
   Element* curr = this;
   while (curr && !curr->HasDirtyDescendantsForServo()) {
     curr->SetHasDirtyDescendantsForServo();
     curr = curr->GetFlattenedTreeParentElementForStyle();
   }
 
   if (nsIPresShell* shell = OwnerDoc()->GetShell()) {
-    shell->SetNeedStyleFlush();
-    shell->ObserveStyleFlushes();
+    shell->EnsureStyleFlush();
   }
 
   MOZ_ASSERT(DirtyDescendantsBitIsPropagatedForServo());
 }
 
 #ifdef DEBUG
 inline bool
 Element::DirtyDescendantsBitIsPropagatedForServo()
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -6,16 +6,17 @@
 
 #include "mozilla/ServoRestyleManager.h"
 
 #include "mozilla/DocumentStyleRootIterator.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/ServoStyleSet.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/ChildIterator.h"
+#include "mozilla/dom/ElementInlines.h"
 #include "nsContentUtils.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsPrintfCString.h"
 #include "nsRefreshDriver.h"
 #include "nsStyleChangeList.h"
 
 using namespace mozilla::dom;
 
@@ -368,16 +369,52 @@ ServoRestyleManager::ProcessPostTraversa
 
     for (nsIFrame* f = primaryFrame; f;
          f = GetNextContinuationWithSameStyle(f, oldStyleContext)) {
       f->SetStyleContext(&newContext);
     }
   }
 }
 
+void
+ServoRestyleManager::ClearSnapshots()
+{
+  for (auto iter = mSnapshots.Iter(); !iter.Done(); iter.Next()) {
+    iter.Key()->UnsetFlags(ELEMENT_HAS_SNAPSHOT | ELEMENT_HANDLED_SNAPSHOT);
+    iter.Remove();
+  }
+}
+
+ServoElementSnapshot&
+ServoRestyleManager::SnapshotFor(Element* aElement)
+{
+  MOZ_ASSERT(!mInStyleRefresh);
+
+  // NOTE(emilio): We can handle snapshots from a one-off restyle of those that
+  // we do to restyle stuff for reconstruction, for example.
+  //
+  // It seems to be the case that we always flush in between that happens and
+  // the next attribute change, so we can assert that we haven't handled the
+  // snapshot here yet. If this assertion didn't hold, we'd need to unset that
+  // flag from here too.
+  //
+  // Can't wait to make ProcessPendingRestyles the only entry-point for styling,
+  // so this becomes much easier to reason about. Today is not that day though.
+  MOZ_ASSERT(aElement->HasServoData());
+  MOZ_ASSERT(!aElement->HasFlag(ELEMENT_HANDLED_SNAPSHOT));
+
+  ServoElementSnapshot* snapshot = mSnapshots.LookupOrAdd(aElement, aElement);
+  aElement->SetFlags(ELEMENT_HAS_SNAPSHOT);
+
+  nsIPresShell* presShell = mPresContext->PresShell();
+  presShell->EnsureStyleFlush();
+
+  return *snapshot;
+}
+
 /* static */ nsIFrame*
 ServoRestyleManager::FrameForPseudoElement(const nsIContent* aContent,
                                            nsIAtom* aPseudoTagOrNull)
 {
   MOZ_ASSERT_IF(aPseudoTagOrNull, aContent->IsElement());
   if (!aPseudoTagOrNull) {
     return aContent->GetPrimaryFrame();
   }
@@ -395,16 +432,17 @@ ServoRestyleManager::FrameForPseudoEleme
   return nullptr;
 }
 
 void
 ServoRestyleManager::ProcessPendingRestyles()
 {
   MOZ_ASSERT(PresContext()->Document(), "No document?  Pshaw!");
   MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "Missing a script blocker!");
+  MOZ_ASSERT(!mInStyleRefresh, "Reentrant call?");
 
   if (MOZ_UNLIKELY(!PresContext()->PresShell()->DidInitialize())) {
     // PresShell::FlushPendingNotifications doesn't early-return in the case
     // where the PreShell hasn't yet been initialized (and therefore we haven't
     // yet done the initial style traversal of the DOM tree). We should arguably
     // fix up the callers and assert against this case, but we just detect and
     // handle it for now.
     return;
@@ -425,17 +463,20 @@ ServoRestyleManager::ProcessPendingResty
 
   // Perform the Servo traversal, and the post-traversal if required. We do this
   // in a loop because certain rare paths in the frame constructor (like
   // uninstalling XBL bindings) can trigger additional style validations.
   mInStyleRefresh = true;
   if (mHaveNonAnimationRestyles) {
     ++mAnimationGeneration;
   }
+
   while (styleSet->StyleDocument()) {
+    ClearSnapshots();
+
     // Recreate style contexts, and queue up change hints (which also handle
     // lazy frame construction).
     nsStyleChangeList currentChanges(StyleBackendType::Servo);
     DocumentStyleRootIterator iter(doc);
     while (Element* root = iter.GetNextStyleRoot()) {
       ProcessPostTraversal(root, nullptr, styleSet, currentChanges);
     }
 
@@ -455,16 +496,17 @@ ServoRestyleManager::ProcessPendingResty
       }
       newChanges.Clear();
     }
     mReentrantChanges = nullptr;
 
     IncrementRestyleGeneration();
   }
 
+  ClearSnapshots();
   FlushOverflowChangedTracker();
 
   mHaveNonAnimationRestyles = false;
   mInStyleRefresh = false;
   styleSet->AssertTreeIsClean();
 
   // Note: We are in the scope of |animationsWithDestroyedFrame|, so
   //       |mAnimationsWithDestroyedFrame| is still valid.
@@ -505,24 +547,30 @@ ServoRestyleManager::ContentRemoved(nsIN
 {
   NS_WARNING("stylo: ServoRestyleManager::ContentRemoved not implemented");
 }
 
 void
 ServoRestyleManager::ContentStateChanged(nsIContent* aContent,
                                          EventStates aChangedBits)
 {
+  MOZ_ASSERT(!mInStyleRefresh);
+
   if (!aContent->IsElement()) {
     return;
   }
 
   Element* aElement = aContent->AsElement();
   nsChangeHint changeHint;
   nsRestyleHint restyleHint;
 
+  if (!aElement->HasServoData()) {
+    return;
+  }
+
   // 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
@@ -535,47 +583,53 @@ ServoRestyleManager::ContentStateChanged
   // 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);
 
+  ServoElementSnapshot& snapshot = SnapshotFor(aElement);
   EventStates previousState = aElement->StyleState() ^ aChangedBits;
-  ServoElementSnapshot* snapshot = Servo_Element_GetSnapshot(aElement);
-  if (snapshot) {
-    snapshot->AddState(previousState);
-    PostRestyleEvent(aElement, restyleHint, changeHint);
+  snapshot.AddState(previousState);
+
+  if (Element* parent = aElement->GetFlattenedTreeParentElementForStyle()) {
+    parent->NoteDirtyDescendantsForServo();
   }
+  PostRestyleEvent(aElement, restyleHint, changeHint);
 }
 
 void
 ServoRestyleManager::AttributeWillChange(Element* aElement,
                                          int32_t aNameSpaceID,
                                          nsIAtom* aAttribute, int32_t aModType,
                                          const nsAttrValue* aNewValue)
 {
-  ServoElementSnapshot* snapshot = Servo_Element_GetSnapshot(aElement);
-  if (snapshot) {
-    snapshot->AddAttrs(aElement);
+  MOZ_ASSERT(!mInStyleRefresh);
+
+  if (!aElement->HasServoData()) {
+    return;
+  }
+
+  ServoElementSnapshot& snapshot = SnapshotFor(aElement);
+  snapshot.AddAttrs(aElement);
+
+  if (Element* parent = aElement->GetFlattenedTreeParentElementForStyle()) {
+    parent->NoteDirtyDescendantsForServo();
   }
 }
 
 void
 ServoRestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
                                       nsIAtom* aAttribute, int32_t aModType,
                                       const nsAttrValue* aOldValue)
 {
   MOZ_ASSERT(!mInStyleRefresh);
-
-#ifdef DEBUG
-  ServoElementSnapshot* snapshot = Servo_Element_GetSnapshot(aElement);
-  MOZ_ASSERT_IF(snapshot, snapshot->HasAttrs());
-#endif
+  MOZ_ASSERT_IF(mSnapshots.Get(aElement), mSnapshots.Get(aElement)->HasAttrs());
 
   nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
   if (primaryFrame) {
     primaryFrame->AttributeChanged(aNameSpaceID, aAttribute, aModType);
   }
 
   if (aAttribute == nsGkAtoms::style) {
     PostRestyleEvent(aElement, eRestyle_StyleAttribute, nsChangeHint(0));
--- a/layout/base/ServoRestyleManager.h
+++ b/layout/base/ServoRestyleManager.h
@@ -2,25 +2,21 @@
 /* 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_ServoRestyleManager_h
 #define mozilla_ServoRestyleManager_h
 
-#include "mozilla/DocumentStyleRootIterator.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/RestyleManager.h"
-#include "mozilla/ServoBindings.h"
 #include "mozilla/ServoElementSnapshot.h"
+#include "mozilla/ServoElementSnapshotTable.h"
 #include "nsChangeHint.h"
-#include "nsHashKeys.h"
-#include "nsINode.h"
-#include "nsISupportsImpl.h"
 #include "nsPresContext.h"
 
 namespace mozilla {
 namespace dom {
 class Element;
 } // namespace dom
 } // namespace mozilla
 class nsAttrValue;
@@ -32,17 +28,19 @@ class nsStyleChangeList;
 namespace mozilla {
 
 /**
  * Restyle manager for a Servo-backed style system.
  */
 class ServoRestyleManager : public RestyleManager
 {
   friend class ServoStyleSet;
+
 public:
+  typedef ServoElementSnapshotTable SnapshotTable;
   typedef RestyleManager base_type;
 
   explicit ServoRestyleManager(nsPresContext* aPresContext);
 
   void PostRestyleEvent(dom::Element* aElement,
                         nsRestyleHint aRestyleHint,
                         nsChangeHint aMinChangeHint);
   void PostRestyleEventForLazyConstruction();
@@ -131,16 +129,20 @@ private:
   inline ServoStyleSet* StyleSet() const
   {
     MOZ_ASSERT(PresContext()->StyleSet()->IsServo(),
                "ServoRestyleManager should only be used with a Servo-flavored "
                "style backend");
     return PresContext()->StyleSet()->AsServo();
   }
 
+  const SnapshotTable& Snapshots() const { return mSnapshots; }
+  void ClearSnapshots();
+  ServoElementSnapshot& SnapshotFor(mozilla::dom::Element* aElement);
+
   // We use a separate data structure from nsStyleChangeList because we need a
   // frame to create nsStyleChangeList entries, and the primary frame may not be
   // attached yet.
   struct ReentrantChange {
     nsCOMPtr<nsIContent> mContent;
     nsChangeHint mHint;
   };
   typedef AutoTArray<ReentrantChange, 10> ReentrantChangeList;
@@ -150,13 +152,17 @@ private:
   ReentrantChangeList* mReentrantChanges;
 
   // We use this flag to track if the current restyle contains any non-animation
   // update, which triggers a normal restyle, and so there might be any new
   // transition created later. Therefore, if this flag is true, we need to
   // increase mAnimationGeneration before creating new transitions, so their
   // creation sequence will be correct.
   bool mHaveNonAnimationRestyles = false;
+
+  // A hashtable with the elements that have changed state or attributes, in
+  // order to calculate restyle hints during the traversal.
+  SnapshotTable mSnapshots;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_ServoRestyleManager_h
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -625,16 +625,17 @@ public:
     return mNeedStyleFlush ||
            (mNeedLayoutFlush &&
             aType >= mozilla::FlushType::InterruptibleLayout) ||
            aType >= mozilla::FlushType::Display ||
            mNeedThrottledAnimationFlush ||
            mInFlush;
   }
 
+  inline void EnsureStyleFlush();
   inline void SetNeedStyleFlush();
   inline void SetNeedLayoutFlush();
   inline void SetNeedThrottledAnimationFlush();
 
   bool ObservingStyleFlushes() const { return mObservingStyleFlushes; }
   bool ObservingLayoutFlushes() const { return mObservingLayoutFlushes; }
 
   void ObserveStyleFlushes()
--- a/layout/base/nsIPresShellInlines.h
+++ b/layout/base/nsIPresShellInlines.h
@@ -21,16 +21,23 @@ nsIPresShell::SetNeedStyleFlush()
   if (nsIDocument* doc = mDocument->GetDisplayDocument()) {
     if (nsIPresShell* shell = doc->GetShell()) {
       shell->mNeedStyleFlush = true;
     }
   }
 }
 
 void
+nsIPresShell::EnsureStyleFlush()
+{
+  SetNeedStyleFlush();
+  ObserveStyleFlushes();
+}
+
+void
 nsIPresShell::SetNeedThrottledAnimationFlush()
 {
   mNeedThrottledAnimationFlush = true;
   if (nsIDocument* doc = mDocument->GetDisplayDocument()) {
     if (nsIPresShell* shell = doc->GetShell()) {
       shell->mNeedThrottledAnimationFlush = true;
     }
   }
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -359,21 +359,16 @@ SERVO_BINDING_FUNC(Servo_ComputedValues_
                    mozilla::InheritTarget target)
 
 // Initialize Servo components. Should be called exactly once at startup.
 SERVO_BINDING_FUNC(Servo_Initialize, void,
                    RawGeckoURLExtraData* dummy_url_data)
 // Shut down Servo components. Should be called exactly once at shutdown.
 SERVO_BINDING_FUNC(Servo_Shutdown, void)
 
-// Gets the snapshot for the element. This will return null if the element
-// has never been styled, since snapshotting in that case is wasted work.
-SERVO_BINDING_FUNC(Servo_Element_GetSnapshot, ServoElementSnapshot*,
-                   RawGeckoElementBorrowed element)
-
 // Gets the source style rules for the element. This returns the result via
 // rules, which would include a list of unowned pointers to RawServoStyleRule.
 SERVO_BINDING_FUNC(Servo_Element_GetStyleRuleList, void,
                    RawGeckoElementBorrowed element,
                    RawGeckoServoStyleRuleListBorrowedMut rules)
 
 // Restyle and change hints.
 SERVO_BINDING_FUNC(Servo_NoteExplicitHints, void, RawGeckoElementBorrowed element,
@@ -399,34 +394,40 @@ SERVO_BINDING_FUNC(Servo_HasAuthorSpecif
 // restyles first. The Element and its ancestors may be unstyled, have pending
 // restyles, or be in a display:none subtree. Styles are cached when possible,
 // though caching is not possible within display:none subtrees, and the styles
 // may be invalidated by already-scheduled restyles.
 //
 // The tree must be in a consistent state such that a normal traversal could be
 // performed, and this function maintains that invariant.
 SERVO_BINDING_FUNC(Servo_ResolveStyleLazily, ServoComputedValuesStrong,
-                   RawGeckoElementBorrowed element, nsIAtom* pseudo_tag,
+                   RawGeckoElementBorrowed element,
+                   nsIAtom* pseudo_tag,
+                   const mozilla::ServoElementSnapshotTable* snapshots,
                    RawServoStyleSetBorrowed set)
 
 // Use ServoStyleSet::PrepareAndTraverseSubtree instead of calling this
 // directly
-SERVO_BINDING_FUNC(Servo_TraverseSubtree, bool,
-                   RawGeckoElementBorrowed root, RawServoStyleSetBorrowed set,
+SERVO_BINDING_FUNC(Servo_TraverseSubtree,
+                   bool,
+                   RawGeckoElementBorrowed root,
+                   RawServoStyleSetBorrowed set,
+                   const mozilla::ServoElementSnapshotTable* snapshots,
                    mozilla::TraversalRootBehavior root_behavior,
                    mozilla::TraversalRestyleBehavior restyle_behavior)
 
 // Assert that the tree has no pending or unconsumed restyles.
 SERVO_BINDING_FUNC(Servo_AssertTreeIsClean, void, RawGeckoElementBorrowed root)
 
 // Returns computed values for the given element without any animations rules.
 SERVO_BINDING_FUNC(Servo_StyleSet_GetBaseComputedValuesForElement,
                    ServoComputedValuesStrong,
                    RawServoStyleSetBorrowed set,
                    RawGeckoElementBorrowed element,
+                   const mozilla::ServoElementSnapshotTable* snapshots,
                    nsIAtom* pseudo_tag)
 
 // Style-struct management.
 #define STYLE_STRUCT(name, checkdata_cb)                            \
   struct nsStyle##name;                                             \
   SERVO_BINDING_FUNC(Servo_GetStyle##name, const nsStyle##name*,  \
                      ServoComputedValuesBorrowedOrNull computed_values)
 #include "nsStyleStructList.h"
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -278,18 +278,17 @@ Gecko_UnsetNodeFlags(RawGeckoNodeBorrowe
 }
 
 void
 Gecko_SetOwnerDocumentNeedsStyleFlush(RawGeckoElementBorrowed aElement)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (nsIPresShell* shell = aElement->OwnerDoc()->GetShell()) {
-    shell->SetNeedStyleFlush();
-    shell->ObserveStyleFlushes();
+    shell->EnsureStyleFlush();
   }
 }
 
 nsStyleContext*
 Gecko_GetStyleContext(RawGeckoElementBorrowed aElement,
                       nsIAtom* aPseudoTagOrNull)
 {
   nsIFrame* relevantFrame =
@@ -340,37 +339,24 @@ Gecko_CalcStyleDifference(nsStyleContext
 }
 
 nsChangeHint
 Gecko_HintsHandledForDescendants(nsChangeHint aHint)
 {
   return aHint & ~NS_HintsNotHandledForDescendantsIn(aHint);
 }
 
-ServoElementSnapshotOwned
-Gecko_CreateElementSnapshot(RawGeckoElementBorrowed aElement)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  return new ServoElementSnapshot(aElement);
-}
-
-void
-Gecko_DropElementSnapshot(ServoElementSnapshotOwned aSnapshot)
+const ServoElementSnapshot*
+Gecko_GetElementSnapshot(const ServoElementSnapshotTable* aTable,
+                         const Element* aElement)
 {
-  // Proxy deletes have a lot of overhead, so Servo tries hard to only drop
-  // snapshots on the main thread. However, there are certain cases where
-  // it's unavoidable (i.e. synchronously dropping the style data for the
-  // descendants of a new display:none root).
-  if (MOZ_UNLIKELY(!NS_IsMainThread())) {
-    nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction([=]() { delete aSnapshot; });
-    SystemGroup::Dispatch("Gecko_DropElementSnapshot", TaskCategory::Other,
-                          task.forget());
-  } else {
-    delete aSnapshot;
-  }
+  MOZ_ASSERT(aTable);
+  MOZ_ASSERT(aElement);
+
+  return aTable->Get(const_cast<Element*>(aElement));
 }
 
 RawServoDeclarationBlockStrongBorrowedOrNull
 Gecko_GetStyleAttrDeclarationBlock(RawGeckoElementBorrowed aElement)
 {
   DeclarationBlock* decl = aElement->GetInlineStyleDeclaration();
   if (!decl) {
     return nullptr;
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -28,28 +28,29 @@
  * Functions beginning with Servo_ are implemented in Servo and invoked from Gecko.
  */
 
 class nsIAtom;
 class nsIPrincipal;
 class nsIURI;
 struct nsFont;
 namespace mozilla {
-  class ServoStyleSheet;
   class FontFamilyList;
   enum FontFamilyType : uint32_t;
   struct Keyframe;
   enum Side;
   struct StyleTransition;
   namespace css {
     struct URLValue;
     struct ImageValue;
   };
   enum class UpdateAnimationsTasks : uint8_t;
   struct LangGroupFontPrefs;
+  class ServoStyleSheet;
+  class ServoElementSnapshotTable;
 }
 using mozilla::FontFamilyList;
 using mozilla::FontFamilyType;
 using mozilla::ServoElementSnapshot;
 class nsCSSFontFaceRule;
 struct nsMediaFeature;
 struct nsStyleList;
 struct nsStyleImage;
@@ -314,18 +315,21 @@ void Gecko_SetOwnerDocumentNeedsStyleFlu
 // Not if we do them in Gecko...
 nsStyleContext* Gecko_GetStyleContext(RawGeckoElementBorrowed element,
                                       nsIAtom* aPseudoTagOrNull);
 nsIAtom* Gecko_GetImplementedPseudo(RawGeckoElementBorrowed element);
 nsChangeHint Gecko_CalcStyleDifference(nsStyleContext* oldstyle,
                                        ServoComputedValuesBorrowed newstyle);
 nsChangeHint Gecko_HintsHandledForDescendants(nsChangeHint aHint);
 
-// Element snapshot.
-ServoElementSnapshotOwned Gecko_CreateElementSnapshot(RawGeckoElementBorrowed element);
+// Get an element snapshot for a given element from the table.
+const ServoElementSnapshot*
+Gecko_GetElementSnapshot(const mozilla::ServoElementSnapshotTable* table,
+                         RawGeckoElementBorrowed element);
+
 void Gecko_DropElementSnapshot(ServoElementSnapshotOwned snapshot);
 
 // `array` must be an nsTArray
 // If changing this signature, please update the
 // friend function declaration in nsTArray.h
 void Gecko_EnsureTArrayCapacity(void* array, size_t capacity, size_t elem_size);
 
 // Same here, `array` must be an nsTArray<T>, for some T.
new file mode 100644
--- /dev/null
+++ b/layout/style/ServoElementSnapshotTable.h
@@ -0,0 +1,24 @@
+/* -*- 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_ServoElementSnapshotTable_h
+#define mozilla_ServoElementSnapshotTable_h
+
+#include "mozilla/dom/Element.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "ServoElementSnapshot.h"
+
+namespace mozilla {
+
+class ServoElementSnapshotTable
+  : public nsClassHashtable<nsRefPtrHashKey<dom::Element>, ServoElementSnapshot>
+{
+};
+
+} // namespace mozilla
+
+#endif
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -8,16 +8,17 @@
 
 #include "gfxPlatformFontList.h"
 #include "mozilla/DocumentStyleRootIterator.h"
 #include "mozilla/ServoRestyleManager.h"
 #include "mozilla/dom/AnonymousContent.h"
 #include "mozilla/dom/ChildIterator.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ElementInlines.h"
+#include "mozilla/RestyleManagerInlines.h"
 #include "mozilla/ServoComputedValuesWithParent.h"
 #include "nsCSSAnonBoxes.h"
 #include "nsCSSPseudoElements.h"
 #include "nsCSSRuleProcessor.h"
 #include "nsDeviceContext.h"
 #include "nsHTMLStyleSheet.h"
 #include "nsIDocumentInlines.h"
 #include "nsPrintfCString.h"
@@ -232,16 +233,22 @@ ServoStyleSet::GetContext(already_AddRef
     if (doc && doc->GetBodyElement() == aElementForAnimation) {
       // Update the prescontext's body color
       mPresContext->SetBodyTextColor(result->StyleColor()->mColor);
     }
   }
   return result.forget();
 }
 
+const ServoElementSnapshotTable&
+ServoStyleSet::Snapshots()
+{
+  return mPresContext->RestyleManager()->AsServo()->Snapshots();
+}
+
 void
 ServoStyleSet::ResolveMappedAttrDeclarationBlocks()
 {
   if (nsHTMLStyleSheet* sheet = mPresContext->Document()->GetAttributeStyleSheet()) {
     sheet->CalculateMappedServoDeclarations(mPresContext);
   }
 
   mPresContext->Document()->ResolveScheduledSVGPresAttrs();
@@ -280,49 +287,51 @@ ServoStyleSet::PreTraverse(Element* aRoo
     mPresContext->EffectCompositor()->PreTraverse();
     if (smilController) {
       smilController->PreTraverse();
     }
   }
 }
 
 bool
-ServoStyleSet::PrepareAndTraverseSubtree(RawGeckoElementBorrowed aRoot,
-                                         TraversalRootBehavior aRootBehavior,
-                                         TraversalRestyleBehavior
-                                           aRestyleBehavior)
+ServoStyleSet::PrepareAndTraverseSubtree(
+  RawGeckoElementBorrowed aRoot,
+  TraversalRootBehavior aRootBehavior,
+  TraversalRestyleBehavior aRestyleBehavior)
 {
   // Get the Document's root element to ensure that the cache is valid before
   // calling into the (potentially-parallel) Servo traversal, where a cache hit
   // is necessary to avoid a data race when updating the cache.
   mozilla::Unused << aRoot->OwnerDoc()->GetRootElement();
 
   AutoSetInServoTraversal guard(this);
 
+  const SnapshotTable& snapshots = Snapshots();
+
   bool isInitial = !aRoot->HasServoData();
   bool forReconstruct =
     aRestyleBehavior == TraversalRestyleBehavior::ForReconstruct;
-  bool postTraversalRequired =
-    Servo_TraverseSubtree(aRoot, mRawSet.get(), aRootBehavior, aRestyleBehavior);
+  bool postTraversalRequired = Servo_TraverseSubtree(
+    aRoot, mRawSet.get(), &snapshots, aRootBehavior, aRestyleBehavior);
   MOZ_ASSERT_IF(isInitial || forReconstruct, !postTraversalRequired);
 
   auto root = const_cast<Element*>(aRoot);
 
   // If there are still animation restyles needed, trigger a second traversal to
   // update CSS animations or transitions' styles.
   //
   // We don't need to do this for SMIL since SMIL only updates its animation
   // values once at the begin of a tick. As a result, even if the previous
   // traversal caused, for example, the font-size to change, the SMIL style
   // won't be updated until the next tick anyway.
   EffectCompositor* compositor = mPresContext->EffectCompositor();
   if (forReconstruct ? compositor->PreTraverseInSubtree(root)
                      : compositor->PreTraverse()) {
-    if (Servo_TraverseSubtree(aRoot, mRawSet.get(),
-                              aRootBehavior, aRestyleBehavior)) {
+    if (Servo_TraverseSubtree(
+          aRoot, mRawSet.get(), &snapshots, aRootBehavior, aRestyleBehavior)) {
       MOZ_ASSERT(!forReconstruct);
       if (isInitial) {
         // We're doing initial styling, and the additional animation
         // traversal changed the styles that were set by the first traversal.
         // This would normally require a post-traversal to update the style
         // contexts, and the DOM now has dirty descendant bits and RestyleData
         // in expectation of that post-traversal. But since this is actually
         // the initial styling, there are no style contexts to update and no
@@ -943,16 +952,17 @@ ServoStyleSet::GetComputedKeyframeValues
 }
 
 already_AddRefed<ServoComputedValues>
 ServoStyleSet::GetBaseComputedValuesForElement(Element* aElement,
                                                nsIAtom* aPseudoTag)
 {
   return Servo_StyleSet_GetBaseComputedValuesForElement(mRawSet.get(),
                                                         aElement,
+                                                        &Snapshots(),
                                                         aPseudoTag).Consume();
 }
 
 already_AddRefed<RawServoAnimationValue>
 ServoStyleSet::ComputeAnimationValue(
   RawServoDeclarationBlock* aDeclarations,
   const ServoComputedValuesWithParent& aComputedValues)
 {
@@ -1014,22 +1024,24 @@ ServoStyleSet::ResolveStyleLazily(Elemen
       elementForStyleResolution = pseudo;
       pseudoTagForStyleResolution = nullptr;
     }
   }
 
   RefPtr<ServoComputedValues> computedValues =
     Servo_ResolveStyleLazily(elementForStyleResolution,
                              pseudoTagForStyleResolution,
+                             &Snapshots(),
                              mRawSet.get()).Consume();
 
   if (mPresContext->EffectCompositor()->PreTraverse(aElement, aPseudoTag)) {
     computedValues =
       Servo_ResolveStyleLazily(elementForStyleResolution,
                                pseudoTagForStyleResolution,
+                               &Snapshots(),
                                mRawSet.get()).Consume();
   }
 
   return computedValues.forget();
 }
 
 bool
 ServoStyleSet::AppendFontFaceRules(nsTArray<nsFontFaceRuleContainer>& aArray)
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -27,16 +27,17 @@ namespace mozilla {
 namespace dom {
 class Element;
 } // namespace dom
 class CSSStyleSheet;
 class ServoRestyleManager;
 class ServoStyleSheet;
 struct Keyframe;
 struct ServoComputedValuesWithParent;
+class ServoElementSnapshotTable;
 } // namespace mozilla
 class nsIContent;
 class nsIDocument;
 class nsStyleContext;
 class nsPresContext;
 struct nsTimingFunction;
 struct RawServoRuleNode;
 struct TreeMatchContext;
@@ -45,16 +46,18 @@ namespace mozilla {
 
 /**
  * The set of style sheets that apply to a document, backed by a Servo
  * Stylist.  A ServoStyleSet contains ServoStyleSheets.
  */
 class ServoStyleSet
 {
   friend class ServoRestyleManager;
+  typedef ServoElementSnapshotTable SnapshotTable;
+
 public:
   class AutoAllowStaleStyles
   {
   public:
     explicit AutoAllowStaleStyles(ServoStyleSet* aStyleSet)
       : mStyleSet(aStyleSet)
     {
       if (mStyleSet) {
@@ -230,19 +233,22 @@ public:
   nsRestyleHint HasStateDependentStyle(dom::Element* aElement,
                                        EventStates aStateMask);
   nsRestyleHint HasStateDependentStyle(
     dom::Element* aElement, mozilla::CSSPseudoElementType aPseudoType,
     dom::Element* aPseudoElement, EventStates aStateMask);
 
   /**
    * Performs a Servo traversal to compute style for all dirty nodes in the
-   * document.  This will traverse all of the document's style roots (that
-   * is, its document element, and the roots of the document-level native
-   * anonymous content).  Returns true if a post-traversal is required.
+   * document.
+   *
+   * This will traverse all of the document's style roots (that is, its document
+   * element, and the roots of the document-level native anonymous content).
+   *
+   * Returns true if a post-traversal is required.
    */
   bool StyleDocument();
 
   /**
    * Eagerly styles a subtree of unstyled nodes that was just appended to the
    * tree. This is used in situations where we need the style immediately and
    * cannot wait for a future batch restyle.
    */
@@ -366,34 +372,44 @@ private:
 
   already_AddRefed<nsStyleContext> GetContext(nsIContent* aContent,
                                               nsStyleContext* aParentContext,
                                               nsIAtom* aPseudoTag,
                                               CSSPseudoElementType aPseudoType,
                                               LazyComputeBehavior aMayCompute);
 
   /**
+   * Gets the pending snapshots to handle from the restyle manager.
+   */
+  const SnapshotTable& Snapshots();
+
+  /**
    * Resolve all ServoDeclarationBlocks attached to mapped
    * presentation attributes cached on the document.
+   *
    * Call this before jumping into Servo's style system.
    */
   void ResolveMappedAttrDeclarationBlocks();
 
   /**
    * Perform all lazy operations required before traversing
-   * a subtree.  Returns whether a post-traversal is required.
+   * a subtree.
+   *
+   * Returns whether a post-traversal is required.
    */
   bool PrepareAndTraverseSubtree(RawGeckoElementBorrowed aRoot,
                                  TraversalRootBehavior aRootBehavior,
                                  TraversalRestyleBehavior aRestyleBehavior);
 
   /**
-   * Clear our cached mNonInheritingStyleContexts.  We do this when we want to
-   * make sure those style contexts won't live too long (e.g. when rebuilding
-   * all style data or when shutting down the style set).
+   * Clear our cached mNonInheritingStyleContexts.
+   *
+   * We do this when we want to make sure those style contexts won't live too
+   * long (e.g. when rebuilding all style data or when shutting down the style
+   * set).
    */
   void ClearNonInheritingStyleContexts();
 
   /**
    * Perform processes that we should do before traversing.
    *
    * When aRoot is null, the entire document is pre-traversed.  Otherwise,
    * only the subtree rooted at aRoot is pre-traversed.
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -99,16 +99,17 @@ EXPORTS.mozilla += [
     'RuleProcessorCache.h',
     'ServoArcTypeList.h',
     'ServoBindingList.h',
     'ServoBindings.h',
     'ServoBindingTypes.h',
     'ServoCSSRuleList.h',
     'ServoDeclarationBlock.h',
     'ServoElementSnapshot.h',
+    'ServoElementSnapshotTable.h',
     'ServoMediaList.h',
     'ServoMediaRule.h',
     'ServoNamespaceRule.h',
     'ServoPageRule.h',
     'ServoPropPrefList.h',
     'ServoSpecifiedValues.h',
     'ServoStyleRule.h',
     'ServoStyleSet.h',
--- a/layout/style/nsTransitionManager.cpp
+++ b/layout/style/nsTransitionManager.cpp
@@ -17,16 +17,17 @@
 #include "mozilla/TimeStamp.h"
 #include "nsRefreshDriver.h"
 #include "nsRuleProcessorData.h"
 #include "nsRuleWalker.h"
 #include "nsCSSPropertyIDSet.h"
 #include "mozilla/EffectCompositor.h"
 #include "mozilla/EffectSet.h"
 #include "mozilla/EventDispatcher.h"
+#include "mozilla/ServoBindings.h"
 #include "mozilla/ServoComputedValuesWithParent.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/dom/DocumentTimeline.h"
 #include "mozilla/dom/Element.h"
 #include "nsIFrame.h"
 #include "Layers.h"
 #include "FrameLayerBuilder.h"
 #include "nsCSSProps.h"