Skip to content

Commit

Permalink
moebius#138: Optimize operations on root of deeply-nested frame tree
Browse files Browse the repository at this point in the history
  • Loading branch information
janekptacijarabaci committed Apr 24, 2018
1 parent d0e748c commit eae8d21
Show file tree
Hide file tree
Showing 23 changed files with 651 additions and 40 deletions.
18 changes: 18 additions & 0 deletions dom/base/Element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1844,6 +1844,24 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent)
SetParentIsContent(false);
}

#ifdef DEBUG
// If we can get access to the PresContext, then we sanity-check that
// we're not leaving behind a pointer to ourselves as the PresContext's
// cached provider of the viewport's scrollbar styles.
if (document) {
nsIPresShell* presShell = document->GetShell();
if (presShell) {
nsPresContext* presContext = presShell->GetPresContext();
if (presContext) {
MOZ_ASSERT(this !=
presContext->GetViewportScrollbarStylesOverrideNode(),
"Leaving behind a raw pointer to this node (as having "
"propagated scrollbar styles) - that's dangerous...");
}
}
}
#endif

// Ensure that CSS transitions don't continue on an element at a
// different place in the tree (even if reinserted before next
// animation refresh).
Expand Down
21 changes: 19 additions & 2 deletions dom/base/nsDocument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2054,10 +2054,17 @@ nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
mFirstChild = content->GetNextSibling();
}
mChildren.RemoveChildAt(i);
if (content == mCachedRootElement) {
// Immediately clear mCachedRootElement, now that it's been removed
// from mChildren, so that GetRootElement() will stop returning this
// now-stale value.
mCachedRootElement = nullptr;
}
nsNodeUtils::ContentRemoved(this, content, i, previousSibling);
content->UnbindFromTree();
}
mCachedRootElement = nullptr;
MOZ_ASSERT(!mCachedRootElement,
"After removing all children, there should be no root elem");
}
mInUnlinkOrDeletion = oldVal;

Expand Down Expand Up @@ -3913,8 +3920,18 @@ nsDocument::RemoveChildAt(uint32_t aIndex, bool aNotify)
DestroyElementMaps();
}

doRemoveChildAt(aIndex, aNotify, oldKid, mChildren);
// Preemptively clear mCachedRootElement, since we may be about to remove it
// from our child list, and we don't want to return this maybe-obsolete value
// from any GetRootElement() calls that happen inside of doRemoveChildAt().
// (NOTE: for this to be useful, doRemoveChildAt() must NOT trigger any
// GetRootElement() calls until after it's removed the child from mChildren.
// Any call before that point would restore this soon-to-be-obsolete cached
// answer, and our clearing here would be fruitless.)
mCachedRootElement = nullptr;
doRemoveChildAt(aIndex, aNotify, oldKid, mChildren);
MOZ_ASSERT(mCachedRootElement != oldKid,
"Stale pointer in mCachedRootElement, after we tried to clear it "
"(maybe somebody called GetRootElement() too early?)");
}

void
Expand Down
4 changes: 4 additions & 0 deletions dom/base/nsINode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1907,6 +1907,10 @@ void
nsINode::doRemoveChildAt(uint32_t aIndex, bool aNotify,
nsIContent* aKid, nsAttrAndChildArray& aChildArray)
{
// NOTE: This function must not trigger any calls to
// nsIDocument::GetRootElement() calls until *after* it has removed aKid from
// aChildArray. Any calls before then could potentially restore a stale
// value for our cached root element, per note in nsDocument::RemoveChildAt().
NS_PRECONDITION(aKid && aKid->GetParentNode() == this &&
aKid == GetChildAt(aIndex) &&
IndexOf(aKid) == (int32_t)aIndex, "Bogus aKid");
Expand Down
63 changes: 62 additions & 1 deletion layout/base/RestyleManagerBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Expand Down Expand Up @@ -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();
Expand Down
15 changes: 11 additions & 4 deletions layout/base/nsCSSFrameConstructor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <body> [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
Expand Down Expand Up @@ -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)) {
Expand Down
55 changes: 54 additions & 1 deletion layout/base/nsChangeHint.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 | \
Expand Down Expand Up @@ -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)

Expand Down
9 changes: 5 additions & 4 deletions layout/base/nsPresContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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);
}

Expand All @@ -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
Expand Down
22 changes: 21 additions & 1 deletion layout/base/nsPresContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,18 @@ class nsPresContext : public nsIObserver,
* 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;
}
Expand Down Expand Up @@ -1310,7 +1321,16 @@ class nsPresContext : public nsIObserver,

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 <body> 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;
Expand Down
18 changes: 18 additions & 0 deletions layout/reftests/scrolling/propagated-overflow-style-1-ref.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title>
Reference case with body and html *independently* scrollable.
</title>
<style>
html {
overflow: scroll;
}
body {
overflow: scroll;
}
</style>
</head>
<body>
</body>
</html>
23 changes: 23 additions & 0 deletions layout/reftests/scrolling/propagated-overflow-style-1a.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html class="reftest-wait">
<head>
<title>
Testcase with body and html *independently* scrollable,
with body's "overflow" set dynamically.
</title>
<style>
html {
overflow: scroll;
}
</style>
<script>
function doTest() {
document.body.style.overflow = "scroll";
document.documentElement.removeAttribute("class");
}
window.addEventListener("MozReftestInvalidate", doTest);
</script>
</head>
<body>
</body>
</html>
Loading

0 comments on commit eae8d21

Please sign in to comment.