ladybird/Tests/LibWeb/Text/input/HTML/parser-tree-construction-edge-cases.html
Andreas Kling 1c7519f9ef LibWeb: Treat fragment parser documents as disconnected
Keep the temporary document used by HTML fragment parsing from
running post-connection work while the parser is staging nodes there.
This lets scripts from Range.createContextualFragment() remain
unstarted until the returned fragment is inserted into the real
document, and removes the script-specific preparation guard.

Strengthen parser coverage so contextual fragment scripts must wait
until the fragment is applied before running.
2026-05-17 15:35:56 +02:00

108 lines
6.9 KiB
HTML

<!DOCTYPE html>
<script src="../include.js"></script>
<script>
test(() => {
const HTML_NS = "http://www.w3.org/1999/xhtml";
const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
const SVG_NS = "http://www.w3.org/2000/svg";
let container = document.createElement("div");
container.innerHTML = "<option id=option><annotation-xml><img></option><p id=after></p>";
let option = container.querySelector("#option");
let after = container.querySelector("#after");
println(`option end tag closes option before p: ${after.parentElement === container && !option.contains(after)}`);
container.innerHTML = "<optgroup id=optgroup><annotation-xml><img></optgroup><p id=after></p>";
let optgroup = container.querySelector("#optgroup");
after = container.querySelector("#after");
println(`optgroup end tag closes optgroup before p: ${after.parentElement === container && !optgroup.contains(after)}`);
container.innerHTML = "<form id=form><math><annotation-xml></form><b id=bold>bold</b>";
let form = container.querySelector("#form");
let bold = container.querySelector("#bold");
println(`MathML annotation-xml keeps breakout HTML inside form: ${bold.closest("form") === form}`);
let annotation = document.createElementNS(MATHML_NS, "annotation-xml");
annotation.setAttribute("encoding", "text/html");
annotation.innerHTML = "<span></span>";
println(`annotation-xml innerHTML child namespace: ${annotation.firstElementChild.namespaceURI === HTML_NS}`);
let namespacedAnnotation = document.createElementNS(MATHML_NS, "annotation-xml");
namespacedAnnotation.setAttributeNS("urn:ladybird:test", "x:encoding", "text/html");
namespacedAnnotation.innerHTML = "<foo></foo>";
println(`namespaced annotation-xml encoding attribute stays foreign: ${namespacedAnnotation.firstElementChild.namespaceURI === MATHML_NS}`);
let namespaceOnlyAnnotation = document.createElementNS(MATHML_NS, "annotation-xml");
namespaceOnlyAnnotation.setAttributeNS("urn:ladybird:test", "encoding", "text/html");
namespaceOnlyAnnotation.innerHTML = "<foo></foo>";
println(`namespace-only annotation-xml encoding attribute stays foreign: ${namespaceOnlyAnnotation.firstElementChild.namespaceURI === MATHML_NS}`);
const CUSTOM_NS = "urn:ladybird:test";
let custom = document.createElementNS(CUSTOM_NS, "x");
custom.innerHTML = "<y></y>";
println(`custom namespace innerHTML child namespace: ${custom.firstElementChild.namespaceURI === CUSTOM_NS}`);
let template = document.createElement("template");
template.innerHTML = "<tr><td id=template-cell>x</td></tr>";
let templateCell = template.content.querySelector("#template-cell");
println(`template innerHTML starts in template mode for table rows: ${templateCell?.parentElement?.localName === "tr" && templateCell.textContent === "x"}`);
let formFragmentContext = document.createElement("form");
formFragmentContext.innerHTML = "<form id=inner-form><input id=form-input></form><span id=form-after></span>";
let formInput = formFragmentContext.querySelector("#form-input");
let formAfter = formFragmentContext.querySelector("#form-after");
println(`form innerHTML uses context as form pointer: ${formFragmentContext.querySelector("#inner-form") === null && formInput?.parentElement === formFragmentContext && formAfter?.parentElement === formFragmentContext}`);
let range = document.createRange();
range.selectNodeContents(annotation);
let fragment = range.createContextualFragment("<span></span>");
println(`annotation-xml contextual fragment child namespace: ${fragment.firstElementChild.namespaceURI === HTML_NS}`);
window.htmlFragmentScriptRan = false;
range.selectNodeContents(document.body);
fragment = range.createContextualFragment("<script>window.htmlFragmentScriptRan = true;<\/script>");
let htmlFragmentScriptRanBeforeAppend = window.htmlFragmentScriptRan;
document.body.appendChild(fragment);
println(`HTML script from contextual fragment waits until applied: ${!htmlFragmentScriptRanBeforeAppend && window.htmlFragmentScriptRan}`);
document.body.lastElementChild.remove();
window.svgFragmentScriptRan = false;
range.selectNodeContents(document.body);
fragment = range.createContextualFragment("<svg><script>window.svgFragmentScriptRan = true;<\/script></svg>");
let svgFragmentScriptRanBeforeAppend = window.svgFragmentScriptRan;
document.body.appendChild(fragment);
println(`SVG script from contextual fragment waits until applied: ${!svgFragmentScriptRanBeforeAppend && window.svgFragmentScriptRan}`);
document.body.lastElementChild.remove();
let svgTitle = document.createElementNS(SVG_NS, "title");
svgTitle.innerHTML = "<div id=svg-title-div></div>";
let svgTitleDiv = svgTitle.querySelector("#svg-title-div");
println(`SVG title innerHTML tokenizes in data state: ${svgTitleDiv?.namespaceURI === HTML_NS && svgTitle.childNodes.length === 1}`);
svgTitle.innerHTML = "</title>X";
println(`SVG title innerHTML treats title end tag as markup: ${svgTitle.childNodes.length === 1 && svgTitle.textContent === "X"}`);
range.selectNodeContents(svgTitle);
fragment = range.createContextualFragment("<figure></figure>");
println(`SVG title contextual fragment creates HTML child: ${fragment.firstElementChild?.namespaceURI === HTML_NS && fragment.firstElementChild?.localName === "figure"}`);
let svgDesc = document.createElementNS(SVG_NS, "desc");
svgDesc.innerHTML = "<frameset>X";
println(`SVG desc innerHTML ignores frameset start tag: ${svgDesc.childNodes.length === 1 && svgDesc.textContent === "X"}`);
let prefixedSvgDesc = document.createElementNS(SVG_NS, "svg:desc");
prefixedSvgDesc.innerHTML = "<frameset>X";
println(`prefixed SVG desc innerHTML ignores frameset start tag: ${prefixedSvgDesc.childNodes.length === 1 && prefixedSvgDesc.textContent === "X"}`);
for (const tagName of ["param", "source", "track"]) {
container.innerHTML = `<p><b>text</p><${tagName} id=void-element><span>after`;
let voidElement = container.querySelector("#void-element");
println(`${tagName} start tag skips active formatting reconstruction: ${voidElement.parentElement === container && !voidElement.closest("b")}`);
}
container.innerHTML = "<applet><b><b><b><b></b></b></b><span id=stale></b><p id=after></p>";
let stale = container.querySelector("#stale");
after = container.querySelector("#after");
println(`adoption agency fallback after marker closes stale span: ${!stale.contains(after) && !after.closest("b")}`);
});
</script>