import { Array, ArrayPrototypePush as push, ArrayPrototypeSplice as splice, Map, MapPrototypeDelete as drop, MapPrototypeGet as get, MapPrototypeSet as set, TypeError, uncurryThis, } from "ext:core/primordials"; import { markPromiseHandled, safePromise } from "ext:core/promises"; import { op_signal_bind, op_signal_poll, op_signal_unbind } from "ext:core/ops"; import { array, required, usv } from "ext:web/idl.js"; import { result } from "ext:file/errors.js"; import { reportErrorValue } from "ext:web/events.js"; const indexOf = uncurryThis(Array.prototype.indexOf); const slice = uncurryThis(Array.prototype.slice); type SignalListener = () => void; const bindings = new Map(); let pumping = false; // Signals with at least one listener. A bind racing the pump's final // poll restarts the loop below instead of stranding its delivery. let boundSignals = 0; /** Delivers each bound signal to its listeners in binding order. */ async function pump(): Promise { try { while (true) { const name = result(await safePromise(op_signal_poll())); // The isolate binds nothing anymore: end, releasing the event loop. if (name === null) return; const listeners = get(bindings, name); if (listeners === undefined) continue; const snapshot = slice(listeners, 0); for (let i = 0; i < snapshot.length; i++) { // A listener removed mid-dispatch stops firing, like EventTarget. // Copied to a local first: calling it off the array would set // `this` to the array. const listener = snapshot[i]; if (indexOf(listeners, listener) === -1) continue; try { listener(); } catch (error) { reportErrorValue(error); } } } } finally { pumping = false; if (boundSignals > 0) ensurePump(); } } function ensurePump(): void { if (pumping) return; pumping = true; markPromiseHandled(pump()); } const addSignalListener = { addSignalListener(signal: string, listener: SignalListener): void { required(arguments.length, 2); const name = usv(signal); if (typeof listener !== "function") { throw new TypeError("Signal listener must be a function"); } let listeners = get(bindings, name); if (listeners !== undefined && indexOf(listeners, listener) !== -1) { return; } if (listeners === undefined) { // Binding once per signal keeps the Rust binding count (one per // isolate per signal) balanced with unbind-on-empty below. result(op_signal_bind(name)); listeners = array(); set(bindings, name, listeners); boundSignals++; } push(listeners, listener); ensurePump(); }, }.addSignalListener; const removeSignalListener = { removeSignalListener(signal: string, listener: SignalListener): void { required(arguments.length, 2); const name = usv(signal); const listeners = get(bindings, name); if (listeners === undefined) return; const at = indexOf(listeners, listener); if (at === -1) return; splice(listeners, at, 1); if (listeners.length === 0) { drop(bindings, name); boundSignals--; op_signal_unbind(name); } }, }.removeSignalListener; export { addSignalListener, removeSignalListener };