From f3f406f76b688cef50b8b12bccf310df08d2fdb0 Mon Sep 17 00:00:00 2001 From: Wei-Cheng Pan Date: Tue, 23 May 2017 12:02:11 +0800 Subject: [PATCH] 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 --HG-- extra : rebase_source : 064880493f9aac2599689cdd0749200bb579c60b --- dom/animation/EffectCompositor.cpp | 14 ++++ dom/animation/EffectCompositor.h | 1 + layout/base/GeckoRestyleManager.cpp | 6 ++ layout/base/GeckoRestyleManager.h | 1 + layout/base/ServoRestyleManager.cpp | 11 ++- layout/style/nsComputedDOMStyle.cpp | 107 ++++++++++++++++++++++++++-- layout/style/nsComputedDOMStyle.h | 4 ++ 7 files changed, 136 insertions(+), 8 deletions(-) diff --git a/dom/animation/EffectCompositor.cpp b/dom/animation/EffectCompositor.cpp index e27c0ca70ae3..4b89b29dfad5 100644 --- a/dom/animation/EffectCompositor.cpp +++ b/dom/animation/EffectCompositor.cpp @@ -569,6 +569,20 @@ EffectCompositor::HasThrottledStyleUpdates() const 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) { diff --git a/dom/animation/EffectCompositor.h b/dom/animation/EffectCompositor.h index 70ad33e0dd88..1d505cbc468f 100644 --- a/dom/animation/EffectCompositor.h +++ b/dom/animation/EffectCompositor.h @@ -168,6 +168,7 @@ public: 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 diff --git a/layout/base/GeckoRestyleManager.cpp b/layout/base/GeckoRestyleManager.cpp index 52060f2e22e9..1062e8993075 100644 --- a/layout/base/GeckoRestyleManager.cpp +++ b/layout/base/GeckoRestyleManager.cpp @@ -3651,6 +3651,12 @@ GeckoRestyleManager::ComputeAndProcessStyleChange( ClearCachedInheritedStyleDataOnDescendants(contextsToClear); } +bool +GeckoRestyleManager::HasPendingRestyles() const +{ + return mPendingRestyles.Count() != 0; +} + nsStyleSet* ElementRestyler::StyleSet() const { diff --git a/layout/base/GeckoRestyleManager.h b/layout/base/GeckoRestyleManager.h index 086ef04822e1..767ab32f4f78 100644 --- a/layout/base/GeckoRestyleManager.h +++ b/layout/base/GeckoRestyleManager.h @@ -345,6 +345,7 @@ public: #endif bool IsProcessingRestyles() { return mIsProcessingRestyles; } + bool HasPendingRestyles() const; private: inline nsStyleSet* StyleSet() const { diff --git a/layout/base/ServoRestyleManager.cpp b/layout/base/ServoRestyleManager.cpp index 7fe7641e3925..18584846f1ad 100644 --- a/layout/base/ServoRestyleManager.cpp +++ b/layout/base/ServoRestyleManager.cpp @@ -1196,10 +1196,15 @@ ServoRestyleManager::ProcessPendingRestyles() 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(); } diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp index 8d6001153180..7a443baf378d 100644 --- a/layout/style/nsComputedDOMStyle.cpp +++ b/layout/style/nsComputedDOMStyle.cpp @@ -38,6 +38,7 @@ #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" @@ -101,6 +102,71 @@ GetBackgroundList(T nsStyleImageLayers::Layer::* aMember, 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. @@ -807,6 +873,32 @@ nsComputedDOMStyle::SetFrameStyleContext(nsStyleContext* 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) { @@ -816,12 +908,15 @@ nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush) 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 @@ -933,10 +1028,12 @@ nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush) // 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(), diff --git a/layout/style/nsComputedDOMStyle.h b/layout/style/nsComputedDOMStyle.h index 2e4e0f85968b..3aee6ee99834 100644 --- a/layout/style/nsComputedDOMStyle.h +++ b/layout/style/nsComputedDOMStyle.h @@ -715,6 +715,10 @@ private: 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();