Add `clone` to DocumentPartRoot
You can now clone a DocumentPartRoot, and it will a) clone the
underlying document or document_fragment, b) clone all contained `Part`
references from the original to the clone, and c) return you the new
DocumentPartRoot. To allow access to the underlying cloned Node tree, I
also added a DocumentPartRoot.root, which returns the owning Document
or DocumentFragment.
So:
const root = document.getPartRoot();
{ ... add NodePart's and ChildNodePart's to root ... }
const clonedPartRoot = root.clone();
const clonedDocument = clonedPartRoot.root;
To make this work, during cloning (via partRoot.clone), references
are kept in NodeCloningData that point from cloned Nodes to their
clones, and from cloned PartRoots to their clones. Also, a "queue"
of to-be-cloned Parts, keyed by the PartRoot they're waiting for,
is used to handle the common case that the PartRoot for one part
might not be cloned before a Part that refers to it. An example is:
<div id=parent>
<div id=previous_sibling></div>
<div id=middle></div>
<div id=next_sibling></div>
</div>
Assume there is a ChildNodePart with previous_sibling/next_sibling,
and a NodePart pointing to middle which has ChildNodePart as its
PartRoot. During the clone of this tree, the ChildNodePart can only
be cloned once parent, previous_sibling, and next_sibling have all
been cloned. However, middle (and its NodePart) will get reached by
the clone operation before next_sibling. So NodePart here needs to
be queued and retried once ChildNodePart is cloned.
Bug: 1453291
Change-Id: I4a85c3ee02547627bcf73ed610310c742238f12c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4671112
Auto-Submit: Mason Freed <masonf@chromium.org>
Reviewed-by: David Baron <dbaron@chromium.org>
Commit-Queue: David Baron <dbaron@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1168260}
diff --git a/dom/parts/basic-dom-part-objects.tentative.html b/dom/parts/basic-dom-part-objects.tentative.html
index b226c40..7b6ac3a 100644
--- a/dom/parts/basic-dom-part-objects.tentative.html
+++ b/dom/parts/basic-dom-part-objects.tentative.html
@@ -8,7 +8,9 @@
<div id=target style="display:none">
Imperative test element
<span id=a>A</span>
- <span id=b>B</span>
+ <span id=b>B
+ <span id=sub>B-sub</span>
+ </span>
<span id=c>C</span>
</div>
</template>
@@ -29,20 +31,17 @@
}
[false,true].forEach(useTemplate => {
+ const doc = useTemplate ? template.content : document;
+ const target = doc.querySelector('#target');
+ assert_true(!!(doc && target && target.children.length >= 3));
+ const description = useTemplate ? "DocumentFragment" : "Document";
test((t) => {
- let doc;
- if (useTemplate) {
- doc = template.content;
- } else {
- doc = document;
- }
- const target = doc.querySelector('#target');
- assert_true(!!(doc && target && target.children.length >= 3));
const root = doc.getPartRoot();
assert_true(root instanceof DocumentPartRoot);
assert_true(root instanceof PartRoot);
const parts = root.getParts();
assert_equals(parts.length,0,'getParts() should start out empty');
+ assert_true(root.root instanceof (useTemplate ? DocumentFragment : Document));
const nodePart = addCleanup(t,new NodePart(root,target));
assert_true(nodePart instanceof NodePart);
@@ -86,7 +85,42 @@
assert_equals(childNodePart.previousSibling,null,'previousSibling should be null after disconnect');
assert_equals(childNodePart.nextSibling,null,'nextSibling should be null after disconnect');
assert_array_equals(root.getParts(),[nodePartBefore,nodePart]);
- }, `Basic imperative DOM Parts object construction (${useTemplate ? "DocumentFragment" : "Document"})`);
+ }, `Basic imperative DOM Parts object construction (${description})`);
+
+ test((t) => {
+ const root = doc.getPartRoot();
+ const nodePart = addCleanup(t,new NodePart(root,target));
+ const childNodePart = addCleanup(t,new ChildNodePart(root,target.children[0], target.children[2]));
+ const nodePart3 = addCleanup(t,new NodePart(childNodePart,target.children[1].firstChild));
+ const nodePart2 = addCleanup(t,new NodePart(childNodePart,target.children[1].firstChild));
+ const childNodePart2 = addCleanup(t,new ChildNodePart(childNodePart,target.children[1].firstElementChild,target.children[1].firstElementChild));
+ assert_array_equals(root.getParts(),[nodePart,childNodePart]);
+ assert_array_equals(childNodePart.getParts(),[childNodePart2,nodePart3,nodePart2],'Parts on the same Node are returned in the order they were constructed');
+ assert_array_equals(childNodePart2.getParts(),[]);
+
+ const clonedPartRoot = root.clone();
+ const clonedContainer = clonedPartRoot.root;
+ assert_true(clonedPartRoot instanceof DocumentPartRoot);
+ assert_true(clonedContainer instanceof (useTemplate ? DocumentFragment : Document));
+ assert_not_equals(clonedPartRoot,root);
+ assert_not_equals(clonedContainer,doc);
+ assert_equals(doc.innerHTML,clonedContainer.innerHTML);
+ assert_equals(clonedPartRoot.getParts().length,root.getParts().length);
+ assert_array_equals(root.getParts(),[nodePart,childNodePart]);
+ assert_true(!clonedPartRoot.getParts().includes(nodePart),'Original parts should not be retained');
+ assert_true(!clonedPartRoot.getParts().includes(childNodePart));
+ const newNodePart = clonedPartRoot.getParts()[0];
+ const newChildNodePart = clonedPartRoot.getParts()[1];
+ assert_true(newChildNodePart instanceof ChildNodePart,'Cloned parts are out of order');
+ assert_true(newNodePart instanceof NodePart);
+ assert_not_equals(newNodePart.node,target,'Node references should not point to original nodes');
+ assert_equals(newNodePart.node.id,'target','New parts should point to cloned nodes');
+ assert_not_equals(newChildNodePart.previousSibling,a,'Node references should not point to original nodes');
+ assert_equals(newChildNodePart.previousSibling.id,'a');
+ assert_not_equals(newChildNodePart.nextSibling,c,'Node references should not point to original nodes');
+ assert_equals(newChildNodePart.nextSibling.id,'c');
+ assert_equals(newChildNodePart.getParts().length,childNodePart.getParts().length);
+ }, `Cloning (${description})`);
});
test((t) => {