commit 42f606b1668787424c77283bf1512739dbcf1ab9
parent 264c0f043745b5aa94a8db704da9fd4defe42b00
Author: Emilio Cobos Álvarez <emilio@crisal.io>
Date: Wed, 17 Dec 2025 20:46:31 +0000
Bug 2006613 - Pass batch removal state through to unbind / unlink. r=smaug,dom-core
Differential Revision: https://phabricator.services.mozilla.com/D276843
Diffstat:
3 files changed, 28 insertions(+), 23 deletions(-)
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp
@@ -2854,14 +2854,15 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
nsINode::Unlink(tmp);
- while (tmp->HasChildren()) {
+ BatchRemovalState state{};
+ while (nsCOMPtr<nsIContent> child = tmp->GetLastChild()) {
// Hold a strong ref to the node when we remove it, because we may be
// the last reference to it.
// If this code changes, change the corresponding code in Document's
// unlink impl and ContentUnbinder::UnbindSubtree.
- nsCOMPtr<nsIContent> child = tmp->GetLastChild();
tmp->DisconnectChild(child);
- child->UnbindFromTree();
+ child->UnbindFromTree(/* aNewParent=*/nullptr, &state);
+ state.mIsFirst = false;
}
tmp->UnlinkOriginalDocumentIfStatic();
diff --git a/dom/base/FragmentOrElement.cpp b/dom/base/FragmentOrElement.cpp
@@ -1232,28 +1232,28 @@ class ContentUnbinder : public Runnable {
~ContentUnbinder() { Run(); }
void UnbindSubtree(nsIContent* aNode) {
+ if (!aNode->HasChildren()) {
+ return;
+ }
if (aNode->NodeType() != nsINode::ELEMENT_NODE &&
aNode->NodeType() != nsINode::DOCUMENT_FRAGMENT_NODE) {
return;
}
- FragmentOrElement* container = static_cast<FragmentOrElement*>(aNode);
- if (container->HasChildren()) {
- // Invalidate cached array of child nodes
- container->InvalidateChildNodes();
-
- while (container->HasChildren()) {
- // Hold a strong ref to the node when we remove it, because we may be
- // the last reference to it. We need to call DisconnectChild()
- // before calling UnbindFromTree, since this last can notify various
- // observers and they should really see consistent
- // tree state.
- // If this code changes, change the corresponding code in
- // FragmentOrElement's and Document's unlink impls.
- nsCOMPtr<nsIContent> child = container->GetLastChild();
- container->DisconnectChild(child);
- UnbindSubtree(child);
- child->UnbindFromTree();
- }
+ auto* container = static_cast<FragmentOrElement*>(aNode);
+ // Invalidate cached array of child nodes
+ container->InvalidateChildNodes();
+ BatchRemovalState state{};
+ while (nsCOMPtr<nsIContent> child = container->GetLastChild()) {
+ // Hold a strong ref to the node when we remove it, because we may be
+ // the last reference to it. We need to call DisconnectChild()
+ // before calling UnbindFromTree, since this last can notify various
+ // observers and they should really see consistent tree state.
+ // If this code changes, change the corresponding code in
+ // FragmentOrElement's and Document's unlink impls.
+ container->DisconnectChild(child);
+ UnbindSubtree(child);
+ child->UnbindFromTree(/* aNewParent = */ nullptr, &state);
+ state.mIsFirst = false;
}
}
@@ -1330,14 +1330,15 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FragmentOrElement)
if (tmp->UnoptimizableCCNode() || !nsCCUncollectableMarker::sGeneration) {
// Don't allow script to run while we're unbinding everything.
nsAutoScriptBlocker scriptBlocker;
- while (tmp->HasChildren()) {
+ BatchRemovalState state{};
+ while (nsCOMPtr<nsIContent> child = tmp->GetLastChild()) {
// Hold a strong ref to the node when we remove it, because we may be
// the last reference to it.
// If this code changes, change the corresponding code in Document's
// unlink impl and ContentUnbinder::UnbindSubtree.
- nsCOMPtr<nsIContent> child = tmp->GetLastChild();
tmp->DisconnectChild(child);
child->UnbindFromTree();
+ state.mIsFirst = false;
}
} else if (!tmp->GetParent() && tmp->HasChildren()) {
ContentUnbinder::Append(tmp);
diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h
@@ -251,6 +251,9 @@ enum class BatchRemovalOrder {
};
struct BatchRemovalState {
+ // Whether we're the fist kid getting removed in the batch. Note that that's
+ // different to whether we're the first _child_, if we're removing
+ // back-to-front.
bool mIsFirst = true;
};