From 5112adc69fcbe4b47574c9d6295659eec07ed5ae Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Tue, 5 May 2026 19:22:34 +0800 Subject: [PATCH] Protect DOM mutations against custom element callbacks. Same issue as https://github.com/lightpanda-io/browser/pull/2313 DOM mutations have certain expectations that custom element callbacks can violate. --- src/browser/tests/node/append_child.html | 29 +++++++++++++++++++++ src/browser/tests/node/insert_before.html | 31 +++++++++++++++++++++++ src/browser/webapi/Node.zig | 21 +++++++++++++++ 3 files changed, 81 insertions(+) diff --git a/src/browser/tests/node/append_child.html b/src/browser/tests/node/append_child.html index 151815b9..48bffe91 100644 --- a/src/browser/tests/node/append_child.html +++ b/src/browser/tests/node/append_child.html @@ -65,3 +65,32 @@ assertChildren(['a'], d3); testing.expectEqual(null, b.parentNode); + +
+
+ diff --git a/src/browser/tests/node/insert_before.html b/src/browser/tests/node/insert_before.html index 50dff07c..1018c314 100644 --- a/src/browser/tests/node/insert_before.html +++ b/src/browser/tests/node/insert_before.html @@ -39,3 +39,34 @@ assertChildren([], d1); assertChildren([c1, c2], d2); + +
+
+ diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig index fafa32c6..c70b3802 100644 --- a/src/browser/webapi/Node.zig +++ b/src/browser/webapi/Node.zig @@ -253,6 +253,11 @@ pub fn appendChild(self: *Node, child: *Node, frame: *Frame) !*Node { try frame.adoptNodeTree(child, child_owner.?, parent_owner); } + // A custom element callback can re-parent the node. If it does, we're done + if (child._parent != null) { + return child; + } + try frame.appendNode(self, child, .{ .child_already_connected = child_connected, .adopting_to_new_document = adopting_to_new_document, @@ -605,6 +610,22 @@ pub fn insertBefore(self: *Node, new_node: *Node, ref_node_: ?*Node, frame: *Fra try frame.adoptNodeTree(new_node, child_owner.?, parent_owner); } + // See Node.appendChild: a callback above (disconnectedCallback or + // adoptedCallback) can re-parent new_node. Let that placement stand. + if (new_node._parent != null) { + return new_node; + } + + // The same callback could also have detached ref_node from self. Fall + // back to append so new_node still lands in self. + if (ref_node._parent != self) { + try frame.appendNode(self, new_node, .{ + .child_already_connected = child_already_connected, + .adopting_to_new_document = adopting_to_new_document, + }); + return new_node; + } + try frame.insertNodeRelative( self, new_node,