From c30d95f2e36cb3519e1e23c0934b388ebba6bc2c Mon Sep 17 00:00:00 2001 From: Nayeem Rahman Date: Tue, 19 Apr 2022 09:59:51 +0100 Subject: [PATCH] feat(ext/web): add globalThis.reportError() (#13799) --- cli/fmt_errors.rs | 5 +++-- cli/tests/integration/run_tests.rs | 11 +++++++++++ cli/tests/testdata/report_error.ts | 3 +++ cli/tests/testdata/report_error.ts.out | 5 +++++ cli/tests/testdata/report_error_handled.ts | 19 +++++++++++++++++++ .../testdata/report_error_handled.ts.out | 13 +++++++++++++ ext/web/02_event.js | 15 +++++++++++++++ ext/web/lib.deno_web.d.ts | 19 +++++++++++++++++++ runtime/web_worker.rs | 3 ++- tools/wpt/expectation.json | 17 +++-------------- 10 files changed, 93 insertions(+), 17 deletions(-) create mode 100644 cli/tests/testdata/report_error.ts create mode 100644 cli/tests/testdata/report_error.ts.out create mode 100644 cli/tests/testdata/report_error_handled.ts create mode 100644 cli/tests/testdata/report_error_handled.ts.out diff --git a/cli/fmt_errors.rs b/cli/fmt_errors.rs index 953dfec8b5..3016e0ff4a 100644 --- a/cli/fmt_errors.rs +++ b/cli/fmt_errors.rs @@ -48,8 +48,9 @@ pub fn format_location(frame: &JsStackFrame) -> String { return cyan("native").to_string(); } let mut result = String::new(); - if let Some(file_name) = &frame.file_name { - result += &cyan(&format_file_name(file_name)).to_string(); + let file_name = frame.file_name.clone().unwrap_or_default(); + if !file_name.is_empty() { + result += &cyan(&format_file_name(&file_name)).to_string(); } else { if frame.is_eval { result += diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs index b99ab28906..85363a27fb 100644 --- a/cli/tests/integration/run_tests.rs +++ b/cli/tests/integration/run_tests.rs @@ -2753,3 +2753,14 @@ fn deno_no_prompt_environment_variable() { .unwrap(); assert!(output.status.success()); } + +itest!(report_error { + args: "run --quiet report_error.ts", + output: "report_error.ts.out", + exit_code: 1, +}); + +itest!(report_error_handled { + args: "run --quiet report_error_handled.ts", + output: "report_error_handled.ts.out", +}); diff --git a/cli/tests/testdata/report_error.ts b/cli/tests/testdata/report_error.ts new file mode 100644 index 0000000000..a6d4af1fdd --- /dev/null +++ b/cli/tests/testdata/report_error.ts @@ -0,0 +1,3 @@ +console.log(1); +reportError(new Error("foo")); +console.log(2); diff --git a/cli/tests/testdata/report_error.ts.out b/cli/tests/testdata/report_error.ts.out new file mode 100644 index 0000000000..185db62a54 --- /dev/null +++ b/cli/tests/testdata/report_error.ts.out @@ -0,0 +1,5 @@ +1 +error: Uncaught Error: foo +reportError(new Error("foo")); + ^ + at [WILDCARD]/report_error.ts:2:13 diff --git a/cli/tests/testdata/report_error_handled.ts b/cli/tests/testdata/report_error_handled.ts new file mode 100644 index 0000000000..de58f0b8e5 --- /dev/null +++ b/cli/tests/testdata/report_error_handled.ts @@ -0,0 +1,19 @@ +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); +}; + +console.log(1); +reportError(new Error("foo")); +console.log(2); diff --git a/cli/tests/testdata/report_error_handled.ts.out b/cli/tests/testdata/report_error_handled.ts.out new file mode 100644 index 0000000000..89fa30314f --- /dev/null +++ b/cli/tests/testdata/report_error_handled.ts.out @@ -0,0 +1,13 @@ +1 +{ + cancelable: true, + message: "Uncaught Error: foo", + filename: "[WILDCARD]/report_error_handled.ts", + lineno: 18, + colno: 13, + error: Error: foo + at [WILDCARD]/report_error_handled.ts:18:13 +} +onerror() called Error: foo + at [WILDCARD]/report_error_handled.ts:18:13 +2 diff --git a/ext/web/02_event.js b/ext/web/02_event.js index 677768ac9b..06df6429cf 100644 --- a/ext/web/02_event.js +++ b/ext/web/02_event.js @@ -1367,6 +1367,20 @@ reportExceptionStackedCalls--; } + function checkThis(thisArg) { + if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) { + throw new TypeError("Illegal invocation"); + } + } + + // https://html.spec.whatwg.org/#dom-reporterror + function reportError(error) { + checkThis(this); + const prefix = "Failed to call 'reportError'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + reportException(error); + } + window.Event = Event; window.EventTarget = EventTarget; window.ErrorEvent = ErrorEvent; @@ -1377,6 +1391,7 @@ window.dispatchEvent = EventTarget.prototype.dispatchEvent; window.addEventListener = EventTarget.prototype.addEventListener; window.removeEventListener = EventTarget.prototype.removeEventListener; + window.reportError = reportError; window.__bootstrap.eventTarget = { EventTarget, setEventTargetData, diff --git a/ext/web/lib.deno_web.d.ts b/ext/web/lib.deno_web.d.ts index 77c502fac4..8c845ced10 100644 --- a/ext/web/lib.deno_web.d.ts +++ b/ext/web/lib.deno_web.d.ts @@ -890,3 +890,22 @@ declare class DecompressionStream { readonly readable: ReadableStream; readonly writable: WritableStream; } + +/** Dispatch an uncaught exception. Similar to a synchronous version of: + * ```ts + * setTimeout(() => { throw error; }, 0); + * ``` + * The error can not be caught with a `try/catch` block. An error event will + * be dispatched to the global scope. You can prevent the error from being + * reported to the console with `Event.prototype.preventDefault()`: + * ```ts + * addEventListener("error", (event) => { + * event.preventDefault(); + * }); + * reportError(new Error("foo")); // Will not be reported. + * ``` + * In Deno, this error will terminate the process if not intercepted like above. + */ +declare function reportError( + error: any, +): void; diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs index 40b222417c..f4c040aa4f 100644 --- a/runtime/web_worker.rs +++ b/runtime/web_worker.rs @@ -638,7 +638,8 @@ impl WebWorker { v8::Local::::new(scope, poll_for_messages_fn); let fn_ = v8::Local::::try_from(poll_for_messages).unwrap(); let undefined = v8::undefined(scope); - fn_.call(scope, undefined.into(), &[]).unwrap(); + // This call may return `None` if worker is terminated. + fn_.call(scope, undefined.into(), &[]); } } diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json index 38a1e3db54..67e2563531 100644 --- a/tools/wpt/expectation.json +++ b/tools/wpt/expectation.json @@ -3646,18 +3646,8 @@ "queue-microtask.any.worker.html": true }, "scripting": { - "reporterror.any.html": [ - "self.reportError(1)", - "self.reportError(TypeError)", - "self.reportError(undefined)", - "self.reportError() doesn't invoke getters" - ], - "reporterror.any.worker.html": [ - "self.reportError(1)", - "self.reportError(TypeError)", - "self.reportError(undefined)", - "self.reportError() doesn't invoke getters" - ] + "reporterror.any.html": false, + "reporterror.any.worker.html": false }, "structured-clone": { "structured-clone.any.html": [ @@ -4237,7 +4227,6 @@ "opening-handshake": { "003-sets-origin.worker.html": false }, - "Create-on-worker-shutdown.any.worker.html": false, "Send-data.worker.html": true, "Send-data.worker.html?wpt_flags=h2": false, "Send-data.worker.html?wss": true, @@ -4648,4 +4637,4 @@ "idlharness.https.any.worker.html": true, "idlharness-shadowrealm.window.html": false } -} \ No newline at end of file +}