From 2585b72c9bdd5ca36b6b43cf5b5609419081c7a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 13 Apr 2020 22:18:31 +0200 Subject: [PATCH] feat: implement EventTarget for worker scope (#4737) --- cli/js/lib.deno.worker.d.ts | 3 +- cli/js/runtime_worker.ts | 42 ++++++++++++++++++------- cli/tests/subdir/event_worker_scope.js | 43 ++++++++++++++++++++++++++ cli/tests/workers_test.out | 5 +-- cli/tests/workers_test.ts | 27 ++++++++++++++++ 5 files changed, 106 insertions(+), 14 deletions(-) create mode 100644 cli/tests/subdir/event_worker_scope.js diff --git a/cli/js/lib.deno.worker.d.ts b/cli/js/lib.deno.worker.d.ts index e1db7e691e..a1e83af485 100644 --- a/cli/js/lib.deno.worker.d.ts +++ b/cli/js/lib.deno.worker.d.ts @@ -8,7 +8,8 @@ declare interface DedicatedWorkerGlobalScope { self: DedicatedWorkerGlobalScope & typeof globalThis; - onmessage: (e: { data: any }) => void; + onmessage: (e: MessageEvent) => void; + onmessageerror: (e: MessageEvent) => void; location: Location; onerror: undefined | typeof onerror; name: typeof __workerMain.name; diff --git a/cli/js/runtime_worker.ts b/cli/js/runtime_worker.ts index c7742cf31d..8881b438bc 100644 --- a/cli/js/runtime_worker.ts +++ b/cli/js/runtime_worker.ts @@ -15,10 +15,12 @@ import { windowOrWorkerGlobalScopeMethods, windowOrWorkerGlobalScopeProperties, eventTargetProperties, + setEventTargetData, } from "./globals.ts"; import * as webWorkerOps from "./ops/web_worker.ts"; import { LocationImpl } from "./web/location.ts"; import { log, assert, immutableDefine } from "./util.ts"; +import { MessageEvent, ErrorEvent } from "./web/workers.ts"; import { TextEncoder } from "./web/text_encoding.ts"; import * as runtime from "./runtime.ts"; @@ -48,33 +50,50 @@ export function close(): void { } export async function workerMessageRecvCallback(data: string): Promise { - let result: void | Promise; - const event = { data }; + const msgEvent = new MessageEvent("message", { + cancelable: false, + data, + }); try { - // if (globalThis["onmessage"]) { - result = globalThis.onmessage!(event); + const result = globalThis.onmessage!(msgEvent); if (result && "then" in result) { await result; } } - - // TODO: run the rest of liteners + globalThis.dispatchEvent(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, + }); + if (globalThis["onerror"]) { - const result = globalThis.onerror( + const ret = globalThis.onerror( e.message, e.fileName, e.lineNumber, e.columnNumber, e ); - if (result === true) { - return; - } + handled = ret === true; + } + + globalThis.dispatchEvent(errorEvent); + if (errorEvent.defaultPrevented) { + handled = true; + } + + if (!handled) { + throw e; } - throw e; } } @@ -99,6 +118,7 @@ export function bootstrapWorkerRuntime(name: string): void { Object.defineProperties(globalThis, workerRuntimeGlobalProperties); Object.defineProperties(globalThis, eventTargetProperties); Object.defineProperties(globalThis, { name: readOnly(name) }); + setEventTargetData(globalThis); const s = runtime.start(name); const location = new LocationImpl(s.location); diff --git a/cli/tests/subdir/event_worker_scope.js b/cli/tests/subdir/event_worker_scope.js new file mode 100644 index 0000000000..0381801a81 --- /dev/null +++ b/cli/tests/subdir/event_worker_scope.js @@ -0,0 +1,43 @@ +let messageHandlersCalled = 0; +let errorHandlersCalled = 0; + +onmessage = function (e) { + if (e.data === "boom") { + throw new Error("boom error!"); + } + messageHandlersCalled++; +}; + +self.addEventListener("message", (_e) => { + messageHandlersCalled++; +}); + +self.addEventListener("message", (_e) => { + messageHandlersCalled++; +}); + +self.addEventListener("message", (_e) => { + messageHandlersCalled++; + + postMessage({ + messageHandlersCalled, + errorHandlersCalled, + }); +}); + +onerror = function (_e) { + errorHandlersCalled++; +}; + +self.addEventListener("error", (_e) => { + errorHandlersCalled++; +}); + +self.addEventListener("error", (_e) => { + errorHandlersCalled++; +}); + +self.addEventListener("error", (e) => { + errorHandlersCalled++; + e.preventDefault(); +}); diff --git a/cli/tests/workers_test.out b/cli/tests/workers_test.out index 541adc20f7..d3c25d3ddc 100644 --- a/cli/tests/workers_test.out +++ b/cli/tests/workers_test.out @@ -1,4 +1,4 @@ -running 7 tests +running 8 tests test worker terminate ... ok [WILDCARD] test worker nested ... ok [WILDCARD] test worker throws when executing ... ok [WILDCARD] @@ -6,5 +6,6 @@ test worker fetch API ... ok [WILDCARD] test worker terminate busy loop ... ok [WILDCARD] test worker race condition ... ok [WILDCARD] test worker is event listener ... ok [WILDCARD] +test worker scope is event listener ... ok [WILDCARD] -test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD] +test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD] diff --git a/cli/tests/workers_test.ts b/cli/tests/workers_test.ts index bb00d67fdd..d181866587 100644 --- a/cli/tests/workers_test.ts +++ b/cli/tests/workers_test.ts @@ -231,3 +231,30 @@ Deno.test({ worker.terminate(); }, }); + +Deno.test({ + name: "worker scope is event listener", + fn: async function (): Promise { + const promise1 = createResolvable(); + + const worker = new Worker("../tests/subdir/event_worker_scope.js", { + type: "module", + }); + + worker.onmessage = (e: MessageEvent): void => { + const { messageHandlersCalled, errorHandlersCalled } = e.data; + assertEquals(messageHandlersCalled, 4); + assertEquals(errorHandlersCalled, 4); + promise1.resolve(); + }; + + worker.onerror = (_e): void => { + throw new Error("unreachable"); + }; + + worker.postMessage("boom"); + worker.postMessage("ping"); + await promise1; + worker.terminate(); + }, +});