From f334bcaadd50a18e99200c95ad80126fe2f59cf0 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Thu, 14 Nov 2024 16:44:52 -0800 Subject: [PATCH 1/4] Enable ShadowRealm testing for ErrorEvent Adapt the tests for the onerror event on Worker global scopes to cover ShadowRealm global scopes as well. One of the tests uses setTimeout() directly to fire the global onerror event. Use queueMicrotask() instead, since that is [Exposed=*]. We cannot use t.step_timeout() because that wraps the callback in a t.step_func() which catches the error and fails the test. --- ...ker.js => otherglobalscope-runtime-error.any.js} | 13 ++++++------- ...=> otherglobalscope-synthetic-errorevent.any.js} | 9 ++++----- ...r.js => otherglobalscope-synthetic-event.any.js} | 7 +++---- 3 files changed, 13 insertions(+), 16 deletions(-) rename html/webappapis/scripting/events/event-handler-processing-algorithm-error/{workerglobalscope-runtime-error.worker.js => otherglobalscope-runtime-error.any.js} (77%) rename html/webappapis/scripting/events/event-handler-processing-algorithm-error/{workerglobalscope-synthetic-errorevent.worker.js => otherglobalscope-synthetic-errorevent.any.js} (82%) rename html/webappapis/scripting/events/event-handler-processing-algorithm-error/{workerglobalscope-synthetic-event.worker.js => otherglobalscope-synthetic-event.any.js} (83%) diff --git a/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-runtime-error.worker.js b/html/webappapis/scripting/events/event-handler-processing-algorithm-error/otherglobalscope-runtime-error.any.js similarity index 77% rename from html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-runtime-error.worker.js rename to html/webappapis/scripting/events/event-handler-processing-algorithm-error/otherglobalscope-runtime-error.any.js index 264fef810dfc7c..5ad23f02db02fa 100644 --- a/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-runtime-error.worker.js +++ b/html/webappapis/scripting/events/event-handler-processing-algorithm-error/otherglobalscope-runtime-error.any.js @@ -1,5 +1,6 @@ +// META: global=worker,shadowrealm + "use strict"; -importScripts("/resources/testharness.js"); setup({ allow_uncaught_exception: true }); @@ -14,10 +15,10 @@ promise_test(t => { assert_equals(e.defaultPrevented, true); }); - setTimeout(() => thisFunctionDoesNotExist(), 0); + queueMicrotask(() => thisFunctionDoesNotExist()); return promise; -}, "error event is weird (return true cancels; many args) on WorkerGlobalScope, with a runtime error"); +}, "error event is weird (return true cancels; many args) on non-Window global scope, with a runtime error"); promise_test(t => { self.onerror = t.step_func(function (message, filename, lineno, colno, error) { @@ -31,10 +32,8 @@ promise_test(t => { return true; }); - setTimeout(() => thisFunctionDoesNotExist(), 0); + queueMicrotask(() => thisFunctionDoesNotExist()); 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(); +}, "error event has the right 5 args on non-Window global scope, with a runtime error"); diff --git a/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-errorevent.worker.js b/html/webappapis/scripting/events/event-handler-processing-algorithm-error/otherglobalscope-synthetic-errorevent.any.js similarity index 82% rename from html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-errorevent.worker.js rename to html/webappapis/scripting/events/event-handler-processing-algorithm-error/otherglobalscope-synthetic-errorevent.any.js index a14f6e01a9bbe4..93c367d5c5d185 100644 --- a/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-errorevent.worker.js +++ b/html/webappapis/scripting/events/event-handler-processing-algorithm-error/otherglobalscope-synthetic-errorevent.any.js @@ -1,5 +1,6 @@ +// META: global=worker,shadowrealm + "use strict"; -importScripts("/resources/testharness.js"); setup({ allow_uncaught_exception: true }); @@ -17,7 +18,7 @@ promise_test(t => { self.dispatchEvent(new ErrorEvent("error", { cancelable: true })); return promise; -}, "error event is weird (return true cancels; many args) on WorkerGlobalScope, with a synthetic ErrorEvent"); +}, "error event is weird (return true cancels; many args) on non-Window global scope, with a synthetic ErrorEvent"); promise_test(t => { const theError = { the: "error object" }; @@ -44,6 +45,4 @@ promise_test(t => { })); return promise; -}, "error event has the right 5 args on WorkerGlobalScope, with a synthetic ErrorEvent"); - -done(); +}, "error event has the right 5 args on non-Window global scope, with a synthetic ErrorEvent"); diff --git a/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-event.worker.js b/html/webappapis/scripting/events/event-handler-processing-algorithm-error/otherglobalscope-synthetic-event.any.js similarity index 83% rename from html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-event.worker.js rename to html/webappapis/scripting/events/event-handler-processing-algorithm-error/otherglobalscope-synthetic-event.any.js index a3e16ded885626..a570d9de005c83 100644 --- a/html/webappapis/scripting/events/event-handler-processing-algorithm-error/workerglobalscope-synthetic-event.worker.js +++ b/html/webappapis/scripting/events/event-handler-processing-algorithm-error/otherglobalscope-synthetic-event.any.js @@ -1,5 +1,6 @@ +// META: global=worker,shadowrealm + "use strict"; -importScripts("/resources/testharness.js"); setup({ allow_uncaught_exception: true }); @@ -17,6 +18,4 @@ promise_test(t => { 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(); +}, "error event is normal (return true does not cancel; one arg) on non-Window global scope, with a synthetic Event"); From b855d2efc5014ac0fb245a32453482a21303c031 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Tue, 19 Nov 2024 13:06:48 -0800 Subject: [PATCH 2/4] Enable ShadowRealm tests for queueMicrotask and microtask evaluation order --- ...fter-shadowrealmglobalscope-onerror.any.js | 32 ++++++++++++++++++ ...ation-order-1-nothrow-static-import.any.js | 2 +- ...luation-order-1-throw-static-import.any.js | 2 +- .../microtasks/evaluation-order-2.any.js | 2 +- .../microtasks/evaluation-order-3.any.js | 2 +- .../queue-microtask-exceptions.any.js | 2 +- ...wrealm-callback-report-exception.window.js | 33 +++++++++++++++++++ .../microtask-queuing/queue-microtask.any.js | 2 +- 8 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-shadowrealmglobalscope-onerror.any.js create mode 100644 html/webappapis/microtask-queuing/queue-microtask-shadowrealm-callback-report-exception.window.js diff --git a/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-shadowrealmglobalscope-onerror.any.js b/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-shadowrealmglobalscope-onerror.any.js new file mode 100644 index 00000000000000..4914c4dd59c1fd --- /dev/null +++ b/html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-shadowrealmglobalscope-onerror.any.js @@ -0,0 +1,32 @@ +// META: title=Microtask checkpoint after ShadowRealm onerror events +// META: global=shadowrealm + +// Adapted from first part of ./resources/checkpoint-after-error-event.js. + +setup({allow_uncaught_exception: true}); + +var log = []; + +addEventListener('error', () => { + log.push('handler 1'); + Promise.resolve().then(() => log.push('handler 1 promise')); +}); +addEventListener('error', () => { + log.push('handler 2'); + Promise.resolve().then(() => log.push('handler 2 promise')); +}); + +async_test(t => { + t.step_timeout(() => { + assert_array_equals(log, [ + 'handler 1', + 'handler 2', + 'handler 1 promise', + 'handler 2 promise' + ]); + t.done(); + }, + 0); +}, "Promise resolved during #report-the-error"); + +queueMicrotask(() => thisFunctionDoesNotExist()); diff --git a/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-nothrow-static-import.any.js b/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-nothrow-static-import.any.js index 006eab7a7e06df..5e85f2822de392 100644 --- a/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-nothrow-static-import.any.js +++ b/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-nothrow-static-import.any.js @@ -1,4 +1,4 @@ -// META: global=dedicatedworker-module,sharedworker-module +// META: global=dedicatedworker-module,sharedworker-module,shadowrealm-in-window,shadowrealm-in-shadowrealm,shadowrealm-in-dedicatedworker,shadowrealm-in-sharedworker // META: script=./resources/evaluation-order-setup.js import './resources/evaluation-order-1-nothrow-setup.js'; diff --git a/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-throw-static-import.any.js b/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-throw-static-import.any.js index f6cc427c719be7..5810cfe47a4db0 100644 --- a/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-throw-static-import.any.js +++ b/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-1-throw-static-import.any.js @@ -1,4 +1,4 @@ -// META: global=dedicatedworker-module,sharedworker-module +// META: global=dedicatedworker-module,sharedworker-module,shadowrealm-in-window,shadowrealm-in-shadowrealm,shadowrealm-in-dedicatedworker,shadowrealm-in-sharedworker // META: script=./resources/evaluation-order-setup.js import './resources/evaluation-order-1-throw-setup.js'; diff --git a/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-2.any.js b/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-2.any.js index bbc64748235535..da8a17b50b998e 100644 --- a/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-2.any.js +++ b/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-2.any.js @@ -1,4 +1,4 @@ -// META: global=dedicatedworker-module,sharedworker-module +// META: global=dedicatedworker-module,sharedworker-module,shadowrealm-in-window,shadowrealm-in-shadowrealm,shadowrealm-in-dedicatedworker,shadowrealm-in-sharedworker // META: script=./resources/evaluation-order-setup.js import './resources/evaluation-order-2-setup.js'; diff --git a/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-3.any.js b/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-3.any.js index 19e94714e5ded1..21bafc9c2ca777 100644 --- a/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-3.any.js +++ b/html/semantics/scripting-1/the-script-element/microtasks/evaluation-order-3.any.js @@ -1,4 +1,4 @@ -// META: global=dedicatedworker-module,sharedworker-module +// META: global=dedicatedworker-module,sharedworker-module,shadowrealm-in-window,shadowrealm-in-shadowrealm,shadowrealm-in-dedicatedworker,shadowrealm-in-sharedworker // META: script=./resources/evaluation-order-setup.js import './resources/evaluation-order-3-setup.js'; diff --git a/html/webappapis/microtask-queuing/queue-microtask-exceptions.any.js b/html/webappapis/microtask-queuing/queue-microtask-exceptions.any.js index 01f32ac9ba1496..a0bed558a5c337 100644 --- a/html/webappapis/microtask-queuing/queue-microtask-exceptions.any.js +++ b/html/webappapis/microtask-queuing/queue-microtask-exceptions.any.js @@ -1,4 +1,4 @@ -// META: global=window,worker +// META: global=window,worker,shadowrealm "use strict"; setup({ diff --git a/html/webappapis/microtask-queuing/queue-microtask-shadowrealm-callback-report-exception.window.js b/html/webappapis/microtask-queuing/queue-microtask-shadowrealm-callback-report-exception.window.js new file mode 100644 index 00000000000000..506664d7700f4a --- /dev/null +++ b/html/webappapis/microtask-queuing/queue-microtask-shadowrealm-callback-report-exception.window.js @@ -0,0 +1,33 @@ +// META: title=Errors thrown by wrapped microtask in ShadowRealm + +setup({ allow_uncaught_exception: true }); + +const realm = new ShadowRealm(); + +const onerrorCalls = []; +window.onerror = e => { + onerrorCalls.push("Window"); +}; +realm.evaluate(`(push) => { + onerror = function (e) { + const assertResult = e instanceof TypeError; + push(assertResult); + }; +}`)(assertResult => { + onerrorCalls.push("ShadowRealm"); + assert_true(assertResult, + "exception should be converted to a fresh ShadowRealm TypeError") +}); + +async_test(t => { + window.onload = t.step_func(() => { + const task = () => { throw new Error("will be converted to TypeError"); }; + realm.evaluate(`queueMicrotask`)(task); + + t.step_timeout(() => { + assert_array_equals(onerrorCalls, ["ShadowRealm"], + "exception should only be reported in ShadowRealm's onerror handler"); + t.done(); + }, 4); + }); +}); diff --git a/html/webappapis/microtask-queuing/queue-microtask.any.js b/html/webappapis/microtask-queuing/queue-microtask.any.js index e67765fade3bc6..1c8b3a35b99ab0 100644 --- a/html/webappapis/microtask-queuing/queue-microtask.any.js +++ b/html/webappapis/microtask-queuing/queue-microtask.any.js @@ -1,4 +1,4 @@ -// META: global=window,worker +// META: global=window,worker,shadowrealm "use strict"; test(() => { From feb313b6ee2c042e4caec6cb24a8d6d135b15e48 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Fri, 22 Nov 2024 15:21:47 -0800 Subject: [PATCH 3/4] Add another ShadowRealm test for onerror This test is similar to window-onerror-runtime-error-throw.html and window-onerror-runtime-error.html, but ensuring that the ShadowRealm global's onerror is triggered and not the Window's onerror. By my reading of the spec, we cannot have a test equivalent to window-onerror-parse-error.html in ShadowRealm because ShadowRealm.prototype.evaluate() will exit early if the given code fails to parse. --- .../globalthis-onerror-shadowrealm.window.js | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 html/webappapis/scripting/processing-model-2/globalthis-onerror-shadowrealm.window.js diff --git a/html/webappapis/scripting/processing-model-2/globalthis-onerror-shadowrealm.window.js b/html/webappapis/scripting/processing-model-2/globalthis-onerror-shadowrealm.window.js new file mode 100644 index 00000000000000..a10595cd5c4a6b --- /dev/null +++ b/html/webappapis/scripting/processing-model-2/globalthis-onerror-shadowrealm.window.js @@ -0,0 +1,71 @@ +// META: title=globalThis.onerror: runtime script errors in ShadowRealm + +// 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. + +async_test(t => { + onerror = t.unreached_func("Window onerror should not be triggered"); + + const realm = new ShadowRealm(); + + realm.evaluate("var errorCount = 0;"); + realm.evaluate(`(doAsserts) => { + globalThis.onerror = function(msg, url, lineno, colno, thrownValue) { + ++errorCount; + doAsserts(url, lineno, colno, typeof thrownValue, String(thrownValue)); + }; + }`)(t.step_func((url, lineno, typeofThrownValue, stringifiedThrownValue) => { + assert_equals(url, "eval code", "correct url passed to onerror"); + assert_equals(lineno, 8, "correct line number passed to onerror"); + assert_equals(typeofThrownValue, "string", "thrown string passed directly to onerror"); + assert_equals(stringifiedThrownValue, "bar", "correct thrown value passed to onerror"); + })); + + assert_throws_js(TypeError, () => realm.evaluate(` + 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"; + `), "thrown error is wrapped in a TypeError object from the surrounding realm"); + + t.step_timeout(() => { + assert_equals(realm.evaluate("errorCount"), 1, "onerror should be called once"); + }, 1000); +}, "onerror triggered by uncaught thrown exception in realm.evaluate"); + +async_test(t => { + onerror = t.unreached_func("Window onerror should not be triggered"); + + const realm = new ShadowRealm(); + + realm.evaluate("var errorCount = 0;"); + realm.evaluate(`(doAsserts) => { + globalThis.onerror = function(msg, url, lineno, colno, thrownValue) { + ++errorCount; + doAsserts(url, lineno, typeof thrownValue, thrownValue instanceof TypeError); + }; + }`)(t.step_func((url, lineno, typeofThrownValue, isTypeError) => { + assert_equals(url, "eval code", "correct url passed to onerror"); + assert_equals(lineno, 8, "correct line number passed to onerror"); + assert_equals(typeofThrownValue, "object", "thrown error instance passed to onerror"); + assert_true(isShadowRealmTypeError, "correct thrown value passed to onerror"); + })); + + assert_throws_js(TypeError, () => realm.evaluate(` + 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(); + `), "thrown error is wrapped in a TypeError object from the surrounding realm"); + + t.step_timeout(() => { + assert_equals(realm.evaluate("errorCount"), 1, "onerror should be called once"); + }, 1000); +}, "onerror triggered by uncaught runtime error in realm.evaluate"); From 50fd1767b3c20ba1b14df70493a5f6b18e887dd7 Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Mon, 25 Nov 2024 17:48:38 -0800 Subject: [PATCH 4/4] Enable reportError tests in ShadowRealm reportError seems to be minimally tested. ShadowRealm doesn't have the location exposed, so just assume it will be blank. This may need to be changed depending on what browsers do. It is not specified what the value should be in https://html.spec.whatwg.org/#extract-error: "Set _attributes_[message], _attributes_[filename], _attributes_[lineno], and _attributes_[colno] to implementation-defined values derived from _exception_. NOTE: Browsers implement behavior not specified here or in the JavaScript specification to gather values which are helpful, including in unusual cases (e.g., `eval`). In the future, this might be specified in greater detail. This seems like exactly such an unusual case. --- html/webappapis/scripting/reporterror.any.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/html/webappapis/scripting/reporterror.any.js b/html/webappapis/scripting/reporterror.any.js index b9e7ba25bc6a39..b354f00cded25b 100644 --- a/html/webappapis/scripting/reporterror.any.js +++ b/html/webappapis/scripting/reporterror.any.js @@ -1,3 +1,5 @@ +// META: global=window,dedicatedworker,shadowrealm + setup({ allow_uncaught_exception:true }); [ @@ -9,7 +11,7 @@ setup({ allow_uncaught_exception:true }); 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_equals(e.filename, location ? 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);