import { ArrayPrototypePush as push, Error, ObjectCreate, ObjectDefineProperty as define, ReflectApply as apply, ReflectConstruct as construct, TypeError, WeakMap, WeakMapPrototypeGet as get, WeakMapPrototypeSet as set, } from "ext:core/primordials"; import { promiseThen as then } from "ext:core/promises"; import { enqueueTask } from "ext:core/tasks"; import { onRuntimeStart } from "ext:core/bootstrap"; import { clone } from "ext:core/structured_clone"; import { op_worker_base, op_worker_close, op_worker_create, op_worker_metadata, op_worker_terminate, op_worker_wait, } from "ext:core/ops"; import { array, dictionary, required, usv } from "ext:web/idl.js"; import { installInterface } from "ext:web/interface.js"; import { addListener, dataCloneError, dispatch, ErrorEvent, EventTarget, preventDefault, removeListener, reportErrorValue, } from "ext:web/events.js"; import { adoptPort, dispatchMessage, isMessagePort, messageData, MessageEvent, MessagePort, messagePorts, startPort, transferList, } from "ext:web/messaging.js"; import { classify, registerUncloneable, transferCallbacks, } from "ext:web/serialization.js"; import { avril } from "ext:base/main.js"; import { preparePledge } from "ext:base/pledge.js"; import { URL } from "ext:url/main.js"; type HandlerName = "onmessage" | "onmessageerror" | "onerror"; type HandlerState = Partial< & Record & Record< `${HandlerName}Listener`, ((event: import("ext:web/events.js").Event) => void) | null > >; interface WorkerState extends HandlerState { id: number; port: MessagePort; terminated: boolean; } type LocationState = Record< | "href" | "origin" | "protocol" | "host" | "hostname" | "port" | "pathname" | "search" | "hash", string >; const workers = new WeakMap(), locations = new WeakMap(); registerUncloneable(workers); registerUncloneable(locations); const post = MessagePort.prototype.postMessage; const closePort = MessagePort.prototype.close; function state(value: unknown): WorkerState { const result = get(workers, value); if (!result) throw new TypeError("Illegal invocation"); return result; } function handler( target: unknown, current: HandlerState, name: HandlerName, type: string, value: unknown, ): void { value = typeof value === "function" ? value : null; current[name] = value as Function | null; const key = name + "Listener" as `${HandlerName}Listener`; if (!value && current[key]) { removeListener(target, type, current[key]); current[key] = null; } else if (value && !current[key]) { current[key] = (event) => { if (current[name] && apply(current[name]!, target, [event]) === false) { preventDefault(event); } }; addListener(target, type, current[key]); } } class Worker extends EventTarget { // @ts-expect-error Reflect.construct preserves new.target without a guest-replaceable super() call. constructor(scriptURL: unknown, options: unknown = {}) { required(arguments.length, 1); const specifier = usv(scriptURL), init = dictionary(options); const type = init.type === undefined ? "classic" : usv(init.type); if (type !== "module") { throw new TypeError("avril supports module workers only"); } const name = init.name === undefined ? "" : usv(init.name); const extension = dictionary(init[avril]); const restrictions = extension.caps === undefined ? [] : preparePledge(extension.caps); const self = construct(EventTarget, [], new.target) as Worker; const created = op_worker_create([ specifier, op_worker_base(), name, restrictions, ]); const port = adoptPort(created[1]); const current = ObjectCreate(null) as WorkerState; current.id = created[0]; current.port = port; current.terminated = false; set(workers, self, current); addListener(port, "message", (event) => { if (!current.terminated) { dispatchMessage(self, messageData(event), messagePorts(event)); } }); addListener(port, "messageerror", () => { if (!current.terminated) { dispatch(self, new MessageEvent("messageerror", ObjectCreate(null))); } }); startPort(port); then(op_worker_wait(current.id), (message) => { enqueueTask(() => { if (!current.terminated && message !== null) { const init = ObjectCreate(null); init.message = message; init.cancelable = true; const event = new ErrorEvent("error", init); if (dispatch(self, event)) reportErrorValue(new Error(message)); } current.terminated = true; apply(closePort, port, []); }, () => true); }); return self; } postMessage(message: unknown, options: unknown = undefined) { required(arguments.length, 1); apply(post, state(this).port, [message, options]); } terminate() { const current = state(this); current.terminated = true; apply(closePort, current.port, []); op_worker_terminate(current.id); } get onmessage() { return state(this).onmessage ?? null; } set onmessage(value: unknown) { handler(this, state(this), "onmessage", "message", value); } get onmessageerror() { return state(this).onmessageerror ?? null; } set onmessageerror(value: unknown) { handler(this, state(this), "onmessageerror", "messageerror", value); } get onerror() { return state(this).onerror ?? null; } set onerror(value: unknown) { handler(this, state(this), "onerror", "error", value); } } installInterface("Worker", Worker); class WorkerLocation { private constructor() { throw new TypeError("Illegal constructor"); } get href() { return get(locations, this)!.href; } get origin() { return get(locations, this)!.origin; } get protocol() { return get(locations, this)!.protocol; } get host() { return get(locations, this)!.host; } get hostname() { return get(locations, this)!.hostname; } get port() { return get(locations, this)!.port; } get pathname() { return get(locations, this)!.pathname; } get search() { return get(locations, this)!.search; } get hash() { return get(locations, this)!.hash; } toString() { return get(locations, this)!.href; } } const global = globalThis, current: HandlerState = ObjectCreate(null); for (const type of ["message", "messageerror"] as const) { define(global, "on" + type, { get: () => current["on" + type as HandlerName] ?? null, set: (value) => handler(global, current, "on" + type as HandlerName, type, value), enumerable: true, configurable: true, }); } onRuntimeStart(() => { const metadata = op_worker_metadata(); if (metadata !== null) { const port = adoptPort(metadata[0]); addListener( port, "message", (event) => dispatchMessage(global, messageData(event), messagePorts(event)), ); addListener( port, "messageerror", () => dispatch(global, new MessageEvent("messageerror", ObjectCreate(null))), ); startPort(port, true); installInterface("WorkerLocation", WorkerLocation); const location = ObjectCreate(WorkerLocation.prototype); const url = new URL(metadata[1]), locationState: LocationState = ObjectCreate(null); for ( const key of [ "href", "origin", "protocol", "host", "hostname", "port", "pathname", "search", "hash", ] as const ) locationState[key] = url[key]; set(locations, location, locationState); define(global, "location", { get: () => location, enumerable: true, configurable: true, }); define(global, "name", { value: metadata[2], enumerable: true, configurable: true, }); define(global, "postMessage", { value: { postMessage(message: unknown, options: unknown = undefined) { required(arguments.length, 1); apply(post, port, [message, options]); }, }.postMessage, writable: true, enumerable: true, configurable: true, }); define(global, "close", { value: function close() { apply(closePort, port, []); op_worker_close(); }, writable: true, enumerable: true, configurable: true, }); } else { define(global, "postMessage", { value: { postMessage(message: unknown, options: unknown = undefined) { required(arguments.length, 1); const transfer = transferList(options), ports = array(); for (let i = 0; i < transfer.length; i++) { if (isMessagePort(transfer[i])) push(ports, transfer[i]); } const callbacks = transferCallbacks(transfer, dataCloneError); let packet: { data: unknown; ports: MessagePort[] }; try { packet = clone( { __proto__: null, data: message, ports }, transfer, classify, callbacks.cloneHost, dataCloneError, callbacks.prepare, callbacks.validate, callbacks.commit, ) as typeof packet; } finally { callbacks.clear(); } enqueueTask( () => dispatchMessage(global, packet.data, packet.ports), () => true, ); }, }.postMessage, writable: true, enumerable: true, configurable: true, }); } }); export type { Worker, WorkerLocation };