summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/dom/nodes/insertion-removing-steps
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/dom/nodes/insertion-removing-steps')
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-append-form-and-script-from-fragment.tentative.html22
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-append-meta-referrer-and-script-from-fragment.tentative.html29
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-button-from-div.tentative.html26
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-custom-from-fragment.tentative.html34
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-default-style-meta-from-fragment.tentative.html35
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-div-from-fragment.tentative.html29
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-iframe.tentative.html89
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-source-from-fragment.tentative.html33
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-style.tentative.html118
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-in-script.tentative.html51
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-with-mutation-observer-takeRecords.html21
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-text-and-script-in-style.tentative.html30
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-text-in-script.tentative.html24
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-three-scripts-from-fragment.tentative.html30
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-three-scripts.tentative.html30
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/blur-event.window.js19
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/insertion-removing-steps-iframe.window.js158
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/insertion-removing-steps-script.window.js48
-rw-r--r--testing/web-platform/tests/dom/nodes/insertion-removing-steps/script-does-not-run-on-child-removal.window.js33
19 files changed, 859 insertions, 0 deletions
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-append-form-and-script-from-fragment.tentative.html b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-append-form-and-script-from-fragment.tentative.html
new file mode 100644
index 0000000000..10351d1645
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-append-form-and-script-from-fragment.tentative.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Node.appendChild: inserting script and associated form</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<button id="someButton" form="someForm"></button>
+<script>
+test(() => {
+ const script = document.createElement("script");
+ const form = document.createElement("form");
+ form.id = "someForm";
+ const fragment = new DocumentFragment();
+ script.textContent = `
+ window.buttonAssociatedForm = document.querySelector("#someButton").form;
+ `;
+ fragment.append(script, form);
+ document.body.append(fragment);
+ assert_equals(window.buttonAssociatedForm, form);
+}, "When adding a script+form in a fragment and the form matches an associated element, " +
+ "the script that checks whether the button is associated to the form should run after " +
+ "inserting the form");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-append-meta-referrer-and-script-from-fragment.tentative.html b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-append-meta-referrer-and-script-from-fragment.tentative.html
new file mode 100644
index 0000000000..d247797603
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-append-meta-referrer-and-script-from-fragment.tentative.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Node.appendChild: inserting script and meta-referrer from a div</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+promise_test(async t => {
+ const script = document.createElement("script");
+ const meta = document.createElement("meta");
+ meta.name = "referrer";
+ meta.content = "no-referrer";
+ const fragment = new DocumentFragment();
+ const done = new Promise(resolve => {
+ window.didFetch = resolve;
+ });
+ script.textContent = `
+ (async function() {
+ const response = await fetch("/html/infrastructure/urls/terminology-0/resources/echo-referrer-text.py")
+ const text = await response.text();
+ window.didFetch(text);
+ })();
+ `;
+ fragment.append(script, meta);
+ document.head.append(fragment);
+ const result = await done;
+ assert_equals(result, "");
+}, "<meta name=referrer> should apply before script, as it is an insertion step " +
+ "and not a post-insertion step");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-button-from-div.tentative.html b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-button-from-div.tentative.html
new file mode 100644
index 0000000000..91f09ae500
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-button-from-div.tentative.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Node.appendChild: inserting script and button from a div</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<form id="form"></form>
+<script>
+let button = null;
+let buttonForm = null;
+test(() => {
+ const form = document.getElementById("form");
+ const script = document.createElement("script");
+ script.textContent = `
+ buttonForm = button.form;
+ `;
+ button = document.createElement("button");
+ const div = document.createElement("div");
+ div.appendChild(script);
+ div.appendChild(button);
+ assert_equals(buttonForm, null);
+ form.appendChild(div);
+ assert_equals(buttonForm, form);
+}, "Script inserted before a form-associated button can observe the button's " +
+ "form, because by the time the script executes, the DOM insertion that " +
+ "associates the button with the form is already done");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-custom-from-fragment.tentative.html b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-custom-from-fragment.tentative.html
new file mode 100644
index 0000000000..23a050f37e
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-custom-from-fragment.tentative.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Node.appendChild: inserting script and custom element from a DocumentFragment</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<body>
+<script>
+let customConstructed = false;
+let customConstructedDuringEarlierScript = false;
+class CustomElement extends HTMLElement {
+ constructor() {
+ super();
+ customConstructed = true;
+ }
+}
+test(() => {
+ const script = document.createElement("script");
+ script.textContent = `
+ customElements.define("custom-element", CustomElement);
+ customConstructedDuringEarlierScript = customConstructed;
+ `;
+ const custom = document.createElement("custom-element");
+ const df = document.createDocumentFragment();
+ df.appendChild(script);
+ df.appendChild(custom);
+ assert_false(customConstructed);
+ assert_false(customConstructedDuringEarlierScript);
+ document.head.appendChild(df);
+ assert_true(customConstructed);
+ assert_true(customConstructedDuringEarlierScript);
+}, "An earlier-inserted script can upgrade a later-inserted custom element, " +
+ "whose upgrading is synchronously observable to the script, since DOM " +
+ "insertion has been completed by the time it runs");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-default-style-meta-from-fragment.tentative.html b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-default-style-meta-from-fragment.tentative.html
new file mode 100644
index 0000000000..a9b7ba633e
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-default-style-meta-from-fragment.tentative.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Node.appendChild: inserting script and default-style meta from a fragment</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<link rel="alternate stylesheet" title="alternative" href="data:text/css,%23div{display:none}">
+<div id="div">hello</div>
+<script>
+let scriptRan = false;
+let computedStyleDuringInsertion = null;
+test(() => {
+ const div = document.getElementById("div");
+ const meta = document.createElement("meta");
+ meta.httpEquiv = "default-style";
+ meta.content = "alternative";
+ const script = document.createElement("script");
+ script.textContent = `
+ computedStyleDuringInsertion = getComputedStyle(div).display;
+ scriptRan = true;
+ `;
+ const df = document.createDocumentFragment();
+ df.appendChild(script);
+ df.appendChild(meta);
+ assert_equals(getComputedStyle(div).display, "block", "div has block display");
+ assert_false(scriptRan, "script has not run before insertion");
+ document.head.appendChild(df);
+ assert_true(scriptRan, "script has run after insertion");
+ assert_equals(computedStyleDuringInsertion, "none",
+ "display: none; style was applied during DOM insertion, before " +
+ "later-inserted script runs");
+ assert_equals(getComputedStyle(div).display, "none",
+ "style remains display: none; after insertion");
+}, "Inserting <meta> that uses alternate stylesheets, applies the style " +
+ "during DOM insertion, and before script runs as a result of any atomic insertions");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-div-from-fragment.tentative.html b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-div-from-fragment.tentative.html
new file mode 100644
index 0000000000..b154c1bf4f
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-div-from-fragment.tentative.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Node.appendChild: inserting script and div from a DocumentFragment</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<body>
+<script>
+let script = null;
+let scriptParent = null;
+let div = null;
+let divParent = null;
+test(() => {
+ script = document.createElement("script");
+ div = document.createElement("div");
+ script.textContent = `
+ divParent = div.parentNode;
+ scriptParent = script.parentNode;
+ `;
+ const df = document.createDocumentFragment();
+ df.appendChild(script);
+ df.appendChild(div);
+ assert_equals(divParent, null);
+ assert_equals(scriptParent, null);
+ document.head.appendChild(df);
+ assert_equals(divParent, scriptParent);
+ assert_equals(divParent, document.head);
+}, "Earlier-inserted scripts can observe the parentNode of later-inserted " +
+ "nodes, because script runs after DOM insertion completes");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-iframe.tentative.html b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-iframe.tentative.html
new file mode 100644
index 0000000000..68b288f24d
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-iframe.tentative.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Node.appendChild: inserting script and iframe</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<body>
+<script>
+
+const kScriptContent = `
+ state = iframe.contentWindow ? "iframe with content window" : "contentWindow is null";
+`;
+
+// This test ensures that a later-inserted script can observe an
+// earlier-inserted iframe's contentWindow.
+test(t => {
+ window.state = "script not run yet";
+ window.iframe = document.createElement("iframe");
+ t.add_cleanup(() => window.iframe.remove());
+
+ const script = document.createElement("script");
+ script.textContent = kScriptContent;
+
+ const div = document.createElement("div");
+ div.appendChild(iframe);
+ div.appendChild(script);
+
+ assert_equals(state, "script not run yet");
+ document.body.appendChild(div);
+ assert_equals(state, "iframe with content window");
+}, "Script inserted after an iframe in the same appendChild() call can " +
+ "observe the iframe's non-null contentWindow");
+
+// The below tests assert that an earlier-inserted script does not observe a
+// later-inserted iframe's contentWindow.
+test(t => {
+ window.state = "script not run yet";
+ window.iframe = document.createElement("iframe");
+ t.add_cleanup(() => window.iframe.remove());
+
+ const script = document.createElement("script");
+ script.textContent = kScriptContent;
+
+ const div = document.createElement("div");
+ div.appendChild(script);
+ div.appendChild(iframe);
+
+ assert_equals(state, "script not run yet");
+ document.body.appendChild(div);
+ assert_equals(state, "contentWindow is null");
+}, "A script inserted atomically before an iframe (using a div) does not " +
+ "observe the iframe's contentWindow, since the 'script running' and " +
+ "'iframe setup' both happen in order, after DOM insertion completes");
+
+test(t => {
+ window.state = "script not run yet";
+ window.iframe = document.createElement("iframe");
+ t.add_cleanup(() => window.iframe.remove());
+
+ const script = document.createElement("script");
+ script.textContent = kScriptContent;
+
+ const df = document.createDocumentFragment();
+ df.appendChild(script);
+ df.appendChild(iframe);
+
+ assert_equals(state, "script not run yet");
+ document.body.appendChild(df);
+ assert_equals(state, "contentWindow is null");
+}, "A script inserted atomically before an iframe (using a DocumentFragment) " +
+ "does not observe the iframe's contentWindow, since the 'script running' " +
+ "and 'iframe setup' both happen in order, after DOM insertion completes");
+
+test(t => {
+ window.state = "script not run yet";
+ window.iframe = document.createElement("iframe");
+ t.add_cleanup(() => window.iframe.remove());
+
+ const script = document.createElement("script");
+ script.textContent = kScriptContent;
+
+ assert_equals(state, "script not run yet");
+ document.body.append(script, iframe);
+
+ assert_equals(state, "contentWindow is null");
+}, "A script inserted atomically before an iframe (using a append() with " +
+ "multiple arguments) does not observe the iframe's contentWindow, since " +
+ "the 'script running' and 'iframe setup' both happen in order, after DOM " +
+ "insertion completes");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-source-from-fragment.tentative.html b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-source-from-fragment.tentative.html
new file mode 100644
index 0000000000..7f93ac43bd
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-source-from-fragment.tentative.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Node.appendChild: inserting script and source from a fragment</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<video id="media"></video>
+<script>
+const happened = [];
+const media = document.getElementById("media");
+test(() => {
+ const source = document.createElement("source");
+ const script = document.createElement("script");
+ script.textContent = `
+ happened.push(media.networkState);
+ `;
+
+ const df = document.createDocumentFragment();
+ df.appendChild(script);
+ df.appendChild(source);
+
+ assert_array_equals(happened, []);
+ media.appendChild(df);
+ // This is because immediately during DOM insertion, before the
+ // post-insertion steps invoke script, `<source>` insertion invokes the
+ // resource selection algorithm [1] which does this assignment. This
+ // assignment takes place before earlier-inserted script elements run
+ // post-insertion.
+ //
+ // [1]: https://html.spec.whatwg.org/#concept-media-load-algorithm
+ assert_array_equals(happened, [HTMLMediaElement.NETWORK_NO_SOURCE]);
+}, "Empty <source> immediately sets media.networkState during DOM insertion, " +
+ "so that an earlier-running script can observe networkState");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-style.tentative.html b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-style.tentative.html
new file mode 100644
index 0000000000..d3365f8a5e
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-style.tentative.html
@@ -0,0 +1,118 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Node.appendChild: inserting a script and a style where the script modifies the style</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<body>
+<script>
+// <script> & <style> element tests.
+test(() => {
+ window.happened = [];
+ window.style = document.createElement("style");
+ let styleSheet = null;
+
+ style.appendChild(new Text("body {}"));
+ const script = document.createElement("script");
+ script.textContent = `
+ styleSheet = style.sheet;
+ happened.push(style.sheet ? "sheet" : null);
+ style.appendChild(new Text("body {}"));
+ happened.push(style.sheet?.cssRules.length);
+ `;
+
+ const div = document.createElement("div");
+ div.appendChild(script);
+ div.appendChild(style);
+
+ assert_array_equals(happened, []);
+ document.body.appendChild(div);
+ assert_array_equals(happened, ["sheet", 2]);
+ assert_not_equals(style.sheet, styleSheet, "style sheet was created only once");
+}, "An earlier-inserted <script> synchronously observes a later-inserted " +
+ "<style> (via a div) being applied");
+
+test(() => {
+ window.happened = [];
+ window.style = document.createElement("style");
+ let styleSheet = null;
+
+ style.appendChild(new Text("body {}"));
+ const script = document.createElement("script");
+ script.textContent = `
+ styleSheet = style.sheet;
+ happened.push(style.sheet ? "sheet" : null);
+ style.appendChild(new Text("body {}"));
+ happened.push(style.sheet?.cssRules.length);
+`;
+
+ const df = document.createDocumentFragment();
+ df.appendChild(script);
+ df.appendChild(style);
+
+ assert_array_equals(happened, []);
+ document.body.appendChild(df);
+ assert_array_equals(happened, ["sheet", 2]);
+ assert_not_equals(style.sheet, styleSheet, "style sheet was created only once");
+}, "An earlier-inserted <script> synchronously observes a later-inserted " +
+ "<style> (via a DocumentFragment) being applied");
+
+// <script> & <link rel=stylesheet> element tests.
+test(() => {
+ window.happened = [];
+ window.link = document.createElement("link");
+ link.rel = "stylesheet";
+ link.href = "data:text/css,";
+
+ const script = document.createElement("script");
+ script.textContent = `
+ happened.push(link.sheet ? "sheet" : null);
+ `;
+
+ const df = document.createDocumentFragment();
+ df.appendChild(script);
+ df.appendChild(link);
+
+ assert_array_equals(happened, []);
+ document.body.appendChild(df);
+ assert_array_equals(happened, ["sheet"]);
+}, "Earlier-inserted <script> (via a DocumentFragment) synchronously " +
+ "observes a later-inserted <link rel=stylesheet>'s CSSStyleSheet creation");
+
+test(() => {
+ window.happened = [];
+ window.link = document.createElement("link");
+ link.rel = "stylesheet";
+ link.href = "data:text/css,";
+
+ const script = document.createElement("script");
+ script.textContent = `
+ happened.push(link.sheet ? "sheet" : null);
+`;
+
+ const div = document.createElement("div");
+ div.appendChild(script);
+ div.appendChild(link);
+
+ assert_array_equals(happened, []);
+ document.body.appendChild(div);
+ assert_array_equals(happened, ["sheet"]);
+}, "Earlier-inserted <script> (via a div) synchronously observes a " +
+ "later-inserted <link rel=stylesheet>'s CSSStyleSheet creation");
+
+test(() => {
+ window.happened = [];
+ window.link = document.createElement("link");
+ link.rel = "stylesheet";
+ link.href = "data:text/css,";
+
+ const script = document.createElement("script");
+ script.textContent = `
+ happened.push(link.sheet ? "sheet" : null);
+`;
+
+ assert_array_equals(happened, []);
+ document.body.append(script, link);
+ assert_array_equals(happened, ["sheet"]);
+}, "Earlier-inserted <script> (via a append()) synchronously observes a " +
+ "later-inserted <link rel=stylesheet>'s CSSStyleSheet creation");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-in-script.tentative.html b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-in-script.tentative.html
new file mode 100644
index 0000000000..39c4393323
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-in-script.tentative.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Node.appendChild: inserting a script and some code in an empty script</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<body>
+<script id="s1"></script>
+<script>
+const happened = [];
+test(() => {
+ const s1 = document.getElementById("s1");
+ const s2 = document.createElement("script");
+
+ // This script, which is ultimately a *child* of the
+ // already-connected-but-empty `s1` script, runs second, after `s1` runs. See
+ // the example in
+ // http://html.spec.whatwg.org/C/#script-processing-model:children-changed-steps
+ // for more information.
+ //
+ // HISTORICAL CONTEXT: There used to be a condition in the HTML standard that
+ // said an "outer" script must be "prepared" when a node gets inserted into
+ // the script. BUT it also stipulated that if the insertion consists of any
+ // "inner" (nested, essentially) script elements, then this "outer" script
+ // must prepare/execute after any of those "inner" newly-inserted scripts
+ // themselves get prepared.
+ //
+ // This changed in https://github.com/whatwg/html/pull/10188.
+ s2.textContent = `
+ happened.push("s2");
+
+ // This text never executes in the outer script, because by the time this
+ // gets appended, the outer script has "already started" [1], so it does not
+ // get re-prepared/executed a second time.
+ //
+ // [1]: https://html.spec.whatwg.org/C#already-started
+ s1.appendChild(new Text("happened.push('s1ran');"));
+
+ happened.push("s2ran");
+`;
+
+ const df = document.createDocumentFragment();
+ df.appendChild(new Text(`happened.push("s1");`));
+ df.appendChild(s2);
+
+ assert_array_equals(happened, []);
+ s1.appendChild(df);
+ assert_array_equals(happened, ["s1", "s2", "s2ran"]);
+}, "An outer script whose preparation/execution gets triggered by the " +
+ "insertion of a 'nested'/'inner' script, executes *before* the inner " +
+ "script executes");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-with-mutation-observer-takeRecords.html b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-with-mutation-observer-takeRecords.html
new file mode 100644
index 0000000000..33598e6408
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-script-with-mutation-observer-takeRecords.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Node.appendChild: inserted script should be able to take own mutation record</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<body>
+<main></main>
+<script>
+
+test(() => {
+ window.mutationObserver = new MutationObserver(() => {});
+ window.mutationObserver.observe(document.querySelector("main"), {childList: true});
+ const script = document.createElement("script");
+ script.textContent = `
+ window.mutationRecords = window.mutationObserver.takeRecords();
+ `;
+ document.querySelector("main").appendChild(script);
+ assert_equals(window.mutationRecords.length, 1);
+ assert_array_equals(window.mutationRecords[0].addedNodes, [script]);
+}, "An inserted script should be able to observe its own mutation record with takeRecords");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-text-and-script-in-style.tentative.html b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-text-and-script-in-style.tentative.html
new file mode 100644
index 0000000000..850af680a0
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-text-and-script-in-style.tentative.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Node.appendChild: inserting text and script nodes in a style element</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<style id="style"></style>
+<body>
+<script>
+const happened = []
+const style = document.getElementById("style");
+test(() => {
+ const r1 = new Text("body {}");
+ const r2 = new Text("body {}");
+ const script = document.createElement("script");
+ script.textContent = `
+ happened.push(style.sheet.cssRules.length);
+ `;
+
+ const df = document.createDocumentFragment();
+ df.appendChild(r1);
+ df.appendChild(script);
+ df.appendChild(r2);
+
+ assert_array_equals(happened, []);
+ style.appendChild(df);
+ assert_array_equals(happened, [2]);
+}, "All style rules appended to a <style> element are inserted and " +
+ "script-observable to scripts inserted in the `<style>` element, by the " +
+ "time scripts execute after DOM insertions.");
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-text-in-script.tentative.html b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-text-in-script.tentative.html
new file mode 100644
index 0000000000..4d6543695c
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-text-in-script.tentative.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Node.appendChild: inserting two text nodes in an empty script</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<body>
+ <script id="script"></script>
+<script>
+const happened = [];
+test(() => {
+ const script = document.getElementById("script");
+ const df = document.createDocumentFragment();
+ df.appendChild(new Text("happened.push('t1');"));
+ df.appendChild(new Text("happened.push('t2');"));
+ assert_array_equals(happened, []);
+ script.appendChild(df);
+ assert_array_equals(happened, ["t1", "t2"]);
+ // At this point it's already executed so further motifications are a no-op
+ script.appendChild(new Text("happened.push('t3');"));
+ script.textContent = "happened.push('t4');"
+ script.text = "happened.push('t5');"
+ assert_array_equals(happened, ["t1", "t2"]);
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-three-scripts-from-fragment.tentative.html b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-three-scripts-from-fragment.tentative.html
new file mode 100644
index 0000000000..a7b7405b64
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-three-scripts-from-fragment.tentative.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Node.appendChild: inserting three scripts from a document fragment</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<body>
+ <script>
+const s1 = document.createElement("script");
+const s2 = document.createElement("script");
+const s3 = document.createElement("script");
+const happened = [];
+
+test(() => {
+ s1.textContent = `
+ s3.appendChild(new Text("happened.push('s3');"));
+ happened.push("s1");
+ `;
+ s2.textContent = `
+ happened.push("s2");
+ `;
+ const df = document.createDocumentFragment();
+ df.appendChild(s1);
+ df.appendChild(s2);
+ df.appendChild(s3);
+
+ assert_array_equals(happened, []);
+ document.body.appendChild(df);
+ assert_array_equals(happened, ["s3", "s1", "s2"]);
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-three-scripts.tentative.html b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-three-scripts.tentative.html
new file mode 100644
index 0000000000..6ffa35515e
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/Node-appendChild-three-scripts.tentative.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Node.appendChild: inserting three scripts from a div</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<body>
+ <script>
+const s1 = document.createElement("script");
+const s2 = document.createElement("script");
+const s3 = document.createElement("script");
+const happened = [];
+
+test(() => {
+ s1.textContent = `
+ s3.appendChild(new Text("happened.push('s3');"));
+ happened.push("s1");
+ `;
+ s2.textContent = `
+ happened.push("s2");
+ `;
+ const div = document.createElement("div");
+ div.appendChild(s1);
+ div.appendChild(s2);
+ div.appendChild(s3);
+
+ assert_array_equals(happened, []);
+ document.body.appendChild(div);
+ assert_array_equals(happened, ["s3", "s1", "s2"]);
+});
+</script>
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/blur-event.window.js b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/blur-event.window.js
new file mode 100644
index 0000000000..4c8cd85cbf
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/blur-event.window.js
@@ -0,0 +1,19 @@
+test(() => {
+ const input = document.body.appendChild(document.createElement('input'));
+ input.focus();
+
+ let blurCalled = false;
+ input.onblur = e => blurCalled = true;
+ input.remove();
+ assert_false(blurCalled, "Blur event was not fired");
+}, "<input> element does not fire blur event upon DOM removal");
+
+test(() => {
+ const button = document.body.appendChild(document.createElement('button'));
+ button.focus();
+
+ let blurCalled = false;
+ button.onblur = e => blurCalled = true;
+ button.remove();
+ assert_false(blurCalled, "Blur event was not fired");
+}, "<button> element does not fire blur event upon DOM removal");
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/insertion-removing-steps-iframe.window.js b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/insertion-removing-steps-iframe.window.js
new file mode 100644
index 0000000000..60c2bec0c8
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/insertion-removing-steps-iframe.window.js
@@ -0,0 +1,158 @@
+// These tests ensure that:
+// 1. The HTML element insertion steps for iframes [1] run *after* all DOM
+// insertion mutations associated with any given call to
+// #concept-node-insert [2] (which may insert many elements at once).
+// Consequently, a preceding element's insertion steps can observe the
+// side-effects of later elements being connected to the DOM, but cannot
+// observe the side-effects of the later element's own insertion steps [1],
+// since insertion steps are run in order after all DOM insertion mutations
+// are complete.
+// 2. The HTML element removing steps for iframes [3] *do not* synchronously
+// run script during child navigable destruction. Therefore, script cannot
+// observe the state of the DOM in the middle of iframe removal, even when
+// multiple iframes are being removed in the same task. Iframe removal,
+// from the perspective of the parent's DOM tree, is atomic.
+//
+// [1]: https://html.spec.whatwg.org/C#the-iframe-element:html-element-insertion-steps
+// [2]: https://dom.spec.whatwg.org/#concept-node-insert
+// [3]: https://html.spec.whatwg.org/C#the-iframe-element:html-element-removing-steps
+
+promise_test(async t => {
+ const fragment = new DocumentFragment();
+
+ const iframe1 = fragment.appendChild(document.createElement('iframe'));
+ const iframe2 = fragment.appendChild(document.createElement('iframe'));
+
+ t.add_cleanup(() => {
+ iframe1.remove();
+ iframe2.remove();
+ });
+
+ let iframe1Loaded = false, iframe2Loaded = false;
+ iframe1.onload = e => {
+ // iframe1 assertions:
+ iframe1Loaded = true;
+ assert_equals(window.frames.length, 1,
+ "iframe1 load event can observe its own participation in the frame " +
+ "tree");
+ assert_equals(iframe1.contentWindow, window.frames[0]);
+
+ // iframe2 assertions:
+ assert_false(iframe2Loaded,
+ "iframe2's load event hasn't fired before iframe1's");
+ assert_true(iframe2.isConnected,
+ "iframe1 can observe that iframe2 is connected to the DOM...");
+ assert_equals(iframe2.contentWindow, null,
+ "... but iframe1 cannot observe iframe2's contentWindow because " +
+ "iframe2's insertion steps have not been run yet");
+ };
+
+ iframe2.onload = e => {
+ iframe2Loaded = true;
+ assert_equals(window.frames.length, 2,
+ "iframe2 load event can observe its own participation in the frame tree");
+ assert_equals(iframe1.contentWindow, window.frames[0]);
+ assert_equals(iframe2.contentWindow, window.frames[1]);
+ };
+
+ // Synchronously consecutively adds both `iframe1` and `iframe2` to the DOM,
+ // invoking their insertion steps (and thus firing each of their `load`
+ // events) in order. `iframe1` will be able to observe itself in the DOM but
+ // not `iframe2`, and `iframe2` will be able to observe both itself and
+ // `iframe1`.
+ document.body.append(fragment);
+ assert_true(iframe1Loaded, "iframe1 loaded");
+ assert_true(iframe2Loaded, "iframe2 loaded");
+}, "Insertion steps: load event fires synchronously *after* iframe DOM " +
+ "insertion, as part of the iframe element's insertion steps");
+
+// There are several versions of the removal variant, since there are several
+// ways to remove multiple elements "at once". For example:
+// 1. `node.innerHTML = ''` ultimately runs
+// https://dom.spec.whatwg.org/#concept-node-replace-all which removes all
+// of a node's children.
+// 2. `node.replaceChildren()` which follows roughly the same path above.
+// 3. `node.remove()` on a parent of many children will invoke not the DOM
+// remove algorithm, but rather the "removing steps" hook [1], for each
+// child.
+//
+// [1]: https://dom.spec.whatwg.org/#concept-node-remove-ext
+
+function runRemovalTest(removal_method) {
+ promise_test(async t => {
+ const div = document.createElement('div');
+
+ const iframe1 = div.appendChild(document.createElement('iframe'));
+ const iframe2 = div.appendChild(document.createElement('iframe'));
+ document.body.append(div);
+
+ // Now that both iframes have been inserted into the DOM, we'll set up a
+ // MutationObserver that we'll use to ensure that multiple synchronous
+ // mutations (removals) are only observed atomically at the end. Specifically,
+ // the observer's callback is not invoked synchronously for each removal.
+ let observerCallbackInvoked = false;
+ const removalObserver = new MutationObserver(mutations => {
+ assert_false(observerCallbackInvoked,
+ "MO callback is only invoked once, not multiple times, i.e., for " +
+ "each removal");
+ observerCallbackInvoked = true;
+ assert_equals(mutations.length, 1, "Exactly one MutationRecord is recorded");
+ assert_equals(mutations[0].removedNodes.length, 2);
+ assert_equals(window.frames.length, 0,
+ "No iframe Windows exist when the MO callback is run");
+ assert_equals(document.querySelector('iframe'), null,
+ "No iframe elements are connected to the DOM when the MO callback is " +
+ "run");
+ });
+
+ removalObserver.observe(div, {childList: true});
+ t.add_cleanup(() => removalObserver.disconnect());
+
+ let iframe1UnloadFired = false, iframe2UnloadFired = false;
+ let iframe1PagehideFired = false, iframe2PagehideFired = false;
+ iframe1.contentWindow.addEventListener('pagehide', e => {
+ assert_false(iframe1UnloadFired, "iframe1 pagehide fires before unload");
+ iframe1PagehideFired = true;
+ });
+ iframe2.contentWindow.addEventListener('pagehide', e => {
+ assert_false(iframe2UnloadFired, "iframe2 pagehide fires before unload");
+ iframe2PagehideFired = true;
+ });
+ iframe1.contentWindow.addEventListener('unload', e => iframe1UnloadFired = true);
+ iframe2.contentWindow.addEventListener('unload', e => iframe2UnloadFired = true);
+
+ // Each `removal_method` will trigger the synchronous removal of each of
+ // `div`'s (iframe) children. This will synchronously, consecutively
+ // invoke HTML's "destroy a child navigable" (per [1]), for each iframe.
+ //
+ // [1]: https://html.spec.whatwg.org/C#the-iframe-element:destroy-a-child-navigable
+
+ if (removal_method === 'replaceChildren') {
+ div.replaceChildren();
+ } else if (removal_method === 'remove') {
+ div.remove();
+ } else if (removal_method === 'innerHTML') {
+ div.innerHTML = '';
+ }
+
+ assert_false(iframe1PagehideFired, "iframe1 pagehide did not fire");
+ assert_false(iframe2PagehideFired, "iframe2 pagehide did not fire");
+ assert_false(iframe1UnloadFired, "iframe1 unload did not fire");
+ assert_false(iframe2UnloadFired, "iframe2 unload did not fire");
+
+ assert_false(observerCallbackInvoked,
+ "MO callback is not invoked synchronously after removals");
+
+ // Wait one microtask.
+ await Promise.resolve();
+
+ if (removal_method !== 'remove') {
+ assert_true(observerCallbackInvoked,
+ "MO callback is invoked asynchronously after removals");
+ }
+ }, `Removing steps (${removal_method}): script does not run synchronously during iframe destruction`);
+}
+
+runRemovalTest('innerHTML');
+runRemovalTest('replaceChildren');
+runRemovalTest('remove');
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/insertion-removing-steps-script.window.js b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/insertion-removing-steps-script.window.js
new file mode 100644
index 0000000000..a1be3e1dd3
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/insertion-removing-steps-script.window.js
@@ -0,0 +1,48 @@
+promise_test(async t => {
+ const fragmentWithTwoScripts = new DocumentFragment();
+ const script0 = document.createElement('script');
+ const script1 = fragmentWithTwoScripts.appendChild(document.createElement('script'));
+ const script2 = fragmentWithTwoScripts.appendChild(document.createElement('script'));
+
+ window.kBaselineNumberOfScripts = document.scripts.length;
+ assert_equals(document.scripts.length, kBaselineNumberOfScripts,
+ "The WPT infra starts out with exactly 3 scripts");
+
+ window.script0Executed = false;
+ script0.innerText = `
+ script0Executed = true;
+ assert_equals(document.scripts.length, kBaselineNumberOfScripts + 1,
+ 'script0 can observe itself and no other scripts');
+ `;
+
+ window.script1Executed = false;
+ script1.innerText = `
+ script1Executed = true;
+ assert_equals(document.scripts.length, kBaselineNumberOfScripts + 2,
+ "script1 executes synchronously, and thus observes only itself and " +
+ "previous scripts");
+ `;
+
+ window.script2Executed = false;
+ script2.innerText = `
+ script2Executed = true;
+ assert_equals(document.scripts.length, kBaselineNumberOfScripts + 3,
+ "script2 executes synchronously, and thus observes itself and all " +
+ "previous scripts");
+ `;
+
+ assert_false(script0Executed, "Script0 does not execute before append()");
+ document.body.append(script0);
+ assert_true(script0Executed,
+ "Script0 executes synchronously during append()");
+
+ assert_false(script1Executed, "Script1 does not execute before append()");
+ assert_false(script2Executed, "Script2 does not execute before append()");
+ document.body.append(fragmentWithTwoScripts);
+ assert_true(script1Executed,
+ "Script1 executes synchronously during fragment append()");
+ assert_true(script2Executed,
+ "Script2 executes synchronously during fragment append()");
+}, "Script node insertion is not atomic with regard to execution. Each " +
+ "script is synchronously executed during the HTML element insertion " +
+ "steps hook");
diff --git a/testing/web-platform/tests/dom/nodes/insertion-removing-steps/script-does-not-run-on-child-removal.window.js b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/script-does-not-run-on-child-removal.window.js
new file mode 100644
index 0000000000..ed5bfbaa60
--- /dev/null
+++ b/testing/web-platform/tests/dom/nodes/insertion-removing-steps/script-does-not-run-on-child-removal.window.js
@@ -0,0 +1,33 @@
+// See:
+// - https://github.com/whatwg/dom/issues/808
+// - https://github.com/whatwg/dom/pull/1261
+// - https://github.com/whatwg/html/pull/10188
+// - https://source.chromium.org/chromium/chromium/src/+/604e798ec6ee30f44d57a5c4a44ce3dab3a871ed
+// - https://github.com/whatwg/dom/pull/732#pullrequestreview-328249015
+// - https://github.com/whatwg/html/pull/4354#issuecomment-476038918
+test(() => {
+ window.script_did_run = false;
+
+ const script = document.createElement('script');
+ // This prevents execution on insertion.
+ script.type = '0';
+ script.textContent = `script_did_run = true;`;
+ document.body.append(script);
+ assert_false(script_did_run,
+ 'Appending script with invalid type does not trigger execution');
+
+ const div = document.createElement('div');
+ script.append(div);
+ assert_false(script_did_run,
+ 'Appending a child to an invalid-type script does not trigger execution');
+
+ // This enables, but does not trigger, execution.
+ script.type = '';
+ assert_false(script_did_run,
+ 'Unsetting script type does not trigger execution');
+
+ div.remove();
+ assert_false(script_did_run,
+ 'Removing child from valid script that has not already run, does not ' +
+ 'trigger execution');
+}, "Script execution is never triggered on child removals");