Preserve namespaces and template content when cloning nodes - #683
olavoasantos wants to merge 1 commit into
Conversation
8799c1f to
4b9db35
Compare
4b9db35 to
9037ed9
Compare
9037ed9 to
acf7f14
Compare
acf7f14 to
2c806da
Compare
2c806da to
f9873d5
Compare
|
One correction from re-review of Example: the source is Reproduced with both Suggested changeMove graph validation into an iterative topology-snapshot pass before the first interface CloneSnapshot {
children: Node[];
content?: DocumentFragment;
}
function snapshotCloneTree(root: Node) {
const snapshots = new Map<Node, CloneSnapshot>();
const pending = [root];
while (pending.length > 0) {
const source = pending.pop()!;
if (snapshots.has(source)) throwCloneGraphError();
const children =
isElementNode(source) || isDocumentFragmentNode(source)
? Array.from(source.childNodes)
: [];
const content =
isElementNode(source) &&
source.namespaceURI === HTML_NAMESPACE &&
source.localName === 'template'
? (source as HTMLTemplateElement)[CONTENT]
: undefined;
snapshots.set(source, {children, content});
if (content) pending.push(content);
for (let index = children.length - 1; index >= 0; index--) {
pending.push(children[index]!);
}
}
return snapshots;
}Keep the shallow/leaf fast path. For deep container clones, start with: const snapshots = snapshotCloneTree(node);
const cloned = cloneNodeShallow(node, document);Then replace the three live reads in the existing frame loop: // Ordinary children:
sourceChild = snapshots.get(frame.source)!.children[frame.childIndex++];
// Initialized template content:
const content = snapshots.get(frame.source)!.content;
// Content children:
sourceChild = snapshots.get(frame.contentSource)!.children[frame.childIndex++];Remove the clone loop's Please add constructor and hook reentrancy regressions for both clone/import, while retaining the malformed-graph and deep-template tests. Validation: a build-only candidate makes both constructor repros match native output and leaves the other 208 comparison outcomes unchanged. Both hook-move repros, three malformed-graph controls, and 6,000-level clone/import checks pass. This is a probe-validated suggestion, not a production patch or full-suite/typechecked candidate. The unmodified PR head passes 347 polyfill tests, 63 focused core tests, and the locked polyfill compiler/changed-file formatting checks. |
Problem
cloneNode()andDocument.importNode()recreated every element through the HTML-element factory and copied only ordinarychildNodes. Consequently, cloning an SVG element lost its namespace and concreteSVGElementshape, while a deep clone or import of anHTMLTemplateElementomitted the separatetemplate.contentfragment.Impact
Important. Consumers that clone or import implemented SVG/template DOM can receive a tree with incompatible namespace and constructor semantics or silently lose template content. That can make subsequent namespace-sensitive DOM operations and Remote DOM output disagree with the source tree.
Reproduction
Before this change,
svgCopywas an HTMLElementin the HTML namespace, andtemplateCopy.contentwas empty. It now preserves the SVG namespace, qualified name, andSVGElementshape, and a deep template clone contains an independent copy of its content.Change
The iterative traversal also avoids overflowing the call stack on deeply nested template content while retaining clone hook ordering.
Tests
Adds
packages/polyfill/source/tests/clone-import-namespaces.test.ts, covering shallow and deep SVG clone behavior; namespace-qualified elements and attributes; clone/import owner-document assignment; shallow and deep template behavior including nested templates; detached attribute cloning; clone hook ordering; malformed cyclic/repeated graphs; and 6,000-level template-content clone and import cases.Stack
normalize-dom-namespacesValidation
Fresh GitHub CI on restacked head
9037ed9passes: