tor-browser

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

commit 3959e0d60e63440f0a868a1022dc088023c4ea88
parent aa05b6927eabd1b2e7227dab73d342e79707fb29
Author: Cristina Horotan <chorotan@mozilla.com>
Date:   Thu, 18 Dec 2025 04:04:39 +0200

Revert "Bug 2006613 - Pass batch removal state through to unbind / unlink. r=smaug,dom-core" for causing crashtest failures on nsINode.h

This reverts commit 42f606b1668787424c77283bf1512739dbcf1ab9.

Revert "Bug 2006613 - Plumb batch removal state through unbind, and use it to optimize Shadow DOM removal. r=smaug"

This reverts commit 264c0f043745b5aa94a8db704da9fd4defe42b00.

Diffstat:
Mdom/base/CharacterData.cpp | 3+--
Mdom/base/Document.cpp | 7+++----
Mdom/base/Element.cpp | 3+--
Mdom/base/FragmentOrElement.cpp | 46++++++++++++++++++++++------------------------
Mdom/base/ShadowRoot.cpp | 18+++++++-----------
Mdom/base/ShadowRoot.h | 4+---
Mdom/base/UnbindContext.h | 9++-------
Mdom/base/nsIContent.h | 6++----
Mdom/base/nsIContentInlines.h | 10++++++----
Mdom/base/nsINode.cpp | 2+-
Mdom/base/nsINode.h | 10+++++-----
11 files changed, 51 insertions(+), 67 deletions(-)

diff --git a/dom/base/CharacterData.cpp b/dom/base/CharacterData.cpp @@ -457,8 +457,7 @@ void CharacterData::UnbindFromTree(UnbindContext& aContext) { UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE | NS_REFRAME_IF_WHITESPACE); const bool nullParent = aContext.IsUnbindRoot(this); - HandleShadowDOMRelatedRemovalSteps(nullParent, - !!aContext.GetBatchRemovalState()); + HandleShadowDOMRelatedRemovalSteps(nullParent); if (nullParent) { if (GetParent()) { diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp @@ -2854,15 +2854,14 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document) nsINode::Unlink(tmp); - BatchRemovalState state{}; - while (nsCOMPtr<nsIContent> child = tmp->GetLastChild()) { + while (tmp->HasChildren()) { // 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(/* aNewParent=*/nullptr, &state); - state.mIsFirst = false; + child->UnbindFromTree(); } tmp->UnlinkOriginalDocumentIfStatic(); diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp @@ -2500,8 +2500,7 @@ static bool WillDetachFromShadowOnUnbind(const Element& aElement, void Element::UnbindFromTree(UnbindContext& aContext) { const bool nullParent = aContext.IsUnbindRoot(this); - HandleShadowDOMRelatedRemovalSteps(nullParent, - !!aContext.GetBatchRemovalState()); + HandleShadowDOMRelatedRemovalSteps(nullParent); if (HasFlag(ELEMENT_IN_CONTENT_IDENTIFIER_FOR_LCP)) { OwnerDoc()->ContentIdentifiersForLCP().Remove(this); diff --git a/dom/base/FragmentOrElement.cpp b/dom/base/FragmentOrElement.cpp @@ -148,9 +148,8 @@ nsIContent* nsIContent::FindFirstNonChromeOnlyAccessContent() const { return nullptr; } -void nsIContent::UnbindFromTree(nsINode* aNewParent, - const BatchRemovalState* aBatchState) { - UnbindContext context(*this, aBatchState); +void nsIContent::UnbindFromTree(nsINode* aNewParent) { + UnbindContext context(*this); context.SetIsMove(aNewParent != nullptr); UnbindFromTree(context); } @@ -1232,28 +1231,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; } - 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; + 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(); + } } } @@ -1330,15 +1329,14 @@ 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; - BatchRemovalState state{}; - while (nsCOMPtr<nsIContent> child = tmp->GetLastChild()) { + while (tmp->HasChildren()) { // 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/ShadowRoot.cpp b/dom/base/ShadowRoot.cpp @@ -184,7 +184,7 @@ void ShadowRoot::Unbind() { OwnerDoc()->RemoveComposedDocShadowRoot(*this); } - UnbindContext context(*this, /* aBatchState = */ nullptr); + UnbindContext context(*this); for (nsIContent* child = GetFirstChild(); child; child = child->GetNextSibling()) { child->UnbindFromTree(context); @@ -824,7 +824,7 @@ nsINode* ShadowRoot::CreateElementAndAppendChildAt(nsINode& aParentNode, return aParentNode.AppendChild(*node, rv); } -void ShadowRoot::MaybeUnslotHostChild(nsIContent& aChild, bool aInBatch) { +void ShadowRoot::MaybeUnslotHostChild(nsIContent& aChild) { // Need to null-check the host because we may be unlinked already. MOZ_ASSERT(!GetHost() || aChild.GetParent() == GetHost()); @@ -837,19 +837,15 @@ void ShadowRoot::MaybeUnslotHostChild(nsIContent& aChild, bool aInBatch) { "How did aChild end up assigned to a slot?"); // If the slot is going to start showing fallback content, we need to tell // layout about it. - if ((aInBatch || slot->AssignedNodes().Length() == 1) && - slot->HasChildren()) { + if (slot->AssignedNodes().Length() == 1 && slot->HasChildren()) { InvalidateStyleAndLayoutOnSubtree(slot); } + slot->RemoveAssignedNode(aChild); slot->EnqueueSlotChangeEvent(); - if (aInBatch) { - slot->ClearAssignedNodes(); - } else { - slot->RemoveAssignedNode(aChild); - if (mIsDetailsShadowTree && aChild.IsHTMLElement(nsGkAtoms::summary)) { - MaybeReassignMainSummary(SummaryChangeReason::Deletion); - } + + if (mIsDetailsShadowTree && aChild.IsHTMLElement(nsGkAtoms::summary)) { + MaybeReassignMainSummary(SummaryChangeReason::Deletion); } } diff --git a/dom/base/ShadowRoot.h b/dom/base/ShadowRoot.h @@ -76,9 +76,7 @@ class ShadowRoot final : public DocumentFragment, public DocumentOrShadowRoot { void MaybeSlotHostChild(nsIContent&); // Called when a direct child of our host is removed. Tries to un-slot the // child from the currently-assigned slot, if any. - // If aInBatch is true, we know all the host kids are getting removed (and - // thus we can just unassign all the kids at once). - void MaybeUnslotHostChild(nsIContent&, bool aInBatch); + void MaybeUnslotHostChild(nsIContent&); // Shadow DOM v1 Element* Host() const { diff --git a/dom/base/UnbindContext.h b/dom/base/UnbindContext.h @@ -22,21 +22,16 @@ struct MOZ_STACK_CLASS UnbindContext final { // The parent node of the subtree we're unbinding from. nsINode* GetOriginalSubtreeParent() const { return mOriginalParent; } - explicit UnbindContext(nsINode& aRoot, const BatchRemovalState* aBatchState) - : mRoot(aRoot), - mOriginalParent(aRoot.GetParentNode()), - mBatchState(aBatchState) {} + explicit UnbindContext(nsINode& aRoot) + : mRoot(aRoot), mOriginalParent(aRoot.GetParentNode()) {} void SetIsMove(bool aIsMove) { mIsMove = aIsMove; } bool IsMove() const { return mIsMove; } - const BatchRemovalState* GetBatchRemovalState() const { return mBatchState; } - private: nsINode& mRoot; nsINode* const mOriginalParent; - const BatchRemovalState* const mBatchState = nullptr; // If set, we're moving the shadow-including inclusive ancestor. bool mIsMove = false; diff --git a/dom/base/nsIContent.h b/dom/base/nsIContent.h @@ -114,8 +114,7 @@ class nsIContent : public nsINode { * @note This method is safe to call on nodes that are not bound to a tree. */ virtual void UnbindFromTree(UnbindContext&) = 0; - void UnbindFromTree(nsINode* aNewParent = nullptr, - const BatchRemovalState* aBatchState = nullptr); + void UnbindFromTree(nsINode* aNewParent = nullptr); enum { /** @@ -388,8 +387,7 @@ class nsIContent : public nsINode { // Handles Shadow-DOM related state tracking. Meant to be called near the // beginning of UnbindFromTree(), before the node has lost the reference to // its parent. - inline void HandleShadowDOMRelatedRemovalSteps(bool aNullParent, - bool aInBatch); + inline void HandleShadowDOMRelatedRemovalSteps(bool aNullParent); public: /** diff --git a/dom/base/nsIContentInlines.h b/dom/base/nsIContentInlines.h @@ -251,15 +251,17 @@ inline void nsIContent::HandleShadowDOMRelatedInsertionSteps(bool aHadParent) { } } -inline void nsIContent::HandleShadowDOMRelatedRemovalSteps(bool aNullParent, - bool aInBatch) { +inline void nsIContent::HandleShadowDOMRelatedRemovalSteps(bool aNullParent) { using mozilla::dom::Element; using mozilla::dom::ShadowRoot; if (aNullParent) { - if (Element* parentElement = Element::FromNode(mParent)) { + // FIXME(emilio, bug 1577141): FromNodeOrNull rather than just FromNode + // because XBL likes to call UnbindFromTree at very odd times (with already + // disconnected anonymous content subtrees). + if (Element* parentElement = Element::FromNodeOrNull(mParent)) { if (ShadowRoot* shadow = parentElement->GetShadowRoot()) { - shadow->MaybeUnslotHostChild(*this, aInBatch); + shadow->MaybeUnslotHostChild(*this); } HandleInsertionToOrRemovalFromSlot(); } diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp @@ -2527,7 +2527,7 @@ void nsINode::RemoveChildNode(nsIContent* aKid, bool aNotify, // Invalidate cached array of child nodes InvalidateChildNodes(); - aKid->UnbindFromTree(aNewParent, aState); + aKid->UnbindFromTree(aNewParent); } // When replacing, aRefChild is the content being replaced; when diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h @@ -251,9 +251,6 @@ 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; }; @@ -1050,15 +1047,18 @@ class nsINode : public mozilla::dom::EventTarget { template <BatchRemovalOrder aOrder = BatchRemovalOrder::FrontToBack> void RemoveAllChildren(bool aNotify) { + if (!HasChildren()) { + return; + } BatchRemovalState state{}; - while (HasChildren()) { + do { nsIContent* nodeToRemove = aOrder == BatchRemovalOrder::FrontToBack ? GetFirstChild() : GetLastChild(); RemoveChildNode(nodeToRemove, aNotify, &state, nullptr, MutationEffectOnScript::KeepTrustWorthiness); state.mIsFirst = false; - } + } while (HasChildren()); } /**