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:
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.