diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs index d2997d7473..2e2b0400ce 100644 --- a/cli/tests/integration/run_tests.rs +++ b/cli/tests/integration/run_tests.rs @@ -1155,6 +1155,11 @@ itest!(error_import_map_unable_to_load { exit_code: 1, }); +itest!(worker_event_handler_test { + args: "run --quiet --reload --allow-read worker_event_handler_test.js", + output: "worker_event_handler_test.js.out", +}); + #[test] fn no_validate_asm() { let output = util::deno_cmd() diff --git a/cli/tests/worker_event_handler_test.js b/cli/tests/worker_event_handler_test.js new file mode 100644 index 0000000000..e0ce3d9f83 --- /dev/null +++ b/cli/tests/worker_event_handler_test.js @@ -0,0 +1,5 @@ +const w = new Worker( + new URL("./workers/worker_event_handlers.js", import.meta.url).href, + { type: "module" }, +); +w.postMessage({}); diff --git a/cli/tests/worker_event_handler_test.js.out b/cli/tests/worker_event_handler_test.js.out new file mode 100644 index 0000000000..5556633b14 --- /dev/null +++ b/cli/tests/worker_event_handler_test.js.out @@ -0,0 +1,11 @@ +Target from self.onmessage: [object DedicatedWorkerGlobalScope] +Target from message event listener: [object DedicatedWorkerGlobalScope] +Arguments from self.onerror: [ + "Some error message", + "", + 0, + 0, + Error: Some error message + at [WILDCARD] +] +Is event canceled?: true diff --git a/cli/tests/workers/worker_event_handlers.js b/cli/tests/workers/worker_event_handlers.js new file mode 100644 index 0000000000..c8976f79e1 --- /dev/null +++ b/cli/tests/workers/worker_event_handlers.js @@ -0,0 +1,23 @@ +self.onmessage = (evt) => { + console.log("Target from self.onmessage:", String(evt.target)); +}; + +self.addEventListener("message", (evt) => { + console.log("Target from message event listener:", String(evt.target)); + + // Throw an error here so the global's error event will fire. + throw new Error("Some error message"); +}); + +self.onerror = (...args) => { + console.log("Arguments from self.onerror:", args); + return true; +}; + +self.addEventListener("error", (evt) => { + // Returning true from self.onerror means that subsequent event listeners + // should see the event as canceled. + console.log("Is event canceled?:", evt.defaultPrevented); + + self.close(); +}); diff --git a/runtime/js/01_web_util.js b/runtime/js/01_web_util.js index 4a9825fa1e..9b51021f9f 100644 --- a/runtime/js/01_web_util.js +++ b/runtime/js/01_web_util.js @@ -27,17 +27,41 @@ } const handlerSymbol = Symbol("eventHandlers"); - function makeWrappedHandler(handler) { + function makeWrappedHandler(handler, isSpecialErrorEventHandler) { function wrappedHandler(...args) { if (typeof wrappedHandler.handler !== "function") { return; } + if (isSpecialErrorEventHandler) { + const evt = args[0]; + if (evt instanceof ErrorEvent && evt.type === "error") { + const ret = FunctionPrototypeCall( + wrappedHandler.handler, + this, + evt.message, + evt.filename, + evt.lineno, + evt.colno, + evt.error, + ); + if (ret === true) { + evt.preventDefault(); + } + return; + } + } + return FunctionPrototypeCall(wrappedHandler.handler, this, ...args); } wrappedHandler.handler = handler; return wrappedHandler; } - function defineEventHandler(emitter, name, defaultValue = undefined) { + function defineEventHandler( + emitter, + name, + defaultValue = undefined, + isSpecialErrorEventHandler = false, + ) { // HTML specification section 8.1.5.1 ObjectDefineProperty(emitter, `on${name}`, { get() { @@ -56,7 +80,10 @@ if (handlerWrapper) { handlerWrapper.handler = value; } else { - handlerWrapper = makeWrappedHandler(value); + handlerWrapper = makeWrappedHandler( + value, + isSpecialErrorEventHandler, + ); this.addEventListener(name, handlerWrapper); } MapPrototypeSet(this[handlerSymbol], name, handlerWrapper); diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index b473acdef8..71480e5a06 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -88,11 +88,6 @@ delete Object.prototype.__proto__; core.opSync("op_worker_close"); } - // TODO(bartlomieju): remove these functions - // Stuff for workers - const onmessage = () => {}; - const onerror = () => {}; - function postMessage(message, transferOrOptions = {}) { const prefix = "Failed to execute 'postMessage' on 'DedicatedWorkerGlobalScope'"; @@ -144,39 +139,19 @@ delete Object.prototype.__proto__; }); try { - if (globalThis.onmessage) { - await globalThis.onmessage(msgEvent); - } globalDispatchEvent(msgEvent); } catch (e) { - let handled = false; - const errorEvent = new ErrorEvent("error", { cancelable: true, message: e.message, lineno: e.lineNumber ? e.lineNumber + 1 : undefined, colno: e.columnNumber ? e.columnNumber + 1 : undefined, filename: e.fileName, - error: null, + error: e, }); - if (globalThis["onerror"]) { - const ret = globalThis.onerror( - e.message, - e.fileName, - e.lineNumber, - e.columnNumber, - e, - ); - handled = ret === true; - } - globalDispatchEvent(errorEvent); - if (errorEvent.defaultPrevented) { - handled = true; - } - - if (!handled) { + if (!errorEvent.defaultPrevented) { core.opSync( "op_worker_unhandled_error", e.message, @@ -465,8 +440,8 @@ delete Object.prototype.__proto__; get: () => workerNavigator, }, self: util.readOnly(globalThis), - onmessage: util.writable(onmessage), - onerror: util.writable(onerror), + onmessage: util.writable(null), + onerror: util.writable(null), // TODO(bartlomieju): should be readonly? close: util.nonEnumerable(workerClose), postMessage: util.writable(postMessage), @@ -585,6 +560,9 @@ delete Object.prototype.__proto__; eventTarget.setEventTargetData(globalThis); + defineEventHandler(self, "message", null); + defineEventHandler(self, "error", null, true); + runtimeStart( runtimeOptions, internalName ?? name,