Bug 1741089 - Make attachShadow() not reframe the shadow host unconditionally. r=smaug

We only need to clean up the frames for its flat tree children which are
about to go away from the flat tree, but we don't need to do anything
else. This avoids issues with the following patches because menupopups
depend a lot on their frame tree state.

Differential Revision: https://phabricator.services.mozilla.com/D131083
This commit is contained in:
Emilio Cobos Álvarez 2021-11-16 16:34:51 +00:00
Родитель b5902ee658
Коммит c2c592e87f
5 изменённых файлов: 67 добавлений и 28 удалений

Просмотреть файл

@ -1223,13 +1223,10 @@ already_AddRefed<ShadowRoot> Element::AttachShadowWithoutNameChecks(
//
// This is a minor optimization, but also works around nasty stuff like
// bug 1397876.
if (HasChildren()) {
if (Document* doc = GetComposedDoc()) {
if (PresShell* presShell = doc->GetPresShell()) {
presShell->DestroyFramesForAndRestyle(this);
}
if (Document* doc = GetComposedDoc()) {
if (PresShell* presShell = doc->GetPresShell()) {
presShell->ShadowRootWillBeAttached(*this);
}
MOZ_ASSERT(!GetPrimaryFrame());
}
/**

Просмотреть файл

@ -2978,51 +2978,86 @@ void PresShell::SlotAssignmentWillChange(Element& aElement,
}
#ifdef DEBUG
static void AssertNoFramesInSubtree(nsIContent* aContent) {
for (nsINode* node : ShadowIncludingTreeIterator(*aContent)) {
static void AssertNoFramesOrStyleDataInDescendants(Element& aElement) {
for (nsINode* node : ShadowIncludingTreeIterator(aElement)) {
nsIContent* c = nsIContent::FromNode(node);
if (c == &aElement) {
continue;
}
MOZ_ASSERT(!c->GetPrimaryFrame());
MOZ_ASSERT(!c->IsElement() || !c->AsElement()->HasServoData());
}
}
#endif
void PresShell::DestroyFramesForAndRestyle(Element* aElement) {
#ifdef DEBUG
auto postCondition =
mozilla::MakeScopeExit([&]() { AssertNoFramesInSubtree(aElement); });
auto postCondition = MakeScopeExit([&]() {
MOZ_ASSERT(!aElement->GetPrimaryFrame());
AssertNoFramesOrStyleDataInDescendants(*aElement);
});
#endif
MOZ_ASSERT(aElement);
if (MOZ_UNLIKELY(!mDidInitialize)) {
if (!aElement->HasServoData()) {
// Nothing to do here, the element already is out of the flat tree or is not
// styled.
return;
}
if (!aElement->GetFlattenedTreeParentNode()) {
// Nothing to do here, the element already is out of the frame tree.
return;
}
nsAutoScriptBlocker scriptBlocker;
// Mark ourselves as not safe to flush while we're doing frame destruction.
nsAutoScriptBlocker scriptBlocker;
++mChangeNestCount;
const bool didReconstruct = FrameConstructor()->DestroyFramesFor(aElement);
// Clear the style data from all the flattened tree descendants, but _not_
// from us, since otherwise we wouldn't see the reframe.
RestyleManager::ClearServoDataFromSubtree(aElement,
RestyleManager::IncludeRoot::No);
auto changeHint =
didReconstruct ? nsChangeHint(0) : nsChangeHint_ReconstructFrame;
mPresContext->RestyleManager()->PostRestyleEvent(
aElement, RestyleHint::RestyleSubtree(), changeHint);
--mChangeNestCount;
}
void PresShell::ShadowRootWillBeAttached(Element& aElement) {
#ifdef DEBUG
auto postCondition = MakeScopeExit(
[&]() { AssertNoFramesOrStyleDataInDescendants(aElement); });
#endif
if (!aElement.HasServoData()) {
// Nothing to do here, the element already is out of the flat tree or is not
// styled.
return;
}
if (!aElement.HasChildren()) {
// The element has no children, just avoid the work.
return;
}
// Mark ourselves as not safe to flush while we're doing frame destruction.
nsAutoScriptBlocker scriptBlocker;
++mChangeNestCount;
// NOTE(emilio): We use FlattenedChildIterator intentionally here (rather than
// StyleChildrenIterator), since we don't want to remove ::before / ::after
// content.
FlattenedChildIterator iter(&aElement);
nsCSSFrameConstructor* fc = FrameConstructor();
for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) {
fc->DestroyFramesFor(c);
if (c->IsElement()) {
RestyleManager::ClearServoDataFromSubtree(c->AsElement());
}
}
--mChangeNestCount;
}
void PresShell::PostRecreateFramesFor(Element* aElement) {
if (MOZ_UNLIKELY(!mDidInitialize)) {
// Nothing to do here. In fact, if we proceed and aElement is the root, we

Просмотреть файл

@ -485,10 +485,17 @@ class PresShell final : public nsStubDocumentObserver,
* Destroy the frames for aElement, and reconstruct them asynchronously if
* needed.
*
* Note that this may destroy frames for an ancestor instead.
* Note that this may destroy frames for an arbitrary ancestor, depending on
* the frame tree structure.
*/
void DestroyFramesForAndRestyle(Element* aElement);
/**
* Called when a ShadowRoot will be attached to an element (and thus the flat
* tree children will go away).
*/
void ShadowRootWillBeAttached(Element& aElement);
/**
* Handles all the layout stuff needed when the slot assignment for an element
* is about to change.

Просмотреть файл

@ -8686,14 +8686,14 @@ void nsCSSFrameConstructor::RecreateFramesForContent(
}
}
bool nsCSSFrameConstructor::DestroyFramesFor(Element* aElement) {
MOZ_ASSERT(aElement && aElement->GetParentNode());
bool nsCSSFrameConstructor::DestroyFramesFor(nsIContent* aContent) {
MOZ_ASSERT(aContent && aContent->GetParentNode());
nsIContent* nextSibling = aElement->IsRootOfNativeAnonymousSubtree()
nsIContent* nextSibling = aContent->IsRootOfNativeAnonymousSubtree()
? nullptr
: aElement->GetNextSibling();
: aContent->GetNextSibling();
return ContentRemoved(aElement, nextSibling, REMOVE_FOR_RECONSTRUCTION);
return ContentRemoved(aContent, nextSibling, REMOVE_FOR_RECONSTRUCTION);
}
//////////////////////////////////////////////////////////////////////

Просмотреть файл

@ -297,7 +297,7 @@ class nsCSSFrameConstructor final : public nsFrameManager {
*
* Returns whether a reconstruct was posted for any ancestor.
*/
bool DestroyFramesFor(Element* aElement);
bool DestroyFramesFor(nsIContent* aContent);
// Request to create a continuing frame. This method never returns null.
nsIFrame* CreateContinuingFrame(nsIFrame* aFrame,