From eae8d21428acdb3fb842df091ded0eb276bcaaaa Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Tue, 24 Apr 2018 21:39:13 +0200 Subject: moebius#138: Optimize operations on root of deeply-nested frame tree https://github.com/MoonchildProductions/moebius/pull/138 --- layout/base/RestyleManagerBase.cpp | 63 ++++++++++++++++++++++++++++++++++- layout/base/nsCSSFrameConstructor.cpp | 15 ++++++--- layout/base/nsChangeHint.h | 55 +++++++++++++++++++++++++++++- layout/base/nsPresContext.cpp | 9 ++--- layout/base/nsPresContext.h | 22 +++++++++++- 5 files changed, 153 insertions(+), 11 deletions(-) (limited to 'layout/base') diff --git a/layout/base/RestyleManagerBase.cpp b/layout/base/RestyleManagerBase.cpp index 9a5ce43eb..d96d9dbbb 100644 --- a/layout/base/RestyleManagerBase.cpp +++ b/layout/base/RestyleManagerBase.cpp @@ -154,7 +154,7 @@ RestyleManagerBase::ChangeHintToString(nsChangeHint aHint) "NeutralChange", "InvalidateRenderingObservers", "ReflowChangesSizeOrPosition", "UpdateComputedBSize", "UpdateUsesOpacity", "UpdateBackgroundPosition", - "AddOrRemoveTransform" + "AddOrRemoveTransform", "CSSOverflowChange", }; static_assert(nsChangeHint_AllHints == (1 << ArrayLength(names)) - 1, "Name list doesn't match change hints."); @@ -1070,6 +1070,67 @@ RestyleManagerBase::ProcessRestyledFrames(nsStyleChangeList& aChangeList) FramePropertyTable* propTable = presContext->PropertyTable(); nsCSSFrameConstructor* frameConstructor = presContext->FrameConstructor(); + // Handle nsChangeHint_CSSOverflowChange, by either updating the + // scrollbars on the viewport, or upgrading the change hint to frame-reconstruct. + for (nsStyleChangeData& data : aChangeList) { + if (data.mHint & nsChangeHint_CSSOverflowChange) { + data.mHint &= ~nsChangeHint_CSSOverflowChange; + bool doReconstruct = true; // assume the worst + + // Only bother with this if we're html/body, since: + // (a) It'd be *expensive* to reframe these particular nodes. They're + // at the root, so reframing would mean rebuilding the world. + // (b) It's often *unnecessary* to reframe for "overflow" changes on + // these particular nodes. In general, the only reason we reframe + // for "overflow" changes is so we can construct (or destroy) a + // scrollframe & scrollbars -- and the html/body nodes often don't + // need their own scrollframe/scrollbars because they coopt the ones + // on the viewport (which always exist). So depending on whether + // that's happening, we can skip the reframe for these nodes. + if (data.mContent->IsAnyOfHTMLElements(nsGkAtoms::body, + nsGkAtoms::html)) { + // If the restyled element provided/provides the scrollbar styles for + // the viewport before and/or after this restyle, AND it's not coopting + // that responsibility from some other element (which would need + // reconstruction to make its own scrollframe now), THEN: we don't need + // to reconstruct - we can just reflow, because no scrollframe is being + // added/removed. + nsIContent* prevOverrideNode = + presContext->GetViewportScrollbarStylesOverrideNode(); + nsIContent* newOverrideNode = + presContext->UpdateViewportScrollbarStylesOverride(); + + if (data.mContent == prevOverrideNode || + data.mContent == newOverrideNode) { + // If we get here, the restyled element provided the scrollbar styles + // for viewport before this restyle, OR it will provide them after. + if (!prevOverrideNode || !newOverrideNode || + prevOverrideNode == newOverrideNode) { + // If we get here, the restyled element is NOT replacing (or being + // replaced by) some other element as the viewport's + // scrollbar-styles provider. (If it were, we'd potentially need to + // reframe to create a dedicated scrollframe for whichever element + // is being booted from providing viewport scrollbar styles.) + // + // Under these conditions, we're OK to assume that this "overflow" + // change only impacts the root viewport's scrollframe, which + // already exists, so we can simply reflow instead of reframing. + // When requesting this reflow, we send the exact same change hints + // that "width" and "height" would send (since conceptually, + // adding/removing scrollbars is like changing the available + // space). + data.mHint |= (nsChangeHint_ReflowHintsForISizeChange | + nsChangeHint_ReflowHintsForBSizeChange); + doReconstruct = false; + } + } + } + if (doReconstruct) { + data.mHint |= nsChangeHint_ReconstructFrame; + } + } + } + // Make sure to not rebuild quote or counter lists while we're // processing restyles frameConstructor->BeginUpdate(); diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index f8c7f52a9..767298b85 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -8246,11 +8246,19 @@ nsCSSFrameConstructor::ContentRemoved(nsIContent* aContainer, *aDestroyedFramesFor = aChild; } + nsPresContext* presContext = mPresShell->GetPresContext(); + MOZ_ASSERT(presContext, "Our presShell should have a valid presContext"); + if (aChild->IsHTMLElement(nsGkAtoms::body) || (!aContainer && aChild->IsElement())) { - // This might be the element we propagated viewport scrollbar - // styles from. Recompute those. - mPresShell->GetPresContext()->UpdateViewportScrollbarStylesOverride(); + // We might be removing the element that we propagated viewport scrollbar + // styles from. Recompute those. (This clause covers two of the three + // possible scrollbar-propagation sources: the [as aChild or a + // descendant] and the root node. The other possible scrollbar-propagation + // source is a fullscreen element, and we have code elsewhere to update + // scrollbars after fullscreen elements are removed -- specifically, it's + // part of the fullscreen cleanup code called by Element::UnbindFromTree.) + presContext->UpdateViewportScrollbarStylesOverride(); } // XXXldb Do we need to re-resolve style to handle the CSS2 + combinator and @@ -8316,7 +8324,6 @@ nsCSSFrameConstructor::ContentRemoved(nsIContent* aContainer, ClearDisplayContentsIn(aChild, aContainer); } - nsPresContext* presContext = mPresShell->GetPresContext(); #ifdef MOZ_XUL if (NotifyListBoxBody(presContext, aContainer, aChild, aOldNextSibling, childFrame, CONTENT_REMOVED)) { diff --git a/layout/base/nsChangeHint.h b/layout/base/nsChangeHint.h index 318b84840..eb2709de6 100644 --- a/layout/base/nsChangeHint.h +++ b/layout/base/nsChangeHint.h @@ -217,6 +217,16 @@ enum nsChangeHint { */ nsChangeHint_AddOrRemoveTransform = 1 << 27, + /** + * Indicates that the overflow-x and/or overflow-y property changed. + * + * In most cases, this is equivalent to nsChangeHint_ReconstructFrame. But + * in some special cases where the change is really targeting the viewport's + * scrollframe, this is instead equivalent to nsChangeHint_AllReflowHints + * (because the viewport always has an associated scrollframe). + */ + nsChangeHint_CSSOverflowChange = 1 << 28, + // IMPORTANT NOTE: When adding new hints, consider whether you need // to add them to NS_HintsNotHandledForDescendantsIn() below. Please // also add them to RestyleManager::ChangeHintToString and modify @@ -225,7 +235,7 @@ enum nsChangeHint { /** * Dummy hint value for all hints. It exists for compile time check. */ - nsChangeHint_AllHints = (1 << 28) - 1, + nsChangeHint_AllHints = (1 << 29) - 1, }; // Redefine these operators to return nothing. This will catch any use @@ -306,6 +316,7 @@ inline nsChangeHint operator^=(nsChangeHint& aLeft, nsChangeHint aRight) nsChangeHint_UpdatePostTransformOverflow | \ nsChangeHint_UpdateParentOverflow | \ nsChangeHint_ChildrenOnlyTransform | \ + nsChangeHint_CSSOverflowChange | \ nsChangeHint_RecomputePosition | \ nsChangeHint_UpdateContainingBlock | \ nsChangeHint_AddOrRemoveTransform | \ @@ -374,6 +385,48 @@ inline nsChangeHint NS_HintsNotHandledForDescendantsIn(nsChangeHint aChangeHint) nsChangeHint_ClearAncestorIntrinsics | \ nsChangeHint_ClearDescendantIntrinsics | \ nsChangeHint_NeedDirtyReflow) + +// Below are the change hints that we send for ISize & BSize changes. +// Each is similar to nsChangeHint_AllReflowHints with a few changes. + +// * For an ISize change, we send nsChangeHint_AllReflowHints, with two bits +// excluded: nsChangeHint_ClearDescendantIntrinsics (because an ancestor's +// inline-size change can't affect descendant intrinsic sizes), and +// nsChangeHint_NeedDirtyReflow (because ISize changes don't need to *force* +// all descendants to reflow). +#define nsChangeHint_ReflowHintsForISizeChange \ + nsChangeHint(nsChangeHint_AllReflowHints & \ + ~(nsChangeHint_ClearDescendantIntrinsics | \ + nsChangeHint_NeedDirtyReflow)) + +// * For a BSize change, we send almost the same hints as for ISize changes, +// with one extra: nsChangeHint_UpdateComputedBSize. We need this hint because +// BSize changes CAN affect descendant intrinsic sizes, due to replaced +// elements with percentage BSizes in descendants which also have percentage +// BSizes. nsChangeHint_UpdateComputedBSize clears intrinsic sizes for frames +// that have such replaced elements. (We could instead send +// nsChangeHint_ClearDescendantIntrinsics, but that's broader than we need.) +// +// NOTE: You might think that BSize changes could exclude +// nsChangeHint_ClearAncestorIntrinsics (which is inline-axis specific), but we +// do need to send it, to clear cached results from CSS Flex measuring reflows. +#define nsChangeHint_ReflowHintsForBSizeChange \ + nsChangeHint((nsChangeHint_AllReflowHints | \ + nsChangeHint_UpdateComputedBSize) & \ + ~(nsChangeHint_ClearDescendantIntrinsics | \ + nsChangeHint_NeedDirtyReflow)) + +// * For changes to the float area of an already-floated element, we need all +// reflow hints, but not the ones that apply to descendants. +// Our descendants aren't impacted when our float area only changes +// placement but not size/shape. (e.g. if we change which side we float to). +// But our ancestors/siblings are potentially impacted, so we need to send +// the non-descendant reflow hints. +#define nsChangeHint_ReflowHintsForFloatAreaChange \ + nsChangeHint(nsChangeHint_AllReflowHints & \ + ~(nsChangeHint_ClearDescendantIntrinsics | \ + nsChangeHint_NeedDirtyReflow)) + #define NS_STYLE_HINT_REFLOW \ nsChangeHint(NS_STYLE_HINT_VISUAL | nsChangeHint_AllReflowHints) diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp index d9f7b368c..4a54a8432 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -208,6 +208,7 @@ nsPresContext::nsPresContext(nsIDocument* aDocument, nsPresContextType aType) mTextZoom(1.0), mFullZoom(1.0), mOverrideDPPX(0.0), mLastFontInflationScreenSize(gfxSize(-1.0, -1.0)), mPageSize(-1, -1), mPPScale(1.0f), + mViewportScrollbarOverrideNode(nullptr), mViewportStyleScrollbar(NS_STYLE_OVERFLOW_AUTO, NS_STYLE_OVERFLOW_AUTO), mImageAnimationModePref(imgIContainer::kNormalAnimMode), mAllInvalidated(false), @@ -1423,10 +1424,10 @@ nsPresContext::UpdateViewportScrollbarStylesOverride() // Start off with our default styles, and then update them as needed. mViewportStyleScrollbar = ScrollbarStyles(NS_STYLE_OVERFLOW_AUTO, NS_STYLE_OVERFLOW_AUTO); - nsIContent* propagatedFrom = nullptr; + mViewportScrollbarOverrideNode = nullptr; // Don't propagate the scrollbar state in printing or print preview. if (!IsPaginated()) { - propagatedFrom = + mViewportScrollbarOverrideNode = GetPropagatedScrollbarStylesForViewport(this, &mViewportStyleScrollbar); } @@ -1438,13 +1439,13 @@ nsPresContext::UpdateViewportScrollbarStylesOverride() // the styles are from, so that the state of those elements is not // affected across fullscreen change. if (fullscreenElement != document->GetRootElement() && - fullscreenElement != propagatedFrom) { + fullscreenElement != mViewportScrollbarOverrideNode) { mViewportStyleScrollbar = ScrollbarStyles(NS_STYLE_OVERFLOW_HIDDEN, NS_STYLE_OVERFLOW_HIDDEN); } } - return propagatedFrom; + return mViewportScrollbarOverrideNode; } bool diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h index 4fdc60a2e..d8f876291 100644 --- a/layout/base/nsPresContext.h +++ b/layout/base/nsPresContext.h @@ -719,7 +719,18 @@ public: * it was propagated from. */ nsIContent* UpdateViewportScrollbarStylesOverride(); - const ScrollbarStyles& GetViewportScrollbarStylesOverride() + + /** + * Returns the cached result from the last call to + * UpdateViewportScrollbarStylesOverride() -- i.e. return the node + * whose scrollbar styles we have propagated to the viewport (or nullptr if + * there is no such node). + */ + nsIContent* GetViewportScrollbarStylesOverrideNode() const { + return mViewportScrollbarOverrideNode; + } + + const ScrollbarStyles& GetViewportScrollbarStylesOverride() const { return mViewportStyleScrollbar; } @@ -1310,7 +1321,16 @@ protected: nscolor mBodyTextColor; + // This is a non-owning pointer. May be null. If non-null, it's guaranteed + // to be pointing to a node that's still alive, because we'll reset it in + // UpdateViewportScrollbarStylesOverride() as part of the cleanup code + // when this node is removed from the document. (For and the root node, + // this call happens in nsCSSFrameConstructor::ContentRemoved(). For + // fullscreen elements, it happens in the fullscreen-specific cleanup + // invoked by Element::UnbindFromTree().) + nsIContent* MOZ_NON_OWNING_REF mViewportScrollbarOverrideNode; ScrollbarStyles mViewportStyleScrollbar; + uint8_t mFocusRingWidth; bool mExistThrottledUpdates; -- cgit v1.2.3