Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that expects 6 subtest issues.
- This WPT test may be referenced by the following Test IDs:
- /html/semantics/scripting-1/the-template-element/template-for/template-for-foster-parenting.html - WPT Dashboard Interop Dashboard
<!DOCTYPE html>
<meta charset="utf-8">
<title>HTML partial updates: foster parenting inside a template for</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<script>
// Content misnested inside a <template for> is foster parented, and must end up
// in the target rather than in the template's own (empty) contents.
async function parseToRoot(method, html) {
switch (method) {
case "iframe": {
const iframe = document.createElement("iframe");
document.body.appendChild(iframe);
iframe.contentDocument.write(html);
iframe.contentDocument.close();
return iframe.contentDocument.body;
}
case "innerHTML": {
const div = document.createElement("div");
div.innerHTML = html;
return div;
}
case "stream": {
const div = document.createElement("div");
const writer = div.streamAppendHTMLUnsafe().getWriter();
await writer.write(html);
await writer.close();
return div;
}
}
}
function processingInstructions(root) {
const walker = root.ownerDocument.createTreeWalker(
root, NodeFilter.SHOW_PROCESSING_INSTRUCTION);
const result = [];
while (walker.nextNode()) {
result.push(walker.currentNode);
}
return result;
}
// The template is older on the stack of open elements than the table, so this
// resolves through the last table, to before it.
for (const method of ["iframe", "innerHTML", "stream"]) {
promise_test(async () => {
const root = await parseToRoot(method,
`<div id="target"><?start name="p">Old<?end></div>` +
`<template for="p"><table><span>X</span></table></template>`);
const target = root.querySelector("#target");
const span = target.querySelector("span");
const table = target.querySelector("table");
assert_equals(processingInstructions(root).length, 0,
"markers should be removed");
assert_not_equals(span, null, "fostered content should not be lost");
assert_equals(span.parentNode, target,
"fostered content should be a child of the target");
assert_equals(span.textContent, "X");
assert_not_equals(table, null, "table should be in the target");
assert_equals(table.parentNode, target);
assert_equals(
span.compareDocumentPosition(table) & Node.DOCUMENT_POSITION_FOLLOWING,
Node.DOCUMENT_POSITION_FOLLOWING,
"fostered content is inserted before the table");
}, `Foster parenting inside <template for> keeps content in the target (${method})`);
}
// With no table on the stack of open elements this resolves through the last
// template instead, which must still honor its insertion target.
for (const method of ["iframe", "innerHTML", "stream"]) {
promise_test(async () => {
const root = await parseToRoot(method,
`<div id="target"><?start name="p">Old<?end></div>` +
`<template for="p"><tr><div id="fostered">X</div></tr></template>`);
const target = root.querySelector("#target");
const tr = target.querySelector("tr");
const fostered = root.querySelector("#fostered");
assert_equals(processingInstructions(root).length, 0,
"markers should be removed");
assert_not_equals(tr, null, "the tr should be inserted at the target");
assert_equals(tr.parentNode, target);
assert_not_equals(fostered, null, "fostered content should not be lost");
assert_equals(fostered.parentNode, target,
"fostered content should be a child of the target");
assert_equals(fostered.textContent, "X");
assert_array_equals([...target.children].map(el => el.localName),
["tr", "div"], "fostered content follows the tr in the target");
}, `Foster parenting out of a tr inside <template for> keeps content in the target (${method})`);
}
</script>
</body>