Bug 1363805 - Part 3: Do lazy flushing if possible. r?heycam draft
authorWei-Cheng Pan <wpan@mozilla.com>
Tue, 23 May 2017 12:02:11 +0800
changeset 669668 5031dfd2ef2f60255e5fc3ba624d2447d11efaed
parent 669667 3bd27b2d96d733a182657591f7f34a530336dc68
child 669669 9a61280ff3243ecd3dfc2625c95df7e4301db9e6
push id81392
push userbmo:wpan@mozilla.com
push dateMon, 25 Sep 2017 07:20:33 +0000
reviewersheycam
bugs1363805
milestone58.0a1
Bug 1363805 - Part 3: Do lazy flushing if possible. r?heycam Skips flushing current document if the target of getComputedDOMStyle cannot be affected by any pending restyles. MozReview-Commit-ID: C87HDIDvOth
dom/animation/EffectCompositor.cpp
dom/animation/EffectCompositor.h
layout/base/GeckoRestyleManager.cpp
layout/base/GeckoRestyleManager.h
layout/base/ServoRestyleManager.cpp
layout/style/nsComputedDOMStyle.cpp
layout/style/nsComputedDOMStyle.h
--- a/dom/animation/EffectCompositor.cpp
+++ b/dom/animation/EffectCompositor.cpp
@@ -564,16 +564,30 @@ EffectCompositor::HasThrottledStyleUpdat
         return true;
       }
     }
   }
 
   return false;
 }
 
+bool
+EffectCompositor::HasPendingStyleUpdatesFor(Element* aElement) const
+{
+  for (auto& elementSet : mElementsToRestyle) {
+    for (auto iter = elementSet.ConstIter(); !iter.Done(); iter.Next()) {
+      if (iter.Key().mElement->Contains(aElement)) {
+        return true;
+      }
+    }
+  }
+
+  return false;
+}
+
 void
 EffectCompositor::AddStyleUpdatesTo(RestyleTracker& aTracker)
 {
   if (!mPresContext) {
     return;
   }
 
   for (size_t i = 0; i < kCascadeLevelCount; i++) {
--- a/dom/animation/EffectCompositor.h
+++ b/dom/animation/EffectCompositor.h
@@ -163,16 +163,17 @@ public:
   bool GetServoAnimationRule(
     const dom::Element* aElement,
     CSSPseudoElementType aPseudoType,
     CascadeLevel aCascadeLevel,
     RawServoAnimationValueMapBorrowedMut aAnimationValues);
 
   bool HasPendingStyleUpdates() const;
   bool HasThrottledStyleUpdates() const;
+  bool HasPendingStyleUpdatesFor(dom::Element* aElement) const;
 
   // Tell the restyle tracker about all the animated styles that have
   // pending updates so that it can update the animation rule for these
   // elements.
   void AddStyleUpdatesTo(RestyleTracker& aTracker);
 
   nsIStyleRuleProcessor* RuleProcessor(CascadeLevel aCascadeLevel) const
   {
--- a/layout/base/GeckoRestyleManager.cpp
+++ b/layout/base/GeckoRestyleManager.cpp
@@ -3646,16 +3646,22 @@ GeckoRestyleManager::ComputeAndProcessSt
                     swappedStructOwners);
   r.RestyleChildrenOfDisplayContentsElement(frame, aNewContext, aMinChange,
                                             aRestyleTracker,
                                             aRestyleHint, aRestyleHintData);
   ProcessRestyledFrames(changeList);
   ClearCachedInheritedStyleDataOnDescendants(contextsToClear);
 }
 
+bool
+GeckoRestyleManager::HasPendingRestyles() const
+{
+  return mPendingRestyles.Count() != 0;
+}
+
 nsStyleSet*
 ElementRestyler::StyleSet() const
 {
   MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),
              "ElementRestyler should only be used with a Gecko-flavored "
              "style backend");
   return mPresContext->StyleSet()->AsGecko();
 }
--- a/layout/base/GeckoRestyleManager.h
+++ b/layout/base/GeckoRestyleManager.h
@@ -340,16 +340,17 @@ public:
   // environment variable.
   static uint32_t StructsToLog();
 
   static nsCString StructNamesToString(uint32_t aSIDs);
   int32_t& LoggingDepth() { return mLoggingDepth; }
 #endif
 
   bool IsProcessingRestyles() { return mIsProcessingRestyles; }
+  bool HasPendingRestyles() const;
 
 private:
   inline nsStyleSet* StyleSet() const {
     MOZ_ASSERT(PresContext()->StyleSet()->IsGecko(),
                "GeckoRestyleManager should only be used with a Gecko-flavored "
                "style backend");
     return PresContext()->StyleSet()->AsGecko();
   }
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -1191,20 +1191,25 @@ void
 ServoRestyleManager::ProcessPendingRestyles()
 {
   DoProcessPendingRestyles(ServoTraversalFlags::Empty);
 }
 
 void
 ServoRestyleManager::ProcessAllPendingAttributeAndStateInvalidations()
 {
-  AutoTimelineMarker marker(mPresContext->GetDocShell(),
-                            "ProcessAllPendingAttributeAndStateInvalidations");
+  if (mSnapshots.IsEmpty()) {
+    return;
+  }
   for (auto iter = mSnapshots.Iter(); !iter.Done(); iter.Next()) {
-    Servo_ProcessInvalidations(StyleSet()->RawSet(), iter.Key(), &mSnapshots);
+    // Servo data for the element might have been dropped. (e.g. by removing
+    // from its document)
+    if (iter.Key()->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
+      Servo_ProcessInvalidations(StyleSet()->RawSet(), iter.Key(), &mSnapshots);
+    }
   }
   ClearSnapshots();
 }
 
 bool
 ServoRestyleManager::HasPendingRestyleAncestor(Element* aElement) const
 {
   return Servo_HasPendingRestyleAncestor(aElement);
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -33,16 +33,17 @@
 
 #include "nsPresContext.h"
 #include "nsIDocument.h"
 
 #include "nsCSSPseudoElements.h"
 #include "mozilla/StyleSetHandle.h"
 #include "mozilla/StyleSetHandleInlines.h"
 #include "mozilla/GeckoRestyleManager.h"
+#include "mozilla/ServoRestyleManager.h"
 #include "mozilla/RestyleManagerInlines.h"
 #include "imgIRequest.h"
 #include "nsLayoutUtils.h"
 #include "nsCSSKeywords.h"
 #include "nsStyleCoord.h"
 #include "nsDisplayList.h"
 #include "nsDOMCSSDeclaration.h"
 #include "nsStyleTransformMatrix.h"
@@ -96,16 +97,81 @@ GetBackgroundList(T nsStyleImageLayers::
     RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
     val->SetIdent(nsCSSProps::ValueToKeywordEnum(aLayers.mLayers[i].*aMember, aTable));
     valueList->AppendCSSValue(val.forget());
   }
 
   return valueList.forget();
 }
 
+// Whether there is any pending restyle for the element or any of its ancestors.
+static bool
+ContentNeedsRestyle(nsIContent* aContent)
+{
+  MOZ_ASSERT(aContent);
+  nsIContent* node = aContent;
+  while (node) {
+    // Check if the element has any flag for restyling. For Gecko, we also need
+    // another flag to know if there is any child has LaterSiblings restyle
+    // hint.
+    if (node->HasFlag(ELEMENT_ALL_RESTYLE_FLAGS |
+                      ELEMENT_HAS_CHILD_WITH_LATER_SIBLINGS_HINT)) {
+      return true;
+    }
+    node = node->GetFlattenedTreeParent();
+  }
+  return false;
+}
+
+// Whether aDocument needs to restyle for aElement
+static bool
+DocumentNeedsRestyle(const nsIDocument* aDocument, Element* aElement)
+{
+  nsIPresShell* shell = aDocument->GetShell();
+  if (!shell) {
+    return true;
+  }
+  // Unfortunately we don't know if the sheet change affects mContent or not, so
+  // just assume it will and that we need to flush normally.
+  StyleSetHandle styleSet = shell->StyleSet();
+  if (styleSet->StyleSheetsHaveChanged()) {
+    return true;
+  }
+  // If any ancestor has pending animation, flush it.
+  nsPresContext* context = shell->GetPresContext();
+  if (context->EffectCompositor()->HasPendingStyleUpdatesFor(aElement)) {
+    return true;
+  }
+  if (styleSet->IsServo()) {
+    // For Servo, we need to process the restyle-hint-invalidations first, to
+    // expand LaterSiblings hint, so that we can look whether ancestors need
+    // restyling.
+    ServoRestyleManager* restyleManager = context->RestyleManager()->AsServo();
+    restyleManager->ProcessAllPendingAttributeAndStateInvalidations();
+
+    // Then if there is a restyle root, we check if the root is an ancestor of
+    // this content. If it is not, then we don't need to restyle immediately.
+    // Note this is different from Gecko: we only check if any ancestor needs
+    // to restyle _itself_, not descendants, since dirty descendants can be
+    // another subtree.
+    if (aDocument->GetServoRestyleRoot() &&
+        restyleManager->HasPendingRestyleAncestor(aElement)) {
+      return true;
+    }
+  } else {
+    // For Gecko, first check if there is any pending restyle, then we check if
+    // any ancestor has dirty bits for restyle.
+    GeckoRestyleManager* restyleManager = context->RestyleManager()->AsGecko();
+    if (restyleManager->HasPendingRestyles() && ContentNeedsRestyle(aElement)) {
+      return true;
+    }
+  }
+  return false;
+}
+
 /**
  * An object that represents the ordered set of properties that are exposed on
  * an nsComputedDOMStyle object and how their computed values can be obtained.
  */
 struct nsComputedStyleMap
 {
   friend class nsComputedDOMStyle;
 
@@ -802,31 +868,60 @@ void
 nsComputedDOMStyle::SetFrameStyleContext(nsStyleContext* aContext,
                                          uint64_t aGeneration)
 {
   ClearStyleContext();
   mStyleContext = aContext;
   mStyleContextGeneration = aGeneration;
 }
 
+FlushTarget
+nsComputedDOMStyle::GetFlushTarget(nsIDocument* aDocument) const
+{
+  // If mContent is not in the same document, we could do some checks to know if
+  // there are some pending restyles can be ignored across documents (since we
+  // will use the caller document's style), but it can be complicated and should
+  // be an edge case, so we just don't bother to do the optimization in this
+  // case.
+  if (aDocument != mContent->OwnerDoc()) {
+    return FlushTarget::Normal;
+  }
+  if (DocumentNeedsRestyle(aDocument, mContent->AsElement())) {
+    return FlushTarget::Normal;
+  }
+  // If parent document is there, also needs to check if there is some change
+  // that needs to flush this document (e.g. size change for iframe).
+  while (nsIDocument* parentDocument = aDocument->GetParentDocument()) {
+    Element* element = parentDocument->FindContentForSubDocument(aDocument);
+    if (DocumentNeedsRestyle(parentDocument, element)) {
+      return FlushTarget::Normal;
+    }
+    aDocument = parentDocument;
+  }
+  return FlushTarget::ParentOnly;
+}
+
 void
 nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush)
 {
   nsCOMPtr<nsIDocument> document = do_QueryReferent(mDocumentWeak);
   if (!document) {
     ClearStyleContext();
     return;
   }
 
+  // If the property we are computing relies on layout, then we must flush.
+  FlushTarget target = aNeedsLayoutFlush ? FlushTarget::Normal : GetFlushTarget(document);
+
   // Flush _before_ getting the presshell, since that could create a new
   // presshell.  Also note that we want to flush the style on the document
   // we're computing style in, not on the document mContent is in -- the two
   // may be different.
   document->FlushPendingNotifications(
-    aNeedsLayoutFlush ? FlushType::Layout : FlushType::Style);
+    aNeedsLayoutFlush ? FlushType::Layout : FlushType::Style, target);
 #ifdef DEBUG
   mFlushedPendingReflows = aNeedsLayoutFlush;
 #endif
 
   mPresShell = document->GetShell();
   if (!mPresShell || !mPresShell->GetPresContext()) {
     ClearStyleContext();
     return;
@@ -928,20 +1023,22 @@ nsComputedDOMStyle::UpdateCurrentStyleSo
                                           mStyleType);
     if (!resolvedStyleContext) {
       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()->GetUndisplayedRestyleGeneration(),
-                 "why should we have flushed style again?");
+    // We don't need to check this if we only flushed the parent.
+    NS_ASSERTION(target == FlushTarget::ParentOnly ||
+                 (mPresShell &&
+                   currentGeneration ==
+                     mPresShell->GetPresContext()->GetUndisplayedRestyleGeneration()),
+                   "why should we have flushed style again?");
 
     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.
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -710,16 +710,20 @@ private:
   // Helper function for computing basic shape styles.
   already_AddRefed<CSSValue> CreatePrimitiveValueForBasicShape(
     const mozilla::StyleBasicShape* aStyleBasicShape);
   void BoxValuesToString(nsAString& aString,
                          const nsTArray<nsStyleCoord>& aBoxValues);
   void BasicShapeRadiiToString(nsAString& aCssText,
                                const nsStyleCorners& aCorners);
 
+  // Find out if we can safely skip flushing for aDocument (i.e. pending
+  // restyles does not affect mContent).
+  mozilla::FlushTarget GetFlushTarget(nsIDocument* aDocument) const;
+
 
   static nsComputedStyleMap* GetComputedStyleMap();
 
   // We don't really have a good immutable representation of "presentation".
   // Given the way GetComputedStyle is currently used, we should just grab the
   // 0th presshell, if any, from the document.
   nsWeakPtr mDocumentWeak;
   nsCOMPtr<nsIContent> mContent;