diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 483144fcfb..4faad19f86 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -236,7 +236,7 @@ jobs:
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
- key: 5-cargo-home-${{ matrix.os }}-${{ hashFiles('Cargo.lock') }}
+ key: 7-cargo-home-${{ matrix.os }}-${{ hashFiles('Cargo.lock') }}
# In main branch, always creates fresh cache
- name: Cache build output (main)
@@ -252,7 +252,7 @@ jobs:
!./target/*/*.zip
!./target/*/*.tar.gz
key: |
- 5-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ github.sha }}
+ 7-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ github.sha }}
# Restore cache from the latest 'main' branch build.
- name: Cache build output (PR)
@@ -268,7 +268,7 @@ jobs:
!./target/*/*.tar.gz
key: never_saved
restore-keys: |
- 5-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-
+ 7-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-
# Don't save cache after building PRs or branches other than 'main'.
- name: Skip save cache (PR)
diff --git a/cli/dts/lib.deno.window.d.ts b/cli/dts/lib.deno.window.d.ts
index fbd0a967b6..d0e04a7a81 100644
--- a/cli/dts/lib.deno.window.d.ts
+++ b/cli/dts/lib.deno.window.d.ts
@@ -7,10 +7,15 @@
///
///
+interface WindowEventMap {
+ "error": ErrorEvent;
+}
+
declare class Window extends EventTarget {
new(): Window;
readonly window: Window & typeof globalThis;
readonly self: Window & typeof globalThis;
+ onerror: ((this: Window, ev: ErrorEvent) => any) | null;
onload: ((this: Window, ev: Event) => any) | null;
onunload: ((this: Window, ev: Event) => any) | null;
close: () => void;
@@ -25,10 +30,38 @@ declare class Window extends EventTarget {
location: Location;
localStorage: Storage;
sessionStorage: Storage;
+
+ addEventListener(
+ type: K,
+ listener: (
+ this: Window,
+ ev: WindowEventMap[K],
+ ) => any,
+ options?: boolean | AddEventListenerOptions,
+ ): void;
+ addEventListener(
+ type: string,
+ listener: EventListenerOrEventListenerObject,
+ options?: boolean | AddEventListenerOptions,
+ ): void;
+ removeEventListener(
+ type: K,
+ listener: (
+ this: Window,
+ ev: WindowEventMap[K],
+ ) => any,
+ options?: boolean | EventListenerOptions,
+ ): void;
+ removeEventListener(
+ type: string,
+ listener: EventListenerOrEventListenerObject,
+ options?: boolean | EventListenerOptions,
+ ): void;
}
declare var window: Window & typeof globalThis;
declare var self: Window & typeof globalThis;
+declare var onerror: ((this: Window, ev: ErrorEvent) => any) | null;
declare var onload: ((this: Window, ev: Event) => any) | null;
declare var onunload: ((this: Window, ev: Event) => any) | null;
declare var localStorage: Storage;
@@ -77,10 +110,17 @@ declare function prompt(message?: string, defaultValue?: string): string | null;
* dispatchEvent(new Event('unload'));
* ```
*/
+declare function addEventListener<
+ K extends keyof WindowEventMap,
+>(
+ type: K,
+ listener: (this: Window, ev: WindowEventMap[K]) => any,
+ options?: boolean | AddEventListenerOptions,
+): void;
declare function addEventListener(
type: string,
- callback: EventListenerOrEventListenerObject | null,
- options?: boolean | AddEventListenerOptions | undefined,
+ listener: EventListenerOrEventListenerObject,
+ options?: boolean | AddEventListenerOptions,
): void;
/** Remove a previously registered event listener from the global scope
@@ -91,10 +131,17 @@ declare function addEventListener(
* removeEventListener('load', listener);
* ```
*/
+declare function removeEventListener<
+ K extends keyof WindowEventMap,
+>(
+ type: K,
+ listener: (this: Window, ev: WindowEventMap[K]) => any,
+ options?: boolean | EventListenerOptions,
+): void;
declare function removeEventListener(
type: string,
- callback: EventListenerOrEventListenerObject | null,
- options?: boolean | EventListenerOptions | undefined,
+ listener: EventListenerOrEventListenerObject,
+ options?: boolean | EventListenerOptions,
): void;
// TODO(nayeemrmn): Move this to `extensions/web` where its implementation is.
diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs
index a56e3f0f15..969a57a9fe 100644
--- a/cli/tests/integration/run_tests.rs
+++ b/cli/tests/integration/run_tests.rs
@@ -2685,3 +2685,32 @@ itest!(future_check2 {
output: "future_check2.out",
envs: vec![("DENO_FUTURE_CHECK".to_string(), "1".to_string())],
});
+
+itest!(event_listener_error {
+ args: "run --quiet event_listener_error.ts",
+ output: "event_listener_error.ts.out",
+ exit_code: 1,
+});
+
+itest!(event_listener_error_handled {
+ args: "run --quiet event_listener_error_handled.ts",
+ output: "event_listener_error_handled.ts.out",
+});
+
+// https://github.com/denoland/deno/pull/14159#issuecomment-1092285446
+itest!(event_listener_error_immediate_exit {
+ args: "run --quiet event_listener_error_immediate_exit.ts",
+ output: "event_listener_error_immediate_exit.ts.out",
+ exit_code: 1,
+});
+
+itest!(set_timeout_error {
+ args: "run --quiet set_timeout_error.ts",
+ output: "set_timeout_error.ts.out",
+ exit_code: 1,
+});
+
+itest!(set_timeout_error_handled {
+ args: "run --quiet set_timeout_error_handled.ts",
+ output: "set_timeout_error_handled.ts.out",
+});
diff --git a/cli/tests/testdata/event_listener_error.ts b/cli/tests/testdata/event_listener_error.ts
new file mode 100644
index 0000000000..1cbdf7bc2e
--- /dev/null
+++ b/cli/tests/testdata/event_listener_error.ts
@@ -0,0 +1,6 @@
+addEventListener("foo", () => {
+ throw new Error("bar");
+});
+console.log(1);
+dispatchEvent(new CustomEvent("foo"));
+console.log(2);
diff --git a/cli/tests/testdata/event_listener_error.ts.out b/cli/tests/testdata/event_listener_error.ts.out
new file mode 100644
index 0000000000..a20a91dfdf
--- /dev/null
+++ b/cli/tests/testdata/event_listener_error.ts.out
@@ -0,0 +1,7 @@
+1
+error: Uncaught Error: bar
+ throw new Error("bar");
+ ^
+ at [WILDCARD]/event_listener_error.ts:2:9
+ at [WILDCARD]
+ at [WILDCARD]/event_listener_error.ts:5:1
diff --git a/cli/tests/testdata/event_listener_error_handled.ts b/cli/tests/testdata/event_listener_error_handled.ts
new file mode 100644
index 0000000000..c4c8fd1cd9
--- /dev/null
+++ b/cli/tests/testdata/event_listener_error_handled.ts
@@ -0,0 +1,23 @@
+addEventListener("error", (event) => {
+ console.log({
+ cancelable: event.cancelable,
+ message: event.message,
+ filename: event.filename?.slice?.(-100),
+ lineno: event.lineno,
+ colno: event.colno,
+ error: event.error,
+ });
+ event.preventDefault();
+});
+
+onerror = (event) => {
+ console.log("onerror() called", event.error);
+};
+
+addEventListener("foo", () => {
+ throw new Error("bar");
+});
+
+console.log(1);
+dispatchEvent(new CustomEvent("foo"));
+console.log(2);
diff --git a/cli/tests/testdata/event_listener_error_handled.ts.out b/cli/tests/testdata/event_listener_error_handled.ts.out
new file mode 100644
index 0000000000..d3cf525c33
--- /dev/null
+++ b/cli/tests/testdata/event_listener_error_handled.ts.out
@@ -0,0 +1,17 @@
+1
+{
+ cancelable: true,
+ message: "Uncaught Error: bar",
+ filename: "[WILDCARD]/event_listener_error_handled.ts",
+ lineno: 18,
+ colno: 9,
+ error: Error: bar
+ at [WILDCARD]/event_listener_error_handled.ts:18:9
+ at [WILDCARD]
+ at [WILDCARD]/event_listener_error_handled.ts:22:1
+}
+onerror() called Error: bar
+ at [WILDCARD]/event_listener_error_handled.ts:18:9
+ at [WILDCARD]
+ at [WILDCARD]/event_listener_error_handled.ts:22:1
+2
diff --git a/cli/tests/testdata/event_listener_error_immediate_exit.ts b/cli/tests/testdata/event_listener_error_immediate_exit.ts
new file mode 100644
index 0000000000..c9e94c01bd
--- /dev/null
+++ b/cli/tests/testdata/event_listener_error_immediate_exit.ts
@@ -0,0 +1,12 @@
+addEventListener("foo", () => {
+ queueMicrotask(() => console.log("queueMicrotask"));
+ setTimeout(() => console.log("timer"), 0);
+ throw new Error("bar");
+});
+console.log(1);
+// @ts-ignore Deno.core
+Deno.core.setNextTickCallback(() => console.log("nextTick"));
+// @ts-ignore Deno.core
+Deno.core.setHasTickScheduled(true);
+dispatchEvent(new CustomEvent("foo"));
+console.log(2);
diff --git a/cli/tests/testdata/event_listener_error_immediate_exit.ts.out b/cli/tests/testdata/event_listener_error_immediate_exit.ts.out
new file mode 100644
index 0000000000..8f03f71b81
--- /dev/null
+++ b/cli/tests/testdata/event_listener_error_immediate_exit.ts.out
@@ -0,0 +1,6 @@
+1
+error: Uncaught Error: bar
+ throw new Error("bar");
+ ^
+ at [WILDCARD]/event_listener_error_immediate_exit.ts:4:9[WILDCARD]
+ at [WILDCARD]/event_listener_error_immediate_exit.ts:11:1
diff --git a/cli/tests/testdata/set_timeout_error.ts b/cli/tests/testdata/set_timeout_error.ts
new file mode 100644
index 0000000000..2864574e7b
--- /dev/null
+++ b/cli/tests/testdata/set_timeout_error.ts
@@ -0,0 +1,3 @@
+setTimeout(() => {
+ throw new Error("foo");
+}, 0);
diff --git a/cli/tests/testdata/set_timeout_error.ts.out b/cli/tests/testdata/set_timeout_error.ts.out
new file mode 100644
index 0000000000..9db053f6c1
--- /dev/null
+++ b/cli/tests/testdata/set_timeout_error.ts.out
@@ -0,0 +1,5 @@
+error: Uncaught Error: foo
+ throw new Error("foo");
+ ^
+ at [WILDCARD]/set_timeout_error.ts:2:9
+ at [WILDCARD]
diff --git a/cli/tests/testdata/set_timeout_error_handled.ts b/cli/tests/testdata/set_timeout_error_handled.ts
new file mode 100644
index 0000000000..aee2d97d22
--- /dev/null
+++ b/cli/tests/testdata/set_timeout_error_handled.ts
@@ -0,0 +1,19 @@
+addEventListener("error", (event) => {
+ console.log({
+ cancelable: event.cancelable,
+ message: event.message,
+ filename: event.filename?.slice?.(-100),
+ lineno: event.lineno,
+ colno: event.colno,
+ error: event.error,
+ });
+ event.preventDefault();
+});
+
+onerror = (event) => {
+ console.log("onerror() called", event.error);
+};
+
+setTimeout(() => {
+ throw new Error("foo");
+}, 0);
diff --git a/cli/tests/testdata/set_timeout_error_handled.ts.out b/cli/tests/testdata/set_timeout_error_handled.ts.out
new file mode 100644
index 0000000000..054dd9b6b8
--- /dev/null
+++ b/cli/tests/testdata/set_timeout_error_handled.ts.out
@@ -0,0 +1,13 @@
+{
+ cancelable: true,
+ message: "Uncaught Error: foo",
+ filename: "[WILDCARD]/set_timeout_error_handled.ts",
+ lineno: 18,
+ colno: 9,
+ error: Error: foo
+ at [WILDCARD]/set_timeout_error_handled.ts:18:9
+ at [WILDCARD]
+}
+onerror() called Error: foo
+ at [WILDCARD]/set_timeout_error_handled.ts:18:9
+ at [WILDCARD]
diff --git a/cli/tests/testdata/worker_drop_handle_race.js.out b/cli/tests/testdata/worker_drop_handle_race.js.out
index 34c2d5be26..a81684bfa7 100644
--- a/cli/tests/testdata/worker_drop_handle_race.js.out
+++ b/cli/tests/testdata/worker_drop_handle_race.js.out
@@ -4,5 +4,5 @@ error: Uncaught (in worker "") Error
at [WILDCARD]/workers/drop_handle_race.js:2:9
at Object.action (deno:ext/web/02_timers.js:[WILDCARD])
at handleTimerMacrotask (deno:ext/web/02_timers.js:[WILDCARD])
-error: Uncaught (in promise) Error: Unhandled error event in child worker.
+error: Uncaught (in promise) Error: Unhandled error in child worker.
at Worker.#pollControl (deno:runtime/js/11_workers.js:[WILDCARD])
diff --git a/cli/tests/testdata/worker_event_handler_test.js.out b/cli/tests/testdata/worker_event_handler_test.js.out
index 5556633b14..b3eed7f6c8 100644
--- a/cli/tests/testdata/worker_event_handler_test.js.out
+++ b/cli/tests/testdata/worker_event_handler_test.js.out
@@ -1,10 +1,10 @@
Target from self.onmessage: [object DedicatedWorkerGlobalScope]
Target from message event listener: [object DedicatedWorkerGlobalScope]
Arguments from self.onerror: [
- "Some error message",
- "",
- 0,
- 0,
+ "Uncaught Error: Some error message",
+ "[WILDCARD]/worker_event_handlers.js",
+ 9,
+ 9,
Error: Some error message
at [WILDCARD]
]
diff --git a/cli/tests/testdata/workers/nonexistent_worker.out b/cli/tests/testdata/workers/nonexistent_worker.out
index 1b5111b14c..08a7e74e70 100644
--- a/cli/tests/testdata/workers/nonexistent_worker.out
+++ b/cli/tests/testdata/workers/nonexistent_worker.out
@@ -1,3 +1,3 @@
[WILDCARD]error: Uncaught (in worker "") Module not found "file:///[WILDCARD]/workers/doesnt_exist.js".
-error: Uncaught (in promise) Error: Unhandled error event in child worker.
+error: Uncaught (in promise) Error: Unhandled error in child worker.
at Worker.#pollControl ([WILDCARD])
diff --git a/cli/tests/testdata/workers/permissions_blob_local.ts.out b/cli/tests/testdata/workers/permissions_blob_local.ts.out
index ee19c7ab56..9d5336bfe8 100644
--- a/cli/tests/testdata/workers/permissions_blob_local.ts.out
+++ b/cli/tests/testdata/workers/permissions_blob_local.ts.out
@@ -1,4 +1,4 @@
error: Uncaught (in worker "") Requires read access to "[WILDCARD]local_file.ts", run again with the --allow-read flag
at blob:null/[WILDCARD]:1:8
-error: Uncaught (in promise) Error: Unhandled error event in child worker.
+error: Uncaught (in promise) Error: Unhandled error in child worker.
at Worker.#pollControl ([WILDCARD])
diff --git a/cli/tests/testdata/workers/permissions_blob_remote.ts.out b/cli/tests/testdata/workers/permissions_blob_remote.ts.out
index 597e5bf1e8..ac06e6ccb4 100644
--- a/cli/tests/testdata/workers/permissions_blob_remote.ts.out
+++ b/cli/tests/testdata/workers/permissions_blob_remote.ts.out
@@ -1,4 +1,4 @@
error: Uncaught (in worker "") Requires net access to "example.com", run again with the --allow-net flag
at blob:null/[WILDCARD]:1:8
-error: Uncaught (in promise) Error: Unhandled error event in child worker.
+error: Uncaught (in promise) Error: Unhandled error in child worker.
at Worker.#pollControl ([WILDCARD])
diff --git a/cli/tests/testdata/workers/permissions_data_local.ts.out b/cli/tests/testdata/workers/permissions_data_local.ts.out
index 5c9bcf1c14..96711f9a08 100644
--- a/cli/tests/testdata/workers/permissions_data_local.ts.out
+++ b/cli/tests/testdata/workers/permissions_data_local.ts.out
@@ -1,4 +1,4 @@
error: Uncaught (in worker "") Requires read access to "[WILDCARD]local_file.ts", run again with the --allow-read flag
at data:application/javascript;base64,[WILDCARD]:1:8
-error: Uncaught (in promise) Error: Unhandled error event in child worker.
+error: Uncaught (in promise) Error: Unhandled error in child worker.
at Worker.#pollControl ([WILDCARD])
diff --git a/cli/tests/testdata/workers/permissions_data_remote.ts.out b/cli/tests/testdata/workers/permissions_data_remote.ts.out
index 0273664bdc..a9ed5c2401 100644
--- a/cli/tests/testdata/workers/permissions_data_remote.ts.out
+++ b/cli/tests/testdata/workers/permissions_data_remote.ts.out
@@ -1,4 +1,4 @@
error: Uncaught (in worker "") Requires net access to "example.com", run again with the --allow-net flag
at data:application/javascript;base64,aW1wb3J0ICJodHRwczovL2V4YW1wbGUuY29tL3NvbWUvZmlsZS50cyI7:1:8
-error: Uncaught (in promise) Error: Unhandled error event in child worker.
+error: Uncaught (in promise) Error: Unhandled error in child worker.
at Worker.#pollControl ([WILDCARD])
diff --git a/cli/tests/testdata/workers/permissions_dynamic_remote.ts.out b/cli/tests/testdata/workers/permissions_dynamic_remote.ts.out
index e5015abff4..cbddb61e04 100644
--- a/cli/tests/testdata/workers/permissions_dynamic_remote.ts.out
+++ b/cli/tests/testdata/workers/permissions_dynamic_remote.ts.out
@@ -2,5 +2,5 @@ error: Uncaught (in worker "") (in promise) TypeError: Requires net access to "e
await import("https://example.com/some/file.ts");
^
at async http://localhost:4545/workers/dynamic_remote.ts:2:1
-[WILDCARD]error: Uncaught (in promise) Error: Unhandled error event in child worker.
+[WILDCARD]error: Uncaught (in promise) Error: Unhandled error in child worker.
at Worker.#pollControl ([WILDCARD])
diff --git a/cli/tests/testdata/workers/permissions_remote_remote.ts.out b/cli/tests/testdata/workers/permissions_remote_remote.ts.out
index 42602cf715..be96b5d4ee 100644
--- a/cli/tests/testdata/workers/permissions_remote_remote.ts.out
+++ b/cli/tests/testdata/workers/permissions_remote_remote.ts.out
@@ -1,4 +1,4 @@
error: Uncaught (in worker "") Requires net access to "example.com", run again with the --allow-net flag
at http://localhost:4545/workers/static_remote.ts:2:8
-error: Uncaught (in promise) Error: Unhandled error event in child worker.
+error: Uncaught (in promise) Error: Unhandled error in child worker.
at Worker.#pollControl ([WILDCARD])
diff --git a/cli/tests/testdata/workers/worker_async_error.ts.out b/cli/tests/testdata/workers/worker_async_error.ts.out
index 0a05534c5f..16f6a6b84e 100644
--- a/cli/tests/testdata/workers/worker_async_error.ts.out
+++ b/cli/tests/testdata/workers/worker_async_error.ts.out
@@ -3,5 +3,5 @@ error: Uncaught (in worker "foo") (in promise) Error: bar
^
at [WILDCARD]/async_error.ts:[WILDCARD]
at [WILDCARD]/async_error.ts:[WILDCARD]
-error: Uncaught (in promise) Error: Unhandled error event in child worker.
+error: Uncaught (in promise) Error: Unhandled error in child worker.
at Worker.#pollControl ([WILDCARD])
diff --git a/cli/tests/testdata/workers/worker_error.ts.out b/cli/tests/testdata/workers/worker_error.ts.out
index cb0a025507..e7b0ea14d2 100644
--- a/cli/tests/testdata/workers/worker_error.ts.out
+++ b/cli/tests/testdata/workers/worker_error.ts.out
@@ -1,5 +1,5 @@
[WILDCARD]error: Uncaught (in worker "bar") Error: foo[WILDCARD]
at foo ([WILDCARD])
at [WILDCARD]
-error: Uncaught (in promise) Error: Unhandled error event in child worker.
+error: Uncaught (in promise) Error: Unhandled error in child worker.
at Worker.#pollControl ([WILDCARD])
diff --git a/cli/tests/testdata/workers/worker_message_handler_error.ts.out b/cli/tests/testdata/workers/worker_message_handler_error.ts.out
index 56458d5e44..0c51ca9d23 100644
--- a/cli/tests/testdata/workers/worker_message_handler_error.ts.out
+++ b/cli/tests/testdata/workers/worker_message_handler_error.ts.out
@@ -1,7 +1,7 @@
-error: Uncaught (in worker "foo") (in promise) Error: bar
+error: Uncaught (in worker "foo") Error: bar
throw new Error("bar");
^
at onmessage ([WILDCARD]/message_handler_error.ts:[WILDCARD])
at [WILDCARD]
-error: Uncaught (in promise) Error: Unhandled error event in child worker.
+error: Uncaught (in promise) Error: Unhandled error in child worker.
at Worker.#pollControl ([WILDCARD])
diff --git a/cli/tests/testdata/workers/worker_nested_error.ts.out b/cli/tests/testdata/workers/worker_nested_error.ts.out
index 5c978ca9bb..afd9f59e2b 100644
--- a/cli/tests/testdata/workers/worker_nested_error.ts.out
+++ b/cli/tests/testdata/workers/worker_nested_error.ts.out
@@ -3,7 +3,7 @@
^
at foo ([WILDCARD]/workers/error.ts:[WILDCARD])
at [WILDCARD]/workers/error.ts:[WILDCARD]
-error: Uncaught (in worker "baz") (in promise) Error: Unhandled error event in child worker.
+error: Uncaught (in worker "baz") (in promise) Error: Unhandled error in child worker.
at Worker.#pollControl ([WILDCARD])
-error: Uncaught (in promise) Error: Unhandled error event in child worker.
+error: Uncaught (in promise) Error: Unhandled error in child worker.
at Worker.#pollControl ([WILDCARD])
diff --git a/core/bindings.rs b/core/bindings.rs
index 5e37232a1f..46dcefe389 100644
--- a/core/bindings.rs
+++ b/core/bindings.rs
@@ -1,6 +1,7 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use crate::error::is_instance_of_error;
+use crate::error::JsError;
use crate::modules::get_module_type_from_assertions;
use crate::modules::parse_import_assertions;
use crate::modules::validate_import_assertions;
@@ -102,6 +103,12 @@ pub static EXTERNAL_REFERENCES: Lazy =
v8::ExternalReference {
function: abort_wasm_streaming.map_fn_to(),
},
+ v8::ExternalReference {
+ function: destructure_error.map_fn_to(),
+ },
+ v8::ExternalReference {
+ function: terminate.map_fn_to(),
+ },
])
});
@@ -228,6 +235,8 @@ pub fn initialize_context<'s>(
set_wasm_streaming_callback,
);
set_func(scope, core_val, "abortWasmStreaming", abort_wasm_streaming);
+ set_func(scope, core_val, "destructureError", destructure_error);
+ set_func(scope, core_val, "terminate", terminate);
// Direct bindings on `window`.
set_func(scope, global, "queueMicrotask", queue_microtask);
@@ -1293,6 +1302,28 @@ fn queue_microtask(
};
}
+fn destructure_error(
+ scope: &mut v8::HandleScope,
+ args: v8::FunctionCallbackArguments,
+ mut rv: v8::ReturnValue,
+) {
+ let js_error = JsError::from_v8_exception(scope, args.get(0));
+ let object = serde_v8::to_v8(scope, js_error).unwrap();
+ rv.set(object);
+}
+
+fn terminate(
+ scope: &mut v8::HandleScope,
+ args: v8::FunctionCallbackArguments,
+ _rv: v8::ReturnValue,
+) {
+ let state_rc = JsRuntime::state(scope);
+ let mut state = state_rc.borrow_mut();
+ state.explicit_terminate_exception =
+ Some(v8::Global::new(scope, args.get(0)));
+ scope.terminate_execution();
+}
+
fn create_host_object(
scope: &mut v8::HandleScope,
_args: v8::FunctionCallbackArguments,
diff --git a/core/error.rs b/core/error.rs
index 106845f046..1fc4a1af77 100644
--- a/core/error.rs
+++ b/core/error.rs
@@ -90,7 +90,8 @@ pub fn get_custom_error_class(error: &Error) -> Option<&'static str> {
/// A `JsError` represents an exception coming from V8, with stack frames and
/// line numbers. The deno_cli crate defines another `JsError` type, which wraps
/// the one defined here, that adds source map support and colorful formatting.
-#[derive(Debug, PartialEq, Clone)]
+#[derive(Debug, PartialEq, Clone, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
pub struct JsError {
pub message: String,
pub cause: Option>,
@@ -103,7 +104,7 @@ pub struct JsError {
pub stack: Option,
}
-#[derive(Debug, PartialEq, Clone, serde::Deserialize)]
+#[derive(Debug, PartialEq, Clone, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct JsStackFrame {
pub type_name: Option,
@@ -190,84 +191,84 @@ impl JsError {
let msg = v8::Exception::create_message(scope, exception);
- let (message, frames, stack, cause) =
- if is_instance_of_error(scope, exception) {
- // The exception is a JS Error object.
- let exception: v8::Local = exception.try_into().unwrap();
- let cause = get_property(scope, exception, "cause");
- let e: NativeJsError =
- serde_v8::from_v8(scope, exception.into()).unwrap();
- // Get the message by formatting error.name and error.message.
- let name = e.name.unwrap_or_else(|| "Error".to_string());
- let message_prop = e.message.unwrap_or_else(|| "".to_string());
- let message = if !name.is_empty() && !message_prop.is_empty() {
- format!("Uncaught {}: {}", name, message_prop)
- } else if !name.is_empty() {
- format!("Uncaught {}", name)
- } else if !message_prop.is_empty() {
- format!("Uncaught {}", message_prop)
- } else {
- "Uncaught".to_string()
- };
- let cause = cause.and_then(|cause| {
- if cause.is_undefined() || seen.contains(&cause) {
- None
- } else {
- seen.insert(cause);
- Some(Box::new(JsError::inner_from_v8_exception(
- scope, cause, seen,
- )))
- }
- });
-
- // Access error.stack to ensure that prepareStackTrace() has been called.
- // This should populate error.__callSiteEvals.
- let stack = get_property(scope, exception, "stack");
- let stack: Option> =
- stack.and_then(|s| s.try_into().ok());
- let stack = stack.map(|s| s.to_rust_string_lossy(scope));
-
- // Read an array of structured frames from error.__callSiteEvals.
- let frames_v8 = get_property(scope, exception, "__callSiteEvals");
- // Ignore non-array values
- let frames_v8: Option> =
- frames_v8.and_then(|a| a.try_into().ok());
-
- // Convert them into Vec
- let frames: Vec = match frames_v8 {
- Some(frames_v8) => {
- serde_v8::from_v8(scope, frames_v8.into()).unwrap()
- }
- None => vec![],
- };
- (message, frames, stack, cause)
+ if is_instance_of_error(scope, exception) {
+ // The exception is a JS Error object.
+ let exception: v8::Local = exception.try_into().unwrap();
+ let cause = get_property(scope, exception, "cause");
+ let e: NativeJsError =
+ serde_v8::from_v8(scope, exception.into()).unwrap();
+ // Get the message by formatting error.name and error.message.
+ let name = e.name.unwrap_or_else(|| "Error".to_string());
+ let message_prop = e.message.unwrap_or_else(|| "".to_string());
+ let message = if !name.is_empty() && !message_prop.is_empty() {
+ format!("Uncaught {}: {}", name, message_prop)
+ } else if !name.is_empty() {
+ format!("Uncaught {}", name)
+ } else if !message_prop.is_empty() {
+ format!("Uncaught {}", message_prop)
} else {
- // The exception is not a JS Error object.
- // Get the message given by V8::Exception::create_message(), and provide
- // empty frames.
- (
- msg.get(scope).to_rust_string_lossy(scope),
- vec![],
- None,
- None,
- )
+ "Uncaught".to_string()
};
+ let cause = cause.and_then(|cause| {
+ if cause.is_undefined() || seen.contains(&cause) {
+ None
+ } else {
+ seen.insert(cause);
+ Some(Box::new(JsError::inner_from_v8_exception(
+ scope, cause, seen,
+ )))
+ }
+ });
- Self {
- message,
- cause,
- script_resource_name: msg
- .get_script_resource_name(scope)
- .and_then(|v| v8::Local::::try_from(v).ok())
- .map(|v| v.to_rust_string_lossy(scope)),
- source_line: msg
- .get_source_line(scope)
- .map(|v| v.to_rust_string_lossy(scope)),
- line_number: msg.get_line_number(scope).and_then(|v| v.try_into().ok()),
- start_column: msg.get_start_column().try_into().ok(),
- end_column: msg.get_end_column().try_into().ok(),
- frames,
- stack,
+ // Access error.stack to ensure that prepareStackTrace() has been called.
+ // This should populate error.__callSiteEvals.
+ let stack = get_property(scope, exception, "stack");
+ let stack: Option> =
+ stack.and_then(|s| s.try_into().ok());
+ let stack = stack.map(|s| s.to_rust_string_lossy(scope));
+
+ // Read an array of structured frames from error.__callSiteEvals.
+ let frames_v8 = get_property(scope, exception, "__callSiteEvals");
+ // Ignore non-array values
+ let frames_v8: Option> =
+ frames_v8.and_then(|a| a.try_into().ok());
+
+ // Convert them into Vec
+ let frames: Vec = match frames_v8 {
+ Some(frames_v8) => serde_v8::from_v8(scope, frames_v8.into()).unwrap(),
+ None => vec![],
+ };
+ Self {
+ message,
+ cause,
+ script_resource_name: msg
+ .get_script_resource_name(scope)
+ .and_then(|v| v8::Local::::try_from(v).ok())
+ .map(|v| v.to_rust_string_lossy(scope)),
+ source_line: msg
+ .get_source_line(scope)
+ .map(|v| v.to_rust_string_lossy(scope)),
+ line_number: msg.get_line_number(scope).and_then(|v| v.try_into().ok()),
+ start_column: msg.get_start_column().try_into().ok(),
+ end_column: msg.get_end_column().try_into().ok(),
+ frames,
+ stack,
+ }
+ } else {
+ // The exception is not a JS Error object.
+ // Get the message given by V8::Exception::create_message(), and provide
+ // empty frames.
+ Self {
+ message: msg.get(scope).to_rust_string_lossy(scope),
+ cause: None,
+ script_resource_name: None,
+ source_line: None,
+ line_number: None,
+ start_column: None,
+ end_column: None,
+ frames: vec![],
+ stack: None,
+ }
}
}
}
@@ -337,6 +338,9 @@ pub(crate) fn is_instance_of_error<'s>(
let mut maybe_prototype =
value.to_object(scope).unwrap().get_prototype(scope);
while let Some(prototype) = maybe_prototype {
+ if !prototype.is_object() {
+ return false;
+ }
if prototype.strict_equals(error_prototype) {
return true;
}
diff --git a/core/runtime.rs b/core/runtime.rs
index 50f7399d4c..7f7b6ead1f 100644
--- a/core/runtime.rs
+++ b/core/runtime.rs
@@ -169,6 +169,10 @@ pub(crate) struct JsRuntimeState {
pub(crate) op_ctxs: Box<[OpCtx]>,
pub(crate) shared_array_buffer_store: Option,
pub(crate) compiled_wasm_module_store: Option,
+ /// The error that was passed to an explicit `Deno.core.terminate` call.
+ /// It will be retrieved by `exception_to_err_result` and used as an error
+ /// instead of any other exceptions.
+ pub(crate) explicit_terminate_exception: Option>,
waker: AtomicWaker,
}
@@ -384,6 +388,7 @@ impl JsRuntime {
op_state: op_state.clone(),
op_ctxs,
have_unpolled_ops: false,
+ explicit_terminate_exception: None,
waker: AtomicWaker::new(),
})));
@@ -1017,19 +1022,33 @@ pub(crate) fn exception_to_err_result<'s, T>(
exception: v8::Local,
in_promise: bool,
) -> Result {
+ let state_rc = JsRuntime::state(scope);
+ let mut state = state_rc.borrow_mut();
+
let is_terminating_exception = scope.is_execution_terminating();
let mut exception = exception;
if is_terminating_exception {
- // TerminateExecution was called. Cancel exception termination so that the
+ // TerminateExecution was called. Cancel isolate termination so that the
// exception can be created..
scope.cancel_terminate_execution();
- // Maybe make a new exception object.
- if exception.is_null_or_undefined() {
- let message = v8::String::new(scope, "execution terminated").unwrap();
- exception = v8::Exception::error(scope, message);
- }
+ // If the termination is the result of a `Deno.core.terminate` call, we want
+ // to use the exception that was passed to it rather than the exception that
+ // was passed to this function.
+ exception = state
+ .explicit_terminate_exception
+ .take()
+ .map(|exception| v8::Local::new(scope, exception))
+ .unwrap_or_else(|| {
+ // Maybe make a new exception object.
+ if exception.is_null_or_undefined() {
+ let message = v8::String::new(scope, "execution terminated").unwrap();
+ v8::Exception::error(scope, message)
+ } else {
+ exception
+ }
+ });
}
let mut js_error = JsError::from_v8_exception(scope, exception);
@@ -1039,9 +1058,6 @@ pub(crate) fn exception_to_err_result<'s, T>(
js_error.message.trim_start_matches("Uncaught ")
);
}
-
- let state_rc = JsRuntime::state(scope);
- let state = state_rc.borrow();
let js_error = (state.js_error_create_fn)(js_error);
if is_terminating_exception {
@@ -1222,7 +1238,14 @@ impl JsRuntime {
// Update status after evaluating.
status = module.get_status();
- if let Some(value) = maybe_value {
+ let explicit_terminate_exception =
+ state_rc.borrow_mut().explicit_terminate_exception.take();
+ if let Some(exception) = explicit_terminate_exception {
+ let exception = v8::Local::new(tc_scope, exception);
+ sender
+ .send(exception_to_err_result(tc_scope, exception, false))
+ .expect("Failed to send module evaluation error.");
+ } else if let Some(value) = maybe_value {
assert!(
status == v8::ModuleStatus::Evaluated
|| status == v8::ModuleStatus::Errored
@@ -3093,8 +3116,8 @@ assertEquals(1, notify_return_value);
const a1b = a1.subarray(0, 3);
const a2 = new Uint8Array([5,10,15]);
const a2b = a2.subarray(0, 3);
-
-
+
+
if (!(a1.length > 0 && a1b.length > 0)) {
throw new Error("a1 & a1b should have a length");
}
@@ -3116,7 +3139,7 @@ assertEquals(1, notify_return_value);
if (a2.byteLength > 0 || a2b.byteLength > 0) {
throw new Error("expecting a2 & a2b to be detached, a3 re-attached");
}
-
+
const wmem = new WebAssembly.Memory({ initial: 1, maximum: 2 });
const w32 = new Uint32Array(wmem.buffer);
w32[0] = 1; w32[1] = 2; w32[2] = 3;
diff --git a/ext/web/02_event.js b/ext/web/02_event.js
index 122add2114..f1078b4ac2 100644
--- a/ext/web/02_event.js
+++ b/ext/web/02_event.js
@@ -7,6 +7,7 @@
"use strict";
((window) => {
+ const core = window.Deno.core;
const webidl = window.__bootstrap.webidl;
const { DOMException } = window.__bootstrap.domException;
const consoleInternal = window.__bootstrap.console;
@@ -32,6 +33,7 @@
ObjectPrototypeIsPrototypeOf,
ReflectDefineProperty,
SafeArrayIterator,
+ StringPrototypeStartsWith,
Symbol,
SymbolFor,
SymbolToStringTag,
@@ -787,7 +789,11 @@
setCurrentTarget(eventImpl, tuple.item);
- innerInvokeEventListeners(eventImpl, getListeners(tuple.item));
+ try {
+ innerInvokeEventListeners(eventImpl, getListeners(tuple.item));
+ } catch (error) {
+ reportException(error);
+ }
}
function normalizeAddEventHandlerOptions(
@@ -1317,6 +1323,49 @@
});
}
+ let reportExceptionStackedCalls = 0;
+
+ // https://html.spec.whatwg.org/#report-the-exception
+ function reportException(error) {
+ reportExceptionStackedCalls++;
+ const jsError = core.destructureError(error);
+ const message = jsError.message;
+ let filename = "";
+ let lineno = 0;
+ let colno = 0;
+ if (jsError.frames.length > 0) {
+ filename = jsError.frames[0].fileName;
+ lineno = jsError.frames[0].lineNumber;
+ colno = jsError.frames[0].columnNumber;
+ } else {
+ const jsError = core.destructureError(new Error());
+ for (const frame of jsError.frames) {
+ if (
+ typeof frame.fileName == "string" &&
+ !StringPrototypeStartsWith(frame.fileName, "deno:")
+ ) {
+ filename = frame.fileName;
+ lineno = frame.lineNumber;
+ colno = frame.columnNumber;
+ break;
+ }
+ }
+ }
+ const event = new ErrorEvent("error", {
+ cancelable: true,
+ message,
+ filename,
+ lineno,
+ colno,
+ error,
+ });
+ // Avoid recursing `reportException()` via error handlers more than once.
+ if (reportExceptionStackedCalls > 1 || window.dispatchEvent(event)) {
+ core.terminate(error);
+ }
+ reportExceptionStackedCalls--;
+ }
+
window.Event = Event;
window.EventTarget = EventTarget;
window.ErrorEvent = ErrorEvent;
@@ -1333,6 +1382,7 @@
listenerCount,
};
window.__bootstrap.event = {
+ reportException,
setIsTrusted,
setTarget,
defineEventHandler,
diff --git a/ext/web/02_timers.js b/ext/web/02_timers.js
index 808d995638..edad89ace4 100644
--- a/ext/web/02_timers.js
+++ b/ext/web/02_timers.js
@@ -21,6 +21,7 @@
TypeError,
} = window.__bootstrap.primordials;
const { webidl } = window.__bootstrap;
+ const { reportException } = window.__bootstrap.event;
const { assert } = window.__bootstrap.infra;
function opNow() {
@@ -139,13 +140,16 @@
// 2.
// 3.
- // TODO(@andreubotella): Error handling.
if (typeof callback === "function") {
- FunctionPrototypeCall(
- callback,
- globalThis,
- ...new SafeArrayIterator(args),
- );
+ try {
+ FunctionPrototypeCall(
+ callback,
+ globalThis,
+ ...new SafeArrayIterator(args),
+ );
+ } catch (error) {
+ reportException(error);
+ }
} else {
// TODO(@andreubotella): eval doesn't seem to have a primordial, but
// it can be redefined in the global scope.
diff --git a/runtime/js/11_workers.js b/runtime/js/11_workers.js
index 80e85a3a15..5b8d03e714 100644
--- a/runtime/js/11_workers.js
+++ b/runtime/js/11_workers.js
@@ -140,14 +140,16 @@
error: null,
});
- let handled = false;
-
this.dispatchEvent(event);
- if (event.defaultPrevented) {
- handled = true;
+ // Don't bubble error event to window for loader errors (`!e.fileName`).
+ // TODO(nayeemrmn): Currently these are never bubbled because worker
+ // error event fields aren't populated correctly and `e.fileName` is
+ // always empty.
+ if (e.fileName && !event.defaultPrevented) {
+ window.dispatchEvent(event);
}
- return handled;
+ return event.defaultPrevented;
}
#pollControl = async () => {
@@ -165,7 +167,7 @@
} /* falls through */
case 2: { // Error
if (!this.#handleError(data)) {
- throw new Error("Unhandled error event in child worker.");
+ throw new Error("Unhandled error in child worker.");
}
break;
}
diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js
index 3a37abeab8..44d457e5cb 100644
--- a/runtime/js/99_main.js
+++ b/runtime/js/99_main.js
@@ -558,6 +558,7 @@ delete Object.prototype.__proto__;
eventTarget.setEventTargetData(globalThis);
+ defineEventHandler(window, "error");
defineEventHandler(window, "load");
defineEventHandler(window, "unload");
diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs
index 0e31670419..014b3e738b 100644
--- a/runtime/web_worker.rs
+++ b/runtime/web_worker.rs
@@ -119,6 +119,7 @@ pub struct WebWorkerInternalHandle {
has_terminated: Arc,
terminate_waker: Arc,
isolate_handle: v8::IsolateHandle,
+ pub name: String,
pub worker_type: WebWorkerType,
}
@@ -264,6 +265,7 @@ impl WebWorkerHandle {
fn create_handles(
isolate_handle: v8::IsolateHandle,
+ name: String,
worker_type: WebWorkerType,
) -> (WebWorkerInternalHandle, SendableWebWorkerHandle) {
let (parent_port, worker_port) = create_entangled_message_port();
@@ -272,13 +274,14 @@ fn create_handles(
let has_terminated = Arc::new(AtomicBool::new(false));
let terminate_waker = Arc::new(AtomicWaker::new());
let internal_handle = WebWorkerInternalHandle {
- sender: ctrl_tx,
+ name,
port: Rc::new(parent_port),
termination_signal: termination_signal.clone(),
has_terminated: has_terminated.clone(),
terminate_waker: terminate_waker.clone(),
isolate_handle: isolate_handle.clone(),
cancel: CancelHandle::new_rc(),
+ sender: ctrl_tx,
worker_type,
};
let external_handle = SendableWebWorkerHandle {
@@ -452,7 +455,7 @@ impl WebWorker {
let (internal_handle, external_handle) = {
let handle = js_runtime.v8_isolate().thread_safe_handle();
let (internal_handle, external_handle) =
- create_handles(handle, options.worker_type);
+ create_handles(handle, name.clone(), options.worker_type);
let op_state = js_runtime.op_state();
let mut op_state = op_state.borrow_mut();
op_state.put(internal_handle.clone());
diff --git a/runtime/worker.rs b/runtime/worker.rs
index db9490dbed..983f174aa7 100644
--- a/runtime/worker.rs
+++ b/runtime/worker.rs
@@ -217,6 +217,10 @@ impl MainWorker {
async fn evaluate_module(&mut self, id: ModuleId) -> Result<(), AnyError> {
let mut receiver = self.js_runtime.mod_evaluate(id);
tokio::select! {
+ // Not using biased mode leads to non-determinism for relatively simple
+ // programs.
+ biased;
+
maybe_result = &mut receiver => {
debug!("received module evaluate {:#?}", maybe_result);
maybe_result.expect("Module evaluation result not provided.")