Bug 1384824 - Part 1: Lazily clear stale Servo element data from a document when its pres shell changes. r?bholley draft
authorCameron McCormack <cam@mcc.id.au>
Thu, 03 Aug 2017 12:08:14 +0800
changeset 620529 97c87a1b4ea145a12164da19322754b1f6a50963
parent 620338 63e261ce8cb04c913d2e6b19ea451b7078d24dc1
child 620530 1e51d9856aac755a93e049f0e30a47595e3152a5
push id72066
push userbmo:cam@mcc.id.au
push dateThu, 03 Aug 2017 14:38:23 +0000
reviewersbholley
bugs1384824
milestone57.0a1
Bug 1384824 - Part 1: Lazily clear stale Servo element data from a document when its pres shell changes. r?bholley MozReview-Commit-ID: 1c566PRqFpe
dom/base/nsDocument.cpp
dom/base/nsIDocument.h
dom/html/nsGenericHTMLElement.cpp
layout/style/ServoBindingList.h
layout/style/ServoStyleSet.cpp
layout/style/ServoStyleSet.h
layout/style/StyleSetHandle.h
layout/style/StyleSetHandleInlines.h
layout/style/nsComputedDOMStyle.cpp
layout/style/nsStyleSet.h
servo/components/style/traversal.rs
servo/ports/geckolib/glue.rs
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -272,16 +272,18 @@
 #include "nsISpeculativeConnect.h"
 
 #include "mozilla/MediaManager.h"
 #ifdef MOZ_WEBRTC
 #include "IPeerConnection.h"
 #endif // MOZ_WEBRTC
 
 #include "nsIURIClassifier.h"
+#include "mozilla/DocumentStyleRootIterator.h"
+#include "mozilla/ServoRestyleManager.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 typedef nsTArray<Link*> LinkArray;
 
 static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
 static LazyLogModule gCspPRLog("CSP");
@@ -1345,16 +1347,17 @@ nsIDocument::nsIDocument()
     mFontFaceSetDirty(true),
     mGetUserFontSetCalled(false),
     mPostedFlushUserFontSet(false),
     mDidFireDOMContentLoaded(true),
     mHasScrollLinkedEffect(false),
     mFrameRequestCallbacksScheduled(false),
     mIsTopLevelContentDocument(false),
     mIsContentDocument(false),
+    mMightHaveStaleServoData(false),
     mIsScopedStyleEnabled(eScopedStyle_Unknown),
     mCompatMode(eCompatibility_FullStandards),
     mReadyState(ReadyState::READYSTATE_UNINITIALIZED),
     mStyleBackendType(StyleBackendType::None),
 #ifdef MOZILLA_INTERNAL_API
     mVisibilityState(dom::VisibilityState::Hidden),
 #else
     mDummy(0),
@@ -3878,16 +3881,21 @@ nsDocument::CreateShell(nsPresContext* a
                         StyleSetHandle aStyleSet)
 {
   NS_ASSERTION(!mPresShell, "We have a presshell already!");
 
   NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr);
 
   FillStyleSet(aStyleSet);
 
+  if (aStyleSet->IsServo()) {
+    // Ensure we start with no stale data in the tree.
+    ClearStaleServoDataFromDocument();
+  }
+
   RefPtr<PresShell> shell = new PresShell;
   shell->Init(this, aContext, aViewManager, aStyleSet);
 
   // Note: we don't hold a ref to the shell (it holds a ref to us)
   mPresShell = shell;
 
   // Make sure to never paint if we belong to an invisible DocShell.
   nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
@@ -3997,16 +4005,22 @@ nsDocument::DeleteShell()
   // Now that we no longer have a shell, we need to forget about any FontFace
   // objects for @font-face rules that came from the style set.
   RebuildUserFontSet();
 
   nsIPresShell* oldShell = mPresShell;
   mPresShell = nullptr;
   UpdateFrameRequestCallbackSchedulingState(oldShell);
   mStyleSetFilled = false;
+
+  // Record that the tree might have stale Servo element data in it
+  // that would need to be cleared if we ever get a new pres shell
+  // or if we call ServoStyleSet style resolving functions on
+  // elements in the document.
+  mMightHaveStaleServoData = true;
 }
 
 static void
 SubDocClearEntry(PLDHashTable *table, PLDHashEntryHdr *entry)
 {
   SubDocMapEntry *e = static_cast<SubDocMapEntry *>(entry);
 
   NS_RELEASE(e->mKey);
@@ -13524,8 +13538,22 @@ nsIDocument::IsScopedStyleEnabled()
     mIsScopedStyleEnabled = nsContentUtils::IsChromeDoc(this) ||
                             IsAboutReader(mDocumentURI) ||
                             nsContentUtils::IsScopedStylePrefEnabled()
                               ? eScopedStyle_Enabled
                               : eScopedStyle_Disabled;
   }
   return mIsScopedStyleEnabled == eScopedStyle_Enabled;
 }
+
+void
+nsIDocument::ClearStaleServoDataFromDocument()
+{
+  if (!mMightHaveStaleServoData) {
+    return;
+  }
+
+  DocumentStyleRootIterator iter(this);
+  while (Element* root = iter.GetNextStyleRoot()) {
+    ServoRestyleManager::ClearServoDataFromSubtree(root);
+  }
+  mMightHaveStaleServoData = false;
+}
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -855,16 +855,21 @@ public:
       mozilla::StyleSetHandle aStyleSet) = 0;
   virtual void DeleteShell() = 0;
 
   nsIPresShell* GetShell() const
   {
     return GetBFCacheEntry() ? nullptr : mPresShell;
   }
 
+  bool HasShellOrBFCacheEntry() const
+  {
+    return mPresShell || mBFCacheEntry;
+  }
+
   // Instead using this method, what you probably want is
   // RemoveFromBFCacheSync() as we do in MessagePort and BroadcastChannel.
   void DisallowBFCaching()
   {
     NS_ASSERTION(!mBFCacheEntry, "We're already in the bfcache!");
     mBFCacheDisallowed = true;
   }
 
@@ -1697,16 +1702,28 @@ public:
 
   bool IsContentDocument() const { return mIsContentDocument; }
   void SetIsContentDocument(bool aIsContentDocument)
   {
     mIsContentDocument = aIsContentDocument;
   }
 
   /**
+   * Checks if this document has no pres shell, and if so, clears any Servo
+   * element data stored on Elements in the document.
+   */
+  void ClearStaleServoDataFromDocument();
+
+  /**
+   * Returns true if there may be Servo element data on Elements in the document
+   * that were created for a pres shell that no longer exists.
+   */
+  bool MightHaveStaleServoData() const { return mMightHaveStaleServoData; }
+
+  /**
    * Create an element with the specified name, prefix and namespace ID.
    * Returns null if element name parsing failed.
    */
   virtual already_AddRefed<Element> CreateElem(const nsAString& aName,
                                                nsIAtom* aPrefix,
                                                int32_t aNamespaceID,
                                                const nsAString* aIs = nullptr) = 0;
 
@@ -3280,16 +3297,20 @@ protected:
   // This should generally be updated only via
   // UpdateFrameRequestCallbackSchedulingState.
   bool mFrameRequestCallbacksScheduled : 1;
 
   bool mIsTopLevelContentDocument : 1;
 
   bool mIsContentDocument : 1;
 
+  // True if there may be Servo element data on Elements in the document that
+  // were created for a pres shell that no longer exists.
+  bool mMightHaveStaleServoData : 1;
+
   // Whether <style scoped> support is enabled in this document.
   enum { eScopedStyle_Unknown, eScopedStyle_Disabled, eScopedStyle_Enabled };
   unsigned int mIsScopedStyleEnabled : 2;
 
   // Compatibility mode
   nsCompatibility mCompatMode;
 
   // Our readyState
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -2973,16 +2973,21 @@ nsGenericHTMLElement::NewURIFromString(c
   }
 
   return NS_OK;
 }
 
 static bool
 IsOrHasAncestorWithDisplayNone(Element* aElement, nsIPresShell* aPresShell)
 {
+  // Let's pretend everything is display:none if we're in the bfcache.
+  if (aElement->OwnerDoc()->GetBFCacheEntry()) {
+    return true;
+  }
+
   AutoTArray<Element*, 10> elementsToCheck;
   // Style and layout work on the flattened tree, so this is what we need to
   // check in order to figure out whether we're in a display: none subtree.
   for (Element* e = aElement; e; e = e->GetFlattenedTreeParentElement()) {
     if (e->GetPrimaryFrame()) {
       // e definitely isn't display:none and doesn't have a display:none
       // ancestor.
       break;
@@ -2993,17 +2998,20 @@ IsOrHasAncestorWithDisplayNone(Element* 
   if (elementsToCheck.IsEmpty()) {
     return false;
   }
 
   StyleSetHandle styleSet = aPresShell->StyleSet();
   RefPtr<nsStyleContext> sc;
   for (auto* element : Reversed(elementsToCheck)) {
     if (sc) {
-      sc = styleSet->ResolveStyleFor(element, sc, LazyComputeBehavior::Assert);
+      // Call ResolveCleanStyleFor to protect against stale element data in the
+      // tree when styled by Servo.
+      sc = styleSet->ResolveCleanStyleFor(element, sc,
+                                          LazyComputeBehavior::Assert);
     } else {
       sc = nsComputedDOMStyle::GetStyleContextNoFlush(element,
                                                       nullptr, aPresShell);
     }
     if (sc->StyleDisplay()->mDisplay == StyleDisplay::None) {
       return true;
     }
   }
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -534,17 +534,18 @@ SERVO_BINDING_FUNC(Servo_HasAuthorSpecif
 //
 // 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, ServoStyleContextStrong,
                    RawGeckoElementBorrowed element,
                    mozilla::CSSPseudoElementType pseudo_type,
                    mozilla::StyleRuleInclusion rule_inclusion,
                    const mozilla::ServoElementSnapshotTable* snapshots,
-                   RawServoStyleSetBorrowed set)
+                   RawServoStyleSetBorrowed set,
+                   bool ignore_existing_styles)
 
 // Reparents style to the new parents.
 SERVO_BINDING_FUNC(Servo_ReparentStyle, ServoStyleContextStrong,
                    ServoStyleContextBorrowed style_to_reparent,
                    ServoStyleContextBorrowed parent_style,
                    ServoStyleContextBorrowed parent_style_ignoring_first_line,
                    ServoStyleContextBorrowed layout_parent_style,
                    // element is null if there is no content node involved, or
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -180,16 +180,59 @@ ServoStyleSet::ResolveStyleFor(Element* 
     PreTraverseSync();
     return ResolveStyleLazily(
         aElement, CSSPseudoElementType::NotPseudo, nullptr, aParentContext);
   }
 
   return ResolveServoStyle(aElement, ServoTraversalFlags::Empty);
 }
 
+/**
+ * Clears any stale Servo element data that might existing in the specified
+ * element's document.  Upon destruction, asserts that the element and all
+ * its ancestors still have no element data, if the document has no pres shell.
+ */
+class MOZ_STACK_CLASS AutoClearStaleData
+{
+public:
+  explicit AutoClearStaleData(Element* aElement)
+    : mElement(aElement)
+  {
+    aElement->OwnerDoc()->ClearStaleServoDataFromDocument();
+  }
+
+  ~AutoClearStaleData()
+  {
+#ifdef DEBUG
+    // Assert that the element and its ancestors are all unstyled, if the
+    // document has no pres shell.
+    if (mElement->OwnerDoc()->HasShellOrBFCacheEntry()) {
+      return;
+    }
+    for (Element* e = mElement; e; e = e->GetParentElement()) {
+      MOZ_ASSERT(!e->HasServoData(), "expected element to be unstyled");
+    }
+#endif
+  }
+
+private:
+  Element* mElement;
+};
+
+already_AddRefed<ServoStyleContext>
+ServoStyleSet::ResolveCleanStyleFor(Element* aElement,
+                                    ServoStyleContext* aParentContext,
+                                    LazyComputeBehavior aMayCompute)
+{
+  // We don't handle ignoring existing element data for bfcached documents.
+  MOZ_ASSERT(!aElement->OwnerDoc()->GetBFCacheEntry());
+
+  AutoClearStaleData guard(aElement);
+  return ResolveStyleFor(aElement, aParentContext, aMayCompute);
+}
 
 const ServoElementSnapshotTable&
 ServoStyleSet::Snapshots()
 {
   return mPresContext->RestyleManager()->AsServo()->Snapshots();
 }
 
 void
@@ -426,30 +469,33 @@ ServoStyleSet::ResolvePseudoElementStyle
 }
 
 already_AddRefed<ServoStyleContext>
 ServoStyleSet::ResolveTransientStyle(Element* aElement,
                                      CSSPseudoElementType aPseudoType,
                                      nsIAtom* aPseudoTag,
                                      StyleRuleInclusion aRuleInclusion)
 {
-  RefPtr<ServoStyleContext> result =
-    ResolveTransientServoStyle(aElement, aPseudoType, aPseudoTag, aRuleInclusion);
-  return result.forget();
+  return ResolveTransientServoStyle(aElement, aPseudoType, aPseudoTag,
+                                    aRuleInclusion);
 }
 
 already_AddRefed<ServoStyleContext>
 ServoStyleSet::ResolveTransientServoStyle(
     Element* aElement,
     CSSPseudoElementType aPseudoType,
     nsIAtom* aPseudoTag,
     StyleRuleInclusion aRuleInclusion)
 {
+  bool ignoreExistingStyles = aElement->OwnerDoc()->GetBFCacheEntry();
+
+  AutoClearStaleData guard(aElement);
   PreTraverseSync();
-  return ResolveStyleLazily(aElement, aPseudoType, aPseudoTag, nullptr, aRuleInclusion);
+  return ResolveStyleLazily(aElement, aPseudoType, aPseudoTag, nullptr,
+                            aRuleInclusion, ignoreExistingStyles);
 }
 
 already_AddRefed<ServoStyleContext>
 ServoStyleSet::ResolveInheritingAnonymousBoxStyle(nsIAtom* aPseudoTag,
                                                   ServoStyleContext* aParentContext)
 {
   MOZ_ASSERT(nsCSSAnonBoxes::IsAnonBox(aPseudoTag) &&
              !nsCSSAnonBoxes::IsNonInheritingAnonBox(aPseudoTag));
@@ -902,16 +948,21 @@ ServoStyleSet::GetKeyframesForName(const
 }
 
 nsTArray<ComputedKeyframeValues>
 ServoStyleSet::GetComputedKeyframeValuesFor(
   const nsTArray<Keyframe>& aKeyframes,
   Element* aElement,
   const ServoStyleContext* aContext)
 {
+  // Servo_GetComputedKeyframeValues below won't handle ignoring existing
+  // element data for bfcached documents.
+  MOZ_ASSERT(!aElement->OwnerDoc()->GetBFCacheEntry());
+
+  AutoClearStaleData guard(aElement);
   nsTArray<ComputedKeyframeValues> result(aKeyframes.Length());
 
   // Construct each nsTArray<PropertyStyleAnimationValuePair> here.
   result.AppendElements(aKeyframes.Length());
 
   Servo_GetComputedKeyframeValues(&aKeyframes,
                                   aElement,
                                   aContext,
@@ -922,45 +973,60 @@ ServoStyleSet::GetComputedKeyframeValues
 
 void
 ServoStyleSet::GetAnimationValues(
   RawServoDeclarationBlock* aDeclarations,
   Element* aElement,
   const ServoStyleContext* aStyleContext,
   nsTArray<RefPtr<RawServoAnimationValue>>& aAnimationValues)
 {
+  // Servo_GetAnimationValues below won't handle ignoring existing element
+  // data for bfcached documents.
+  MOZ_ASSERT(!aElement->OwnerDoc()->GetBFCacheEntry());
+
+  AutoClearStaleData guard(aElement);
   Servo_GetAnimationValues(aDeclarations,
                            aElement,
                            aStyleContext,
                            mRawSet.get(),
                            &aAnimationValues);
 }
 
 already_AddRefed<ServoStyleContext>
 ServoStyleSet::GetBaseContextForElement(
   Element* aElement,
   ServoStyleContext* aParentContext,
   nsPresContext* aPresContext,
   nsIAtom* aPseudoTag,
   CSSPseudoElementType aPseudoType,
   const ServoStyleContext* aStyle)
 {
+  MOZ_ASSERT(!aElement->OwnerDoc()->GetBFCacheEntry(),
+             "GetBaseContextForElement does not support documents in the "
+             "bfcache");
+
+  AutoClearStaleData guard(aElement);
   return Servo_StyleSet_GetBaseComputedValuesForElement(mRawSet.get(),
                                                         aElement,
                                                         aStyle,
                                                         &Snapshots(),
                                                         aPseudoType).Consume();
 }
 
 already_AddRefed<RawServoAnimationValue>
 ServoStyleSet::ComputeAnimationValue(
   Element* aElement,
   RawServoDeclarationBlock* aDeclarations,
   const ServoStyleContext* aContext)
 {
+  // Servo_AnimationValue_Compute below won't handle ignoring existing element
+  // data for bfcached documents.
+  MOZ_ASSERT(!aElement->OwnerDoc()->GetBFCacheEntry());
+
+  AutoClearStaleData guard(aElement);
   return Servo_AnimationValue_Compute(aElement,
                                       aDeclarations,
                                       aContext,
                                       mRawSet.get()).Consume();
 }
 
 bool
 ServoStyleSet::EnsureUniqueInnerOnCSSSheets()
@@ -1062,17 +1128,18 @@ ServoStyleSet::ClearNonInheritingStyleCo
   }
 }
 
 already_AddRefed<ServoStyleContext>
 ServoStyleSet::ResolveStyleLazily(Element* aElement,
                                   CSSPseudoElementType aPseudoType,
                                   nsIAtom* aPseudoTag,
                                   const ServoStyleContext* aParentContext,
-                                  StyleRuleInclusion aRuleInclusion)
+                                  StyleRuleInclusion aRuleInclusion,
+                                  bool aIgnoreExistingStyles)
 {
   mPresContext->EffectCompositor()->PreTraverse(aElement, aPseudoType);
   MOZ_ASSERT(!StylistNeedsUpdate());
 
   AutoSetInServoTraversal guard(this);
 
   /**
    * NB: This is needed because we process animations and transitions on the
@@ -1099,25 +1166,27 @@ ServoStyleSet::ResolveStyleLazily(Elemen
     }
   }
 
   RefPtr<ServoStyleContext> computedValues =
     Servo_ResolveStyleLazily(elementForStyleResolution,
                              pseudoTypeForStyleResolution,
                              aRuleInclusion,
                              &Snapshots(),
-                             mRawSet.get()).Consume();
+                             mRawSet.get(),
+                             aIgnoreExistingStyles).Consume();
 
   if (mPresContext->EffectCompositor()->PreTraverse(aElement, aPseudoType)) {
     computedValues =
       Servo_ResolveStyleLazily(elementForStyleResolution,
                                pseudoTypeForStyleResolution,
                                aRuleInclusion,
                                &Snapshots(),
-                               mRawSet.get()).Consume();
+                               mRawSet.get(),
+                               aIgnoreExistingStyles).Consume();
   }
 
   if (aPseudoType == CSSPseudoElementType::NotPseudo) {
     UpdateBodyTextColorIfNeeded(*aElement, *computedValues, *mPresContext);
   }
 
   return computedValues.forget();
 }
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -130,16 +130,23 @@ public:
   void BeginUpdate();
   nsresult EndUpdate();
 
   already_AddRefed<ServoStyleContext>
   ResolveStyleFor(dom::Element* aElement,
                   ServoStyleContext* aParentContext,
                   LazyComputeBehavior aMayCompute);
 
+  // Same as ResolveStyleFor, but additionally ensures that any stale
+  // element data in the tree has been cleared before resolving style.
+  already_AddRefed<ServoStyleContext>
+  ResolveCleanStyleFor(dom::Element* aElement,
+                       ServoStyleContext* aParentContext,
+                       LazyComputeBehavior aMayCompute);
+
   // Get a style context for a text node (which no rules will match).
   //
   // The returned style context will have nsCSSAnonBoxes::mozText as its pseudo.
   //
   // (Perhaps mozText should go away and we shouldn't even create style
   // contexts for such content nodes, when text-combine-upright is not
   // present.  However, not doing any rule matching for them is a first step.)
   already_AddRefed<ServoStyleContext>
@@ -189,16 +196,19 @@ public:
   ResolveTransientStyle(dom::Element* aElement,
                         CSSPseudoElementType aPseudoType,
                         nsIAtom* aPseudoTag,
                         StyleRuleInclusion aRules =
                           StyleRuleInclusion::All);
 
   // Similar to ResolveTransientStyle() but doesn't update the context state
   // Unlike ResolveServoStyle() this function calls PreTraverseSync().
+  //
+  // XXXheycam There's no longer any difference between ResolveTransientStyle
+  // and ResolveTransientServoStyle so we should merge them.
   already_AddRefed<ServoStyleContext>
   ResolveTransientServoStyle(dom::Element* aElement,
                              CSSPseudoElementType aPseudoType,
                              nsIAtom* aPseudoTag,
                              StyleRuleInclusion aRules =
                                StyleRuleInclusion::All);
 
   // Get a style context for an anonymous box.  aPseudoTag is the pseudo-tag to
@@ -564,17 +574,18 @@ private:
   void UpdateStylist();
 
   already_AddRefed<ServoStyleContext>
     ResolveStyleLazily(dom::Element* aElement,
                        CSSPseudoElementType aPseudoType,
                        nsIAtom* aPseudoTag,
                        const ServoStyleContext* aParentContext,
                        StyleRuleInclusion aRules =
-                         StyleRuleInclusion::All);
+                         StyleRuleInclusion::All,
+                       bool aIgnoreExistingStyles = false);
 
   void RunPostTraversalTasks();
 
   void PrependSheetOfType(SheetType aType,
                           ServoStyleSheet* aSheet);
 
   void AppendSheetOfType(SheetType aType,
                          ServoStyleSheet* aSheet);
--- a/layout/style/StyleSetHandle.h
+++ b/layout/style/StyleSetHandle.h
@@ -122,16 +122,20 @@ public:
                     nsStyleContext* aParentContext,
                     LazyComputeBehavior aMayCompute);
     inline already_AddRefed<nsStyleContext>
     ResolveStyleFor(dom::Element* aElement,
                     nsStyleContext* aParentContext,
                     LazyComputeBehavior aMayCompute,
                     TreeMatchContext* aTreeMatchContext);
     inline already_AddRefed<nsStyleContext>
+    ResolveCleanStyleFor(dom::Element* aElement,
+                         nsStyleContext* aParentContext,
+                         LazyComputeBehavior aMayCompute);
+    inline already_AddRefed<nsStyleContext>
     ResolveStyleForText(nsIContent* aTextNode,
                         nsStyleContext* aParentContext);
     inline already_AddRefed<nsStyleContext>
     ResolveStyleForFirstLetterContinuation(nsStyleContext* aParentContext);
     inline already_AddRefed<nsStyleContext>
     ResolveStyleForPlaceholder();
     inline already_AddRefed<nsStyleContext>
     ResolvePseudoElementStyle(dom::Element* aParentElement,
--- a/layout/style/StyleSetHandleInlines.h
+++ b/layout/style/StyleSetHandleInlines.h
@@ -107,16 +107,25 @@ StyleSetHandle::Ptr::ResolveStyleFor(dom
     return AsGecko()->ResolveStyleFor(aElement, parent, aMayCompute, *aTreeMatchContext);
   } else {
     auto* parent = aParentContext ? aParentContext->AsServo() : nullptr;
     return AsServo()->ResolveStyleFor(aElement, parent, aMayCompute);
   }
 }
 
 already_AddRefed<nsStyleContext>
+StyleSetHandle::Ptr::ResolveCleanStyleFor(dom::Element* aElement,
+                                          nsStyleContext* aParentContext,
+                                          LazyComputeBehavior aMayCompute)
+{
+  FORWARD_WITH_PARENT(ResolveCleanStyleFor, aParentContext,
+                      (aElement, parent, aMayCompute));
+}
+
+already_AddRefed<nsStyleContext>
 StyleSetHandle::Ptr::ResolveStyleForText(nsIContent* aTextNode,
                                          nsStyleContext* aParentContext)
 {
   FORWARD_WITH_PARENT(ResolveStyleForText, aParentContext, (aTextNode, parent));
 }
 
 already_AddRefed<nsStyleContext>
 StyleSetHandle::Ptr::ResolveStyleForPlaceholder()
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -583,16 +583,25 @@ nsComputedDOMStyle::DoGetStyleContextNoF
   bool inDocWithShell = true;
   if (!presShell) {
     inDocWithShell = false;
     presShell = aPresShell;
     if (!presShell)
       return nullptr;
   }
 
+  // We do this check to avoid having to add too much special casing of
+  // Servo functions we call to explicitly ignore any element data in
+  // the tree.
+  MOZ_ASSERT((aStyleType == eAll && aAnimationFlag == eWithAnimation) ||
+             !aElement->OwnerDoc()->GetBFCacheEntry(),
+             "nsComputedDOMStyle doesn't support getting styles without "
+             "document rules or without animation for documents in the "
+             "bfcache");
+
   auto pseudoType = CSSPseudoElementType::NotPseudo;
   if (aPseudo) {
     pseudoType = nsCSSPseudoElements::
       GetPseudoType(aPseudo, CSSEnabledState::eIgnoreEnabledState);
     if (pseudoType >= CSSPseudoElementType::Count) {
       return nullptr;
     }
   }
--- a/layout/style/nsStyleSet.h
+++ b/layout/style/nsStyleSet.h
@@ -121,16 +121,24 @@ class nsStyleSet final
   ResolveStyleFor(mozilla::dom::Element* aElement,
                   mozilla::GeckoStyleContext* aParentContext,
                   mozilla::LazyComputeBehavior)
   {
     return ResolveStyleFor(aElement, aParentContext);
   }
 
   already_AddRefed<mozilla::GeckoStyleContext>
+  ResolveCleanStyleFor(mozilla::dom::Element* aElement,
+                       mozilla::GeckoStyleContext* aParentContext,
+                       mozilla::LazyComputeBehavior)
+  {
+    return ResolveStyleFor(aElement, aParentContext);
+  }
+
+  already_AddRefed<mozilla::GeckoStyleContext>
   ResolveStyleFor(mozilla::dom::Element* aElement,
                   mozilla::GeckoStyleContext* aParentContext,
                   TreeMatchContext& aTreeMatchContext);
 
   already_AddRefed<mozilla::GeckoStyleContext>
   ResolveStyleFor(mozilla::dom::Element* aElement,
                   mozilla::GeckoStyleContext* aParentContext,
                   mozilla::LazyComputeBehavior aMayCompute,
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -386,61 +386,67 @@ pub trait DomTraversal<E: TElement> : Sy
 /// Manually resolve style by sequentially walking up the parent chain to the
 /// first styled Element, ignoring pending restyles. The resolved style is made
 /// available via a callback, and can be dropped by the time this function
 /// returns in the display:none subtree case.
 pub fn resolve_style<E>(
     context: &mut StyleContext<E>,
     element: E,
     rule_inclusion: RuleInclusion,
+    ignore_existing_style: bool,
 ) -> ElementStyles
 where
     E: TElement,
 {
     use style_resolver::StyleResolverForElement;
 
     debug_assert!(rule_inclusion == RuleInclusion::DefaultOnly ||
+                  ignore_existing_style ||
                   element.borrow_data().map_or(true, |d| !d.has_styles()),
                   "Why are we here?");
     let mut ancestors_requiring_style_resolution = SmallVec::<[E; 16]>::new();
 
     // Clear the bloom filter, just in case the caller is reusing TLS.
     context.thread_local.bloom_filter.clear();
 
     let mut style = None;
     let mut ancestor = element.traversal_parent();
     while let Some(current) = ancestor {
-        if rule_inclusion == RuleInclusion::All {
+        if rule_inclusion == RuleInclusion::All && !ignore_existing_style {
             if let Some(data) = current.borrow_data() {
                 if let Some(ancestor_style) = data.styles.get_primary() {
                     style = Some(ancestor_style.clone());
                     break;
                 }
             }
         }
         ancestors_requiring_style_resolution.push(current);
         ancestor = current.traversal_parent();
     }
 
     if let Some(ancestor) = ancestor {
         context.thread_local.bloom_filter.rebuild(ancestor);
         context.thread_local.bloom_filter.push(ancestor);
     }
 
-    let mut layout_parent_style = style.clone();
-    while let Some(style) = layout_parent_style.take() {
-        if !style.is_display_contents() {
-            layout_parent_style = Some(style);
-            break;
+    let mut layout_parent_style = None;
+
+    if !ignore_existing_style {
+        layout_parent_style = style.clone();
+        while let Some(style) = layout_parent_style.take() {
+            if !style.is_display_contents() {
+                layout_parent_style = Some(style);
+                break;
+            }
+
+            ancestor = ancestor.unwrap().traversal_parent();
+            layout_parent_style = ancestor.map(|a| {
+                a.borrow_data().unwrap().styles.primary().clone()
+            });
         }
-
-        ancestor = ancestor.unwrap().traversal_parent();
-        layout_parent_style = ancestor.map(|a| {
-            a.borrow_data().unwrap().styles.primary().clone()
-        });
     }
 
     for ancestor in ancestors_requiring_style_resolution.iter().rev() {
         context.thread_local.bloom_filter.assert_complete(*ancestor);
 
         let primary_style =
             StyleResolverForElement::new(*ancestor, context, rule_inclusion)
                 .resolve_primary_style(
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -2826,17 +2826,18 @@ pub extern "C" fn Servo_ResolveStyleAllo
     data.styles.primary().clone().into()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed,
                                            pseudo_type: CSSPseudoElementType,
                                            rule_inclusion: StyleRuleInclusion,
                                            snapshots: *const ServoElementSnapshotTable,
-                                           raw_data: RawServoStyleSetBorrowed)
+                                           raw_data: RawServoStyleSetBorrowed,
+                                           ignore_existing_styles: bool)
      -> ServoStyleContextStrong
 {
     debug_assert!(!snapshots.is_null());
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let guard = global_style_data.shared_lock.read();
     let element = GeckoElement(element);
     let doc_data = PerDocumentStyleData::from_ffi(raw_data);
     let data = doc_data.borrow();
@@ -2857,18 +2858,19 @@ pub extern "C" fn Servo_ResolveStyleLazi
                          back")
             }
             None => styles.primary().clone(),
         }
     };
 
     // In the common case we already have the style. Check that before setting
     // up all the computation machinery. (Don't use it when we're getting
-    // default styles, though.)
-    if rule_inclusion == RuleInclusion::All {
+    // default styles or in a bfcached document (as indicated by
+    // ignore_existing_styles), though.)
+    if rule_inclusion == RuleInclusion::All && !ignore_existing_styles {
         let styles = element.mutate_data().and_then(|d| {
             if d.has_styles() {
                 Some(finish(&d.styles))
             } else {
                 None
             }
         });
         if let Some(result) = styles {
@@ -2883,17 +2885,17 @@ pub extern "C" fn Servo_ResolveStyleLazi
                                        TraversalFlags::empty(),
                                        unsafe { &*snapshots });
     let mut tlc = ThreadLocalStyleContext::new(&shared);
     let mut context = StyleContext {
         shared: &shared,
         thread_local: &mut tlc,
     };
 
-    let styles = resolve_style(&mut context, element, rule_inclusion);
+    let styles = resolve_style(&mut context, element, rule_inclusion, ignore_existing_styles);
     finish(&styles).into()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_ReparentStyle(style_to_reparent: ServoStyleContextBorrowed,
                                       parent_style: ServoStyleContextBorrowed,
                                       parent_style_ignoring_first_line: ServoStyleContextBorrowed,
                                       layout_parent_style: ServoStyleContextBorrowed,