tor-browser

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

commit 5ad336428440ebc73a9ebef4746a5fd76fb8c54d
parent 08d129ddc5b86a4b259806d379507a2b2be0d0ce
Author: Nicolas Chevobbe <nchevobbe@mozilla.com>
Date:   Tue,  2 Dec 2025 09:55:12 +0000

Bug 2002718 - [devtools] Handle ignored nodes in WalkerActor onMutations. r=devtools-reviewers,jdescottes.

We might get mutations notifications for nodes we ignored but for which we created
actors for their children (i.e. filtered with `FILTER_ACCEPT_CHILDREN`), and we
need to handle their children in the mutation so the client knows that those were
removed/moved (the latter can't really apply at the moment).

For example, this allows to properly select the html element when a `::view-transition`
node was selected and the view transition was removed.
A test case is added for this specific example.

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

Diffstat:
Mdevtools/client/inspector/markup/test/browser_markup_pseudo_view-transition.js | 33+++++++++++++++++++++++++++++++--
Mdevtools/server/actors/inspector/walker.js | 74+++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
2 files changed, 84 insertions(+), 23 deletions(-)

diff --git a/devtools/client/inspector/markup/test/browser_markup_pseudo_view-transition.js b/devtools/client/inspector/markup/test/browser_markup_pseudo_view-transition.js @@ -63,7 +63,7 @@ add_task(async function () { altKey: true, }); - const tree = ` + let tree = ` html head!ignore-children body!ignore-children @@ -83,9 +83,38 @@ add_task(async function () { `.trim(); await assertMarkupViewAsTree(tree, "html", inspector); - // Cancel transition + info( + "Check that html element is selected back when view transition is over and pseudo elements removed" + ); + + const htmlChildren = await inspector.markup.walker.children(htmlNodeFront); + const viewTransitionNodeFront = htmlChildren.nodes[2]; + await selectNode(viewTransitionNodeFront, inspector); + is( + inspector.selection.nodeFront.displayName, + "::view-transition", + "::view-transition element is properly selected" + ); + + info("Stop the view transition"); + const onSelection = inspector.selection.once("new-node-front"); await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => { content.testTransition.skipTransition(); delete content.testTransition; }); + await onSelection; + + is( + inspector.selection.nodeFront.displayName, + "html", + "html element was selected after the view transition was skipped" + ); + + // Check that the ::view-transition elements are removed + tree = ` + html + head!ignore-children + body!ignore-children + `.trim(); + await assertMarkupViewAsTree(tree, "html", inspector); }); diff --git a/devtools/server/actors/inspector/walker.js b/devtools/server/actors/inspector/walker.js @@ -2294,29 +2294,10 @@ class WalkerActor extends Actor { const removedActors = []; const addedActors = []; for (const removed of change.removedNodes) { - const removedActor = this.getNode(removed); - if (!removedActor) { - // If the client never encountered this actor we don't need to - // mention that it was removed. - continue; - } - // While removed from the tree, nodes are saved as orphaned. - this._orphaned.add(removedActor); - removedActors.push(removedActor.actorID); + this._onMutationsNode(removed, removedActors, "removed"); } for (const added of change.addedNodes) { - const addedActor = this.getNode(added); - if (!addedActor) { - // If the client never encounted this actor we don't need to tell - // it about its addition for ownership tree purposes - if the - // client wants to see the new nodes it can ask for children. - continue; - } - // The actor is reconnected to the ownership tree, unorphan - // it and let the client know so that its ownership tree is up - // to date. - this._orphaned.delete(addedActor); - addedActors.push(addedActor.actorID); + this._onMutationsNode(added, addedActors, "added"); } mutation.numChildren = targetActor.numChildren; @@ -2333,6 +2314,57 @@ class WalkerActor extends Actor { } /** + * Handle a mutation on a node + * + * @param {Element} node + * The element that is added/removed in the mutation + * @param {NodeActor[]} actors + * An array that will be populated by this function with the node actors that + * were added + * @param {string} mutationType + * The type of mutation we're handlign ("added" or "removed") + */ + _onMutationsNode(node, actors, mutationType) { + if (mutationType !== "added" && mutationType !== "removed") { + console.error("Unknown mutation type", mutationType); + return; + } + + const actor = this.getNode(node); + if (actor) { + actors.push(actor.actorID); + if (mutationType === "added") { + // The actor is reconnected to the ownership tree, unorphan + // it and let the client know so that its ownership tree is up + // to date. + this._orphaned.delete(actor); + return; + } + if (mutationType === "removed") { + // While removed from the tree, nodes are saved as orphaned. + this._orphaned.add(actor); + return; + } + } + + // Here, we might be in a case where a node is remove/added for which we don't have an + // actor for, but do have actors for its children. + const filter = this.getDocumentWalkerFilter(); + if (filter(node) !== nodeFilterConstants.FILTER_ACCEPT_CHILDREN) { + // At this point, the client never encountered this actor and the node wasn't ignored, + // so we don't need to tell it about this mutation. + // For added node, if the client wants to see the new nodes it can ask for children. + return; + } + + // Otherwise, the node was ignored, so we need to go over its children to find + // actor references we might have. + for (const child of this._rawChildren(node)) { + this._onMutationsNode(child, actors, mutationType); + } + } + + /** * Check if the provided mutation could change the way the target element is * inlined with its parent node. If it might, a custom mutation of type * "inlineTextChild" will be queued.