Files
browser/src/browser/tests/node/append_child.html
Karl Seguin 433c03c709 Handle appendAllChildren mutating the list of children
`appendAllChildren` iterates through the children, but when a child is appended
it can mutate the DOM (only via a custom element connected callback AFAIK) which
can render the iterator invalid. Constantly get parent.firstChild() as the
target.
2026-03-23 21:16:11 +08:00

68 lines
2.0 KiB
HTML

<!DOCTYPE html>
<script src="../testing.js"></script>
<div id=d1></div>
<div id=d2><p id=p1></p></div>
<script id=appendChild>
function assertChildren(expected, parent) {
const actual = Array.from(parent.childNodes).map((n) => n.id);
testing.expectEqual(expected, actual)
for (let child of parent.childNodes) {
testing.expectEqual(parent, child.parentNode);
}
}
const d1 = $('#d1');
const d2 = $('#d2');
const p1 = $('#p1');
assertChildren([], d1);
assertChildren(['p1'], d2);
d1.appendChild(p1);
assertChildren(['p1'], d1);
assertChildren([], d2);
const p2 = document.createElement('p');
p2.id = 'p2';
d1.appendChild(p2);
assertChildren(['p1', 'p2'], d1);
</script>
<div id=d3></div>
<script id=appendChild_fragment_mutation>
// Test that appendChild with DocumentFragment handles synchronous callbacks
// (like custom element connectedCallback) that modify the fragment during iteration.
// This reproduces a bug where the iterator captures "next" node pointers
// before processing, but callbacks can remove those nodes from the fragment.
const d3 = $('#d3');
const fragment = document.createDocumentFragment();
// Create custom element whose connectedCallback modifies the fragment
let bElement = null;
class ModifyingElement extends HTMLElement {
connectedCallback() {
// When this element is connected, remove 'b' from the fragment
if (bElement && bElement.parentNode === fragment) {
fragment.removeChild(bElement);
}
}
}
customElements.define('modifying-element', ModifyingElement);
const a = document.createElement('modifying-element');
a.id = 'a';
const b = document.createElement('span');
b.id = 'b';
bElement = b;
fragment.appendChild(a);
fragment.appendChild(b);
// This should not crash - appendChild should handle the modification gracefully
d3.appendChild(fragment);
// 'a' should be in d3, 'b' was removed by connectedCallback and is now detached
assertChildren(['a'], d3);
testing.expectEqual(null, b.parentNode);
</script>