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
--- 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;