diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /testing/web-platform/tests/html/webappapis | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/html/webappapis')
413 files changed, 13704 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/callback-cross-realm-report-exception.html b/testing/web-platform/tests/html/webappapis/animation-frames/callback-cross-realm-report-exception.html new file mode 100644 index 0000000000..1b8aa41a6d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/animation-frames/callback-cross-realm-report-exception.html @@ -0,0 +1,29 @@ +<!doctype html> +<meta charset=utf-8> +<title>requestAnimationFrame() reports the exception from its callback in the callback's global object</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<iframe></iframe> +<iframe></iframe> +<iframe></iframe> +<script> +setup({ allow_uncaught_exception: true }); + +const onerrorCalls = []; +window.onerror = () => { onerrorCalls.push("top"); }; +frames[0].onerror = () => { onerrorCalls.push("frame0"); }; +frames[1].onerror = () => { onerrorCalls.push("frame1"); }; +frames[2].onerror = () => { onerrorCalls.push("frame2"); }; + +async_test(t => { + window.onload = t.step_func(() => { + frames[0].requestAnimationFrame(new frames[1].Function(`throw new parent.frames[2].Error("PASS");`)); + document.querySelector("iframe").height = 200; + + t.step_timeout(() => { + assert_array_equals(onerrorCalls, ["frame1"]); + t.done(); + }, 100); + }); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/callback-exception.html b/testing/web-platform/tests/html/webappapis/animation-frames/callback-exception.html new file mode 100644 index 0000000000..3867f0c41d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/animation-frames/callback-exception.html @@ -0,0 +1,27 @@ +<!doctype html> +<html> + <head> + <title>requestAnimationFrame callback exception reported to error handler</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <link rel="help" href="https://w3c.github.io/web-performance/specs/RequestAnimationFrame/Overview.html#dom-windowanimationtiming-requestanimationframe"/> + </head> + <body> + <div id="log"></div> + <script> + var custom_exception = 'requestAnimationFrameException'; + setup({allow_uncaught_exception : true}); + async_test(function (t) { + addEventListener("error",function(e) { + t.step(function() { + assert_equals(e.error.message, custom_exception); + t.done(); + }) + }); + window.requestAnimationFrame(function () { + throw new Error(custom_exception); + }); + }, "requestAnimationFrame callback exceptions are reported to error handler"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/callback-handle.html b/testing/web-platform/tests/html/webappapis/animation-frames/callback-handle.html new file mode 100644 index 0000000000..f1b8830031 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/animation-frames/callback-handle.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>AnimationTiming Test: FrameRequestCallback - valid callback handle</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#animation-frames"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + + test(() => { + let requestId = window.requestAnimationFrame(() => {}); + assert_greater_than(requestId, 0, "callback handle is a integer greater than zero"); + }, "Check window.requestAnimationFrame can return a valid callback handle"); + +</script> diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/callback-invoked.html b/testing/web-platform/tests/html/webappapis/animation-frames/callback-invoked.html new file mode 100644 index 0000000000..ca34e455a2 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/animation-frames/callback-invoked.html @@ -0,0 +1,18 @@ +<!doctype html> +<html> + <head> + <title>requestAnimationFrame must be triggered once</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <link rel="help" href="https://w3c.github.io/web-performance/specs/RequestAnimationFrame/Overview.html#dom-windowanimationtiming-requestanimationframe"/> + </head> + <body> + <div id="log"></div> + <script> + async_test(function (t) { + assert_false(document.hidden, "document.hidden must exist and be false to run this test properly"); + window.requestAnimationFrame(t.step_func_done()); + }, "requestAnimationFrame callback is invoked at least once before the timeout"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/callback-multicalls.html b/testing/web-platform/tests/html/webappapis/animation-frames/callback-multicalls.html new file mode 100644 index 0000000000..38f34171ea --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/animation-frames/callback-multicalls.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>AnimationTiming Test: multiple calls to requestAnimationFrame with the same callback</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-window-requestanimationframe"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + + async_test(function(t) { + var counter = 0; + window.requestAnimationFrame(callback); + + function callback() { + ++counter; + if (counter == 2) { + t.done(); + } else { + window.requestAnimationFrame(callback); + } + }; + + }, "Check that multiple calls to requestAnimationFrame with the same callback will result in multiple entries being in the list with that same callback."); + +</script> diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/callback-timestamp.html b/testing/web-platform/tests/html/webappapis/animation-frames/callback-timestamp.html new file mode 100644 index 0000000000..8e61db61b8 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/animation-frames/callback-timestamp.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>AnimationTiming Test: FrameRequestCallback - timestamp argument</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#animation-frames"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + + async_test(t => { + requestAnimationFrame(t.step_func_done(time => { + assert_equals(typeof time, "number", "callback contains a number argument"); + })) + }, "Check FrameRequestCallback has a DOMHighResTimeStamp argument"); + +</script> diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/cancel-handle-manual.html b/testing/web-platform/tests/html/webappapis/animation-frames/cancel-handle-manual.html new file mode 100644 index 0000000000..0328272522 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/animation-frames/cancel-handle-manual.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>AnimationTiming Test: cancelAnimationFrame used to cancel request callback</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#animation-frames"> + +<style> + #animated { + background: blue; + color: white; + height: 100px; + width: 100px; + position: absolute; + } +</style> + +<p> + Test passes if there is a filled blue square with 'Filler Text', + which moves from left to right repeatly, when click the 'stop' button, + the square stops. +</p> +<button onclick="stop()">stop</button> +<div id="animated">Filler Text</div> + +<script> + +let requestId = 0; +let requestAnimation = window.requestAnimationFrame; +let cancelAnimation = window.cancelAnimationFrame; + +function animate(time) { + let div = document.getElementById("animated"); + div.style.left = (time - animationStartTime) % 2000 / 4 + "px"; + requestId = requestAnimation(animate); +} + +function start() { + animationStartTime = window.performance.now(); + requestId = requestAnimation(animate); +} + +function stop() { + if (requestId) { + cancelAnimation(requestId); + requestId = 0; + } +} + +start(); + +</script> diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/cancel-invoked.html b/testing/web-platform/tests/html/webappapis/animation-frames/cancel-invoked.html new file mode 100644 index 0000000000..d075c0fdac --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/animation-frames/cancel-invoked.html @@ -0,0 +1,18 @@ +<!doctype html> +<html> + <head> + <title>cancelAnimationFrame does nothing</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <link rel="help" href="https://w3c.github.io/web-performance/specs/RequestAnimationFrame/Overview.html#dom-windowanimationtiming-cancelanimationframe"/> + </head> + <body> + <div id="log"></div> + <script> + test(function (t) { + window.cancelAnimationFrame(42); + assert_true(true); + }, "cancelAnimationFrame does nothing if there is no callback with the given handle"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/cancel-pending.html b/testing/web-platform/tests/html/webappapis/animation-frames/cancel-pending.html new file mode 100644 index 0000000000..9c9aff511d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/animation-frames/cancel-pending.html @@ -0,0 +1,26 @@ +<!doctype html> +<title>cancelAnimationFrame cancels a pending animation frame callback</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#run-the-animation-frame-callbacks"> +<div id="log"></div> +<script> +async_test(t => { + let didCall = false; + + function callbackOne() { + cancelAnimationFrame(twoHandle); + requestAnimationFrame(t.step_func(() => { + assert_false(didCall, 'Should NOT have called the second callback'); + t.done(); + })); + } + + function callbackTwo() { + didCall = true; + } + + requestAnimationFrame(callbackOne); + const twoHandle = requestAnimationFrame(callbackTwo); +}, 'cancelAnimationFrame cancels a pending animation frame callback'); +</script> diff --git a/testing/web-platform/tests/html/webappapis/animation-frames/same-dispatch-time.html b/testing/web-platform/tests/html/webappapis/animation-frames/same-dispatch-time.html new file mode 100644 index 0000000000..28e94f1e33 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/animation-frames/same-dispatch-time.html @@ -0,0 +1,31 @@ +<!doctype html> +<html> + <head> + <title>requestAnimationFrame in queue get the same timestamp</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <link rel="help" href="http://w3c.github.io/animation-timing/#dfn-invoke-callbacks-algorithm"/> + </head> + <body> + <div id="log"></div> + <script> + async_test(function (t) { + var a = 0, b = 0; + + /* REASONING: + * These two methods that will be called with a timestamp. Because + * they execute right after eachother, they're added to the same + * queue and SHOULD be timestamped with the same value. + */ + requestAnimationFrame(t.step_func(function() { a = arguments[0]; })); + requestAnimationFrame(t.step_func(function() { + b = arguments[0]; + assert_not_equals(a, 0); + assert_not_equals(b, 0); + assert_equals(a, b); + t.done(); + })); + }, "requestAnimationFrame will timestamp events in the same queue with the same time"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/atob/base64.any.js b/testing/web-platform/tests/html/webappapis/atob/base64.any.js new file mode 100644 index 0000000000..7f433f4d8a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/atob/base64.any.js @@ -0,0 +1,163 @@ +/** + * btoa() as defined by the HTML5 spec, which mostly just references RFC4648. + */ +function mybtoa(s) { + // String conversion as required by WebIDL. + s = String(s); + + // "The btoa() method must throw an INVALID_CHARACTER_ERR exception if the + // method's first argument contains any character whose code point is + // greater than U+00FF." + for (var i = 0; i < s.length; i++) { + if (s.charCodeAt(i) > 255) { + return "INVALID_CHARACTER_ERR"; + } + } + + var out = ""; + for (var i = 0; i < s.length; i += 3) { + var groupsOfSix = [undefined, undefined, undefined, undefined]; + groupsOfSix[0] = s.charCodeAt(i) >> 2; + groupsOfSix[1] = (s.charCodeAt(i) & 0x03) << 4; + if (s.length > i + 1) { + groupsOfSix[1] |= s.charCodeAt(i + 1) >> 4; + groupsOfSix[2] = (s.charCodeAt(i + 1) & 0x0f) << 2; + } + if (s.length > i + 2) { + groupsOfSix[2] |= s.charCodeAt(i + 2) >> 6; + groupsOfSix[3] = s.charCodeAt(i + 2) & 0x3f; + } + for (var j = 0; j < groupsOfSix.length; j++) { + if (typeof groupsOfSix[j] == "undefined") { + out += "="; + } else { + out += btoaLookup(groupsOfSix[j]); + } + } + } + return out; +} + +/** + * Lookup table for mybtoa(), which converts a six-bit number into the + * corresponding ASCII character. + */ +function btoaLookup(idx) { + if (idx < 26) { + return String.fromCharCode(idx + 'A'.charCodeAt(0)); + } + if (idx < 52) { + return String.fromCharCode(idx - 26 + 'a'.charCodeAt(0)); + } + if (idx < 62) { + return String.fromCharCode(idx - 52 + '0'.charCodeAt(0)); + } + if (idx == 62) { + return '+'; + } + if (idx == 63) { + return '/'; + } + // Throw INVALID_CHARACTER_ERR exception here -- won't be hit in the tests. +} + +function btoaException(input) { + input = String(input); + for (var i = 0; i < input.length; i++) { + if (input.charCodeAt(i) > 255) { + return true; + } + } + return false; +} + +function testBtoa(input) { + // "The btoa() method must throw an INVALID_CHARACTER_ERR exception if the + // method's first argument contains any character whose code point is + // greater than U+00FF." + var normalizedInput = String(input); + for (var i = 0; i < normalizedInput.length; i++) { + if (normalizedInput.charCodeAt(i) > 255) { + assert_throws_dom("InvalidCharacterError", function() { btoa(input); }, + "Code unit " + i + " has value " + normalizedInput.charCodeAt(i) + ", which is greater than 255"); + return; + } + } + assert_equals(btoa(input), mybtoa(input)); + assert_equals(atob(btoa(input)), String(input), "atob(btoa(input)) must be the same as String(input)"); +} + +var tests = ["עברית", "", "ab", "abc", "abcd", "abcde", + // This one is thrown in because IE9 seems to fail atob(btoa()) on it. Or + // possibly to fail btoa(). I actually can't tell what's happening here, + // but it doesn't hurt. + "\xff\xff\xc0", + // Is your DOM implementation binary-safe? + "\0a", "a\0b", + // WebIDL tests. + undefined, null, 7, 12, 1.5, true, false, NaN, +Infinity, -Infinity, 0, -0, + {toString: function() { return "foo" }}, +]; +for (var i = 0; i < 258; i++) { + tests.push(String.fromCharCode(i)); +} +tests.push(String.fromCharCode(10000)); +tests.push(String.fromCharCode(65534)); +tests.push(String.fromCharCode(65535)); + +// This is supposed to be U+10000. +tests.push(String.fromCharCode(0xd800, 0xdc00)); +tests = tests.map( + function(elem) { + var expected = mybtoa(elem); + if (expected === "INVALID_CHARACTER_ERR") { + return ["btoa(" + format_value(elem) + ") must raise INVALID_CHARACTER_ERR", elem]; + } + return ["btoa(" + format_value(elem) + ") == " + format_value(mybtoa(elem)), elem]; + } +); + +var everything = ""; +for (var i = 0; i < 256; i++) { + everything += String.fromCharCode(i); +} +tests.push(["btoa(first 256 code points concatenated)", everything]); + +generate_tests(testBtoa, tests); + +promise_test(() => fetch("../../../fetch/data-urls/resources/base64.json").then(res => res.json()).then(runAtobTests), "atob() setup."); + +const idlTests = [ + [undefined, null], + [null, [158, 233, 101]], + [7, null], + [12, [215]], + [1.5, null], + [true, [182, 187]], + [false, null], + [NaN, [53, 163]], + [+Infinity, [34, 119, 226, 158, 43, 114]], + [-Infinity, null], + [0, null], + [-0, null], + [{toString: function() { return "foo" }}, [126, 138]], + [{toString: function() { return "abcd" }}, [105, 183, 29]] +]; + +function runAtobTests(tests) { + const allTests = tests.concat(idlTests); + for(let i = 0; i < allTests.length; i++) { + const input = allTests[i][0], + output = allTests[i][1]; + test(() => { + if(output === null) { + assert_throws_dom("InvalidCharacterError", () => globalThis.atob(input)); + } else { + const result = globalThis.atob(input); + for(let ii = 0; ii < output.length; ii++) { + assert_equals(result.charCodeAt(ii), output[ii]); + } + } + }, "atob(" + format_value(input) + ")"); + } +} diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/document-close-with-pending-script.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/document-close-with-pending-script.html new file mode 100644 index 0000000000..1584ca5f97 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/document-close-with-pending-script.html @@ -0,0 +1,67 @@ +<!doctype html> +<meta charset=utf-8> +<title>document.close called while a script is pending</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<body> + <script> + window.t = async_test(); + // We want start a document load, create an non-blocking script load inside + // it, have the parser complete, then call document.open()/document.close() + // after the parser is done but before the non-blocking script load + // completes. After we do that, the document should reach the 'complete' + // ready state and the iframe's load event should fire. + var loadFired = false; + var i; + + var finish = t.step_func_done(() => { + assert_equals(loadFired, true, "Should have fired a load event"); + assert_equals(i.contentDocument.readyState, "complete", + "Should be fully loaded"); + }); + + var checkForLoad = t.step_func(() => { + if (loadFired) { + finish(); + } else { + i.addEventListener("load", finish); + } + }); + + window.parserDone = t.step_func(() => { + var doc = i.contentDocument; + i.onload = () => { loadFired = true; } + doc.open(); + doc.close(); + // It's not very clearly specced whether the document is + // supposed to be fully loaded at this point or not, so allow + // that to be the case, or to happen soonish. + assert_true(doc.readyState == "interactive" || + doc.readyState == "complete", "Should be almost loaded"); + if (doc.readyState == "complete") { + checkForLoad(); + } else { + doc.addEventListener("readystatechange", checkForLoad); + } + }); + + t.step(() => { + i = document.createElement("iframe"); + i.srcdoc = ` + <script> + parent.t.step(() => { + var s = document.createElement("script"); + s.src = "/common/slow.py"; + document.documentElement.appendChild(s); + // Call into the parent async, so we finish our "end of parse" + // work before it runs. + document.addEventListener( + "DOMContentLoaded", + () => parent.t.step_timeout(parent.parserDone, 0)); + }); + <\/script> + `; + document.body.appendChild(i); + }); + </script> +</body> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/document.close-01.xhtml b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/document.close-01.xhtml new file mode 100644 index 0000000000..164d71d191 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/document.close-01.xhtml @@ -0,0 +1,19 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<title>document.close in XHTML</title> +<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"/> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#closing-the-input-stream"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + assert_throws_dom("INVALID_STATE_ERR", function() { + document.close(); + }, "document.close in XHTML should throw an INVALID_STATE_ERR "); +}, "document.close in XHTML"); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/load-event-after-location-set-during-write.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/load-event-after-location-set-during-write.window.js new file mode 100644 index 0000000000..d5c8469baf --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/closing-the-input-stream/load-event-after-location-set-during-write.window.js @@ -0,0 +1,19 @@ +// Make sure that the load event for an iframe doesn't fire at the +// point when a navigation triggered by document.write() starts in it, +// but rather when that navigation completes. + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + const doc = frame.contentDocument; + const url = URL.createObjectURL(new Blob(["PASS"], { type: "text/html"})); + + frame.onload = t.step_func_done(() => { + assert_equals(frame.contentDocument.body.textContent, "PASS", + "Why is our load event firing before the new document loaded?"); + }); + + doc.open(); + doc.write(`FAIL<script>location = "${url}"</` + "script>"); + doc.close(); +}, "Setting location from document.write() call should not trigger load event until that load completes"); + diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/001.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/001.html new file mode 100644 index 0000000000..3ac6423f4a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/001.html @@ -0,0 +1,12 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( +function() { + document.write("PASS"); + assert_equals(document.body.textContent, "PASS"); +} +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/002.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/002.html new file mode 100644 index 0000000000..08975bca7b --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/002.html @@ -0,0 +1,13 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( +function() { + document.write("<i>Filler Text"); + assert_equals(document.body.firstChild.localName, "i"); + assert_equals(document.body.firstChild.textContent, "Filler Text"); +} +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/003.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/003.html new file mode 100644 index 0000000000..915e1f6d61 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/003.html @@ -0,0 +1,14 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( +function() { + document.write("<"); + document.write("i>Filler Text"); + assert_equals(document.body.firstChild.localName, "i"); + assert_equals(document.body.firstChild.textContent, "Filler Text"); +} +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/004.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/004.html new file mode 100644 index 0000000000..dd01725860 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/004.html @@ -0,0 +1,14 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( +function() { + document.write("<i"); + document.write(">Filler Text"); + assert_equals(document.body.firstChild.localName, "i"); + assert_equals(document.body.firstChild.textContent, "Filler Text"); +} +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/005.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/005.html new file mode 100644 index 0000000000..4c161c4d47 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/005.html @@ -0,0 +1,14 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( +function() { + document.write("<i>"); + document.write("Filler Text"); + assert_equals(document.body.firstChild.localName, "i"); + assert_equals(document.body.firstChild.textContent, "Filler Text"); +} +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/005.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/005.js new file mode 100644 index 0000000000..ebfd7e2585 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/005.js @@ -0,0 +1 @@ +order.push(3);
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/006.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/006.html new file mode 100644 index 0000000000..92bfb44c3a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/006.html @@ -0,0 +1,14 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( +function() { + document.write("<i id='test'>Filler Text"); + assert_equals(document.body.firstChild.localName, "i"); + assert_equals(document.body.firstChild.getAttribute("id"), "test"); + assert_equals(document.body.firstChild.textContent, "Filler Text"); +} +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/006.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/006.js new file mode 100644 index 0000000000..ebfd7e2585 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/006.js @@ -0,0 +1 @@ +order.push(3);
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/007.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/007.html new file mode 100644 index 0000000000..753316b89c --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/007.html @@ -0,0 +1,15 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( +function() { + document.write("<i "); + document.write("id='test'>Filler Text"); + assert_equals(document.body.firstChild.localName, "i"); + assert_equals(document.body.firstChild.getAttribute("id"), "test"); + assert_equals(document.body.firstChild.textContent, "Filler Text"); +} +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/007.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/007.js new file mode 100644 index 0000000000..31fcf18d49 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/007.js @@ -0,0 +1,4 @@ +t.step(function() { + order.push(2); + document.write("<script>t.step(function() {order.push(3)})</script>"); + });
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008-1.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008-1.js new file mode 100644 index 0000000000..ef90c722b7 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008-1.js @@ -0,0 +1,3 @@ +t.step(function() { + order.push(3); + });
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008.html new file mode 100644 index 0000000000..4818bc388f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008.html @@ -0,0 +1,15 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( +function() { + document.write("<i i"); + document.write("d='test'>Filler Text"); + assert_equals(document.body.firstChild.localName, "i"); + assert_equals(document.body.firstChild.getAttribute("id"), "test"); + assert_equals(document.body.firstChild.textContent, "Filler Text"); +} +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008.js new file mode 100644 index 0000000000..367597515d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/008.js @@ -0,0 +1,4 @@ +t.step(function() { + order.push(2); + document.write("<script src=\"008-1.js\"></script>"); + });
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/009.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/009.html new file mode 100644 index 0000000000..d7b78333b8 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/009.html @@ -0,0 +1,15 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( +function() { + document.write("<i id"); + document.write("='test'>Filler Text"); + assert_equals(document.body.firstChild.localName, "i"); + assert_equals(document.body.firstChild.getAttribute("id"), "test"); + assert_equals(document.body.firstChild.textContent, "Filler Text"); +} +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010-1.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010-1.js new file mode 100644 index 0000000000..fd815bab77 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010-1.js @@ -0,0 +1,4 @@ +t.step(function() { + order.push(4); + assert_equals(document.getElementsByTagName("meta").length, 1); + });
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010.html new file mode 100644 index 0000000000..c8b9958258 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010.html @@ -0,0 +1,15 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( +function() { + document.write("<i id="); + document.write("'test'>Filler Text"); + assert_equals(document.body.firstChild.localName, "i"); + assert_equals(document.body.firstChild.getAttribute("id"), "test"); + assert_equals(document.body.firstChild.textContent, "Filler Text"); +} +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010.js new file mode 100644 index 0000000000..bb328ad55a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/010.js @@ -0,0 +1,4 @@ +t.step(function() { + order.push(3); + assert_equals(document.getElementsByTagName("meta").length, 0); + }); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011-1.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011-1.js new file mode 100644 index 0000000000..944b70d2d0 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011-1.js @@ -0,0 +1,5 @@ +t.step(function() { + order.push(4); + document.write("<meta>"); + assert_equals(document.getElementsByTagName("meta").length, 1); + });
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011.html new file mode 100644 index 0000000000..33464429e6 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011.html @@ -0,0 +1,15 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( +function() { + document.write("<i id='"); + document.write("test'>Filler Text"); + assert_equals(document.body.firstChild.localName, "i"); + assert_equals(document.body.firstChild.getAttribute("id"), "test"); + assert_equals(document.body.firstChild.textContent, "Filler Text"); +} +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011.js new file mode 100644 index 0000000000..ce47bcd283 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/011.js @@ -0,0 +1,5 @@ +t.step(function() { + order.push(3); + document.write("<script src='011-1.js'></script" + "><meta>"); + assert_equals(document.getElementsByTagName("meta").length, 0); + });
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/012.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/012.html new file mode 100644 index 0000000000..c9902a4875 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/012.html @@ -0,0 +1,15 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( +function() { + document.write("<i id='te"); + document.write("st'>Filler Text"); + assert_equals(document.body.firstChild.localName, "i"); + assert_equals(document.body.firstChild.getAttribute("id"), "test"); + assert_equals(document.body.firstChild.textContent, "Filler Text"); +} +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/012.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/012.js new file mode 100644 index 0000000000..7ab4c6b386 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/012.js @@ -0,0 +1,5 @@ +t.step( +function() { + order.push(5); + assert_equals(document.getElementsByTagName("meta").length, 0); +});
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/013.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/013.html new file mode 100644 index 0000000000..7b87d28976 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/013.html @@ -0,0 +1,15 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( +function() { + document.write("<i id='test"); + document.write("'>Filler Text"); + assert_equals(document.body.firstChild.localName, "i"); + assert_equals(document.body.firstChild.getAttribute("id"), "test"); + assert_equals(document.body.firstChild.textContent, "Filler Text"); +} +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/013.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/013.js new file mode 100644 index 0000000000..b5ce5f27da --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/013.js @@ -0,0 +1 @@ +document.write('<svg><![CDATA[');
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/014.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/014.html new file mode 100644 index 0000000000..75518a8981 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/014.html @@ -0,0 +1,15 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( +function() { + document.write("<i id='test'"); + document.write(">Filler Text"); + assert_equals(document.body.firstChild.localName, "i"); + assert_equals(document.body.firstChild.getAttribute("id"), "test"); + assert_equals(document.body.firstChild.textContent, "Filler Text"); +} +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/015.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/015.html new file mode 100644 index 0000000000..3dd79a63ef --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/015.html @@ -0,0 +1,16 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( +function() { + document.write("<i id='test'"); + document.write("class='a'>Filler Text"); + assert_equals(document.body.firstChild.localName, "i"); + assert_equals(document.body.firstChild.getAttribute("id"), "test"); + assert_equals(document.body.firstChild.getAttribute("class"), "a"); + assert_equals(document.body.firstChild.textContent, "Filler Text"); +} +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/016.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/016.html new file mode 100644 index 0000000000..4c2f58912a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/016.html @@ -0,0 +1,16 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( +function() { + document.write("<i>Filler Text"); + document.write("</i><b>Filler Text"); + assert_equals(document.body.firstChild.localName, "i"); + assert_equals(document.body.firstChild.textContent, "Filler Text"); + assert_equals(document.body.childNodes[1].localName, "b"); + assert_equals(document.body.childNodes[1].textContent, "Filler Text"); +} +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/017.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/017.html new file mode 100644 index 0000000000..8d1b24b06e --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/017.html @@ -0,0 +1,19 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( +function() { + var s = "<i id=test>Filler Text</i><b>Filler Text" + for (var i=0; i<s.length; i++) { + document.write(s[i]); + } + assert_equals(document.body.firstChild.localName, "i"); + assert_equals(document.body.firstChild.getAttribute('id'), "test"); + assert_equals(document.body.firstChild.textContent, "Filler Text"); + assert_equals(document.body.childNodes[1].localName, "b"); + assert_equals(document.body.childNodes[1].textContent, "Filler Text"); +} +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/018.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/018.html new file mode 100644 index 0000000000..cf8dddbc54 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/018.html @@ -0,0 +1,19 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( +function() { + document.write("<body>"); + var s = "<!--comment--><i>Filler Text</i>" + for (var i=0; i<s.length; i++) { + document.write(s[i]); + } + assert_equals(document.body.firstChild.nodeType, document.COMMENT_NODE); + assert_equals(document.body.firstChild.data, "comment"); + assert_equals(document.body.childNodes[1].localName, "i"); + assert_equals(document.body.childNodes[1].textContent, "Filler Text"); +} +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/019.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/019.html new file mode 100644 index 0000000000..5e988f79ef --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/019.html @@ -0,0 +1,19 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + document.write("<i"); +}); +</script> +>Filler Text</i> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].localName, "i"); + assert_equals(document.body.childNodes[0].textContent, "Filler Text"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/020.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/020.html new file mode 100644 index 0000000000..1d31bbf35d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/020.html @@ -0,0 +1,18 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + document.write("<body><"); +}); +</script>!--comment--> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].nodeType, document.COMMENT_NODE); + assert_equals(document.body.childNodes[0].data, "comment"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/021.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/021.html new file mode 100644 index 0000000000..500bb19398 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/021.html @@ -0,0 +1,18 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + document.write("<body><sp"); +}); +</script>an>Filler Text</span> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].localName, "span"); + assert_equals(document.body.childNodes[0].textContent, "Filler Text"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/022.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/022.html new file mode 100644 index 0000000000..53ba299012 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/022.html @@ -0,0 +1,18 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + document.write("<body><span>"); +}); +</script>Filler Text</span> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].localName, "span"); + assert_equals(document.body.childNodes[0].textContent, "Filler Text"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/023.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/023.html new file mode 100644 index 0000000000..ca89e0e0bc --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/023.html @@ -0,0 +1,19 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + document.write("<body><span "); +}); +</script>id=a>Filler Text</span> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].localName, "span"); + assert_equals(document.body.childNodes[0].getAttribute("id"), "a"); + assert_equals(document.body.childNodes[0].textContent, "Filler Text"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/024.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/024.html new file mode 100644 index 0000000000..2a47d76cb3 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/024.html @@ -0,0 +1,19 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + document.write("<body><span i"); +}); +</script>d=a>Filler Text</span> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].localName, "span"); + assert_equals(document.body.childNodes[0].getAttribute("id"), "a"); + assert_equals(document.body.childNodes[0].textContent, "Filler Text"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/025.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/025.html new file mode 100644 index 0000000000..31c68cf7df --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/025.html @@ -0,0 +1,19 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + document.write("<body><span id"); +}); +</script>=a>Filler Text</span> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].localName, "span"); + assert_equals(document.body.childNodes[0].getAttribute("id"), "a"); + assert_equals(document.body.childNodes[0].textContent, "Filler Text"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/026.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/026.html new file mode 100644 index 0000000000..a9bce7743e --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/026.html @@ -0,0 +1,19 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + document.write("<body><span id="); +}); +</script>a>Filler Text</span> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].localName, "span"); + assert_equals(document.body.childNodes[0].getAttribute("id"), "a"); + assert_equals(document.body.childNodes[0].textContent, "Filler Text"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/027.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/027.html new file mode 100644 index 0000000000..dcfd67c0f7 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/027.html @@ -0,0 +1,19 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + document.write("<body><span id=a"); +}); +</script>>Filler Text</span> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].localName, "span"); + assert_equals(document.body.childNodes[0].getAttribute("id"), "a"); + assert_equals(document.body.childNodes[0].textContent, "Filler Text"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/028.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/028.html new file mode 100644 index 0000000000..f5b7e9ef2b --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/028.html @@ -0,0 +1,21 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + document.write("<body><span id=a>Filler Text<"); +}); +</script>/span><b>Filler Text</b></span> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].localName, "span"); + assert_equals(document.body.childNodes[0].getAttribute("id"), "a"); + assert_equals(document.body.childNodes[0].textContent, "Filler Text"); + assert_equals(document.body.childNodes[1].localName, "b"); + assert_equals(document.body.childNodes[1].textContent, "Filler Text"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/029.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/029.html new file mode 100644 index 0000000000..f005a72227 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/029.html @@ -0,0 +1,21 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + document.write("<body><span id=a>Filler Text</"); +}); +</script>span><b>Filler Text</b></span> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].localName, "span"); + assert_equals(document.body.childNodes[0].getAttribute("id"), "a"); + assert_equals(document.body.childNodes[0].textContent, "Filler Text"); + assert_equals(document.body.childNodes[1].localName, "b"); + assert_equals(document.body.childNodes[1].textContent, "Filler Text"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/030.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/030.html new file mode 100644 index 0000000000..cc361d3aaf --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/030.html @@ -0,0 +1,21 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + document.write("<body><span id=a>Filler Text</sp"); +}); +</script>an><b>Filler Text</b></span> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].localName, "span"); + assert_equals(document.body.childNodes[0].getAttribute("id"), "a"); + assert_equals(document.body.childNodes[0].textContent, "Filler Text"); + assert_equals(document.body.childNodes[1].localName, "b"); + assert_equals(document.body.childNodes[1].textContent, "Filler Text"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/031.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/031.html new file mode 100644 index 0000000000..32c97c5056 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/031.html @@ -0,0 +1,21 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + document.write("<body><span id=a>Filler Text</span"); +}); +</script>><b>Filler Text</b></span> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].localName, "span"); + assert_equals(document.body.childNodes[0].getAttribute("id"), "a"); + assert_equals(document.body.childNodes[0].textContent, "Filler Text"); + assert_equals(document.body.childNodes[1].localName, "b"); + assert_equals(document.body.childNodes[1].textContent, "Filler Text"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/032.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/032.html new file mode 100644 index 0000000000..1a33408f1b --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/032.html @@ -0,0 +1,22 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + var tag_name_length = 100000; + var tag_name = ""; + for (var i=0; i<tag_name_length; i++) { + tag_name += "a"; + } + document.write("<body><" + tag_name + ">Filler Text</" + tag_name + ">"); +}); +</script> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].textContent, "Filler Text"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/033.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/033.html new file mode 100644 index 0000000000..1b8e1c2706 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/033.html @@ -0,0 +1,20 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +test( + function() { + document.writeln("<i"); + var s = " b='a'>Filler" + for (var i=0; i<s.length; i++) { + document.write(s[i]+"\n"); + } + document.writeln("</i"); + document.writeln(">"); + assert_equals(document.body.childNodes[0].localName, "i"); + assert_equals(document.body.childNodes[0].getAttribute("b"), "\na\n"); + assert_equals(document.body.childNodes[0].textContent, "\nF\ni\nl\nl\ne\nr\n"); + } +); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/034.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/034.html new file mode 100644 index 0000000000..abd481a64d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/034.html @@ -0,0 +1,21 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + var s = "<svg><![CDATA[Filler Text]]></svg>"; + for (var i=0; i<s.length; i++) { + document.write(s[i]); + } +}); +</script> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].localName, "svg"); + assert_equals(document.body.childNodes[0].textContent, "Filler Text"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/035.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/035.html new file mode 100644 index 0000000000..a1e7f9ee67 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/035.html @@ -0,0 +1,21 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + var s = "<svg><!"; + for (var i=0; i<s.length; i++) { + document.write(s[i]); + } +}); +</script>[CDATA[Filler Text]]></svg> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].localName, "svg"); + assert_equals(document.body.childNodes[0].textContent, "Filler Text"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/036.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/036.html new file mode 100644 index 0000000000..8719e0598d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/036.html @@ -0,0 +1,21 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + var s = "<svg><![CDATA[Filler Text]"; + for (var i=0; i<s.length; i++) { + document.write(s[i]); + } +}); +</script>]></svg> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].localName, "svg"); + assert_equals(document.body.childNodes[0].textContent, "Filler Text"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/037.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/037.html new file mode 100644 index 0000000000..cf0787ce76 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/037.html @@ -0,0 +1,21 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + var s = "<body><!DOCTYPE html>"; + for (var i=0; i<s.length; i++) { + document.write(s[i]); + } +}); +</script><script> +t.step(function() { + //Nothing should be inserted into the DOM for the doctype node so + //just checking nothing odd happens + assert_equals(document.body.childNodes[0].localName, "script"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/038.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/038.html new file mode 100644 index 0000000000..4ae9d32b23 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/038.html @@ -0,0 +1,21 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + var s = "<body><"; + for (var i=0; i<s.length; i++) { + document.write(s[i]); + } +}); +</script>!DOCTYPE html><script> +t.step(function() { + //Nothing should be inserted into the DOM for the doctype node so + //just checking nothing odd happens + assert_equals(document.body.childNodes[0].localName, "script"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/039.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/039.html new file mode 100644 index 0000000000..611a01390c --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/039.html @@ -0,0 +1,21 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + var s = "<body><!"; + for (var i=0; i<s.length; i++) { + document.write(s[i]); + } +}); +</script>DOCTYPE html><script> +t.step(function() { + //Nothing should be inserted into the DOM for the doctype node so + //just checking nothing odd happens + assert_equals(document.body.childNodes[0].localName, "script"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/040.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/040.html new file mode 100644 index 0000000000..d76deffa40 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/040.html @@ -0,0 +1,10 @@ +<!doctype html> +<title>document.write entity</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = test(function() { + document.write("<body><span>∉abc"); + assert_equals(document.body.childNodes[0].textContent, "\u2209abc"); +}); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/041.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/041.html new file mode 100644 index 0000000000..592711c94f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/041.html @@ -0,0 +1,13 @@ +<!doctype html> +<title>document.write entity</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = test(function() { + var s = "<body><span>∉abc"; + for (var i=0; i<s.length; i++) { + document.write(s[i]); + } + assert_equals(document.body.childNodes[0].textContent, "\u2209abc"); +}); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/042.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/042.html new file mode 100644 index 0000000000..e15f1d0c0f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/042.html @@ -0,0 +1,16 @@ +<!doctype html> +<title>document.write entity</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + document.write("<body><span>¬"); +}); +</script>in;abc</span> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].textContent, "\u2209abc"); +}) +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/043.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/043.html new file mode 100644 index 0000000000..4058e7a823 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/043.html @@ -0,0 +1,16 @@ +<!doctype html> +<title>document.write entity</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + document.write("<body><span>&"); +}); +</script>notabc</span> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].textContent, "\u00ACabc"); +}) +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/044.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/044.html new file mode 100644 index 0000000000..4c9f50273c --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/044.html @@ -0,0 +1,17 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + document.write("<body><textarea><span>Filler</span></textarea>"); +}); +</script> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].localName, "textarea"); + assert_equals(document.body.childNodes[0].textContent, "<span>Filler</span>"); +}) +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/045.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/045.html new file mode 100644 index 0000000000..987eabf0f4 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/045.html @@ -0,0 +1,20 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + var s = "<body><textarea><span>Filler</span></textarea>"; + for (var i=0; i<s.length; i++) { + document.write(s[i]); + } +}); +</script> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].localName, "textarea"); + assert_equals(document.body.childNodes[0].textContent, "<span>Filler</span>"); +}) +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/046.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/046.html new file mode 100644 index 0000000000..e87e9cc825 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/046.html @@ -0,0 +1,20 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + var s = "<body><textarea>"; + for (var i=0; i<s.length; i++) { + document.write(s[i]); + } +}); +</script><span>Filler</span></textarea> +<script> +t.step(function() { + assert_equals(document.body.childNodes[0].localName, "textarea"); + assert_equals(document.body.childNodes[0].textContent, "<span>Filler</span>"); +}) +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/047-1.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/047-1.html new file mode 100644 index 0000000000..6a43faec51 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/047-1.html @@ -0,0 +1,7 @@ +<script> +onload = opener.t.step_func_done(function() { + document.write("<body>Filler Text<div id='log'></div>"); + opener.assert_equals(document.body.textContent, "Filler Text"); +}); +</script> +<body>FAIL diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/047.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/047.html new file mode 100644 index 0000000000..677d3e1786 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/047.html @@ -0,0 +1,11 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +var win; +var t = async_test(() => { + win = window.open("047-1.html"); +}); +t.add_cleanup(() => win.close()); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/049.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/049.html new file mode 100644 index 0000000000..0ec282f2bc --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/049.html @@ -0,0 +1,18 @@ +<!doctype html> +<title>document.write plaintext</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<div id="log"></div><script> +test(function() { + var s = "<table><tr><td>Text</tr><plaintext><tr><td>Filler "; + for (var i=0; i<s.length; i++) { + document.write(s[i]); + } + document.close(); + assert_equals(document.body.childNodes[2].nodeType, document.ELEMENT_NODE); + assert_equals(document.body.childNodes[2].localName, "plaintext"); + assert_equals(document.body.childNodes[2].textContent, "<tr><td>Filler "); + assert_equals(document.body.childNodes[3].nodeType, document.ELEMENT_NODE); + assert_equals(document.body.childNodes[3].localName, "table"); + assert_equals(document.body.childNodes[3].textContent, "Text"); +}); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/050.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/050.html new file mode 100644 index 0000000000..0a37fa4c5f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/050.html @@ -0,0 +1,25 @@ +<!doctype html> +<title>document.write plaintext</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<div id="log"></div><script> +var t = async_test(); + +t.step(function() { + document.write("<plaintext>"); + assert_equals(document.body.childNodes[2].nodeType, document.ELEMENT_NODE); + assert_equals(document.body.childNodes[2].localName, "plaintext"); + var s = "Filler "; + for (var i=0; i<s.length; i++) { + document.write(s[i]); + assert_equals(document.body.childNodes[2].textContent, s.slice(0,i+1)); + } + document.close(); +}); + +onload = function() { + t.step(function() { + assert_equals(document.body.childNodes[2].textContent, "Filler Text\n"); + }); + t.done(); +} +</script>Text diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/051.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/051.html new file mode 100644 index 0000000000..80ea279dad --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/051.html @@ -0,0 +1,22 @@ +<!doctype html> +<title>document.write \r\n</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script> +var t = async_test(); + +t.step(function() { + document.write("\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\nA"); +}) + +onload = function() { + t.step(function() { + const lastNode = document.getElementById('after'); + assert_equals(lastNode.previousSibling.data, "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nAB"); + }); + t.done(); +}; +</script>B<div id=after></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/contentType.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/contentType.window.js new file mode 100644 index 0000000000..5a91203874 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/contentType.window.js @@ -0,0 +1,28 @@ +// META: script=/common/media.js + +const videoURL = getVideoURI("/images/pattern"), + videoMIMEType = getMediaContentType(videoURL); + +[ + [videoURL, videoMIMEType, "video"], + ["/images/red.png", "image/png", "image"], + ["/common/text-plain.txt", "text/plain", "text"], + ["/common/blank.html", "text/html", "HTML"] +].forEach(val => { + async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.src = val[0]; + frame.onload = t.step_func_done(() => { + assert_equals(frame.contentDocument.contentType, val[1]); + frame.contentDocument.write("<b>Heya</b>"); + assert_equals(frame.contentDocument.body.firstChild.localName, "b"); + assert_equals(frame.contentDocument.body.firstChild.textContent, "Heya"); + assert_equals(frame.contentDocument.contentType, val[1]); + + // Make sure a load event is fired across browsers + // https://github.com/web-platform-tests/wpt/pull/10239 + frame.contentDocument.close(); + }); + }, "document.write(): " + val[2] + " document"); +}); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/document.write-01.xhtml b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/document.write-01.xhtml new file mode 100644 index 0000000000..fc21d4e2bf --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/document.write-01.xhtml @@ -0,0 +1,19 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<title>document.write in XHTML</title> +<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"/> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#document.write%28%29"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + assert_throws_dom("INVALID_STATE_ERR", function() { + document.write("Failure: document.write actually worked"); + }, "document.write in XHTML should throw an INVALID_STATE_ERR "); +}, "document.write in XHTML"); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/document.write-02.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/document.write-02.html new file mode 100644 index 0000000000..4c25da8b68 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/document.write-02.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>document.write and null/undefined</title> +<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-document-write%28%29"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#documents-in-the-dom"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +test(function() { + var iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + doc = iframe.contentDocument; + test(function() { + doc.open(); + doc.write(null); + doc.close(); + assert_equals(doc.documentElement.textContent, "null"); + }, "document.write(null)"); + test(function() { + doc.open(); + doc.write(undefined); + doc.close(); + assert_equals(doc.documentElement.textContent, "undefined"); + }, "document.write(undefined)"); +}, "Calling document.write with null and undefined"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/during-readystatechange.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/during-readystatechange.window.js new file mode 100644 index 0000000000..49d5051c25 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/during-readystatechange.window.js @@ -0,0 +1,24 @@ +// This tests whether the insertion point gets reset before or after the readystatechange event. +// See https://github.com/whatwg/html/pull/6613#discussion_r620171070. +// Recall that resetting the insertion point means that document.write() performs the document open +// steps and blows away previous content in the document. + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => { frame.remove(); }); + frame.src = "../opening-the-input-stream/resources/dummy.html"; + frame.onload = t.step_func_done(() => { + const states = []; + frame.contentDocument.onreadystatechange = t.step_func(() => { + if (frame.contentDocument.readyState === "interactive") { + assert_not_equals(frame.contentDocument.textContent, "", "Precondition check: dummy document is not empty"); + + frame.contentDocument.write("Some text"); + + // If the insertion point is reset before the readystatechange handler, then the + // document.write() call above will blow away the text originally in dummy.html, leaving only what we wrote. + assert_equals(frame.contentDocument.textContent, "Some text"); + } + }); + }); +}, "document.write() during readystatechange to interactive"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/empty.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/empty.html new file mode 100644 index 0000000000..0dc101b533 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/empty.html @@ -0,0 +1 @@ +<html><body></body></html> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_001.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_001.html new file mode 100644 index 0000000000..8b54560c6c --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_001.html @@ -0,0 +1,14 @@ +<!doctype html> +<title>document.write into iframe</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<iframe id="test"></iframe> +<script> +test( +function() { + var iframe = document.getElementById("test"); + iframe.contentDocument.write("Filler Text"); + iframe.contentDocument.close(); + assert_equals(iframe.contentDocument.body.textContent, "Filler Text"); +}); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_002.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_002.html new file mode 100644 index 0000000000..f77819adb6 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_002.html @@ -0,0 +1,22 @@ +<!doctype html> +<title>document.write into iframe</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<iframe id="test"></iframe> +<script> +test( +function() { + var iframe = document.getElementById("test"); + var s = "<i id='a'>Filler Text</i><b id=b>Filler Text</b>" + for (var i=0; i<s.length; i++) { + iframe.contentDocument.write(s[i]); + } + iframe.contentDocument.close(); + assert_equals(iframe.contentDocument.body.childNodes[0].textContent, "Filler Text"); + assert_equals(iframe.contentDocument.body.childNodes[0].localName, "i"); + assert_equals(iframe.contentDocument.body.childNodes[0].getAttribute('id'), "a"); + assert_equals(iframe.contentDocument.body.childNodes[1].textContent, "Filler Text"); + assert_equals(iframe.contentDocument.body.childNodes[1].localName, "b"); + assert_equals(iframe.contentDocument.body.childNodes[1].getAttribute('id'), "b"); +}); +</script> +<div id="log"></div>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_003.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_003.html new file mode 100644 index 0000000000..9865874da4 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_003.html @@ -0,0 +1,23 @@ +<!doctype html> +<title>document.write script into iframe</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<iframe id="test"></iframe> +<script> +test( +function() { + var iframe = document.getElementById("test"); + var s = "<script>document.write(\"<i id='a'>Filler Text</i>\")</script" + "><b id=b>Filler Text</b>" + for (var i=0; i<s.length; i++) { + iframe.contentDocument.write(s[i]); + } + iframe.contentDocument.close(); + //Note: <script> ends up in <head> + assert_equals(iframe.contentDocument.body.childNodes[0].textContent, "Filler Text"); + assert_equals(iframe.contentDocument.body.childNodes[0].localName, "i"); + assert_equals(iframe.contentDocument.body.childNodes[0].getAttribute('id'), "a"); + assert_equals(iframe.contentDocument.body.childNodes[1].textContent, "Filler Text"); + assert_equals(iframe.contentDocument.body.childNodes[1].localName, "b"); + assert_equals(iframe.contentDocument.body.childNodes[1].getAttribute('id'), "b"); +}); +</script> +<div id="log"></div>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_004.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_004.html new file mode 100644 index 0000000000..a4d7b1ddad --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_004.html @@ -0,0 +1,22 @@ +<!doctype html> +<title>document.write script into iframe write back into parent</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<iframe id="test"></iframe> +<script> +var t = async_test(); +var iframe = document.getElementById("test"); +var order = []; +t.step(function() { + order.push(1); + var s = "<script>parent.order.push(2); parent.document.write('<script>order.push(3);</script'+'>'); parent.order.push(4)</script" + ">"; + for (var i=0; i<s.length; i++) { + iframe.contentDocument.write(s[i]); + } + iframe.contentDocument.close(); + order.push(5); + assert_array_equals(order, [1,2,3,4,5]) +} +); +t.done(); +</script> +<div id="log"></div>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_005.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_005.html new file mode 100644 index 0000000000..7bc3ed6c29 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_005.html @@ -0,0 +1,25 @@ +<!doctype html> +<title>document.write external script into iframe write back into parent</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<iframe id="test"></iframe> +<script> +var t = async_test(); +var iframe = document.getElementById("test"); +var order = []; +t.step(function() { + order.push(1); + var s = "<script src='iframe_005.js'></script" + ">"; + iframe.contentDocument.write(s); + iframe.contentDocument.close(); + order.push(2); + assert_array_equals(order, [1,2]) +} +); +addEventListener("load", function() { + t.step(function() { + assert_array_equals(order, [1,2,3,4,5]) + }); + t.done(); +}, false); +</script> +<div id="log"></div>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_005.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_005.js new file mode 100644 index 0000000000..bf038f7004 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_005.js @@ -0,0 +1,3 @@ +parent.order.push(3); +document.write("<script>parent.order.push(4)</script>"); +parent.order.push(5);
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_006.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_006.html new file mode 100644 index 0000000000..d080ee3673 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_006.html @@ -0,0 +1,19 @@ +<!doctype html> +<title>document.write external script into iframe write back into parent</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<iframe id="test"></iframe> +<script> +var t = async_test(); +var iframe = document.getElementById("test"); +var order = []; +t.step(function() { + order.push(1); + var s = "<script>parent.order.push(2); parent.document.write('<script>order.push(3); iframe.contentDocument.write(\"<script>parent.order.push(4)</script\"+\">\");order.push(5);</script' + '>'); parent.order.push(6)</script"+">"; + iframe.contentDocument.write(s); + iframe.contentDocument.close(); + order.push(7); + assert_array_equals(order, [1,2,3,4,5,6,7]); +}); +t.done(); +</script> +<div id="log"></div>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_007.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_007.html new file mode 100644 index 0000000000..c00aa7062d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_007.html @@ -0,0 +1,17 @@ +<!doctype html> +<title>document.write comment into iframe</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<iframe id="test"></iframe> +<script> +test(function() { + var iframe = document.getElementById("test"); + var s = "<!--Filler-->"; + for (var i=0; i<s.length; i++) { + iframe.contentDocument.write(s); + } + iframe.contentDocument.close(); + assert_equals(iframe.contentDocument.childNodes[0].nodeType, document.COMMENT_NODE); + assert_equals(iframe.contentDocument.childNodes[0].data, "Filler"); +}); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_008.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_008.html new file mode 100644 index 0000000000..c814958d19 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_008.html @@ -0,0 +1,18 @@ +<!doctype html> +<title>document.write plaintext into iframe</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<iframe id="test"></iframe> +<script> +test(function() { + var iframe = document.getElementById("test"); + var s = "<plaintext><span>Filler Text"; + for (var i=0; i<s.length; i++) { + iframe.contentDocument.write(s[i]); + } + iframe.contentDocument.close(); + assert_equals(iframe.contentDocument.body.childNodes[0].nodeType, document.ELEMENT_NODE); + assert_equals(iframe.contentDocument.body.childNodes[0].localName, "plaintext"); + assert_equals(iframe.contentDocument.body.childNodes[0].textContent, "<span>Filler Text"); +}); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_009.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_009.html new file mode 100644 index 0000000000..8b271c7a03 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_009.html @@ -0,0 +1,21 @@ +<!doctype html> +<title>document.write plaintext into iframe</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<iframe id="test"></iframe> +<script> +test(function() { + var iframe = document.getElementById("test"); + var s = "<table><tr><td>Text</tr><plaintext><tr><td>Filler "; + for (var i=0; i<s.length; i++) { + iframe.contentDocument.write(s[i]); + } + iframe.contentDocument.close(); + assert_equals(iframe.contentDocument.body.childNodes[0].nodeType, document.ELEMENT_NODE); + assert_equals(iframe.contentDocument.body.childNodes[0].localName, "plaintext"); + assert_equals(iframe.contentDocument.body.childNodes[0].textContent, "<tr><td>Filler "); + assert_equals(iframe.contentDocument.body.childNodes[1].nodeType, document.ELEMENT_NODE); + assert_equals(iframe.contentDocument.body.childNodes[1].localName, "table"); + assert_equals(iframe.contentDocument.body.childNodes[1].textContent, "Text"); +}); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_010.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_010.html new file mode 100644 index 0000000000..8dc21a013a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/iframe_010.html @@ -0,0 +1,23 @@ +<!doctype html> +<title>document.write plaintext</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<iframe id="test"></iframe> +<script> +var t = async_test(); +var iframe = document.getElementById("test"); + +function check_dom() { + assert_equals(iframe.contentDocument.body.childNodes[0].localName, "plaintext") + assert_equals(iframe.contentDocument.body.childNodes[0].textContent, "Filler ") + assert_equals(iframe.contentDocument.body.childNodes[1].localName, "table") +} + +t.step(function() { + var s = "<script>document.write('<table><plaintext>Filler '); document.close(); top.t.step(function() {top.check_dom()})</script" + ">"; + for (var i=0; i<s.length; i++) { + iframe.contentDocument.write(s[i]); + } + t.done(); +}); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-delayed-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-delayed-iframe.html new file mode 100644 index 0000000000..f97f597238 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-delayed-iframe.html @@ -0,0 +1,9 @@ +<!doctype html> +<script type=module> +window.parent.document.test.step_timeout(() => { + document.write("document.write body contents\n") + document.close(); + window.parent.document.dispatchEvent(new CustomEvent("documentWriteDone")); +}, 0); +</script> +Initial body contents diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-delayed.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-delayed.html new file mode 100644 index 0000000000..acdeab59ff --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-delayed.html @@ -0,0 +1,29 @@ +<!doctype html> +<title>async document.write in a module</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +async_test(t => { + // Expose {test} in the iframe for using the step_timeout helper. + document.test = t; + const iframe = document.createElement("iframe"); + + iframe.onerror = t.unreached_func("Error loading iframe"); + + let onLoadWasCalled = false; + iframe.onload = t.step_func(() => { + onLoadWasCalled = true; + assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n"); + // Don't call the event handler another time after document.write. + iframe.onload = null; + }); + document.addEventListener("documentWriteDone", t.step_func_done(() => { + assert_true(onLoadWasCalled, "onload must be called"); + assert_equals(iframe.contentDocument.body.textContent, "document.write body contents\n"); + })); + + iframe.src = "module-delayed-iframe.html"; + document.body.appendChild(iframe); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import-iframe.html new file mode 100644 index 0000000000..672bb953d6 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import-iframe.html @@ -0,0 +1,7 @@ +<!doctype html> +<script type=module> +(async () => { + let module = await import("./module-dynamic-import.mjs"); +})(); +</script> +Initial body contents diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import.html new file mode 100644 index 0000000000..5939968f05 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import.html @@ -0,0 +1,27 @@ +<!doctype html> +<title>document.write in an imported module</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +async_test(t => { + const iframe = document.createElement("iframe"); + + iframe.onerror = t.unreached_func("Error loading iframe"); + + let onLoadWasCalled = false; + iframe.onload = t.step_func(() => { + onLoadWasCalled = true; + assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n"); + // Don't call the event handler another time after document.write. + iframe.onload = null; + }); + document.addEventListener("documentWriteDone", t.step_func_done(() => { + assert_true(onLoadWasCalled, "onload must be called"); + assert_equals(iframe.contentDocument.body.textContent, "document.write body contents\n"); + })); + + iframe.src = "module-dynamic-import-iframe.html"; + document.body.appendChild(iframe); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import.mjs b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import.mjs new file mode 100644 index 0000000000..74d2427537 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-dynamic-import.mjs @@ -0,0 +1,4 @@ +document.write("document.write body contents\n"); +document.close(); + +window.parent.document.dispatchEvent(new CustomEvent("documentWriteDone")); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-iframe.html new file mode 100644 index 0000000000..f8646df56b --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-iframe.html @@ -0,0 +1,7 @@ +<!doctype html> +<script type=module> +document.write("document.write body contents\n"); +document.close(); +window.parent.document.dispatchEvent(new CustomEvent("documentWriteDone")); +</script> +Initial body contents diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed-iframe.html new file mode 100644 index 0000000000..3ae1464653 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed-iframe.html @@ -0,0 +1,5 @@ +<!doctype html> +<script type=module> +import "./module-static-import-delayed.mjs" +</script> +Initial body contents diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed.html new file mode 100644 index 0000000000..a6e003907f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed.html @@ -0,0 +1,28 @@ +<!doctype html> +<title>document.write in an imported module</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +async_test(t => { + // Expose {test} in the iframe for using the step_timeout helper. + document.test = t; + const iframe = document.createElement("iframe"); + iframe.onerror = t.unreached_func("Error loading iframe"); + + let onLoadWasCalled = false; + iframe.onload = t.step_func(() => { + onLoadWasCalled = true; + assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n"); + // Don't call the event handler another time after document.write. + iframe.onload = null; + }); + document.addEventListener("documentWriteDone", t.step_func_done(() => { + assert_true(onLoadWasCalled, "onload must be called"); + assert_equals(iframe.contentDocument.body.textContent, "document.write body contents\n"); + })); + + iframe.src = "module-static-import-delayed-iframe.html"; + document.body.appendChild(iframe); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed.mjs b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed.mjs new file mode 100644 index 0000000000..45478d6f63 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-delayed.mjs @@ -0,0 +1,5 @@ +window.parent.document.test.step_timeout(() => { + document.write("document.write body contents\n") + document.close(); + window.parent.document.dispatchEvent(new CustomEvent("documentWriteDone")); +}, 0); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-iframe.html new file mode 100644 index 0000000000..ed4f6d1c6c --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import-iframe.html @@ -0,0 +1,5 @@ +<!doctype html> +<script type=module> +import "./module-static-import.mjs" +</script> +Initial body contents diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import.html new file mode 100644 index 0000000000..3cae88047e --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import.html @@ -0,0 +1,26 @@ +<!doctype html> +<title>document.write in an imported module</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +async_test(t => { + const iframe = document.createElement("iframe"); + + iframe.onerror = t.unreached_func("Error loading iframe"); + + let testEndWasCalled = false; + document.addEventListener("documentWriteDone", t.step_func(() => { + testEndWasCalled = true; + assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n"); + })); + iframe.onload = t.step_func_done(() => { + assert_true(testEndWasCalled, "onload must be called"); + assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n"); + }); + + iframe.src = "module-static-import-iframe.html"; + document.body.appendChild(iframe); +}); +</script> +ß diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import.mjs b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import.mjs new file mode 100644 index 0000000000..74d2427537 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-static-import.mjs @@ -0,0 +1,4 @@ +document.write("document.write body contents\n"); +document.close(); + +window.parent.document.dispatchEvent(new CustomEvent("documentWriteDone")); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-delayed-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-delayed-iframe.html new file mode 100644 index 0000000000..5629c47be7 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-delayed-iframe.html @@ -0,0 +1,14 @@ +<!doctype html> +<script type=module> +let delay = new Promise( + resolve => window.parent.document.test.step_timeout(resolve, 0)); + +delay.then(() => { + document.write("document.write body contents\n"); + document.close(); + window.parent.document.dispatchEvent(new CustomEvent("documentWriteDone")); +}); + +await delay; +</script> +Initial body contents diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-delayed.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-delayed.html new file mode 100644 index 0000000000..5fa8216600 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-delayed.html @@ -0,0 +1,30 @@ +<!doctype html> +<title>document.write in an imported module</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +async_test(t => { + // Expose {test} in the iframe for using the step_timeout helper. + document.test = t; + + const iframe = document.createElement("iframe"); + + iframe.onunhandledrejection = t.unreached_func("Unhandled promise rejection detected"); + iframe.onerror = t.unreached_func("Error loading iframe"); + + let onLoadWasCalled = false; + iframe.onload = t.step_func(() => { + onLoadWasCalled = true; + assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n"); + iframe.onload = null; + }); + document.addEventListener("documentWriteDone", t.step_func_done(() => { + assert_true(onLoadWasCalled, "onload must be called"); + assert_equals(iframe.contentDocument.body.textContent, "document.write body contents\n"); + })); + + iframe.src = "module-tla-delayed-iframe.html"; + document.body.appendChild(iframe); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-immediate-promise-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-immediate-promise-iframe.html new file mode 100644 index 0000000000..3e90fb2ea7 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-immediate-promise-iframe.html @@ -0,0 +1,10 @@ +<!doctype html> +<script type=module> +await new Promise(resolve => { + document.write("document.write body contents\n"); + document.close(); + window.parent.document.dispatchEvent(new CustomEvent("documentWriteDone")); + resolve(); +}); +</script> +Initial body contents diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-immediate-promise.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-immediate-promise.html new file mode 100644 index 0000000000..f60aa38e00 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-immediate-promise.html @@ -0,0 +1,29 @@ +<!doctype html> +<title>document.write in an imported module</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +async_test(t => { + // Expose {test} in the iframe for using the step_timeout helper. + document.test = t; + + const iframe = document.createElement("iframe"); + iframe.onerror = t.unreached_func("Error loading iframe"); + + let onLoadWasCalled = false; + iframe.onload = t.step_func(() => { + onLoadWasCalled = true; + assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n"); + // Don't call the event handler another time after document.write. + iframe.onload = null; + }); + document.addEventListener("documentWriteDone", t.step_func_done(() => { + assert_false(onLoadWasCalled, "onload must not be called yet"); + assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n"); + })); + + iframe.src = "module-tla-immediate-promise-iframe.html"; + document.body.appendChild(iframe); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import-iframe.html new file mode 100644 index 0000000000..ec4a6ed6aa --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import-iframe.html @@ -0,0 +1,5 @@ +<!doctype html> +<script type=module> +await import("./module-tla-import.mjs"); +</script> +Initial body contents diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import.html new file mode 100644 index 0000000000..20645f4d78 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import.html @@ -0,0 +1,27 @@ +<!doctype html> +<title>document.write in an imported module</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +async_test(t => { + const iframe = document.createElement("iframe"); + iframe.onerror = t.unreached_func("Error loading iframe"); + + let onLoadWasCalled = false; + + iframe.onload = t.step_func(() => { + onLoadWasCalled = true; + assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n"); + // Don't call the event handler another time after document.write. + iframe.onload = null; + }); + document.addEventListener("documentWriteDone", t.step_func_done(() => { + assert_true(onLoadWasCalled, "onload must be called"); + assert_equals(iframe.contentDocument.body.textContent, "document.write body contents\n"); + })); + + iframe.src = "module-tla-import-iframe.html"; + document.body.appendChild(iframe); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import.mjs b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import.mjs new file mode 100644 index 0000000000..74d2427537 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-import.mjs @@ -0,0 +1,4 @@ +document.write("document.write body contents\n"); +document.close(); + +window.parent.document.dispatchEvent(new CustomEvent("documentWriteDone")); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-promise-iframe.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-promise-iframe.html new file mode 100644 index 0000000000..edc9e80cb3 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-promise-iframe.html @@ -0,0 +1,11 @@ +<!doctype html> +<script type=module> +await new Promise(resolve => { + window.parent.document.test.step_timeout(resolve, 0); + document.write("document.write body contents\n"); + document.close(); + window.parent.document.dispatchEvent(new CustomEvent("documentWriteDone")); +}); +</script> + +Initial body contents diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-promise.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-promise.html new file mode 100644 index 0000000000..4f1281bcce --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module-tla-promise.html @@ -0,0 +1,24 @@ +<!doctype html> +<title>document.write in an imported module</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +async_test(t => { + // Expose {test} in the iframe for using the step_timeout helper. + document.test = t; + + const iframe = document.createElement("iframe"); + iframe.onerror = t.unreached_func("Error loading iframe"); + + document.addEventListener("documentWriteDone", t.step_func(() => { + assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n"); + })); + iframe.onload = t.step_func_done(() => { + assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n"); + }); + + iframe.src = "module-tla-promise-iframe.html"; + document.body.appendChild(iframe); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module.html new file mode 100644 index 0000000000..7e970d3fd9 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/module.html @@ -0,0 +1,22 @@ +<!doctype html> +<meta charset=utf-8> +<title>document.write in a module</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +async_test(t => { + const iframe = document.createElement("iframe"); + + iframe.onerror = t.unreached_func("Error loading iframe"); + document.addEventListener("documentWriteDone", t.step_func(() => { + assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n"); + })); + iframe.onload = t.step_func_done(() => { + assert_equals(iframe.contentDocument.body.textContent, "Initial body contents\n"); + }); + + iframe.src = "module-iframe.html"; + document.body.appendChild(iframe); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-1.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-1.html new file mode 100644 index 0000000000..c7a7a1db4e --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-1.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<body>You should see the word "worked" below<br><script>document.write("\u003cscript>document.write(\"\\u003cscript src='nested-document-write-external.js'>\\u003c/script>r\"); document.write(\"k\");\u003c/script>e"); document.write("d");</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-2.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-2.html new file mode 100644 index 0000000000..60b8eae1ef --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-2.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<body> +You should see the word "worked" in the frame below.<br> +<iframe></iframe> +<script> +var doc = document.getElementsByTagName("iframe")[0].contentDocument; +doc.open(); doc.write("\u003cscript>document.write(\"\\u003cscript src='nested-document-write-external.js'>\\u003c/script>r\"); document.write(\"k\");\u003c/script>e"); doc.write("d"); doc.close();</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-external.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-external.js new file mode 100644 index 0000000000..bf91daf986 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/nested-document-write-external.js @@ -0,0 +1 @@ +document.write("w"); document.write("o"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/original-id.json b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/original-id.json new file mode 100644 index 0000000000..08bd4d0d4e --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/original-id.json @@ -0,0 +1 @@ +{"original_id":"document.write()"}
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_001.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_001.html new file mode 100644 index 0000000000..43c7adb4d4 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_001.html @@ -0,0 +1,10 @@ +<!doctype html> +<title>document.write script</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + document.write("<script>t.done();<"+"/script>"); +}); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_002.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_002.html new file mode 100644 index 0000000000..3879d8489f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_002.html @@ -0,0 +1,20 @@ +<!doctype html> +<title>document.write script executed synchronously</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +var order = []; +t.step(function() { + document.write("<script>t.step(function() {order.push(1);});<"+"/script>"); + order.push(2); +}); +</script> +<script> +t.step(function() { + order.push(3); + assert_array_equals(order, [1,2,3]); +}) +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_003.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_003.html new file mode 100644 index 0000000000..e669252f75 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_003.html @@ -0,0 +1,10 @@ +<!doctype html> +<title>document.write script writing a further script</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + document.write("<script>document.write('<script>t.done()</script'+'>')<"+"/script>"); +}); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_004.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_004.html new file mode 100644 index 0000000000..15fda325b1 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_004.html @@ -0,0 +1,19 @@ +<!doctype html> +<title>document.write script writing script; order of execution</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +var order = []; +t.step(function() { + order.push(1); + document.write("<script>order.push(2); document.write('<script>order.push(3);</script'+'>'); order.push(4);<"+"/script>"); + order.push(5); +}); +</script> +<script> +t.step(function() { + assert_array_equals(order, [1,2,3,4,5]); +}); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_005.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_005.html new file mode 100644 index 0000000000..b99196c7d0 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_005.html @@ -0,0 +1,20 @@ +<!doctype html> +<title>document.write external script</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +var order = []; +t.step(function() { + order.push(1); + document.write("<script src='005.js'><"+"/script>"); + order.push(2); +}); +</script> +<script> +order.push(4); +t.step(function() { + assert_array_equals(order, [1,2,3,4]); +}); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_006.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_006.html new file mode 100644 index 0000000000..c8dd9a5f9a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_006.html @@ -0,0 +1,20 @@ +<!doctype html> +<title>document.write external script followed by internal script</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +var order = []; +t.step(function() { + order.push(1); + document.write("<script src='006.js'><"+"/script><script>t.step(function(){order.push(4)})</script"+">"); + order.push(2); +}); +</script> +<script> +t.step(function() { + order.push(5); + assert_array_equals(order, [1,2,3,4,5]); +}); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_007.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_007.html new file mode 100644 index 0000000000..fbbe5b2f86 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_007.html @@ -0,0 +1,19 @@ +<!doctype html> +<title>document.write external script that document.writes inline script</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +var order = []; +t.step(function() { + order.push(1); +}); +</script> +<script src="007.js"></script> +<script> +t.step(function() { + order.push(4); + assert_array_equals(order, [1,2,3,4]); +}); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_008.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_008.html new file mode 100644 index 0000000000..c5a44dc700 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_008.html @@ -0,0 +1,19 @@ +<!doctype html> +<title>document.write external script that document.writes external script</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +var order = []; +t.step(function() { + order.push(1); +}); +</script> +<script src="008.js"></script> +<script> +t.step(function() { + order.push(4); + assert_array_equals(order, [1,2,3,4]); +}); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_009.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_009.html new file mode 100644 index 0000000000..d12d934ea0 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_009.html @@ -0,0 +1,19 @@ +<!doctype html> +<title>document.write script that document.writes script</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +var order = []; +t.step(function() { + order.push(1); + document.write("<script>order.push(2); document.write('<script>order.push(3); document.write(\"<script>order.push(4);</script\"+\">\"); order.push(5);</script' + '>'); order.push(6);</script" + ">"); + order.push(7); +}); +</script> +<script> +t.step(function() { + assert_array_equals(order, [1,2,3,4,5,6,7]); +}); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_010.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_010.html new file mode 100644 index 0000000000..93728d6f27 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_010.html @@ -0,0 +1,22 @@ +<!doctype html> +<title>document.write external script tokenizer order</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +var order = []; +t.step(function() { + order.push(1); + document.write("<script src='010.js'></script" + "><meta><script src='010-1.js'></script" + ">"); + order.push(2); + assert_equals(document.getElementsByTagName("meta").length, 0); +}); +</script> +<script> +t.step(function() { + order.push(5); + assert_equals(document.getElementsByTagName("meta").length, 1); + assert_array_equals(order, [1,2,3,4,5]); +}); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_011.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_011.html new file mode 100644 index 0000000000..2bbcaf976e --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_011.html @@ -0,0 +1,22 @@ +<!doctype html> +<title>document.write external script that document.writes external script</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +var order = []; +t.step(function() { + order.push(1); + document.write("<script src='011.js'></script" + "><meta>"); + order.push(2); + assert_equals(document.getElementsByTagName("meta").length, 0); +}); +</script> +<script> +t.step(function() { + order.push(5); + assert_equals(document.getElementsByTagName("meta").length, 3, "Number of meta elements at end"); + assert_array_equals(order, [1,2,3,4,5]); +}); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_012.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_012.html new file mode 100644 index 0000000000..57755f4c94 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_012.html @@ -0,0 +1,22 @@ +<!doctype html> +<title>document.write external script tokenizer order</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +var order = []; +t.step(function() { + order.push(1); + document.write("<script>order.push(2); document.write('<script src=\"012.js\"></script' + '><meta>'); order.push(3); t.step(function() {assert_equals(document.getElementsByTagName('meta').length, 0)});</script" + "><meta>"); + order.push(4); + assert_equals(document.getElementsByTagName("meta").length, 0); +}); +</script> +<script> +t.step(function() { + order.push(6); + assert_equals(document.getElementsByTagName("meta").length, 2); + assert_array_equals(order, [1,2,3,4,5,6]); +}); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_013.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_013.html new file mode 100644 index 0000000000..0e71e5eb0c --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/script_013.html @@ -0,0 +1,24 @@ +<!doctype html> +<title>document.write</title> +<script src="/resources/testharness.js"></script><script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test(); +t.step(function() { + var s = "<script src='013.js'><" + "/script></svg>]]><path></svg>"; + for (var i=0; i<s.length; i++) { + document.write(s[i]); + } +}); +</script><script> +t.step(function() { + assert_equals(document.body.childNodes[0].nodeType, document.ELEMENT_NODE); + assert_equals(document.body.childNodes[0].localName, "svg"); + assert_equals(document.body.childNodes[0].childNodes[0].nodeType, document.TEXT_NODE); + assert_equals(document.body.childNodes[0].childNodes[0].data, "</svg>"); + assert_equals(document.body.childNodes[0].childNodes[1].nodeType, document.ELEMENT_NODE); + assert_equals(document.body.childNodes[0].childNodes[1].localName, "path"); +} +); +t.done(); +</script> +<div id="log"></div> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/write-active-document.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/write-active-document.html new file mode 100644 index 0000000000..6faffd81de --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-write/write-active-document.html @@ -0,0 +1,35 @@ +<!doctype html> +<title>document.write only writes to active documents</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body><div id="log"></div></body> +<script> + async_test(function(t) { + var child = document.createElement("iframe"); + child.src = "empty.html?1"; + child.onload = t.step_func(function() { + var child1 = child.contentDocument; + var link = child1.createElement("a"); + link.href = "data:text/html,Clicked."; + link.innerText = "Link."; + child1.body.appendChild(link); + var grandchild = child1.createElement("iframe"); + grandchild.src = "empty.html?2"; + grandchild.onload = t.step_func(function() { + var grandchild1 = grandchild.contentDocument; + child.onload = t.step_func(function() { + // This is a write to an inactive document + child1.write('WRITE HAPPENED'); + assert_equals(child1.body.lastChild.tagName, "IFRAME"); + // This is a write to an active but not fully active document + grandchild1.write('WRITE HAPPENED'); + assert_equals(grandchild1.body.innerHTML, "WRITE HAPPENED"); + t.done(); + }); + link.click(); + }); + child1.body.appendChild(grandchild); + }); + document.body.appendChild(child); + }); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-01.xhtml b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-01.xhtml new file mode 100644 index 0000000000..cb5ec3a33a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-01.xhtml @@ -0,0 +1,19 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<title>document.writeln in XHTML</title> +<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"/> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#document.writeln%28%29"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + assert_throws_dom("INVALID_STATE_ERR", function() { + document.writeln("Failure: document.writeln actually worked"); + }, "document.writeln in XHTML should throw an INVALID_STATE_ERR "); +}, "document.writeln in XHTML"); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-02.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-02.html new file mode 100644 index 0000000000..2a64ac7561 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-02.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>document.writeln and null/undefined</title> +<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-document-writeln%28%29"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#documents-in-the-dom"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +test(function() { + var iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + doc = iframe.contentDocument; + test(function() { + doc.open(); + doc.writeln(null); + doc.close(); + assert_equals(doc.documentElement.textContent, "null\n"); + }, "document.writeln(null)"); + test(function() { + doc.open(); + doc.writeln(undefined); + doc.close(); + assert_equals(doc.documentElement.textContent, "undefined\n"); + }, "document.writeln(undefined)"); +}, "Calling document.writeln with null and undefined"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-03.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-03.html new file mode 100644 index 0000000000..df9a7a15c2 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/document.writeln-03.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<title>document.writeln with multiple arguments</title> +<link rel="author" title="Sebmaster" href="mailto:wpt@smayr.name"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-document-writeln%28%29"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#documents-in-the-dom"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +test(function() { + var iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + var doc = iframe.contentDocument; + doc.open(); + doc.writeln('a', 'b'); + doc.close(); + assert_equals(doc.documentElement.textContent, "ab\n"); +}, "Calling document.writeln with multiple arguments"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/original-id.json b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/original-id.json new file mode 100644 index 0000000000..0cc32be6a2 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/document-writeln/original-id.json @@ -0,0 +1 @@ +{"original_id":"document.writeln()"}
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/002.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/002.html new file mode 100644 index 0000000000..5584bf9afb --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/002.html @@ -0,0 +1,12 @@ +<!doctype html> +<title>document.open during parsing</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +test(function() { + var log = document.getElementById("log"); + assert_equals(document.open(), document); + assert_equals(document.getElementById("log"), log); +}) +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/004.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/004.html new file mode 100644 index 0000000000..3fb443a993 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/004.html @@ -0,0 +1,19 @@ +<!doctype html> +<title>Reuse of document object after document.open</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe src="/common/blank.html"></iframe> +<script> +var t = async_test(); +var iframe; +onload = t.step_func(function() { + var iframe = document.getElementsByTagName("iframe")[0]; + var handle = iframe.contentDocument; + iframe.contentDocument.test_state = 1; + assert_equals(iframe.contentDocument.open(), handle); + assert_equals(iframe.contentDocument.test_state, 1); + assert_equals(iframe.contentDocument, handle); + t.done(); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/006.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/006.html new file mode 100644 index 0000000000..1dcb92615d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/006.html @@ -0,0 +1,19 @@ +<!doctype html> +<title>Cancelling error after document.open</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe src="/common/blank.html"></iframe> +<script> +var t = async_test(); +var iframe; +onload = t.step_func(function() { + var iframe = document.getElementsByTagName("iframe")[0]; + var img = iframe.contentDocument.createElement("img"); + img.onerror = t.step_func(function() {assert_unreached()}) + img.src = "missing"; + iframe.contentDocument.body.appendChild(img); + assert_equals(iframe.contentDocument.open(), iframe.contentDocument); + setTimeout(function() {t.done();}, 500); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/011-1.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/011-1.html new file mode 100644 index 0000000000..37973fd52e --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/011-1.html @@ -0,0 +1,5 @@ +<script> +parent.t.step(() => { parent.assert_equals(document.open(), document); }); +setTimeout(parent.t.step_func(function() {parent.t.done()}), 0); +document.close(); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/011.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/011.html new file mode 100644 index 0000000000..2acc884c54 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/011.html @@ -0,0 +1,9 @@ +<!doctype html> +<title>Timeout after document.open</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +</script> +<iframe src="011-1.html"></iframe> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/012-1.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/012-1.html new file mode 100644 index 0000000000..644b30827d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/012-1.html @@ -0,0 +1,7 @@ +<script> +onload = parent.t.step_func(function() { + parent.assert_equals(document.open(), document); + setTimeout(parent.t.step_func(function() {parent.t.done()}), 0); + document.close(); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/012.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/012.html new file mode 100644 index 0000000000..518454858d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/012.html @@ -0,0 +1,9 @@ +<!doctype html> +<title>Timeout after document.open in load event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +</script> +<iframe src="012-1.html"></iframe> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/013-1.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/013-1.html new file mode 100644 index 0000000000..ea321238ed --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/013-1.html @@ -0,0 +1,7 @@ +<script> +addEventListener("DOMContentLoaded", parent.t.step_func(function() { + parent.assert_equals(document.open(), document); + setTimeout(parent.t.step_func(function() {parent.t.done()}), 0); + document.close(); +}), false); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/013.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/013.html new file mode 100644 index 0000000000..5749361aa8 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/013.html @@ -0,0 +1,9 @@ +<!doctype html> +<title>Timeout after document.open in DOMContentLoaded event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +</script> +<iframe src="013-1.html"></iframe> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/014-1.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/014-1.html new file mode 100644 index 0000000000..0e97808116 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/014-1.html @@ -0,0 +1,9 @@ +<script> +onload = parent.t.step_func(function() { + setTimeout(parent.t.step_func(function() { + parent.assert_equals(document.open(), document); + setTimeout(parent.t.step_func(function() {parent.t.done()}), 0); + document.close(); + }), 100) +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/014.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/014.html new file mode 100644 index 0000000000..b4e4b17cf4 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/014.html @@ -0,0 +1,9 @@ +<!doctype html> +<title>Timeout after document.open after document is completely loaded</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +</script> +<iframe src="014-1.html"></iframe> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/015-1.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/015-1.html new file mode 100644 index 0000000000..c325bd0801 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/015-1.html @@ -0,0 +1,17 @@ +<script> +onload = function() { + window.test_prop = 1; + parent.tests[0].step(function() {parent.assert_equals(test_prop, 1)}); + parent.tests[0].step(function() {parent.assert_equals(document.open(), document)}); + document.write("<script>test_prop = 2;<\/script>"); + document.close(); + parent.tests[0].step(function() {parent.assert_equals(test_prop, 2)}); + parent.tests[1].step(function() {parent.assert_equals(window.test_prop, 2)}); + parent.tests[2].step(function() {parent.assert_equals(get_this(), window)}); + parent.tests_done(); +}; + +function get_this() { + return this; +} +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/015.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/015.html new file mode 100644 index 0000000000..cce9e65d4c --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/015.html @@ -0,0 +1,14 @@ +<!doctype html> +<title>Window vs global scope after document.open</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var tests = [async_test("global scope unchanged"), + async_test("window object unchanged"), + async_test("this is the window object")]; +function tests_done() { + tests.forEach(function(t) {t.done()}); +} +</script> +<iframe src="015-1.html"></iframe> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/016-1.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/016-1.html new file mode 100644 index 0000000000..ceeeb64df6 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/016-1.html @@ -0,0 +1,41 @@ +<script> +window.test_prop = 1; +</script> +<script> +onload = function() { + parent.tests[0].step(function() { + parent.assert_equals(document.open(), document); + }); + document.write("<script>test_prop = 2; timeout_fired=false;<\/script>"); + document.close(); + + setTimeout(function() { + parent.tests[0].step(function() { + parent.assert_equals(test_prop, 2, "Global scope from original window timeout"); + parent.assert_equals(window.test_prop, 2, "Window property from original window timeout") + }); + parent.tests[1].step(function() { + var t = get_this(); + parent.assert_equals(t.test_prop, 2, "Window property from original window timeout"); + parent.assert_equals(t, window, "Global scope from original window timeout"); + }); + }, 0); + + window.setTimeout(function() { + parent.tests[2].step(function() { + parent.assert_equals(test_prop, 2, "Global scope from original window timeout"); + parent.assert_equals(window.test_prop, 2, "Window property from original window timeout") + }); + parent.tests[3].step(function() { + var t = get_this(); + parent.assert_equals(t.test_prop, 2, "Window property from original window timeout"); + parent.assert_equals(t, window, "Global scope from original window timeout"); + }); + parent.tests_done(); + }, 100); +}; + +function get_this() { + return this; +} +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/016.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/016.html new file mode 100644 index 0000000000..1c70fce591 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/016.html @@ -0,0 +1,15 @@ +<!doctype html> +<title>setTimeout document.open</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var tests = [async_test("Timeout on original window, scope"), + async_test("Timeout on original window, this object"), + async_test("Timeout on new window, scope"), + async_test("Timeout on new window, this object")]; +function tests_done() { + tests.forEach(function(t) {t.done()}); +} +</script> +<iframe src="016-1.html"></iframe> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-immediate.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-immediate.window.js new file mode 100644 index 0000000000..8d045b9e0a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-immediate.window.js @@ -0,0 +1,119 @@ +// The following tests deal with the <meta http-equiv=refresh> pragma and the +// `Refresh` header. The spec is still hazy on the precise behavior in those +// cases but we use https://github.com/whatwg/html/issues/4003 as a guideline. + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + + const client = new frame.contentWindow.XMLHttpRequest(); + client.open("GET", "/common/blank.html"); + client.onabort = t.step_func_done(); + client.send(); + + frame.contentDocument.open(); + }); + frame.src = "resources/meta-refresh.py?0"; +}, "document.open() aborts documents that are queued for navigation through <meta> refresh with timeout 0 (XMLHttpRequest)"); + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + + frame.contentWindow.fetch("/common/blank.html").then( + t.unreached_func("Fetch should have been aborted"), + t.step_func_done()); + + frame.contentDocument.open(); + }); + frame.src = "resources/meta-refresh.py?0"; +}, "document.open() aborts documents that are queued for navigation through <meta> refresh with timeout 0 (fetch())"); + +// We cannot test for img element's error event for this test, as Firefox does +// not fire the event if the fetch is aborted while Chrome does. +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + + let happened = false; + const img = frame.contentDocument.createElement("img"); + img.src = new URL("resources/slow-png.py", document.URL); + img.onload = t.unreached_func("Image loading should not have succeeded"); + // The image fetch starts in a microtask, so let's be sure to test after + // the fetch has started. + t.step_timeout(() => { + frame.contentDocument.open(); + happened = true; + }); + // If 3 seconds have passed and the image has still not loaded, we consider + // it aborted. slow-png.py only sleeps for 2 wallclock seconds. + t.step_timeout(t.step_func_done(() => { + assert_true(happened); + }), 3000); + }); + frame.src = "resources/meta-refresh.py?0"; +}, "document.open() aborts documents that are queued for navigation through <meta> refresh with timeout 0 (image loading)"); + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + + const client = new frame.contentWindow.XMLHttpRequest(); + client.open("GET", "/common/blank.html"); + client.onabort = t.step_func_done(); + client.send(); + + frame.contentDocument.open(); + }); + frame.src = "resources/http-refresh.py?0"; +}, "document.open() aborts documents that are queued for navigation through Refresh header with timeout 0 (XMLHttpRequest)"); + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + + frame.contentWindow.fetch("/common/blank.html").then( + t.unreached_func("Fetch should have been aborted"), + t.step_func_done()); + + frame.contentDocument.open(); + }); + frame.src = "resources/http-refresh.py?0"; +}, "document.open() aborts documents that are queued for navigation through Refresh header with timeout 0 (fetch())"); + +// We cannot test for img element's error event for this test, as Firefox does +// not fire the event if the fetch is aborted while Chrome does. +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + + let happened = false; + const img = frame.contentDocument.createElement("img"); + img.src = new URL("resources/slow-png.py", document.URL); + img.onload = t.unreached_func("Image loading should not have succeeded"); + // The image fetch starts in a microtask, so let's be sure to test after + // the fetch has started. + t.step_timeout(() => { + frame.contentDocument.open(); + happened = true; + }); + // If 3 seconds have passed and the image has still not loaded, we consider + // it aborted. slow-png.py only sleeps for 2 wallclock seconds. + t.step_timeout(t.step_func_done(() => { + assert_true(happened); + }), 3000); + }); + frame.src = "resources/http-refresh.py?0"; +}, "document.open() aborts documents that are queued for navigation through Refresh header with timeout 0 (image loading)"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-multisecond-header.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-multisecond-header.window.js new file mode 100644 index 0000000000..8c6c1267c4 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-multisecond-header.window.js @@ -0,0 +1,69 @@ +// The following tests deal with the <meta http-equiv=refresh> pragma and the +// `Refresh` header. The spec is still hazy on the precise behavior in those +// cases but we use https://github.com/whatwg/html/issues/4003 as a guideline. +// +// This is separate from abort-refresh-multisecond-meta.window.js to avoid +// browser interventions that limit the number of connections in a tab. + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + let happened = false; + + const client = new frame.contentWindow.XMLHttpRequest(); + client.open("GET", "/common/blank.html"); + client.onload = t.step_func_done(() => { + assert_true(happened); + }); + client.onerror = t.unreached_func("XMLHttpRequest should have succeeded"); + client.onabort = t.unreached_func("XMLHttpRequest should have succeeded"); + client.ontimeout = t.unreached_func("XMLHttpRequest should have succeeded"); + client.send(); + + frame.contentDocument.open(); + happened = true; + }); + frame.src = "resources/http-refresh.py?1"; +}, "document.open() does NOT abort documents that are queued for navigation through Refresh header with 1-sec timeout (XMLHttpRequest)"); + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + let happened = false; + frame.contentWindow.fetch("/common/blank.html").then( + t.step_func_done(() => { + assert_true(happened); + }), + t.unreached_func("Fetch should have succeeded") + ); + frame.contentDocument.open(); + happened = true; + }); + frame.src = "resources/http-refresh.py?1"; +}, "document.open() does NOT abort documents that are queued for navigation through Refresh header with 1-sec timeout (fetch())"); + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + let happened = false; + const img = frame.contentDocument.createElement("img"); + img.src = new URL("resources/slow-png.py", document.URL); + img.onload = t.step_func_done(() => { + assert_true(happened); + }); + img.onerror = t.unreached_func("Image loading should not have errored"); + // The image fetch starts in a microtask, so let's be sure to test after + // the fetch has started. + t.step_timeout(() => { + frame.contentDocument.open(); + happened = true; + }); + }); + frame.src = "resources/http-refresh.py?4"; +}, "document.open() does NOT abort documents that are queued for navigation through Refresh header with 4-sec timeout (image loading)"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-multisecond-meta.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-multisecond-meta.window.js new file mode 100644 index 0000000000..2895f959e5 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-refresh-multisecond-meta.window.js @@ -0,0 +1,69 @@ +// The following tests deal with the <meta http-equiv=refresh> pragma and the +// `Refresh` header. The spec is still hazy on the precise behavior in those +// cases but we use https://github.com/whatwg/html/issues/4003 as a guideline. +// +// This is separate from abort-refresh-multisecond-header.window.js to avoid +// browser interventions that limit the number of connections in a tab. + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + let happened = false; + + const client = new frame.contentWindow.XMLHttpRequest(); + client.open("GET", "/common/blank.html"); + client.onload = t.step_func_done(() => { + assert_true(happened); + }); + client.onerror = t.unreached_func("XMLHttpRequest should have succeeded"); + client.onabort = t.unreached_func("XMLHttpRequest should have succeeded"); + client.ontimeout = t.unreached_func("XMLHttpRequest should have succeeded"); + client.send(); + + frame.contentDocument.open(); + happened = true; + }); + frame.src = "resources/meta-refresh.py?1"; +}, "document.open() does NOT abort documents that are queued for navigation through <meta> refresh with 1-sec timeout (XMLHttpRequest)"); + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + let happened = false; + frame.contentWindow.fetch("/common/blank.html").then( + t.step_func_done(() => { + assert_true(happened); + }), + t.unreached_func("Fetch should have succeeded") + ); + frame.contentDocument.open(); + happened = true; + }); + frame.src = "resources/meta-refresh.py?1"; +}, "document.open() does NOT abort documents that are queued for navigation through <meta> refresh with 1-sec timeout (fetch())"); + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + let happened = false; + const img = frame.contentDocument.createElement("img"); + img.src = new URL("resources/slow-png.py", document.URL); + img.onload = t.step_func_done(() => { + assert_true(happened); + }); + img.onerror = t.unreached_func("Image loading should not have errored"); + // The image fetch starts in a microtask, so let's be sure to test after + // the fetch has started. + t.step_timeout(() => { + frame.contentDocument.open(); + happened = true; + }); + }); + frame.src = "resources/meta-refresh.py?4"; +}, "document.open() does NOT abort documents that are queued for navigation through <meta> refresh with 4-sec timeout (image loading)"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-while-navigating.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-while-navigating.window.js new file mode 100644 index 0000000000..e3efeffb8b --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort-while-navigating.window.js @@ -0,0 +1,179 @@ +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + const client = new frame.contentWindow.XMLHttpRequest(); + client.open("GET", "/common/blank.html"); + // The abort event handler is called synchronously in Chrome but + // asynchronously in Firefox. See https://crbug.com/879620. + client.onabort = t.step_func_done(); + client.send(); + frame.contentWindow.location.href = new URL("resources/dummy.html", document.URL); + frame.contentDocument.open(); + }); + frame.src = "/common/blank.html"; +}, "document.open() aborts documents that are navigating through Location (XMLHttpRequest)"); + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + let happened = false; + frame.contentWindow.fetch("/common/blank.html").then( + t.unreached_func("Fetch should have been aborted"), + t.step_func_done(() => { + assert_true(happened); + })); + frame.contentWindow.location.href = new URL("resources/dummy.html", document.URL); + frame.contentDocument.open(); + happened = true; + }); + frame.src = "/common/blank.html"; +}, "document.open() aborts documents that are navigating through Location (fetch())"); + +// We cannot test for img element's error event for this test, as Firefox does +// not fire the event if the fetch is aborted while Chrome does. +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + let happened = false; + const img = frame.contentDocument.createElement("img"); + img.src = new URL("resources/slow-png.py", document.URL); + img.onload = t.unreached_func("Image loading should not have succeeded"); + // The image fetch starts in a microtask, so let's be sure to test after + // the fetch has started. + t.step_timeout(() => { + frame.contentWindow.location.href = new URL("resources/dummy.html", document.URL); + frame.contentDocument.open(); + happened = true; + }); + // If 3 seconds have passed and the image has still not loaded, we consider + // it aborted. slow-png.py only sleeps for 2 wallclock seconds. + t.step_timeout(t.step_func_done(() => { + assert_true(happened); + }), 3000); + }); + frame.src = "/common/blank.html"; +}, "document.open() aborts documents that are navigating through Location (image loading)"); + +async_test(t => { + const div = document.body.appendChild(document.createElement("div")); + t.add_cleanup(() => div.remove()); + div.innerHTML = "<iframe src='/common/slow.py'></iframe>"; + const frame = div.childNodes[0]; + const client = new frame.contentWindow.XMLHttpRequest(); + client.open("GET", "/common/blank.html"); + client.onabort = t.step_func_done(); + client.send(); + frame.contentDocument.open(); +}, "document.open() aborts documents that are navigating through iframe loading (XMLHttpRequest)"); + +async_test(t => { + const div = document.body.appendChild(document.createElement("div")); + t.add_cleanup(() => div.remove()); + div.innerHTML = "<iframe src='/common/slow.py'></iframe>"; + const frame = div.childNodes[0]; + frame.contentWindow.fetch("/common/blank.html").then( + t.unreached_func("Fetch should have been aborted"), + t.step_func_done()); + frame.contentDocument.open(); +}, "document.open() aborts documents that are navigating through iframe loading (fetch())"); + +// We cannot test for img element's error event for this test, as Firefox does +// not fire the event if the fetch is aborted while Chrome does. +// +// We use /common/slow.py here as the source of the iframe, to prevent the +// situation where when document.open() is called the initial about:blank +// document has already become inactive. +async_test(t => { + const div = document.body.appendChild(document.createElement("div")); + t.add_cleanup(() => div.remove()); + div.innerHTML = "<iframe src='/common/slow.py'></iframe>"; + const frame = div.childNodes[0]; + let happened = false; + const img = frame.contentDocument.createElement("img"); + img.src = new URL("resources/slow-png.py", document.URL); + img.onload = t.unreached_func("Image loading should not have succeeded"); + // The image fetch starts in a microtask, so let's be sure to test after + // the fetch has started. + t.step_timeout(() => { + frame.contentDocument.open(); + happened = true; + }); + // If 3 seconds have passed and the image has still not loaded, we consider + // it aborted. slow-png.py only sleeps for 2 wallclock seconds. + t.step_timeout(t.step_func_done(() => { + assert_true(happened); + }), 3000); +}, "document.open() aborts documents that are navigating through iframe loading (image loading)"); + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + const link = frame.contentDocument.body.appendChild(frame.contentDocument.createElement("a")); + link.href = new URL("resources/dummy.html", document.URL); + + const client = new frame.contentWindow.XMLHttpRequest(); + client.open("GET", "/common/blank.html"); + client.onabort = t.step_func_done(); + client.send(); + + link.click(); + frame.contentDocument.open(); + }); + frame.src = "/common/blank.html"; +}, "document.open() aborts documents that are queued for navigation through .click() (XMLHttpRequest)"); + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + const link = frame.contentDocument.body.appendChild(frame.contentDocument.createElement("a")); + link.href = new URL("resources/dummy.html", document.URL); + + frame.contentWindow.fetch("/common/blank.html").then( + t.unreached_func("Fetch should have been aborted"), + t.step_func_done()); + + link.click(); + frame.contentDocument.open(); + }); + frame.src = "/common/blank.html"; +}, "document.open() aborts documents that are queued for navigation through .click() (fetch())"); + +// We cannot test for img element's error event for this test, as Firefox does +// not fire the event if the fetch is aborted while Chrome does. +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + const link = frame.contentDocument.body.appendChild(frame.contentDocument.createElement("a")); + link.href = new URL("resources/dummy.html", document.URL); + + let happened = false; + const img = frame.contentDocument.createElement("img"); + img.src = new URL("resources/slow-png.py", document.URL); + img.onload = t.unreached_func("Image loading should not have succeeded"); + // The image fetch starts in a microtask, so let's be sure to test after + // the fetch has started. + t.step_timeout(() => { + link.click(); + frame.contentDocument.open(); + happened = true; + }); + // If 3 seconds have passed and the image has still not loaded, we consider + // it aborted. slow-png.py only sleeps for 2 wallclock seconds. + t.step_timeout(t.step_func_done(() => { + assert_true(happened); + }), 3000); + }); + frame.src = "/common/blank.html"; +}, "document.open() aborts documents that are queued for navigation through .click() (image loading)"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort.sub.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort.sub.window.js new file mode 100644 index 0000000000..b2f05cf056 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/abort.sub.window.js @@ -0,0 +1,104 @@ +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + let happened = false; + const client = new frame.contentWindow.XMLHttpRequest(); + client.open("GET", "/common/blank.html"); + client.onload = t.step_func_done(e => { + assert_true(happened); + }); + client.onerror = t.unreached_func("XMLHttpRequest should have succeeded"); + client.onabort = t.unreached_func("XMLHttpRequest should have succeeded"); + client.ontimeout = t.unreached_func("XMLHttpRequest should have succeeded"); + client.send(); + frame.contentDocument.open(); + happened = true; + }); + frame.src = "/common/blank.html"; +}, "document.open() does not abort documents that are not navigating (XMLHttpRequest)"); + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + let happened = false; + frame.contentWindow.fetch("/common/blank.html").then( + t.step_func_done(() => { + assert_true(happened); + }), + t.unreached_func("Fetch should have succeeded") + ); + frame.contentDocument.open(); + happened = true; + }); + frame.src = "/common/blank.html"; +}, "document.open() does not abort documents that are not navigating (fetch())"); + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + let happened = false; + const img = frame.contentDocument.createElement("img"); + img.src = new URL("resources/slow-png.py", document.URL); + img.onload = t.step_func_done(() => { + assert_true(happened); + }); + img.onerror = t.unreached_func("Image loading should not have errored"); + // The image fetch starts in a microtask, so let's be sure to test after + // the fetch has started. + t.step_timeout(() => { + frame.contentDocument.open(); + happened = true; + }); + }); + frame.src = "/common/blank.html"; +}, "document.open() does not abort documents that are not navigating (image loading)"); + +async_test(t => { + const __SERVER__NAME = "{{host}}"; + const __PORT = {{ports[ws][0]}}; + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + let happened = false; + const ws = new frame.contentWindow.WebSocket(`ws://${__SERVER__NAME}:${__PORT}/echo`); + ws.onopen = t.step_func_done(() => { + assert_true(happened); + }); + ws.onclose = t.unreached_func("WebSocket fetch should have succeeded"); + ws.onerror = t.unreached_func("WebSocket should have no error"); + frame.contentDocument.open(); + happened = true; + }); + frame.src = "/common/blank.html"; +}, "document.open() does not abort documents that are not navigating (establish a WebSocket connection)"); + +// An already established WebSocket connection shouldn't be terminated during +// an "abort a document" anyway. Test just for completeness. +async_test(t => { + const __SERVER__NAME = "{{host}}"; + const __PORT = {{ports[ws][0]}}; + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + frame.onload = null; + let happened = false; + const ws = new frame.contentWindow.WebSocket(`ws://${__SERVER__NAME}:${__PORT}/echo`); + ws.onopen = t.step_func(() => { + t.step_timeout(t.step_func_done(() => { + assert_true(happened); + }), 100); + frame.contentDocument.open(); + happened = true; + }); + ws.onclose = t.unreached_func("WebSocket should not be closed"); + ws.onerror = t.unreached_func("WebSocket should have no error"); + }); + frame.src = "/common/blank.html"; +}, "document.open() does not abort documents that are not navigating (already established WebSocket connection)"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/aborted-parser.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/aborted-parser.window.js new file mode 100644 index 0000000000..ba7278ef18 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/aborted-parser.window.js @@ -0,0 +1,31 @@ +// document.open() bails out early if there is an active parser with non-zero +// script nesting level or if a load was aborted while there was an active +// parser. window.stop() aborts the current parser, so once it has been called +// while a parser is active, document.open() will no longer do anything to that +// document, + +window.handlers = {}; + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.src = "resources/aborted-parser-frame.html"; + window.handlers.afterOpen = t.step_func_done(() => { + const openCalled = frame.contentDocument.childNodes.length === 0; + assert_false(openCalled, "child document should not be empty"); + assert_equals(frame.contentDocument.querySelector("p").textContent, + "Text", "Should still have our paragraph"); + }); +}, "document.open() after parser is aborted"); + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.src = "resources/aborted-parser-async-frame.html"; + window.handlers.afterOpenAsync = t.step_func_done(() => { + const openCalled = frame.contentDocument.childNodes.length === 0; + assert_false(openCalled, "child document should not be empty"); + assert_equals(frame.contentDocument.querySelector("p").textContent, + "Text", "Should still have our paragraph"); + }); +}, "async document.open() after parser is aborted"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/active.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/active.window.js new file mode 100644 index 0000000000..f96710999a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/active.window.js @@ -0,0 +1,98 @@ +function assertOpenIsEffective(doc, initialNodeCount) { + assert_equals(doc.childNodes.length, initialNodeCount); + + // Test direct document.open() call. + assert_equals(doc.open(), doc); + assert_equals(doc.childNodes.length, 0, "after open: no nodes in document"); + doc.write("<!DOCTYPE html>"); + assert_equals(doc.childNodes.length, 1, "after write: doctype node in document"); + doc.close(); + assert_equals(doc.childNodes.length, 2, "after parser close: doctype node and an html element in document"); + + // Test implicit document.open() call through write(). Since we called + // doc.close() above, which sets the insertion point of the parser to + // undefined, document.write() will run the document open steps. + doc.write(); + assert_equals(doc.childNodes.length, 0, "after implicit open: no nodes in document"); + doc.write("<!DOCTYPE html>"); + assert_equals(doc.childNodes.length, 1, "after write: doctype node in document"); + doc.close(); + assert_equals(doc.childNodes.length, 2, "after parser close: doctype node and an html element in document"); +} + +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + assertOpenIsEffective(frame.contentDocument, 1); +}, "document.open() removes the document's children (fully active document)"); + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + const childFrame = frame.contentDocument.querySelector("iframe"); + const childDoc = childFrame.contentDocument; + const childWin = childFrame.contentWindow; + + // Right now childDoc is still fully active. + + frame.onload = t.step_func_done(() => { + // Now childDoc is still active but no longer fully active. + assertOpenIsEffective(childDoc, 1); + }); + frame.src = "/common/blank.html"; + }); + frame.src = "resources/page-with-frame.html"; +}, "document.open() removes the document's children (active but not fully active document)"); + +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + const doc = frame.contentDocument; + + // Right now the frame is connected and it has an active document. + frame.remove(); + + // Now the frame is no longer connected. Its document is no longer active. + assertOpenIsEffective(doc, 1); +}, "document.open() removes the document's children (non-active document with an associated Window object; frame is removed)"); + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.src = "resources/dummy.html"; + + frame.onload = t.step_func(() => { + const firstDocument = frame.contentDocument; + // Right now the frame is connected and it has an active document. + + frame.onload = t.step_func_done(() => { + // Now even though the frame is still connected, its document is no + // longer active. + assert_not_equals(frame.contentDocument, firstDocument); + assertOpenIsEffective(firstDocument, 2); + }); + + frame.src = "/common/blank.html"; + }); +}, "document.open() removes the document's children (non-active document with an associated Window object; navigated away)"); + +test(t => { + const doc = document.implementation.createHTMLDocument(); + assertOpenIsEffective(doc, 2); +}, "document.open() removes the document's children (non-active document without an associated Window object; createHTMLDocument)"); + +test(t => { + const doc = new DOMParser().parseFromString("", "text/html"); + assertOpenIsEffective(doc, 1); +}, "document.open() removes the document's children (non-active document without an associated Window object; DOMParser)"); + +async_test(t => { + const xhr = new XMLHttpRequest(); + xhr.onload = t.step_func_done(() => { + assert_equals(xhr.status, 200); + assertOpenIsEffective(xhr.responseXML, 2); + }); + xhr.responseType = "document"; + xhr.open("GET", "resources/dummy.html"); + xhr.send(); +}, "document.open() removes the document's children (non-active document without an associated Window object; XMLHttpRequest)"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-origin.sub.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-origin.sub.window.js new file mode 100644 index 0000000000..b20c3e3f31 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-origin.sub.window.js @@ -0,0 +1,117 @@ +document.domain = "{{host}}"; + +// In many cases in this test, we want to delay execution of a piece of code so +// that the entry settings object would be the top-level page. A microtask is +// perfect for this purpose as it is executed in the "clean up after running +// script" algorithm, which is generally called right after the callback. +function setEntryToTopLevel(cb) { + Promise.resolve().then(cb); +} + +async_test(t => { + const iframe = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => { iframe.remove(); }); + iframe.onload = t.step_func_done(() => { + // Since this is called as an event handler on an element of this window, + // the entry settings object is that of this browsing context. + assert_throws_dom( + "InvalidStateError", + iframe.contentWindow.DOMException, + () => { + iframe.contentDocument.open(); + }, + "opening an XML document should throw an InvalidStateError" + ); + }); + const frameURL = new URL("resources/bailout-order-xml-with-domain-frame.sub.xhtml", document.URL); + frameURL.port = "{{ports[http][1]}}"; + iframe.src = frameURL.href; +}, "document.open should throw an InvalidStateError with XML document even if it is cross-origin"); + +async_test(t => { + const iframe = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => { iframe.remove(); }); + window.onCustomElementReady = t.step_func(() => { + window.onCustomElementReady = t.unreached_func("onCustomElementReady called again"); + // Here, the entry settings object is still the iframe's, as the function + // is called from a custom element constructor in the iframe document. + // Delay execution in such a way that makes the entry settings object the + // top-level page's, but without delaying too much that the + // throw-on-dynamic-markup-insertion counter gets decremented (which is + // what this test tries to pit against the cross-origin document check). + // + // "Clean up after running script" is executed through the "construct" Web + // IDL algorithm in "create an element", called by "create an element for a + // token" in the parser. + setEntryToTopLevel(t.step_func_done(() => { + assert_throws_dom( + "InvalidStateError", + iframe.contentWindow.DOMException, + () => { + iframe.contentDocument.open(); + }, + "opening a document when the throw-on-dynamic-markup-insertion counter is incremented should throw an InvalidStateError" + ); + })); + }); + const frameURL = new URL("resources/bailout-order-custom-element-with-domain-frame.sub.html", document.URL); + frameURL.port = "{{ports[http][1]}}"; + iframe.src = frameURL.href; +}, "document.open should throw an InvalidStateError when the throw-on-dynamic-markup-insertion counter is incremented even if the document is cross-origin"); + +async_test(t => { + const iframe = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => { iframe.remove(); }); + self.testSynchronousScript = t.step_func(() => { + // Here, the entry settings object is still the iframe's, as the function + // is synchronously called from a <script> element in the iframe's + // document. + // + // "Clean up after running script" is executed when the </script> tag is + // seen by the HTML parser. + setEntryToTopLevel(t.step_func_done(() => { + assert_throws_dom( + "SecurityError", + iframe.contentWindow.DOMException, + () => { + iframe.contentDocument.open(); + }, + "opening a same origin-domain (but not same origin) document should throw a SecurityError" + ); + })); + }); + const frameURL = new URL("resources/bailout-order-synchronous-script-with-domain-frame.sub.html", document.URL); + frameURL.port = "{{ports[http][1]}}"; + iframe.src = frameURL.href; +}, "document.open should throw a SecurityError with cross-origin document even when there is an active parser executing script"); + +for (const ev of ["beforeunload", "pagehide", "unload"]) { + async_test(t => { + const iframe = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => { iframe.remove(); }); + iframe.addEventListener("load", t.step_func(() => { + iframe.contentWindow.addEventListener(ev, t.step_func(() => { + // Here, the entry settings object should be the top-level page's, as + // the callback context of this event listener is the incumbent + // settings object, which is the this page. However, due to a Chrome + // bug (https://crbug.com/606900), the entry settings object may be + // mis-set to the iframe's. + // + // "Clean up after running script" is called in the task that + // navigates. + setEntryToTopLevel(t.step_func_done(() => { + assert_throws_dom( + "SecurityError", + iframe.contentWindow.DOMException, + () => { + iframe.contentDocument.open(); + }, + "opening a same origin-domain (but not same origin) document should throw a SecurityError" + ); + })); + })); + iframe.src = "about:blank"; + }), { once: true }); + iframe.src = "http://{{host}}:{{ports[http][1]}}/common/domain-setter.sub.html"; + }, `document.open should throw a SecurityError with cross-origin document even when the ignore-opens-during-unload counter is greater than 0 (during ${ev} event)`); +} diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-xml.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-xml.window.js new file mode 100644 index 0000000000..45a67f925b --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-exception-vs-return-xml.window.js @@ -0,0 +1,26 @@ +async_test(t => { + const iframe = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => { iframe.remove(); }); + self.testSynchronousScript = t.step_func_done(() => { + assert_throws_dom("InvalidStateError", iframe.contentWindow.DOMException, () => { + iframe.contentDocument.open(); + }, "opening an XML document should throw"); + }); + iframe.src = "resources/bailout-order-xml-with-synchronous-script-frame.xhtml"; +}, "document.open should throw an InvalidStateError with XML document even when there is an active parser executing script"); + +for (const ev of ["beforeunload", "pagehide", "unload"]) { + async_test(t => { + const iframe = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => { iframe.remove(); }); + iframe.addEventListener("load", t.step_func(() => { + iframe.contentWindow.addEventListener(ev, t.step_func_done(() => { + assert_throws_dom("InvalidStateError", iframe.contentWindow.DOMException, () => { + iframe.contentDocument.open(); + }, "opening an XML document should throw"); + })); + iframe.src = "about:blank"; + }), { once: true }); + iframe.src = "/common/dummy.xhtml"; + }, `document.open should throw an InvalidStateError with XML document even when the ignore-opens-during-unload counter is greater than 0 (during ${ev} event)`); +} diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-ignore-opens-during-unload.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-ignore-opens-during-unload.window.js new file mode 100644 index 0000000000..98ffba20a1 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-ignore-opens-during-unload.window.js @@ -0,0 +1,18 @@ +// META: script=resources/document-open-side-effects.js + +for (const ev of ["unload", "beforeunload", "pagehide"]) { + async_test(t => { + const iframe = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => iframe.remove()); + iframe.src = "/common/blank.html"; + iframe.onload = t.step_func(() => { + iframe.contentWindow.addEventListener(ev, t.step_func_done(() => { + const origURL = iframe.contentDocument.URL; + assertDocumentIsReadyForSideEffectsTest(iframe.contentDocument, `ignore-opens-during-unload counter is greater than 0 during ${ev} event`); + assert_equals(iframe.contentDocument.open(), iframe.contentDocument); + assertOpenHasNoSideEffects(iframe.contentDocument, origURL, `ignore-opens-during-unload counter is greater than 0 during ${ev} event`); + })); + iframe.src = "about:blank"; + }); + }, `document.open bailout should not have any side effects (ignore-opens-during-unload is greater than 0 during ${ev} event)`); +} diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-same-origin-domain.sub.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-same-origin-domain.sub.window.js new file mode 100644 index 0000000000..f5edd7aed9 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-same-origin-domain.sub.window.js @@ -0,0 +1,14 @@ +// META: script=/html/resources/common.js +// META: script=resources/document-open-side-effects.js + +document.domain = "{{host}}"; + +testInIFrame("http://{{host}}:{{ports[http][1]}}/common/domain-setter.sub.html", (ctx) => { + const iframe = ctx.iframes[0]; + const origURL = iframe.contentDocument.URL; + assertDocumentIsReadyForSideEffectsTest(iframe.contentDocument, "same origin-domain (but not same origin) document"); + assert_throws_dom("SecurityError", iframe.contentWindow.DOMException, () => { + ctx.iframes[0].contentDocument.open(); + }, "document.open() should throw a SecurityError on a same origin-domain (but not same origin) document"); + assertOpenHasNoSideEffects(iframe.contentDocument, origURL, "same origin-domain (but not same origin) document"); +}, "document.open bailout should not have any side effects (same origin-domain (but not same origin) document)"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-synchronous-script.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-synchronous-script.window.js new file mode 100644 index 0000000000..fb26c70a9c --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-synchronous-script.window.js @@ -0,0 +1,19 @@ +// META: script=resources/document-open-side-effects.js + +async_test(t => { + const iframe = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => iframe.remove()); + self.testSynchronousScript = t.step_func(() => { + // Here, the entry settings object is still the iframe's. Delay it in such + // a way that makes the entry settings object the top-level page's, but + // without delaying too much that the parser becomes inactive. A microtask + // is perfect as it's executed in "clean up after running script". + Promise.resolve().then(t.step_func_done(() => { + const origURL = iframe.contentDocument.URL; + assertDocumentIsReadyForSideEffectsTest(iframe.contentDocument, "active parser whose script nesting level is greater than 0"); + assert_equals(iframe.contentDocument.open(), iframe.contentDocument); + assertOpenHasNoSideEffects(iframe.contentDocument, origURL, "active parser whose script nesting level is greater than 0"); + })); + }); + iframe.src = "resources/bailout-order-synchronous-script-frame.html"; +}, "document.open bailout should not have any side effects (active parser whose script nesting level is greater than 0)"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-xml.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-xml.window.js new file mode 100644 index 0000000000..bbfc015c68 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/bailout-side-effects-xml.window.js @@ -0,0 +1,20 @@ +// META: script=resources/document-open-side-effects.js + +async_test(t => { + const iframe = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => iframe.remove()); + iframe.src = "/common/dummy.xhtml"; + iframe.onload = t.step_func_done(() => { + const origURL = iframe.contentDocument.URL; + assertDocumentIsReadyForSideEffectsTest(iframe.contentDocument, "XML document"); + assert_throws_dom( + "InvalidStateError", + iframe.contentWindow.DOMException, + () => { + iframe.contentDocument.open(); + }, + "document.open() should throw on XML documents" + ); + assertOpenHasNoSideEffects(iframe.contentDocument, origURL, "XML document"); + }); +}, "document.open bailout should not have any side effects (XML document)"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/beforeunload.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/beforeunload.window.js new file mode 100644 index 0000000000..1e2f891c17 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/beforeunload.window.js @@ -0,0 +1,18 @@ +// In an earlier version of the HTML Standard, document open steps had "prompt +// to unload document" as a step. Test that this no longer happens. + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.src = "/common/blank.html"; + frame.onload = t.step_func(() => { + frame.contentWindow.onbeforeunload = t.unreached_func("beforeunload should not be fired"); + frame.contentDocument.open(); + t.step_timeout(t.step_func_done(() => { + // If the beforeunload event has still not fired by this point, we + // consider the test a success. `frame.remove()` above will allow the + // `load` event to be fired on the top-level Window, thus unblocking + // testharness. + }), 500); + }); +}, "document.open() should not fire a beforeunload event"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/crbug-583445-regression.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/crbug-583445-regression.window.js new file mode 100644 index 0000000000..3809c2e081 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/crbug-583445-regression.window.js @@ -0,0 +1,127 @@ +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/common/dispatcher/dispatcher.js +// +// This is a regression test for crbug.com/583445. It checks an obscure bug in +// Chromium's handling of `document.open()` whereby the URL change would affect +// the document's origin after a javascript navigation. +// +// See also dcheng@'s comments on the original code review in which he +// introduced the precursor to this test: +// https://codereview.chromium.org/1675473002. + +function nextMessage() { + return new Promise((resolve) => { + window.addEventListener("message", (e) => { resolve(e.data); }, { + once: true + }); + }); +} + +promise_test(async (t) => { + // Embed a cross-origin frame A and set up remote code execution. + const iframeA = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => { iframeA.remove(); }); + + const uuidA = token(); + iframeA.src = remoteExecutorUrl(uuidA, { host: get_host_info().REMOTE_HOST }); + const ctxA = new RemoteContext(uuidA); + + // Frame A embeds a cross-origin frame B, which is same-origin with the + // top-level frame. Frame B is the center of this test: it is where we will + // verify that a bug does not grant it UXSS in frame A. + // + // Though we could reach into `iframeA.frames[0]` to get a proxy to frame B + // and use `setTimeout()` like below to execute code inside it, we set up + // remote code execution using `dispatcher.js` for better ergonomics. + const uuidB = token(); + await ctxA.execute_script((url) => { + const iframeB = document.createElement("iframe"); + iframeB.src = url; + document.body.appendChild(iframeB); + }, [remoteExecutorUrl(uuidB).href]); + + // Start listening for a message, which will come as a result of executing + // the code below in frame B. + const message = nextMessage(); + + const ctxB = new RemoteContext(uuidB); + await ctxB.execute_script(() => { + // Frame B embeds an `about:blank` frame C. + const iframeC = document.body.appendChild(document.createElement("iframe")); + + // We wish to execute code inside frame C, but it is important to this test + // that its URL remain `about:blank`, so we cannot use `dispatcher.js`. + // Instead we rely on `setTimeout()`. + // + // We use `setTimeout(string, ...)` instead of `setTimeout(function, ...)` + // as the given script executes against the target window's global object + // and does not capture any local variables. + // + // In order to have nice syntax highlighting and avoid quote-escaping hell, + // we use a trick employed by `dispatcher.js`. We rely on the fact that + // functions in JS have a stringifier that returns their source code. Thus + // `"(" + func + ")()"` is a string that executes `func()` when evaluated. + iframeC.contentWindow.setTimeout("(" + (() => { + // This executes in frame C. + + // Frame C calls `document.open()` on its parent, which results in B's + // URL being set to `about:blank` (C's URL). + // + // However, just before `document.open()` is called, B schedules a + // self-navigation to a `javascript:` URL. This will occur after + // `document.open()`, so the document will navigate from `about:blank` to + // the new URL. + // + // This should not result in B's origin changing, so B should remain + // same-origin with the top-level frame. + // + // Due to crbug.com/583445, this used to behave wrongly in Chromium. The + // navigation code incorrectly assumed that B's origin should be inherited + // from its parent A because B's URL was `about:blank`. + // + // It is important to schedule this from within the child, as this + // guarantees that `document.open()` will be called before the navigation. + // A previous version of this test scheduled this from within frame B + // right after scheduling the call to `document.open()`, but that ran the + // risk of races depending on which timeout fired first. + parent.window.setTimeout("(" + (() => { + // This executes in frame B. + + location = "javascript:(" + (() => { + /* This also executes in frame B. + * + * Note that because this whole function gets stuffed in a JS URL, + * single-line comments do not work, as they affect the following + * lines. */ + + let error; + try { + /* This will fail with a `SecurityError` if frame B is no longer + * same-origin with the top-level frame. */ + top.window.testSameOrigin = true; + } catch (e) { + error = e; + } + + top.postMessage({ + error: error?.toString(), + }, "*"); + + }) + ")()"; + + }) + ")()", 0); + + // This executes in frame C. + parent.document.open(); + + }) + ")()", 0); + }); + + // Await the message from frame B after its navigation. + const { error } = await message; + assert_equals(error, undefined, "error accessing top frame from frame B"); + assert_true(window.testSameOrigin, "top frame testSameOrigin is mutated"); + +}, "Regression test for crbug.com/583445"); + diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/custom-element.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/custom-element.window.js new file mode 100644 index 0000000000..1ad06b3d37 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/custom-element.window.js @@ -0,0 +1,39 @@ +// The document open steps have: +// +// 2. If document's throw-on-dynamic-markup-insertion counter is greater than +// 0, then throw an "InvalidStateError" DOMException. +// +// The throw-on-dynamic-markup-insertion counter is only incremented when the +// parser creates a custom element, not when createElement is called. Test for +// this. +// +// See: https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps + +const noError = Symbol("no error"); +let err = noError; + +class CustomElement extends HTMLElement { + constructor() { + super(); + try { + assert_equals(document.open(), document); + } catch (e) { + err = e; + } + } +} +customElements.define("custom-element", CustomElement); + +test(t => { + err = noError; + document.createElement("custom-element"); + assert_equals(err, noError); +}, "document.open() works in custom element constructor for createElement()"); + +test(t => { + err = noError; + document.write("<custom-element></custom-element>"); + assert_throws_dom("InvalidStateError", () => { + throw err; + }); +}, "document.open() is forbidden in custom element constructor when creating element from parser"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document-open-cancels-javascript-url-navigation.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document-open-cancels-javascript-url-navigation.html new file mode 100644 index 0000000000..5596382f22 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document-open-cancels-javascript-url-navigation.html @@ -0,0 +1,17 @@ +<body> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +async_test(t => { + window.onload = t.step_func_done(() => assert_equals(i.contentDocument.body.innerText, "PASS")); + + var i = document.createElement('iframe'); + i.id ='i'; + i.src = "javascript:'FAIL'"; + document.body.appendChild(i); + i.contentDocument.open(); + i.contentDocument.write("PASS") + i.contentDocument.close(); +}); +</script> +</body> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-01.xhtml b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-01.xhtml new file mode 100644 index 0000000000..c02b3e4db5 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-01.xhtml @@ -0,0 +1,19 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<title>document.open in XHTML</title> +<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"/> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#opening-the-input-stream"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> +<div id="log"></div> +<script> +test(function() { + assert_throws_dom("INVALID_STATE_ERR", function() { + document.open(); + }, "document.open in XHTML should throw an INVALID_STATE_ERR "); +}, "document.open in XHTML"); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-02.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-02.html new file mode 100644 index 0000000000..c7e67a0cf7 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-02.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>document.open with three arguments</title> +<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-document-open"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +function open() { + assert_unreached("The call should be redirected to the real window.open") +} +test(function(t) { + var w; + t.add_cleanup(function() {try {w.close()} catch(e) {}}); + w = document.open("/resources/testharness.js", "", ""); + assert_true(w instanceof w.Window, "Expected a window"); +}, "document.open should redirect to window.open when called with three arguments"); + +test(function() { + var parser = new DOMParser(); + var doc = parser.parseFromString("", "text/html"); + assert_equals(doc.defaultView, null); + assert_throws_dom("INVALID_ACCESS_ERR", function() { + doc.open("/resources/testharness.js", "", ""); + }); +}, "document.open should throw when it has no window and is called with three arguments"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-03-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-03-frame.html new file mode 100644 index 0000000000..a4b370cea4 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-03-frame.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<script> +onload = function() { + document.open(); + document.close(); + parent.report(window.setTimeout === setTimeout, true, "setTimeout"); + parent.report(window === this, true, "this"); + parent.done(); +} +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-03.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-03.html new file mode 100644 index 0000000000..e446d70219 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/document.open-03.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<title>document.open and no singleton replacement</title> +<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-document-open"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test(); +function report(actual, expected, message) { + t.step(function() { + assert_equals(actual, expected, message); + }); +} +function done() { + t.done(); +} +</script> +<iframe src=document.open-03-frame.html></iframe> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/encoding.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/encoding.window.js new file mode 100644 index 0000000000..f0d133a532 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/encoding.window.js @@ -0,0 +1,12 @@ +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + frame.src = "resources/encoding-frame.html"; + frame.onload = t.step_func_done(t => { + // Using toLowerCase() to avoid an Edge bug + assert_equals(frame.contentDocument.characterSet.toLowerCase(), "shift_jis", "precondition"); + assert_equals(frame.contentDocument.open(), frame.contentDocument); + assert_equals(frame.contentDocument.characterSet.toLowerCase(), "shift_jis", "actual test"); + frame.contentDocument.close(); + assert_equals(frame.contentDocument.characterSet.toLowerCase(), "shift_jis", "might as well"); + }); +}, "doucment.open() and the document's encoding"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/event-listeners.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/event-listeners.window.js new file mode 100644 index 0000000000..df07124d81 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/event-listeners.window.js @@ -0,0 +1,308 @@ +// Many of the active-related test cases in this file came from +// active.window.js. However, we cannot test the "navigated away" non-active +// case right now due to https://github.com/whatwg/html/issues/3997. + +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")), + body = frame.contentDocument.body; + t.add_cleanup(() => frame.remove()); + const div = body.appendChild(frame.contentDocument.createElement("div")); + div.addEventListener("click", t.unreached_func("element event listener not removed")); + frame.contentDocument.open(); + div.click(); + frame.contentDocument.close(); +}, "Standard event listeners are to be removed"); + +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")), + body = frame.contentDocument.body; + t.add_cleanup(() => frame.remove()); + frame.contentDocument.addEventListener("x", t.unreached_func("document event listener not removed")); + body.addEventListener("x", t.unreached_func("body event listener not removed")); + frame.contentDocument.open(); + frame.contentDocument.dispatchEvent(new Event("x")); + body.dispatchEvent(new Event("x")); + frame.contentDocument.close(); +}, "Custom event listeners are to be removed"); + +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")), + body = frame.contentDocument.body; + t.add_cleanup(() => frame.remove()); + // Focus on the current window so that the frame's window is blurred. + window.focus(); + assert_false(frame.contentDocument.hasFocus()); + frame.contentWindow.addEventListener("focus", t.unreached_func("window event listener not removed")); + body.onfocus = t.unreached_func("body event listener not removed"); + frame.contentDocument.open(); + assert_equals(body.onfocus, null); + frame.contentWindow.focus(); + frame.contentDocument.close(); +}, "Standard event listeners are to be removed from Window"); + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + const childFrame = frame.contentDocument.querySelector("iframe"); + const childWin = childFrame.contentWindow; + const childDoc = childFrame.contentDocument; + const childBody = childDoc.body; + + // Right now childDoc is still fully active. + + frame.onload = t.step_func_done(() => { + // Focus on the current window so that the frame's window is blurred. + window.focus(); + // Now childDoc is still active but no longer fully active. + childWin.addEventListener("focus", t.unreached_func("window event listener not removed")); + childBody.onfocus = t.unreached_func("body event listener not removed"); + + childDoc.open(); + assert_equals(childBody.onfocus, null); + + // Now try to fire the focus event two different ways. + childWin.focus(); + const focusEvent = new FocusEvent("focus"); + childWin.dispatchEvent(focusEvent); + childDoc.close(); + }); + frame.src = "/common/blank.html"; + }); + frame.src = "resources/page-with-frame.html"; +}, "Standard event listeners are to be removed from Window for an active but not fully active document"); + +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + const win = frame.contentWindow; + const doc = frame.contentDocument; + const body = doc.body; + + // Right now the frame is connected and it has an active document. + frame.remove(); + + win.addEventListener("focus", t.unreached_func("window event listener not removed")); + body.onfocus = t.unreached_func("body event listener not removed"); + doc.open(); + assert_equals(body.onfocus, null); + + // Now try to fire the focus event two different ways. + win.focus(); + const focusEvent = new FocusEvent("focus"); + win.dispatchEvent(focusEvent); + doc.close(); +}, "Standard event listeners are to be removed from Window for a non-active document that is the associated Document of a Window (frame is removed)"); + +test(t => { + let winHappened = 0; + const winListener = t.step_func(() => { winHappened++; }); + window.addEventListener("focus", winListener); + t.add_cleanup(() => { window.removeEventListener("focus", winListener); }); + + let bodyHappened = 0; + const bodyListener = t.step_func(() => { bodyHappened++; }); + document.body.onfocus = bodyListener; + t.add_cleanup(() => { document.body.onfocus = null; }); + + const doc = document.implementation.createHTMLDocument(); + doc.open(); + + const focusEvent = new FocusEvent("focus"); + window.dispatchEvent(focusEvent); + + assert_equals(winHappened, 1); + assert_equals(bodyHappened, 1); +}, "Standard event listeners are NOT to be removed from Window for a Window-less document (createHTMLDocument)"); + +test(t => { + let winHappened = 0; + const winListener = t.step_func(() => { winHappened++; }); + window.addEventListener("focus", winListener); + t.add_cleanup(() => { window.removeEventListener("focus", winListener); }); + + let bodyHappened = 0; + const bodyListener = t.step_func(() => { bodyHappened++; }); + document.body.onfocus = bodyListener; + t.add_cleanup(() => { document.body.onfocus = null; }); + + const doc = new DOMParser().parseFromString("", "text/html"); + doc.open(); + + const focusEvent = new FocusEvent("focus"); + window.dispatchEvent(focusEvent); + + assert_equals(winHappened, 1); + assert_equals(bodyHappened, 1); +}, "Standard event listeners are NOT to be removed from Window for a Window-less document (DOMParser)"); + +async_test(t => { + const xhr = new XMLHttpRequest(); + xhr.onload = t.step_func_done(() => { + assert_equals(xhr.status, 200); + const doc = xhr.responseXML; + + let winHappened = 0; + const winListener = t.step_func(() => { winHappened++; }); + window.addEventListener("focus", winListener); + t.add_cleanup(() => { window.removeEventListener("focus", winListener); }); + + let bodyHappened = 0; + const bodyListener = t.step_func(() => { bodyHappened++; }); + document.body.onfocus = bodyListener; + t.add_cleanup(() => { document.body.onfocus = null; }); + + doc.open(); + + const focusEvent = new FocusEvent("focus"); + window.dispatchEvent(focusEvent); + + assert_equals(winHappened, 1); + assert_equals(bodyHappened, 1); + }); + xhr.responseType = "document"; + xhr.open("GET", "resources/dummy.html"); + xhr.send(); +}, "Standard event listeners are NOT to be removed from Window for a Window-less document (XMLHttpRequest)"); + +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.contentWindow.addEventListener("x", t.unreached_func("window event listener not removed")); + frame.contentDocument.open(); + frame.contentWindow.dispatchEvent(new Event("x")); + frame.contentDocument.close(); +}, "Custom event listeners are to be removed from Window"); + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + const childFrame = frame.contentDocument.querySelector("iframe"); + const childDoc = childFrame.contentDocument; + const childWin = childFrame.contentWindow; + + // Right now childDoc is still fully active. + + frame.onload = t.step_func_done(() => { + // Now childDoc is still active but no longer fully active. + childWin.addEventListener("x", t.unreached_func("window event listener not removed")); + childDoc.open(); + childWin.dispatchEvent(new Event("x")); + childDoc.close(); + }); + frame.src = "/common/blank.html"; + }); + frame.src = "resources/page-with-frame.html"; +}, "Custom event listeners are to be removed from Window for an active but not fully active document"); + +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + const win = frame.contentWindow; + const doc = frame.contentDocument; + + // Right now the frame is connected and it has an active document. + frame.remove(); + + win.addEventListener("x", t.unreached_func("window event listener not removed")); + doc.open(); + win.dispatchEvent(new Event("x")); + doc.close(); +}, "Custom event listeners are to be removed from Window for a non-active document that is the associated Document of a Window (frame is removed)"); + +test(t => { + const doc = document.implementation.createHTMLDocument(); + let happened = false; + window.addEventListener("createHTMLDocumentTest", t.step_func(() => { happened = true; })); + doc.open(); + window.dispatchEvent(new Event("createHTMLDocumentTest")); + assert_true(happened); +}, "Custom event listeners are NOT to be removed from Window for a Window-less document (createHTMLDocument)"); + +test(t => { + const doc = new DOMParser().parseFromString("", "text/html"); + let happened = false; + window.addEventListener("DOMParserTest", t.step_func(() => { happened = true; })); + doc.open(); + window.dispatchEvent(new Event("DOMParserTest")); + assert_true(happened); +}, "Custom event listeners are NOT to be removed from Window for a Window-less document (DOMParser)"); + +async_test(t => { + const xhr = new XMLHttpRequest(); + xhr.onload = t.step_func_done(() => { + assert_equals(xhr.status, 200); + const doc = xhr.responseXML; + let happened = false; + window.addEventListener("XHRTest", t.step_func(() => { happened = true; })); + doc.open(); + window.dispatchEvent(new Event("XHRTest")); + assert_true(happened); + }); + xhr.responseType = "document"; + xhr.open("GET", "resources/dummy.html"); + xhr.send(); +}, "Custom event listeners are NOT to be removed from Window for a Window-less document (XMLHttpRequest)"); + +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")), + body = frame.contentDocument.body; + t.add_cleanup(() => frame.remove()); + const div = body.appendChild(frame.contentDocument.createElement("div")); + div.onclick = t.unreached_func("element event listener not removed"); + frame.contentDocument.open(); + assert_equals(div.onclick, null); + const e = frame.contentDocument.createEvent("mouseevents") + e.initEvent("click", false, false); + div.dispatchEvent(e); + frame.contentDocument.close(); +}, "IDL attribute event handlers are to be deactivated"); + +var thrower; + +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")), + body = frame.contentDocument.body; + t.add_cleanup(() => frame.remove()); + const div = body.appendChild(frame.contentDocument.createElement("div")); + thrower = t.step_func(() => { throw new Error('element event listener not removed'); }); + div.setAttribute("onclick", "parent.thrower()"); + assert_not_equals(div.onclick, null); + frame.contentDocument.open(); + assert_equals(div.getAttribute("onclick"), "parent.thrower()"); + assert_equals(div.onclick, null); + const e = frame.contentDocument.createEvent("mouseevents") + e.initEvent("click", false, false); + div.dispatchEvent(e); + frame.contentDocument.close(); +}, "Content attribute event handlers are to be deactivated"); + +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + let once = false; + frame.contentDocument.addEventListener("x", () => { + frame.contentDocument.open(); + once = true; + }); + frame.contentDocument.addEventListener("x", t.unreached_func("second event listener not removed")); + frame.contentDocument.dispatchEvent(new Event("x")); + assert_true(once); + frame.contentDocument.close(); +}, "Event listeners are to be removed with immediate effect"); + +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")), + shadow = frame.contentDocument.body.attachShadow({ mode: "closed" }), + shadowChild = shadow.appendChild(document.createElement("div")), + shadowShadow = shadowChild.attachShadow({ mode: "open" }), + nodes = [shadow, shadowChild, shadowShadow]; + t.add_cleanup(() => frame.remove()); + nodes.forEach(node => { + node.addEventListener("x", t.unreached_func(node + "'s event listener not removed")); + }); + frame.contentDocument.open(); + nodes.forEach(node => { + node.dispatchEvent(new Event("x")); + }); + frame.contentDocument.close(); +}, "Event listeners are to be removed from shadow trees as well"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/form-control-state.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/form-control-state.html new file mode 100644 index 0000000000..7d03a885f0 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/form-control-state.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<html> +<head> +<title>Writing out a document with form controls with values</title> +<link rel="author" href="mailto:bzbarsky@mit.edu"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +</head> +<body> +<div id="log"></div> +<script> +function asyncHop(t, arg) { + return new Promise(res => t.step_timeout(res.bind(null, arg), 0)); +} + +function loadPromise(t, iframe) { + var p = new Promise(res => + iframe.addEventListener("load", res.bind(null, iframe), { once: true })); + // We need to do one trip through the event loop to make sure we're + // not still under the load event firing when we start doing our + // document.open bits. + return p.then(asyncHop.bind(null, t)); +} + +async function createIframe(t) { + var i = document.createElement("iframe"); + t.add_cleanup(() => i.remove()); + var p = loadPromise(t, i); + document.body.appendChild(i); + return p; +} + +async function replaceIframe(t, i, text) { + var p = loadPromise(t, i); + var doc = i.contentDocument; + doc.open(); + doc.write(text); + doc.close(); + return p; +} + +promise_test(async function(t) { + var i = await createIframe(t); + var str = "<textarea>123</textarea>"; + await replaceIframe(t, i, str); + i.contentDocument.querySelector("textarea").value = "abc"; + await replaceIframe(t, i, str); + assert_equals(i.contentDocument.querySelector("textarea").value, "123"); +}, "textarea state"); + +promise_test(async function(t) { + var i = await createIframe(t); + var str = "<input value='123'>"; + await replaceIframe(t, i, str); + i.contentDocument.querySelector("input").value = "abc"; + await replaceIframe(t, i, str); + assert_equals(i.contentDocument.querySelector("input").value, "123"); +}, "input state"); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/history-state.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/history-state.window.js new file mode 100644 index 0000000000..7fb172a141 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/history-state.window.js @@ -0,0 +1,29 @@ +async_test(t => { + const iframe = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => iframe.remove()); + iframe.src = "/common/blank.html"; + iframe.onload = t.step_func_done(() => { + const win = iframe.contentWindow; + const doc = iframe.contentDocument; + assert_equals(win.history.state, null); + win.history.replaceState("state", ""); + assert_equals(win.history.state, "state"); + assert_equals(doc.open(), doc); + assert_equals(win.history.state, "state"); + }); +}, "history.state is kept by document.open()"); + +async_test(t => { + const iframe = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => iframe.remove()); + iframe.src = "/common/blank.html"; + iframe.onload = t.step_func_done(() => { + const win = iframe.contentWindow; + const doc = iframe.contentDocument; + assert_equals(win.history.state, null); + win.history.replaceState("state", ""); + assert_equals(win.history.state, "state"); + assert_equals(doc.open("", "replace"), doc); + assert_equals(win.history.state, "state"); + }); +}, "history.state is kept by document.open() (with historical replace parameter set)"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/history.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/history.window.js new file mode 100644 index 0000000000..0134da24f0 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/history.window.js @@ -0,0 +1,29 @@ +// Historically, document.open() created an entry in the session history so +// that the original page could be seen by going back. Test that this behavior +// no longer occurs. +// +// This test uses window.open() for variety, as most other tests in this +// directory use document.open(). An <iframe> would probably work also. We can +// always add an <iframe>-based test later if it is deemed necessary. + +const t = async_test("document.open should not add an entry to the session history"); + +const frameURL = new URL("resources/history-frame.html", document.URL).href; + +let origLength; +window.onFrameLoaded = t.step_func(() => { + window.onFrameLoaded = t.unreached_func("onFrameLoaded should only be called once"); + assert_equals(win.document.URL, frameURL); + assert_true(win.document.body.textContent.includes("Old")); + origLength = win.history.length; +}); +window.onDocumentOpen = t.step_func_done(() => { + window.onDocumentOpen = t.unreached_func("onDocumentOpen should only be called once"); + assert_equals(win.document.URL, frameURL); + assert_true(win.document.body.textContent.includes("New")); + assert_not_equals(origLength, undefined); + assert_equals(win.history.length, origLength); +}); + +const win = window.open(frameURL); +t.add_cleanup(() => win.close()); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/ignore-opens-during-unload.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/ignore-opens-during-unload.window.js new file mode 100644 index 0000000000..43506a22a4 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/ignore-opens-during-unload.window.js @@ -0,0 +1,60 @@ +for (const [ev, target] of [ + ["beforeunload", iframe => iframe.contentWindow], + ["pagehide", iframe => iframe.contentWindow], + ["unload", iframe => iframe.contentWindow], + ["visibilitychange", iframe => iframe.contentDocument], +]) { + async_test(t => { + const iframe = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => iframe.remove()); + iframe.src = "/common/blank.html"; + iframe.onload = t.step_func(() => { + target(iframe).addEventListener(ev, t.step_func_done(() => { + assert_not_equals(iframe.contentDocument.childNodes.length, 0); + assert_equals(iframe.contentDocument.open(), iframe.contentDocument); + assert_not_equals(iframe.contentDocument.childNodes.length, 0); + })); + iframe.src = "about:blank"; + }); + }, `document.open should bail out when ignore-opens-during-unload is greater than 0 during ${ev} event (in top-level browsing context)`); + + async_test(t => { + const iframe = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => iframe.remove()); + iframe.src = "/common/blank.html?1"; + iframe.onload = t.step_func(() => { + const doc = iframe.contentDocument; + const innerIframe = doc.body.appendChild(doc.createElement("iframe")); + innerIframe.src = "/common/blank.html?2"; + innerIframe.onload = t.step_func(() => { + // Navigate the parent, listen on the child, and open() the parent. + target(innerIframe).addEventListener(ev, t.step_func_done(() => { + assert_not_equals(iframe.contentDocument.childNodes.length, 0); + iframe.contentDocument.open(); + assert_not_equals(iframe.contentDocument.childNodes.length, 0); + })); + iframe.src = "about:blank"; + }); + }); + }, `document.open should bail out when ignore-opens-during-unload is greater than 0 during ${ev} event (open(parent) while unloading parent and child)`); + + async_test(t => { + const iframe = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => iframe.remove()); + iframe.src = "/common/blank.html?1"; + iframe.onload = t.step_func(() => { + const doc = iframe.contentDocument; + const innerIframe = doc.body.appendChild(doc.createElement("iframe")); + innerIframe.src = "/common/blank.html?2"; + innerIframe.onload = t.step_func(() => { + // Navigate the child, listen on the child, and open() the parent. + target(innerIframe).addEventListener(ev, t.step_func_done(() => { + assert_not_equals(iframe.contentDocument.childNodes.length, 0); + iframe.contentDocument.open(); + assert_equals(iframe.contentDocument.childNodes.length, 0); + })); + innerIframe.src = "about:blank"; + }); + }); + }, `document.open should bail out when ignore-opens-during-unload is greater than 0 during ${ev} event (open(parent) while unloading child only)`); +} diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/location-set-and-document-open.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/location-set-and-document-open.html new file mode 100644 index 0000000000..a3bdd86ee6 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/location-set-and-document-open.html @@ -0,0 +1,31 @@ +<!doctype html> +<meta charset=utf-8> +<title></title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<body> + <script> + var t = async_test("Location sets should cancel current navigation and prevent later document.open() from doing anything"); + + var finishTest = t.step_func_done(function() { + assert_equals(frames[0].document.body.textContent, "PASS", + "Should not have FAIL in our textContent"); + }); + + t.step(function() { + var i = document.createElement("iframe"); + i.srcdoc = ` + <script> + var blob = new Blob(["PASS"], { type: "text/html" }); + var url = URL.createObjectURL(blob); + location.href = url; + frameElement.onload = parent.finishTest; + document.open(); + document.write("FAIL"); + document.close(); + <\/script>`; + document.body.appendChild(i); + }); + + </script> +</body> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/mutation-events.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/mutation-events.window.js new file mode 100644 index 0000000000..4efbb863c6 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/mutation-events.window.js @@ -0,0 +1,22 @@ +// In an ideal world this test would eventually be obsolete due to mutation events disappearing. Or +// would have to change to account for mutation events not firing synchronously. Neither seems +// realistic to the author though. + +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + frame.contentWindow.addEventListener("DOMNodeInserted", t.unreached_func()); + frame.contentWindow.addEventListener("DOMNodeInserted", t.unreached_func(), true); + frame.contentWindow.addEventListener("DOMNodeInsertedIntoDocument", t.unreached_func(), true); + frame.contentWindow.addEventListener("DOMNodeRemoved", t.unreached_func()); + frame.contentWindow.addEventListener("DOMNodeRemoved", t.unreached_func(), true); + frame.contentWindow.addEventListener("DOMNodeRemovedFromDocument", t.unreached_func(), true); + frame.contentWindow.addEventListener("DOMSubtreeModified", t.unreached_func()); + frame.contentWindow.addEventListener("DOMSubtreeModified", t.unreached_func(), true); + assert_equals(frame.contentDocument.documentElement.localName, "html"); + assert_equals(frame.contentDocument.open(), frame.contentDocument); + assert_equals(frame.contentDocument.documentElement, null); + frame.contentDocument.write("<div>heya</div>"); + frame.contentDocument.close(); + assert_equals(frame.contentDocument.documentElement.localName, "html"); + frame.remove(); +}, "document.open(), the HTML parser, and mutation events"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/mutation-observer.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/mutation-observer.window.js new file mode 100644 index 0000000000..34e73146a9 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/mutation-observer.window.js @@ -0,0 +1,19 @@ +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => { frame.remove(); }); + const originalHTMLElement = frame.contentDocument.documentElement; + assert_equals(originalHTMLElement.localName, "html"); + const observer = new frame.contentWindow.MutationObserver(t.step_func_done(records => { + // Even though we passed `subtree: true` to observer.observe, due to the + // fact that "replace all" algorithm removes children with the "suppress + // observers flag" set, we still only get the html element as the sole + // removed node. + assert_equals(records.length, 1); + assert_equals(records[0].type, "childList"); + assert_equals(records[0].target, frame.contentDocument); + assert_array_equals(records[0].addedNodes, []); + assert_array_equals(records[0].removedNodes, [originalHTMLElement]); + })); + observer.observe(frame.contentDocument, { childList: true, subtree: true }); + assert_equals(frame.contentDocument.open(), frame.contentDocument); +}, "document.open() should inform mutation observer of node removal"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/no-new-global.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/no-new-global.window.js new file mode 100644 index 0000000000..d4a9296fca --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/no-new-global.window.js @@ -0,0 +1,57 @@ +// In an earlier version of the HTML Standard, document open steps created a +// new JavaScript realm and migrated the existing objects to use the new realm. +// Test that this no longer happens. + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + // Ensure a load event gets dispatched to unblock testharness + t.add_cleanup(() => frame.remove()); + frame.src = "resources/global-variables-frame.html"; + frame.onload = t.step_func_done(() => { + assert_equals(frame.contentWindow.hey, "You", "precondition"); + frame.contentDocument.open(); + assert_equals(frame.contentWindow.hey, "You", "actual check"); + }); +}, "Obtaining a variable from a global whose document had open() invoked"); + +function testIdentity(desc, frameToObject, frameToConstructor) { + async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + // Ensure a load event gets dispatched to unblock testharness + t.add_cleanup(() => frame.remove()); + frame.src = "/common/blank.html"; + frame.onload = t.step_func_done(() => { + const obj = frameToObject(frame); + frame.contentDocument.open(); + assert_equals(frameToObject(frame), obj); + }); + }, `${desc} maintains object identity through open()`); + + async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + // Ensure a load event gets dispatched to unblock testharness + t.add_cleanup(() => frame.remove()); + frame.src = "/common/blank.html"; + frame.onload = t.step_func_done(() => { + const obj = frameToObject(frame); + const origProto = Object.getPrototypeOf(obj); + const origCtor = frameToConstructor(frame); + const sym = Symbol(); + obj[sym] = "foo"; + frame.contentDocument.open(); + assert_equals(frameToObject(frame)[sym], "foo"); + assert_true(frameToObject(frame) instanceof origCtor); + assert_equals(Object.getPrototypeOf(frameToObject(frame)), origProto); + assert_equals(frameToConstructor(frame), origCtor); + }); + }, `${desc} maintains its prototype and properties through open()`); +} + +testIdentity("Document", frame => frame.contentDocument, frame => frame.contentWindow.Document); +testIdentity("WindowProxy", frame => frame.contentWindow, frame => frame.contentWindow.Window); +testIdentity("BarProp", frame => frame.contentWindow.locationbar, frame => frame.contentWindow.BarProp); +testIdentity("History", frame => frame.contentWindow.history, frame => frame.contentWindow.History); +testIdentity("localStorage", frame => frame.contentWindow.localStorage, frame => frame.contentWindow.Storage); +testIdentity("Location", frame => frame.contentWindow.location, frame => frame.contentWindow.Location); +testIdentity("sessionStorage", frame => frame.contentWindow.sessionStorage, frame => frame.contentWindow.Storage); +testIdentity("Navigator", frame => frame.contentWindow.navigator, frame => frame.contentWindow.Navigator); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/origin-check-in-document-open-basic.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/origin-check-in-document-open-basic.html new file mode 100644 index 0000000000..118be71af1 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/origin-check-in-document-open-basic.html @@ -0,0 +1,26 @@ +<!doctype html> +<title>Origin check in document.open() - Basic usage</title> +<link rel="author" title="Jochen Eisinger" href="mailto:jochen@chromium.org"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#opening-the-input-stream"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/html/resources/common.js"></script> +<body> +<script> +testInIFrame(undefined, (ctx) => { + try { + ctx.iframes[0].contentDocument.open(); + } catch (e) { + assert_unreached("Opening a same origin document throws"); + } +}, "It should be possible to open same origin documents."); + +testInIFrame(undefined, (ctx) => { + try { + ctx.iframes[0].contentDocument.write(""); + } catch (e) { + assert_unreached("Implicitly opening a same origin document throws"); + } +}, "It should be possible to implicitly open same origin documents."); +</script> +</body> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/origin-check-in-document-open-same-origin-domain.sub.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/origin-check-in-document-open-same-origin-domain.sub.html new file mode 100644 index 0000000000..ba4ef3bae8 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/origin-check-in-document-open-same-origin-domain.sub.html @@ -0,0 +1,24 @@ +<!doctype html> +<title>Origin check in document.open() - same origin-domain (but not same origin) documents</title> +<link rel="author" title="Jochen Eisinger" href="mailto:jochen@chromium.org"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#opening-the-input-stream"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/html/resources/common.js"></script> +<body> +<script> +testInIFrame("http://{{host}}:{{ports[http][1]}}/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/set-document-domain.html", (ctx) => { + document.domain = document.domain; + let doc = ctx.iframes[0].contentDocument; + let constructor = ctx.iframes[0].contentWindow.DOMException; + assert_throws_dom("SecurityError", constructor, doc.open.bind(doc), "Opening a same origin-domain (but not same origin) document doesn't throw."); +}, "It should not be possible to open same origin-domain (but not same origin) documents."); + +testInIFrame("http://{{host}}:{{ports[http][1]}}/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/set-document-domain.html", (ctx) => { + document.domain = document.domain; + let doc = ctx.iframes[0].contentDocument; + let constructor = ctx.iframes[0].contentWindow.DOMException; + assert_throws_dom("SecurityError", constructor, doc.write.bind(doc, ""), "Implicitly opening a same origin-domain (but not same origin) document doesn't throw."); +}, "It should not be possible to implicitly open same origin-domain (but not same origin) documents."); +</script> +</body> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/quirks.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/quirks.window.js new file mode 100644 index 0000000000..0ff0bb9944 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/quirks.window.js @@ -0,0 +1,74 @@ +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.contentDocument.close()); + assert_equals(frame.contentDocument.compatMode, "BackCompat"); + frame.contentDocument.open(); + assert_equals(frame.contentDocument.compatMode, "CSS1Compat"); + frame.contentDocument.close(); + assert_equals(frame.contentDocument.compatMode, "BackCompat"); +}, "document.open() sets document to no-quirks mode (write no doctype)"); + +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.contentDocument.close()); + assert_equals(frame.contentDocument.compatMode, "BackCompat"); + frame.contentDocument.open(); + assert_equals(frame.contentDocument.compatMode, "CSS1Compat"); + frame.contentDocument.write("<!doctype html public"); + assert_equals(frame.contentDocument.compatMode, "CSS1Compat"); + frame.contentDocument.write(" \"-//IETF//DTD HTML 3//\""); + assert_equals(frame.contentDocument.compatMode, "CSS1Compat"); + frame.contentDocument.write(">"); + assert_equals(frame.contentDocument.compatMode, "BackCompat"); + frame.contentDocument.close(); + assert_equals(frame.contentDocument.compatMode, "BackCompat"); +}, "document.open() sets document to no-quirks mode (write old doctype)"); + +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.contentDocument.close()); + assert_equals(frame.contentDocument.compatMode, "BackCompat"); + frame.contentDocument.open(); + assert_equals(frame.contentDocument.compatMode, "CSS1Compat"); + frame.contentDocument.write("<!doctype html"); + assert_equals(frame.contentDocument.compatMode, "CSS1Compat"); + frame.contentDocument.write(">"); + assert_equals(frame.contentDocument.compatMode, "CSS1Compat"); + frame.contentDocument.close(); + assert_equals(frame.contentDocument.compatMode, "CSS1Compat"); +}, "document.open() sets document to no-quirks mode (write new doctype)"); + +// This tests the document.open() call in fact sets the document to no-quirks +// mode, not limited-quirks mode. It is derived from +// quirks/blocks-ignore-line-height.html in WPT, as there is no direct way to +// distinguish between a no-quirks document and a limited-quirks document. It +// assumes that the user agent passes the linked test, which at the time of +// writing is all major web browsers. +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.contentDocument.close()); + assert_equals(frame.contentDocument.compatMode, "BackCompat"); + frame.contentDocument.open(); + assert_equals(frame.contentDocument.compatMode, "CSS1Compat"); + + // Create the DOM tree manually rather than going through document.write() to + // bypass the parser, which resets the document mode. + const html = frame.contentDocument.appendChild(frame.contentDocument.createElement("html")); + const body = html.appendChild(frame.contentDocument.createElement("body")); + assert_equals(frame.contentDocument.body, body); + body.innerHTML = ` + <style>#ref { display:block }</style> + <div id=test><font size=1>x</font></div> + <font id=ref size=1>x</font> + <div id=s_ref>x</div> + `; + assert_equals(frame.contentDocument.compatMode, "CSS1Compat"); + + const idTest = frame.contentDocument.getElementById("test"); + const idRef = frame.contentDocument.getElementById("ref"); + const idSRef = frame.contentDocument.getElementById("s_ref"); + assert_equals(frame.contentWindow.getComputedStyle(idTest).height, + frame.contentWindow.getComputedStyle(idSRef).height); + assert_not_equals(frame.contentWindow.getComputedStyle(idTest).height, + frame.contentWindow.getComputedStyle(idRef).height); +}, "document.open() sets document to no-quirks mode, not limited-quirks mode"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/readiness.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/readiness.window.js new file mode 100644 index 0000000000..729a958700 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/readiness.window.js @@ -0,0 +1,25 @@ +// This tests the behavior of dynamic markup insertion APIs with a document's +// readiness. + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => { frame.remove(); }); + frame.src = "/common/blank.html"; + frame.onload = t.step_func_done(() => { + const states = []; + frame.contentDocument.onreadystatechange = t.step_func(() => { + states.push(frame.contentDocument.readyState); + }); + assert_equals(frame.contentDocument.readyState, "complete"); + assert_array_equals(states, []); + + // When open() is called, it first removes the event listeners and handlers + // from all nodes in the DOM tree. Then, after a new parser is created and + // initialized, it changes the current document readiness to "loading". + // However, because all event listeners are removed, we cannot observe the + // readystatechange event fired for "loading" inside open(). + frame.contentDocument.open(); + assert_equals(frame.contentDocument.readyState, "loading"); + assert_array_equals(states, []); + }); +}, "document.open() and readiness"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/reload.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/reload.window.js new file mode 100644 index 0000000000..279020f64d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/reload.window.js @@ -0,0 +1,71 @@ +// This test tests for the nonexistence of a reload override buffer, which is +// used in a previous version of the HTML Standard to make reloads of a +// document.open()'d document load the written-to document rather than doing an +// actual reload of the document's URL. +// +// This test has a somewhat interesting structure compared to the other tests +// in this directory. It eschews the <iframe> structure used by other tests, +// since when the child frame is reloaded it would adopt the URL of the test +// page (the responsible document of the entry settings object), and the spec +// forbids navigation in nested browsing contexts to the same URL as their +// parent. To work around that, we use window.open() which does not suffer from +// that restriction. +// +// In any case, this test as the caller of `document.open()` would be used both +// as the test file and as part of the test file. The `if (window.name !== +// "opened-dummy-window")` condition controls what role this file plays. + +if (window.name !== "opened-dummy-window") { + async_test(t => { + const testURL = document.URL; + const dummyURL = new URL("resources/dummy.html", document.URL).href; + + // 1. Open an auxiliary window. + const win = window.open("resources/dummy.html", "opened-dummy-window"); + t.add_cleanup(() => { win.close(); }); + + win.addEventListener("load", t.step_func(() => { + // The timeout seems to be necessary for Firefox, which when `load` is + // called may still have an active parser. + t.step_timeout(() => { + const doc = win.document; + assert_true(doc.body.textContent.includes("Dummy"), "precondition"); + assert_equals(doc.URL, dummyURL, "precondition"); + + window.onChildLoad = t.step_func(message => { + // 3. The dynamically overwritten content will trigger this function, + // which puts in place the actual test. + + assert_equals(message, "Written", "script on written page is executed"); + assert_true(win.document.body.textContent.includes("Content"), "page is written to"); + assert_equals(win.document.URL, testURL, "postcondition: after document.write()"); + assert_equals(win.document, doc, "document.open should not change the document object"); + window.onChildLoad = t.step_func_done(message => { + // 6. This function should be called from the if (opener) branch of + // this file. It would throw an assertion error if the overwritten + // content was executed instead. + assert_equals(message, "Done!", "actual test"); + assert_true(win.document.body.textContent.includes("Back to the test"), "test is reloaded"); + assert_equals(win.document.URL, testURL, "postcondition: after reload"); + assert_not_equals(win.document, doc, "reload should change the document object"); + }); + + // 4. Reload the pop-up window. Because of the doc.open() call, this + // pop-up window will reload to the same URL as this test itself. + win.location.reload(); + }); + + // 2. When it is loaded, dynamically overwrite its content. + assert_equals(doc.open(), doc); + assert_equals(doc.URL, testURL, "postcondition: after document.open()"); + doc.write("<p>Content</p><script>opener.onChildLoad('Written');</script>"); + doc.close(); + }, 100); + }), { once: true }); + }, "Reloading a document.open()'d page should reload the URL of the entry realm's responsible document"); +} else { + document.write("<p>Back to the test</p>"); + // 5. Since this window is window.open()'d, opener refers to the test window. + // Inform the opener that reload succeeded. + opener.onChildLoad("Done!"); +} diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/remove-initial-about-blankness.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/remove-initial-about-blankness.window.js new file mode 100644 index 0000000000..7442bc4925 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/remove-initial-about-blankness.window.js @@ -0,0 +1,65 @@ +// This tests the issues discussed in https://github.com/whatwg/html/issues/4299 +// and fixed in https://github.com/whatwg/html/pull/6567. + +// Note: because browsers do not interoperate on the spec's notion of window reuse (see e.g. https://crbug.com/778318) +// we pick a specific interoperable test case, which is "currently on initial about:blank, but loading something". + +async_test(t => { + const iframe = document.createElement("iframe"); + + // We can't just leave it at the actual initial about:blank because of the interop issues mentioned above. + // So put it in the "currently on initial about:blank, but loading something" state which interoperably does Window + // reuse. + iframe.src = "/common/blank.html"; + + // Create the Window object. It will be for the initial about:blank since the load of /common/blank.html hasn't + // completed. + document.body.append(iframe); + + // Store a string on that Window object so we can later test if it's reused. + iframe.contentWindow.persistedString = "Hello world!"; + + // This will reset the initial about:blank-ness. But, it will also cancel any ongoing loads. + iframe.contentDocument.open(); + + // So, re-start the load of /common/blank.html. + iframe.src = "/common/blank.html"; + + // When the load finally happens, will it reuse the Window object or not? + // Because document.open() resets the initial about:blank-ness, it will *not* reuse the Window object. + // The point of the test is to assert that. + iframe.addEventListener("load", t.step_func_done(() => { + assert_equals( + iframe.contentDocument.URL, + iframe.src, + "Prerequisite check: we are getting the right load event" + ); + + assert_equals(iframe.contentWindow.persistedString, undefined); + }), { once: true }); +}, "document.open() removes the initial about:blank-ness of the document"); + +// This test is redundant with others in WPT but it's intended to make it clear that document.open() is the +// distinguishing factor. It does the same exact thing but without document.open() and with the resulting final assert +// flipped. +async_test(t => { + const iframe = document.createElement("iframe"); + iframe.src = "/common/blank.html"; + document.body.append(iframe); + + iframe.contentWindow.persistedString = "Hello world!"; + + // NO document.open() call. + + iframe.src = "/common/blank.html"; + + iframe.addEventListener("load", t.step_func_done(() => { + assert_equals( + iframe.contentDocument.URL, + iframe.src, + "Prerequisite check: we are getting the right load event" + ); + + assert_equals(iframe.contentWindow.persistedString, "Hello world!"); + }), { once: true }); +}, "Double-check: without document.open(), Window reuse indeed happens"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-async-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-async-frame.html new file mode 100644 index 0000000000..d5535630be --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-async-frame.html @@ -0,0 +1,9 @@ +<!doctype html> +<p>Text</p> +<script> +window.stop(); +parent.step_timeout(() => { + document.open(); + parent.handlers.afterOpenAsync(); +}, 10); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-frame.html new file mode 100644 index 0000000000..d9ec23590b --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/aborted-parser-frame.html @@ -0,0 +1,7 @@ +<!doctype html> +<p>Text</p> +<script> +window.stop(); +document.open(); +parent.handlers.afterOpen(); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-custom-element-with-domain-frame.sub.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-custom-element-with-domain-frame.sub.html new file mode 100644 index 0000000000..4de97e8ed1 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-custom-element-with-domain-frame.sub.html @@ -0,0 +1,13 @@ +<p>Text</p> +<script> +document.domain = "{{host}}"; + +class CustomElement extends HTMLElement { + constructor() { + super(); + parent.onCustomElementReady(); + } +} +customElements.define("custom-element", CustomElement); +</script> +<custom-element></custom-element> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-synchronous-script-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-synchronous-script-frame.html new file mode 100644 index 0000000000..632b2934ac --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-synchronous-script-frame.html @@ -0,0 +1,4 @@ +<p>Text</p> +<script> +parent.testSynchronousScript(); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-synchronous-script-with-domain-frame.sub.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-synchronous-script-with-domain-frame.sub.html new file mode 100644 index 0000000000..7ca7b5f44c --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-synchronous-script-with-domain-frame.sub.html @@ -0,0 +1,5 @@ +<p>Text</p> +<script> +document.domain = "{{host}}"; +parent.testSynchronousScript(); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-xml-with-domain-frame.sub.xhtml b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-xml-with-domain-frame.sub.xhtml new file mode 100644 index 0000000000..b054c0fe3a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-xml-with-domain-frame.sub.xhtml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> + <head><title>XHTML document with domain set</title></head> + <body> + <p>Text</p> + <script> + document.domain = "{{host}}"; + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-xml-with-synchronous-script-frame.xhtml b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-xml-with-synchronous-script-frame.xhtml new file mode 100644 index 0000000000..00fc71eccf --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/bailout-order-xml-with-synchronous-script-frame.xhtml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> + <head><title>XHTML document with hook to run script from a script tag</title></head> + <body> + <p>Text</p> + <script> + parent.testSynchronousScript(); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/document-open-side-effects.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/document-open-side-effects.js new file mode 100644 index 0000000000..7cb86dcba0 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/document-open-side-effects.js @@ -0,0 +1,8 @@ +function assertDocumentIsReadyForSideEffectsTest(doc, description) { + assert_not_equals(doc.childNodes.length, 0, `document should not be empty before side effects test (${description})`); +} + +function assertOpenHasNoSideEffects(doc, originalURL, description) { + assert_not_equals(doc.childNodes.length, 0, `document nodes should not be cleared (${description})`); + assert_equals(doc.URL, originalURL, `The original URL should be kept (${description})`); +} diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/dummy.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/dummy.html new file mode 100644 index 0000000000..a092f4e2d7 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/dummy.html @@ -0,0 +1,2 @@ +<!-- Like /common/blank.html, but with some content in it. --> +<p>Dummy</p> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/encoding-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/encoding-frame.html new file mode 100644 index 0000000000..843c3a2c79 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/encoding-frame.html @@ -0,0 +1,3 @@ +<!doctype html> +<meta charset=ms932> +<p>Encoded in Shift_JIS.</p> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/global-variables-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/global-variables-frame.html new file mode 100644 index 0000000000..0fe189914c --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/global-variables-frame.html @@ -0,0 +1,4 @@ +<!doctype html> +<script> +hey = "You"; +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/history-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/history-frame.html new file mode 100644 index 0000000000..2404105b09 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/history-frame.html @@ -0,0 +1,20 @@ +<script> +function queueTest() { + // The timeout is necessary to avoid the parser still being active when + // `document.open()` is called and becoming a no-op. + // + // We also cannot use setTimeout(..., 0), as the parser is terminated in a + // task with DOM manipulation task source while the timeout is run in a task + // on the timer task source. The order is therefore not guaranteed. Let's + // play it safer and use some actual timeout. + setTimeout(() => { + document.open(); + document.write("<p>New content</p>"); + document.close(); + opener.onDocumentOpen(); + }, 200); +} +</script> +<body onload="opener.onFrameLoaded(); queueTest();"> +<p>Old content</p> +</body> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/http-refresh.py b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/http-refresh.py new file mode 100644 index 0000000000..161a34b6b5 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/http-refresh.py @@ -0,0 +1,5 @@ +from wptserve.utils import isomorphic_encode + +def main(request, response): + time = isomorphic_encode(request.url_parts.query) if request.url_parts.query else b'0' + return 200, [(b'Refresh', time), (b'Content-Type', b"text/html")], b'' diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/meta-refresh.py b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/meta-refresh.py new file mode 100644 index 0000000000..2dfbab6e76 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/meta-refresh.py @@ -0,0 +1,3 @@ +def main(request, response): + time = request.url_parts.query if request.url_parts.query else u'0' + return 200, [[b'Content-Type', b'text/html']], u'<meta http-equiv=refresh content=%s>' % time diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/page-with-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/page-with-frame.html new file mode 100644 index 0000000000..a1ab01e072 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/page-with-frame.html @@ -0,0 +1 @@ +<iframe src="/common/blank.html"></iframe> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/set-document-domain.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/set-document-domain.html new file mode 100644 index 0000000000..a92a7ae39f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/set-document-domain.html @@ -0,0 +1,4 @@ +<!doctype html> +<script> +document.domain = document.domain; +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/slow-png.py b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/slow-png.py new file mode 100644 index 0000000000..fced22aa26 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/slow-png.py @@ -0,0 +1,8 @@ +import time +from base64 import decodebytes + +png_response = decodebytes(b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg==') + +def main(request, response): + time.sleep(2) + return 200, [], png_response diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-entry-document-incumbent-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-entry-document-incumbent-frame.html new file mode 100644 index 0000000000..bd78d8ee52 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-entry-document-incumbent-frame.html @@ -0,0 +1,4 @@ +<!doctype html> +<script> +window.callDocumentMethod = methodName => document[methodName](); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-entry-document-timer-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-entry-document-timer-frame.html new file mode 100644 index 0000000000..b2c050768c --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-entry-document-timer-frame.html @@ -0,0 +1,3 @@ +<script> +setTimeout(parent.timerTest, 10); +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-frame.html b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-frame.html new file mode 100644 index 0000000000..be483ff0ae --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/resources/url-frame.html @@ -0,0 +1,9 @@ +<script> +onload = () => { + const beforeURL = document.URL; + document.open(); + const afterURL = document.URL; + document.close(); + parent.testDone(beforeURL, afterURL); +} +</script> diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/tasks.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/tasks.window.js new file mode 100644 index 0000000000..887adcb739 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/tasks.window.js @@ -0,0 +1,106 @@ +// An older version of the HTML Standard mandated that document.open() remove +// all tasks associated with the document on which open() is called. This step +// has been proposed to be removed. This series of tests ensures that this step +// is no longer executed. +// +// This file comprehensively (but not exhaustively) tests for many queued tasks +// that may be observable. Each taskTest() call in fact runs two tests: the +// first one "tasks without document.open()" does not actually run +// document.open(), just to test that the tested task works ordinarily; the +// second actually calls document.open() to test if the method call removes +// that specific task from the queue. + +// This is necessary to allow the promise rejection test below. +setup({ + allow_uncaught_exception: true +}); + +function taskTest(description, testBody) { + async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + // The empty HTML seems to be necessary to cajole Chrome and Safari into + // firing a load event asynchronously, which is necessary to make sure the + // frame's document doesn't have a parser associated with it. + // See: https://crbug.com/569511 + frame.src = "/common/blank.html"; + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + // Make sure there is no parser. Firefox seems to have an additional + // non-spec-compliant readiness state "uninitialized", so test for the + // two known valid readiness states instead. + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1191683 + assert_in_array(frame.contentDocument.readyState, ["interactive", "complete"]); + testBody(t, frame, doc => {}); + }); + }, `tasks without document.open() (${description})`); + + async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + // The empty HTML seems to be necessary to cajole Chrome into firing a load + // event, which is necessary to make sure the frame's document doesn't have + // a parser associated with it. + // See: https://crbug.com/569511 + frame.src = "/common/blank.html"; + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + // Make sure there is no parser. Firefox seems to have an additional + // non-spec-compliant readiness state "uninitialized", so test for the + // two known valid readiness states instead. + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1191683 + assert_in_array(frame.contentDocument.readyState, ["interactive", "complete"]); + testBody(t, frame, doc => doc.open()); + }); + }, `document.open() and tasks (${description})`); +} + +taskTest("timeout", (t, frame, open) => { + frame.contentWindow.setTimeout(t.step_func_done(), 100); + open(frame.contentDocument); +}); + +taskTest("window message", (t, frame, open) => { + let counter = 0; + frame.contentWindow.postMessage(undefined, "*"); + open(frame.contentDocument); + frame.contentWindow.postMessage(undefined, "*"); + frame.contentWindow.onmessage = t.step_func(e => { + assert_equals(e.data, undefined); + counter++; + assert_less_than_equal(counter, 2); + if (counter == 2) { + t.done(); + } + }); +}); + +taskTest("canvas.toBlob()", (t, frame, open) => { + const canvas = frame.contentDocument.body.appendChild(frame.contentDocument.createElement("canvas")); + canvas.toBlob(t.step_func_done()); + open(frame.contentDocument); +}); + +taskTest("MessagePort", (t, frame, open) => { + frame.contentWindow.eval(`({ port1, port2 } = new MessageChannel());`); + frame.contentWindow.port2.onmessage = t.step_func_done(ev => { + assert_equals(ev.data, "Hello world"); + }); + frame.contentWindow.port1.postMessage("Hello world"); + open(frame.contentDocument); +}); + +taskTest("Promise rejection", (t, frame, open) => { + // There is currently some ambiguity on which Window object the + // unhandledrejection event should be fired on. Here, let's account for that + // ambiguity and allow event fired on _any_ global to pass this test. + // See: + // - https://github.com/whatwg/html/issues/958, + // - https://bugs.webkit.org/show_bug.cgi?id=187822 + const promise = frame.contentWindow.eval("Promise.reject(42);"); + open(frame.contentDocument); + const listener = t.step_func_done(ev => { + assert_equals(ev.promise, promise); + assert_equals(ev.reason, 42); + }); + frame.contentWindow.onunhandledrejection = listener; + window.onunhandledrejection = listener; +}); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext-subframe.txt b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext-subframe.txt new file mode 100644 index 0000000000..3e715502b9 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext-subframe.txt @@ -0,0 +1 @@ +Some text. diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext.window.js new file mode 100644 index 0000000000..ab1d9706a4 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument-plaintext.window.js @@ -0,0 +1,23 @@ +["replace", + "NOBODY", + "@ FD ;", + "it does not matter, you see \f", + "text/plain", + "text/xml", + "application/octet-stream", + "\0"].forEach(type => { + async_test(t => { + const frame = document.createElement("iframe"); + frame.src = "type-argument-plaintext-subframe.txt"; + document.body.appendChild(frame); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func_done(() => { + assert_equals(frame.contentDocument.open(type), frame.contentDocument); + frame.contentDocument.write("<B>heya</b>"); + frame.contentDocument.close(); + assert_equals(frame.contentDocument.body.firstChild.localName, "b"); + assert_equals(frame.contentDocument.body.textContent, "heya"); + assert_equals(frame.contentDocument.contentType, "text/plain"); + }); + }, "document.open() on plaintext document with type set to: " + type + " (type argument is supposed to be ignored)"); +}); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument.window.js new file mode 100644 index 0000000000..9174008da3 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/type-argument.window.js @@ -0,0 +1,20 @@ +["replace", + "NOBODY", + "@ FD ;", + "it does not matter, you see \f", + "text/plain", + "text/xml", + "application/octet-stream", + "\0"].forEach(type => { + async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + assert_equals(frame.contentDocument.open(type), frame.contentDocument); + frame.contentDocument.write("<B>heya</b>"); + frame.contentDocument.close(); + assert_equals(frame.contentDocument.body.firstChild.localName, "b"); + assert_equals(frame.contentDocument.body.textContent, "heya"); + assert_equals(frame.contentDocument.contentType, "text/html"); + t.done(); + }, "document.open() with type set to: " + type + " (type argument is supposed to be ignored)"); +}); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/unload.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/unload.window.js new file mode 100644 index 0000000000..e275a4987a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/unload.window.js @@ -0,0 +1,19 @@ +// In an earlier version of the HTML Standard, document open steps had "unload +// document" as a step. Test that this no longer happens. + +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.src = "/common/blank.html"; + frame.onload = t.step_func(() => { + frame.contentWindow.onpagehide = t.unreached_func("onpagehide got called"); + frame.contentDocument.onvisibilitychange = t.unreached_func("onvisibilitychange got called"); + frame.contentWindow.onunload = t.unreached_func("onunload got called"); + frame.contentDocument.open(); + t.step_timeout(t.step_func_done(() => { + // If none of the three events have been fired by this point, we consider + // the test a success. `frame.remove()` above will allow the `load` event + // to be fired on the top-level Window, thus unblocking testharness. + }), 500); + }); +}, "document.open(): Do not fire pagehide, visibilitychange, or unload events"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-entry-document-sync-call.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-entry-document-sync-call.window.js new file mode 100644 index 0000000000..f20b4341e3 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-entry-document-sync-call.window.js @@ -0,0 +1,13 @@ +for (const methodName of ["open", "write", "writeln"]) { + async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => { frame.remove(); }); + const frameURL = new URL("resources/url-entry-document-incumbent-frame.html", document.URL).href; + frame.onload = t.step_func_done(() => { + assert_equals(frame.contentDocument.URL, frameURL); + frame.contentWindow.callDocumentMethod(methodName); + assert_equals(frame.contentDocument.URL, document.URL); + }); + frame.src = frameURL; + }, `document.${methodName}() changes document's URL to the entry global object's associate document's (sync call)`); +} diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-entry-document.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-entry-document.window.js new file mode 100644 index 0000000000..c3a1c3a874 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-entry-document.window.js @@ -0,0 +1,18 @@ +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + const frameURL = new URL("resources/url-entry-document-timer-frame.html", document.URL).href; + window.timerTest = t.step_func_done(() => { + assert_equals(frame.contentDocument.URL, frameURL); + assert_equals(frame.contentWindow.location.href, frameURL); + + // In this case, the entry settings object was set when this function is + // executed in the timer task through Web IDL's "invoke a callback + // function" algorithm, to be the relevant settings object of this + // function. Therefore the URL of this document would be inherited. + assert_equals(frame.contentDocument.open(), frame.contentDocument); + assert_equals(frame.contentDocument.URL, document.URL); + assert_equals(frame.contentWindow.location.href, document.URL); + }); + frame.src = frameURL; +}, "document.open() changes document's URL to the entry settings object's responsible document's (through timeouts)"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-fragment.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-fragment.window.js new file mode 100644 index 0000000000..0c528935b5 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url-fragment.window.js @@ -0,0 +1,26 @@ +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")), + urlSansHash = document.URL; + t.add_cleanup(() => { frame.remove(); }); + assert_equals(frame.contentDocument.URL, "about:blank"); + assert_equals(frame.contentWindow.location.href, "about:blank"); + self.onhashchange = t.step_func_done(() => { + frame.contentDocument.open(); + assert_equals(frame.contentDocument.URL, urlSansHash); + assert_equals(frame.contentWindow.location.href, urlSansHash); + }); + self.location.hash = "heya"; +}, "document.open() and document's URL containing a fragment (entry is not relevant)"); + +window.testDone = undefined; +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")) + t.add_cleanup(() => { frame.remove(); }); + frame.src = "resources/url-frame.html#heya"; + window.testDone = t.step_func_done((beforeURL, afterURL) => { + assert_equals(beforeURL, frame.src); + assert_equals(afterURL, frame.src); + assert_equals(frame.contentDocument.URL, frame.src); + assert_equals(frame.contentWindow.location.href, frame.src); + }); +}, "document.open() and document's URL containing a fragment (entry is relevant)"); diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url.window.js new file mode 100644 index 0000000000..4e7c649f45 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/url.window.js @@ -0,0 +1,93 @@ +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + assert_equals(frame.contentDocument.URL, "about:blank"); + assert_equals(frame.contentWindow.location.href, "about:blank"); + assert_equals(frame.contentDocument.open(), frame.contentDocument); + assert_equals(frame.contentDocument.URL, document.URL); + assert_equals(frame.contentWindow.location.href, document.URL); +}, "document.open() changes document's URL (fully active document)"); + +async_test(t => { + const blankURL = new URL("/common/blank.html", document.URL).href; + const frameURL = new URL("resources/page-with-frame.html", document.URL).href; + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + frame.onload = t.step_func(() => { + assert_equals(frame.contentDocument.URL, frameURL); + assert_equals(frame.contentWindow.location.href, frameURL); + const childFrame = frame.contentDocument.querySelector("iframe"); + const childDoc = childFrame.contentDocument; + const childWin = childFrame.contentWindow; + assert_equals(childDoc.URL, blankURL); + assert_equals(childWin.location.href, blankURL); + + // Right now childDoc is still fully active. + + frame.onload = t.step_func_done(() => { + // Now childDoc is still active but no longer fully active. + assert_equals(childDoc.open(), childDoc); + assert_equals(childDoc.URL, blankURL); + assert_equals(childWin.location.href, blankURL); + }); + frame.src = "/common/blank.html"; + }); + frame.src = frameURL; +}, "document.open() does not change document's URL (active but not fully active document)"); + +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + const doc = frame.contentDocument; + + // We do not test for win.location.href in this test due to + // https://github.com/whatwg/html/issues/3959. + + // Right now the frame is connected and it has an active document. + assert_equals(doc.URL, "about:blank"); + + frame.remove(); + + // Now the frame is no longer connected. Its document is no longer active. + assert_equals(doc.URL, "about:blank"); + assert_equals(doc.open(), doc); + assert_equals(doc.URL, "about:blank"); +}, "document.open() does not change document's URL (non-active document with an associated Window object; frame is removed)"); + +async_test(t => { + const frame = document.createElement("iframe"); + t.add_cleanup(() => frame.remove()); + + // We do not test for win.location.href in this test due to + // https://github.com/whatwg/html/issues/3959. + + frame.onload = t.step_func(() => { + const doc = frame.contentDocument; + // Right now the frame is connected and it has an active document. + assert_equals(doc.URL, "about:blank"); + + frame.onload = t.step_func_done(() => { + // Now even though the frame is still connected, its document is no + // longer active. + assert_not_equals(frame.contentDocument, doc); + assert_equals(doc.URL, "about:blank"); + assert_equals(doc.open(), doc); + assert_equals(doc.URL, "about:blank"); + }); + + frame.src = "/common/blank.html"; + }); + + // We need to connect the frame after the load event is set up to mitigate + // against https://crbug.com/569511. + document.body.appendChild(frame); +}, "document.open() does not change document's URL (non-active document with an associated Window object; navigated away)"); + +test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + const doc = frame.contentDocument.implementation.createHTMLDocument(); + assert_equals(doc.URL, "about:blank"); + assert_equals(doc.open(), doc); + assert_equals(doc.URL, "about:blank"); +}, "document.open() does not change document's URL (non-active document without an associated Window object)"); diff --git a/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask-cross-realm-callback-report-exception.html b/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask-cross-realm-callback-report-exception.html new file mode 100644 index 0000000000..fa153f8f96 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask-cross-realm-callback-report-exception.html @@ -0,0 +1,28 @@ +<!doctype html> +<meta charset=utf-8> +<title>queueMicrotask() reports the exception from its callback in the callback's global object</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<iframe></iframe> +<iframe></iframe> +<iframe></iframe> +<script> +setup({ allow_uncaught_exception: true }); + +const onerrorCalls = []; +window.onerror = () => { onerrorCalls.push("top"); }; +frames[0].onerror = () => { onerrorCalls.push("frame0"); }; +frames[1].onerror = () => { onerrorCalls.push("frame1"); }; +frames[2].onerror = () => { onerrorCalls.push("frame2"); }; + +async_test(t => { + window.onload = t.step_func(() => { + frames[0].queueMicrotask(new frames[1].Function(`throw new parent.frames[2].Error("PASS");`)); + + t.step_timeout(() => { + assert_array_equals(onerrorCalls, ["frame1"]); + t.done(); + }, 4); + }); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask-exceptions.any.js b/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask-exceptions.any.js new file mode 100644 index 0000000000..01f32ac9ba --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask-exceptions.any.js @@ -0,0 +1,15 @@ +// META: global=window,worker +"use strict"; + +setup({ + allow_uncaught_exception: true +}); + +async_test(t => { + const error = new Error("boo"); + self.addEventListener("error", t.step_func_done(ev => { + assert_equals(ev.error, error); + })); + + queueMicrotask(() => { throw error; }); +}, "It rethrows exceptions"); diff --git a/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask.any.js b/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask.any.js new file mode 100644 index 0000000000..e67765fade --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask.any.js @@ -0,0 +1,39 @@ +// META: global=window,worker +"use strict"; + +test(() => { + assert_equals(typeof queueMicrotask, "function"); +}, "It exists and is a function"); + +test(() => { + assert_throws_js(TypeError, () => queueMicrotask(), "no argument"); + assert_throws_js(TypeError, () => queueMicrotask(undefined), "undefined"); + assert_throws_js(TypeError, () => queueMicrotask(null), "null"); + assert_throws_js(TypeError, () => queueMicrotask(0), "0"); + assert_throws_js(TypeError, () => queueMicrotask({ handleEvent() { } }), "an event handler object"); + assert_throws_js(TypeError, () => queueMicrotask("window.x = 5;"), "a string"); +}, "It throws when given non-functions"); + +async_test(t => { + let called = false; + queueMicrotask(t.step_func_done(() => { + called = true; + })); + assert_false(called); +}, "It calls the callback asynchronously"); + +async_test(t => { + queueMicrotask(t.step_func_done(function () { // note: intentionally not an arrow function + assert_array_equals(arguments, []); + }), "x", "y"); +}, "It does not pass any arguments"); + +async_test(t => { + const happenings = []; + Promise.resolve().then(() => happenings.push("a")); + queueMicrotask(() => happenings.push("b")); + Promise.reject().catch(() => happenings.push("c")); + queueMicrotask(t.step_func_done(() => { + assert_array_equals(happenings, ["a", "b", "c"]); + })); +}, "It interleaves with promises as expected"); diff --git a/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask.window.js b/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask.window.js new file mode 100644 index 0000000000..78cdcfc5d9 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/microtask-queuing/queue-microtask.window.js @@ -0,0 +1,45 @@ +"use strict"; + +// This does not work as you expect because mutation observer compound microtasks are confusing. +// Basically you can only use it once per test. +function queueMicrotaskViaMO(cb) { + const observer = new MutationObserver(cb); + const node = document.createTextNode(""); + observer.observe(node, { characterData: true }); + node.data = "foo"; +} + +// Need to use promise_test to get sequential ordering; otherwise the global mutation observer +// compound microtask business screws us over. + +promise_test(() => { + return new Promise(resolve => { + const happenings = []; + + queueMicrotaskViaMO(() => happenings.push("x")); + queueMicrotask(() => happenings.push("a")); + + queueMicrotask(() => { + assert_array_equals(happenings, ["x", "a"]); + resolve(); + }); + }); +}, "It interleaves with MutationObservers as expected"); + +promise_test(() => { + return new Promise(resolve => { + const happenings = []; + + queueMicrotask(() => happenings.push("a")); + Promise.reject().catch(() => happenings.push("x")); + queueMicrotaskViaMO(() => happenings.push(1)); + Promise.resolve().then(() => happenings.push("y")); + queueMicrotask(() => happenings.push("b")); + queueMicrotask(() => happenings.push("c")); + + queueMicrotask(() => { + assert_array_equals(happenings, ["a", "x", 1, "y", "b", "c"]); + resolve(); + }); + }); +}, "It interleaves with MutationObservers and promises together as expected"); diff --git a/testing/web-platform/tests/html/webappapis/scripting/event-loops/fully_active_document.window.js b/testing/web-platform/tests/html/webappapis/scripting/event-loops/fully_active_document.window.js new file mode 100644 index 0000000000..950a8a29ee --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/event-loops/fully_active_document.window.js @@ -0,0 +1,29 @@ +async_test(t => { + const frame = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => frame.remove()); + + frame.onload = t.step_func(() => { + // Right now the doc of the iframe inside "frame" is still "fully-active". + // Navigate parent away, making the child iframe's doc "active", not "fully-active". + frame.contentWindow.location = "/common/blank.html"; + + frame.onload = t.step_func(() => { + // The child iframe's doc is "active", not "fully-active", and should not receive the storage notification. + sessionStorage.setItem('myCat', 'Tom'); + t.step_timeout(() => { + // The child iframe's hasn't received the storage notification. + assert_equals(sessionStorage.getItem("Received storage event"), null); + frame.contentWindow.history.go(-1); + t.step_timeout(() => { + // Now The child iframe's doc is "fully-active" again, + // the previously not run storage task should now have been run. + assert_equals(sessionStorage.getItem("Received storage event"), "true"); + t.done(); + }, 1000); + }, 1000); + }); + }); + + frame.src = "resources/page-with-frame.html"; +}, "Tasks for documents that are not fully active are stored, and run when the documents becomes fully-active"); + diff --git a/testing/web-platform/tests/html/webappapis/scripting/event-loops/microtask_after_raf.html b/testing/web-platform/tests/html/webappapis/scripting/event-loops/microtask_after_raf.html new file mode 100644 index 0000000000..824dbc4b92 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/event-loops/microtask_after_raf.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<head> +<link rel=author title="Aleks Totic" href="mailto:atotic@chromium.org"> +<link rel=help href="https://html.spec.whatwg.org/#clean-up-after-running-script"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/common.js"></script> +</head> +<body style="height:2000px;"> +<script> +/* +promise 1, promise 2 execute immediately after rAF +promise 1 child executes immediately after promise 2. + +Relevant specs: + +https://html.spec.whatwg.org/#clean-up-after-running-script +If the JavaScript execution context stack is now empty, perform a microtask checkpoint. + +https://html.spec.whatwg.org/#perform-a-microtask-checkpoint +"perform a microtask checkpoint" runs in a loop until all microtasks have been delivered. +*/ + +var test = async_test("Microtask execute immediately after script"); + +window.requestAnimationFrame( function() { + var events = []; + + Promise.resolve() + .then(function() { + events.push("promise 1"); + return Promise.resolve(); + }) + .then(function() { + test.step(function() { + events.push("promise 1 child"); + assert_array_equals(events, ["promise 1", "promise 2", "promise 1 child"]); + test.done(); + }); + }); + Promise.resolve() + .then(function() { + events.push("promise 2"); + }); + + // Set up events that must be executed after Promise. + window.setTimeout(function() { + events.push('timeout'); + }, 0); + window.addEventListener('scroll', function() { + events.push('scroll'); + }); + window.scrollBy(0,10); + +}); +</script> +</body> diff --git a/testing/web-platform/tests/html/webappapis/scripting/event-loops/microtask_after_script.html b/testing/web-platform/tests/html/webappapis/scripting/event-loops/microtask_after_script.html new file mode 100644 index 0000000000..799a0de605 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/event-loops/microtask_after_script.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<head> +<link rel=author title="Aleks Totic" href="mailto:atotic@chromium.org"> +<link rel=help href="https://html.spec.whatwg.org/#clean-up-after-running-script"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/common.js"></script> +</head> +<body style="height:2000px;"> +<script> +/* +promise 1, promise 2 execute immediately after script tag +promise 1 child executes immediately after promise 2. + +Relevant specs: + +https://html.spec.whatwg.org/#clean-up-after-running-script +If the JavaScript execution context stack is now empty, perform a microtask checkpoint. + +https://html.spec.whatwg.org/#perform-a-microtask-checkpoint +"perform a microtask checkpoint" runs in a loop until all microtasks have been delivered. +*/ + +var test = async_test("Microtask immediately after script"); + +var events = []; + +Promise.resolve() +.then(function() { + events.push("promise 1"); + return Promise.resolve(); +}) +.then(function() { + test.step(function() { + events.push("promise 1 child"); + assert_array_equals(events, ["promise 1", "promise 2", "promise 1 child"]); + test.done(); + }); +}); +Promise.resolve() +.then(function() { + events.push("promise 2"); +}); + +// Set up events that must be executed after Promise. +window.setTimeout(function() { + events.push('timeout'); +}, 0); +window.addEventListener('scroll', function() { + events.push('scroll'); +}); +window.scrollBy(0,10); + +</script> +</body> diff --git a/testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/common.js b/testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/common.js new file mode 100644 index 0000000000..e2279f93dd --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/common.js @@ -0,0 +1,20 @@ +// Helper for tests that just want to verify the ordering of a series of events. +// Usage: +// log_test(function(t, log) { +// log('first'); +// log('second'); +// }, ['first', 'second'], 'Ordinal numbers are ordinal'); + +function log_test(func, expected, description) { + async_test(function(t) { + var actual = []; + function log(entry) { + actual.push(entry); + if (expected.length <= actual.length) { + assert_array_equals(actual, expected); + t.done(); + } + } + func(t, t.step_func(log)); + }, description); +} diff --git a/testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/iframe.html b/testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/iframe.html new file mode 100644 index 0000000000..32e4862360 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/iframe.html @@ -0,0 +1,7 @@ +<!doctype html> +<title>Childframe</title> +<script> + window.addEventListener('storage', () => { + sessionStorage.setItem("Received storage event", true); + }); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/page-with-frame.html b/testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/page-with-frame.html new file mode 100644 index 0000000000..f13170576e --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/event-loops/resources/page-with-frame.html @@ -0,0 +1 @@ +<iframe src="iframe.html"></iframe> diff --git a/testing/web-platform/tests/html/webappapis/scripting/event-loops/task_microtask_ordering-manual.html b/testing/web-platform/tests/html/webappapis/scripting/event-loops/task_microtask_ordering-manual.html new file mode 100644 index 0000000000..ed2f70e196 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/event-loops/task_microtask_ordering-manual.html @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<title>Task and Microtask Ordering </title> +<link rel=author title="Joshua Bell" href="mailto:jsbell@google.com"> +<link rel=help href="https://html.spec.whatwg.org/multipage/#event-loops"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/common.js"></script> +<style> +.inner { padding: 46px; width: 0; margin: 0 auto; background: #d4d4d4; } +.outer { padding: 25px; width: 92px; background: #f1f1f1; } +</style> + +<p>Click on the inner box:</p> +<div class="outer"> + <div class="inner"></div> +</div> + +<script> + +// Based on: https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ + +log_test(function(t, log) { + // Let's get hold of those elements + var outer = document.querySelector('.outer'); + var inner = document.querySelector('.inner'); + + // Let's listen for attribute changes on the + // outer element + new MutationObserver(function() { + log('mutate'); + }).observe(outer, { + attributes: true + }); + + // Here's a click listener... + function onClick() { + log('click'); + + setTimeout(function() { + log('timeout'); + }, 0); + + Promise.resolve().then(function() { + log('promise'); + }); + + outer.setAttribute('data-random', Math.random()); + } + + // ...which we'll attach to both elements + inner.addEventListener('click', onClick); + outer.addEventListener('click', onClick); +}, [ + 'click', + 'promise', + 'mutate', + 'click', + 'promise', + 'mutate', + 'timeout', + 'timeout' +], 'Level 1 bossfight (manual click)'); + +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/event-loops/task_microtask_ordering.html b/testing/web-platform/tests/html/webappapis/scripting/event-loops/task_microtask_ordering.html new file mode 100644 index 0000000000..c14a043b6a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/event-loops/task_microtask_ordering.html @@ -0,0 +1,85 @@ +<!DOCTYPE html> +<title>Task and Microtask Ordering </title> +<link rel=author title="Joshua Bell" href="mailto:jsbell@google.com"> +<link rel=help href="https://html.spec.whatwg.org/multipage/#event-loops"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/common.js"></script> + +<div class="outer"> + <div class="inner"></div> +</div> + +<script> + +// Based on: https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ + +log_test(function(t, log) { + log('script start'); + + setTimeout(function() { + log('setTimeout'); + }, 0); + + Promise.resolve().then(function() { + log('promise1'); + }).then(function() { + log('promise2'); + }); + + log('script end'); +}, [ + 'script start', + 'script end', + 'promise1', + 'promise2', + 'setTimeout' +], 'Basic task and microtask ordering'); + +log_test(function(t, log) { + // Let's get hold of those elements + var outer = document.querySelector('.outer'); + var inner = document.querySelector('.inner'); + + // Let's listen for attribute changes on the + // outer element + new MutationObserver(function() { + log('mutate'); + }).observe(outer, { + attributes: true + }); + + // Here's a click listener... + function onClick() { + log('click'); + + setTimeout(function() { + log('timeout'); + }, 0); + + Promise.resolve().then(function() { + log('promise'); + }); + + outer.setAttribute('data-random', Math.random()); + } + + // ...which we'll attach to both elements + inner.addEventListener('click', onClick); + outer.addEventListener('click', onClick); + + // Note that this will behave differently than a real click, + // since the dispatch is synchronous and microtasks will not + // run between event bubbling steps. + inner.click(); +}, [ + 'click', + 'click', + 'promise', + 'mutate', + 'promise', + 'timeout', + 'timeout' +], 'Level 1 bossfight (synthetic click)'); + +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/body-onload.html b/testing/web-platform/tests/html/webappapis/scripting/events/body-onload.html new file mode 100644 index 0000000000..1e43d1ccd4 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/body-onload.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<title>HTMLBodyElement.onload</title> +<link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu"> +<link rel="author" title="Ms2ger" href="mailto:ms2ger@gmail.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#handler-window-onload"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test("body.onload should set the window.onload handler") +window.onload = t.step_func(function() { + assert_unreached("This handler should be overwritten.") +}) +var b = document.createElement("body") +b.onload = t.step_func(function(e) { + assert_equals(e.currentTarget, window, + "The event should be fired at the window.") + t.done() +}) +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-lexical-scopes-form-owner.html b/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-lexical-scopes-form-owner.html new file mode 100644 index 0000000000..e31bd2496a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-lexical-scopes-form-owner.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Form's lexical scope is established only for form-associated elements</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#form-associated-element"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/webappapis.html#getting-the-current-value-of-the-event-handler"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<form id="form"> + <input onclick="window.inputOnClickElements = elements;"> + <img onclick="window.imgOnClickElements = elements;" alt="img"> + <div onclick="window.divOnClickElements = elements;">div</div> + <x-foo onclick="window.xFooOnClickElements = elements;">x-foo</x-foo> +</form> + +<script> +"use strict"; + +window.elements = "global_elements"; + +test(() => { + const input = form.querySelector("input"); + input.click(); + assert_equals(window.inputOnClickElements, form.elements); +}, "<input> has a form owner"); + +test(() => { + const img = form.querySelector("img"); + img.click(); + assert_equals(window.imgOnClickElements, form.elements); +}, "<img> has a form owner"); + +test(() => { + const div = form.querySelector("div"); + div.click(); + assert_equals(window.divOnClickElements, window.elements); +}, "<div> doesn't have a form owner"); + +test(() => { + customElements.define("x-foo", class extends HTMLElement { + static formAssociated = true; + }); + + const xFoo = form.querySelector("x-foo"); + xFoo.click(); + assert_equals(window.xFooOnClickElements, form.elements); +}, "form-associated <x-foo> has a form owner"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-lexical-scopes.html b/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-lexical-scopes.html new file mode 100644 index 0000000000..ed6c006651 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-lexical-scopes.html @@ -0,0 +1,167 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Lexical scopes when compiling an inline event handler</title> +<link rel="help" href="https://html.spec.whatwg.org/C/#getting-the-current-value-of-the-event-handler"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> +setup({allow_uncaught_exception: true}); +</script> + +<!-- test case 1: element, document, and window --> + +<table> + <tr id="test1_outer"> + <td id="test1_target" onclick=" +window.testResults.complete = typeof(complete); +window.testResults.cellIndex = typeof(cellIndex); +window.testResults.cells = typeof(cells); +window.testResults.domain = typeof(domain); +window.testResults.print = typeof(print); +window.testResults.testResults = typeof(testResults); +window.testResults.target_own_property = typeof(target_own_property); +window.testResults.inner_own_property = typeof(inner_own_property); +window.testResults.outer_own_property = typeof(outer_own_property); +window.testResults.event = typeof(event); +"> + <img id="test1_inner"> + </td> + </tr> +</table> + +<script> +"use strict"; + +test(() => { + const target_element = document.getElementById("test1_target"); + const inner_element = document.getElementById("test1_inner"); + const outer_element = document.getElementById("test1_outer"); + + target_element.target_own_property = {}; + inner_element.inner_own_property = {}; + outer_element.outer_own_property = {}; + + const results = window.testResults = {}; + // Clicking an inner element makes the event target where the event handler is + // registered doesn't match the event target that the event is fired. From a + // point of view |target_element|, event.target != event.currentTarget. + inner_element.click(); + // Expected scopes are: |target_element|, document, and window. + assert_equals(results.complete, "undefined", "HTMLImageElement.prototype.complete"); + assert_equals(results.cellIndex, "number", "HTMLTableCellElement.prototype.cellIndex"); + assert_equals(results.cells, "undefined", "HTMLTableRowElement.prototype.cellIndex"); + assert_equals(results.domain, "string", "Document.prototype.domain"); + assert_equals(results.print, "function", "window.print"); + assert_equals(results.testResults, "object"); + assert_equals(results.target_own_property, "object"); + assert_equals(results.inner_own_property, "undefined"); + assert_equals(results.outer_own_property, "undefined"); + assert_equals(results.event, "object", "The argument of event handler"); +}, "The EventHandler is an element's event handler and has no form owner."); +</script> + + +<!-- test case 2: element, form owner, document, and window --> + +<form id="test2_form_owner" onsubmit="return false;"> + <!-- 'button' is a form-associated element and has a form owner. + https://html.spec.whatwg.org/C/#form-associated-element + --> + <button id="test2_target" onclick=" +window.testResults.cite = typeof(cite); +window.testResults.autofocus = typeof(autofocus); +window.testResults.form = typeof(form); +window.testResults.encoding = typeof(encoding); +window.testResults.domain = typeof(domain); +window.testResults.print = typeof(print); +window.testResults.testResults = typeof(testResults); +window.testResults.target_own_property = typeof(target_own_property); +window.testResults.inner_own_property = typeof(inner_own_property); +window.testResults.form_owner_own_property = typeof(form_owner_own_property); +window.testResults.event = typeof(event); +"> + <q id="test2_inner"></q> + </button> +</form> + +<script> +"use strict"; + +test(() => { + const target_element = document.getElementById("test2_target"); + const inner_element = document.getElementById("test2_inner"); + const form_owner_element = document.getElementById("test2_form_owner"); + + target_element.target_own_property = {}; + inner_element.inner_own_property = {}; + form_owner_element.form_owner_own_property = {}; + + const results = window.testResults = {}; + // Clicking an inner element makes the event target where the event handler is + // registered doesn't match the event target that the event is fired. From a + // point of view |target_element|, event.target != event.currentTarget. + inner_element.click(); + // Expected scopes are: |target_element|, form owner, document, and window. + assert_equals(results.cite, "undefined", "HTMLQuoteElement.prototype.cite"); + assert_equals(results.autofocus, "boolean", "HTMLButtonElement.prototype.autofocus"); + assert_equals(results.form, "object", "HTMLButtonElement.prototype.form"); + assert_equals(results.encoding, "string", "HTMLFormElement.prototype.encoding"); + assert_equals(results.domain, "string", "Document.prototype.domain"); + assert_equals(results.print, "function", "window.print"); + assert_equals(results.testResults, "object"); + assert_equals(results.target_own_property, "object"); + assert_equals(results.inner_own_property, "undefined"); + assert_equals(results.form_owner_own_property, "object"); + assert_equals(results.event, "object", "The argument of event handler"); +}, "The EventHandler is an element's event handler and has a form owner."); +</script> + + +<!-- test case 3: element and window --> + +<a id="test3_inner"></a> + +<script> +"use strict"; + +// This test is placed at last so that it can safely use a global variable +// without conflicting other tests. Only this test is asynchronous. +async_test(t => { + const target_element = window; + const inner_element = document.getElementById("test3_inner"); + + target_element.target_own_property = {}; + inner_element.inner_own_property = {}; + document.body.body_own_property = {}; + + // "onerror" is one of the Window-reflecting body element event handler set. + // https://html.spec.whatwg.org/C/#window-reflecting-body-element-event-handler-set + // So, the EventHandler is treated as a Window's event handler. + document.body.setAttribute("onerror", "\ +window.testResults.ping = typeof(ping); \ +window.testResults.domain = typeof(domain); \ +window.testResults.print = typeof(print); \ +window.testResults.testResults = typeof(testResults); \ +window.testResults.target_own_property = typeof(target_own_property); \ +window.testResults.inner_own_property = typeof(inner_own_property); \ +window.testResults.body_own_property = typeof(body_own_property); \ +window.testResults.event = typeof(event); \ +"); + + const results = window.testResults = {}; + window.addEventListener("error", t.step_func_done(() => { + // Expected scopes are: |target_element| and window only. + assert_equals(results.domain, "undefined", "Document.prototype.domain"); + assert_equals(results.print, "function", "window.print"); + assert_equals(results.testResults, "object"); + assert_equals(results.target_own_property, "object"); + assert_equals(results.inner_own_property, "undefined"); + assert_in_array(results.event, ["object", "string"], "The first argument of onerror event handler"); + })); + + // Make a compilation error happen in order to invoke onerror event handler. + inner_element.setAttribute("onclick", "cause a compilation error"); + inner_element.click(); +}, "The EventHandler is not an element's event handler (i.e. Window's event handler) and has no form owner."); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-settings-objects.html b/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-settings-objects.html new file mode 100644 index 0000000000..29ac9b8ced --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-settings-objects.html @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Entry and incumbent settings objects when compiling an inline event handler</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="log"></div> + +<script> +"use strict"; +window.name = "parent frame"; + +async_test(t => { + const iframe = document.createElement("iframe"); + iframe.src = "resources/compiled-event-handler-settings-objects-support.html"; + iframe.onload = t.step_func(() => { + const button = iframe.contentDocument.querySelector("button"); + const compiled = button.onclick; + + assert_equals(compiled.constructor, iframe.contentWindow.Function, "The constructor must be from the iframe"); + assert_not_equals(compiled.constructor, Function, "The constructor must not be from this page"); + + t.done(); + }); + + document.body.appendChild(iframe); + +}, "The Function instance must be created in the Realm of the node document"); + +async_test(t => { + const iframe = document.createElement("iframe"); + iframe.src = "resources/compiled-event-handler-settings-objects-support.html"; + iframe.onload = t.step_func(() => { + const button = iframe.contentDocument.querySelector("button"); + + window.onWindowLoaded = t.step_func_done(url => { + const pathname = new URL(url).pathname; + assert_equals(pathname, + "/html/webappapis/scripting/events/resources/open-window.html"); + // This tests that the entry settings object used to resolve URLs in that window.open() was the same as that + // of the node document (i.e. the iframe document), not e.g. this window. + }); + + button.click(); + }); + + document.body.appendChild(iframe); + +}, "The entry settings object while executing the compiled callback via Web IDL's invoke must be that " + + "of the node document"); + +async_test(t => { + const iframe = document.createElement("iframe"); + iframe.src = "resources/compiled-event-handler-settings-objects-support.html"; + iframe.onload = t.step_func(() => { + window.onmessage = t.step_func_done(event => { + assert_equals(event.data, "PASS"); + assert_equals(event.source.name, "iframe"); + assert_equals(event.source, iframe.contentWindow, "The source must be the iframe"); + }); + + iframe.src = "about:blank"; + }); + + document.body.appendChild(iframe); + +}, "The incumbent settings object while executing the compiled callback via Web IDL's invoke must be that " + + "of the node document"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-symbol-unscopables.html b/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-symbol-unscopables.html new file mode 100644 index 0000000000..c840059e68 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/compile-event-handler-symbol-unscopables.html @@ -0,0 +1,71 @@ +<!DOCTYPE html> +<meta charset="UTF-8" /> +<title>Inline event handler scopes exclude unscopable properties</title> +<link rel="author" title="ExE Boss" href="https://ExE-Boss.tech" /> +<link rel="help" href="https://html.spec.whatwg.org/multipage/webappapis.html#getting-the-current-value-of-the-event-handler" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> + "use strict"; + window.testVariable = {}; +</script> + +<!-- test case 1: element, document, and window --> +<div id="test1_target" onclick=' + "use strict"; + + window.testResults.testVariable = testVariable; +'></div> + +<script> + "use strict"; + + test(() => { + const results = window.testResults = {}; + + document[Symbol.unscopables].testVariable = true; + document.testVariable = "FAIL (document)"; + + document.getElementById("test1_target").click(); + assert_equals(results.testVariable, window.testVariable); + }, "unscopable `document.testVariable` doesn't shadow `window.testVariable`"); + + test(() => { + const results = window.testResults = {}; + const element = document.getElementById("test1_target"); + + element[Symbol.unscopables].testVariable = true; + element.testVariable = "FAIL (element)"; + + element.click(); + assert_equals(results.testVariable, window.testVariable); + }, "unscopable `element.testVariable` doesn't shadow `window.testVariable`"); +</script> + +<!-- test case 2: element, form owner, document, and window --> +<form id="test2_form_owner" onsubmit="return false;"> + <!-- <button> is a form-associated element and has a form owner. + https://html.spec.whatwg.org/C/#form-associated-element --> + <button id="test2_target" onclick=' + "use strict"; + + window.testResults.testVariable = testVariable; + '></button> +</form> + +<script> + "use strict"; + + test(() => { + const results = window.testResults = {}; + const element = document.getElementById("test2_target"); + const formOwner = document.getElementById("test2_form_owner"); + + formOwner[Symbol.unscopables].testVariable = true; + formOwner.testVariable = "FAIL (formOwner)"; + + element.click(); + assert_equals(results.testVariable, window.testVariable); + }, "unscopable `formOwner.testVariable` doesn't shadow `window.testVariable`") +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/contextmenu-event-manual.htm b/testing/web-platform/tests/html/webappapis/scripting/events/contextmenu-event-manual.htm new file mode 100644 index 0000000000..2331fa17ee --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/contextmenu-event-manual.htm @@ -0,0 +1,21 @@ +<!doctype html> +<html> + <head> + <title>HTML contextmenu event is a MouseEvent</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <style>#contextmenutarget { width: 100px; height: 100px; background-color: red; }</style> + </head> + <body> + <div id='contextmenutarget'>Trigger context menu in this box.</div> + <div id="log"></div> + <script type="text/javascript"> +var t = async_test('contextmenu event generated from user action is MouseEvent'); +document.querySelector("#contextmenutarget").addEventListener('contextmenu', t.step_func(function (e) { + assert_equals(e.constructor, window.MouseEvent); + document.querySelector("#contextmenutarget").style.backgroundColor = "green"; + t.done(); +})); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-all-global-events.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-all-global-events.html new file mode 100644 index 0000000000..ee8c34ced3 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-all-global-events.html @@ -0,0 +1,88 @@ +<!DOCTYPE html> +<title>GlobalEventHandlers</title> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#globaleventhandlers"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#event-handler-idl-attributes"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#event-handler-content-attributes"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/WebIDLParser.js"></script> + +<script> +"use strict"; + +// The prefixed animation events are special; their event types are +// camel-case. +const prefixedAnimationAttributeToEventType = new Map([ + ["webkitanimationend", "webkitAnimationEnd"], + ["webkitanimationiteration", "webkitAnimationIteration"], + ["webkitanimationstart", "webkitAnimationStart"], + ["webkittransitionend", "webkitTransitionEnd"], +]); + +setup({ explicit_done: true }); + +fetch("/interfaces/html.idl").then(res => res.text()).then(htmlIDL => { + const parsedHTMLIDL = WebIDL2.parse(htmlIDL); + const globalEventHandlers = parsedHTMLIDL.find(idl => idl.name === "GlobalEventHandlers"); + + // onerror is too special + const names = globalEventHandlers.members.map(member => member.name).filter(name => name !== "onerror"); + + for (const name of names) { + const withoutOn = name.substring(2); + + test(() => { + for (const location of [window, HTMLElement.prototype, SVGElement.prototype, Document.prototype]) { + assert_true(location.hasOwnProperty(name), + `${location.constructor.name} has an own property named "${name}"`); + } + assert_false(name in Element.prototype, `Element.prototype must not contain a "${name}" property`); + }, `${name}: must be on the appropriate locations for GlobalEventHandlers`); + + test(() => { + const htmlElement = document.createElement("span"); + const svgElement = document.createElementNS("http://www.w3.org/2000/svg", "g"); + + for (var location of [window, htmlElement, svgElement, document]) { + assert_equals(location[name], null, + `The default value of the property is null for a ${location.constructor.name} instance`); + } + }, `${name}: the default value must be null`); + + test(() => { + const el = document.createElement("div"); + el.setAttribute(name, `window.${name}Happened = true;`); + const compiledHandler = el[name]; + + assert_equals(typeof compiledHandler, "function", `The ${name} property must be a function`); + compiledHandler(); + assert_true(window[name + "Happened"], "Calling the handler must run the code"); + }, `${name}: the content attribute must be compiled into a function as the corresponding property`); + + test(() => { + const el = document.createElement("div"); + el.setAttribute(name, `window.${name}Happened2 = true;`); + + let eventType = withoutOn; + if (prefixedAnimationAttributeToEventType.has(eventType)) { + eventType = prefixedAnimationAttributeToEventType.get(eventType); + } + el.dispatchEvent(new Event(eventType)); + + assert_true(window[name + "Happened2"], "Dispatching an event must run the code"); + }, `${name}: the content attribute must execute when an event is dispatched`); + + test(() => { + const element = document.createElement("meta"); + element[name] = e => { + assert_equals(e.currentTarget, element, "The event must be fired at the <meta> element"); + }; + + element.dispatchEvent(new Event(withoutOn)); + }, `${name}: dispatching an Event at a <meta> element must trigger element.${name}`); + } + + done(); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-body-window.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-body-window.html new file mode 100644 index 0000000000..e8055d99f3 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-body-window.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<title>HTMLBodyElement event handlers</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/WebIDLParser.js"></script> +<script src="resources/event-handler-body.js"></script> +<div id="log"></div> +<body> +<script> +setup({ explicit_done: true }); + +handlersListPromise.then(({ shadowedHandlers, notShadowedHandlers }) => { + eventHandlerTest(shadowedHandlers, notShadowedHandlers, "body"); + + done(); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-frameset-window.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-frameset-window.html new file mode 100644 index 0000000000..b583eca52d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-frameset-window.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>event handlers</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/WebIDLParser.js"></script> +<script src="resources/event-handler-body.js"></script> +<script> +setup({ explicit_done: true }); + +handlersListPromise.then(({ shadowedHandlers, notShadowedHandlers }) => { + eventHandlerTest(shadowedHandlers, notShadowedHandlers, "frameset"); + + // The testharness framework appends test results to document.body, + // show test results in frame after test done. + add_completion_callback(() => { + const log_elem = document.getElementById("log"); + const frame_elem = document.querySelector("frame"); + if (log_elem) { + frame_elem.contentDocument.body.innerHTML = log_elem.innerHTML; + } + }); + + done(); +}); +</script> +<frameset> + <frame src="/common/blank.html" /> +</frameset> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-windowless-body.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-windowless-body.html new file mode 100644 index 0000000000..9b81d42ff7 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-attributes-windowless-body.html @@ -0,0 +1,71 @@ +<!doctype html> +<meta charset="utf-8"> +<title></title> +<body></body> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/WebIDLParser.js"></script> +<script src="resources/event-handler-body.js"></script> +<script> +setup({ explicit_done: true }); +const elements = ['body', 'frameset']; +handlersListPromise.then(({ shadowedHandlers, notShadowedHandlers }) => { + elements.forEach(function (elementName) { + shadowedHandlers.forEach(function (eventName) { + var handlerName = "on" + eventName; + + test(function() { + var windowHandler = function () { return "Handler attached to the window"; }; + window[handlerName] = windowHandler; + + var d = (new DOMParser).parseFromString('', 'text/html'); + var b = d.createElement(elementName); + + assert_equals(b[handlerName], null); + + window[handlerName] = null; + }, "Return null when getting the " + eventName + " event handler of a windowless " + elementName); + + test(function() { + var windowHandler = function () { return "Handler attached to the window"; }; + window[handlerName] = windowHandler; + + var d = (new DOMParser).parseFromString('', 'text/html'); + var b = d.createElement(elementName); + b[handlerName] = function() { return "Handler attached to windowless element"; }; + + assert_equals(window[handlerName], windowHandler); + assert_equals(b[handlerName], null); + + // Clean up window event handler + window[handlerName] = null; + }, "Ignore setting of " + eventName + " window event handlers on windowless " + elementName); + }); + + notShadowedHandlers.forEach(function (eventName) { + var handlerName = "on" + eventName; + + test(function() { + var windowHandler = function () { return "Handler attached to the window"; }; + window[handlerName] = windowHandler; + + var d = (new DOMParser).parseFromString('', 'text/html'); + var b = d.createElement(elementName); + + assert_equals(b[handlerName], null); + + var elementHandler = function () { return "Handler attached to the element"; }; + b[handlerName] = elementHandler; + + assert_equals(window[handlerName], windowHandler); + assert_equals(b[handlerName], elementHandler); + + // Clean up window event handler + window[handlerName] = null; + }, eventName + " is unaffected on a windowless " + elementName); + }); + }); + + done(); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-handleEvent-ignored.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-handleEvent-ignored.html new file mode 100644 index 0000000000..8039bac7ad --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-handleEvent-ignored.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>"handleEvent" property of EventHandler should be ignored</title> +<link rel="help" href="https://html.spec.whatwg.org/#eventhandler"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +"use strict"; + +test(t => { + const handler = Object.create(null, { + handleEvent: { + get: t.unreached_func('"handleEvent" property should not be looked up'), + }, + }); + + const el = document.createElement("div"); + el.onmouseenter = handler; + el.dispatchEvent(new MouseEvent("mouseenter")); +}, 'plain object "mouseenter" handler'); + +async_test(t => { + const handler = Object.create(Function.prototype, { + handleEvent: { + get: t.unreached_func('"handleEvent" property should not be looked up'), + }, + }); + assert_true(handler instanceof Function); + + window.onmessage = handler; + window.postMessage({}, "*"); + + step_timeout(() => { + t.done(); + }, 50); +}, 'non-callable "message" handler that is instance of Function'); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-javascript.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-javascript.html new file mode 100644 index 0000000000..657a37839d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-javascript.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<title>Event handler with labels</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body onload="javascript: + for (var i = 0; i < 2; ++i) { + for (var j = 0; j < 2; ++j) { + t.step(function() { + assert_equals(i, 0); + assert_equals(j, 0); + }); + break javascript; + } + } + t.done(); +"> +<div id="log"></div> +<script> +var t = async_test("Event handlers starting with 'javascript:' should treat that as a label."); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onresize.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onresize.html new file mode 100644 index 0000000000..0e44e7272f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onresize.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<title>HTMLBodyElement.onresize</title> +<link rel="author" title="His-Name-Is-Joof" href="mailto:jeffrharrison@gmail.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#handler-window-onresize"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +var t = async_test("body.onresize should set the window.onresize handler") +window.onresize = t.step_func(function() { + assert_unreached("This handler should be overwritten.") +}) + +var body = document.createElement("body") +body.onresize = t.step_func(function(e) { + assert_equals(e.currentTarget, window, + "The event should be fired at the window.") + t.done() +}) +window.dispatchEvent(new Event('resize')); + +t = async_test("document.onresize should set the document.onresize handler"); +document.onresize = t.step_func(function(e) { + assert_equals(e.currentTarget, document, + "The event should be fired at the document") + t.done() +}) +document.dispatchEvent(new Event('resize')); + +t = async_test("meta.onresize should set the meta.onresize handler"); +var meta = document.createElement("meta") +meta.onresize = t.step_func(function(e) { + assert_equals(e.currentTarget, meta, + "The event should be fired at the <meta> object") + t.done() +}) +meta.dispatchEvent(new Event('resize')); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/body-element-synthetic-errorevent.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/body-element-synthetic-errorevent.html new file mode 100644 index 0000000000..9ab0020ec3 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/body-element-synthetic-errorevent.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Event handlers processing algorithm: error events</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm"> + +<div id="log"></div> + +<script> +"use strict"; +setup({ allow_uncaught_exception: true }); + +promise_test(t => { + document.body.onerror = t.step_func((...args) => { + assert_greater_than(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, window, "error"); + const promise = eventWatcher.wait_for("error").then(e => { + assert_equals(e.defaultPrevented, true); + }); + + document.body.dispatchEvent(new ErrorEvent("error", { bubbles: true, cancelable: true })); + + return promise; +}, "error event is weird (return true cancels; many args) on Window, with a synthetic ErrorEvent"); + +promise_test(t => { + const theError = { the: "error object" }; + + document.body.onerror = t.step_func(function (message, filename, lineno, colno, error) { + assert_equals(arguments.length, 5, "There must be exactly 5 arguments"); + assert_equals(message, "message"); + assert_equals(filename, "filename"); + assert_equals(lineno, 1); + assert_equals(colno, 2); + assert_equals(error, theError); + return true; + }); + + const eventWatcher = new EventWatcher(t, window, "error"); + const promise = eventWatcher.wait_for("error"); + + document.body.dispatchEvent(new ErrorEvent("error", { + bubbles: true, + message: "message", + filename: "filename", + lineno: 1, + colno: 2, + error: theError + })); + + return promise; +}, "error event has the right 5 args on Window, with a synthetic ErrorEvent"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/body-element-synthetic-event.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/body-element-synthetic-event.html new file mode 100644 index 0000000000..9ed2638416 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/body-element-synthetic-event.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Event handlers processing algorithm: error events</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm"> + +<div id="log"></div> + +<script> +"use strict"; +setup({ allow_uncaught_exception: true }); + +promise_test(t => { + document.body.onerror = t.step_func((...args) => { + assert_equals(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, window, "error"); + const promise = eventWatcher.wait_for("error").then(e => { + assert_equals(e.defaultPrevented, false); + }); + + document.body.dispatchEvent(new Event("error", { bubbles: true, cancelable: true })); + + return promise; +}, "error event is normal (return true does not cancel; one arg) on Window, with a synthetic Event"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/document-synthetic-errorevent.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/document-synthetic-errorevent.html new file mode 100644 index 0000000000..4165beaf63 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/document-synthetic-errorevent.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Event handlers processing algorithm: error events</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<div id="log"></div> + +<script> +"use strict"; +setup({ allow_uncaught_exception: true }); + +promise_test(t => { + document.onerror = t.step_func((...args) => { + assert_equals(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, document, "error"); + const promise = eventWatcher.wait_for("error").then(e => { + assert_equals(e.defaultPrevented, false); + assert_equals(e.message, ""); + assert_equals(e.filename, ""); + assert_equals(e.lineno, 0); + assert_equals(e.colno, 0); + assert_equals(e.error, undefined); + }); + + document.dispatchEvent(new ErrorEvent("error", { cancelable: true })); + + return promise; +}, "error event is normal (return true does not cancel; one arg) on Document, with a synthetic ErrorEvent"); + +test(() => { + const e = new ErrorEvent("error"); + assert_equals(e.message, ""); + assert_equals(e.filename, ""); + assert_equals(e.lineno, 0); + assert_equals(e.colno, 0); + assert_equals(e.error, undefined); +}, "Initial values of ErrorEvent members") + +test(() => { + const e = new ErrorEvent("error", {error : null}); + assert_equals(e.error, null); +}, "error member can be set to null") + +test(() => { + const e = new ErrorEvent("error", {error : undefined}); + assert_equals(e.error, undefined); +}, "error member can be set to undefined") + +test(() => { + const e = new ErrorEvent("error", {error : "foo"}); + assert_equals(e.error, "foo"); +}, "error member can be set to arbitrary") + +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/document-synthetic-event.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/document-synthetic-event.html new file mode 100644 index 0000000000..6cf44e9d35 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/document-synthetic-event.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Event handlers processing algorithm: error events</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<div id="log"></div> + +<script> +"use strict"; +setup({ allow_uncaught_exception: true }); + +promise_test(t => { + document.onerror = t.step_func((...args) => { + assert_equals(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, document, "error"); + const promise = eventWatcher.wait_for("error").then(e => { + assert_equals(e.defaultPrevented, false); + }); + + document.dispatchEvent(new Event("error", { cancelable: true })); + + return promise; +}, "error event is normal (return true does not cancel; one arg) on Document, with a synthetic Event"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/frameset-element-synthetic-errorevent.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/frameset-element-synthetic-errorevent.html new file mode 100644 index 0000000000..20d87dbacf --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/frameset-element-synthetic-errorevent.html @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Event handlers processing algorithm: error events</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm"> + +<iframe name="framesetWindow" src="resources/frameset-frame.html"></iframe> +<div id="log"></div> + +<script> +"use strict"; +setup({ allow_uncaught_exception: true }); + +window.onload = () => { + +const frameset = framesetWindow.document.querySelector("frameset"); + +promise_test(t => { + frameset.onerror = t.step_func((...args) => { + assert_greater_than(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, framesetWindow, "error"); + const promise = eventWatcher.wait_for("error").then(e => { + assert_equals(e.defaultPrevented, true); + }); + + frameset.dispatchEvent(new ErrorEvent("error", { bubbles: true, cancelable: true })); + + return promise; +}, "error event is weird (return true cancels; many args) on Window, with a synthetic ErrorEvent"); + +promise_test(t => { + const theError = { the: "error object" }; + + frameset.onerror = t.step_func(function (message, filename, lineno, colno, error) { + assert_equals(arguments.length, 5, "There must be exactly 5 arguments"); + assert_equals(message, "message"); + assert_equals(filename, "filename"); + assert_equals(lineno, 1); + assert_equals(colno, 2); + assert_equals(error, theError); + return true; + }); + + const eventWatcher = new EventWatcher(t, framesetWindow, "error"); + const promise = eventWatcher.wait_for("error"); + + frameset.dispatchEvent(new ErrorEvent("error", { + bubbles: true, + message: "message", + filename: "filename", + lineno: 1, + colno: 2, + error: theError + })); + + return promise; +}, "error event has the right 5 args on Window, with a synthetic ErrorEvent"); + +}; +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/frameset-element-synthetic-event.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/frameset-element-synthetic-event.html new file mode 100644 index 0000000000..2fdca3ad86 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/frameset-element-synthetic-event.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Event handlers processing algorithm: error events</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm"> + +<iframe name="framesetWindow" src="resources/frameset-frame.html"></iframe> +<div id="log"></div> + +<script> +"use strict"; +setup({ allow_uncaught_exception: true }); + +window.onload = () => { + +const frameset = framesetWindow.document.querySelector("frameset"); + +promise_test(t => { + frameset.onerror = t.step_func((...args) => { + assert_equals(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, framesetWindow, "error"); + const promise = eventWatcher.wait_for("error").then(e => { + assert_equals(e.defaultPrevented, false); + }); + + frameset.dispatchEvent(new Event("error", { bubbles: true, cancelable: true })); + + return promise; +}, "error event is normal (return true does not cancel; one arg) on Window, with a synthetic Event"); + +}; +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/frameset-frame.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/frameset-frame.html new file mode 100644 index 0000000000..028be4919e --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/frameset-frame.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<meta charset="utf-8"> + +<frameset></frameset> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/no-op-worker.js b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/no-op-worker.js new file mode 100644 index 0000000000..3918c74e44 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/no-op-worker.js @@ -0,0 +1 @@ +"use strict"; diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/worker-with-syntax-error.js b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/worker-with-syntax-error.js new file mode 100644 index 0000000000..dc9a0dbf4a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/resources/worker-with-syntax-error.js @@ -0,0 +1 @@ +< 3; diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/script-element.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/script-element.html new file mode 100644 index 0000000000..f3ef1165e0 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/script-element.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Event handlers processing algorithm: error events</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<div id="log"></div> + +<script> +"use strict"; + +promise_test(t => { + const script = document.createElement("script"); + script.onerror = t.step_func((...args) => { + assert_equals(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, script, "error"); + const promise = eventWatcher.wait_for("error").then(e => { + assert_equals(e.constructor, Event); // not ErrorEvent + assert_equals(e.defaultPrevented, false); + }); + + script.src = "404.js"; + document.body.appendChild(script); + + return promise; +}, "error event behaves normally (return true does not cancel; one arg) on a script element, with a 404 error"); + +promise_test(t => { + const script = document.createElement("script"); + script.onerror = t.step_func((...args) => { + assert_equals(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, script, "error"); + const promise = eventWatcher.wait_for("error").then(e => { + assert_equals(e.defaultPrevented, false); + }); + + script.dispatchEvent(new Event("error", { cancelable: true })); + + return promise; +}, "error event behaves normally (return true does not cancel; one arg) on a script element, with a synthetic Event"); + +promise_test(t => { + const script = document.createElement("script"); + script.onerror = t.step_func((...args) => { + assert_equals(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, script, "error"); + const promise = eventWatcher.wait_for("error").then(e => { + assert_equals(e.defaultPrevented, false); + }); + + script.dispatchEvent(new ErrorEvent("error", { cancelable: true })); + + return promise; +}, "error event behaves normally (return true does not cancel; one arg) on a script element, with a synthetic ErrorEvent"); + +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/synthetic-errorevent-click.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/synthetic-errorevent-click.html new file mode 100644 index 0000000000..75a1772485 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/synthetic-errorevent-click.html @@ -0,0 +1,78 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Event handlers processing algorithm: click events using ErrorEvent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<div id="log"></div> + +<script> +"use strict"; +promise_test(t => { + document.onclick = t.step_func((...args) => { + assert_equals(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, document, "click"); + const promise = eventWatcher.wait_for("click").then(e => { + assert_equals(e.defaultPrevented, false); + }); + + document.dispatchEvent(new ErrorEvent("click", { cancelable: true })); + + return promise; +}, "click event is normal (return true does not cancel; one arg) on Document, with a synthetic ErrorEvent"); + +promise_test(t => { + window.onclick = t.step_func((...args) => { + assert_equals(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, window, "click"); + const promise = eventWatcher.wait_for("click").then(e => { + assert_equals(e.defaultPrevented, false); + }); + + window.dispatchEvent(new ErrorEvent("click", { cancelable: true })); + + return promise; +}, "click event is normal (return true does not cancel; one arg) on Window, with a synthetic ErrorEvent"); + +promise_test(t => { + const el = document.createElement("script"); + el.onclick = t.step_func((...args) => { + assert_equals(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, el, "click"); + const promise = eventWatcher.wait_for("click").then(e => { + assert_equals(e.defaultPrevented, false); + }); + + el.dispatchEvent(new ErrorEvent("click", { cancelable: true })); + + return promise; +}, "click event is normal (return true does not cancel; one arg) on a script element, with a synthetic ErrorEvent"); + +promise_test(t => { + const worker = new Worker("resources/no-op-worker.js"); + worker.onerror = t.step_func((...args) => { + assert_equals(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, worker, "click"); + const promise = eventWatcher.wait_for("click").then(e => { + assert_equals(e.defaultPrevented, false); + }); + + worker.dispatchEvent(new ErrorEvent("click", { cancelable: true })); + + return promise; +}, "click event is normal (return true does not cancel; one arg) on Worker, with a synthetic ErrorEvent"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/synthetic-errorevent-click.worker.js b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/synthetic-errorevent-click.worker.js new file mode 100644 index 0000000000..177a99e2ce --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/synthetic-errorevent-click.worker.js @@ -0,0 +1,22 @@ +"use strict"; +importScripts("/resources/testharness.js"); + +setup({ allow_uncaught_exception: true }); + +promise_test(t => { + self.onerror = t.step_func((...args) => { + assert_equals(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, self, "click"); + const promise = eventWatcher.wait_for("click").then(e => { + assert_equals(e.defaultPrevented, false); + }); + + self.dispatchEvent(new ErrorEvent("click", { cancelable: true })); + + return promise; +}, "error event is normal (return true does not cancel; one arg) on WorkerGlobalScope, with a synthetic ErrorEvent"); + +done(); diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-runtime-error.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-runtime-error.html new file mode 100644 index 0000000000..1b387ca81c --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-runtime-error.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Event handlers processing algorithm: error events</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<div id="log"></div> + +<script> +"use strict"; +setup({ allow_uncaught_exception: true }); + +promise_test(t => { + window.onerror = t.step_func((...args) => { + assert_greater_than(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, window, "error"); + const promise = eventWatcher.wait_for("error").then(e => { + assert_equals(e.defaultPrevented, true); + }); + + setTimeout(() => thisFunctionDoesNotExist(), 0); + + return promise; +}, "error event is weird (return true cancels; many args) on Window, with a runtime error"); + +promise_test(t => { + window.onerror = t.step_func(function (message, filename, lineno, colno, error) { + assert_equals(arguments.length, 5, "There must be exactly 5 arguments"); + assert_equals(typeof message, "string", "message argument must be a string"); + assert_equals(typeof filename, "string", "filename argument must be a string"); + assert_equals(typeof lineno, "number", "lineno argument must be a number"); + assert_equals(typeof colno, "number", "colno argument must be a number"); + assert_equals(typeof error, "object", "error argument must be an object"); + assert_equals(error.constructor, ReferenceError, "error argument must be a ReferenceError"); + return true; + }); + + setTimeout(() => thisFunctionDoesNotExist(), 0); + + const eventWatcher = new EventWatcher(t, window, "error"); + return eventWatcher.wait_for("error"); +}, "error event has the right 5 args on Window, with a runtime error"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-synthetic-errorevent.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-synthetic-errorevent.html new file mode 100644 index 0000000000..2d62d8a204 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-synthetic-errorevent.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Event handlers processing algorithm: error events</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<div id="log"></div> + +<script> +"use strict"; +setup({ allow_uncaught_exception: true }); + +promise_test(t => { + window.onerror = t.step_func((...args) => { + assert_greater_than(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, window, "error"); + const promise = eventWatcher.wait_for("error").then(e => { + assert_equals(e.defaultPrevented, true); + }); + + window.dispatchEvent(new ErrorEvent("error", { cancelable: true })); + + return promise; +}, "error event is weird (return true cancels; many args) on Window, with a synthetic ErrorEvent"); + +promise_test(t => { + const theError = { the: "error object" }; + + window.onerror = t.step_func(function (message, filename, lineno, colno, error) { + assert_equals(arguments.length, 5, "There must be exactly 5 arguments"); + assert_equals(message, "message"); + assert_equals(filename, "filename"); + assert_equals(lineno, 1); + assert_equals(colno, 2); + assert_equals(error, theError); + return true; + }); + + const eventWatcher = new EventWatcher(t, window, "error"); + const promise = eventWatcher.wait_for("error"); + + window.dispatchEvent(new ErrorEvent("error", { + message: "message", + filename: "filename", + lineno: 1, + colno: 2, + error: theError + })); + + return promise; +}, "error event has the right 5 args on Window, with a synthetic ErrorEvent"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-synthetic-event.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-synthetic-event.html new file mode 100644 index 0000000000..0bcc7defb7 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/window-synthetic-event.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Event handlers processing algorithm: error events</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<div id="log"></div> + +<script> +"use strict"; +setup({ allow_uncaught_exception: true }); + +promise_test(t => { + window.onerror = t.step_func((...args) => { + assert_equals(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, window, "error"); + const promise = eventWatcher.wait_for("error").then(e => { + assert_equals(e.defaultPrevented, false); + }); + + window.dispatchEvent(new Event("error", { cancelable: true })); + + return promise; +}, "error event is normal (return true does not cancel; one arg) on Window, with a synthetic Event"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/worker.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/worker.html new file mode 100644 index 0000000000..a8c0d97ce2 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/worker.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Event handlers processing algorithm: error events</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<div id="log"></div> + +<script> +"use strict"; +setup({ allow_uncaught_exception: true }); + +promise_test(t => { + const worker = new Worker("resources/worker-with-syntax-error.js"); + worker.onerror = t.step_func((...args) => { + assert_equals(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, worker, "error"); + const promise = eventWatcher.wait_for("error").then(e => { + assert_equals(e.defaultPrevented, false); + }); + + return promise; +}, "error event is normal (return true does not cancel; one arg) on Worker, with a syntax error in the worker code"); + +promise_test(t => { + const worker = new Worker("resources/no-op-worker.js"); + worker.onerror = t.step_func((...args) => { + assert_equals(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, worker, "error"); + const promise = eventWatcher.wait_for("error").then(e => { + assert_equals(e.defaultPrevented, false); + }); + + worker.dispatchEvent(new Event("error", { cancelable: true })); + + return promise; +}, "error event is normal (return true does not cancel; one arg) on Worker, with a synthetic Event"); + +promise_test(t => { + const worker = new Worker("resources/no-op-worker.js"); + worker.onerror = t.step_func((...args) => { + assert_equals(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, worker, "error"); + const promise = eventWatcher.wait_for("error").then(e => { + assert_equals(e.defaultPrevented, false); + }); + + worker.dispatchEvent(new ErrorEvent("error", { cancelable: true })); + + return promise; +}, "error event is normal (return true does not cancel; one arg) on Worker, with a synthetic ErrorEvent"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-runtime-error.worker.js b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-runtime-error.worker.js new file mode 100644 index 0000000000..264fef810d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-runtime-error.worker.js @@ -0,0 +1,40 @@ +"use strict"; +importScripts("/resources/testharness.js"); + +setup({ allow_uncaught_exception: true }); + +promise_test(t => { + self.onerror = t.step_func((...args) => { + assert_greater_than(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, self, "error"); + const promise = eventWatcher.wait_for("error").then(e => { + assert_equals(e.defaultPrevented, true); + }); + + setTimeout(() => thisFunctionDoesNotExist(), 0); + + return promise; +}, "error event is weird (return true cancels; many args) on WorkerGlobalScope, with a runtime error"); + +promise_test(t => { + self.onerror = t.step_func(function (message, filename, lineno, colno, error) { + assert_equals(arguments.length, 5, "There must be exactly 5 arguments"); + assert_equals(typeof message, "string", "message argument must be a string"); + assert_equals(typeof filename, "string", "filename argument must be a string"); + assert_equals(typeof lineno, "number", "lineno argument must be a number"); + assert_equals(typeof colno, "number", "colno argument must be a number"); + assert_equals(typeof error, "object", "error argument must be an object"); + assert_equals(error.constructor, ReferenceError, "error argument must be a ReferenceError"); + return true; + }); + + setTimeout(() => thisFunctionDoesNotExist(), 0); + + const eventWatcher = new EventWatcher(t, self, "error"); + return eventWatcher.wait_for("error"); +}, "error event has the right 5 args on WorkerGlobalScope, with a runtime error"); + +done(); diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-errorevent.worker.js b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-errorevent.worker.js new file mode 100644 index 0000000000..a14f6e01a9 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-errorevent.worker.js @@ -0,0 +1,49 @@ +"use strict"; +importScripts("/resources/testharness.js"); + +setup({ allow_uncaught_exception: true }); + +promise_test(t => { + self.onerror = t.step_func((...args) => { + assert_greater_than(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, self, "error"); + const promise = eventWatcher.wait_for("error").then(e => { + assert_equals(e.defaultPrevented, true); + }); + + self.dispatchEvent(new ErrorEvent("error", { cancelable: true })); + + return promise; +}, "error event is weird (return true cancels; many args) on WorkerGlobalScope, with a synthetic ErrorEvent"); + +promise_test(t => { + const theError = { the: "error object" }; + + self.onerror = t.step_func(function (message, filename, lineno, colno, error) { + assert_equals(arguments.length, 5, "There must be exactly 5 arguments"); + assert_equals(message, "message"); + assert_equals(filename, "filename"); + assert_equals(lineno, 1); + assert_equals(colno, 2); + assert_equals(error, theError); + return true; + }); + + const eventWatcher = new EventWatcher(t, self, "error"); + const promise = eventWatcher.wait_for("error"); + + self.dispatchEvent(new ErrorEvent("error", { + message: "message", + filename: "filename", + lineno: 1, + colno: 2, + error: theError + })); + + return promise; +}, "error event has the right 5 args on WorkerGlobalScope, with a synthetic ErrorEvent"); + +done(); diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-event.worker.js b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-event.worker.js new file mode 100644 index 0000000000..a3e16ded88 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-event.worker.js @@ -0,0 +1,22 @@ +"use strict"; +importScripts("/resources/testharness.js"); + +setup({ allow_uncaught_exception: true }); + +promise_test(t => { + self.onerror = t.step_func((...args) => { + assert_equals(args.length, 1); + return true; + }); + + const eventWatcher = new EventWatcher(t, self, "error"); + const promise = eventWatcher.wait_for("error").then(e => { + assert_equals(e.defaultPrevented, false); + }); + + self.dispatchEvent(new Event("error", { cancelable: true })); + + return promise; +}, "error event is normal (return true does not cancel; one arg) on WorkerGlobalScope, with a synthetic Event"); + +done(); diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-manual.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-manual.html new file mode 100644 index 0000000000..205e876c1d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm-manual.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Event handlers processing algorithm: manual tests</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-event-handler-processing-algorithm"> +<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me"> + +<style> + div[id^="d"] { + width: 100px; + height: 100px; + background-color: blue; + } +</style> + +<div id="log"></div> + +<p>Mouseover these four divs</p> + +<div id="d1"></div> +<div id="d2"></div> + +<div id="d3" onmouseover="return false"></div> +<div id="d4" onmouseover="return true"></div> + +<script> +"use strict"; +async_test(t => { + const div = document.querySelector("#d1"); + + div.onmouseover = t.step_func(() => false); + div.addEventListener("mouseover", t.step_func_done(e => { + assert_equals(e.defaultPrevented, true); + })); +}, "Listener added via JavaScript, returns false: cancels the event"); + +async_test(t => { + const div = document.querySelector("#d2"); + + div.onmouseover = t.step_func(() => true); + div.addEventListener("mouseover", t.step_func_done(e => { + assert_equals(e.defaultPrevented, false); + })); +}, "Listener added via JavaScript, returns true: does not cancel the event"); + +async_test(t => { + const div = document.querySelector("#d3"); + + div.addEventListener("mouseover", t.step_func_done(e => { + assert_equals(e.defaultPrevented, true); + })); +}, "Listener added via markup, returns false: cancels the event"); + +async_test(t => { + const div = document.querySelector("#d4"); + + div.addEventListener("mouseover", t.step_func_done(e => { + assert_equals(e.defaultPrevented, false); + })); +}, "Listener added via markup, returns true: does not cancel the event"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm.html new file mode 100644 index 0000000000..f5423d7ed4 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-processing-algorithm.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<title>Event handlers processing algorithm</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + <body> + <div id="foo" style="width: 100px; height: 100px; background-color: black"></div> + <script> + + // Historically mouseover was special in the spec, but now it is not. See https://github.com/whatwg/html/pull/2398. + test(function(t) { + var ev = new Event('mouseover', {cancelable: true}); + document.getElementById("foo").onmouseover = t.step_func(function() { return false }); + document.getElementById("foo").dispatchEvent(ev); + assert_equals(ev.defaultPrevented, true) + }, "mouseover listener returning false cancels event (using Event)"); + + test(function(t) { + var ev = new MouseEvent('mouseover', {cancelable: true}); + document.getElementById("foo").onmouseover = t.step_func(function() { return false }); + document.getElementById("foo").dispatchEvent(ev); + assert_equals(ev.defaultPrevented, true) + }, "mouseover listener returning false cancels event (using MouseEvent)"); + + test(function(t) { + var ev = new Event('mouseover', {cancelable: true}); + document.getElementById("foo").onmouseover = t.step_func(function() { return true }); + document.getElementById("foo").dispatchEvent(ev); + assert_equals(ev.defaultPrevented, false) + }, "mouseover listener returning true doesn't cancel event (using Event)"); + + test(function(t) { + var ev = new MouseEvent('mouseover', {cancelable: true}); + document.getElementById("foo").onmouseover = t.step_func(function() { return true }); + document.getElementById("foo").dispatchEvent(ev); + assert_equals(ev.defaultPrevented, false) + }, "mouseover listener returning true doesn't cancel event (using MouseEvent)"); + + // beforeunload is tested in html/browsers/browsing-the-web/unloading-documents/beforeunload-canceling.html + + test(function(t) { + var ev = new Event("click", {cancelable: true}); + document.getElementById("foo").onclick = t.step_func(function() { return false; }); + document.getElementById("foo").dispatchEvent(ev); + assert_equals(ev.defaultPrevented, true); + }, "click listener returning false cancels event"); + + test(function(t) { + var ev = new Event("blur", {cancelable: true}); + document.getElementById("foo").onblur = t.step_func(function() { return false; }); + document.getElementById("foo").dispatchEvent(ev); + assert_equals(ev.defaultPrevented, true); + }, "blur listener returning false cancels event"); + + test(function(t) { + var ev = new Event("dblclick", {cancelable: true}); + document.getElementById("foo").ondblclick = t.step_func(function() { return false; }); + document.getElementById("foo").dispatchEvent(ev); + assert_equals(ev.defaultPrevented, true); + }, "dblclick listener returning false cancels event"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-removal.window.js b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-removal.window.js new file mode 100644 index 0000000000..a20e2ec1d2 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-removal.window.js @@ -0,0 +1,76 @@ +let firstEventHandler; + +test(t => { + var i = 0; + firstEventHandler = t.unreached_func('First event handler.'); + var uncalled = "firstEventHandler();"; + var button = document.createElement('button'); + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 1) }), false); + button.setAttribute('onclick', uncalled); // event handler is activated here + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 2) }), false); + button.onclick = null; // but de-activated here + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 3) }), false); + button.onclick = t.step_func(() => { assert_equals(++i, 4); }); // and re-activated here + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 5) }), false); + button.click() + assert_equals(button.getAttribute("onclick"), uncalled) + assert_equals(i, 5); +}, "Event handler set through content attribute should be removed when they are set to null."); + +let happened = 0; +test(() => { + var script = "happened++;"; + var button = document.createElement('button'); + button.setAttribute('onclick', script); // event handler is activated here + button.onclick = null; // but de-activated here + assert_equals(button.getAttribute("onclick"), script) + button.setAttribute('onclick', script); // and re-activated here + button.click() + assert_equals(happened, 1); +}, "Event handler set through content attribute should be re-activated even if content is the same."); + +test(t => { + var i = 0; + firstEventHandler = t.unreached_func('First event handler.'); + var uncalled = "firstEventHandler();"; + var button = document.createElement('button'); + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 1) }), false); + button.setAttribute('onclick', uncalled); // event handler is activated here + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 2) }), false); + button.removeAttribute('onclick'); // but de-activated here + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 3) }), false); + button.onclick = t.step_func(() => { assert_equals(++i, 4); }); // and re-activated here + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 5) }), false); + button.click() + assert_equals(i, 5); +}, "Event handler set through content attribute should be deactivated when the content attribute is removed."); +test(t => { + var i = 0; + firstEventHandler = t.unreached_func('First event handler.'); + var uncalled = "firstEventHandler();"; + var button = document.createElement('button'); + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 1) }), false); + button.onclick = t.unreached_func('First event handler.'); // event handler is activated here + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 2) }), false); + button.onclick = null; // but de-activated here + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 3) }), false); + button.onclick = t.step_func(() => { assert_equals(++i, 4); }); // and re-activated here + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 5) }), false); + button.click() + assert_equals(i, 5); +}, "Event handler set through IDL should be deactivated when the IDL attribute is set to null."); +test(t => { + var i = 0; + firstEventHandler = t.unreached_func('First event handler.'); + var uncalled = "firstEventHandler();"; + var button = document.createElement('button'); + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 1) }), false); + button.onclick = t.unreached_func('First event handler.'); // event handler is activated here + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 3) }), false); + button.removeAttribute('onclick'); // and NOT de-activated here + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 4) }), false); + button.onclick = t.step_func(() => { assert_equals(++i, 2); }); + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 5) }), false); + button.click() + assert_equals(i, 5); +}, "Event handler set through IDL should NOT be deactivated when the content attribute is removed."); diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-sourcetext.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-sourcetext.html new file mode 100644 index 0000000000..57555faa7b --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-sourcetext.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test the sourceText of event handlers</title> +<link rel="help" href="https://github.com/whatwg/html/issues/5500"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script> +"use strict"; + +test(() => { + const el = document.createElement("div"); + el.setAttribute("onclick", "foo"); + assert_equals(el.onclick.toString(), "function onclick(event) {\nfoo\n}"); +}, "non-error event handler"); + +test(() => { + const el = document.createElement("div"); + el.setAttribute("onerror", "foo"); + assert_equals(el.onerror.toString(), "function onerror(event) {\nfoo\n}"); +}, "error event handler not on body"); + +test(() => { + const el = document.createElement("body"); + el.setAttribute("onerror", "foo"); + assert_equals(el.onerror.toString(), "function onerror(event, source, lineno, colno, error) {\nfoo\n}"); +}, "error event handler on disconnected body"); + +test(() => { + const el = document.createElement("frameset"); + el.setAttribute("onerror", "foo"); + assert_equals(el.onerror.toString(), "function onerror(event, source, lineno, colno, error) {\nfoo\n}"); +}, "error event handler on disconnected frameset"); + +test(() => { + document.body.setAttribute("onerror", "foo"); + assert_equals(window.onerror.toString(), "function onerror(event, source, lineno, colno, error) {\nfoo\n}"); +}, "error event handler on connected body, reflected to Window"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-spec-example.window.js b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-spec-example.window.js new file mode 100644 index 0000000000..abf46882aa --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-spec-example.window.js @@ -0,0 +1,55 @@ +var objects = [{}, function() {}, new Number(42), new String()]; +var primitives = [42, null, undefined, ""]; +var firstEventHandler; +objects.forEach(function(object) { + test(t => { + var i = 0; + firstEventHandler = t.unreached_func('First event handler.'); + var uncalled = "firstEventHandler();"; + var button = document.createElement('button'); + button.onclick = object; // event handler listener is registered here + assert_equals(button.onclick, object); + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 2) }), false); + button.setAttribute('onclick', uncalled); + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 3) }), false); + button.onclick = t.step_func(() => { assert_equals(++i, 1); }); + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 4) }), false); + button.click() + assert_equals(button.getAttribute("onclick"), uncalled) + assert_equals(i, 4); + }, "Event handler listeners should be registered when they are first set to an object value " + + "(" + format_value(object) + ")."); +}); +primitives.forEach(function(primitive) { + test(t => { + var i = 0; + firstEventHandler = t.unreached_func('First event handler.'); + var uncalled = "firstEventHandler();"; + var button = document.createElement('button'); + button.onclick = primitive; + assert_equals(button.onclick, null); + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 1) }), false); + button.setAttribute('onclick', uncalled); // event handler listener is registered here + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 3) }), false); + button.onclick = t.step_func(() => { assert_equals(++i, 2); }); + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 4) }), false); + button.click() + assert_equals(button.getAttribute("onclick"), uncalled) + assert_equals(i, 4); + }, "Event handler listeners should be registered when they are first set to an object value " + + "(" + format_value(primitive) + ")."); +}); +test(t => { + var i = 0; + firstEventHandler = t.unreached_func('First event handler.'); + var uncalled = "firstEventHandler();"; + var button = document.createElement('button'); + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 1) }), false); + button.setAttribute('onclick', uncalled); // event handler listener is registered here + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 3) }), false); + button.onclick = t.step_func(() => { assert_equals(++i, 2); }); + button.addEventListener('click', t.step_func(() => { assert_equals(++i, 4) }), false); + button.click() + assert_equals(button.getAttribute("onclick"), uncalled) + assert_equals(i, 4); +}, "Event handler listeners should be registered when they are first set to an object value."); diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/eventhandler-cancellation.html b/testing/web-platform/tests/html/webappapis/scripting/events/eventhandler-cancellation.html new file mode 100644 index 0000000000..6be581fa24 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/eventhandler-cancellation.html @@ -0,0 +1,76 @@ +<!doctype html> +<meta charset=utf-8> +<title></title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<!-- A window to work with that won't trigger the harness exception detection + when we fire "error" events at it --> +<iframe style="display: none"></iframe> +<script> + test(function() { + var blob = new Blob([""]); + // Most targets disabled for now until + // https://github.com/whatwg/html/issues/2296 is sorted out. + var targets = [ frames[0] /*, document, document.documentElement, + new Worker(URL.createObjectURL(blob) */ ]; + // Event constructors also mostly disabled until + // https://github.com/whatwg/html/issues/2296 is sorted out. + var eventCtors = [ /* Event, */ ErrorEvent /*, MouseEvent */ ]; + var values = [true, false, "", "abc", {}, 0, 1, -1, null, undefined, + 2.5, NaN, Infinity, Symbol.toStringTag ]; + // Event types also mostly disabled pending + // https://github.com/whatwg/html/issues/2296 + var eventTypes = [ "error"/*, "click", "load"*/ ]; + + // Variables that keep track of which subtest we're running. + var curTarget; + var curValue; + var curCtor; + var curType; + + function defaultPreventedTester(event) { + var expectedValue; + if (curTarget === frames[0] && + curCtor === ErrorEvent && + curValue === true && + curType == "error") { + expectedValue = true; + } else { + // This will need adjusting once we allow more targets and event + // constructors above! + expectedValue = false; + } + var valueRepr; + if (typeof curValue == "string") { + valueRepr = '"' + curValue + '"'; + } else { + valueRepr = String(curValue); + } + test(function() { + assert_equals(event.defaultPrevented, expectedValue); + }, "Returning " + valueRepr + + " from " + String(curTarget) + "'s on" + curType + + " event handler while " + curCtor.name + + " is firing should" + + (expectedValue ? "" : " not") + + " cancel the event"); + } + + for (curCtor of eventCtors) { + for (curTarget of targets) { + for (curType of eventTypes) { + for (curValue of values) { + // We have to make sure that defaultPreventedTester is added after + // our event handler. + curTarget["on" + curType] = function() { return curValue; } + curTarget.addEventListener(curType, defaultPreventedTester); + var e = new curCtor(curType, { cancelable: true }); + curTarget.dispatchEvent(e); + curTarget["on" + curType] = null; + curTarget.removeEventListener(curType, defaultPreventedTester); + } + } + } + } + }, "event handler cancellation behavior"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/inline-event-handler-ordering.html b/testing/web-platform/tests/html/webappapis/scripting/events/inline-event-handler-ordering.html new file mode 100644 index 0000000000..aae0f1abf8 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/inline-event-handler-ordering.html @@ -0,0 +1,53 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Inline event handlers retain their ordering even when invalid</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> +setup({ allow_uncaught_exception: true }); +var events = []; + +test(function() { + events = []; + var e = document.createElement("div"); + document.body.appendChild(e); + e.addEventListener("click", function() { events.push("ONE") }); + e.setAttribute("onclick", "window.open("); + e.addEventListener("click", function() { events.push("THREE") }); + // Try to compile the event handler. + e.onclick; + e.setAttribute("onclick", "events.push('TWO')"); + e.dispatchEvent(new Event("click")); + var expected_events = ["ONE", "TWO", "THREE"]; + assert_array_equals(events, expected_events); +}, "Inline event handlers retain their ordering when invalid and force-compiled"); + +test(function() { + events = []; + var e = document.createElement("div"); + document.body.appendChild(e); + e.addEventListener("click", function() { events.push("ONE") }); + e.setAttribute("onclick", "window.open("); + e.addEventListener("click", function() { events.push("THREE") }); + e.dispatchEvent(new Event("click")); + e.setAttribute("onclick", "events.push('TWO')"); + e.dispatchEvent(new Event("click")); + var expected_events = ["ONE", "THREE", "ONE", "TWO", "THREE"]; + assert_array_equals(events, expected_events); +}, "Inline event handlers retain their ordering when invalid and force-compiled via dispatch"); + +test(function() { + events = []; + var e = document.createElement("div"); + document.body.appendChild(e); + e.addEventListener("click", function() { events.push("ONE") }); + e.setAttribute("onclick", "window.open("); + e.addEventListener("click", function() { events.push("THREE") }); + e.setAttribute("onclick", "events.push('TWO')"); + e.dispatchEvent(new Event("click")); + var expected_events = ["ONE", "TWO", "THREE"]; + assert_array_equals(events, expected_events); +}, "Inline event handlers retain their ordering when invalid and lazy-compiled"); +</script> +</body> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-compiled-late.window.js b/testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-compiled-late.window.js new file mode 100644 index 0000000000..2892a4c3ab --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-compiled-late.window.js @@ -0,0 +1,16 @@ +setup({ allow_uncaught_exception: true }); + +test(function() { + var events = []; + window.onerror = function() { + events.push("error"); + }; + + var div = document.createElement("div"); + div.addEventListener("click", function (e) { events.push("click 1") }); + div.setAttribute("onclick", "}"); + div.addEventListener("click", function (e) { events.push("click 2") }); + div.dispatchEvent(new Event("click")); + assert_equals(div.onclick, null); + assert_array_equals(events, ["click 1", "error", "click 2"]); +}, "Invalid uncompiled raw handlers should only be compiled when about to call them"); diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-compiled-once.window.js b/testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-compiled-once.window.js new file mode 100644 index 0000000000..b39b54b0e9 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-compiled-once.window.js @@ -0,0 +1,14 @@ +setup({ allow_uncaught_exception: true }); + +var errors = 0; +window.onerror = function() { + errors++; +}; + +test(function() { + var e = document.body; + e.setAttribute("onclick", "window.open("); + assert_equals(e.onclick, null); + assert_equals(e.onclick, null); + assert_equals(errors, 1); +}, "Invalid uncompiled raw handlers should only be compiled once"); diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-keeps-position.window.js b/testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-keeps-position.window.js new file mode 100644 index 0000000000..f9443bf99a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/invalid-uncompiled-raw-handler-keeps-position.window.js @@ -0,0 +1,20 @@ +setup({ allow_uncaught_exception: true }); + +test(function() { + var events = []; + window.onerror = function() { + events.push("error"); + }; + + var div = document.createElement("div"); + div.addEventListener("click", function (e) { events.push("click 1"); }); + div.setAttribute("onclick", "}"); + div.addEventListener("click", function (e) { events.push("click 3"); }); + assert_equals(div.onclick, null); + assert_array_equals(events, ["error"]); + + events = []; + div.onclick = function (e) { events.push("click 2"); }; + div.dispatchEvent(new Event("click")); + assert_array_equals(events, ["click 1", "click 2", "click 3"]); +}, "Compiling invalid uncompiled raw handlers should keep the position in event listener list"); diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/messageevent-constructor.https.html b/testing/web-platform/tests/html/webappapis/scripting/events/messageevent-constructor.https.html new file mode 100644 index 0000000000..2d62019dbf --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/messageevent-constructor.https.html @@ -0,0 +1,114 @@ +<!DOCTYPE html> +<title>MessageEvent constructor</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> +test(function() { + assert_throws_js( + TypeError, + () => MessageEvent(""), + "Calling MessageEvent constructor without 'new' must throw" + ); +}, "MessageEvent constructor called as normal function"); + +test(function() { + var ev = new MessageEvent("test") + assert_equals(ev.type, "test", "type attribute") + assert_equals(ev.target, null, "target attribute") + assert_equals(ev.currentTarget, null, "currentTarget attribute") + assert_equals(ev.eventPhase, Event.NONE, "eventPhase attribute") + assert_equals(ev.bubbles, false, "bubbles attribute") + assert_equals(ev.cancelable, false, "cancelable attribute") + assert_equals(ev.defaultPrevented, false, "defaultPrevented attribute") + assert_equals(ev.isTrusted, false, "isTrusted attribute") + assert_true(ev.timeStamp > 0, "timeStamp attribute") + assert_true("initMessageEvent" in ev, "initMessageEvent operation") + assert_equals(ev.data, null, "data attribute") + assert_equals(ev.origin, "", "origin attribute") + assert_equals(ev.lastEventId, "", "lastEventId attribute") + assert_equals(ev.source, null, "source attribute") + assert_array_equals(ev.ports, [], "ports attribute") +}, "Default event values") + +test(function() { + var channel = new MessageChannel() + var ev = new MessageEvent("test", { data: "testData", origin: "testOrigin", lastEventId: "testId", source: window, ports: [channel.port1] }) + assert_equals(ev.type, "test", "type attribute") + assert_equals(ev.data, "testData", "data attribute") + assert_equals(ev.origin, "testOrigin", "origin attribute") + assert_equals(ev.lastEventId, "testId", "lastEventId attribute") + assert_equals(ev.source, window, "source attribute") + assert_array_equals(ev.ports, [channel.port1], "ports attribute") +}, "MessageEventInit dictionary") + +test(function() { + assert_throws_js(TypeError, function() { + new MessageEvent("test", { ports: null }) + }) +}, "Passing null for ports member") + +test(function() { + var ev = new MessageEvent("test", { ports: [] }) + assert_true(Array.isArray(ev.ports), "Array.isArray() should return true") + assert_true(Object.isFrozen(ev.ports), "Object.isFrozen() should return true") + assert_equals(ev.ports, ev.ports, "ev.ports should return the same object") + + const oldPorts = ev.ports; + ev.initMessageEvent("test", false, false, null, "", "", null, ev.ports); + assert_not_equals(oldPorts, ev.ports, "initMessageEvent() changes ev.ports"); +}, "ports attribute should be a FrozenArray") + +test(function() { + var ev = document.createEvent("messageevent"); + var channel = new MessageChannel() + ev.initMessageEvent("test", true, false, "testData", "testOrigin", "testId", window, [channel.port1]) + assert_equals(ev.type, "test", "type attribute") + assert_equals(ev.bubbles, true, "bubbles attribute") + assert_equals(ev.cancelable, false, "bubbles attribute") + assert_equals(ev.data, "testData", "data attribute") + assert_equals(ev.origin, "testOrigin", "origin attribute") + assert_equals(ev.lastEventId, "testId", "lastEventId attribute") + assert_equals(ev.source, window, "source attribute") + assert_array_equals(ev.ports, [channel.port1], "ports attribute") +}, "initMessageEvent operation") + +test(function() { + var ev = document.createEvent("messageevent") + assert_throws_js(TypeError, function() { + ev.initMessageEvent("test", true, false, "testData", "testOrigin", "testId", window, null) + }) +}, "Passing null for ports parameter to initMessageEvent") + +test(function() { + var ev = document.createEvent("messageevent") + assert_equals(MessageEvent.prototype.initMessageEvent.length, 1, "MessageEvent.prototype.initMessageEvent.length should be 1") + ev.initMessageEvent("test") + assert_equals(ev.type, "test", "type attribute") + assert_equals(ev.bubbles, false, "bubbles attribute") + assert_equals(ev.cancelable, false, "bubbles attribute") + assert_equals(ev.data, null, "data attribute") + assert_equals(ev.origin, "", "origin attribute") + assert_equals(ev.lastEventId, "", "lastEventId attribute") + assert_equals(ev.source, null, "source attribute") + assert_array_equals(ev.ports, [], "ports attribute") +}, "initMessageEvent operation default parameter values") + +promise_test(function(t) { + var worker_url = "/service-workers/service-worker/resources/empty-worker.js"; + var scope = "/service-workers/service-worker/resources/"; + var registration; + + return service_worker_unregister_and_register(t, worker_url, scope) + .then(function(r) { + registration = r; + return wait_for_state(t, r.installing, "activated"); + }) + .then(function() { + var ev = new MessageEvent("test", { source: registration.active }); + assert_equals(ev.source, registration.active, "source attribute should return the ServiceWorker"); + service_worker_unregister(t, scope); + }); + }, "Passing ServiceWorker for source member"); + +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/onerroreventhandler-frame.html b/testing/web-platform/tests/html/webappapis/scripting/events/onerroreventhandler-frame.html new file mode 100644 index 0000000000..79e4af3020 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/onerroreventhandler-frame.html @@ -0,0 +1,56 @@ +<body></body> +<script> +function check1(args, callee) { + parent.t.step(function() { + parent.assert_equals(callee.length, 5); + parent.assert_equals(args.length, 5); + parent.assert_equals(args[0], reference_error.message); + parent.assert_equals(args[1], reference_error.filename); + parent.assert_equals(args[2], reference_error.lineno); + parent.assert_equals(args[3], reference_error.colno); + parent.assert_equals(args[4], reference_error.error); + parent.t.done(); + }); +} + +var reference_error = new ErrorEvent("error", { + filename: "error_file.js", + lineno: 333, + colno: 999, + message: "there was an error", + error: {nondefault: 'some unusual object'}, +}); + +parent.t.step(function() { + document.body.outerHTML = "<body onerror='check1(arguments, arguments.callee)'></body>" + window.dispatchEvent(reference_error); +}); + +function check2(args, callee) { + parent.t2.step(function() { + parent.assert_equals(callee.length, 5); + parent.assert_equals(args.length, 1); + parent.assert_false(args[0] instanceof ErrorEvent); + parent.t2.done() + }); +} + +parent.t2.step(function() { + document.body.outerHTML = "<body onerror='check2(arguments, arguments.callee)'></body>" + window.dispatchEvent(new Event("error")); +}); + +function check3(args, callee) { + parent.t3.step(function() { + parent.assert_equals(args.length, 1); + parent.assert_equals(callee.length, 1); + }); +} + +parent.t3.step(function() { + document.body.outerHTML = "<body><span onerror='check3(arguments, arguments.callee)'></span></body>" + document.body.firstChild.dispatchEvent(reference_error); + document.body.firstChild.dispatchEvent(new Event("error")); + parent.t3.done(); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/onerroreventhandler.html b/testing/web-platform/tests/html/webappapis/scripting/events/onerroreventhandler.html new file mode 100644 index 0000000000..60fc674d57 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/onerroreventhandler.html @@ -0,0 +1,11 @@ +<!doctype html> +<meta charset=utf-8> +<title>OnErrorEventHandler + ErrorEvent is treated differently</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script> +var t = async_test("onerror + ErrorEvent + Window"); +var t2 = async_test("onerror + !ErrorEvent + Window"); +var t3 = async_test("onerror + Document"); +</script> +<iframe src="onerroreventhandler-frame.html"></iframe> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/resources/compiled-event-handler-settings-objects-support.html b/testing/web-platform/tests/html/webappapis/scripting/events/resources/compiled-event-handler-settings-objects-support.html new file mode 100644 index 0000000000..d40c0b9cce --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/resources/compiled-event-handler-settings-objects-support.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>This will be in an iframe</title> + +<script> +window.name = "iframe"; +</script> + +<body onbeforeunload="return { toString: parent.postMessage.bind(parent, 'PASS', '*') };"> + +<!-- window.open() uses the entry settings object to determine how the URL will be parsed --> +<button onclick="var w = window.open('open-window.html'); w.onload = () => { parent.onWindowLoaded(w.document.URL); };">This will be clicked</button> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/resources/event-handler-body.js b/testing/web-platform/tests/html/webappapis/scripting/events/resources/event-handler-body.js new file mode 100644 index 0000000000..d7889e230e --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/resources/event-handler-body.js @@ -0,0 +1,61 @@ +const windowReflectingBodyElementEventHandlerSet = + new Set(['blur', 'error', 'focus', 'load', 'resize', 'scroll']); + +function handlersInInterface(mainIDL, name) { + return mainIDL.find(idl => idl.name === name).members.map(member => member.name.slice(2)); +} + +const handlersListPromise = fetch("/interfaces/html.idl").then(res => res.text()).then(htmlIDL => { + const parsedHTMLIDL = WebIDL2.parse(htmlIDL); + const windowEventHandlers = handlersInInterface(parsedHTMLIDL, "WindowEventHandlers"); + const globalEventHandlers = handlersInInterface(parsedHTMLIDL, "GlobalEventHandlers"); + + const shadowedHandlers = [ + ...windowReflectingBodyElementEventHandlerSet, + ...windowEventHandlers + ]; + const notShadowedHandlers = globalEventHandlers.filter(name => !windowReflectingBodyElementEventHandlerSet.has(name)); + return { + shadowedHandlers, + notShadowedHandlers + }; +}); + +function eventHandlerTest(shadowedHandlers, notShadowedHandlers, element) { + const altBody = document.createElement(element); + for (const [des, obj1, obj2, obj3, des1, des2, des3] of [ + ["document.body", document.body, altBody, window, "body", "alternative body", "window"], + [`document.createElement("${element}")`, altBody, document.body, window, "alternative body", "body", "window"], + ["window", window, document.body, altBody, "window", "body", "alternative body"] + ]) { + const f = () => 0; + + shadowedHandlers.forEach(handler => { + const eventHandler = obj1['on' + handler]; + test(() => { + obj1['on' + handler] = f; + assert_equals(obj2['on' + handler], f, `${des2} should reflect`); + assert_equals(obj3['on' + handler], f, `${des3} should reflect`); + }, `shadowed ${handler} (${des})`); + obj1['on' + handler] = eventHandler; + }); + + notShadowedHandlers.forEach(handler => { + const eventHandler = obj1['on' + handler]; + test(() => { + obj1['on' + handler] = f; + assert_equals(obj2['on' + handler], null, `${des2} should reflect`); + assert_equals(obj3['on' + handler], null, `${des3} should reflect`); + }, `not shadowed ${handler} (${des})`); + obj1['on' + handler] = eventHandler; + }); + + shadowedHandlers.forEach(handler => { + test(() => { + assert_equals(obj1['on' + handler], null, `${des1} should reflect changes to itself`); + assert_equals(obj2['on' + handler], null, `${des2} should reflect`); + assert_equals(obj3['on' + handler], null, `${des3} should reflect`); + }, `shadowed ${handler} removal (${des})`); + }); + } +} diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/resources/open-window.html b/testing/web-platform/tests/html/webappapis/scripting/events/resources/open-window.html new file mode 100644 index 0000000000..1d23263570 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/resources/open-window.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>This window will open during the course of the test</title> +<h1>Hello</h1> diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/uncompiled_event_handler_with_scripting_disabled.html b/testing/web-platform/tests/html/webappapis/scripting/events/uncompiled_event_handler_with_scripting_disabled.html new file mode 100644 index 0000000000..a912b32d7f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/uncompiled_event_handler_with_scripting_disabled.html @@ -0,0 +1,21 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Uncompiled event handler check that scripting is enabled</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + setup({ allow_uncaught_exception: true }); + test(function() { + var invoked = false; + window.addEventListener("error", function() { + invoked = true; + }); + + // Make sure that `this_will_error` will in fact error when it's referenced + assert_equals(typeof this_will_error, "undefined"); + var dom = (new DOMParser()).parseFromString("<div id=\"has-event-handler\" onclick=\"this_will_error;\"></div>", "text/html"); + var click = new MouseEvent("click"); + dom.getElementById("has-event-handler").dispatchEvent(click); + assert_equals(invoked, false); + }, "when scripting is disabled, the handler is never compiled"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/addEventListener.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/addEventListener.html new file mode 100644 index 0000000000..dbb1cdd5a9 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/addEventListener.html @@ -0,0 +1,32 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - addEventListener</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var ran = false; + window.addEventListener('error', t.step_func(function(e){ + ran = true; + assert_true(e.isTrusted, 'isTrusted'); + }), false); + </script> + <script> + undefined_variable; + </script> + <script> + for (;) {} + </script> + <script> + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-compile-error-data-url.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-compile-error-data-url.html new file mode 100644 index 0000000000..66e1dfed4d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-compile-error-data-url.html @@ -0,0 +1,37 @@ +<!doctype html> +<html> + <head> + <title><body onerror> - compile error in <script src=data:...></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + </script> + <body onerror=" + t.step(function(){ + ran = true; + assert_equals(typeof event, 'string', 'first arg'); + assert_equals(source, 'data:text/javascript,for(;){}', 'second arg'); + assert_equals(typeof lineno, 'number', 'third arg'); + }); + t_col.step(function() { + assert_equals(typeof colno, 'number', 'fourth arg'); + }); + "> + <div id=log></div> + <script src="data:text/javascript,for(;){}"></script> + <script> + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + t_col.done(); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-compile-error.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-compile-error.html new file mode 100644 index 0000000000..0f65f73999 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-compile-error.html @@ -0,0 +1,39 @@ +<!doctype html> +<html> + <head> + <title><body onerror> - compile error in <script></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + </script> + <body onerror=" + t.step(function(){ + ran = true; + assert_equals(typeof event, 'string', 'first arg'); + assert_equals(source, location.href, 'second arg'); + assert_equals(typeof lineno, 'number', 'third arg'); + }); + t_col.step(function() { + assert_equals(typeof colno, 'number', 'fourth arg'); + }); + "> + <div id=log></div> + <script> + for(;) {} + </script> + <script> + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + t_col.done(); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-runtime-error.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-runtime-error.html new file mode 100644 index 0000000000..faaddd9ed9 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/body-onerror-runtime-error.html @@ -0,0 +1,39 @@ +<!doctype html> +<html> + <head> + <title><body onerror> - runtime error in <script></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + </script> + <body onerror=" + t.step(function(){ + ran = true; + assert_equals(typeof event, 'string', 'first arg'); + assert_equals(source, location.href, 'second arg'); + assert_equals(typeof lineno, 'number', 'third arg'); + }); + t_col.step(function(){ + assert_equals(typeof colno, 'number', 'fourth arg'); + }); + "> + <div id=log></div> + <script> + undefined_variable; + </script> + <script> + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + t_col.done(); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin-setInterval.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin-setInterval.html new file mode 100644 index 0000000000..c4028e650b --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin-setInterval.html @@ -0,0 +1,25 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - compile error in cross-origin setInterval</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var ran = false; + var interval; + window.addEventListener('error', t.step_func(e => { + clearInterval(interval); + ran = true; + assert_equals(e.error.constructor, SyntaxError); + })); + var script = document.createElement('script'); + script.src = location.href.replace('://', '://www1.').replace(/\/[^\/]+$/, '/support/syntax-error-in-setInterval.js'); + document.body.appendChild(script); + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin-setTimeout.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin-setTimeout.html new file mode 100644 index 0000000000..1eebf82fbb --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin-setTimeout.html @@ -0,0 +1,23 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - compile error in cross-origin setTimeout</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var ran = false; + window.addEventListener('error', t.step_func(e => { + ran = true; + assert_equals(e.error.constructor, SyntaxError); + })); + var script = document.createElement('script'); + script.src = location.href.replace('://', '://www1.').replace(/\/[^\/]+$/, '/support/syntax-error-in-setTimeout.js'); + document.body.appendChild(script); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin.html new file mode 100644 index 0000000000..b7e989529f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-cross-origin.html @@ -0,0 +1,38 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - compile error in <script src=//www1...></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + var col_value; + window.onerror = t.step_func(function(a, b, c, d){ + ran = true; + col_value = d; + assert_equals(a, 'Script error.', 'first arg'); + assert_equals(b, '', 'second arg'); + assert_equals(c, 0, 'third arg'); + }); + var script = document.createElement('script'); + script.src = location.href.replace('://', '://www1.').replace(/\/[^\/]+$/, '/support/syntax-error.js'); + document.body.appendChild(script); + onload = function(){ + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + assert_equals(col_value, 0, 'fourth arg'); + t_col.done(); + }); + }; + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-data-url.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-data-url.html new file mode 100644 index 0000000000..08ce2f348f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-data-url.html @@ -0,0 +1,36 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - compile error in <script src=data:...></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + var col_value; + window.onerror = t.step_func(function(a, b, c, d){ + ran = true; + col_value = d; + assert_equals(typeof a, 'string', 'first arg'); + assert_equals(b, 'data:text/javascript,for(;){}', 'second arg'); + assert_equals(typeof c, 'number', 'third arg'); + }); + </script> + <script src="data:text/javascript,for(;){}"></script> + <script> + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + assert_equals(typeof col_value, 'number', 'fourth arg'); + t_col.done(); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-attribute.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-attribute.html new file mode 100644 index 0000000000..864d09fc1e --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-attribute.html @@ -0,0 +1,39 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - compile error in attribute</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + var col_value; + window.onerror = t.step_func(function(a, b, c, d){ + ran = true; + col_value = d; + assert_equals(typeof a, 'string', 'first arg'); + assert_equals(b, location.href, 'second arg'); + assert_equals(typeof c, 'number', 'third arg'); + }); + </script> + <p onclick="{"></p> + <script> + t.step(function(){ + var ev = document.createEvent('Event'); + ev.initEvent('click', false, false); + document.querySelector('p').dispatchEvent(ev); + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + assert_equals(typeof col_value, 'number', 'fourth arg'); + t_col.done(); + }); + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-body-onerror.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-body-onerror.html new file mode 100644 index 0000000000..0b094e71c3 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-body-onerror.html @@ -0,0 +1,28 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - compile error in <body onerror></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var ran = false; + window.onerror = t.step_func(function(){ + ran = true; + }); + </script> + </head> + <body onerror="{"><!-- sets the event handler to null before compiling --> + <div id=log></div> + <script> + for(;) {} + </script> + <script> + t.step(function(){ + assert_false(ran, 'ran'); + t.done(); + }); + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-setInterval.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-setInterval.html new file mode 100644 index 0000000000..79ca7d524a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-setInterval.html @@ -0,0 +1,39 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - compile error in setInterval</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + var col_value; + var interval; + window.onerror = t.step_func(function(a, b, c, d){ + clearInterval(interval); + ran = true; + col_value = d; + assert_equals(typeof a, 'string', 'first arg'); + assert_equals(b, location.href, 'second arg'); + assert_equals(typeof c, 'number', 'third arg'); + }); + interval = setInterval("{", 10); + step_timeout(function(){ + t.step(function(){ + clearInterval(interval); + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + assert_equals(typeof col_value, 'number', 'fourth arg'); + t_col.done(); + }); + }, 20); + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-setTimeout.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-setTimeout.html new file mode 100644 index 0000000000..1bb730e134 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-in-setTimeout.html @@ -0,0 +1,36 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - compile error in setTimeout</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + var col_value; + window.onerror = t.step_func(function(a, b, c, d){ + ran = true; + col_value = d; + assert_equals(typeof a, 'string', 'first arg'); + assert_equals(b, location.href, 'second arg'); + assert_equals(typeof c, 'number', 'third arg'); + }); + setTimeout("{", 10); + setTimeout(function(){ + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + assert_equals(typeof col_value, 'number', 'fourth arg'); + t_col.done(); + }); + }, 20); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-same-origin-with-hash.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-same-origin-with-hash.html new file mode 100644 index 0000000000..c367e6cb2f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-same-origin-with-hash.html @@ -0,0 +1,36 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - compile error in <script src=...> with hash</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + var col_value; + window.onerror = t.step_func(function(a, b, c, d){ + ran = true; + col_value = d; + assert_equals(typeof a, 'string', 'first arg'); + assert_equals(b, document.querySelector('script[src="support/syntax-error.js#"]').src, 'second arg'); + assert_equals(typeof c, 'number', 'third arg'); + }); + </script> + <script src="support/syntax-error.js#"></script> + <script> + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + assert_equals(typeof col_value, 'number', 'fourth arg'); + t_col.done(); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-same-origin.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-same-origin.html new file mode 100644 index 0000000000..71c28b584d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error-same-origin.html @@ -0,0 +1,36 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - compile error in <script src=...></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + var col_value; + window.onerror = t.step_func(function(a, b, c, d){ + ran = true; + col_value = d; + assert_equals(typeof a, 'string', 'first arg'); + assert_equals(b, document.querySelector('script[src="support/syntax-error.js"]').src, 'second arg'); + assert_equals(typeof c, 'number', 'third arg'); + }); + </script> + <script src="support/syntax-error.js"></script> + <script> + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + assert_equals(typeof col_value, 'number', 'fourth arg'); + t_col.done(); + }); + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error.html new file mode 100644 index 0000000000..a4bdfd9c47 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/compile-error.html @@ -0,0 +1,38 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - compile error in <script></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + var col_value; + window.onerror = t.step_func(function(a, b, c, d){ + ran = true; + col_value = d; + assert_equals(typeof a, 'string', 'first arg'); + assert_equals(b, location.href, 'second arg'); + assert_equals(typeof c, 'number', 'third arg'); + }); + </script> + <script> + for(;) {} + </script> + <script> + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + assert_equals(typeof col_value, 'number', 'fourth arg'); + t_col.done(); + }); + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/atomics-wait-async.https.any.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/atomics-wait-async.https.any.js new file mode 100644 index 0000000000..3a3ea40d77 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/atomics-wait-async.https.any.js @@ -0,0 +1,25 @@ +// META: global=window,dedicatedworker + +promise_test(async () => { + const sab = new SharedArrayBuffer(64); + const ta = new Int32Array(sab); + + const waitAsyncObj = Atomics.waitAsync(ta, 0, 0, 10); + assert_equals(waitAsyncObj.async, true); + const v = await waitAsyncObj.value; + assert_equals(v, "timed-out"); +}, `Atomics.waitAsync timeout in a ${self.constructor.name}`); + +promise_test(async () => { + const sab = new SharedArrayBuffer(64); + const ta = new Int32Array(sab); + + const waitAsyncObj = Atomics.waitAsync(ta, 0, 0); + assert_equals(waitAsyncObj.async, true); + + const worker = new Worker("resources/notify-worker.js"); + worker.postMessage(sab); + + const v = await waitAsyncObj.value; + assert_equals(v, "ok"); +}, `Atomics.waitAsync notification in a ${self.constructor.name}`); diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/atomics-wait-async.https.any.js.headers b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/atomics-wait-async.https.any.js.headers new file mode 100644 index 0000000000..5f8621ef83 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/atomics-wait-async.https.any.js.headers @@ -0,0 +1,2 @@ +Cross-Origin-Embedder-Policy: require-corp +Cross-Origin-Opener-Policy: same-origin diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/requires-failure.https.any.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/requires-failure.https.any.js new file mode 100644 index 0000000000..fddf85dbed --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/requires-failure.https.any.js @@ -0,0 +1,11 @@ +// META: global=window,serviceworker + +test(() => { + // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()` + const sab = new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer; + const ta = new Int32Array(sab); + + assert_throws_js(TypeError, () => { + Atomics.wait(ta, 0, 0, 10); + }); +}, `[[CanBlock]] in a ${self.constructor.name}`); diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/requires-success.any.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/requires-success.any.js new file mode 100644 index 0000000000..0da449a7cf --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/requires-success.any.js @@ -0,0 +1,9 @@ +// META: global=dedicatedworker,sharedworker + +test(() => { + // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()` + const sab = new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer; + const ta = new Int32Array(sab); + + assert_equals(Atomics.wait(ta, 0, 0, 10), "timed-out"); +}, `[[CanBlock]] in a ${self.constructor.name}`); diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/resources/notify-worker.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/resources/notify-worker.js new file mode 100644 index 0000000000..2780e5bc34 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/resources/notify-worker.js @@ -0,0 +1,5 @@ +onmessage = (e) => { + const sab = e.data; + const ta = new Int32Array(sab); + Atomics.notify(ta, 0); +}; diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/resources/notify-worker.js.headers b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/resources/notify-worker.js.headers new file mode 100644 index 0000000000..6604450991 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-agent-formalism/resources/notify-worker.js.headers @@ -0,0 +1 @@ +Cross-Origin-Embedder-Policy: require-corp diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-entry-different-function-realm.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-entry-different-function-realm.html new file mode 100644 index 0000000000..71f03a4dcf --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-entry-different-function-realm.html @@ -0,0 +1,112 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Entry settings object for promise jobs when the function realm is different from the test realm</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<!-- https://github.com/whatwg/html/pull/5212 --> +<!-- https://github.com/whatwg/html/issues/1426 --> + +<!-- This is what would normally be considered the entry page. However, we use functions from the + resources/function/function.html realm. So window.open() should resolve relative to that realm + inside promise jobs. --> + +<iframe src="resources/promise-job-entry-incumbent.html"></iframe> +<iframe src="resources/function/function.html" id="function-frame"></iframe> + +<script> +setup({ explicit_done: true }); + +const relativeURL = "resources/window-to-open.html"; +const expectedURL = (new URL(relativeURL, document.querySelector("#function-frame").src)).href; + +const incumbentWindow = frames[0]; +const functionWindow = frames[1]; +const FunctionFromAnotherWindow = frames[1].Function; + +window.onload = () => { + async_test(t => { + const func = FunctionFromAnotherWindow(` + const [incumbentWindow, relativeURL, t, assert_equals, expectedURL] = arguments[0]; + + const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL); + w.onload = t.step_func_done(() => { + t.add_cleanup(() => w.close()); + assert_equals(w.location.href, expectedURL); + }); + `); + + Promise.resolve([incumbentWindow, relativeURL, t, assert_equals, expectedURL]).then(func); + }, "Fulfillment handler on fulfilled promise"); + + async_test(t => { + const func = FunctionFromAnotherWindow(` + const [incumbentWindow, relativeURL, t, assert_equals, expectedURL] = arguments[0]; + + const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL); + w.onload = t.step_func_done(() => { + t.add_cleanup(() => w.close()); + assert_equals(w.location.href, expectedURL); + }); + `); + + Promise.reject([incumbentWindow, relativeURL, t, assert_equals, expectedURL]).catch(func); + }, "Rejection handler on rejected promise"); + + async_test(t => { + let resolve; + const p = new Promise(r => { resolve = r; }); + + const func = FunctionFromAnotherWindow(` + const [incumbentWindow, relativeURL, t, assert_equals, expectedURL] = arguments[0]; + + const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL); + w.onload = t.step_func_done(() => { + t.add_cleanup(() => w.close()); + assert_equals(w.location.href, expectedURL); + }); + `); + + p.then(func); + t.step_timeout(() => resolve([incumbentWindow, relativeURL, t, assert_equals, expectedURL]), 0); + }, "Fulfillment handler on pending-then-fulfilled promise"); + + async_test(t => { + let reject; + const p = new Promise((_, r) => { reject = r; }); + + const func = FunctionFromAnotherWindow(` + const [incumbentWindow, relativeURL, t, assert_equals, expectedURL] = arguments[0]; + + const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL); + w.onload = t.step_func_done(() => { + t.add_cleanup(() => w.close()); + assert_equals(w.location.href, expectedURL); + }); + `); + + p.catch(func); + t.step_timeout(() => reject([incumbentWindow, relativeURL, t, assert_equals, expectedURL]), 0); + }, "Rejection handler on pending-then-rejected promise"); + + async_test(t => { + t.add_cleanup(() => { delete frames[1].args; }); + frames[1].args = [incumbentWindow, relativeURL, t, assert_equals, expectedURL]; + + const func = FunctionFromAnotherWindow(` + const [incumbentWindow, relativeURL, t, assert_equals, expectedURL] = window.args; + + const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL); + w.onload = t.step_func_done(() => { + t.add_cleanup(() => w.close()); + assert_equals(w.location.href, expectedURL); + }); + `); + + const thenable = { then: func }; + + Promise.resolve(thenable); + }, "Thenable resolution"); + + done(); +}; +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-entry.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-entry.html new file mode 100644 index 0000000000..6d075d674c --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-entry.html @@ -0,0 +1,101 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Entry settings object for promise jobs</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<!-- https://github.com/whatwg/html/pull/5212 --> +<!-- https://github.com/whatwg/html/issues/1426 --> + +<!-- This is the entry page, so window.open() should resolve relative to it, even inside promise jobs. --> + +<iframe src="resources/promise-job-entry-incumbent.html"></iframe> + +<script> +setup({ explicit_done: true }); + +const relativeURL = "resources/window-to-open.html"; +const expectedURL = (new URL(relativeURL, location.href)).href; + +const incumbentWindow = frames[0]; + +window.onload = () => { + async_test(t => { + const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL); + w.onload = t.step_func_done(() => { + t.add_cleanup(() => w.close()); + assert_equals(w.location.href, expectedURL); + }); + }, "Sanity check: this all works as expected with no promises involved"); + + async_test(t => { + // No t.step_func because that could change the realms + Promise.resolve().then(() => { + const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL); + w.onload = t.step_func_done(() => { + t.add_cleanup(() => w.close()); + assert_equals(w.location.href, expectedURL); + }); + }); + }, "Fulfillment handler on fulfilled promise"); + + async_test(t => { + // No t.step_func because that could change the realms + Promise.reject().catch(() => { + const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL); + w.onload = t.step_func_done(() => { + t.add_cleanup(() => w.close()); + assert_equals(w.location.href, expectedURL); + }); + }); + }, "Rejection handler on rejected promise"); + + async_test(t => { + let resolve; + const p = new Promise(r => { resolve = r; }); + + // No t.step_func because that could change the realms + p.then(() => { + const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL); + w.onload = t.step_func_done(() => { + t.add_cleanup(() => w.close()); + assert_equals(w.location.href, expectedURL); + }); + }); + + t.step_timeout(resolve, 0); + }, "Fulfillment handler on pending-then-fulfilled promise"); + + async_test(t => { + let reject; + const p = new Promise((_, r) => { reject = r; }); + + // No t.step_func because that could change the realms + p.catch(() => { + const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL); + w.onload = t.step_func_done(() => { + t.add_cleanup(() => w.close()); + assert_equals(w.location.href, expectedURL); + }); + }); + + t.step_timeout(reject, 0); + }, "Rejection handler on pending-then-rejected promise"); + + async_test(t => { + const thenable = { + // No t.step_func because that could change the realms + then(f) { + const w = incumbentWindow.runWindowOpenVeryIndirectly(relativeURL); + w.onload = t.step_func_done(() => { + t.add_cleanup(() => w.close()); + assert_equals(w.location.href, expectedURL); + }); + } + }; + + Promise.resolve(thenable); + }, "Thenable resolution"); + + done(); +}; +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-incumbent.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-incumbent.html new file mode 100644 index 0000000000..af00f834c1 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/promise-job-incumbent.html @@ -0,0 +1,164 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Incumbent settings object for promise jobs</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<!-- This is the entry page. --> + +<iframe src="resources/promise-job-incumbent-incumbent.html"></iframe> +<iframe src="resources/promise-job-incumbent-resolver.html"></iframe> + +<script> +setup({ explicit_done: true }); + +// postMessage should pick the incumbent page as its .source value to set on the MessageEvent, even +// inside promise jobs. +const expectedURL = (new URL("resources/promise-job-incumbent-incumbent.html", location.href)).href; + +let testId = 0; + +window.onload = () => { + const relevantWindow = frames[0].document.querySelector("#r").contentWindow; + const runInResolver = frames[1].runWhatYouGiveMe; + + function setupTest(t) { + ++testId; + const thisTestId = testId; + + relevantWindow.addEventListener("messagereceived", t.step_func(e => { + const [receivedTestId, receivedSourceURL] = e.detail; + + if (receivedTestId !== thisTestId) { + return; + } + + assert_equals(receivedSourceURL, expectedURL); + t.done(); + })); + + return thisTestId; + } + + async_test(t => { + const thisTestId = setupTest(t); + + frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*"); + }, "Sanity check: this all works as expected with no promises involved"); + + async_test(t => { + const thisTestId = setupTest(t); + + // No t.step_func because that could change the realms + Promise.resolve().then(() => { + frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*"); + }); + }, "Fulfillment handler on fulfilled promise"); + + async_test(t => { + const thisTestId = setupTest(t); + + const p = Promise.resolve(); + frames[0].runWindowPostMessageVeryIndirectlyWithNoUserCode(p, "then", thisTestId, "*"); + }, "Fulfillment handler on fulfilled promise, using backup incumbent settings object stack"); + + async_test(t => { + const thisTestId = setupTest(t); + + // No t.step_func because that could change the realms + Promise.reject().catch(() => { + frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*"); + }); + }, "Rejection handler on rejected promise"); + + async_test(t => { + const thisTestId = setupTest(t); + + const p = Promise.reject(); + frames[0].runWindowPostMessageVeryIndirectlyWithNoUserCode(p, "catch", thisTestId, "*"); + }, "Rejection handler on rejected promise, using backup incumbent settings object stack"); + + // The following tests test that we derive the incumbent settings object at promise-job time from + // the incumbent realm at the time the handler was added, not at the time the resolve()/reject() + // was done. See https://github.com/whatwg/html/issues/5213 for the spec side of this issue. + + async_test(t => { + const thisTestId = setupTest(t); + + let resolve; + const p = new Promise(r => { resolve = r; }); + + // No t.step_func because that could change the realms + p.then(() => { + frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*"); + }); + + t.step_timeout(() => { + runInResolver(resolve); + }, 0); + }, "Fulfillment handler on pending-then-fulfilled promise"); + + async_test(t => { + const thisTestId = setupTest(t); + + let resolve; + const p = new Promise(r => { resolve = r; }); + + frames[0].runWindowPostMessageVeryIndirectlyWithNoUserCode(p, "then", thisTestId, "*"); + + t.step_timeout(() => { + runInResolver(resolve); + }, 0); + }, "Fulfillment handler on pending-then-fulfilled promise, using backup incumbent settings object stack"); + + async_test(t => { + const thisTestId = setupTest(t); + + let reject; + const p = new Promise((_, r) => { reject = r; }); + + // No t.step_func because that could change the realms + p.catch(() => { + frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*"); + }); + + t.step_timeout(() => { + runInResolver(reject); + }, 0); + }, "Rejection handler on pending-then-rejected promise"); + + async_test(t => { + const thisTestId = setupTest(t); + + let reject; + const p = new Promise((_, r) => { reject = r; }); + + frames[0].runWindowPostMessageVeryIndirectlyWithNoUserCode(p, "catch", thisTestId, "*"); + + t.step_timeout(() => { + runInResolver(reject); + }, 0); + }, "Rejection handler on pending-then-rejected promise, using backup incumbent settings object stack"); + + async_test(t => { + const thisTestId = setupTest(t); + + const thenable = { + // No t.step_func because that could change the realms + then(f) { + frames[0].runWindowPostMessageVeryIndirectly(thisTestId, "*"); + } + }; + + Promise.resolve(thenable); + }, "Thenable resolution"); + + async_test(t => { + const thisTestId = setupTest(t); + + frames[0].resolveThenableThatRunsWindowPostMessageVeryIndirectlyWithNoUserCode(testId, "*", []); + }, "Thenable resolution, using backup incumbent settings object stack"); + + done(); +}; +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/README.md b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/README.md new file mode 100644 index 0000000000..a89258a4e0 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/README.md @@ -0,0 +1,5 @@ +A couple notes about the files scattered in this `resources/` directory: + +* The nested directory structure is necessary here so that relative URL resolution can be tested; we need different sub-paths for each document. + +* The semi-duplicate `window-to-open.html`s scattered throughout are present because Firefox, at least, does not fire `Window` `load` events for 404s, so we want to ensure that no matter which global is used, `window`'s `load` event is hit and our tests can proceed. diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/current/current.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/current/current.html new file mode 100644 index 0000000000..63d9c437fc --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/current/current.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Current page used as a test helper</title> + diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/current/resources/window-to-open.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/current/resources/window-to-open.html new file mode 100644 index 0000000000..1bc4cca9a3 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/current/resources/window-to-open.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>If the current settings object is used this page will be opened</title> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/function/function.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/function/function.html new file mode 100644 index 0000000000..15841d387d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/function/function.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Realm for a "then" function used as a test helper</title> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/function/resources/window-to-open.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/function/resources/window-to-open.html new file mode 100644 index 0000000000..3928c1f8aa --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/function/resources/window-to-open.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>If the function's settings object is used this page will be opened</title> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-entry-incumbent.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-entry-incumbent.html new file mode 100644 index 0000000000..3740c1467d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-entry-incumbent.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Incumbent page used as a test helper</title> + +<iframe src="relevant/relevant.html" id="r"></iframe> +<iframe src="current/current.html" id="c"></iframe> + +<script> + const relevant = document.querySelector("#r"); + const current = document.querySelector("#c"); + + window.runWindowOpenVeryIndirectly = (...args) => { + return current.contentWindow.open.call(relevant.contentWindow, ...args); + }; +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-incumbent-incumbent.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-incumbent-incumbent.html new file mode 100644 index 0000000000..57dd5dff10 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-incumbent-incumbent.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Incumbent page used as a test helper</title> + +<iframe src="relevant/relevant.html" id="r"></iframe> +<iframe src="current/current.html" id="c"></iframe> + +<script> + const relevant = document.querySelector("#r"); + const current = document.querySelector("#c"); + + window.runWindowPostMessageVeryIndirectly = (...args) => { + return current.contentWindow.postMessage.call(relevant.contentWindow, ...args); + }; + + // This tests the backup incumbent settings object stack scenario, by avoiding putting user code on the stack. + window.runWindowPostMessageVeryIndirectlyWithNoUserCode = (promise, promiseMethod, ...args) => { + const runWindowPostMessage = current.contentWindow.postMessage.bind(relevant.contentWindow, ...args); + promise[promiseMethod](runWindowPostMessage); + }; + + window.resolveThenableThatRunsWindowPostMessageVeryIndirectlyWithNoUserCode = (...args) => { + Promise.resolve({ + then: current.contentWindow.postMessage.bind(relevant.contentWindow, ...args) + }); + }; +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-incumbent-resolver.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-incumbent-resolver.html new file mode 100644 index 0000000000..a730b9c3ce --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/promise-job-incumbent-resolver.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Incumbent page used as a test helper</title> + +<script> + window.runWhatYouGiveMe = (func) => { + func(); + }; +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/relevant/relevant.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/relevant/relevant.html new file mode 100644 index 0000000000..f5965f2231 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/relevant/relevant.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Relevant page used as a test helper</title> + +<script> +// promise-job-incumbent will end up posting a message to here. We need to signal back the "source". + +window.onmessage = e => { + const testId = e.data; + const sourceURL = e.source.document.URL; + + window.dispatchEvent(new CustomEvent("messagereceived", { detail: [testId, sourceURL] })); +}; +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/relevant/resources/window-to-open.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/relevant/resources/window-to-open.html new file mode 100644 index 0000000000..4138b5a084 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/relevant/resources/window-to-open.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>If the relevant settings object is used this page will be opened</title> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/resources/window-to-open.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/resources/window-to-open.html new file mode 100644 index 0000000000..7743b9b578 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/resources/window-to-open.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>If the incumbent settings object is used this page will be opened</title> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/window-to-open.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/window-to-open.html new file mode 100644 index 0000000000..ce357937f5 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/integration-with-the-javascript-job-queue/resources/window-to-open.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>If the entry settings object is used this page will be opened</title> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin-setInterval.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin-setInterval.html new file mode 100644 index 0000000000..8b92f7d148 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin-setInterval.html @@ -0,0 +1,25 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - runtime error in cross-origin setInterval</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var ran = false; + var interval; + window.addEventListener('error', t.step_func(e => { + clearInterval(interval); + ran = true; + assert_equals(e.error.constructor, ReferenceError); + })); + var script = document.createElement('script'); + script.src = location.href.replace('://', '://www1.').replace(/\/[^\/]+$/, '/support/undefined-variable-in-setInterval.js'); + document.body.appendChild(script); + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin-setTimeout.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin-setTimeout.html new file mode 100644 index 0000000000..2e1a9d2315 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin-setTimeout.html @@ -0,0 +1,23 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - runtime error in cross-origin setTimeout</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var ran = false; + window.addEventListener('error', t.step_func(e => { + ran = true; + assert_equals(e.error.constructor, ReferenceError); + })); + var script = document.createElement('script'); + script.src = location.href.replace('://', '://www1.').replace(/\/[^\/]+$/, '/support/undefined-variable-in-setTimeout.js'); + document.body.appendChild(script); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin.html new file mode 100644 index 0000000000..d63aaa6d3b --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-cross-origin.html @@ -0,0 +1,38 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - runtime error in <script src=//www1...></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + var col_value; + window.onerror = t.step_func(function(a, b, c, d){ + ran = true; + col_value = d; + assert_equals(a, 'Script error.', 'first arg'); + assert_equals(b, '', 'second arg'); + assert_equals(c, 0, 'third arg'); + }); + var script = document.createElement('script'); + script.src = location.href.replace('://', '://www1.').replace(/\/[^\/]+$/, '/support/undefined-variable.js'); + document.body.appendChild(script); + onload = function(){ + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + assert_equals(col_value, 0, 'fourth arg'); + t_col.done(); + }); + }; + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-data-url.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-data-url.html new file mode 100644 index 0000000000..485ce90aa6 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-data-url.html @@ -0,0 +1,36 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - runtime error in <script src=data:...></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + var col_value; + window.onerror = t.step_func(function(a, b, c, d){ + ran = true; + col_value = d; + assert_equals(typeof a, 'string', 'first arg'); + assert_equals(b, 'data:text/javascript,undefined_variable;', 'second arg'); + assert_equals(typeof c, 'number', 'third arg'); + }); + </script> + <script src="data:text/javascript,undefined_variable;"></script> + <script> + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + assert_equals(typeof col_value, 'number', 'fourth arg'); + t_col.done(); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-attribute.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-attribute.html new file mode 100644 index 0000000000..b4f69da7a2 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-attribute.html @@ -0,0 +1,39 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - runtime error in attribute</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + var col_value; + window.onerror = t.step_func(function(a, b, c, d){ + ran = true; + col_value = d; + assert_equals(typeof a, 'string', 'first arg'); + assert_equals(b, location.href, 'second arg'); + assert_equals(typeof c, 'number', 'third arg'); + }); + </script> + <p onclick="undefined_variable;"></p> + <script> + t.step(function(){ + var ev = document.createEvent('Event'); + ev.initEvent('click', false, false); + document.querySelector('p').dispatchEvent(ev); + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + assert_equals(typeof col_value, 'number', 'fourth arg'); + t_col.done(); + }); + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-body-onerror.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-body-onerror.html new file mode 100644 index 0000000000..e0fd1dcbd5 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-body-onerror.html @@ -0,0 +1,25 @@ +<!doctype html> +<html> + <head> + <title>runtime error in <body onerror></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var ran = 0; + </script> + </head> + <body onerror="ran++; undefined_variable_in_onerror;"> + <div id=log></div> + <script> + undefined_variable; + </script> + <script> + t.step(function(){ + assert_equals(ran, 1, 'ran'); + t.done(); + }); + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-setInterval.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-setInterval.html new file mode 100644 index 0000000000..090e1dd78e --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-setInterval.html @@ -0,0 +1,39 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - runtime error in setInterval</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + var col_value; + var interval; + window.onerror = t.step_func(function(a, b, c, d){ + clearInterval(interval); + ran = true; + col_value = d; + assert_equals(typeof a, 'string', 'first arg'); + assert_equals(b, location.href, 'second arg'); + assert_equals(typeof c, 'number', 'third arg'); + }); + interval = setInterval("undefined_variable;", 10); + step_timeout(function(){ + clearInterval(interval); + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + assert_equals(typeof col_value, 'number', 'fourth arg'); + t_col.done(); + }); + }, 20); + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-setTimeout.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-setTimeout.html new file mode 100644 index 0000000000..cebcd4346c --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-setTimeout.html @@ -0,0 +1,36 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - runtime error in setTimeout</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + var col_value; + window.onerror = t.step_func(function(a, b, c, d){ + ran = true; + col_value = d; + assert_equals(typeof a, 'string', 'first arg'); + assert_equals(b, location.href, 'second arg'); + assert_equals(typeof c, 'number', 'third arg'); + }); + setTimeout("undefined_variable;", 10); + setTimeout(function(){ + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + assert_equals(typeof col_value, 'number', 'fourth arg'); + t_col.done(); + }); + }, 20); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-window-onerror.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-window-onerror.html new file mode 100644 index 0000000000..150a793b79 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-in-window-onerror.html @@ -0,0 +1,29 @@ +<!doctype html> +<html> + <head> + <title>runtime error in window.onerror</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var ran = 0; + window.onerror = function(){ + ran++; + undefined_variable_in_onerror; + }; + </script> + <script> + undefined_variable; + </script> + <script> + t.step(function(){ + assert_equals(ran, 1, 'ran'); + t.done(); + }); + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-same-origin-with-hash.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-same-origin-with-hash.html new file mode 100644 index 0000000000..dc6ec059a5 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-same-origin-with-hash.html @@ -0,0 +1,36 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - runtime error in <script src=...> with hash</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + var col_value; + window.onerror = t.step_func(function(a, b, c, d){ + ran = true; + col_value = d; + assert_equals(typeof a, 'string', 'first arg'); + assert_equals(b, document.querySelector('script[src="support/undefined-variable.js#"]').src, 'second arg'); + assert_equals(typeof c, 'number', 'third arg'); + }); + </script> + <script src="support/undefined-variable.js#"></script> + <script> + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + assert_equals(typeof col_value, 'number', 'fourth arg'); + t_col.done(); + }); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-same-origin.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-same-origin.html new file mode 100644 index 0000000000..8f3cfb70b2 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error-same-origin.html @@ -0,0 +1,36 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - runtime error in <script src=...></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + var col_value; + window.onerror = t.step_func(function(a, b, c, d){ + ran = true; + col_value = d; + assert_equals(typeof a, 'string', 'first arg'); + assert_equals(b, document.querySelector('script[src="support/undefined-variable.js"]').src, 'second arg'); + assert_equals(typeof c, 'number', 'third arg'); + }); + </script> + <script src="support/undefined-variable.js"></script> + <script> + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + assert_equals(typeof col_value, 'number', 'fourth arg'); + t_col.done(); + }); + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error.html new file mode 100644 index 0000000000..7907494aa6 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/runtime-error.html @@ -0,0 +1,38 @@ +<!doctype html> +<html> + <head> + <title>window.onerror - runtime error in <script></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <div id=log></div> + <script> + setup({allow_uncaught_exception:true}); + var t = async_test(); + var t_col = async_test(document.title+' (column)'); + var ran = false; + var col_value; + window.onerror = t.step_func(function(a, b, c, d){ + ran = true; + col_value = d; + assert_equals(typeof a, 'string', 'first arg'); + assert_equals(b, location.href, 'second arg'); + assert_equals(typeof c, 'number', 'third arg'); + }); + </script> + <script> + undefined_variable; + </script> + <script> + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); + t_col.step(function(){ + assert_equals(typeof col_value, 'number', 'fourth arg'); + t_col.done(); + }); + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error-in-setInterval.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error-in-setInterval.js new file mode 100644 index 0000000000..afec114458 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error-in-setInterval.js @@ -0,0 +1,8 @@ +interval = setInterval('{', 10); +step_timeout(function(){ + clearInterval(interval); + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); +}, 20);
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error-in-setTimeout.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error-in-setTimeout.js new file mode 100644 index 0000000000..427542b42e --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error-in-setTimeout.js @@ -0,0 +1,7 @@ +setTimeout('{', 10); +setTimeout(function(){ + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); +}, 20); diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error.js new file mode 100644 index 0000000000..0f74a6fca6 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/syntax-error.js @@ -0,0 +1 @@ +for (;) {}
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable-in-setInterval.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable-in-setInterval.js new file mode 100644 index 0000000000..c2a017a2ab --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable-in-setInterval.js @@ -0,0 +1,8 @@ +interval = setInterval('undefined_variable;', 10); +step_timeout(function(){ + clearInterval(interval); + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); +}, 20);
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable-in-setTimeout.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable-in-setTimeout.js new file mode 100644 index 0000000000..6fa54cda9f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable-in-setTimeout.js @@ -0,0 +1,7 @@ +setTimeout('undefined_variable;', 10); +setTimeout(function(){ + t.step(function(){ + assert_true(ran, 'ran'); + t.done(); + }); +}, 20); diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable.js new file mode 100644 index 0000000000..e73a62ceda --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/support/undefined-variable.js @@ -0,0 +1 @@ +undefined_variable;
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/allow-crossorigin.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/allow-crossorigin.html new file mode 100644 index 0000000000..7524604113 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/allow-crossorigin.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cors/support.js?pipe=sub"></script> +<link rel="help" href="https://html.spec.whatwg.org/#unhandled-promise-rejections"> +<link rel="help" href="https://html.spec.whatwg.org/#muted-errors"> + +<body> +<script> +'use strict'; +setup({ + allow_uncaught_exception: true +}); + +async_test(function(t) { + addEventListener('unhandledrejection', t.step_func(function(e) { + assert_equals(e.reason, 42, 'reason should be the one given by the script'); + t.done(); + })); +}, 'Promise rejection event should be received for the cross-origin CORS script'); + +(function() { + var scriptEl = document.createElement('script'); + scriptEl.src = CROSSDOMAIN + 'support/promise-access-control.py?allow=true'; + scriptEl.crossOrigin = 'anonymous'; + document.body.appendChild(scriptEl); +}()); +</script> +</body> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/disallow-crossorigin.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/disallow-crossorigin.html new file mode 100644 index 0000000000..d61618a53e --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/disallow-crossorigin.html @@ -0,0 +1,96 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/cors/support.js?pipe=sub"></script> +<link rel="help" href="https://html.spec.whatwg.org/#unhandled-promise-rejections"> +<link rel="help" href="https://html.spec.whatwg.org/#muted-errors"> + +<body> +<script> +'use strict'; + +setup({ + allow_uncaught_exception: true +}); + +(function() { + var resolveLoaded; + var loadedPromise = new Promise(function(resolve) { resolveLoaded = resolve; }); + + promise_test(function(t) { + var unreachedUnhandled = t.unreached_func('unhandledrejection event should never be triggered'); + var unreachedHandled = t.unreached_func('rejectionhandled event should never be triggered'); + + addEventListener('unhandledrejection', unreachedUnhandled); + addEventListener('rejectionhandled', unreachedHandled); + ensureCleanup(t, unreachedUnhandled, unreachedHandled); + + return loadedPromise.then(t.step_func(function() { + return new Promise(function(resolve) { + t.step_timeout(function() { + resolve(); + }, 1000); + }); + })); + }, 'Promise rejection event should be muted for cross-origin non-CORS script'); + + promise_test(function(t) { + var unreachedUnhandled = t.unreached_func('unhandledrejection event should never be triggered'); + var unreachedHandled = t.unreached_func('rejectionhandled event should never be triggered'); + + addEventListener('unhandledrejection', unreachedUnhandled); + addEventListener('rejectionhandled', unreachedHandled); + ensureCleanup(t, unreachedUnhandled, unreachedHandled); + + return new Promise(function(resolve) { + handleRejectedPromise(new Promise(function(resolve, reject) { reject(42); })); + t.step_timeout(function() { + resolve(); + }, 1000); + }); + }, 'Promise rejection should be muted if the rejected promise is handled in cross-origin non-CORS script'); + + promise_test(function(t) { + var promise = new Promise(function(resolve, reject) { reject(42); }); + var resolveReceived; + var eventPromise = new Promise(function(resolve) { resolveReceived = resolve; }); + var unhandled = t.step_func(function(e) { + if (e.promise === promise) { + handleRejectedPromise(promise); + resolveReceived(); + } + }); + var unreachedHandled = t.unreached_func('rejectionhandled event should never be triggered'); + + addEventListener('unhandledrejection', unhandled); + addEventListener('rejectionhandled', unreachedHandled); + ensureCleanup(t, unhandled, unreachedHandled); + + return eventPromise.then(t.step_func(function() { + return new Promise(function(resolve) { + t.step_timeout(function() { + resolve(); + }, 1000); + }); + })); + }, 'Promise rejection should be muted if the rejected promise is handled in unhandledrejection event handler in cross-origin non-CORS script'); + + function ensureCleanup(t, unhandled, handled) { + t.add_cleanup(function() { + if (unhandled) { + removeEventListener('unhandledrejection', unhandled); + } + if (handled) { + removeEventListener('rejectionhandled', handled); + } + }); + } + + var scriptEl = document.createElement('script'); + scriptEl.src = CROSSDOMAIN + 'support/promise-access-control.py?allow=false'; + scriptEl.onload = resolveLoaded; + document.body.appendChild(scriptEl); +}()); +</script> +</body> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-event-constructor.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-event-constructor.html new file mode 100644 index 0000000000..4319deee7b --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-event-constructor.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/#the-promiserejectionevent-interface"> +<script> +'use strict'; + +test(function() { + var p = new Promise(function(resolve, reject) {}); + + assert_throws_js(TypeError, + function() { + PromiseRejectionEvent('', { promise: p }); + }, + "Calling PromiseRejectionEvent constructor without 'new' must throw"); + + // No custom options are passed (besides required promise). + assert_equals(new PromiseRejectionEvent('eventType', { promise: p }).bubbles, false); + assert_equals(new PromiseRejectionEvent('eventType', { promise: p }).cancelable, false); + assert_equals(new PromiseRejectionEvent('eventType', { promise: p }).promise, p); + assert_equals(new PromiseRejectionEvent('eventType', { promise: p }).reason, undefined); + + // No promise is passed. + assert_throws_js(TypeError, + function() { + new PromiseRejectionEvent('eventType', { bubbles: false }); + }, + 'Cannot construct PromiseRejectionEventInit without promise'); + + // bubbles is passed. + assert_equals(new PromiseRejectionEvent('eventType', { bubbles: false, promise: p }).bubbles, false); + assert_equals(new PromiseRejectionEvent('eventType', { bubbles: true, promise: p }).bubbles, true); + + // cancelable is passed. + assert_equals(new PromiseRejectionEvent('eventType', { cancelable: false, promise: p }).cancelable, false); + assert_equals(new PromiseRejectionEvent('eventType', { cancelable: true, promise: p }).cancelable, true); + + // reason is passed. + var r = new Error(); + assert_equals(new PromiseRejectionEvent('eventType', { promise: p, reason: r }).reason, r); + assert_equals(new PromiseRejectionEvent('eventType', { promise: p, reason: null }).reason, null); + + // All initializers are passed. + assert_equals(new PromiseRejectionEvent('eventType', { bubbles: true, cancelable: true, promise: p, reason: r }).bubbles, true); + assert_equals(new PromiseRejectionEvent('eventType', { bubbles: true, cancelable: true, promise: p, reason: r }).cancelable, true); + assert_equals(new PromiseRejectionEvent('eventType', { bubbles: true, cancelable: true, promise: p, reason: r }).promise, p); + assert_equals(new PromiseRejectionEvent('eventType', { bubbles: true, cancelable: true, promise: p, reason: r }).reason, r); +}, "This tests the constructor for the PromiseRejectionEvent DOM class."); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-event-during-parse.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-event-during-parse.html new file mode 100644 index 0000000000..160dad9b36 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-event-during-parse.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Promise rejection during initial parsing of document</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/#unhandled-promise-rejections"> +<body> +<p>The script in this test is executed immediately while parsing is ongoing, and +<a +href="https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script">cleaning +up after running script</a> involves queueing a task on the DOM manipulation +task source to fire the <code>unhandledrejection</code> event. Parsing then +completes, immediately transitioning the document's readiness state to +"interactive," and queuing another task on the DOM manipulation task source to +transition the state to "complete." +</p> +<script> +'use strict'; +setup({ allow_uncaught_exception: true }); + +async_test(function(t) { + const events = []; + document.addEventListener('readystatechange', t.step_func(function() { + events.push('readystatechange:' + document.readyState); + })); + addEventListener('unhandledrejection', t.step_func(function() { + events.push('unhandledrejection'); + })); + + Promise.reject(new Error('this error is intentional')); + + addEventListener('load', t.step_func(function() { + assert_array_equals( + events, + [ + 'readystatechange:interactive', + 'unhandledrejection', + 'readystatechange:complete' + ] + ); + t.done(); + })); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-attached-in-event.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-attached-in-event.html new file mode 100644 index 0000000000..b151bd812f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-attached-in-event.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/#unhandled-promise-rejections"> +<script> +'use strict'; +setup({ + allow_uncaught_exception: true +}); +async_test(function(t) { + var e = new Error('e'); + var p = Promise.reject(e); + + window.onunhandledrejection = function(evt) { + t.step(function() { + assert_equals(evt.promise, p); + assert_equals(evt.reason, e); + }); + var unreached = t.unreached_func('promise should not be fulfilled'); + p.then(unreached, function(reason) { + t.step(function() { + assert_equals(reason, e); + }); + t.step_timeout(function() { t.done(); }, 10); + }); + }; + + window.onrejectionhandled = t.unreached_func('rejectionhandled event should not be invoked'); +}, 'Attaching a handler in unhandledrejection should not trigger rejectionhandled.'); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-iframe.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-iframe.html new file mode 100644 index 0000000000..c749eadef4 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-iframe.html @@ -0,0 +1,146 @@ +<!doctype html> +<meta charset=utf-8> +<title></title> +<div id="log"></div><br> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script> +'use strict'; + +setup({ + allow_uncaught_exception: true +}); + +async_test(function(t) { + createIframeAndStartTest(t, function(w) { + let e = new Error(); + let promise = new w.Promise(function(_, reject) { + setTimeout(function() { + reject(e); + }, 1); + }); + + let unhandled = function(evt) { + if (evt.promise === promise) { + t.step(function() { + assert_equals(evt.reason, e); + assert_equals(evt.promise, promise); + }); + t.done(); + } + }; + let handled = function(evt) { + if (evt.promise === promise) { + t.step(function() { + assert_unreached('rejectionhandled event is not supposed to be triggered'); + }); + } + }; + + w.addEventListener('unhandledrejection', unhandled); + w.addEventListener('rejectionhandled', handled); + ensureCleanup(t, w, unhandled, handled); + }); +}, "unhandledrejection: promise is created in iframe and being rejected elsewhere"); + +async_test(function(t) { + createIframeAndStartTest(t, function(w) { + let e = new Error(); + let promise = w.Promise.reject(e); + + let unhandled = function(evt) { + if (evt.promise === promise) { + t.step(function() { + assert_unreached('unhandledrejection event is not supposed to be triggered'); + }); + } + }; + let handled = function(evt) { + if (evt.promise === promise) { + t.step(function() { + assert_unreached('rejectionhandled event is not supposed to be triggered'); + }); + } + }; + + w.addEventListener('unhandledrejection', unhandled); + w.addEventListener('rejectionhandled', handled); + ensureCleanup(t, w, unhandled, handled); + + promise.catch(function() {}); + setTimeout(function() { + t.done(); + }, 10); + }); +}, 'no unhandledrejection/rejectionhandled: promise is created in iframe and being rejected elsewhere'); + +async_test(function(t) { + createIframeAndStartTest(t, function(w) { + let e = new Error(); + let promise = w.Promise.reject(e); + var unhandledPromises = []; + var unhandledReasons = []; + var handledPromises = []; + var handledReasons = []; + + let unhandled = function(evt) { + if (evt.promise === promise) { + t.step(function() { + unhandledPromises.push(evt.promise); + unhandledReasons.push(evt.reason); + + setTimeout(function() { + var unreached = t.unreached_func('promise should not be fulfilled'); + promise.then(unreached, function(reason) { + assert_equals(reason, e); + setTimeout(function() { + assert_array_equals(handledPromises, [promise]); + assert_array_equals(handledReasons, [e]); + t.done(); + }, 10); + }); + }, 10); + }); + } + }; + let handled = function(evt) { + if (evt.promise === promise) { + t.step(function() { + assert_array_equals(unhandledPromises, [promise]); + assert_array_equals(unhandledReasons, [e]); + handledPromises.push(evt.promise); + handledReasons.push(evt.reason); + }); + } + }; + + w.addEventListener('unhandledrejection', unhandled); + w.addEventListener('rejectionhandled', handled); + ensureCleanup(t, w, unhandled, handled); + }); +}, 'delayed handling: promise is created in iframe and being rejected elsewhere'); + +// Helpers + +function createIframeAndStartTest(t, runTest) { + var iframe = document.createElement("iframe"); + iframe.onload = function() { + t.add_cleanup(() => iframe.remove()); + runTest(iframe.contentWindow); + }; + iframe.srcdoc = ''; + document.documentElement.appendChild(iframe); +} + +function ensureCleanup(t, win, unhandled, handled) { + t.add_cleanup(function() { + if (unhandled) { + win.removeEventListener('unhandledrejection', unhandled); + } + if (handled) { + win.removeEventListener('rejectionhandled', handled); + } + }); +} + +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-onerror.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-onerror.html new file mode 100644 index 0000000000..b6c02d27c9 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events-onerror.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/#runtime-script-errors"> +<link rel="help" href="https://html.spec.whatwg.org/#unhandled-promise-rejections"> +<script> +'use strict'; +setup({ + allow_uncaught_exception: true +}); +async_test(function(t) { + var e = new Error('e'); + var e2 = new Error('e2'); + + window.onerror = function (msg, url, line, col, error) { + t.step(function() { + assert_true(msg.includes('e2')); + assert_equals(error, e2); + }); + t.done(); + }; + + window.onrejectionhandled = function() { + // This should cause onerror + throw e2; + }; + + var p = Promise.reject(e); + queueTask(function() { + queueTask(t.step_func(function() { + // This will cause onrejectionhandled + p.catch(function() {}); + })); + }); +}, 'Throwing inside an unhandledrejection handler invokes the error handler.'); + +// This function queues a task in "DOM manipulation task source" +function queueTask(f) { + var d = document.createElement("details"); + d.ontoggle = function() { + f(); + }; + + d.setAttribute("open", ""); +} +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.dedicatedworker.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.dedicatedworker.html new file mode 100644 index 0000000000..b6a4a9f3e6 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.dedicatedworker.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Promise rejection events tests: in a dedicated worker context</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/#unhandled-promise-rejections"> + +<script> +'use strict'; +fetch_tests_from_worker(new Worker('support/promise-rejection-events.js')); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.html new file mode 100644 index 0000000000..2fdfe26025 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Promise rejection events tests: in a Window context</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/#unhandled-promise-rejections"> + +<script src="support/promise-rejection-events.js"></script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.serviceworker.https.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.serviceworker.https.html new file mode 100644 index 0000000000..9d12125928 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.serviceworker.https.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Promise rejection events tests: in a service worker context</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/#unhandled-promise-rejections"> + +<script> +'use strict'; +service_worker_test('support/promise-rejection-events.js', 'Service worker setup'); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.sharedworker.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.sharedworker.html new file mode 100644 index 0000000000..d832d1822f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.sharedworker.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Promise rejection events tests: in a shared worker context</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/#unhandled-promise-rejections"> + +<script> +'use strict'; +fetch_tests_from_worker(new SharedWorker('support/promise-rejection-events.js')); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-resolution-order.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-resolution-order.html new file mode 100644 index 0000000000..314063025f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-resolution-order.html @@ -0,0 +1,70 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Promise rejection ordering</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint"> +<body> +<p>A microtask checkpoint should notify about rejected promises. After +<a +href="https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script">cleaning +up after running script</a> involves running a microtask checkpoint. So the order of unhandledrejection +should occur before error2. +</p> + +<script> +'use strict'; +setup({ allow_uncaught_exception: true }); + +async_test(function(t) { + const events = []; + addEventListener('unhandledrejection', t.step_func(() => { + events.push('unhandledrejection'); + })); + + function insertInvalidScript(id) { + // Inserting <script> with an empty source schedules dispatching an 'error' event on + // the dom manipulation task source. + let script = document.createElement('script'); + script.setAttribute('src', ' '); + script.addEventListener('error', t.step_func(() => { + events.push(`error${id}`); + + // This will be the end of the test. Verify the results are correct. + if (id == 2) { + assert_array_equals( + events, + [ + 'raf1', + 'resolve1', + 'raf2', + 'resolve2', + 'error1', + 'unhandledrejection', + 'error2' + ] + ); + t.done(); + } + })); + document.body.append(script); + } + + requestAnimationFrame(t.step_func(() => { + events.push('raf1'); + Promise.reject(); + Promise.resolve(0).then(t.step_func(() => { + events.push('resolve1'); + })); + insertInvalidScript(1); + })); + + requestAnimationFrame(t.step_func(() => { + events.push('raf2'); + Promise.resolve(0).then(t.step_func(() => { + events.push('resolve2'); + })); + insertInvalidScript(2); + })); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-access-control.py b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-access-control.py new file mode 100644 index 0000000000..cf8ed5e492 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-access-control.py @@ -0,0 +1,18 @@ +def main(request, response): + allow = request.GET.first(b"allow", b"false") + + headers = [(b"Content-Type", b"application/javascript")] + if allow != b"false": + headers.append((b"Access-Control-Allow-Origin", b"*")) + + body = b""" + function handleRejectedPromise(promise) { + promise.catch(() => {}); + } + + (function() { + new Promise(function(resolve, reject) { reject(42); }); + })(); + """ + + return headers, body diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-rejection-events.js b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-rejection-events.js new file mode 100644 index 0000000000..036e1784db --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-rejection-events.js @@ -0,0 +1,961 @@ +'use strict'; + +if (self.importScripts) { + importScripts('/resources/testharness.js'); +} + +setup({ + allow_uncaught_exception: true +}); + +// +// Straightforward unhandledrejection tests +// +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledSucceed(t, e, function() { return p; }); + + p = Promise.reject(e); +}, 'unhandledrejection: from Promise.reject'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledSucceed(t, e, function() { return p; }); + + p = new Promise(function(_, reject) { + reject(e); + }); +}, 'unhandledrejection: from a synchronous rejection in new Promise'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledSucceed(t, e, function() { return p; }); + + p = new Promise(function(_, reject) { + queueTask(function() { + reject(e); + }); + }); +}, 'unhandledrejection: from a task-delayed rejection'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledSucceed(t, e, function() { return p; }); + + p = new Promise(function(_, reject) { + setTimeout(function() { + reject(e); + }, 1); + }); +}, 'unhandledrejection: from a setTimeout-delayed rejection'); + +async_test(function(t) { + var e = new Error(); + var e2 = new Error(); + var promise2; + + onUnhandledSucceed(t, e2, function() { return promise2; }); + + var unreached = t.unreached_func('promise should not be fulfilled'); + promise2 = Promise.reject(e).then(unreached, function(reason) { + t.step(function() { + assert_equals(reason, e); + }); + throw e2; + }); +}, 'unhandledrejection: from a throw in a rejection handler chained off of Promise.reject'); + +async_test(function(t) { + var e = new Error(); + var e2 = new Error(); + var promise2; + + onUnhandledSucceed(t, e2, function() { return promise2; }); + + var unreached = t.unreached_func('promise should not be fulfilled'); + promise2 = new Promise(function(_, reject) { + setTimeout(function() { + reject(e); + }, 1); + }).then(unreached, function(reason) { + t.step(function() { + assert_equals(reason, e); + }); + throw e2; + }); +}, 'unhandledrejection: from a throw in a rejection handler chained off of a setTimeout-delayed rejection'); + +async_test(function(t) { + var e = new Error(); + var e2 = new Error(); + var promise2; + + onUnhandledSucceed(t, e2, function() { return promise2; }); + + var promise = new Promise(function(_, reject) { + setTimeout(function() { + reject(e); + mutationObserverMicrotask(function() { + var unreached = t.unreached_func('promise should not be fulfilled'); + promise2 = promise.then(unreached, function(reason) { + t.step(function() { + assert_equals(reason, e); + }); + throw e2; + }); + }); + }, 1); + }); +}, 'unhandledrejection: from a throw in a rejection handler attached one microtask after a setTimeout-delayed rejection'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledSucceed(t, e, function() { return p; }); + + p = Promise.resolve().then(function() { + return Promise.reject(e); + }); +}, 'unhandledrejection: from returning a Promise.reject-created rejection in a fulfillment handler'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledSucceed(t, e, function() { return p; }); + + p = Promise.resolve().then(function() { + throw e; + }); +}, 'unhandledrejection: from a throw in a fulfillment handler'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledSucceed(t, e, function() { return p; }); + + p = Promise.resolve().then(function() { + return new Promise(function(_, reject) { + setTimeout(function() { + reject(e); + }, 1); + }); + }); +}, 'unhandledrejection: from returning a setTimeout-delayed rejection in a fulfillment handler'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledSucceed(t, e, function() { return p; }); + + p = Promise.all([Promise.reject(e)]); +}, 'unhandledrejection: from Promise.reject, indirected through Promise.all'); + +async_test(function(t) { + var p; + + var unhandled = function(ev) { + if (ev.promise === p) { + t.step(function() { + assert_equals(ev.reason.name, 'InvalidStateError'); + assert_equals(ev.promise, p); + }); + t.done(); + } + }; + addEventListener('unhandledrejection', unhandled); + ensureCleanup(t, unhandled); + + p = createImageBitmap(new Blob()); +}, 'unhandledrejection: from createImageBitmap which is UA triggered'); + +// +// Negative unhandledrejection/rejectionhandled tests with immediate attachment +// + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + var unreached = t.unreached_func('promise should not be fulfilled'); + p = Promise.reject(e).then(unreached, function() {}); +}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise from Promise.reject'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + var unreached = t.unreached_func('promise should not be fulfilled'); + p = Promise.all([Promise.reject(e)]).then(unreached, function() {}); +}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise from ' + + 'Promise.reject, indirecting through Promise.all'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + var unreached = t.unreached_func('promise should not be fulfilled'); + p = new Promise(function(_, reject) { + reject(e); + }).then(unreached, function() {}); +}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a synchronously-rejected ' + + 'promise created with new Promise'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + var unreached = t.unreached_func('promise should not be fulfilled'); + p = Promise.resolve().then(function() { + throw e; + }).then(unreached, function(reason) { + t.step(function() { + assert_equals(reason, e); + }); + }); +}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise created from ' + + 'throwing in a fulfillment handler'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + var unreached = t.unreached_func('promise should not be fulfilled'); + p = Promise.resolve().then(function() { + return Promise.reject(e); + }).then(unreached, function(reason) { + t.step(function() { + assert_equals(reason, e); + }); + }); +}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise created from ' + + 'returning a Promise.reject-created promise in a fulfillment handler'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + var unreached = t.unreached_func('promise should not be fulfilled'); + p = Promise.resolve().then(function() { + return new Promise(function(_, reject) { + setTimeout(function() { + reject(e); + }, 1); + }); + }).then(unreached, function(reason) { + t.step(function() { + assert_equals(reason, e); + }); + }); +}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise created from ' + + 'returning a setTimeout-delayed rejection in a fulfillment handler'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + queueTask(function() { + p = Promise.resolve().then(function() { + return Promise.reject(e); + }) + .catch(function() {}); + }); +}, 'no unhandledrejection/rejectionhandled: all inside a queued task, a rejection handler attached synchronously to ' + + 'a promise created from returning a Promise.reject-created promise in a fulfillment handler'); + +async_test(function(t) { + var p; + + onUnhandledFail(t, function() { return p; }); + + var unreached = t.unreached_func('promise should not be fulfilled'); + p = createImageBitmap(new Blob()).then(unreached, function() {}); +}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise created from ' + + 'createImageBitmap'); + +// +// Negative unhandledrejection/rejectionhandled tests with microtask-delayed attachment +// + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + p = Promise.reject(e); + mutationObserverMicrotask(function() { + var unreached = t.unreached_func('promise should not be fulfilled'); + p.then(unreached, function() {}); + }); +}, 'delayed handling: a microtask delay before attaching a handler prevents both events (Promise.reject-created ' + + 'promise)'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + p = new Promise(function(_, reject) { + reject(e); + }); + mutationObserverMicrotask(function() { + var unreached = t.unreached_func('promise should not be fulfilled'); + p.then(unreached, function() {}); + }); +}, 'delayed handling: a microtask delay before attaching a handler prevents both events (immediately-rejected new ' + + 'Promise-created promise)'); + +async_test(function(t) { + var e = new Error(); + var p1; + var p2; + + onUnhandledFail(t, function() { return p1; }); + onUnhandledFail(t, function() { return p2; }); + + p1 = new Promise(function(_, reject) { + mutationObserverMicrotask(function() { + reject(e); + }); + }); + p2 = Promise.all([p1]); + mutationObserverMicrotask(function() { + var unreached = t.unreached_func('promise should not be fulfilled'); + p2.then(unreached, function() {}); + }); +}, 'delayed handling: a microtask delay before attaching the handler, and before rejecting the promise, indirected ' + + 'through Promise.all'); + +// +// Negative unhandledrejection/rejectionhandled tests with nested-microtask-delayed attachment +// + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + p = Promise.reject(e); + mutationObserverMicrotask(function() { + Promise.resolve().then(function() { + mutationObserverMicrotask(function() { + Promise.resolve().then(function() { + p.catch(function() {}); + }); + }); + }); + }); +}, 'microtask nesting: attaching a handler inside a combination of mutationObserverMicrotask + promise microtasks'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + queueTask(function() { + p = Promise.reject(e); + mutationObserverMicrotask(function() { + Promise.resolve().then(function() { + mutationObserverMicrotask(function() { + Promise.resolve().then(function() { + p.catch(function() {}); + }); + }); + }); + }); + }); +}, 'microtask nesting: attaching a handler inside a combination of mutationObserverMicrotask + promise microtasks, ' + + 'all inside a queueTask'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + setTimeout(function() { + p = Promise.reject(e); + mutationObserverMicrotask(function() { + Promise.resolve().then(function() { + mutationObserverMicrotask(function() { + Promise.resolve().then(function() { + p.catch(function() {}); + }); + }); + }); + }); + }, 0); +}, 'microtask nesting: attaching a handler inside a combination of mutationObserverMicrotask + promise microtasks, ' + + 'all inside a setTimeout'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + p = Promise.reject(e); + Promise.resolve().then(function() { + mutationObserverMicrotask(function() { + Promise.resolve().then(function() { + mutationObserverMicrotask(function() { + p.catch(function() {}); + }); + }); + }); + }); +}, 'microtask nesting: attaching a handler inside a combination of promise microtasks + mutationObserverMicrotask'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + queueTask(function() { + p = Promise.reject(e); + Promise.resolve().then(function() { + mutationObserverMicrotask(function() { + Promise.resolve().then(function() { + mutationObserverMicrotask(function() { + p.catch(function() {}); + }); + }); + }); + }); + }); +}, 'microtask nesting: attaching a handler inside a combination of promise microtasks + mutationObserverMicrotask, ' + + 'all inside a queueTask'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + setTimeout(function() { + p = Promise.reject(e); + Promise.resolve().then(function() { + mutationObserverMicrotask(function() { + Promise.resolve().then(function() { + mutationObserverMicrotask(function() { + p.catch(function() {}); + }); + }); + }); + }); + }, 0); +}, 'microtask nesting: attaching a handler inside a combination of promise microtasks + mutationObserverMicrotask, ' + + 'all inside a setTimeout'); + + +// For workers, queueTask() involves posting tasks to other threads, so +// the following tests don't work there. + +if ('document' in self) { + // + // Negative unhandledrejection/rejectionhandled tests with task-delayed attachment + // + + async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + var _reject; + p = new Promise(function(_, reject) { + _reject = reject; + }); + _reject(e); + queueTask(function() { + var unreached = t.unreached_func('promise should not be fulfilled'); + p.then(unreached, function() {}); + }); + }, 'delayed handling: a task delay before attaching a handler prevents unhandledrejection'); + + async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + p = Promise.reject(e); + queueTask(function() { + Promise.resolve().then(function() { + p.catch(function() {}); + }); + }); + }, 'delayed handling: queueTask after promise creation/rejection, plus promise microtasks, is not too late to ' + + 'attach a rejection handler'); + + async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + queueTask(function() { + Promise.resolve().then(function() { + Promise.resolve().then(function() { + Promise.resolve().then(function() { + Promise.resolve().then(function() { + p.catch(function() {}); + }); + }); + }); + }); + }); + p = Promise.reject(e); + }, 'delayed handling: queueTask before promise creation/rejection, plus many promise microtasks, is not too ' + + 'late to attach a rejection handler'); + + async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledFail(t, function() { return p; }); + + p = Promise.reject(e); + queueTask(function() { + Promise.resolve().then(function() { + Promise.resolve().then(function() { + Promise.resolve().then(function() { + Promise.resolve().then(function() { + p.catch(function() {}); + }); + }); + }); + }); + }); + }, 'delayed handling: queueTask after promise creation/rejection, plus many promise microtasks, is not too ' + + 'late to attach a rejection handler'); +} + +// +// Positive unhandledrejection/rejectionhandled tests with delayed attachment +// + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledSucceed(t, e, function() { return p; }); + + var _reject; + p = new Promise(function(_, reject) { + _reject = reject; + }); + _reject(e); + queueTask(function() { + queueTask(function() { + var unreached = t.unreached_func('promise should not be fulfilled'); + p.then(unreached, function() {}); + }); + }); +}, 'delayed handling: a nested-task delay before attaching a handler causes unhandledrejection'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledSucceed(t, e, function() { return p; }); + + p = Promise.reject(e); + queueTask(function() { + queueTask(function() { + Promise.resolve().then(function() { + p.catch(function() {}); + }); + }); + }); +}, 'delayed handling: a nested-queueTask after promise creation/rejection, plus promise microtasks, is too ' + + 'late to attach a rejection handler'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledSucceed(t, e, function() { return p; }); + + queueTask(function() { + queueTask(function() { + Promise.resolve().then(function() { + Promise.resolve().then(function() { + Promise.resolve().then(function() { + Promise.resolve().then(function() { + p.catch(function() {}); + }); + }); + }); + }); + }); + }); + p = Promise.reject(e); +}, 'delayed handling: a nested-queueTask before promise creation/rejection, plus many promise microtasks, is ' + + 'too late to attach a rejection handler'); + +async_test(function(t) { + var e = new Error(); + var p; + + onUnhandledSucceed(t, e, function() { return p; }); + + p = Promise.reject(e); + queueTask(function() { + queueTask(function() { + Promise.resolve().then(function() { + Promise.resolve().then(function() { + Promise.resolve().then(function() { + Promise.resolve().then(function() { + p.catch(function() {}); + }); + }); + }); + }); + }); + }); +}, 'delayed handling: a nested-queueTask after promise creation/rejection, plus many promise microtasks, is ' + + 'too late to attach a rejection handler'); + +async_test(function(t) { + var unhandledPromises = []; + var unhandledReasons = []; + var e = new Error(); + var p; + + var unhandled = function(ev) { + if (ev.promise === p) { + t.step(function() { + unhandledPromises.push(ev.promise); + unhandledReasons.push(ev.reason); + }); + } + }; + var handled = function(ev) { + if (ev.promise === p) { + t.step(function() { + assert_array_equals(unhandledPromises, [p]); + assert_array_equals(unhandledReasons, [e]); + assert_equals(ev.promise, p); + assert_equals(ev.reason, e); + }); + } + }; + addEventListener('unhandledrejection', unhandled); + addEventListener('rejectionhandled', handled); + ensureCleanup(t, unhandled, handled); + + p = new Promise(function() { + throw e; + }); + setTimeout(function() { + var unreached = t.unreached_func('promise should not be fulfilled'); + p.then(unreached, function(reason) { + assert_equals(reason, e); + setTimeout(function() { t.done(); }, 10); + }); + }, 10); +}, 'delayed handling: delaying handling by setTimeout(,10) will cause both events to fire'); + +async_test(function(t) { + var unhandledPromises = []; + var unhandledReasons = []; + var p; + + var unhandled = function(ev) { + if (ev.promise === p) { + t.step(function() { + unhandledPromises.push(ev.promise); + unhandledReasons.push(ev.reason.name); + }); + } + }; + var handled = function(ev) { + if (ev.promise === p) { + t.step(function() { + assert_array_equals(unhandledPromises, [p]); + assert_array_equals(unhandledReasons, ['InvalidStateError']); + assert_equals(ev.promise, p); + assert_equals(ev.reason.name, 'InvalidStateError'); + }); + } + }; + addEventListener('unhandledrejection', unhandled); + addEventListener('rejectionhandled', handled); + ensureCleanup(t, unhandled, handled); + + p = createImageBitmap(new Blob()); + setTimeout(function() { + var unreached = t.unreached_func('promise should not be fulfilled'); + p.then(unreached, function(reason) { + assert_equals(reason.name, 'InvalidStateError'); + setTimeout(function() { t.done(); }, 10); + }); + }, 10); +}, 'delayed handling: delaying handling rejected promise created from createImageBitmap will cause both events to fire'); + +// +// Miscellaneous tests about integration with the rest of the platform +// + +async_test(function(t) { + var e = new Error(); + var l = function(ev) { + var order = []; + mutationObserverMicrotask(function() { + order.push(1); + }); + setTimeout(function() { + order.push(2); + t.step(function() { + assert_array_equals(order, [1, 2]); + }); + t.done(); + }, 1); + }; + addEventListener('unhandledrejection', l); + ensureCleanup(t, l); + Promise.reject(e); +}, 'mutationObserverMicrotask vs. queueTask ordering is not disturbed inside unhandledrejection events'); + +// For workers, queueTask() involves posting tasks to other threads, so +// the following tests don't work there. + +if ('document' in self) { + + // For the next two see https://github.com/domenic/unhandled-rejections-browser-spec/issues/2#issuecomment-121121695 + // and the following comments. + + async_test(function(t) { + var sequenceOfEvents = []; + + addEventListener('unhandledrejection', l); + ensureCleanup(t, l); + + var p1 = Promise.reject(); + var p2; + queueTask(function() { + p2 = Promise.reject(); + queueTask(function() { + sequenceOfEvents.push('queueTask'); + checkSequence(); + }); + }); + + function l(ev) { + if (ev.promise === p1 || ev.promise === p2) { + sequenceOfEvents.push(ev.promise); + checkSequence(); + } + } + + function checkSequence() { + if (sequenceOfEvents.length === 3) { + t.step(function() { + assert_array_equals(sequenceOfEvents, [p1, 'queueTask', p2]); + }); + t.done(); + } + } + }, 'queueTask ordering vs. the task queued for unhandled rejection notification (1)'); + + async_test(function(t) { + var sequenceOfEvents = []; + + addEventListener('unhandledrejection', l); + ensureCleanup(t, l); + + var p2; + queueTask(function() { + p2 = Promise.reject(); + queueTask(function() { + sequenceOfEvents.push('queueTask'); + checkSequence(); + }); + }); + + function l(ev) { + if (ev.promise == p2) { + sequenceOfEvents.push(ev.promise); + checkSequence(); + } + } + + function checkSequence() { + if (sequenceOfEvents.length === 2) { + t.step(function() { + assert_array_equals(sequenceOfEvents, ['queueTask', p2]); + }); + t.done(); + } + } + }, 'queueTask ordering vs. the task queued for unhandled rejection notification (2)'); + + async_test(function(t) { + var sequenceOfEvents = []; + + + addEventListener('unhandledrejection', unhandled); + addEventListener('rejectionhandled', handled); + ensureCleanup(t, unhandled, handled); + + var p = Promise.reject(); + + function unhandled(ev) { + if (ev.promise === p) { + sequenceOfEvents.push('unhandled'); + checkSequence(); + setTimeout(function() { + queueTask(function() { + sequenceOfEvents.push('task before catch'); + checkSequence(); + }); + + p.catch(function() { + sequenceOfEvents.push('catch'); + checkSequence(); + }); + + queueTask(function() { + sequenceOfEvents.push('task after catch'); + checkSequence(); + }); + + sequenceOfEvents.push('after catch'); + checkSequence(); + }, 10); + } + } + + function handled(ev) { + if (ev.promise === p) { + sequenceOfEvents.push('handled'); + checkSequence(); + } + } + + function checkSequence() { + if (sequenceOfEvents.length === 6) { + t.step(function() { + assert_array_equals(sequenceOfEvents, + ['unhandled', 'after catch', 'catch', 'task before catch', 'handled', 'task after catch']); + }); + t.done(); + } + } + }, 'rejectionhandled is dispatched from a queued task, and not immediately'); +} + +// +// HELPERS +// + +// This function queues a task in "DOM manipulation task source" in window +// context, but not in workers. +function queueTask(f) { + if ('document' in self) { + var d = document.createElement("details"); + d.ontoggle = function() { + f(); + }; + d.setAttribute("open", ""); + } else { + // We need to fix this to use something that can queue tasks in + // "DOM manipulation task source" to ensure the order is correct + var channel = new MessageChannel(); + channel.port1.onmessage = function() { channel.port1.close(); f(); }; + channel.port2.postMessage('abusingpostmessageforfunandprofit'); + channel.port2.close(); + } +} + +function mutationObserverMicrotask(f) { + if ('document' in self) { + var observer = new MutationObserver(function() { f(); }); + var node = document.createTextNode(''); + observer.observe(node, { characterData: true }); + node.data = 'foo'; + } else { + // We don't have mutation observers on workers, so just post a promise-based + // microtask. + Promise.resolve().then(function() { f(); }); + } +} + +function onUnhandledSucceed(t, expectedReason, expectedPromiseGetter) { + var l = function(ev) { + if (ev.promise === expectedPromiseGetter()) { + t.step(function() { + assert_equals(ev.reason, expectedReason); + assert_equals(ev.promise, expectedPromiseGetter()); + }); + t.done(); + } + }; + addEventListener('unhandledrejection', l); + ensureCleanup(t, l); +} + +function onUnhandledFail(t, expectedPromiseGetter) { + var unhandled = function(evt) { + if (evt.promise === expectedPromiseGetter()) { + t.step(function() { + assert_unreached('unhandledrejection event is not supposed to be triggered'); + }); + } + }; + var handled = function(evt) { + if (evt.promise === expectedPromiseGetter()) { + t.step(function() { + assert_unreached('rejectionhandled event is not supposed to be triggered'); + }); + } + }; + addEventListener('unhandledrejection', unhandled); + addEventListener('rejectionhandled', handled); + ensureCleanup(t, unhandled, handled); + setTimeout(function() { + t.done(); + }, 10); +} + +function ensureCleanup(t, unhandled, handled) { + t.add_cleanup(function() { + if (unhandled) + removeEventListener('unhandledrejection', unhandled); + if (handled) + removeEventListener('rejectionhandled', handled); + }); +} + +done(); diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-parse-error.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-parse-error.html new file mode 100644 index 0000000000..3c21df49c9 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-parse-error.html @@ -0,0 +1,40 @@ +<!doctype html> +<html> + <head> + <title>window.onerror: parse errors</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <!-- + + In https://html.spec.whatwg.org/multipage/#creating-scripts , + step 3 describes parsing the script, and step 5 says: + # Otherwise, report the error using the onerror event handler of + # the script's global object. If the error is still not handled + # after this, then the error may be reported to the user. + which links to + https://html.spec.whatwg.org/multipage/#report-the-error , + which describes what to do when onerror is a Function. + + --> + </head> + <body> + + <div id="log"></div> + <script> + setup({allow_uncaught_exception:true}); + var error_count = 0; + window.onerror = function(msg, url, lineno) { + ++error_count; + test(function() {assert_equals(url, window.location.href)}, + "correct url passed to window.onerror"); + test(function() {assert_equals(lineno, 34)}, + "correct line number passed to window.onerror"); + }; + </script> + <script>This script does not parse correctly.</script> + <script> + test(function() {assert_equals(error_count, 1)}, + "correct number of calls to window.onerror"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-runtime-error-throw.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-runtime-error-throw.html new file mode 100644 index 0000000000..4b2bc1f22c --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-runtime-error-throw.html @@ -0,0 +1,43 @@ +<!doctype html> +<html> + <head> + <title>window.onerror: runtime scripterrors</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <!-- + + https://html.spec.whatwg.org/multipage/#runtime-script-errors + says what to do for uncaught runtime script errors, and just below + describes what to do when onerror is a Function. + + --> + </head> + <body> + + <div id="log"></div> + <script> + setup({allow_uncaught_exception:true}); + var error_count = 0; + window.onerror = function(msg, url, lineno) { + ++error_count; + test(function() {assert_equals(url, window.location.href)}, + "correct url passed to window.onerror"); + test(function() {assert_equals(lineno, 36)}, + "correct line number passed to window.onerror"); + }; + </script> + <script> + try { + // This error is caught, so it should NOT trigger onerror. + throw "foo"; + } catch (ex) { + } + // This error is NOT caught, so it should trigger onerror. + throw "bar"; + </script> + <script> + test(function() {assert_equals(error_count, 1)}, + "correct number of calls to window.onerror"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-runtime-error.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-runtime-error.html new file mode 100644 index 0000000000..1fdab521ae --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-runtime-error.html @@ -0,0 +1,43 @@ +<!doctype html> +<html> + <head> + <title>window.onerror: runtime scripterrors</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <!-- + + https://html.spec.whatwg.org/multipage/#runtime-script-errors + says what to do for uncaught runtime script errors, and just below + describes what to do when onerror is a Function. + + --> + </head> + <body> + + <div id="log"></div> + <script> + setup({allow_uncaught_exception:true}); + var error_count = 0; + window.onerror = function(msg, url, lineno) { + ++error_count; + test(function() {assert_equals(url, window.location.href)}, + "correct url passed to window.onerror"); + test(function() {assert_equals(lineno, 36)}, + "correct line number passed to window.onerror"); + }; + </script> + <script> + try { + // This error is caught, so it should NOT trigger onerror. + window.nonexistentproperty.oops(); + } catch (ex) { + } + // This error is NOT caught, so it should trigger onerror. + window.nonexistentproperty.oops(); + </script> + <script> + test(function() {assert_equals(error_count, 1)}, + "correct number of calls to window.onerror"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-1.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-1.html new file mode 100644 index 0000000000..65a1a02b11 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-1.html @@ -0,0 +1,33 @@ +<!doctype html> +<meta charset=utf-8> +<title> + When a listener from window A is added to an event target in window B via the + addEventListener function from window B, errors in that listener should be + reported to window A. +</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<iframe></iframe> +<iframe></iframe> +<script> +test(function() { + var f = new frames[0].Function("thereIsNoSuchCallable()"); + frames[1].document.addEventListener("myevent", f); + var frame0ErrorFired = false; + var frame1ErrorFired = false; + var ourErrorFired = false; + frames[0].addEventListener("error", function() { + frame0ErrorFired = true; + }); + frames[1].addEventListener("error", function() { + frame1ErrorFired = true; + }); + addEventListener("error", function() { + ourErrorFired = true; + }); + frames[1].document.dispatchEvent(new Event("myevent")); + assert_true(frame0ErrorFired); + assert_false(frame1ErrorFired); + assert_false(ourErrorFired); +}, "The error event from an event listener should fire on that listener's global"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-2.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-2.html new file mode 100644 index 0000000000..6c5476542b --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-2.html @@ -0,0 +1,33 @@ +<!doctype html> +<meta charset=utf-8> +<title> + When a listener from window A is added to an event target in window B via the + addEventListener function from window A, errors in that listener should be + reported to window A. +</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<iframe></iframe> +<iframe></iframe> +<script> +test(function() { + var f = new frames[0].Function("thereIsNoSuchCallable()"); + frames[0].document.addEventListener.call(frames[1].document, "myevent", f); + var frame0ErrorFired = false; + var frame1ErrorFired = false; + var ourErrorFired = false; + frames[0].addEventListener("error", function() { + frame0ErrorFired = true; + }); + frames[1].addEventListener("error", function() { + frame1ErrorFired = true; + }); + addEventListener("error", function() { + ourErrorFired = true; + }); + frames[1].document.dispatchEvent(new Event("myevent")); + assert_true(frame0ErrorFired); + assert_false(frame1ErrorFired); + assert_false(ourErrorFired); +}, "The error event from an event listener should fire on that listener's global"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-3.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-3.html new file mode 100644 index 0000000000..5e78baa8de --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-3.html @@ -0,0 +1,33 @@ +<!doctype html> +<meta charset=utf-8> +<title> + When a listener from window A is added to an event target in window A via the + addEventListener function from window A, errors in that listener should be + reported to window A. +</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<iframe></iframe> +<iframe></iframe> +<script> +test(function() { + var f = new frames[1].Function("thereIsNoSuchCallable()"); + frames[1].document.addEventListener("myevent", f); + var frame0ErrorFired = false; + var frame1ErrorFired = false; + var ourErrorFired = false; + frames[0].addEventListener("error", function() { + frame0ErrorFired = true; + }); + frames[1].addEventListener("error", function() { + frame1ErrorFired = true; + }); + addEventListener("error", function() { + ourErrorFired = true; + }); + frames[1].document.dispatchEvent(new Event("myevent")); + assert_false(frame0ErrorFired); + assert_true(frame1ErrorFired); + assert_false(ourErrorFired); +}, "The error event from an event listener should fire on that listener's global"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-4.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-4.html new file mode 100644 index 0000000000..a5f35d613f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-4.html @@ -0,0 +1,33 @@ +<!doctype html> +<meta charset=utf-8> +<title> + When a listener from window A is added to an event target in window A via the + addEventListener function from window B, errors in that listener should be + reported to window A. +</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<iframe></iframe> +<iframe></iframe> +<script> +test(function() { + var f = new frames[1].Function("thereIsNoSuchCallable()"); + frames[0].document.addEventListener.call(frames[1].document, "myevent", f); + var frame0ErrorFired = false; + var frame1ErrorFired = false; + var ourErrorFired = false; + frames[0].addEventListener("error", function() { + frame0ErrorFired = true; + }); + frames[1].addEventListener("error", function() { + frame1ErrorFired = true; + }); + addEventListener("error", function() { + ourErrorFired = true; + }); + frames[1].document.dispatchEvent(new Event("myevent")); + assert_false(frame0ErrorFired); + assert_true(frame1ErrorFired); + assert_false(ourErrorFired); +}, "The error event from an event listener should fire on that listener's global"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-5.html b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-5.html new file mode 100644 index 0000000000..da93e782ca --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/processing-model-2/window-onerror-with-cross-frame-event-listeners-5.html @@ -0,0 +1,25 @@ +<!doctype html> +<meta charset=utf-8> +<title>window.onerror listener reports the exception in global object of its callback</title> +<link rel=help href="https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<iframe></iframe> +<iframe></iframe> +<iframe></iframe> +<script> +setup({ allow_uncaught_exception: true }); + +window.onload = () => { + test(() => { + window.onerrorCalls = []; + window.onerror = () => { onerrorCalls.push("top"); }; + frames[0].onerror = new frames[1].Function(`top.onerrorCalls.push("frame0"); throw new parent.frames[2].Error("PASS");`); + frames[1].onerror = () => { onerrorCalls.push("frame1"); }; + frames[2].onerror = () => { onerrorCalls.push("frame2"); }; + + frames[0].dispatchEvent(new ErrorEvent("error", { error: new Error("foo") })); + assert_array_equals(onerrorCalls, ["frame0", "frame1"]); + }); +}; +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/reporterror-cross-realm-method.html b/testing/web-platform/tests/html/webappapis/scripting/reporterror-cross-realm-method.html new file mode 100644 index 0000000000..6e2c2aae8f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/reporterror-cross-realm-method.html @@ -0,0 +1,30 @@ +<!doctype html> +<meta charset="utf-8"> +<title>self.reportError() dispatches an "error" event for this's relevant global object</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/webappapis.html#dom-reporterror"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script> +setup({ allow_uncaught_exception: true }); + +async_test(t => { + window.addEventListener("error", t.unreached_func("'error' event should not be dispatched for top window!")); + + const iframe = document.createElement("iframe"); + iframe.onload = t.step_func_done(() => { + let eventFired = false; + const error = new TypeError("foo"); + const otherWindow = iframe.contentWindow; + otherWindow.addEventListener("error", t.step_func(event => { + assert_equals(event.error, error); + eventFired = true; + })); + + window.reportError.call(otherWindow, error); + assert_true(eventFired); + }); + document.body.append(iframe); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/reporterror-in-detached-window-crash.html b/testing/web-platform/tests/html/webappapis/scripting/reporterror-in-detached-window-crash.html new file mode 100644 index 0000000000..33662caa4d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/reporterror-in-detached-window-crash.html @@ -0,0 +1,11 @@ +<!doctype html> +<title>window.reportError() doesn't crash when window is detached</title> +<link rel="help" href="https://crbug.com/1445375"> +<body> +<script> +let i = document.createElement("iframe"); +document.body.appendChild(i); +let i_win = i.contentWindow; +i.remove(); +i_win.reportError("an error"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/scripting/reporterror.any.js b/testing/web-platform/tests/html/webappapis/scripting/reporterror.any.js new file mode 100644 index 0000000000..b9e7ba25bc --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/reporterror.any.js @@ -0,0 +1,49 @@ +setup({ allow_uncaught_exception:true }); + +[ + 1, + new TypeError(), + undefined +].forEach(throwable => { + test(t => { + let happened = false; + self.addEventListener("error", t.step_func(e => { + assert_true(e.message !== ""); + assert_equals(e.filename, new URL("reporterror.any.js", location.href).href); + assert_greater_than(e.lineno, 0); + assert_greater_than(e.colno, 0); + assert_equals(e.error, throwable); + happened = true; + }), { once:true }); + self.reportError(throwable); + assert_true(happened); + }, `self.reportError(${throwable})`); +}); + +test(() => { + assert_throws_js(TypeError, () => self.reportError()); +}, `self.reportError() (without arguments) throws`); + +test(() => { + // Workaround for https://github.com/web-platform-tests/wpt/issues/32105 + let invoked = false; + self.reportError({ + get name() { + invoked = true; + assert_unreached('get name') + }, + get message() { + invoked = true; + assert_unreached('get message'); + }, + get fileName() { + invoked = true; + assert_unreached('get fileName'); + }, + get lineNumber() { + invoked = true; + assert_unreached('get lineNumber'); + } + }); + assert_false(invoked); +}, `self.reportError() doesn't invoke getters`); diff --git a/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js new file mode 100644 index 0000000000..00a86fa74b --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js @@ -0,0 +1,45 @@ +/** + * Runs a collection of tests that determine if an API implements structured clone + * correctly. + * + * The `runner` parameter has the following properties: + * - `setup()`: An optional function run once before testing starts + * - `teardown()`: An option function run once after all tests are done + * - `preTest()`: An optional, async function run before a test + * - `postTest()`: An optional, async function run after a test is done + * - `structuredClone(obj, transferList)`: Required function that somehow + * structurally clones an object. + * Must return a promise. + * - `hasDocument`: When true, disables tests that require a document. True by default. + */ + +function runStructuredCloneBatteryOfTests(runner) { + const defaultRunner = { + setup() {}, + preTest() {}, + postTest() {}, + teardown() {}, + hasDocument: true + }; + runner = Object.assign({}, defaultRunner, runner); + + let setupPromise = runner.setup(); + const allTests = structuredCloneBatteryOfTests.map(test => { + + if (!runner.hasDocument && test.requiresDocument) { + return; + } + + return new Promise(resolve => { + promise_test(async t => { + test = await test; + await setupPromise; + await runner.preTest(test); + await test.f(runner, t) + await runner.postTest(test); + resolve(); + }, test.description); + }).catch(_ => {}); + }); + Promise.all(allTests).then(_ => runner.teardown()); +} diff --git a/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js new file mode 100644 index 0000000000..23cf4f651a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js @@ -0,0 +1,169 @@ +structuredCloneBatteryOfTests.push({ + description: 'ArrayBuffer', + async f(runner) { + const buffer = new Uint8Array([1]).buffer; + const copy = await runner.structuredClone(buffer, [buffer]); + assert_equals(buffer.byteLength, 0); + assert_equals(copy.byteLength, 1); + } +}); + +structuredCloneBatteryOfTests.push({ + description: 'MessagePort', + async f(runner) { + const {port1, port2} = new MessageChannel(); + const copy = await runner.structuredClone(port2, [port2]); + const msg = new Promise(resolve => port1.onmessage = resolve); + copy.postMessage('ohai'); + assert_equals((await msg).data, 'ohai'); + } +}); + +// TODO: ImageBitmap + +structuredCloneBatteryOfTests.push({ + description: 'A detached ArrayBuffer cannot be transferred', + async f(runner, t) { + const buffer = new ArrayBuffer(); + await runner.structuredClone(buffer, [buffer]); + await promise_rejects_dom( + t, + "DataCloneError", + runner.structuredClone(buffer, [buffer]) + ); + } +}); + +structuredCloneBatteryOfTests.push({ + description: 'A detached platform object cannot be transferred', + async f(runner, t) { + const {port1} = new MessageChannel(); + await runner.structuredClone(port1, [port1]); + await promise_rejects_dom( + t, + "DataCloneError", + runner.structuredClone(port1, [port1]) + ); + } +}); + +structuredCloneBatteryOfTests.push({ + description: 'Transferring a non-transferable platform object fails', + async f(runner, t) { + const blob = new Blob(); + await promise_rejects_dom( + t, + "DataCloneError", + runner.structuredClone(blob, [blob]) + ); + } +}); + +structuredCloneBatteryOfTests.push({ + description: 'An object whose interface is deleted from the global object must still be received', + async f(runner) { + const {port1} = new MessageChannel(); + const messagePortInterface = globalThis.MessagePort; + delete globalThis.MessagePort; + try { + const transfer = await runner.structuredClone(port1, [port1]); + assert_true(transfer instanceof messagePortInterface); + } finally { + globalThis.MessagePort = messagePortInterface; + } + } +}); + +structuredCloneBatteryOfTests.push({ + description: 'A subclass instance will be received as its closest transferable superclass', + async f(runner) { + // MessagePort doesn't have a constructor, so we must use something else. + + // Make sure that ReadableStream is transferable before we test its subclasses. + try { + const stream = new ReadableStream(); + await runner.structuredClone(stream, [stream]); + } catch(err) { + if (err instanceof DOMException && err.code === DOMException.DATA_CLONE_ERR) { + throw new OptionalFeatureUnsupportedError("ReadableStream isn't transferable"); + } else { + throw err; + } + } + + class ReadableStreamSubclass extends ReadableStream {} + const original = new ReadableStreamSubclass(); + const transfer = await runner.structuredClone(original, [original]); + assert_equals(Object.getPrototypeOf(transfer), ReadableStream.prototype); + } +}); + +structuredCloneBatteryOfTests.push({ + description: 'Resizable ArrayBuffer is transferable', + async f(runner) { + const buffer = new ArrayBuffer(16, { maxByteLength: 1024 }); + const copy = await runner.structuredClone(buffer, [buffer]); + assert_equals(buffer.byteLength, 0); + assert_equals(copy.byteLength, 16); + assert_equals(copy.maxByteLength, 1024); + assert_true(copy.resizable); + } +}); + +structuredCloneBatteryOfTests.push({ + description: 'Length-tracking TypedArray is transferable', + async f(runner) { + const ab = new ArrayBuffer(16, { maxByteLength: 1024 }); + const ta = new Uint8Array(ab); + const copy = await runner.structuredClone(ta, [ab]); + assert_equals(ab.byteLength, 0); + assert_equals(copy.buffer.byteLength, 16); + assert_equals(copy.buffer.maxByteLength, 1024); + assert_true(copy.buffer.resizable); + copy.buffer.resize(32); + assert_equals(copy.byteLength, 32); + } +}); + +structuredCloneBatteryOfTests.push({ + description: 'Length-tracking DataView is transferable', + async f(runner) { + const ab = new ArrayBuffer(16, { maxByteLength: 1024 }); + const dv = new DataView(ab); + const copy = await runner.structuredClone(dv, [ab]); + assert_equals(ab.byteLength, 0); + assert_equals(copy.buffer.byteLength, 16); + assert_equals(copy.buffer.maxByteLength, 1024); + assert_true(copy.buffer.resizable); + copy.buffer.resize(32); + assert_equals(copy.byteLength, 32); + } +}); + +structuredCloneBatteryOfTests.push({ + description: 'Transferring OOB TypedArray throws', + async f(runner, t) { + const ab = new ArrayBuffer(16, { maxByteLength: 1024 }); + const ta = new Uint8Array(ab, 8); + ab.resize(0); + await promise_rejects_dom( + t, + "DataCloneError", + runner.structuredClone(ta, [ab]) + ); + } +}); + +structuredCloneBatteryOfTests.push({ + description: 'Transferring OOB DataView throws', + async f(runner, t) { + const ab = new ArrayBuffer(16, { maxByteLength: 1024 }); + const dv = new DataView(ab, 8); + ab.resize(0); + await promise_rejects_dom( + t, + "DataCloneError", + runner.structuredClone(dv, [ab]) + ); + } +}); diff --git a/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests.js b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests.js new file mode 100644 index 0000000000..923ac9dc16 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-battery-of-tests.js @@ -0,0 +1,753 @@ +/* This file is mostly a remix of @zcorpan’s web worker test suite */ + +structuredCloneBatteryOfTests = []; + +function check(description, input, callback, requiresDocument = false) { + structuredCloneBatteryOfTests.push({ + description, + async f(runner) { + let newInput = input; + if (typeof input === 'function') { + newInput = input(); + } + const copy = await runner.structuredClone(newInput); + await callback(copy, newInput); + }, + requiresDocument + }); +} + +function compare_primitive(actual, input) { + assert_equals(actual, input); +} +function compare_Array(callback) { + return async function(actual, input) { + if (typeof actual === 'string') + assert_unreached(actual); + assert_true(actual instanceof Array, 'instanceof Array'); + assert_not_equals(actual, input); + assert_equals(actual.length, input.length, 'length'); + await callback(actual, input); + } +} + +function compare_Object(callback) { + return async function(actual, input) { + if (typeof actual === 'string') + assert_unreached(actual); + assert_true(actual instanceof Object, 'instanceof Object'); + assert_false(actual instanceof Array, 'instanceof Array'); + assert_not_equals(actual, input); + await callback(actual, input); + } +} + +function enumerate_props(compare_func) { + return async function(actual, input) { + for (const x in input) { + await compare_func(actual[x], input[x]); + } + }; +} + +check('primitive undefined', undefined, compare_primitive); +check('primitive null', null, compare_primitive); +check('primitive true', true, compare_primitive); +check('primitive false', false, compare_primitive); +check('primitive string, empty string', '', compare_primitive); +check('primitive string, lone high surrogate', '\uD800', compare_primitive); +check('primitive string, lone low surrogate', '\uDC00', compare_primitive); +check('primitive string, NUL', '\u0000', compare_primitive); +check('primitive string, astral character', '\uDBFF\uDFFD', compare_primitive); +check('primitive number, 0.2', 0.2, compare_primitive); +check('primitive number, 0', 0, compare_primitive); +check('primitive number, -0', -0, compare_primitive); +check('primitive number, NaN', NaN, compare_primitive); +check('primitive number, Infinity', Infinity, compare_primitive); +check('primitive number, -Infinity', -Infinity, compare_primitive); +check('primitive number, 9007199254740992', 9007199254740992, compare_primitive); +check('primitive number, -9007199254740992', -9007199254740992, compare_primitive); +check('primitive number, 9007199254740994', 9007199254740994, compare_primitive); +check('primitive number, -9007199254740994', -9007199254740994, compare_primitive); +check('primitive BigInt, 0n', 0n, compare_primitive); +check('primitive BigInt, -0n', -0n, compare_primitive); +check('primitive BigInt, -9007199254740994000n', -9007199254740994000n, compare_primitive); +check('primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n', -9007199254740994000900719925474099400090071992547409940009007199254740994000n, compare_primitive); + +check('Array primitives', [undefined, + null, + true, + false, + '', + '\uD800', + '\uDC00', + '\u0000', + '\uDBFF\uDFFD', + 0.2, + 0, + -0, + NaN, + Infinity, + -Infinity, + 9007199254740992, + -9007199254740992, + 9007199254740994, + -9007199254740994, + -12n, + -0n, + 0n], compare_Array(enumerate_props(compare_primitive))); +check('Object primitives', {'undefined':undefined, + 'null':null, + 'true':true, + 'false':false, + 'empty':'', + 'high surrogate':'\uD800', + 'low surrogate':'\uDC00', + 'nul':'\u0000', + 'astral':'\uDBFF\uDFFD', + '0.2':0.2, + '0':0, + '-0':-0, + 'NaN':NaN, + 'Infinity':Infinity, + '-Infinity':-Infinity, + '9007199254740992':9007199254740992, + '-9007199254740992':-9007199254740992, + '9007199254740994':9007199254740994, + '-9007199254740994':-9007199254740994}, compare_Object(enumerate_props(compare_primitive))); + +function compare_Boolean(actual, input) { + if (typeof actual === 'string') + assert_unreached(actual); + assert_true(actual instanceof Boolean, 'instanceof Boolean'); + assert_equals(String(actual), String(input), 'converted to primitive'); + assert_not_equals(actual, input); +} +check('Boolean true', new Boolean(true), compare_Boolean); +check('Boolean false', new Boolean(false), compare_Boolean); +check('Array Boolean objects', [new Boolean(true), new Boolean(false)], compare_Array(enumerate_props(compare_Boolean))); +check('Object Boolean objects', {'true':new Boolean(true), 'false':new Boolean(false)}, compare_Object(enumerate_props(compare_Boolean))); + +function compare_obj(what) { + const Type = self[what]; + return function(actual, input) { + if (typeof actual === 'string') + assert_unreached(actual); + assert_true(actual instanceof Type, 'instanceof '+what); + assert_equals(Type(actual), Type(input), 'converted to primitive'); + assert_not_equals(actual, input); + }; +} +check('String empty string', new String(''), compare_obj('String')); +check('String lone high surrogate', new String('\uD800'), compare_obj('String')); +check('String lone low surrogate', new String('\uDC00'), compare_obj('String')); +check('String NUL', new String('\u0000'), compare_obj('String')); +check('String astral character', new String('\uDBFF\uDFFD'), compare_obj('String')); +check('Array String objects', [new String(''), + new String('\uD800'), + new String('\uDC00'), + new String('\u0000'), + new String('\uDBFF\uDFFD')], compare_Array(enumerate_props(compare_obj('String')))); +check('Object String objects', {'empty':new String(''), + 'high surrogate':new String('\uD800'), + 'low surrogate':new String('\uDC00'), + 'nul':new String('\u0000'), + 'astral':new String('\uDBFF\uDFFD')}, compare_Object(enumerate_props(compare_obj('String')))); + +check('Number 0.2', new Number(0.2), compare_obj('Number')); +check('Number 0', new Number(0), compare_obj('Number')); +check('Number -0', new Number(-0), compare_obj('Number')); +check('Number NaN', new Number(NaN), compare_obj('Number')); +check('Number Infinity', new Number(Infinity), compare_obj('Number')); +check('Number -Infinity', new Number(-Infinity), compare_obj('Number')); +check('Number 9007199254740992', new Number(9007199254740992), compare_obj('Number')); +check('Number -9007199254740992', new Number(-9007199254740992), compare_obj('Number')); +check('Number 9007199254740994', new Number(9007199254740994), compare_obj('Number')); +check('Number -9007199254740994', new Number(-9007199254740994), compare_obj('Number')); +// BigInt does not have a non-throwing constructor +check('BigInt -9007199254740994n', Object(-9007199254740994n), compare_obj('BigInt')); + +check('Array Number objects', [new Number(0.2), + new Number(0), + new Number(-0), + new Number(NaN), + new Number(Infinity), + new Number(-Infinity), + new Number(9007199254740992), + new Number(-9007199254740992), + new Number(9007199254740994), + new Number(-9007199254740994)], compare_Array(enumerate_props(compare_obj('Number')))); +check('Object Number objects', {'0.2':new Number(0.2), + '0':new Number(0), + '-0':new Number(-0), + 'NaN':new Number(NaN), + 'Infinity':new Number(Infinity), + '-Infinity':new Number(-Infinity), + '9007199254740992':new Number(9007199254740992), + '-9007199254740992':new Number(-9007199254740992), + '9007199254740994':new Number(9007199254740994), + '-9007199254740994':new Number(-9007199254740994)}, compare_Object(enumerate_props(compare_obj('Number')))); + +function compare_Date(actual, input) { + if (typeof actual === 'string') + assert_unreached(actual); + assert_true(actual instanceof Date, 'instanceof Date'); + assert_equals(Number(actual), Number(input), 'converted to primitive'); + assert_not_equals(actual, input); +} +check('Date 0', new Date(0), compare_Date); +check('Date -0', new Date(-0), compare_Date); +check('Date -8.64e15', new Date(-8.64e15), compare_Date); +check('Date 8.64e15', new Date(8.64e15), compare_Date); +check('Array Date objects', [new Date(0), + new Date(-0), + new Date(-8.64e15), + new Date(8.64e15)], compare_Array(enumerate_props(compare_Date))); +check('Object Date objects', {'0':new Date(0), + '-0':new Date(-0), + '-8.64e15':new Date(-8.64e15), + '8.64e15':new Date(8.64e15)}, compare_Object(enumerate_props(compare_Date))); + +function compare_RegExp(expected_source) { + // XXX ES6 spec doesn't define exact serialization for `source` (it allows several ways to escape) + return function(actual, input) { + if (typeof actual === 'string') + assert_unreached(actual); + assert_true(actual instanceof RegExp, 'instanceof RegExp'); + assert_equals(actual.global, input.global, 'global'); + assert_equals(actual.ignoreCase, input.ignoreCase, 'ignoreCase'); + assert_equals(actual.multiline, input.multiline, 'multiline'); + assert_equals(actual.source, expected_source, 'source'); + assert_equals(actual.sticky, input.sticky, 'sticky'); + assert_equals(actual.unicode, input.unicode, 'unicode'); + assert_equals(actual.lastIndex, 0, 'lastIndex'); + assert_not_equals(actual, input); + } +} +function func_RegExp_flags_lastIndex() { + const r = /foo/gim; + r.lastIndex = 2; + return r; +} +function func_RegExp_sticky() { + return new RegExp('foo', 'y'); +} +function func_RegExp_unicode() { + return new RegExp('foo', 'u'); +} +check('RegExp flags and lastIndex', func_RegExp_flags_lastIndex, compare_RegExp('foo')); +check('RegExp sticky flag', func_RegExp_sticky, compare_RegExp('foo')); +check('RegExp unicode flag', func_RegExp_unicode, compare_RegExp('foo')); +check('RegExp empty', new RegExp(''), compare_RegExp('(?:)')); +check('RegExp slash', new RegExp('/'), compare_RegExp('\\/')); +check('RegExp new line', new RegExp('\n'), compare_RegExp('\\n')); +check('Array RegExp object, RegExp flags and lastIndex', [func_RegExp_flags_lastIndex()], compare_Array(enumerate_props(compare_RegExp('foo')))); +check('Array RegExp object, RegExp sticky flag', function() { return [func_RegExp_sticky()]; }, compare_Array(enumerate_props(compare_RegExp('foo')))); +check('Array RegExp object, RegExp unicode flag', function() { return [func_RegExp_unicode()]; }, compare_Array(enumerate_props(compare_RegExp('foo')))); +check('Array RegExp object, RegExp empty', [new RegExp('')], compare_Array(enumerate_props(compare_RegExp('(?:)')))); +check('Array RegExp object, RegExp slash', [new RegExp('/')], compare_Array(enumerate_props(compare_RegExp('\\/')))); +check('Array RegExp object, RegExp new line', [new RegExp('\n')], compare_Array(enumerate_props(compare_RegExp('\\n')))); +check('Object RegExp object, RegExp flags and lastIndex', {'x':func_RegExp_flags_lastIndex()}, compare_Object(enumerate_props(compare_RegExp('foo')))); +check('Object RegExp object, RegExp sticky flag', function() { return {'x':func_RegExp_sticky()}; }, compare_Object(enumerate_props(compare_RegExp('foo')))); +check('Object RegExp object, RegExp unicode flag', function() { return {'x':func_RegExp_unicode()}; }, compare_Object(enumerate_props(compare_RegExp('foo')))); +check('Object RegExp object, RegExp empty', {'x':new RegExp('')}, compare_Object(enumerate_props(compare_RegExp('(?:)')))); +check('Object RegExp object, RegExp slash', {'x':new RegExp('/')}, compare_Object(enumerate_props(compare_RegExp('\\/')))); +check('Object RegExp object, RegExp new line', {'x':new RegExp('\n')}, compare_Object(enumerate_props(compare_RegExp('\\n')))); + +function compare_Error(actual, input) { + assert_true(actual instanceof Error, "Checking instanceof"); + assert_equals(actual.constructor, input.constructor, "Checking constructor"); + assert_equals(actual.name, input.name, "Checking name"); + assert_equals(actual.hasOwnProperty("message"), input.hasOwnProperty("message"), "Checking message existence"); + assert_equals(actual.message, input.message, "Checking message"); + assert_equals(actual.foo, undefined, "Checking for absence of custom property"); +} + +check('Empty Error object', new Error, compare_Error); + +const errorConstructors = [Error, EvalError, RangeError, ReferenceError, + SyntaxError, TypeError, URIError]; +for (const constructor of errorConstructors) { + check(`${constructor.name} object`, () => { + let error = new constructor("Error message here"); + error.foo = "testing"; + return error; + }, compare_Error); +} + +async function compare_Blob(actual, input, expect_File) { + if (typeof actual === 'string') + assert_unreached(actual); + assert_true(actual instanceof Blob, 'instanceof Blob'); + if (!expect_File) + assert_false(actual instanceof File, 'instanceof File'); + assert_equals(actual.size, input.size, 'size'); + assert_equals(actual.type, input.type, 'type'); + assert_not_equals(actual, input); + const ab1 = await new Response(actual).arrayBuffer(); + const ab2 = await new Response(input).arrayBuffer(); + assert_equals(ab1.byteLength, ab2.byteLength, 'byteLength'); + const ta1 = new Uint8Array(ab1); + const ta2 = new Uint8Array(ab2); + for(let i = 0; i < ta1.size; i++) { + assert_equals(ta1[i], ta2[i]); + } +} +function func_Blob_basic() { + return new Blob(['foo'], {type:'text/x-bar'}); +} +check('Blob basic', func_Blob_basic, compare_Blob); + +function b(str) { + return parseInt(str, 2); +} +function encode_cesu8(codeunits) { + // http://www.unicode.org/reports/tr26/ section 2.2 + // only the 3-byte form is supported + const rv = []; + codeunits.forEach(function(codeunit) { + rv.push(b('11100000') + ((codeunit & b('1111000000000000')) >> 12)); + rv.push(b('10000000') + ((codeunit & b('0000111111000000')) >> 6)); + rv.push(b('10000000') + (codeunit & b('0000000000111111'))); + }); + return rv; +} +function func_Blob_bytes(arr) { + return function() { + const buffer = new ArrayBuffer(arr.length); + const view = new DataView(buffer); + for (let i = 0; i < arr.length; ++i) { + view.setUint8(i, arr[i]); + } + return new Blob([view]); + }; +} +check('Blob unpaired high surrogate (invalid utf-8)', func_Blob_bytes(encode_cesu8([0xD800])), compare_Blob); +check('Blob unpaired low surrogate (invalid utf-8)', func_Blob_bytes(encode_cesu8([0xDC00])), compare_Blob); +check('Blob paired surrogates (invalid utf-8)', func_Blob_bytes(encode_cesu8([0xD800, 0xDC00])), compare_Blob); + +function func_Blob_empty() { + return new Blob(['']); +} +check('Blob empty', func_Blob_empty , compare_Blob); +function func_Blob_NUL() { + return new Blob(['\u0000']); +} +check('Blob NUL', func_Blob_NUL, compare_Blob); + +check('Array Blob object, Blob basic', [func_Blob_basic()], compare_Array(enumerate_props(compare_Blob))); +check('Array Blob object, Blob unpaired high surrogate (invalid utf-8)', [func_Blob_bytes([0xD800])()], compare_Array(enumerate_props(compare_Blob))); +check('Array Blob object, Blob unpaired low surrogate (invalid utf-8)', [func_Blob_bytes([0xDC00])()], compare_Array(enumerate_props(compare_Blob))); +check('Array Blob object, Blob paired surrogates (invalid utf-8)', [func_Blob_bytes([0xD800, 0xDC00])()], compare_Array(enumerate_props(compare_Blob))); +check('Array Blob object, Blob empty', [func_Blob_empty()], compare_Array(enumerate_props(compare_Blob))); +check('Array Blob object, Blob NUL', [func_Blob_NUL()], compare_Array(enumerate_props(compare_Blob))); +check('Array Blob object, two Blobs', [func_Blob_basic(), func_Blob_empty()], compare_Array(enumerate_props(compare_Blob))); + +check('Object Blob object, Blob basic', {'x':func_Blob_basic()}, compare_Object(enumerate_props(compare_Blob))); +check('Object Blob object, Blob unpaired high surrogate (invalid utf-8)', {'x':func_Blob_bytes([0xD800])()}, compare_Object(enumerate_props(compare_Blob))); +check('Object Blob object, Blob unpaired low surrogate (invalid utf-8)', {'x':func_Blob_bytes([0xDC00])()}, compare_Object(enumerate_props(compare_Blob))); +check('Object Blob object, Blob paired surrogates (invalid utf-8)', {'x':func_Blob_bytes([0xD800, 0xDC00])() }, compare_Object(enumerate_props(compare_Blob))); +check('Object Blob object, Blob empty', {'x':func_Blob_empty()}, compare_Object(enumerate_props(compare_Blob))); +check('Object Blob object, Blob NUL', {'x':func_Blob_NUL()}, compare_Object(enumerate_props(compare_Blob))); + +async function compare_File(actual, input) { + assert_true(actual instanceof File, 'instanceof File'); + assert_equals(actual.name, input.name, 'name'); + assert_equals(actual.lastModified, input.lastModified, 'lastModified'); + await compare_Blob(actual, input, true); +} +function func_File_basic() { + return new File(['foo'], 'bar', {type:'text/x-bar', lastModified:42}); +} +check('File basic', func_File_basic, compare_File); + +function compare_FileList(actual, input) { + if (typeof actual === 'string') + assert_unreached(actual); + assert_true(actual instanceof FileList, 'instanceof FileList'); + assert_equals(actual.length, input.length, 'length'); + assert_not_equals(actual, input); + // XXX when there's a way to populate or construct a FileList, + // check the items in the FileList +} +function func_FileList_empty() { + const input = document.createElement('input'); + input.type = 'file'; + return input.files; +} +check('FileList empty', func_FileList_empty, compare_FileList, true); +check('Array FileList object, FileList empty', () => ([func_FileList_empty()]), compare_Array(enumerate_props(compare_FileList)), true); +check('Object FileList object, FileList empty', () => ({'x':func_FileList_empty()}), compare_Object(enumerate_props(compare_FileList)), true); + +function compare_ArrayBuffer(actual, input) { + assert_true(actual instanceof ArrayBuffer, 'instanceof ArrayBuffer'); + assert_equals(actual.byteLength, input.byteLength, 'byteLength'); + assert_equals(actual.maxByteLength, input.maxByteLength, 'maxByteLength'); + assert_equals(actual.resizable, input.resizable, 'resizable'); + assert_equals(actual.growable, input.growable, 'growable'); +} + +function compare_ArrayBufferView(view) { + const Type = self[view]; + return function(actual, input) { + if (typeof actual === 'string') + assert_unreached(actual); + assert_true(actual instanceof Type, 'instanceof '+view); + assert_equals(actual.length, input.length, 'length'); + assert_equals(actual.byteLength, input.byteLength, 'byteLength'); + assert_equals(actual.byteOffset, input.byteOffset, 'byteOffset'); + assert_not_equals(actual.buffer, input.buffer, 'buffer'); + for (let i = 0; i < actual.length; ++i) { + assert_equals(actual[i], input[i], 'actual['+i+']'); + } + }; +} +function compare_ImageData(actual, input) { + if (typeof actual === 'string') + assert_unreached(actual); + assert_equals(actual.width, input.width, 'width'); + assert_equals(actual.height, input.height, 'height'); + assert_not_equals(actual.data, input.data, 'data'); + compare_ArrayBufferView('Uint8ClampedArray')(actual.data, input.data, null); +} +function func_ImageData_1x1_transparent_black() { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + return ctx.createImageData(1, 1); +} +check('ImageData 1x1 transparent black', func_ImageData_1x1_transparent_black, compare_ImageData, true); +function func_ImageData_1x1_non_transparent_non_black() { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + const imagedata = ctx.createImageData(1, 1); + imagedata.data[0] = 100; + imagedata.data[1] = 101; + imagedata.data[2] = 102; + imagedata.data[3] = 103; + return imagedata; +} +check('ImageData 1x1 non-transparent non-black', func_ImageData_1x1_non_transparent_non_black, compare_ImageData, true); +check('Array ImageData object, ImageData 1x1 transparent black', () => ([func_ImageData_1x1_transparent_black()]), compare_Array(enumerate_props(compare_ImageData)), true); +check('Array ImageData object, ImageData 1x1 non-transparent non-black', () => ([func_ImageData_1x1_non_transparent_non_black()]), compare_Array(enumerate_props(compare_ImageData)), true); +check('Object ImageData object, ImageData 1x1 transparent black', () => ({'x':func_ImageData_1x1_transparent_black()}), compare_Object(enumerate_props(compare_ImageData)), true); +check('Object ImageData object, ImageData 1x1 non-transparent non-black', () => ({'x':func_ImageData_1x1_non_transparent_non_black()}), compare_Object(enumerate_props(compare_ImageData)), true); + + +check('Array sparse', new Array(10), compare_Array(enumerate_props(compare_primitive))); +check('Array with non-index property', function() { + const rv = []; + rv.foo = 'bar'; + return rv; +}, compare_Array(enumerate_props(compare_primitive))); +check('Object with index property and length', {'0':'foo', 'length':1}, compare_Object(enumerate_props(compare_primitive))); +function check_circular_property(prop) { + return function(actual) { + assert_equals(actual[prop], actual); + }; +} +check('Array with circular reference', function() { + const rv = []; + rv[0] = rv; + return rv; +}, compare_Array(check_circular_property('0'))); +check('Object with circular reference', function() { + const rv = {}; + rv['x'] = rv; + return rv; +}, compare_Object(check_circular_property('x'))); +function check_identical_property_values(prop1, prop2) { + return function(actual) { + assert_equals(actual[prop1], actual[prop2]); + }; +} +check('Array with identical property values', function() { + const obj = {} + return [obj, obj]; +}, compare_Array(check_identical_property_values('0', '1'))); +check('Object with identical property values', function() { + const obj = {} + return {'x':obj, 'y':obj}; +}, compare_Object(check_identical_property_values('x', 'y'))); + +function check_absent_property(prop) { + return function(actual) { + assert_false(prop in actual); + }; +} +check('Object with property on prototype', function() { + const Foo = function() {}; + Foo.prototype = {'foo':'bar'}; + return new Foo(); +}, compare_Object(check_absent_property('foo'))); + +check('Object with non-enumerable property', function() { + const rv = {}; + Object.defineProperty(rv, 'foo', {value:'bar', enumerable:false, writable:true, configurable:true}); + return rv; +}, compare_Object(check_absent_property('foo'))); + +function check_writable_property(prop) { + return function(actual, input) { + assert_equals(actual[prop], input[prop]); + actual[prop] += ' baz'; + assert_equals(actual[prop], input[prop] + ' baz'); + }; +} +check('Object with non-writable property', function() { + const rv = {}; + Object.defineProperty(rv, 'foo', {value:'bar', enumerable:true, writable:false, configurable:true}); + return rv; +}, compare_Object(check_writable_property('foo'))); + +function check_configurable_property(prop) { + return function(actual, input) { + assert_equals(actual[prop], input[prop]); + delete actual[prop]; + assert_false('prop' in actual); + }; +} +check('Object with non-configurable property', function() { + const rv = {}; + Object.defineProperty(rv, 'foo', {value:'bar', enumerable:true, writable:true, configurable:false}); + return rv; +}, compare_Object(check_configurable_property('foo'))); + +structuredCloneBatteryOfTests.push({ + description: 'Object with a getter that throws', + async f(runner, t) { + const exception = new Error(); + const testObject = { + get testProperty() { + throw exception; + } + }; + await promise_rejects_exactly( + t, + exception, + runner.structuredClone(testObject) + ); + } +}); + +/* The tests below are inspired by @zcorpan’s work but got some +more substantial changed due to their previous async setup */ + +function get_canvas_1x1_transparent_black() { + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + return canvas; +} + +function get_canvas_1x1_non_transparent_non_black() { + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d'); + const imagedata = ctx.getImageData(0, 0, 1, 1); + imagedata.data[0] = 100; + imagedata.data[1] = 101; + imagedata.data[2] = 102; + imagedata.data[3] = 103; + return canvas; +} + +function compare_ImageBitmap(actual, input) { + if (typeof actual === 'string') + assert_unreached(actual); + assert_true(actual instanceof ImageBitmap, 'instanceof ImageBitmap'); + assert_not_equals(actual, input); + // XXX paint the ImageBitmap on a canvas and check the data +} + +structuredCloneBatteryOfTests.push({ + description: 'ImageBitmap 1x1 transparent black', + async f(runner) { + const canvas = get_canvas_1x1_transparent_black(); + const bm = await createImageBitmap(canvas); + const copy = await runner.structuredClone(bm); + compare_ImageBitmap(bm, copy); + }, + requiresDocument: true +}); + +structuredCloneBatteryOfTests.push({ + description: 'ImageBitmap 1x1 non-transparent non-black', + async f(runner) { + const canvas = get_canvas_1x1_non_transparent_non_black(); + const bm = await createImageBitmap(canvas); + const copy = await runner.structuredClone(bm); + compare_ImageBitmap(bm, copy); + }, + requiresDocument: true +}); + +structuredCloneBatteryOfTests.push({ + description: 'Array ImageBitmap object, ImageBitmap 1x1 transparent black', + async f(runner) { + const canvas = get_canvas_1x1_transparent_black(); + const bm = [await createImageBitmap(canvas)]; + const copy = await runner.structuredClone(bm); + compare_Array(enumerate_props(compare_ImageBitmap))(bm, copy); + }, + requiresDocument: true +}); + +structuredCloneBatteryOfTests.push({ + description: 'Array ImageBitmap object, ImageBitmap 1x1 transparent non-black', + async f(runner) { + const canvas = get_canvas_1x1_non_transparent_non_black(); + const bm = [await createImageBitmap(canvas)]; + const copy = await runner.structuredClone(bm); + compare_Array(enumerate_props(compare_ImageBitmap))(bm, copy); + }, + requiresDocument: true +}); + +structuredCloneBatteryOfTests.push({ + description: 'Object ImageBitmap object, ImageBitmap 1x1 transparent black', + async f(runner) { + const canvas = get_canvas_1x1_transparent_black(); + const bm = {x: await createImageBitmap(canvas)}; + const copy = await runner.structuredClone(bm); + compare_Object(enumerate_props(compare_ImageBitmap))(bm, copy); + }, + requiresDocument: true +}); + +structuredCloneBatteryOfTests.push({ + description: 'Object ImageBitmap object, ImageBitmap 1x1 transparent non-black', + async f(runner) { + const canvas = get_canvas_1x1_non_transparent_non_black(); + const bm = {x: await createImageBitmap(canvas)}; + const copy = await runner.structuredClone(bm); + compare_Object(enumerate_props(compare_ImageBitmap))(bm, copy); + }, + requiresDocument: true +}); + +check('ObjectPrototype must lose its exotic-ness when cloned', + () => Object.prototype, + (copy, original) => { + assert_not_equals(copy, original); + assert_true(copy instanceof Object); + + const newProto = { some: 'proto' }; + // Must not throw: + Object.setPrototypeOf(copy, newProto); + + assert_equals(Object.getPrototypeOf(copy), newProto); + } +); + +structuredCloneBatteryOfTests.push({ + description: 'Serializing a non-serializable platform object fails', + async f(runner, t) { + const request = new Response(); + await promise_rejects_dom( + t, + "DataCloneError", + runner.structuredClone(request) + ); + } +}); + +structuredCloneBatteryOfTests.push({ + description: 'An object whose interface is deleted from the global must still deserialize', + async f(runner) { + const blob = new Blob(); + const blobInterface = globalThis.Blob; + delete globalThis.Blob; + try { + const copy = await runner.structuredClone(blob); + assert_true(copy instanceof blobInterface); + } finally { + globalThis.Blob = blobInterface; + } + } +}); + +check( + 'A subclass instance will deserialize as its closest serializable superclass', + () => { + class FileSubclass extends File {} + return new FileSubclass([], ""); + }, + (copy) => { + assert_equals(Object.getPrototypeOf(copy), File.prototype); + } +); + +check( + 'Resizable ArrayBuffer', + () => { + const ab = new ArrayBuffer(16, { maxByteLength: 1024 }); + assert_true(ab.resizable); + return ab; + }, + compare_ArrayBuffer); + +structuredCloneBatteryOfTests.push({ + description: 'Growable SharedArrayBuffer', + async f(runner) { + const sab = createBuffer('SharedArrayBuffer', 16, { maxByteLength: 1024 }); + assert_true(sab.growable); + try { + const copy = await runner.structuredClone(sab); + compare_ArrayBuffer(sab, copy); + } catch (e) { + // If we're cross-origin isolated, cloning SABs should not fail. + if (e instanceof DOMException && e.code === DOMException.DATA_CLONE_ERR) { + assert_false(self.crossOriginIsolated); + } else { + throw e; + } + } + } +}); + +check( + 'Length-tracking TypedArray', + () => { + const ab = new ArrayBuffer(16, { maxByteLength: 1024 }); + assert_true(ab.resizable); + return new Uint8Array(ab); + }, + compare_ArrayBufferView('Uint8Array')); + +check( + 'Length-tracking DataView', + () => { + const ab = new ArrayBuffer(16, { maxByteLength: 1024 }); + assert_true(ab.resizable); + return new DataView(ab); + }, + compare_ArrayBufferView('DataView')); + +structuredCloneBatteryOfTests.push({ + description: 'Serializing OOB TypedArray throws', + async f(runner, t) { + const ab = new ArrayBuffer(16, { maxByteLength: 1024 }); + const ta = new Uint8Array(ab, 8); + ab.resize(0); + await promise_rejects_dom( + t, + "DataCloneError", + runner.structuredClone(ta) + ); + } +}); + +structuredCloneBatteryOfTests.push({ + description: 'Serializing OOB DataView throws', + async f(runner, t) { + const ab = new ArrayBuffer(16, { maxByteLength: 1024 }); + const dv = new DataView(ab, 8); + ab.resize(0); + await promise_rejects_dom( + t, + "DataCloneError", + runner.structuredClone(dv) + ); + } +}); diff --git a/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-cross-realm-method.html b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-cross-realm-method.html new file mode 100644 index 0000000000..d80010e0df --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-cross-realm-method.html @@ -0,0 +1,20 @@ +<!doctype html> +<title>self.structuredClone() uses this's relevant Realm for deserialization</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/structured-data.html#structured-cloning"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script> +const iframe = document.createElement("iframe"); +iframe.onload = () => { + const otherWindow = iframe.contentWindow; + for (const key of ["Object", "Array", "Date", "RegExp"]) { + test(() => { + const cloned = otherWindow.structuredClone.call(window, new otherWindow[key]); + assert_true(cloned instanceof window[key]); + }, `${key} instance`); + } +}; +document.body.append(iframe); +</script> diff --git a/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-detached-window-crash.html b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-detached-window-crash.html new file mode 100644 index 0000000000..75971023d2 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone-detached-window-crash.html @@ -0,0 +1,11 @@ +<!doctype html> +<title>window.structuredClone() doesn't crash when window is detached</title> +<link rel="help" href="https://crbug.com/1445375"> +<body> +<script> +let i = document.createElement("iframe"); +document.body.appendChild(i); +let i_win = i.contentWindow; +i.remove(); +i_win.structuredClone("some data"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone.any.js b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone.any.js new file mode 100644 index 0000000000..1358a71fc0 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/structured-clone/structured-clone.any.js @@ -0,0 +1,14 @@ +// META: title=structuredClone() tests +// META: script=/common/sab.js +// META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests.js +// META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js +// META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js + +runStructuredCloneBatteryOfTests({ + structuredClone: (obj, transfer) => { + return new Promise(resolve => { + resolve(self.structuredClone(obj, { transfer })); + }); + }, + hasDocument: typeof document !== "undefined", +}); diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/clientinformation.window.js b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/clientinformation.window.js new file mode 100644 index 0000000000..3d3b6b5947 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/clientinformation.window.js @@ -0,0 +1,3 @@ +test(() => { + assert_equals(window.clientInformation, window.navigator); +}, "window.clientInformation exists and equals window.navigator"); diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/get-navigatorlanguage-manual.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/get-navigatorlanguage-manual.html new file mode 100644 index 0000000000..4bdab91121 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/get-navigatorlanguage-manual.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>NavigatorLanguage: navigator.language returns the user's preferred language</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#navigatorlanguage"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<h2>Precondition</h2> +<p>The user agent's preferred language is set as English (en).</p> +<div id="log"></div> +<script> + test(function() { + assert_equals(navigator.language, "en"); + }); +</script> + diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/historical.https.window.js b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/historical.https.window.js new file mode 100644 index 0000000000..f6b9db078e --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/historical.https.window.js @@ -0,0 +1,16 @@ +[ + "registerContentHandler", + "isProtocolHandlerRegistered", + "isContentHandlerRegistered", + "unregisterContentHandler" +].forEach(method => { + test(() => { + assert_false(method in self.navigator); + }, method + "() is removed"); +}); + +test(() => { + let called = false; + self.navigator.registerProtocolHandler("web+test", "%s", { toString: () => called = true }); + assert_false(called); +}, "registerProtocolHandler has no third argument"); diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-indexed.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-indexed.html new file mode 100644 index 0000000000..a971fe9d1c --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-indexed.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Test for lack of indexed getter on Navigator</title> +<link rel="author" title="Ms2ger" href="mailto:Ms2ger@gmail.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-navigator-object"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +test(function() { + assert_false("0" in window.navigator); + assert_equals(window.navigator[0], undefined); +}, "window.navigator[0] should not exist"); +test(function() { + window.navigator[0] = "pass"; + assert_true("0" in window.navigator); + assert_equals(window.navigator[0], "pass"); +}, "window.navigator[0] should be settable"); +test(function() { + assert_false("-1" in window.navigator); + assert_equals(window.navigator[-1], undefined); +}, "window.navigator[-1] should not exist"); +test(function() { + window.navigator[-1] = "pass"; + assert_true("-1" in window.navigator); + assert_equals(window.navigator[-1], "pass"); +}, "window.navigator[-1] should be settable"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-window-controls-overlay.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-window-controls-overlay.html new file mode 100644 index 0000000000..9cff8d3163 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-window-controls-overlay.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset='utf-8'> +<title>navigator.windowControlsOverlay</title> + +<script src='/resources/testharness.js'></script> +<script src='/resources/testharnessreport.js'></script> + +<script> + test(function(){ + assert_idl_attribute(navigator, 'windowControlsOverlay'); + }, 'the windowControlsOverlay object should exist on the navigator object'); + + test(function(){ + assert_idl_attribute(navigator.windowControlsOverlay, 'visible'); + }, 'visible should be a member of the windowControlsOverlay object'); + + test(function(){ + assert_false(navigator.windowControlsOverlay.visible); + }, 'visible should be false'); + + test(function(){ + assert_idl_attribute(navigator.windowControlsOverlay, 'getTitlebarAreaRect'); + }, 'getTitlebarAreaRect should be a method of the windowControlsOverlay object'); + + test(function(){ + var rect = navigator.windowControlsOverlay.getTitlebarAreaRect(); + assert_true(rect instanceof DOMRect); + }, 'getTitlebarAreaRect return type should be DOMRect'); + + test(function(){ + var rect = navigator.windowControlsOverlay.getTitlebarAreaRect(); + assert_equals(rect.x, 0); + assert_equals(rect.y, 0); + assert_equals(rect.width, 0); + assert_equals(rect.height, 0); + }, 'getTitlebarAreaRect should return a empty DOMRect'); + + test(function(){ + assert_idl_attribute(navigator.windowControlsOverlay, 'ongeometrychange'); + }, 'ongeometrychange should be a member of the windowControlsOverlay object'); +</script> diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator.any.js b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator.any.js new file mode 100644 index 0000000000..07bccb7880 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator.any.js @@ -0,0 +1,106 @@ + var compatibilityMode; + if (navigator.userAgent.includes("Chrome")) { + compatibilityMode = "Chrome"; + } else if (navigator.userAgent.includes("WebKit")) { + compatibilityMode = "WebKit"; + } else { + compatibilityMode = "Gecko"; + } + + test(function() { + assert_equals(navigator.appCodeName, "Mozilla"); + }, "appCodeName"); + + test(function() { + assert_equals(navigator.appName, "Netscape"); + }, "appName"); + + test(function() { + assert_equals(typeof navigator.appVersion, "string", + "navigator.appVersion should be a string"); + }, "appVersion"); + + test(function() { + assert_equals(typeof navigator.platform, "string", + "navigator.platform should be a string"); + }, "platform"); + + test(function() { + assert_equals(navigator.product, "Gecko"); + }, "product"); + + test(function() { + if ("window" in self) { + if (compatibilityMode == "Gecko") { + assert_equals(navigator.productSub, "20100101"); + } else { + assert_equals(navigator.productSub, "20030107"); + } + } else { + assert_false("productSub" in navigator); + } + }, "productSub"); + + test(function() { + assert_equals(typeof navigator.userAgent, "string", + "navigator.userAgent should be a string"); + }, "userAgent type"); + + async_test(function() { + var request = new XMLHttpRequest(); + request.onload = this.step_func_done(function() { + assert_equals("User-Agent: " + navigator.userAgent + "\n", + request.response, + "userAgent should return the value sent in the " + + "User-Agent header"); + }); + request.open("GET", "/xhr/resources/inspect-headers.py?" + + "filter_name=User-Agent"); + request.send(); + }, "userAgent value"); + + test(function() { + if ("window" in self) { + if (compatibilityMode == "Chrome") { + assert_equals(navigator.vendor, "Google Inc."); + } else if (compatibilityMode == "WebKit") { + assert_equals(navigator.vendor, "Apple Computer, Inc."); + } else { + assert_equals(navigator.vendor, ""); + } + } else { + assert_false("vendor" in navigator); + } + }, "vendor"); + + test(function() { + if ("window" in self) { + assert_equals(navigator.vendorSub, ""); + } else { + assert_false("vendorSub" in navigator); + } + }, "vendorSub"); + + // "If the navigator compatibility mode is Gecko, then the user agent must + // also support the following partial interface" (taintEnabled() and oscpu) + // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=22555 and + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=27820 + + test(function() { + if ("window" in self && compatibilityMode == "Gecko") { + assert_false(navigator.taintEnabled()); + } else { + assert_false("taintEnabled" in navigator); + } + }, "taintEnabled"); + + test(function() { + if ("window" in self && compatibilityMode == "Gecko") { + assert_equals(typeof navigator.oscpu, "string", + "navigator.oscpu should be a string"); + } else { + assert_false("oscpu" in navigator); + } + }, "oscpu"); + +done() diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator_user_agent.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator_user_agent.https.html new file mode 100644 index 0000000000..b015d24e50 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator_user_agent.https.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + test(t => { + assert_true("userAgentData" in navigator); + assert_true("NavigatorUAData" in window); + assert_equals(typeof self.NavigatorUAData, "function") + }, "navigator.userAgentData is exposed."); + + promise_test(async t => { + let didMicrotaskRun = false; + const uaData = navigator.userAgentData; + const brandRegex = /^[a-zA-Z ()\-.\/:;=?_]+$/; + for (brandVersionPair of uaData.brands) { + assert_equals(typeof brandVersionPair.brand, "string", "brand should be a string"); + assert_regexp_match(brandVersionPair.brand, brandRegex, "brand should not contain unexpected characters"); + assert_equals(typeof brandVersionPair.version, "string", "version should be a string"); + } + assert_equals(typeof uaData.mobile, "boolean", "mobile should be a boolean"); + const highEntropyData = await uaData.getHighEntropyValues(["platformVersion", "architecture", "model", "uaFullVersion", "fullVersionList"]); + assert_equals(typeof highEntropyData["platform"], "string", "Platform brand should be a string"); + assert_equals(typeof highEntropyData["platformVersion"], "string", "Platform version should be a string"); + assert_equals(typeof highEntropyData["architecture"], "string", "Architecture should be a string"); + assert_equals(typeof highEntropyData["model"], "string", "Model should be a string"); + assert_equals(typeof highEntropyData["uaFullVersion"], "string", "UAFullVersion should be a string"); + for (brandVersionPair of highEntropyData['fullVersionList']) { + assert_equals(typeof brandVersionPair.brand, "string", "brand should be a string"); + assert_regexp_match(brandVersionPair.brand, brandRegex, "brand should not contain unexpected characters"); + assert_equals(typeof brandVersionPair.version, "string", "version should be a string"); + } + const highEntropyData2 = await uaData.getHighEntropyValues([]); + assert_equals(typeof highEntropyData["platform"], "string", "Platform brand should be a string"); + assert_false("platformVersion" in highEntropyData2, "Platform version should be an empty string"); + assert_false("architecture" in highEntropyData2, "Architecture should be an empty string"); + assert_false("model" in highEntropyData2, "Model should be an empty string"); + assert_false("uaFullVersion" in highEntropyData2, "UAFullVersion should be an empty string"); + assert_false("fullVersionList" in highEntropyData2, "fullVersionList should be an empty string"); + let finalPromise = uaData.getHighEntropyValues([]).then(() => { + assert_true(didMicrotaskRun, "getHighEntropyValues queued on a task"); + }); + await Promise.resolve().then(function() { + didMicrotaskRun = true; + }); + return finalPromise; + }, "navigator.userAgentData returns a UserAgentMetadata object."); +</script> diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator_user_agent.tentative.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator_user_agent.tentative.html new file mode 100644 index 0000000000..dd4c531070 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator_user_agent.tentative.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + test(t => { + assert_false("getUserAgent" in navigator); + }, "navigator.getUserAgent() is not available in non-secure contexts."); +</script> diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorcookies-cookieenabled-false-manual.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorcookies-cookieenabled-false-manual.html new file mode 100644 index 0000000000..adcea90a36 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorcookies-cookieenabled-false-manual.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>navigator.cookieEnabled false</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://html.spec.whatwg.org/#dom-navigator-cookieenabled"> +<meta name="flags" content="interact"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<h2>Preconditions</h2> +<p>Disable cookies in browser settings.</p> + +<script> + test(() => { + assert_false(navigator.cookieEnabled); + }, "navigator.cookieEnabled is false when cookies are disabled"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorcookies-cookieenabled-true.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorcookies-cookieenabled-true.html new file mode 100644 index 0000000000..fc7f49d143 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorcookies-cookieenabled-true.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>navigator.cookieEnabled true</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://html.spec.whatwg.org/#dom-navigator-cookieenabled"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> + test(() => { + assert_true(navigator.cookieEnabled); + }, "navigator.cookieEnabled is true when cookies are enabled"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorlanguage.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorlanguage.html new file mode 100644 index 0000000000..d56df8a3d8 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigatorlanguage.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>NavigatorLanguage: the most preferred language is the one returned by navigator.language</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#navigatorlanguage"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> + test(function() { + assert_true("language" in navigator); + assert_true("languages" in navigator); + + assert_equals(navigator.languages[0], navigator.language, + "navigator.languages is the most preferred language first"); + + }); +</script> + diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/per-global.window.js b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/per-global.window.js new file mode 100644 index 0000000000..016aef8c4e --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/per-global.window.js @@ -0,0 +1,4 @@ +// META: script=/common/object-association.js + +testIsPerWindow("navigator"); +testIsPerWindow("clientInformation"); diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/plugins-and-mimetypes.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/plugins-and-mimetypes.html new file mode 100644 index 0000000000..af05015801 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/plugins-and-mimetypes.html @@ -0,0 +1,83 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Navigator.plugins and navigator.mimeTypes behavior</title> +<link rel="author" href="mailto:domenic@chromium.org"> +<link rel="help" href="https://github.com/whatwg/html/pull/6738"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script> +test(() => { + assert_true('pdfViewerEnabled' in navigator, "property exists"); + assert_equals(typeof navigator.pdfViewerEnabled, 'boolean', "property is boolean"); +}, "navigator.pdfViewerEnabled exists"); + +const pluginNames = [ + "PDF Viewer", + "Chrome PDF Viewer", + "Chromium PDF Viewer", + "Microsoft Edge PDF Viewer", + "WebKit built-in PDF" +]; + +const mimeTypes = [ + "application/pdf", + "text/pdf" +]; + +if (navigator.pdfViewerEnabled) { + test(() => { + assert_equals(navigator.mimeTypes.length, mimeTypes.length, "length"); + + for (let i = 0; i < mimeTypes.length; ++i) { + const mimeType = mimeTypes[i]; + const mimeTypeObject = navigator.mimeTypes.item(i); + + assert_equals(mimeTypeObject.type, mimeType, `${i}th type`); + assert_equals(mimeTypeObject.description, "Portable Document Format", `${i}th description`); + assert_equals(mimeTypeObject.suffixes, "pdf", `${i}th suffixes`); + assert_equals(mimeTypeObject, navigator.mimeTypes.namedItem(mimeType), `mimeTypes.item(${i}) matches namedItem("${mimeType}")`); + assert_equals(mimeTypeObject.enabledPlugin, navigator.plugins[0], `${i}th enabledPlugin matches 0th Plugin`) + } + }, "navigator.mimeTypes contains the hard-coded list"); + + test(() => { + assert_equals(navigator.plugins.length, pluginNames.length, "length"); + + for (let i = 0; i < pluginNames.length; ++i) { + const pluginName = pluginNames[i]; + const pluginObject = navigator.plugins.item(i); + + assert_equals(pluginObject.name, pluginName, `${i}th name`); + assert_equals(pluginObject.description, "Portable Document Format", `${i}th description`); + assert_equals(pluginObject.filename, "internal-pdf-viewer", `${i}th filename`); + assert_equals(pluginObject, navigator.plugins.namedItem(pluginName), `plugins.item(${i}) matches namedItem("${pluginName}")`); + + for (let j = 0; j < mimeTypes.length; ++j) { + const mimeType = mimeTypes[j]; + assert_equals(pluginObject.item(j).type, navigator.mimeTypes[j].type, `item(${j}) on plugin(${i}) (${pluginObject.name})`); + assert_equals(pluginObject.namedItem(mimeType).type, navigator.mimeTypes.item(j).type, `namedItem("${mimeType}") on plugin(${i})`); + } + } + }, "navigator.plugins contains the hard-coded list"); +} else { + test(() => { + assert_equals(navigator.mimeTypes.length, 0, "length"); + assert_equals(navigator.mimeTypes.item(0), null, "item"); + + for (const mimeType of mimeTypes) { + assert_equals(navigator.mimeTypes.namedItem(mimeType), null, `namedItem("${mimeType}")`); + } + }, "navigator.mimeTypes is empty"); + + test(() => { + assert_equals(navigator.plugins.length, 0, "length"); + assert_equals(navigator.plugins.item(0), null, "item"); + + for (const pluginName of pluginNames) { + assert_equals(navigator.plugins.namedItem(pluginName), null, `namedItem("${pluginName}")`); + } + }, "navigator.plugins is empty"); +} +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-manual.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-manual.https.html new file mode 100644 index 0000000000..1561786569 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-manual.https.html @@ -0,0 +1,20 @@ +<!doctype html> +<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed --> +<meta charset=windows-1254> +<meta name=timeout content=long> +<title>registerProtocolHandler() and a handler with %s in the fragment</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/service-workers/service-worker/resources/test-helpers.sub.js></script> +<script> +// Configure expectations for individual test +window.type = "fragment"; +window.noSW = false; +</script> +<script src=resources/handler-tools.js></script> +<ol> + <li><p>First, register the handler: <button onclick='register()'>Register</button>. + <li><p>Then, run the test: <button onclick='runTest()'>Run</button>. + <li><p>Or, run the test with U+0000 NULL: <button onclick='runTest({ includeNull: true })'>Run NULL</button>. +</ol> +<div id=log></div> diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw-manual.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw-manual.https.html new file mode 100644 index 0000000000..be3a6be666 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw-manual.https.html @@ -0,0 +1,20 @@ +<!doctype html> +<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed --> +<meta charset=windows-1254> +<meta name=timeout content=long> +<title>registerProtocolHandler() and a handler with %s in the fragment (does not use a service worker)</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/service-workers/service-worker/resources/test-helpers.sub.js></script> +<script> +// Configure expectations for individual test +window.type = "fragment"; +window.noSW = true; +</script> +<script src=resources/handler-tools.js></script> +<ol> + <li><p>First, register the handler: <button onclick='register()'>Register</button>. + <li><p>Then, run the test: <button onclick='runTest()'>Run</button>. + <li><p>Or, run the test with U+0000 NULL: <button onclick='runTest({ includeNull: true })'>Run NULL</button>. +</ol> +<div id=log></div> diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path-manual.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path-manual.https.html new file mode 100644 index 0000000000..085c5723ec --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path-manual.https.html @@ -0,0 +1,20 @@ +<!doctype html> +<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed --> +<meta charset=windows-1254> +<meta name=timeout content=long> +<title>registerProtocolHandler() and a handler with %s in the path</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/service-workers/service-worker/resources/test-helpers.sub.js></script> +<script> +// Configure expectations for individual test +window.type = "path"; +window.noSW = false; +</script> +<script src=resources/handler-tools.js></script> +<ol> + <li><p>First, register the handler: <button onclick='register()'>Register</button>. + <li><p>Then, run the test: <button onclick='runTest()'>Run</button>. + <li><p>Or, run the test with U+0000 NULL: <button onclick='runTest({ includeNull: true })'>Run NULL</button>. +</ol> +<div id=log></div> diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-manual.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-manual.https.html new file mode 100644 index 0000000000..8ce65a5bad --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-manual.https.html @@ -0,0 +1,20 @@ +<!doctype html> +<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed --> +<meta charset=windows-1254> +<meta name=timeout content=long> +<title>registerProtocolHandler() and a handler with %s in the query</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/service-workers/service-worker/resources/test-helpers.sub.js></script> +<script> +// Configure expectations for individual test +window.type = "query"; +window.noSW = false; +</script> +<script src=resources/handler-tools.js></script> +<ol> + <li><p>First, register the handler: <button onclick='register()'>Register</button>. + <li><p>Then, run the test: <button onclick='runTest()'>Run</button>. + <li><p>Or, run the test with U+0000 NULL: <button onclick='runTest({ includeNull: true })'>Run NULL</button>. +</ol> +<div id=log></div> diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw-manual.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw-manual.https.html new file mode 100644 index 0000000000..9b4473fb89 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw-manual.https.html @@ -0,0 +1,20 @@ +<!doctype html> +<!-- Use a non-UTF-8 encoding to see how the handler URL is parsed --> +<meta charset=windows-1254> +<meta name=timeout content=long> +<title>registerProtocolHandler() and a handler with %s in the query (does not use a service worker)</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/service-workers/service-worker/resources/test-helpers.sub.js></script> +<script> +// Configure expectations for individual test +window.type = "query"; +window.noSW = true; +</script> +<script src=resources/handler-tools.js></script> +<ol> + <li><p>First, register the handler: <button onclick='register()'>Register</button>. + <li><p>Then, run the test: <button onclick='runTest()'>Run</button>. + <li><p>Or, run the test with U+0000 NULL: <button onclick='runTest({ includeNull: true })'>Run NULL</button>. +</ol> +<div id=log></div> diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html new file mode 100644 index 0000000000..cc16260d70 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html @@ -0,0 +1,217 @@ +<!DOCTYPE html> +<meta charset='utf-8'> +<title>protocol handlers</title> + +<script src='/resources/testharness.js'></script> +<script src='/resources/testharnessreport.js'></script> + +<noscript><p>Enable JavaScript and reload.</p></noscript> + +<p><strong>Note:</strong> If your browser limits the number of handler +registration requests on a page, you might need to disable or significantly +increase that limit for the tests below to run.</p> + +<script> +test(() => { + assert_idl_attribute(navigator, 'registerProtocolHandler'); +}, 'the registerProtocolHandler method should exist on the navigator object'); + +test(() => { + assert_idl_attribute(navigator, 'unregisterProtocolHandler'); +}, 'the unregisterProtocolHandler method should exist on the navigator object'); + +/* URL argument */ +[ + '%s', + 'foo/%s', + `%s${location.href}`, + location.href.replace(location.protocol, + `${location.protocol[0]}%s${location.protocol.substring(1)}`), + location.href.replace(location.protocol, `${location.protocol}%s`), + location.href + '/%s', + location.href + '#%s', + location.href + '?foo=%s', + location.href + '?foo=%s&bar', + location.href + '/%s/bar/baz/', + location.href + '/%s/bar/baz/?foo=1337&bar#baz', + location.href + '/%s/foo/%s/', +].forEach(url => { + test(() => { + navigator.registerProtocolHandler('tel', url, 'foo'); + }, 'registerProtocolHandler: Valid URL "' + url + '" should work.'); + + test(() => { + navigator.unregisterProtocolHandler('tel', url); + }, 'unregisterProtocolHandler: Valid URL "' + url + '" should work.'); +}); + +/* Invalid URLs */ +[ + '', + '%S', + 'http://%s.com', + 'http://%s.example.com', + location.href.replace(location.hostname, `%s${location.hostname}`), + location.href.replace(location.port, `%s${location.port}`), + location.href + '', + location.href + '/%', + location.href + '/%a', + 'http://example.com', + 'http://[v8.:::]//url=%s', + 'https://test:test/', +].forEach(url => { + test(() => { + assert_throws_dom('SYNTAX_ERR', () => { navigator.registerProtocolHandler('mailto', url, 'foo'); }); + assert_throws_dom('SECURITY_ERR', () => { navigator.registerProtocolHandler('x', url, 'foo'); }); + }, `registerProtocolHandler: Invalid URL "${url}" should throw (but after scheme)`); + + test(() => { + assert_throws_dom('SYNTAX_ERR', () => { navigator.unregisterProtocolHandler('mailto', url); }); + assert_throws_dom('SECURITY_ERR', () => { navigator.unregisterProtocolHandler('x', url, 'foo'); }); + }, `unregisterProtocolHandler: Invalid URL "${url}" should throw (but after scheme)`); +}); + +[ + 'http://example.com/%s', + 'https://example.com/%s', + 'http://foobar.example.com/%s', + 'mailto:%s@example.com', + 'mailto:%s', + `ftp://${location.host}/%s`, + `chrome://${location.host}/%s`, + `foo://${location.host}/%s`, + URL.createObjectURL(new Blob()) + "#%s", +].forEach(url => { + const title = url.startsWith("blob:") ? "blob: URL" : url; + test(() => { + assert_throws_dom('SECURITY_ERR', () => { navigator.registerProtocolHandler('mailto', url, 'foo'); }); + }, `registerProtocolHandler: Invalid URL "${title}" should throw SECURITY_ERR.`); + + test(() => { + assert_throws_dom('SECURITY_ERR', () => { navigator.unregisterProtocolHandler('mailto', url); }); + }, `unregisterProtocolHandler: Invalid URL "${title}" should throw SECURITY_ERR.`); +}); + +/* Protocol argument */ + +/* Overriding any of the following protocols must never be allowed. That would + * break the browser. */ +[ + 'about', + 'attachment', + 'blob', + 'chrome', + 'cid', + 'data', + 'file', + 'ftp', + 'http', + 'https', + 'javascript', + 'livescript', + 'mid', + 'mocha', + 'moz-icon', + 'opera', + 'operamail', + 'res', + 'resource', + 'shttp', + 'tcl', + 'vbscript', + 'view-source', + 'ws', + 'wss', + 'wyciwyg', + /* other invalid schemes */ + 'unrecognized', + 'mаilto', /* a cyrillic "а" */ + 'mailto:', + 'mailto://', + 'mailto' + String.fromCharCode(0), + 'mailtoo' + String.fromCharCode(8), + 'mailto' + String.fromCharCode(10), + 'http://', + 'ssh:/', + 'magnet:+', + 'tel:sip', + 'foo', + 'fweb+oo', + /* web+ prefixed schemes must be followed by 1+ ascii alphas */ + 'web+', + 'web+1', + 'web+namewithid123', + 'web+namewithtrailingspace ', + 'web+préfixewithaccent', // é is not ascii alpha + 'web+Kelvinsign', // ASCII-lower KELVIN SIGN is not k + 'web+latinsmallletterlongſ', // ASCII-lower LATIN SMALL LETTER LONG S is not s + 'web+dots.are.forbidden', + 'web+dashes-are-forbidden', + 'web+underscores_are_forbidden', + 'web+spaces are forbidden', + 'web+non*alpha*are*forbidden', + 'web+digits123areforbidden', +].forEach(scheme => { + test(() => { + // https://test:test/ does not parse and does not contain %s, but the scheme check happens first + assert_throws_dom('SECURITY_ERR', () => { navigator.registerProtocolHandler(scheme, 'https://test:test/', 'foo'); }); + }, 'registerProtocolHandler: Attempting to override the "' + scheme + '" protocol should throw SECURITY_ERR.'); + + test(() => { + assert_throws_dom('SECURITY_ERR', () => { navigator.unregisterProtocolHandler(scheme, 'https://test:test/'); }); + }, 'unregisterProtocolHandler: Attempting to override the "' + scheme + '" protocol should throw SECURITY_ERR.'); +}); + +/* The following protocols must be possible to override. + * We're just testing that the call goes through here. Whether or not they + * actually work as handlers is covered by the interactive tests. */ + +[ + /* safelisted schemes listed in + * https://html.spec.whatwg.org/multipage/system-state.html#safelisted-scheme */ + 'bitcoin', + 'geo', + 'im', + 'irc', + 'ircs', + 'magnet', + 'mailto', + 'matrix', + 'mms', + 'news', + 'nntp', + 'openpgp4fpr', + 'sip', + 'sms', + 'smsto', + 'ssh', + 'tel', + 'urn', + 'webcal', + 'wtai', + 'xmpp', + /* other valid schemes */ + 'BitcoIn', + 'Irc', + 'MagneT', + 'Matrix', + 'SmsTo', + 'TEL', + 'teL', + 'WebCAL', + 'WTAI', + 'web+myprotocol', + 'web+abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', // all alphas + 'web+UpperCasedIsLowercased', + 'WEB+seeabove', + 'WeB+SeEaBoVe' +].forEach(scheme => { + test(() => { + navigator.registerProtocolHandler(scheme, location.href + '/%s', "foo"); + }, 'registerProtocolHandler: overriding the "' + scheme + '" protocol should work'); + + test(() => { + navigator.unregisterProtocolHandler(scheme, location.href + '/%s'); + }, 'unregisterProtocolHandler: overriding the "' + scheme + '" protocol should work'); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.tentative.https.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.tentative.https.html new file mode 100644 index 0000000000..0120aaa12f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.tentative.https.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<meta charset='utf-8'> +<title>protocol handlers</title> + +<script src='/resources/testharness.js'></script> +<script src='/resources/testharnessreport.js'></script> + +<script> +// This should be merged into protocol.https.html when/if +// https://github.com/whatwg/html/pull/5482 is approved. +[ + 'cabal', + 'dat', + 'did', + 'dweb', + 'ethereum', + 'hyper', + 'ipfs', + 'ipns', + 'ssb', +].forEach(scheme => { + test(() => { + navigator.registerProtocolHandler(scheme, location.href + '/%s', "foo"); + }, 'registerProtocolHandler: overriding the "' + scheme + '" protocol should work'); + + test(() => { + navigator.unregisterProtocolHandler(scheme, location.href + '/%s'); + }, 'unregisterProtocolHandler: overriding the "' + scheme + '" protocol should work'); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-sw.js b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-sw.js new file mode 100644 index 0000000000..5fd915d17f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-sw.js @@ -0,0 +1,3 @@ +onfetch = e => { + e.respondWith(fetch("handler.html")); +} diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-tools.js b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-tools.js new file mode 100644 index 0000000000..88c62ec373 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler-tools.js @@ -0,0 +1,53 @@ +// These can be used in an environment that has these global variables defined: +// * type (one of "path", "query", or "fragment") +// * noSW (a boolean) + +if (type === "path" && noSW) { + throw new Error("There is no support for a path handler without a service worker."); +} + +const swString = noSW ? "" : "sw"; +const handler = { + "path": "PSS%sPSE/?QES\u2020QEE#FES\u2020FEE", + "query": "?QES\u2020QEEPSS%sPSE#FES\u2020FEE", + "fragment": "?QES\u2020QEE#FES\u2020FEEPSS%sPSE" +}[type]; +const scheme = `web+wpt${type}${swString}`; + +function register() { + const handlerURL = noSW ? `resources/handler.html${handler}${type}` : `resources/handler/${type}/${handler}`; + navigator.registerProtocolHandler(scheme, handlerURL, `WPT ${type} handler${noSW ? ", without service worker" : ""}`); +} + +function runTest({ includeNull = false } = {}) { + promise_test(async t => { + const bc = new BroadcastChannel(`protocol-handler-${type}${swString}`); + if (!noSW) { + const reg = await service_worker_unregister_and_register(t, "resources/handler-sw.js", "resources/handler/"); + t.add_cleanup(async () => await reg.unregister()); + await wait_for_state(t, reg.installing, 'activated'); + } + const a = document.body.appendChild(document.createElement("a")); + const codePoints = []; + let i = includeNull ? 0 : 1; + for (; i < 0x82; i++) { + codePoints.push(String.fromCharCode(i)); + } + a.href = `${scheme}:${codePoints.join("")}`; + a.target = "_blank"; + a.click(); + await new Promise(resolve => { + bc.onmessage = t.step_func(e => { + resultingURL = e.data; + assert_equals(stringBetweenMarkers(resultingURL, "QES", "QEE"), "%86", "query baseline"); + assert_equals(stringBetweenMarkers(resultingURL, "FES", "FEE"), "%E2%80%A0", "fragment baseline"); + assert_equals(stringBetweenMarkers(resultingURL, "PSS", "PSE"), `${encodeURIComponent(scheme)}%3A${includeNull ? "%2500" : ""}%2501%2502%2503%2504%2505%2506%2507%2508%250B%250C%250E%250F%2510%2511%2512%2513%2514%2515%2516%2517%2518%2519%251A%251B%251C%251D%251E%251F%20!%22%23%24%25%26${type === "query" ? "%27" : "'"}()*%2B%2C-.%2F0123456789%3A%3B%253C%3D%253E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%2560abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%257F%25C2%2580%25C2%2581`, "actual test"); + resolve(); + }); + }); + }); +} + +function stringBetweenMarkers(string, start, end) { + return string.substring(string.indexOf(start) + start.length, string.indexOf(end)); +} diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler.html new file mode 100644 index 0000000000..552e541784 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler.html @@ -0,0 +1,23 @@ +<!doctype html> +<p>This popup can be closed if it does not close itself. +<p> +<script> +// This resource either gets navigated to through a service worker as a result of a URL that looks +// like: +// https://.../html/webappapis/system-state-and-capabilities/the-navigator-object/resources/handler/{type}/... +// (the host is excluded to not upset the lint tool) +// or it gets navigated to directly with the type appended to the end of the URL. In that case type +// can only be fragment or query. + +let type = null; +let swString = null; +if (new URL(document.URL).pathname.endsWith("handler.html")) { + swString = ""; + type = (document.URL.endsWith("fragment")) ? "fragment" : "query"; +} else { + type = document.URL.split("/")[9]; + swString = "sw"; +} +new BroadcastChannel(`protocol-handler-${type}${swString}`).postMessage(document.URL); +window.close(); +</script> diff --git a/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/secure_context.html b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/secure_context.html new file mode 100644 index 0000000000..685f5d19d7 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/system-state-and-capabilities/the-navigator-object/secure_context.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script> + test(t => { + assert_false('registerProtocolHandler' in navigator); + assert_equals(navigator.registerProtocolHandler, undefined); + }, "navigator.registerProtocolHandler does not exist in non-secure contexts."); + + test(t => { + assert_false('unregisterProtocolHandler' in navigator); + assert_equals(navigator.unregisterProtocolHandler, undefined); + }, "navigator.unregisterProtocolHandler does not exist in non-secure contexts."); + </script> +</head> +</html> diff --git a/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/README.md b/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/README.md new file mode 100644 index 0000000000..10ae3e5f03 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/README.md @@ -0,0 +1 @@ +`self.crossOriginIsolated` is tested in `html/cross-origin-opener-policy/coep.https.html`, `html/cross-origin-opener-policy/no-https.html`, `html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/no-coop-coep.https.any.js`, and `html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-worker-success.js`. diff --git a/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/Worker_Self_Origin.html b/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/Worker_Self_Origin.html new file mode 100644 index 0000000000..22b28b3e35 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/Worker_Self_Origin.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Test workers self.origin</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +function assertOriginWorker(workerSource, expectedOrigin, testName) { + async_test(function(t) { + w = new Worker(workerSource); + w.onmessage = t.step_func(function(e) { + assert_equals(e.data, expectedOrigin); + t.done(); + }); + }, testName + ' Worker'); +} + +function assertOriginSharedWorker(workerSource, expectedOrigin, testName) { + async_test(function(t) { + w = new SharedWorker(workerSource); + w.port.start(); + w.port.onmessage = t.step_func(function(e) { + assert_equals(e.data, expectedOrigin); + t.done(); + }); + }, testName + ' SharedWorker'); +} + +// Test same-origin workers +assertOriginWorker("./support/WorkerSelfOriginWorker.js", self.origin, "Same Origin"); +assertOriginSharedWorker("./support/WorkerSelfOriginSharedWorker.js", self.origin, "Same Origin"); + +// Test data url workers have opaque origin +assertOriginWorker("data:application/javascript,postMessage(self.origin);", "null", "Data Url"); +assertOriginSharedWorker("data:application/javascript,onconnect = function(e) { e.ports[0].postMessage(self.origin); }", "null", "Data Url"); + +// Test blob url workers +blob = new Blob(["postMessage(self.origin);"]); +blobUrl = URL.createObjectURL(blob); +assertOriginWorker(blobUrl, self.origin, "Blob Url"); + +blob = new Blob(["onconnect = function(e) { e.ports[0].postMessage(self.origin); }"]); +blobUrl = URL.createObjectURL(blob); +assertOriginSharedWorker(blobUrl, self.origin, "Blob Url"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/support/WorkerSelfOriginSharedWorker.js b/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/support/WorkerSelfOriginSharedWorker.js new file mode 100644 index 0000000000..3acc57102a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/support/WorkerSelfOriginSharedWorker.js @@ -0,0 +1,5 @@ +// Post back the location of the worker + +onconnect = function(e) { + e.ports[0].postMessage(self.origin); +} diff --git a/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/support/WorkerSelfOriginWorker.js b/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/support/WorkerSelfOriginWorker.js new file mode 100644 index 0000000000..1a69b55d3f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/the-windoworworkerglobalscope-mixin/support/WorkerSelfOriginWorker.js @@ -0,0 +1,4 @@ +// Post back the location of the worker + +postMessage(self.origin); + diff --git a/testing/web-platform/tests/html/webappapis/timers/clearinterval-from-callback.any.js b/testing/web-platform/tests/html/webappapis/timers/clearinterval-from-callback.any.js new file mode 100644 index 0000000000..bf4eb7cf5a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/timers/clearinterval-from-callback.any.js @@ -0,0 +1,19 @@ +async_test((t) => { + let wasPreviouslyCalled = false; + + const handle = setInterval( + t.step_func(() => { + if (!wasPreviouslyCalled) { + wasPreviouslyCalled = true; + + clearInterval(handle); + + // Make the test succeed after the callback would've run next. + setInterval(t.step_func_done(), 750); + } else { + assert_unreached(); + } + }), + 500 + ); +}, "Clearing an interval from the callback should still clear it."); diff --git a/testing/web-platform/tests/html/webappapis/timers/cleartimeout-clearinterval.any.js b/testing/web-platform/tests/html/webappapis/timers/cleartimeout-clearinterval.any.js new file mode 100644 index 0000000000..44551aa8a1 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/timers/cleartimeout-clearinterval.any.js @@ -0,0 +1,29 @@ +async_test((t) => { + const handle = setTimeout( + t.step_func(() => { + assert_unreached("Timeout was not canceled"); + }), + 0 + ); + + clearInterval(handle); + + setTimeout(() => { + t.done(); + }, 100); +}, "Clear timeout with clearInterval"); + +async_test((t) => { + const handle = setInterval( + t.step_func(() => { + assert_unreached("Interval was not canceled"); + }), + 0 + ); + + clearTimeout(handle); + + setTimeout(() => { + t.done(); + }, 100); +}, "Clear interval with clearTimeout"); diff --git a/testing/web-platform/tests/html/webappapis/timers/evil-spec-example.any.js b/testing/web-platform/tests/html/webappapis/timers/evil-spec-example.any.js new file mode 100644 index 0000000000..17215e218a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/timers/evil-spec-example.any.js @@ -0,0 +1,12 @@ +var t = async_test("Interaction of setTimeout and WebIDL") +function finishTest() { + assert_equals(log, "ONE TWO ") + t.done() +} +var log = ''; +function logger(s) { log += s + ' '; } + +setTimeout({ toString: function () { + setTimeout("logger('ONE')", 100); + return "logger('TWO'); t.step(finishTest)"; +} }, 100); diff --git a/testing/web-platform/tests/html/webappapis/timers/missing-timeout-setinterval.any.js b/testing/web-platform/tests/html/webappapis/timers/missing-timeout-setinterval.any.js new file mode 100644 index 0000000000..33a1cc073c --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/timers/missing-timeout-setinterval.any.js @@ -0,0 +1,34 @@ +function timeout_trampoline(t, timeout, message) { + t.step_timeout(function() { + // Yield in case we managed to be called before the second interval callback. + t.step_timeout(function() { + assert_unreached(message); + }, timeout); + }, timeout); +} + +async_test(function(t) { + let ctr = 0; + let h = setInterval(t.step_func(function() { + if (++ctr == 2) { + clearInterval(h); + t.done(); + return; + } + }) /* no interval */); + + timeout_trampoline(t, 100, "Expected setInterval callback to be called two times"); +}, "Calling setInterval with no interval should be the same as if called with 0 interval"); + +async_test(function(t) { + let ctr = 0; + let h = setInterval(t.step_func(function() { + if (++ctr == 2) { + clearInterval(h); + t.done(); + return; + } + }), undefined); + + timeout_trampoline(t, 100, "Expected setInterval callback to be called two times"); +}, "Calling setInterval with undefined interval should be the same as if called with 0 interval"); diff --git a/testing/web-platform/tests/html/webappapis/timers/negative-setinterval.any.js b/testing/web-platform/tests/html/webappapis/timers/negative-setinterval.any.js new file mode 100644 index 0000000000..5646140c2a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/timers/negative-setinterval.any.js @@ -0,0 +1,12 @@ +setup({ single_test: true }); +var i = 0; +var interval; +function next() { + i++; + if (i === 20) { + clearInterval(interval); + done(); + } +} +setTimeout(assert_unreached, 1000); +interval = setInterval(next, -100); diff --git a/testing/web-platform/tests/html/webappapis/timers/negative-settimeout.any.js b/testing/web-platform/tests/html/webappapis/timers/negative-settimeout.any.js new file mode 100644 index 0000000000..da191f1bf0 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/timers/negative-settimeout.any.js @@ -0,0 +1,3 @@ +setup({ single_test: true }); +setTimeout(done, -100); +setTimeout(assert_unreached, 10); diff --git a/testing/web-platform/tests/html/webappapis/timers/setinterval-cross-realm-callback-report-exception.html b/testing/web-platform/tests/html/webappapis/timers/setinterval-cross-realm-callback-report-exception.html new file mode 100644 index 0000000000..4a780fc932 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/timers/setinterval-cross-realm-callback-report-exception.html @@ -0,0 +1,32 @@ +<!doctype html> +<meta charset=utf-8> +<title>window.setInterval() reports the exception from its callback in the callback's global object</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<iframe></iframe> +<iframe></iframe> +<iframe></iframe> +<script> +setup({ allow_uncaught_exception: true }); + +const onerrorCalls = []; +window.onerror = () => { onerrorCalls.push("top"); }; +frames[0].onerror = () => { onerrorCalls.push("frame0"); }; +frames[1].onerror = () => { onerrorCalls.push("frame1"); }; +frames[2].onerror = () => { onerrorCalls.push("frame2"); }; + +async_test(t => { + window.onload = t.step_func(() => { + const id = frames[0].setInterval(new frames[1].Function(` + parent.clearThisInterval(); + throw new parent.frames[2].Error("PASS"); + `), 4); + window.clearThisInterval = () => { frames[0].clearInterval(id); }; + + t.step_timeout(() => { + assert_array_equals(onerrorCalls, ["frame1"]); + t.done(); + }, 8); + }); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/timers/settimeout-cross-realm-callback-report-exception.html b/testing/web-platform/tests/html/webappapis/timers/settimeout-cross-realm-callback-report-exception.html new file mode 100644 index 0000000000..b4860151a6 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/timers/settimeout-cross-realm-callback-report-exception.html @@ -0,0 +1,28 @@ +<!doctype html> +<meta charset=utf-8> +<title>window.setTimeout() reports the exception from its callback in the callback's global object</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<iframe></iframe> +<iframe></iframe> +<iframe></iframe> +<script> +setup({ allow_uncaught_exception: true }); + +const onerrorCalls = []; +window.onerror = () => { onerrorCalls.push("top"); }; +frames[0].onerror = () => { onerrorCalls.push("frame0"); }; +frames[1].onerror = () => { onerrorCalls.push("frame1"); }; +frames[2].onerror = () => { onerrorCalls.push("frame2"); }; + +async_test(t => { + window.onload = t.step_func(() => { + frames[0].setTimeout(new frames[1].Function(`throw new parent.frames[2].Error("PASS");`), 4); + + t.step_timeout(() => { + assert_array_equals(onerrorCalls, ["frame1"]); + t.done(); + }, 8); + }); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/timers/type-long-setinterval.any.js b/testing/web-platform/tests/html/webappapis/timers/type-long-setinterval.any.js new file mode 100644 index 0000000000..164527f18b --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/timers/type-long-setinterval.any.js @@ -0,0 +1,8 @@ +setup({ single_test: true }); +var interval; +function next() { + clearInterval(interval); + done(); +} +interval = setInterval(next, Math.pow(2, 32)); +setTimeout(assert_unreached, 100); diff --git a/testing/web-platform/tests/html/webappapis/timers/type-long-settimeout.any.js b/testing/web-platform/tests/html/webappapis/timers/type-long-settimeout.any.js new file mode 100644 index 0000000000..9092f13f3b --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/timers/type-long-settimeout.any.js @@ -0,0 +1,3 @@ +setup({ single_test: true }); +setTimeout(done, Math.pow(2, 32)); +setTimeout(assert_unreached, 100); diff --git a/testing/web-platform/tests/html/webappapis/update-rendering/child-document-raf-order.html b/testing/web-platform/tests/html/webappapis/update-rendering/child-document-raf-order.html new file mode 100644 index 0000000000..222c1af444 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/update-rendering/child-document-raf-order.html @@ -0,0 +1,118 @@ +<!DOCTYPE HTML> +<meta charset=UTF-8> +<title>Ordering of steps in "Update the Rendering" - child document requestAnimationFrame order</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/webappapis.html#update-the-rendering"> +<link rel="author" title="L. David Baron" href="https://dbaron.org/"> +<link rel="author" title="Mozilla" href="https://mozilla.org/"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id=log></div> + +<!-- + +This test tests the interaction of just two substeps of the "Update the +rendering" steps in +https://html.spec.whatwg.org/multipage/webappapis.html#update-the-rendering + +These are: + + 1. Let docs be the list of Document objects associated with the event + loop in question, sorted arbitrarily except that the following + conditions must be met: + + - Any Document B that is nested through a Document A must be listed + after A in the list. + + - If there are two documents A and B whose browsing contexts are + both nested browsing contexts and their browsing context + containers are both elements in the same Document C, then the + order of A and B in the list must match the relative tree order of + their respective browsing context containers in C. + + In the steps below that iterate over docs, each Document must be + processed in the order it is found in the list. + +and later: + +10. For each fully active Document in docs, run the animation frame + callbacks for that Document, passing in now as the timestamp. + + +It tests this by setting up a tree of three documents, two children and +one parent, and testing for the relative order of the animation frame +callbacks for each. + +--> + +<script> + +async_test(function (t) { + step_timeout(setup, 0); + + let first_frame, second_frame; + + let notification_sequence = []; + + function setup() { + // Start by creating two iframes. To test (a little bit) the rule + // about iteration being in document order, insert them in the reverse + // order of creation. + let body = document.body; + function make_iframe() { + let iframe = document.createElement("iframe"); + iframe.setAttribute("srcdoc", "<body onload='parent.child_ready()'>"); + iframe.setAttribute("width", "30"); + iframe.setAttribute("height", "15"); + return iframe; + } + second_frame = make_iframe(); + body.prepend(second_frame); + first_frame = make_iframe(); + body.prepend(first_frame); + + let children_waiting = 2; + window.child_ready = function() { + if (--children_waiting == 0) { + // Call requestAnimationFrame in neither the order nor the reverse + // of the order in which we expect to be called (which is parent, + // first, second). + first_frame.contentWindow.requestAnimationFrame(first_child_raf); + second_frame.contentWindow.requestAnimationFrame(second_child_raf); + window.requestAnimationFrame(parent_raf); + } + }; + } + + let parent_raf = t.step_func(function() { + notification_sequence.push("parent_raf"); + + // Request another notification to help ensure we're getting expected behavior. + window.requestAnimationFrame(parent_raf); + }); + + let first_child_raf = t.step_func(function() { + notification_sequence.push("first_child_raf"); + + // Request another notification to help ensure we're getting expected behavior. + first_frame.contentWindow.requestAnimationFrame(first_child_raf); + }); + + let second_child_raf = t.step_func(function() { + notification_sequence.push("second_child_raf"); + + // Request another notification to help ensure we're getting expected behavior. + second_frame.contentWindow.requestAnimationFrame(second_child_raf); + + step_timeout(finish, 0); + }); + + let finish = t.step_func(function() { + assert_array_equals(notification_sequence, + ["parent_raf", "first_child_raf", "second_child_raf"], + "expected order of notifications"); + t.done(); + }); +}); + +</script> diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/confirm-different-origin-frame.sub.html b/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/confirm-different-origin-frame.sub.html new file mode 100644 index 0000000000..693cde2192 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/confirm-different-origin-frame.sub.html @@ -0,0 +1,24 @@ +<html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> + setup({ single_test: true }); + function handleEvent(e) { + assert_equals(e.data, 'pass'); + done(); + } + function on_iframe_load() { + var frameWin = document.getElementById('confirmFrame').contentWindow; + frameWin.postMessage('Confirm', '*'); + } + window.addEventListener('message', handleEvent); +</script> + +<body> + <iframe id='confirmFrame' + src='http://{{hosts[alt][www]}}:{{ports[http][0]}}/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/confirm.html' + onload='on_iframe_load()'></iframe> +</body> + +</html> diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/prompt-different-origin-frame.sub.html b/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/prompt-different-origin-frame.sub.html new file mode 100644 index 0000000000..151def9b5a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/prompt-different-origin-frame.sub.html @@ -0,0 +1,24 @@ +<html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> + setup({ single_test: true }); + function handleEvent(e) { + assert_equals(e.data, 'pass'); + done(); + } + function on_iframe_load() { + var frameWin = document.getElementById('promptFrame').contentWindow; + frameWin.postMessage('Prompt', '*'); + } + window.addEventListener('message', handleEvent); +</script> + +<body> + <iframe id='promptFrame' + src='http://{{hosts[alt][www]}}:{{ports[http][0]}}/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/prompt.html' + onload='on_iframe_load()'></iframe> +</body> + +</html> diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/confirm.html b/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/confirm.html new file mode 100644 index 0000000000..80b2c61b39 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/confirm.html @@ -0,0 +1,11 @@ +<script> + function handleEvent(e) { + var conf = window.confirm('Confirm Dialog'); + if (conf == false) { + window.parent.postMessage('pass', '*'); + } else { + window.parent.postMessage('fail', '*'); + } + } + window.addEventListener('message', handleEvent); +</script> diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/prompt.html b/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/prompt.html new file mode 100644 index 0000000000..db40774770 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/prompt.html @@ -0,0 +1,11 @@ +<script> + function handleEvent(e) { + var conf = window.prompt('Prompt Dialog'); + if (conf == null) { + window.parent.postMessage('pass', '*'); + } else { + window.parent.postMessage('fail', '*'); + } + } + window.addEventListener('message', handleEvent); +</script> diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/newline-normalization-manual.html b/testing/web-platform/tests/html/webappapis/user-prompts/newline-normalization-manual.html new file mode 100644 index 0000000000..55cb5ce527 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/user-prompts/newline-normalization-manual.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Newline normalization in simple dialogs</title> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#simple-dialogs"> + +<p>The dialogs should all contain text looking like:</p> + +<pre>Line 1.1 +Line 1.2 +Line 1.3 +Line 1.4 + +Line 2.1</pre> + +<script> +"use strict"; + +for (const func of [alert, confirm, prompt]) { + func('Line 1.1\nLine 1.2\rLine 1.3\r\nLine 1.4\n\rLine 2.1'); +} +</script> diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/print-during-beforeunload.html b/testing/web-platform/tests/html/webappapis/user-prompts/print-during-beforeunload.html new file mode 100644 index 0000000000..5925bdde4d --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/user-prompts/print-during-beforeunload.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>print() during beforeunload</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script> +"use strict"; + +async_test(t => { + const w = window.open("resources/print-during-event.sub.html?event=beforeunload"); + t.add_cleanup(() => w.close()); + + const messages = []; + window.addEventListener("message", t.step_func(({ data }) => { + messages.push(data); + + if (messages.length === 1) { + assert_array_equals(messages, ["start"]); + w.location.href = "resources/destination.html"; + } else if (messages.length === 2) { + // The test passes if we've reached this point because the print() dialog did not block the navigation. + assert_array_equals(messages, ["start", "destination"]); + t.done(); + } + })); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/print-during-unload.html b/testing/web-platform/tests/html/webappapis/user-prompts/print-during-unload.html new file mode 100644 index 0000000000..81259a9fe0 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/user-prompts/print-during-unload.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>print() during unload</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script> +"use strict"; + +async_test(t => { + const w = window.open("resources/print-during-event.sub.html?event=unload"); + t.add_cleanup(() => w.close()); + + const messages = []; + window.addEventListener("message", t.step_func(({ data }) => { + messages.push(data); + + if (messages.length === 1) { + assert_array_equals(messages, ["start"]); + w.location.href = "resources/destination.html"; + } else if (messages.length === 2) { + // The test passes if we've reached this point because the print() dialog did not block the navigation. + assert_array_equals(messages, ["start", "destination"]); + t.done(); + } + })); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/print-in-detached-frame.html b/testing/web-platform/tests/html/webappapis/user-prompts/print-in-detached-frame.html new file mode 100644 index 0000000000..474f4f4d2a --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/user-prompts/print-in-detached-frame.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>print() in a detached iframe</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script> +"use strict"; + +test(() => { + const iframe = document.createElement("iframe"); + document.body.append(iframe); + const print = iframe.contentWindow.print; + iframe.remove(); + + print(); +}); +</script> diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/print-manual.html b/testing/web-platform/tests/html/webappapis/user-prompts/print-manual.html new file mode 100644 index 0000000000..67cbd0dd48 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/user-prompts/print-manual.html @@ -0,0 +1,93 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Printing</title> +<link rel=help href="https://html.spec.whatwg.org/#printing"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +setup({explicit_timeout: true}) +</script> + +<p>Click on the button below.</p> +<p>If the browser offers to print the current page, dismiss the prompt to +return to this page. The following should then appear in the box marked as +"Output":</p> +<pre> +before calling print() +beforeprint +afterprint +after calling print() +</pre> +<p>If no user dialog appears, either the above or the following should be +printed in the box marked as "Output":</p> +<pre> +before calling print() +after calling print() +</pre> +<p>The test passes if the above criteria are satisfied and "PASS" appears in a +table below this paragraph. The test fails otherwise.</p> + +<button onclick="testBtn(this)">Click here!</button> +<h3>Output</h3> +<pre id=output></pre> + +<script> +"use strict"; +// This test is actually synchronous, but we use async_test()'s nice +// infrastructure around t.step() to capture errors in event listeners. This is +// necessary since it seems like in Chrome, exceptions in beforeprint/afterprint +// event listeners are not propagated to error events. See +// https://crbug.com/977828. +const t = async_test(); +const log = []; +function out(str) { + log.push(str); + output.appendChild(document.createTextNode(str + "\n")); +} +function testBtn(btn) { + btn.remove(); + window.addEventListener("beforeprint", function (ev) { + // t.step_func() does not preserve `this`, which we test below. + t.step(() => { + out("beforeprint"); + assert_equals(Object.getPrototypeOf(ev), Event.prototype); + assert_equals(this, window); + assert_equals(ev.target, window); + assert_equals(ev.type, "beforeprint"); + }); + }); + window.addEventListener("afterprint", function (ev) { + // t.step_func() does not preserve `this`, which we test below. + t.step(() => { + out("afterprint"); + assert_equals(Object.getPrototypeOf(ev), Event.prototype); + assert_equals(this, window); + assert_equals(ev.target, window); + assert_equals(ev.type, "afterprint"); + }); + }); + out("before calling print()"); + print(); + out("after calling print()"); + t.step(() => { + try { + assert_array_equals(log, [ + "before calling print()", + "beforeprint", + "afterprint", + "after calling print()", + ]); + } catch (err) { + try { + assert_array_equals(log, [ + "before calling print()", + "after calling print()", + ]); + } catch (err) { + assert_unreached("Output does not match either possibility"); + } + } + }); + t.done(); +} +</script> diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/resources/destination.html b/testing/web-platform/tests/html/webappapis/user-prompts/resources/destination.html new file mode 100644 index 0000000000..87fb35cea3 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/user-prompts/resources/destination.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<meta charset="utf-8"> + +<script> +"use strict"; + +opener.postMessage("destination", "*"); +</script> diff --git a/testing/web-platform/tests/html/webappapis/user-prompts/resources/print-during-event.sub.html b/testing/web-platform/tests/html/webappapis/user-prompts/resources/print-during-event.sub.html new file mode 100644 index 0000000000..6f6a78b03c --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/user-prompts/resources/print-during-event.sub.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Page that tries to print during an event</title> + +<script> +"use strict"; + +window.on{{GET[event]}} = () => { + try { + window.print(); + } catch (e) { + window.opener.postMessage(`error: ${e.message}`); + } +}; + +window.opener.postMessage("start", "*"); +</script> |