diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/html/webappapis/scripting | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/html/webappapis/scripting')
138 files changed, 5950 insertions, 0 deletions
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-onmove-01.tentative.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onmove-01.tentative.html new file mode 100644 index 0000000000..e4454a20a1 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onmove-01.tentative.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<title>Window Object 'onmove'</title> +<link rel="author" title="Sonja Laurila" href="mailto:laurila@google.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#handler-window-onmove"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script> +promise_test(async t => { + var onMoveCalled = false; + window.onmove = (event) => { onMoveCalled = true; } + window.dispatchEvent(new Event('move')); + assert_true(onMoveCalled, "The onMove handler has been executed."); +}, "Window move event"); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onmove-02.tentative.html b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onmove-02.tentative.html new file mode 100644 index 0000000000..a751f39d30 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/scripting/events/event-handler-onmove-02.tentative.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<title>Window Object 'onmove'</title> +<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/#handler-window-onmove"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script> +promise_test((t) => { + var expectedRect = {'x': 100, 'y': 200}; + return new Promise(resolve => { + window.addEventListener("move", resolve); + test_driver.set_window_rect(expectedRect); + }).then(event => { + return test_driver.get_window_rect() + .then(rect => { + assert_equals(rect.x, expectedRect.x, "The window rect X is correct.") + assert_equals(rect.y, expectedRect.y, "The window rect Y is correct.") + }); + }); +}, "Window move event"); +</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`); |