summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/resources/moving-between-documents-helper.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/resources/moving-between-documents-helper.js')
-rw-r--r--testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/resources/moving-between-documents-helper.js214
1 files changed, 214 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/resources/moving-between-documents-helper.js b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/resources/moving-between-documents-helper.js
new file mode 100644
index 0000000000..de4af6ac10
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/scripting-1/the-script-element/moving-between-documents/resources/moving-between-documents-helper.js
@@ -0,0 +1,214 @@
+"use strict";
+
+function createDocument(documentType, result, inlineOrExternal, type, hasBlockingStylesheet) {
+ return new Promise((resolve, reject) => {
+ const iframe = document.createElement("iframe");
+ iframe.src =
+ "resources/moving-between-documents-iframe.py" +
+ "?result=" + result +
+ "&inlineOrExternal=" + inlineOrExternal +
+ "&type=" + type +
+ "&hasBlockingStylesheet=" + hasBlockingStylesheet +
+ "&cache=" + Math.random();
+ // As blocking stylesheets delays Document load events, we use
+ // DOMContentLoaded here.
+ // After that point, we expect iframe.contentDocument exists
+ // while still waiting for blocking stylesheet loading.
+ document.body.appendChild(iframe);
+
+ window.addEventListener('message', (event) => {
+ if (documentType === "iframe") {
+ resolve([iframe.contentWindow, iframe.contentDocument]);
+ } else if (documentType === "createHTMLDocument") {
+ resolve([
+ iframe.contentWindow,
+ iframe.contentDocument.implementation.createHTMLDocument("")]);
+ } else {
+ reject(new Error("Invalid document type: " + documentType));
+ }
+ }, {once: true});
+ });
+}
+
+window.didExecute = undefined;
+
+// For a script, there are three associated Documents that can
+// potentially different:
+//
+// [1] script's parser document
+// https://html.spec.whatwg.org/C/#parser-document
+//
+// [2] script's preparation-time document
+// https://html.spec.whatwg.org/C/#preparation-time-document
+// == script's node document at the beginning of #prepare-a-script
+//
+// [3] script's node document at the beginning of
+// #execute-the-script-block
+//
+// This helper is for tests where [1]/[2]/[3] are different.
+
+// In the spec, scripts are executed only if [1]/[2]/[3] are all the same
+// (or [1] is null and [2]==[3]).
+//
+// A check for [1]==[2] is in #prepare-a-script and
+// a check for [1]==[3] is in #execute-the-script-block,
+// but these are under debate: https://github.com/whatwg/html/issues/2137
+//
+// A check for [2]==[3] is in #execute-the-script-block, which is added by
+// https://github.com/whatwg/html/pull/2673
+
+// timing:
+// "before-prepare":
+// A <script> is moved during parsing before #prepare-a-script.
+// [1] != [2] == [3]
+//
+// "after-prepare":
+// A <script> is moved after parsing/#prepare-a-script but
+// before #execute-the-script-block.
+// [1] == [2] != [3]
+//
+// To move such scripts, #has-a-style-sheet-that-is-blocking-scripts
+// is utilized to block inline scripts after #prepare-a-script.
+// Note: this is a corner case in the spec which might be removed
+// from the spec in the future, e.g.
+// https://github.com/whatwg/html/issues/1349
+// https://github.com/chrishtr/rendering/blob/master/stylesheet-loading-proposal.md
+//
+// TODO(domfarolino): Remove the "parsing but moved back" tests, because if a
+// <script> is moved before #prepare-a-script, per spec it should never make
+// it to #execute-the-script-block. If an implementation does not implement
+// the check in #prepare-a-script, then it will fail the "before-prepare"
+// tests, so these are not necessary.
+// "parsing but moved back"
+// A <script> is moved before #prepare-a-script, but moved back again
+// to the original Document after #prepare-a-script.
+// [1] == [3] != [2]
+//
+// destType: "iframe" or "createHTMLDocument".
+// result: "fetch-error", "parse-error", or "success".
+// inlineOrExternal: "inline" or "external" or "empty-src".
+// type: "classic" or "module".
+async function runTest(timing, destType, result, inlineOrExternal, type) {
+ const description =
+ `Move ${result} ${inlineOrExternal} ${type} script ` +
+ `to ${destType} ${timing}`;
+
+ const t = async_test("Eval: " + description);
+ const tScriptLoadEvent = async_test("<script> load: " + description);
+ const tScriptErrorEvent = async_test("<script> error: " + description);
+ const tWindowErrorEvent = async_test("window error: " + description);
+
+ // If scripts should be moved after #prepare-a-script before
+ // #execute-the-script-block, we add a style sheet that is
+ // blocking scripts.
+ const hasBlockingStylesheet =
+ timing === "after-prepare" || timing === "move-back";
+
+ const [sourceWindow, sourceDocument] = await createDocument(
+ "iframe", result, inlineOrExternal, type, hasBlockingStylesheet);
+
+ // Due to https://crbug.com/1034176, Chromium needs
+ // blocking stylesheets also in the destination Documents.
+ const [destWindow, destDocument] = await createDocument(
+ destType, null, null, null, hasBlockingStylesheet);
+
+ const scriptOnLoad =
+ tScriptLoadEvent.unreached_func("Script load event fired unexpectedly");
+ const scriptOnError = (event) => {
+ // For Firefox: Prevent window.onerror is fired due to propagation
+ // from <script>'s error event.
+ event.stopPropagation();
+
+ tScriptErrorEvent.unreached_func("Script error evennt fired unexpectedly")();
+ };
+
+ sourceWindow.didExecute = false;
+ sourceWindow.t = t;
+ sourceWindow.scriptOnLoad = scriptOnLoad;
+ sourceWindow.scriptOnError = scriptOnError;
+ sourceWindow.onerror = tWindowErrorEvent.unreached_func(
+ "Window error event shouldn't fired on source window");
+ sourceWindow.readyToEvaluate = false;
+
+ destWindow.didExecute = false;
+ destWindow.t = t;
+ destWindow.scriptOnLoad = scriptOnLoad;
+ destWindow.scriptOnError = scriptOnError;
+ destWindow.onerror = tWindowErrorEvent.unreached_func(
+ "Window error event shouldn't fired on destination window");
+ destWindow.readyToEvaluate = false;
+
+ // t=0 sec: Move between documents before #prepare-a-script.
+ // At this time, the script element is not yet inserted to the DOM.
+ if (timing === "before-prepare" || timing === "move-back") {
+ destDocument.body.appendChild(
+ sourceDocument.querySelector("streaming-element"));
+ }
+ if (timing === "before-prepare") {
+ sourceWindow.readyToEvaluate = true;
+ destWindow.readyToEvaluate = true;
+ }
+
+ // t=1 sec: the script element is inserted to the DOM, i.e.
+ // #prepare-a-script is triggered (see monving-between-documents-iframe.py).
+ // In the case of `before-prepare`, the script can be evaluated.
+ // In other cases, the script evaluation is blocked by a style sheet.
+ await new Promise(resolve => step_timeout(resolve, 2000));
+
+ // t=2 sec: Move between documents after #prepare-a-script.
+ if (timing === "after-prepare") {
+ // At this point, the script hasn't been moved yet, so we'll move it for the
+ // first time, after #prepare-a-script, but before #execute-the-script-block.
+ destDocument.body.appendChild(
+ sourceDocument.querySelector("streaming-element"));
+ } else if (timing === "move-back") {
+ // At this point the script has already been moved to the destination block
+ // before #prepare-a-script, so we'll move it back to the source document
+ // before #execute-the-script-block.
+ sourceDocument.body.appendChild(
+ destDocument.querySelector("streaming-element"));
+ }
+ sourceWindow.readyToEvaluate = true;
+ destWindow.readyToEvaluate = true;
+
+ // t=3 or 5 sec: Blocking stylesheet and external script are loaded,
+ // and thus script evaulation is unblocked.
+
+ // Note: scripts are expected to be loaded at t=3, because the fetch
+ // is started by #prepare-a-script at t=1, and the script's delay is
+ // 2 seconds. However in Chromium, due to preload scanner, the script
+ // loading might take 4 seconds, because the first request by preload
+ // scanner of the source Document takes 2 seconds (between t=1 and t=3)
+ // which blocks the second request by #prepare-a-script that takes
+ // another 2 seconds (between t=3 and t=5).
+
+ // t=6 sec: After all possible script evaluation points, test whether
+ // the script/events were evaluated/fired or not.
+ // As we have concurrent tests, a single global step_timeout() is
+ // used instead of multiple `t.step_timeout()` etc.,
+ // to avoid potential race conditions between `t.step_timeout()`s.
+ return new Promise(resolve => {
+ step_timeout(() => {
+ tWindowErrorEvent.done();
+ tScriptLoadEvent.done();
+ tScriptErrorEvent.done();
+
+ t.step_func_done(() => {
+ assert_false(sourceWindow.didExecute,
+ "The script must not have executed in source window");
+ assert_false(destWindow.didExecute,
+ "The script must not have executed in destination window");
+ })();
+ resolve();
+ }, 4000);
+ });
+}
+
+async_test(t => {
+ t.step_timeout(() => {
+ assert_equals(window.didExecute, undefined,
+ "The script must not have executed in the top-level window");
+ t.done();
+ },
+ 4000);
+}, "Sanity check around top-level Window");