import { ArrayPrototypePush as push, FinalizationRegistry, getter, ObjectCreate, ObjectFreeze as freeze, ReflectApply as apply, ReflectConstruct as construct, Set, SetPrototypeAdd as retain, SetPrototypeDelete as release, SymbolIterator, TypeError, uncurryThis, WeakMap, WeakMapPrototypeGet as get, WeakMapPrototypeSet as set, } from "ext:core/primordials"; import { enqueueTask } from "ext:core/tasks"; import { promiseThen as then } from "ext:core/promises"; import { op_port_adopt, op_port_close, op_port_move, op_port_pair, op_port_peer, op_port_send, op_port_take, op_port_wait, op_port_wait_ref, } from "ext:core/ops"; import { array, dictionary, required, string, usv } from "./idl.js"; import { addListener, dataCloneError, dispatch, Event, EventTarget, hasListener, isDispatching, observeListeners, preventDefault, removeListener, } from "./events.js"; import { classify, decodeWire, registerTransferable, registerUncloneable, registerWire, transferCallbacks, } from "./serialization.js"; const WeakRef = globalThis.WeakRef, deref = uncurryThis(WeakRef.prototype.deref); interface Endpoint { id: number; epoch: number; port: WeakRef | null; live: boolean; started: boolean; closed: boolean; scheduled: boolean; referenced: boolean; onclose?: () => void; } interface PortState { endpoint: Endpoint | null; detached: boolean; onmessage: ((event: Event) => unknown) | null; onmessageerror: ((event: Event) => unknown) | null; messageListener: ((event: Event) => void) | null; errorListener: ((event: Event) => void) | null; } interface MessageEventState { data: unknown; origin: string; lastEventId: string; source: MessagePort | null; ports: readonly MessagePort[]; } interface ChannelState { port1: MessagePort; port2: MessagePort; } const activePorts = new Set(); const initEvent = uncurryThis(Event.prototype.initEvent); const registerFinalizer = uncurryThis(FinalizationRegistry.prototype.register); const unregisterFinalizer = uncurryThis( FinalizationRegistry.prototype.unregister, ); const portFinalizer = new FinalizationRegistry((id) => op_port_close(id) ); const ports = new WeakMap(), messageEvents = new WeakMap(), channels = new WeakMap(), secret = ObjectCreate(null); registerTransferable(ports, preparePort, validatePort, commitPort); registerWire({ name: "MessagePort", states: ports, encode: (current: unknown) => (current as PortState).endpoint?.id ?? null, decode: (id: unknown) => adoptPort(id as number | null), prepare: (_current: unknown, source: unknown) => { validatePort(null, source); return null; }, validate: validatePort, commit: (_current: unknown, source: unknown) => { const origin = state(ports, source); if (origin.endpoint) op_port_move(origin.endpoint.id); unregisterFinalizer(portFinalizer, source as MessagePort); origin.endpoint = null; origin.detached = true; retainPort(source as MessagePort); }, }); registerUncloneable(messageEvents); registerUncloneable(channels); function state(map: WeakMap, value: unknown): V { const result = get(map, value); if (result === undefined) throw new TypeError("Illegal invocation"); return result; } function port(value: unknown): value is MessagePort { return get(ports, value) !== undefined; } function freezePorts(values: readonly MessagePort[]): readonly MessagePort[] { const result: MessagePort[] = []; for (let i = 0; i < values.length; i++) push(result, values[i]); return freeze(result); } function portList( value: unknown, requirePorts: true, method?: unknown, methodRead?: boolean, ): MessagePort[]; function portList( value: unknown, requirePorts?: false, method?: unknown, methodRead?: boolean, ): object[]; function portList( value: unknown, requirePorts = false, method: unknown = undefined, methodRead = false, ) { if (value === undefined) return array(); if (value === null) { throw new TypeError("Expected an iterable of MessagePorts"); } if (!methodRead) { method = (value as Record)[SymbolIterator]; } if (typeof method !== "function") { throw new TypeError("Expected an iterable of MessagePorts"); } const iterator = apply( method as (...args: never[]) => unknown, value, [], ) as Record; if ( iterator === null || (typeof iterator !== "object" && typeof iterator !== "function") ) { throw new TypeError("Invalid transfer iterator"); } const next = iterator.next; if (typeof next !== "function") { throw new TypeError("Invalid transfer iterator next method"); } const result = array(); while (true) { const step = apply( next as (...args: never[]) => unknown, iterator, [], ) as Record; if ( step === null || (typeof step !== "object" && typeof step !== "function") ) { throw new TypeError("Invalid transfer iterator result"); } if (step.done) return result; const item = step.value; if ( item === null || (typeof item !== "object" && typeof item !== "function") ) { throw new TypeError("Sequence entries must be objects"); } if (requirePorts && !port(item)) { throw new TypeError("Expected a MessagePort"); } push(result, item); } } function transferList(options: unknown): object[] { if (options === undefined) return array(); if ( options !== null && (typeof options === "object" || typeof options === "function") ) { const method = (options as Record)[SymbolIterator]; if (typeof method === "function") { return portList(options, false, method, true); } } const value = dictionary(options).transfer; return portList(value); } function preparePort(_state: unknown, source: unknown): MessagePort { if (state(ports, source).detached) { throw dataCloneError("MessagePort has already been transferred"); } return new MessagePort(secret); } function validatePort(_state: unknown, source: unknown): void { if (state(ports, source).detached) { throw dataCloneError("MessagePort has already been transferred"); } } function commitPort( _state: unknown, source: unknown, destination: unknown, ): void { const origin = state(ports, source), target = state(ports, destination); target.endpoint = origin.endpoint; target.detached = false; if (origin.endpoint) { op_port_move(origin.endpoint.id); origin.endpoint.epoch = op_port_adopt(origin.endpoint.id); origin.endpoint.port = new WeakRef(destination as MessagePort); origin.endpoint.started = false; origin.endpoint.scheduled = false; registerFinalizer( portFinalizer, destination as MessagePort, origin.endpoint.id, destination as MessagePort, ); } unregisterFinalizer(portFinalizer, source as MessagePort); origin.endpoint = null; origin.detached = true; retainPort(source as MessagePort); retainPort(destination as MessagePort); } function retainPort(value: object): void { const endpoint = state(ports, value).endpoint; if ( endpoint && endpoint.live && endpoint.started && !endpoint.closed && (endpoint.referenced || hasListener(value, "message") || hasListener(value, "messageerror")) ) { retain(activePorts, value as MessagePort); } else release(activePorts, value as MessagePort); } function schedule(endpoint: Endpoint | null): void { if (!endpoint || endpoint.closed || !endpoint.started || endpoint.scheduled) { return; } endpoint.scheduled = true; const epoch = endpoint.epoch; const wait = endpoint.referenced ? op_port_wait_ref : op_port_wait; then(wait([endpoint.id, epoch]), (ready) => { if (endpoint.epoch !== epoch) return; endpoint.scheduled = false; if (!ready) { endpoint.live = false; const target = endpoint.port && deref(endpoint.port); if (target) retainPort(target); endpoint.onclose?.(); return; } if (endpoint.closed || !endpoint.started) return; enqueueTask(() => { if (endpoint.epoch !== epoch || endpoint.closed || !endpoint.started) { return; } const target = endpoint.port && deref(endpoint.port); if (!target) { endpoint.closed = true; op_port_close(endpoint.id); return; } let packet; try { packet = op_port_take(endpoint.id, epoch, decodeWire, dataCloneError); } catch { if (target) { dispatch( target, new MessageEvent("messageerror", ObjectCreate(null)), ); } return; } if (packet !== null && target) { dispatchMessage(target, packet.data, packet.ports); } }, () => { schedule(endpoint); return true; }); }); } function adoptPort(id: number | null): MessagePort { const result = new MessagePort(secret); if (id !== null) { registerFinalizer(portFinalizer, result, id, result); state(ports, result).endpoint = { id, epoch: op_port_adopt(id), port: new WeakRef(result), live: true, started: false, closed: false, scheduled: false, referenced: false, }; } return result; } function startPort(value: MessagePort, referenced = false): void { const endpoint = state(ports, value).endpoint; if (endpoint && !endpoint.closed) { endpoint.referenced = referenced; endpoint.started = true; retainPort(value); schedule(endpoint); } } function dispatchMessage( target: object, data: unknown, eventPorts: readonly MessagePort[] = [], ): void { const event = new MessageEvent("message", ObjectCreate(null)); set(messageEvents, event, { data, origin: "", lastEventId: "", source: null, ports: freezePorts(eventPorts), }); dispatch(target, event); } class MessageEvent extends Event { // @ts-expect-error Reflect.construct preserves new.target without a guest-replaceable super(). constructor(type: unknown, init: unknown = {}) { required(arguments.length, 1); type = string(type); const options = dictionary(init); const self = construct(Event, [type, options], new.target); const rawData = options.data, rawOrigin = options.origin, rawId = options.lastEventId, rawSource = options.source; const data = rawData === undefined ? null : rawData; const origin = rawOrigin === undefined ? "" : usv(rawOrigin); const lastEventId = rawId === undefined ? "" : string(rawId); const source = rawSource === undefined ? null : rawSource; if (source !== null && !port(source)) { throw new TypeError("Expected a MessagePort or null"); } const eventPorts = portList(options.ports, true); set(messageEvents, self, { data, origin, lastEventId, source, ports: freezePorts(eventPorts), }); return self as MessageEvent; } get data() { return state(messageEvents, this).data; } get origin() { return state(messageEvents, this).origin; } get lastEventId() { return state(messageEvents, this).lastEventId; } get source() { return state(messageEvents, this).source; } get ports() { return state(messageEvents, this).ports; } initMessageEvent( type: unknown, bubbles: unknown = false, cancelable: unknown = false, data: unknown = null, origin: unknown = "", lastEventId: unknown = "", source: unknown = null, eventPorts: unknown = [], ) { required(arguments.length, 1); type = string(type); bubbles = !!bubbles; cancelable = !!cancelable; origin = string(origin); lastEventId = string(lastEventId); if (source !== null && !port(source)) { throw new TypeError("Expected a MessagePort or null"); } eventPorts = portList(eventPorts, true); if (isDispatching(this)) return; initEvent(this, type, bubbles, cancelable); const current = state(messageEvents, this); current.data = data; current.origin = origin as string; current.lastEventId = lastEventId as string; current.source = source; current.ports = freezePorts(eventPorts as MessagePort[]); } } class MessagePort extends EventTarget { // @ts-expect-error Reflect.construct preserves new.target without a guest-replaceable super(). constructor(key: unknown = undefined) { if (key !== secret) throw new TypeError("Illegal constructor"); const self = construct(EventTarget, [], new.target) as MessagePort; set(ports, self, { endpoint: null, detached: false, onmessage: null, onmessageerror: null, messageListener: null, errorListener: null, }); observeListeners(self, retainPort); return self as MessagePort; } get onmessage() { return state(ports, this).onmessage; } set onmessage(value) { const current = state(ports, this); value = typeof value === "function" ? value : null; current.onmessage = value; if (!value) { if (current.messageListener) { removeListener(this, "message", current.messageListener); } current.messageListener = null; } else { if (!current.messageListener) { current.messageListener = (event: Event) => { if (apply(current.onmessage!, this, [event]) === false) { preventDefault(event); } }; addListener(this, "message", current.messageListener); } const endpoint = current.endpoint; if (endpoint && !endpoint.closed) { endpoint.started = true; retainPort(this); schedule(endpoint); } } } get onmessageerror() { return state(ports, this).onmessageerror; } set onmessageerror(value) { const current = state(ports, this); value = typeof value === "function" ? value : null; current.onmessageerror = value; if (!value) { if (current.errorListener) { removeListener(this, "messageerror", current.errorListener); } current.errorListener = null; } else if (!current.errorListener) { current.errorListener = (event) => { if (apply(current.onmessageerror!, this, [event]) === false) { preventDefault(event); } }; addListener(this, "messageerror", current.errorListener); } } postMessage(data: unknown, options: unknown = undefined): void { required(arguments.length, 1); const transfer = transferList(options); const current = state(ports, this), endpoint = current.endpoint; const peer = endpoint ? op_port_peer(endpoint.id) : null; let doomed = false; const messagePorts = array(); for (let i = 0; i < transfer.length; i++) { if (transfer[i] === this) { throw dataCloneError("A MessagePort cannot be transferred to itself"); } if (port(transfer[i])) { if (state(ports, transfer[i]).endpoint?.id === peer) doomed = true; push(messagePorts, transfer[i] as MessagePort); } } const callbacks = transferCallbacks(transfer, dataCloneError, true); try { callbacks.prepare(); op_port_send( doomed ? null : endpoint?.id ?? null, { data, ports: messagePorts }, transfer, classify, callbacks.encodeHost, dataCloneError, callbacks.validate, callbacks.commit, ); } finally { callbacks.clear(); } } start() { startPort(this); } close() { const current = state(ports, this), endpoint = current.endpoint; if (!endpoint) return; endpoint.closed = true; endpoint.port = null; op_port_close(endpoint.id); unregisterFinalizer(portFinalizer, this); current.endpoint = null; retainPort(this); } } class MessageChannel { constructor() { const ids = op_port_pair(); const port1 = adoptPort(ids[0]), port2 = adoptPort(ids[1]); set(channels, this, { port1, port2 }); } get port1() { return state(channels, this).port1; } get port2() { return state(channels, this).port2; } } const messageData = getter(MessageEvent.prototype, "data"); const messagePorts = getter(MessageEvent.prototype, "ports"); function setPortCloseHandler(value: MessagePort, callback: () => void): void { const endpoint = state(ports, value).endpoint; if (endpoint) endpoint.onclose = callback; } function referencePort(value: MessagePort): Promise { const endpoint = state(ports, value).endpoint; return op_port_wait_ref([endpoint!.id, endpoint!.epoch]); } export { adoptPort, dispatchMessage, MessageChannel, messageData, MessageEvent, MessagePort, messagePorts, port as isMessagePort, referencePort, setPortCloseHandler, startPort, transferList, };