From 97954003cc87b664768918173e8d00f6df35e04f Mon Sep 17 00:00:00 2001 From: Nayeem Rahman Date: Sun, 21 Aug 2022 19:16:42 +0100 Subject: [PATCH] feat: `queueMicrotask()` error handling (#15522) Adds error event dispatching for queueMicrotask(). Consequently unhandled errors are now reported with Deno.core.terminate(), which is immune to the existing quirk with plainly thrown errors (#14158). --- cli/tests/integration/run_tests.rs | 11 +++++++ cli/tests/testdata/queue_microtask_error.ts | 5 ++++ .../testdata/queue_microtask_error.ts.out | 6 ++++ .../testdata/queue_microtask_error_handled.ts | 21 +++++++++++++ .../queue_microtask_error_handled.ts.out | 15 ++++++++++ core/01_core.js | 30 ++++++++++++++++++- runtime/js/99_main.js | 3 +- tools/wpt/expectation.json | 4 +-- 8 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 cli/tests/testdata/queue_microtask_error.ts create mode 100644 cli/tests/testdata/queue_microtask_error.ts.out create mode 100644 cli/tests/testdata/queue_microtask_error_handled.ts create mode 100644 cli/tests/testdata/queue_microtask_error_handled.ts.out diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs index 9c60cc7454..8998eecc97 100644 --- a/cli/tests/integration/run_tests.rs +++ b/cli/tests/integration/run_tests.rs @@ -2745,6 +2745,17 @@ itest!(report_error_end_of_program { exit_code: 1, }); +itest!(queue_microtask_error { + args: "run --quiet queue_microtask_error.ts", + output: "queue_microtask_error.ts.out", + exit_code: 1, +}); + +itest!(queue_microtask_error_handled { + args: "run --quiet queue_microtask_error_handled.ts", + output: "queue_microtask_error_handled.ts.out", +}); + itest!(spawn_stdout_inherit { args: "run --quiet --unstable -A spawn_stdout_inherit.ts", output: "spawn_stdout_inherit.ts.out", diff --git a/cli/tests/testdata/queue_microtask_error.ts b/cli/tests/testdata/queue_microtask_error.ts new file mode 100644 index 0000000000..b2e9642c52 --- /dev/null +++ b/cli/tests/testdata/queue_microtask_error.ts @@ -0,0 +1,5 @@ +queueMicrotask(() => { + throw new Error("foo"); +}); +console.log(1); +Promise.resolve().then(() => console.log(2)); diff --git a/cli/tests/testdata/queue_microtask_error.ts.out b/cli/tests/testdata/queue_microtask_error.ts.out new file mode 100644 index 0000000000..6c4d419364 --- /dev/null +++ b/cli/tests/testdata/queue_microtask_error.ts.out @@ -0,0 +1,6 @@ +1 +error: Uncaught Error: foo + throw new Error("foo"); + ^ + at [WILDCARD]/queue_microtask_error.ts:2:9 + at deno:core/[WILDCARD] diff --git a/cli/tests/testdata/queue_microtask_error_handled.ts b/cli/tests/testdata/queue_microtask_error_handled.ts new file mode 100644 index 0000000000..7d1440135f --- /dev/null +++ b/cli/tests/testdata/queue_microtask_error_handled.ts @@ -0,0 +1,21 @@ +addEventListener("error", (event) => { + console.log({ + cancelable: event.cancelable, + message: event.message, + filename: event.filename, + lineno: event.lineno, + colno: event.colno, + error: event.error, + }); + event.preventDefault(); +}); + +onerror = (event) => { + console.log("onerror() called", event.error); +}; + +queueMicrotask(() => { + throw new Error("foo"); +}); +console.log(1); +Promise.resolve().then(() => console.log(2)); diff --git a/cli/tests/testdata/queue_microtask_error_handled.ts.out b/cli/tests/testdata/queue_microtask_error_handled.ts.out new file mode 100644 index 0000000000..7f3f7f84a5 --- /dev/null +++ b/cli/tests/testdata/queue_microtask_error_handled.ts.out @@ -0,0 +1,15 @@ +1 +{ + cancelable: true, + message: "Uncaught Error: foo", + filename: "[WILDCARD]/queue_microtask_error_handled.ts", + lineno: 18, + colno: 9, + error: Error: foo + at [WILDCARD]/queue_microtask_error_handled.ts:18:9 + at deno:core/[WILDCARD] +} +onerror() called Error: foo + at [WILDCARD]/queue_microtask_error_handled.ts:18:9 + at deno:core/[WILDCARD] +2 diff --git a/core/01_core.js b/core/01_core.js index aaff59148e..e1ac275ebf 100644 --- a/core/01_core.js +++ b/core/01_core.js @@ -210,8 +210,35 @@ return aggregate; } + let reportExceptionCallback = undefined; + + // Used to report errors thrown from functions passed to `queueMicrotask()`. + // The callback will be passed the thrown error. For example, you can use this + // to dispatch an error event to the global scope. + // In other words, set the implementation for + // https://html.spec.whatwg.org/multipage/webappapis.html#report-the-exception + function setReportExceptionCallback(cb) { + if (typeof cb != "function") { + throw new TypeError("expected a function"); + } + reportExceptionCallback = cb; + } + function queueMicrotask(cb) { - return ops.op_queue_microtask(cb); + if (typeof cb != "function") { + throw new TypeError("expected a function"); + } + return ops.op_queue_microtask(() => { + try { + cb(); + } catch (error) { + if (reportExceptionCallback) { + reportExceptionCallback(error); + } else { + throw error; + } + } + }); } // Some "extensions" rely on "BadResource" and "Interrupted" errors in the @@ -252,6 +279,7 @@ opCallTraces, refOp, unrefOp, + setReportExceptionCallback, close: (rid) => ops.op_close(rid), tryClose: (rid) => ops.op_try_close(rid), read: opAsync.bind(null, "op_read"), diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index cbc5c1b5b2..b25022a089 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -76,7 +76,7 @@ delete Intl.v8BreakIterator; const errors = window.__bootstrap.errors.errors; const webidl = window.__bootstrap.webidl; const domException = window.__bootstrap.domException; - const { defineEventHandler } = window.__bootstrap.event; + const { defineEventHandler, reportException } = window.__bootstrap.event; const { deserializeJsMessageData, serializeJsMessageData } = window.__bootstrap.messagePort; @@ -243,6 +243,7 @@ delete Intl.v8BreakIterator; core.setMacrotaskCallback(timers.handleTimerMacrotask); core.setMacrotaskCallback(promiseRejectMacrotaskCallback); core.setWasmStreamingCallback(fetch.handleWasmStreaming); + core.setReportExceptionCallback(reportException); ops.op_set_format_exception_callback(formatException); version.setVersions( runtimeOptions.denoVersion, diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json index 813abbbba5..39fe595151 100644 --- a/tools/wpt/expectation.json +++ b/tools/wpt/expectation.json @@ -3615,7 +3615,7 @@ "type-long-settimeout.any.worker.html": true }, "microtask-queuing": { - "queue-microtask-exceptions.any.html": false, + "queue-microtask-exceptions.any.html": true, "queue-microtask.any.html": true, "queue-microtask.any.worker.html": true }, @@ -4610,4 +4610,4 @@ "idlharness.https.any.worker.html": true, "idlharness-shadowrealm.window.html": false } -} \ No newline at end of file +}