Skip to content

Commit 6e15bc3

Browse files
committed
Bug 1940405 - Avoid quadratic behavior on some kinds of style invalidation on DOM mutations. r=dshin
This seems simple enough and should prevent the issue. Differential Revision: https://phabricator.services.mozilla.com/D234734
1 parent ff2cb6a commit 6e15bc3

File tree

2 files changed

+43
-43
lines changed

2 files changed

+43
-43
lines changed

layout/base/RestyleManager.cpp

Lines changed: 39 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -134,19 +134,7 @@ void RestyleManager::ContentAppended(nsIContent* aFirstNewContent) {
134134
}
135135

136136
if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
137-
if (container->IsElement()) {
138-
auto* containerElement = container->AsElement();
139-
PostRestyleEvent(containerElement, RestyleHint::RestyleSubtree(),
140-
nsChangeHint(0));
141-
if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
142-
StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
143-
containerElement->GetFirstElementChild(),
144-
/* aForceRestyleSiblings = */ false);
145-
}
146-
} else {
147-
RestylePreviousSiblings(aFirstNewContent);
148-
RestyleSiblingsStartingWith(aFirstNewContent);
149-
}
137+
RestyleWholeContainer(container, selectorFlags);
150138
// Restyling the container is the most we can do here, so we're done.
151139
return;
152140
}
@@ -185,6 +173,24 @@ void RestyleManager::RestyleSiblingsStartingWith(nsIContent* aStartingSibling) {
185173
}
186174
}
187175

176+
void RestyleManager::RestyleWholeContainer(nsINode* aContainer,
177+
NodeSelectorFlags aSelectorFlags) {
178+
if (!mRestyledAsWholeContainer.EnsureInserted(aContainer)) {
179+
return;
180+
}
181+
if (auto* containerElement = Element::FromNode(aContainer)) {
182+
PostRestyleEvent(containerElement, RestyleHint::RestyleSubtree(),
183+
nsChangeHint(0));
184+
if (aSelectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
185+
StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
186+
containerElement->GetFirstElementChild(),
187+
/* aForceRestyleSiblings = */ false);
188+
}
189+
} else {
190+
RestyleSiblingsStartingWith(aContainer->GetFirstChild());
191+
}
192+
}
193+
188194
void RestyleManager::RestyleForEmptyChange(Element* aContainer) {
189195
PostRestyleEvent(aContainer, RestyleHint::RestyleSubtree(), nsChangeHint(0));
190196
StyleSet()->MaybeInvalidateRelativeSelectorForEmptyDependency(*aContainer);
@@ -399,19 +405,7 @@ void RestyleManager::RestyleForInsertOrChange(nsIContent* aChild) {
399405
}
400406

401407
if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
402-
if (container->IsElement()) {
403-
auto* containerElement = container->AsElement();
404-
PostRestyleEvent(containerElement, RestyleHint::RestyleSubtree(),
405-
nsChangeHint(0));
406-
if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
407-
StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
408-
containerElement->GetFirstElementChild(),
409-
/* aForceRestyleSiblings = */ false);
410-
}
411-
} else {
412-
RestylePreviousSiblings(aChild);
413-
RestyleSiblingsStartingWith(aChild);
414-
}
408+
RestyleWholeContainer(container, selectorFlags);
415409
// Restyling the container is the most we can do here, so we're done.
416410
return;
417411
}
@@ -468,10 +462,11 @@ void RestyleManager::ContentWillBeRemoved(nsIContent* aOldChild) {
468462
}
469463

470464
// The container cannot be a document.
471-
MOZ_ASSERT(container->IsElement() || container->IsShadowRoot());
465+
const bool containerIsElement = container->IsElement();
466+
MOZ_ASSERT(containerIsElement || container->IsShadowRoot());
472467

473468
if (selectorFlags & NodeSelectorFlags::HasEmptySelector &&
474-
container->IsElement()) {
469+
containerIsElement) {
475470
// see whether we need to restyle the container
476471
bool isEmpty = true; // :empty or :-moz-only-whitespace
477472
for (nsIContent* child = container->GetFirstChild(); child;
@@ -485,26 +480,25 @@ void RestyleManager::ContentWillBeRemoved(nsIContent* aOldChild) {
485480
break;
486481
}
487482
}
488-
if (isEmpty && container->IsElement()) {
483+
if (isEmpty && containerIsElement) {
489484
RestyleForEmptyChange(container->AsElement());
490485
return;
491486
}
492487
}
493488

494-
if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
495-
if (container->IsElement()) {
496-
auto* containerElement = container->AsElement();
497-
PostRestyleEvent(containerElement, RestyleHint::RestyleSubtree(),
498-
nsChangeHint(0));
499-
if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
500-
StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
501-
containerElement->GetFirstElementChild(),
502-
/* aForceRestyleSiblings = */ false);
503-
}
504-
} else {
505-
RestylePreviousSiblings(aOldChild);
506-
RestyleSiblingsStartingWith(aOldChild);
507-
}
489+
// It is somewhat common to remove all nodes in a container from the
490+
// beginning. If we're doing that, going through the
491+
// HasSlowSelectorLaterSiblings code-path would be quadratic, so that's not
492+
// amazing. Instead, we take the slower path (which also restyles the
493+
// container) in that case. It restyles one more element, but it avoids the
494+
// quadratic behavior.
495+
const bool restyleWholeContainer =
496+
(selectorFlags & NodeSelectorFlags::HasSlowSelector) ||
497+
(selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings &&
498+
!aOldChild->GetPreviousSibling());
499+
500+
if (restyleWholeContainer) {
501+
RestyleWholeContainer(container, selectorFlags);
508502
// Restyling the container is the most we can do here, so we're done.
509503
return;
510504
}
@@ -3267,6 +3261,7 @@ void RestyleManager::DoProcessPendingRestyles(ServoTraversalFlags aFlags) {
32673261

32683262
while (styleSet->StyleDocument(aFlags)) {
32693263
ClearSnapshots();
3264+
mRestyledAsWholeContainer.Clear();
32703265

32713266
// Select scroll anchors for frames that have been scrolled. Do this
32723267
// before processing restyled frames so that anchor nodes are correctly
@@ -3362,6 +3357,7 @@ void RestyleManager::DoProcessPendingRestyles(ServoTraversalFlags aFlags) {
33623357
presContext->FinishedContainerQueryUpdate();
33633358
presContext->UpdateHiddenByContentVisibilityForAnimationsIfNeeded();
33643359
ClearSnapshots();
3360+
mRestyledAsWholeContainer.Clear();
33653361
styleSet->AssertTreeIsClean();
33663362

33673363
mHaveNonAnimationRestyles = false;

layout/base/RestyleManager.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@ class RestyleManager {
506506

507507
ServoStyleSet* StyleSet() const { return PresContext()->StyleSet(); }
508508

509+
void RestyleWholeContainer(nsINode* aContainer, NodeSelectorFlags);
509510
void RestylePreviousSiblings(nsIContent* aStartingSibling);
510511
void RestyleSiblingsStartingWith(nsIContent* aStartingSibling);
511512

@@ -548,6 +549,9 @@ class RestyleManager {
548549
// they're referenced again later in the changelist.
549550
mozilla::UniquePtr<nsTHashSet<const nsIFrame*>> mDestroyedFrames;
550551

552+
// Containers we've already fully restyled / invalidated.
553+
nsTHashSet<RefPtr<nsINode>> mRestyledAsWholeContainer;
554+
551555
protected:
552556
// True if we're in the middle of a nsRefreshDriver refresh
553557
bool mInStyleRefresh;

0 commit comments

Comments
 (0)