tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 7d68e993a73efdd03020af6a86c71eea5b7f9a6d
parent 09b0c2e50449d8c16ad4500a8db1991366319b96
Author: Emilio Cobos Álvarez <emilio@crisal.io>
Date:   Thu, 18 Dec 2025 09:42:56 +0000

Bug 2006613 - Pass batch removal state through to unbind / unlink. r=smaug,dom-core

Differential Revision: https://phabricator.services.mozilla.com/D276843

Diffstat:
Mdom/base/Document.cpp | 7++++---
Mdom/base/FragmentOrElement.cpp | 43++++++++++++++++++++++---------------------
Mdom/base/nsINode.h | 3+++
3 files changed, 29 insertions(+), 24 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(); + child->UnbindFromTree(/* aNewParent = */ nullptr, &state); + 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; };