diff --git a/cli/ops/worker_host.rs b/cli/ops/worker_host.rs index ad915f7f7d..4c344e78d4 100644 --- a/cli/ops/worker_host.rs +++ b/cli/ops/worker_host.rs @@ -1,5 +1,6 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +use crate::colors; use crate::fmt_errors::JsError; use crate::ops::io::get_stdio; use crate::permissions::Permissions; @@ -8,7 +9,9 @@ use crate::tokio_util::create_basic_runtime; use crate::worker::WebWorker; use crate::worker::WebWorkerHandle; use crate::worker::WorkerEvent; +use deno_core::error::generic_error; use deno_core::error::AnyError; +use deno_core::futures::channel::mpsc; use deno_core::futures::future::FutureExt; use deno_core::serde_json; use deno_core::serde_json::json; @@ -25,7 +28,15 @@ use std::rc::Rc; use std::sync::Arc; use std::thread::JoinHandle; -pub fn init(rt: &mut deno_core::JsRuntime) { +#[derive(Deserialize)] +struct HostUnhandledErrorArgs { + message: String, +} + +pub fn init( + rt: &mut deno_core::JsRuntime, + sender: Option>, +) { { let op_state = rt.op_state(); let mut state = op_state.borrow_mut(); @@ -40,6 +51,21 @@ pub fn init(rt: &mut deno_core::JsRuntime) { ); super::reg_json_sync(rt, "op_host_post_message", op_host_post_message); super::reg_json_async(rt, "op_host_get_message", op_host_get_message); + super::reg_json_sync( + rt, + "op_host_unhandled_error", + move |_state, args, _zero_copy| { + if let Some(mut sender) = sender.clone() { + let args: HostUnhandledErrorArgs = serde_json::from_value(args)?; + sender + .try_send(WorkerEvent::Error(generic_error(args.message))) + .expect("Failed to propagate error event to parent worker"); + Ok(json!(true)) + } else { + Err(generic_error("Cannot be called from main worker.")) + } + }, + ); } pub type WorkersTable = HashMap, WebWorkerHandle)>; @@ -162,6 +188,12 @@ fn run_worker_thread( } if let Err(e) = result { + eprintln!( + "{}: Uncaught (in worker \"{}\") {}", + colors::red_bold("error"), + name, + e.to_string().trim_start_matches("Uncaught "), + ); sender .try_send(WorkerEvent::TerminalError(e)) .expect("Failed to post message to host"); diff --git a/cli/rt/11_workers.js b/cli/rt/11_workers.js index 0a726a7147..dcf98aee60 100644 --- a/cli/rt/11_workers.js +++ b/cli/rt/11_workers.js @@ -3,6 +3,7 @@ ((window) => { const core = window.Deno.core; + const { Window } = window.__bootstrap.globalInterfaces; const { log } = window.__bootstrap.util; function createWorker( @@ -142,7 +143,14 @@ if (type === "terminalError") { this.#terminated = true; if (!this.#handleError(event.error)) { - throw Error(event.error.message); + if (globalThis instanceof Window) { + throw new Error("Unhandled error event reached main worker."); + } else { + core.jsonOpSync( + "op_host_unhandled_error", + { message: event.error.message }, + ); + } } continue; } @@ -154,7 +162,14 @@ if (type === "error") { if (!this.#handleError(event.error)) { - throw Error(event.error.message); + if (globalThis instanceof Window) { + throw new Error("Unhandled error event reached main worker."); + } else { + core.jsonOpSync( + "op_host_unhandled_error", + { message: event.error.message }, + ); + } } continue; } diff --git a/cli/rt/99_main.js b/cli/rt/99_main.js index e332647267..d7cf50588e 100644 --- a/cli/rt/99_main.js +++ b/cli/rt/99_main.js @@ -7,8 +7,8 @@ delete Object.prototype.__proto__; ((window) => { const core = Deno.core; const util = window.__bootstrap.util; - const { illegalConstructorKey } = window.__bootstrap.webUtil; const eventTarget = window.__bootstrap.eventTarget; + const globalInterfaces = window.__bootstrap.globalInterfaces; const dispatchMinimal = window.__bootstrap.dispatchMinimal; const build = window.__bootstrap.build; const version = window.__bootstrap.version; @@ -192,42 +192,6 @@ delete Object.prototype.__proto__; core.registerErrorClass("URIError", URIError); } - class Window extends EventTarget { - constructor(key) { - if (key !== illegalConstructorKey) { - throw new TypeError("Illegal constructor."); - } - } - - get [Symbol.toStringTag]() { - return "Window"; - } - } - - class WorkerGlobalScope extends EventTarget { - constructor(key) { - if (key != illegalConstructorKey) { - throw new TypeError("Illegal constructor."); - } - } - - get [Symbol.toStringTag]() { - return "WorkerGlobalScope"; - } - } - - class DedicatedWorkerGlobalScope extends WorkerGlobalScope { - constructor(key) { - if (key != illegalConstructorKey) { - throw new TypeError("Illegal constructor."); - } - } - - get [Symbol.toStringTag]() { - return "DedicatedWorkerGlobalScope"; - } - } - // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope const windowOrWorkerGlobalScope = { Blob: util.nonEnumerable(fetch.Blob), @@ -277,7 +241,7 @@ delete Object.prototype.__proto__; }; const mainRuntimeGlobalProperties = { - Window: util.nonEnumerable(Window), + Window: globalInterfaces.windowConstructorDescriptor, window: util.readOnly(globalThis), self: util.readOnly(globalThis), // TODO(bartlomieju): from MDN docs (https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope) @@ -292,8 +256,9 @@ delete Object.prototype.__proto__; }; const workerRuntimeGlobalProperties = { - WorkerGlobalScope: util.nonEnumerable(WorkerGlobalScope), - DedicatedWorkerGlobalScope: util.nonEnumerable(DedicatedWorkerGlobalScope), + WorkerGlobalScope: globalInterfaces.workerGlobalScopeConstructorDescriptor, + DedicatedWorkerGlobalScope: + globalInterfaces.dedicatedWorkerGlobalScopeConstructorDescriptor, self: util.readOnly(globalThis), onmessage: util.writable(onmessage), onerror: util.writable(onerror), diff --git a/cli/tests/073_worker_error.ts b/cli/tests/073_worker_error.ts new file mode 100644 index 0000000000..736c4fde69 --- /dev/null +++ b/cli/tests/073_worker_error.ts @@ -0,0 +1,5 @@ +const worker = new Worker( + new URL("subdir/worker_error.ts", import.meta.url).href, + { type: "module", name: "bar" }, +); +setTimeout(() => worker.terminate(), 30000); diff --git a/cli/tests/073_worker_error.ts.out b/cli/tests/073_worker_error.ts.out new file mode 100644 index 0000000000..412ab33760 --- /dev/null +++ b/cli/tests/073_worker_error.ts.out @@ -0,0 +1,5 @@ +[WILDCARD]error: Uncaught (in worker "bar") Error: foo[WILDCARD] + at foo ([WILDCARD]) + at [WILDCARD] +error: Uncaught Error: Unhandled error event reached main worker. + at Worker.#poll ([WILDCARD]) diff --git a/cli/tests/074_worker_nested_error.ts b/cli/tests/074_worker_nested_error.ts new file mode 100644 index 0000000000..975d897caa --- /dev/null +++ b/cli/tests/074_worker_nested_error.ts @@ -0,0 +1,5 @@ +const worker = new Worker( + new URL("073_worker_error.ts", import.meta.url).href, + { type: "module", name: "baz" }, +); +setTimeout(() => worker.terminate(), 30000); diff --git a/cli/tests/074_worker_nested_error.ts.out b/cli/tests/074_worker_nested_error.ts.out new file mode 100644 index 0000000000..412ab33760 --- /dev/null +++ b/cli/tests/074_worker_nested_error.ts.out @@ -0,0 +1,5 @@ +[WILDCARD]error: Uncaught (in worker "bar") Error: foo[WILDCARD] + at foo ([WILDCARD]) + at [WILDCARD] +error: Uncaught Error: Unhandled error event reached main worker. + at Worker.#poll ([WILDCARD]) diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index d7786e9668..0c11ea75e9 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -2136,6 +2136,18 @@ fn _066_prompt() { util::test_pty(args, output, input); } +itest!(_073_worker_error { + args: "run -A 073_worker_error.ts", + output: "073_worker_error.ts.out", + exit_code: 1, +}); + +itest!(_074_worker_nested_error { + args: "run -A 074_worker_nested_error.ts", + output: "074_worker_nested_error.ts.out", + exit_code: 1, +}); + itest!(js_import_detect { args: "run --quiet --reload js_import_detect.ts", output: "js_import_detect.ts.out", diff --git a/cli/tests/subdir/worker_error.ts b/cli/tests/subdir/worker_error.ts new file mode 100644 index 0000000000..495971090d --- /dev/null +++ b/cli/tests/subdir/worker_error.ts @@ -0,0 +1,5 @@ +function foo() { + throw new Error("foo"); +} + +foo(); diff --git a/cli/worker.rs b/cli/worker.rs index d9d9f61c1f..877af32089 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -1,5 +1,6 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +use crate::colors; use crate::fmt_errors::JsError; use crate::inspector::DenoInspector; use crate::inspector::InspectorSession; @@ -275,7 +276,7 @@ impl MainWorker { ops::runtime::init(js_runtime, main_module); ops::fetch::init(js_runtime, program_state.flags.ca_file.as_deref()); ops::timers::init(js_runtime); - ops::worker_host::init(js_runtime); + ops::worker_host::init(js_runtime, None); ops::random::init(js_runtime, program_state.flags.seed); ops::reg_json_sync(js_runtime, "op_close", deno_core::op_close); ops::reg_json_sync(js_runtime, "op_resources", deno_core::op_resources); @@ -443,11 +444,11 @@ impl WebWorker { op_state.put::(permissions); } - ops::web_worker::init(js_runtime, sender, handle); + ops::web_worker::init(js_runtime, sender.clone(), handle); ops::runtime::init(js_runtime, main_module); ops::fetch::init(js_runtime, program_state.flags.ca_file.as_deref()); ops::timers::init(js_runtime); - ops::worker_host::init(js_runtime); + ops::worker_host::init(js_runtime, Some(sender)); ops::reg_json_sync(js_runtime, "op_close", deno_core::op_close); ops::reg_json_sync(js_runtime, "op_resources", deno_core::op_resources); ops::reg_json_sync( @@ -510,6 +511,12 @@ impl WebWorker { } if let Err(e) = r { + eprintln!( + "{}: Uncaught (in worker \"{}\") {}", + colors::red_bold("error"), + worker.name.to_string(), + e.to_string().trim_start_matches("Uncaught "), + ); let mut sender = worker.internal_channels.sender.clone(); sender .try_send(WorkerEvent::Error(e)) diff --git a/op_crates/web/03_global_interfaces.js b/op_crates/web/03_global_interfaces.js new file mode 100644 index 0000000000..f900e71609 --- /dev/null +++ b/op_crates/web/03_global_interfaces.js @@ -0,0 +1,66 @@ +((window) => { + const { EventTarget } = window; + + const illegalConstructorKey = Symbol("illegalConstuctorKey"); + + class Window extends EventTarget { + constructor(key) { + if (key !== illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } + } + + get [Symbol.toStringTag]() { + return "Window"; + } + } + + class WorkerGlobalScope extends EventTarget { + constructor(key) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } + } + + get [Symbol.toStringTag]() { + return "WorkerGlobalScope"; + } + } + + class DedicatedWorkerGlobalScope extends WorkerGlobalScope { + constructor(key) { + if (key != illegalConstructorKey) { + throw new TypeError("Illegal constructor."); + } + } + + get [Symbol.toStringTag]() { + return "DedicatedWorkerGlobalScope"; + } + } + + window.__bootstrap = (window.__bootstrap || {}); + window.__bootstrap.globalInterfaces = { + DedicatedWorkerGlobalScope, + Window, + WorkerGlobalScope, + dedicatedWorkerGlobalScopeConstructorDescriptor: { + configurable: true, + enumerable: false, + value: DedicatedWorkerGlobalScope, + writable: true, + }, + windowConstructorDescriptor: { + configurable: true, + enumerable: false, + value: Window, + writable: true, + }, + workerGlobalScopeConstructorDescriptor: { + configurable: true, + enumerable: false, + value: WorkerGlobalScope, + writable: true, + }, + }; +})(this); diff --git a/op_crates/web/lib.rs b/op_crates/web/lib.rs index eaf7e9f140..958d111772 100644 --- a/op_crates/web/lib.rs +++ b/op_crates/web/lib.rs @@ -52,6 +52,10 @@ pub fn init(isolate: &mut JsRuntime) { "deno:op_crates/web/02_abort_signal.js", include_str!("02_abort_signal.js"), ), + ( + "deno:op_crates/web/03_global_interfaces.js", + include_str!("03_global_interfaces.js"), + ), ( "deno:op_crates/web/08_text_encoding.js", include_str!("08_text_encoding.js"),