From c2c1a4037f5c3835e027468e7b3a66768d1e5e15 Mon Sep 17 00:00:00 2001
From: Anne van Kesteren
Date: Sun, 19 Jul 2026 17:38:18 +0200
Subject: [PATCH 1/5] Drop a node the adoption agency cannot reinsert
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The adoption agency algorithm's "Insert lastNode ... at the adjusted insertion
location" step moves an already-inserted node. Script running during parsing can
reparent that node first, so the move would either form a cycle — including one
that crosses a shadow root or a template's contents, i.e. where lastNode is a
host-including inclusive ancestor of the target — or target a Document that
already has an element child. The step used a low-level insertion with no
validity check, so such a move built a cyclic or otherwise invalid tree.
Tests: https://github.com/web-platform-tests/wpt/pull/61398
---
source | 33 +++++++++++++++++++++++++++++----
1 file changed, 29 insertions(+), 4 deletions(-)
diff --git a/source b/source
index 8c38ccd201c..cd06eecb8a4 100644
--- a/source
+++ b/source
@@ -3324,8 +3324,9 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
descendant,
shadow-including ancestor,
shadow-including descendant,
- shadow-including inclusive descendant, and
- shadow-including inclusive ancestor concepts
+ shadow-including inclusive descendant,
+ shadow-including inclusive ancestor, and
+ host-including inclusive ancestor concepts
The first child,
last child,
next sibling,
@@ -147342,8 +147343,32 @@ document.body.appendChild(text);
Let insertionLocation be commonAncestor, after its last child, if
any.
- Insert whatever lastNode ended up being in the previous step at the
- adjusted insertion location given insertionLocation.
+ Let adjustedInsertionLocation be the adjusted insertion location
+ given insertionLocation.
+
+
+ If either of the following is true:
+
+
+ - lastNode is a host-including inclusive ancestor of the node in
+ which adjustedInsertionLocation finds itself; or
+
+ - the node in which adjustedInsertionLocation finds itself is a
+
Document node that already has an element child,
+
+
+ then remove lastNode.
+
+ Otherwise:
+
+
+ Assert: ensure pre-insert validity given
+ lastNode, the node in which adjustedInsertionLocation finds itself,
+ null, and « » does not throw.
+
+ Insert lastNode at adjustedInsertionLocation.
+
+
Create an element for the token for which formattingElement was created,
in the HTML namespace, with furthestBlock as the intended parent.
From 8044ee3c2ca389aee6921d2f8dd4459228c5af3c Mon Sep 17 00:00:00 2001
From: Anne van Kesteren
Date: Tue, 18 Aug 2026 10:55:46 +0200
Subject: [PATCH 2/5] fixup! Drop a node the adoption agency cannot reinsert
x
---
source | 38 ++++++++++++++++++++++++++++----------
1 file changed, 28 insertions(+), 10 deletions(-)
diff --git a/source b/source
index cd06eecb8a4..2bd747dbf7b 100644
--- a/source
+++ b/source
@@ -147346,25 +147346,43 @@ document.body.appendChild(text);
Let adjustedInsertionLocation be the adjusted insertion location
given insertionLocation.
+ Let target be the node in which adjustedInsertionLocation finds
+ itself.
+
+ Let refNode be the node adjustedInsertionLocation is immediately
+ before, or null if adjustedInsertionLocation is after target's last
+ child.
+
+
+ If lastNode's parent is non-null, then
+ remove lastNode.
+
+ This can run script, e.g., because it destroys an iframe
+ element's navigable. Such script can change the tree, which is why the conditions
+ below are checked afterwards rather than before.
+
+
- If either of the following is true:
+ If any of the following are true:
- - lastNode is a host-including inclusive ancestor of the node in
- which adjustedInsertionLocation finds itself; or
+ - lastNode's parent is non-null;
- - the node in which adjustedInsertionLocation finds itself is a
-
Document node that already has an element child,
-
+ lastNode is a host-including inclusive ancestor of
+ target;
- then remove lastNode.
+ target is a Document node that already has an element child;
+ or
- Otherwise:
+ refNode is non-null and its parent is
+ not target,
+
+
+ then lastNode is dropped on the floor. Otherwise:
Assert: ensure pre-insert validity given
- lastNode, the node in which adjustedInsertionLocation finds itself,
- null, and « » does not throw.
+ lastNode, target, refNode, and « » does not throw.
Insert lastNode at adjustedInsertionLocation.
From 8469a4553c8813aaeb151dd701d61df7bd84d1cf Mon Sep 17 00:00:00 2001
From: Anne van Kesteren
Date: Tue, 18 Aug 2026 11:15:10 +0200
Subject: [PATCH 3/5] fixup! Drop a node the adoption agency cannot reinsert
x
---
source | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/source b/source
index 2bd747dbf7b..3f92236de66 100644
--- a/source
+++ b/source
@@ -147353,14 +147353,14 @@ document.body.appendChild(text);
before, or null if adjustedInsertionLocation is after target's last
child.
-
- If lastNode's parent is non-null, then
- remove lastNode.
-
- This can run script, e.g., because it destroys an iframe
- element's navigable. Such script can change the tree, which is why the conditions
- below are checked afterwards rather than before.
-
+ If lastNode's parent is non-null, then
+ remove lastNode.
+
+
If any of the following are true:
From 0a8f796d7926ac5ca55d28870ce4d98111c1840c Mon Sep 17 00:00:00 2001
From: Anne van Kesteren
Date: Tue, 18 Aug 2026 11:15:54 +0200
Subject: [PATCH 4/5] fixup! Drop a node the adoption agency cannot reinsert
x
---
source | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/source b/source
index 3f92236de66..6b47bca27b6 100644
--- a/source
+++ b/source
@@ -147378,7 +147378,11 @@ document.body.appendChild(text);
not target,
- then lastNode is dropped on the floor. Otherwise:
+ then lastNode is dropped on the floor.
+
+
+
+ Otherwise:
Assert: ensure pre-insert validity given
From 16b0955cee58e0441a190669a67069e9ad746b86 Mon Sep 17 00:00:00 2001
From: Anne van Kesteren
Date: Tue, 18 Aug 2026 11:17:04 +0200
Subject: [PATCH 5/5] Update source
---
source | 15 +++++++--------
1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/source b/source
index 6b47bca27b6..72cdb4312c0 100644
--- a/source
+++ b/source
@@ -147353,14 +147353,13 @@ document.body.appendChild(text);
before, or null if adjustedInsertionLocation is after target's last
child.
- If lastNode's parent is non-null, then
- remove lastNode.
-
-
+ If lastNode's parent is non-null, then remove lastNode.
+
-
If any of the following are true: