Bug 1366964 - Update style context generation after style resolved. r?heycam draft
authorWei-Cheng Pan <wpan@mozilla.com>
Tue, 23 May 2017 14:13:47 +0800
changeset 607537 747e11588427dc11d1a28354343ba03a06ced083
parent 606958 6fec4855b5345eb63fef57089e61829b88f5f4eb
child 637054 4dccc314580d2e5f51dbbd22c634e39cdf4c5d05
push id68011
push userbmo:wpan@mozilla.com
push dateWed, 12 Jul 2017 10:13:09 +0000
reviewersheycam
bugs1366964
milestone56.0a1
Bug 1366964 - Update style context generation after style resolved. r?heycam Adds another restyle generation which represents the dirty state of raw style changes, so that getComputedStyle() wont be confused by optimizations made by style engines. MozReview-Commit-ID: 7RYeNCzFygO
layout/base/RestyleManager.cpp
layout/base/RestyleManager.h
layout/base/ServoRestyleManager.cpp
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
layout/style/nsComputedDOMStyle.cpp
layout/style/nsComputedDOMStyle.h
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -15,16 +15,17 @@
 
 
 namespace mozilla {
 
 RestyleManager::RestyleManager(StyleBackendType aType,
                                nsPresContext* aPresContext)
   : mPresContext(aPresContext)
   , mRestyleGeneration(1)
+  , mUndisplayedRestyleGeneration(1)
   , mHoverGeneration(0)
   , mType(aType)
   , mInStyleRefresh(false)
   , mAnimationGeneration(0)
 {
   MOZ_ASSERT(mPresContext);
 }
 
--- a/layout/base/RestyleManager.h
+++ b/layout/base/RestyleManager.h
@@ -34,16 +34,23 @@ class RestyleManager
 public:
   typedef mozilla::dom::Element Element;
 
   NS_INLINE_DECL_REFCOUNTING(mozilla::RestyleManager)
 
   // Get an integer that increments every time we process pending restyles.
   // The value is never 0.
   uint32_t GetRestyleGeneration() const { return mRestyleGeneration; }
+  // Unlike GetRestyleGeneration, which means the actual restyling count,
+  // GetUndisplayedRestyleGeneration represents any possible DOM changes that
+  // can cause restyling. This is needed for getComputedStyle to work with
+  // non-styled (e.g. display: none) elements.
+  uint32_t GetUndisplayedRestyleGeneration() const {
+    return mUndisplayedRestyleGeneration;
+  }
 
   // Get an integer that increments every time there is a style change
   // as a result of a change to the :hover content state.
   uint32_t GetHoverGeneration() const { return mHoverGeneration; }
 
   void Disconnect() { mPresContext = nullptr; }
 
   static nsCString RestyleHintToString(nsRestyleHint aHint);
@@ -230,30 +237,40 @@ protected:
 
   void IncrementRestyleGeneration() {
     if (++mRestyleGeneration == 0) {
       // Keep mRestyleGeneration from being 0, since that's what
       // nsPresContext::GetRestyleGeneration returns when it no
       // longer has a RestyleManager.
       ++mRestyleGeneration;
     }
+    IncrementUndisplayedRestyleGeneration();
+  }
+
+  void IncrementUndisplayedRestyleGeneration() {
+    if (++mUndisplayedRestyleGeneration == 0) {
+      // Ensure mUndisplayedRestyleGeneration > 0, for the same reason as
+      // IncrementRestyleGeneration.
+      ++mUndisplayedRestyleGeneration;
+    }
   }
 
   nsPresContext* PresContext() const {
     MOZ_ASSERT(mPresContext);
     return mPresContext;
   }
 
   nsCSSFrameConstructor* FrameConstructor() const {
     return PresContext()->FrameConstructor();
   }
 
 private:
   nsPresContext* mPresContext; // weak, can be null after Disconnect().
   uint32_t mRestyleGeneration;
+  uint32_t mUndisplayedRestyleGeneration;
   uint32_t mHoverGeneration;
 
   // Used to keep track of frames that have been destroyed during
   // ProcessRestyledFrames, so we don't try to touch them again even if
   // they're referenced again later in the changelist.
   mozilla::UniquePtr<nsTHashtable<nsPtrHashKey<const nsIFrame>>> mDestroyedFrames;
 
 protected:
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -125,16 +125,23 @@ ServoRestyleManager::PostRestyleEvent(El
   // We allow posting restyles from within change hint handling, but not from
   // within the restyle algorithm itself.
   MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal());
 
   if (aRestyleHint == 0 && !aMinChangeHint) {
     return; // Nothing to do.
   }
 
+  // Assuming the restyle hints will invalidate cached style for
+  // getComputedStyle, since we don't know if any of the restyling that we do
+  // would affect undisplayed elements.
+  if (aRestyleHint) {
+    IncrementUndisplayedRestyleGeneration();
+  }
+
   // Processing change hints sometimes causes new change hints to be generated,
   // and very occasionally, additional restyle hints. We collect the change
   // hints manually to avoid re-traversing the DOM to find them.
   if (mReentrantChanges && !aRestyleHint) {
     mReentrantChanges->AppendElement(ReentrantChange { aElement, aMinChangeHint });
     return;
   }
 
@@ -936,16 +943,20 @@ ServoRestyleManager::ContentStateChanged
 
   if (Element* parent = aElement->GetFlattenedTreeParentElementForStyle()) {
     parent->NoteDirtyDescendantsForServo();
   }
 
   if (restyleHint || changeHint) {
     Servo_NoteExplicitHints(aElement, restyleHint, changeHint);
   }
+
+  // Assuming we need to invalidate cached style in getComputedStyle for
+  // undisplayed elements, since we don't know if it is needed.
+  IncrementUndisplayedRestyleGeneration();
 }
 
 static inline bool
 AttributeInfluencesOtherPseudoClassState(const Element& aElement,
                                          const nsIAtom* aAttribute)
 {
   // We must record some state for :-moz-browser-frame and
   // :-moz-table-border-nonzero.
@@ -1030,16 +1041,22 @@ ServoRestyleManager::TakeSnapshotForAttr
   if (!NeedToRecordAttrChange(*StyleSet(),
                               *aElement,
                               aNameSpaceID,
                               aAttribute,
                               &influencesOtherPseudoClassState)) {
     return;
   }
 
+  // We cannot tell if the attribute change will affect the styles of
+  // undisplayed elements, because we don't actually restyle those elements
+  // during the restyle traversal. So just assume that the attribute change can
+  // cause the style to change.
+  IncrementUndisplayedRestyleGeneration();
+
   ServoElementSnapshot& snapshot = SnapshotFor(aElement);
   snapshot.AddAttrs(aElement, aNameSpaceID, aAttribute);
 
   if (influencesOtherPseudoClassState) {
     snapshot.AddOtherPseudoClassState(aElement);
   }
 
   if (Element* parent = aElement->GetFlattenedTreeParentElementForStyle()) {
@@ -1084,16 +1101,22 @@ ServoRestyleManager::AttributeChanged(El
     restyleHint |= eRestyle_Subtree;
   } else if (aElement->IsAttributeMapped(aAttribute)) {
     restyleHint |= eRestyle_Self;
   }
 
   if (restyleHint || changeHint) {
     Servo_NoteExplicitHints(aElement, restyleHint, changeHint);
   }
+
+  if (restyleHint) {
+    // Assuming we need to invalidate cached style in getComputedStyle for
+    // undisplayed elements, since we don't know if it is needed.
+    IncrementUndisplayedRestyleGeneration();
+  }
 }
 
 nsresult
 ServoRestyleManager::ReparentStyleContext(nsIFrame* aFrame)
 {
   NS_WARNING("stylo: ServoRestyleManager::ReparentStyleContext not implemented");
   return NS_OK;
 }
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -3090,16 +3090,25 @@ uint64_t
 nsPresContext::GetRestyleGeneration() const
 {
   if (!mRestyleManager) {
     return 0;
   }
   return mRestyleManager->GetRestyleGeneration();
 }
 
+uint64_t
+nsPresContext::GetUndisplayedRestyleGeneration() const
+{
+  if (!mRestyleManager) {
+    return 0;
+  }
+  return mRestyleManager->GetUndisplayedRestyleGeneration();
+}
+
 nsBidi&
 nsPresContext::GetBidiEngine()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!mBidiEngine) {
     mBidiEngine.reset(new nsBidi());
   }
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -1032,16 +1032,17 @@ public:
   void SetNotifySubDocInvalidationData(mozilla::layers::ContainerLayer* aContainer);
   static void ClearNotifySubDocInvalidationData(mozilla::layers::ContainerLayer* aContainer);
   bool IsDOMPaintEventPending();
 
   /**
    * Returns the RestyleManager's restyle generation counter.
    */
   uint64_t GetRestyleGeneration() const;
+  uint64_t GetUndisplayedRestyleGeneration() const;
 
   /**
    * Returns whether there are any pending restyles or reflows.
    */
   bool HasPendingRestyleOrReflow();
 
   /**
    * Informs the document's FontFaceSet that the refresh driver ticked,
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -776,30 +776,34 @@ nsComputedDOMStyle::ClearStyleContext()
   if (mResolvedStyleContext) {
     mResolvedStyleContext = false;
     mContent->RemoveMutationObserver(this);
   }
   mStyleContext = nullptr;
 }
 
 void
-nsComputedDOMStyle::SetResolvedStyleContext(RefPtr<nsStyleContext>&& aContext)
+nsComputedDOMStyle::SetResolvedStyleContext(RefPtr<nsStyleContext>&& aContext,
+                                            uint64_t aGeneration)
 {
   if (!mResolvedStyleContext) {
     mResolvedStyleContext = true;
     mContent->AddMutationObserver(this);
   }
   mStyleContext = aContext;
+  mStyleContextGeneration = aGeneration;
 }
 
 void
-nsComputedDOMStyle::SetFrameStyleContext(nsStyleContext* aContext)
+nsComputedDOMStyle::SetFrameStyleContext(nsStyleContext* aContext,
+                                         uint64_t aGeneration)
 {
   ClearStyleContext();
   mStyleContext = aContext;
+  mStyleContextGeneration = aGeneration;
 }
 
 void
 nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush)
 {
   nsCOMPtr<nsIDocument> document = do_QueryReferent(mDocumentWeak);
   if (!document) {
     ClearStyleContext();
@@ -817,21 +821,36 @@ nsComputedDOMStyle::UpdateCurrentStyleSo
 #endif
 
   mPresShell = document->GetShell();
   if (!mPresShell || !mPresShell->GetPresContext()) {
     ClearStyleContext();
     return;
   }
 
+  // We need to use GetUndisplayedRestyleGeneration instead of
+  // GetRestyleGeneration, because the caching of mStyleContext is an
+  // optimization that is useful only for displayed elements.
+  // For undisplayed elements we need to take into account any DOM changes that
+  // might cause a restyle, because Servo will not increase the generation for
+  // undisplayed elements.
+  // As for Gecko, GetUndisplayedRestyleGeneration is effectively equal to
+  // GetRestyleGeneration, since the generation is incremented whenever we
+  // process restyles.
   uint64_t currentGeneration =
-    mPresShell->GetPresContext()->GetRestyleGeneration();
+    mPresShell->GetPresContext()->GetUndisplayedRestyleGeneration();
 
   if (mStyleContext) {
-    if (mStyleContextGeneration == currentGeneration) {
+    // We can't rely on the undisplayed restyle generation if
+    // mContent is out-of-document, since that generation is not
+    // incremented for DOM changes on out-of-document elements.
+    // So we always need to update the style context to ensure it
+    // it up-to-date.
+    if (mStyleContextGeneration == currentGeneration
+        && mContent->IsInComposedDoc()) {
       // Our cached style context is still valid.
       return;
     }
     // We've processed some restyles, so the cached style context might
     // be out of date.
     mStyleContext = nullptr;
   }
 
@@ -861,17 +880,17 @@ nsComputedDOMStyle::UpdateCurrentStyleSo
         // from the inner table frame.
         mInnerFrame = mOuterFrame->PrincipalChildList().FirstChild();
         NS_ASSERTION(mInnerFrame, "table wrapper must have an inner");
         NS_ASSERTION(!mInnerFrame->GetNextSibling(),
                      "table wrapper frames should have just one child, "
                      "the inner table");
       }
 
-      SetFrameStyleContext(mInnerFrame->StyleContext());
+      SetFrameStyleContext(mInnerFrame->StyleContext(), currentGeneration);
       NS_ASSERTION(mStyleContext, "Frame without style context?");
     }
   }
 
   if (!mStyleContext || MustReresolveStyle(mStyleContext)) {
 #ifdef DEBUG
     if (mStyleContext && mStyleContext->IsGecko()) {
       // We want to check that going through this path because of
@@ -905,33 +924,33 @@ nsComputedDOMStyle::UpdateCurrentStyleSo
       ClearStyleContext();
       return;
     }
 
     // No need to re-get the generation, even though GetStyleContext
     // will flush, since we flushed style at the top of this function.
     NS_ASSERTION(mPresShell &&
                  currentGeneration ==
-                   mPresShell->GetPresContext()->GetRestyleGeneration(),
+                   mPresShell->GetPresContext()->GetUndisplayedRestyleGeneration(),
                  "why should we have flushed style again?");
 
-    SetResolvedStyleContext(Move(resolvedStyleContext));
+    SetResolvedStyleContext(Move(resolvedStyleContext), currentGeneration);
     NS_ASSERTION(mPseudo || !mStyleContext->HasPseudoElementData(),
                  "should not have pseudo-element data");
   }
 
   if (mAnimationFlag == eWithoutAnimation) {
     // We will support Servo in bug 1311257.
     MOZ_ASSERT(mPresShell->StyleSet()->IsGecko(),
                "eWithoutAnimationRules support Gecko only");
     nsStyleSet* styleSet = mPresShell->StyleSet()->AsGecko();
     RefPtr<nsStyleContext> unanimatedStyleContext =
       styleSet->ResolveStyleByRemovingAnimation(
         mContent->AsElement(), mStyleContext, eRestyle_AllHintsWithAnimations);
-    SetResolvedStyleContext(Move(unanimatedStyleContext));
+    SetResolvedStyleContext(Move(unanimatedStyleContext), currentGeneration);
   }
 
   // mExposeVisitedStyle is set to true only by testing APIs that
   // require chrome privilege.
   MOZ_ASSERT(!mExposeVisitedStyle || nsContentUtils::IsCallerChrome(),
              "mExposeVisitedStyle set incorrectly");
   if (mExposeVisitedStyle && mStyleContext->RelevantLinkVisited()) {
     nsStyleContext *styleIfVisited = mStyleContext->GetStyleIfVisited();
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -163,18 +163,19 @@ private:
                                                   bool aAlignTrue,
                                                   const KTableEntry aTable[]);
   // This indicates error by leaving mStyleContext null.
   void UpdateCurrentStyleSources(bool aNeedsLayoutFlush);
   void ClearCurrentStyleSources();
 
   // Helper functions called by UpdateCurrentStyleSources.
   void ClearStyleContext();
-  void SetResolvedStyleContext(RefPtr<nsStyleContext>&& aContext);
-  void SetFrameStyleContext(nsStyleContext* aContext);
+  void SetResolvedStyleContext(RefPtr<nsStyleContext>&& aContext,
+                               uint64_t aGeneration);
+  void SetFrameStyleContext(nsStyleContext* aContext, uint64_t aGeneration);
 
   static already_AddRefed<nsStyleContext>
   DoGetStyleContextNoFlush(mozilla::dom::Element* aElement,
                            nsIAtom* aPseudo,
                            nsIPresShell* aPresShell,
                            StyleType aStyleType,
                            AnimationFlag aAnimationFlag);