UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

445 lines (403 loc) 13.5 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/worker/io.js import { kEnumerableProperty, setOwnProperty } from "nstdlib/lib/internal/util"; import { handle_onclose as handleOnCloseSymbol, oninit as onInitSymbol, no_message_symbol as noMessageSymbol, } from "nstdlib/stub/binding/symbols"; import { MessagePort, MessageChannel, broadcastChannel, drainMessagePort, moveMessagePortToContext, receiveMessageOnPort as receiveMessageOnPort_, stopMessagePort, DOMException, } from "nstdlib/stub/binding/messaging"; import { getEnvMessagePort } from "nstdlib/stub/binding/worker"; import { Readable, Writable } from "nstdlib/lib/stream"; import { Event, EventTarget, NodeEventTarget, defineEventHandler, initNodeEventTarget, kCreateEvent, kNewListener, kRemoveListener, } from "nstdlib/lib/internal/event_target"; import { inspect } from "nstdlib/lib/internal/util/inspect"; import { codes as __codes__ } from "nstdlib/lib/internal/errors"; import * as __hoisted_internal_deps_undici_undici__ from "nstdlib/stub/internal/deps/undici/undici"; const { ERR_INVALID_THIS, ERR_MISSING_ARGS } = __codes__; const kHandle = Symbol("kHandle"); const kIncrementsPortRef = Symbol("kIncrementsPortRef"); const kName = Symbol("kName"); const kOnMessage = Symbol("kOnMessage"); const kOnMessageError = Symbol("kOnMessageError"); const kPort = Symbol("kPort"); const kWaitingStreams = Symbol("kWaitingStreams"); const kWritableCallbacks = Symbol("kWritableCallbacks"); const kStartedReading = Symbol("kStartedReading"); const kStdioWantsMoreDataCallback = Symbol("kStdioWantsMoreDataCallback"); const kCurrentlyReceivingPorts = Symbol.for( "nodejs.internal.kCurrentlyReceivingPorts", ); const kType = Symbol("kType"); const messageTypes = { UP_AND_RUNNING: "upAndRunning", COULD_NOT_SERIALIZE_ERROR: "couldNotSerializeError", ERROR_MESSAGE: "errorMessage", STDIO_PAYLOAD: "stdioPayload", STDIO_WANTS_MORE_DATA: "stdioWantsMoreData", LOAD_SCRIPT: "loadScript", }; // createFastMessageEvent skips webidl argument validation when the arguments // passed are known to be valid. let fastCreateMessageEvent; function lazyMessageEvent(type, init) { fastCreateMessageEvent ??= __hoisted_internal_deps_undici_undici__.createFastMessageEvent; return fastCreateMessageEvent(type, init); } // We have to mess with the MessagePort prototype a bit, so that a) we can make // it inherit from NodeEventTarget, even though it is a C++ class, and b) we do // not provide methods that are not present in the Browser and not documented // on our side (e.g. stopMessagePort). const messagePortPrototypePropertyDescriptors = Object.getOwnPropertyDescriptors(MessagePort.prototype); const propertiesValues = Object.values(messagePortPrototypePropertyDescriptors); for (let i = 0; i < propertiesValues.length; i++) { // We want to use null-prototype objects to not rely on globally mutable // %Object.prototype%. Object.setPrototypeOf(propertiesValues[i], null); } // Save a copy of the original set of methods as a shallow clone. const MessagePortPrototype = Object.create( Object.getPrototypeOf(MessagePort.prototype), messagePortPrototypePropertyDescriptors, ); // Set up the new inheritance chain. Object.setPrototypeOf(MessagePort, NodeEventTarget); Object.setPrototypeOf(MessagePort.prototype, NodeEventTarget.prototype); // Copy methods that are inherited from HandleWrap, because // changing the prototype of MessagePort.prototype implicitly removed them. MessagePort.prototype.ref = MessagePortPrototype.ref; MessagePort.prototype.unref = MessagePortPrototype.unref; MessagePort.prototype.hasRef = function () { return !!Function.prototype.call.call(MessagePortPrototype.hasRef, this); }; const originalCreateEvent = EventTarget.prototype[kCreateEvent]; Object.defineProperty(MessagePort.prototype, kCreateEvent, { __proto__: null, value: function (data, type) { if (type !== "message" && type !== "messageerror") { return ReflectApply(originalCreateEvent, this, arguments); } const ports = this[kCurrentlyReceivingPorts]; this[kCurrentlyReceivingPorts] = undefined; return lazyMessageEvent(type, { data, ports }); }, configurable: false, writable: false, enumerable: false, }); // This is called from inside the `MessagePort` constructor. function oninit() { initNodeEventTarget(this); setupPortReferencing(this, this, "message"); this[kCurrentlyReceivingPorts] = undefined; } defineEventHandler(MessagePort.prototype, "message"); defineEventHandler(MessagePort.prototype, "messageerror"); Object.defineProperty(MessagePort.prototype, onInitSymbol, { __proto__: null, enumerable: true, writable: false, value: oninit, }); class MessagePortCloseEvent extends Event { constructor() { super("close"); } } // This is called after the underlying `uv_async_t` has been closed. function onclose() { this.dispatchEvent(new MessagePortCloseEvent()); } Object.defineProperty(MessagePort.prototype, handleOnCloseSymbol, { __proto__: null, enumerable: false, writable: false, value: onclose, }); MessagePort.prototype.close = function (cb) { if (typeof cb === "function") this.once("close", cb); Function.prototype.call.call(MessagePortPrototype.close, this); }; Object.defineProperty(MessagePort.prototype, inspect.custom, { __proto__: null, enumerable: false, writable: false, value: function inspect() { // eslint-disable-line func-name-matching let ref; try { // This may throw when `this` does not refer to a native object, // e.g. when accessing the prototype directly. ref = Function.prototype.call.call(MessagePortPrototype.hasRef, this); } catch { return this; } return Object.assign( { __proto__: MessagePort.prototype }, ref === undefined ? { active: false, } : { active: true, refed: ref, }, this, ); }, }); function setupPortReferencing(port, eventEmitter, eventName) { // Keep track of whether there are any workerMessage listeners: // If there are some, ref() the channel so it keeps the event loop alive. // If there are none or all are removed, unref() the channel so the worker // can shutdown gracefully. port.unref(); eventEmitter.on("newListener", function (name) { if (name === eventName) newListener(eventEmitter.listenerCount(name)); }); eventEmitter.on("removeListener", function (name) { if (name === eventName) removeListener(eventEmitter.listenerCount(name)); }); const origNewListener = eventEmitter[kNewListener]; setOwnProperty(eventEmitter, kNewListener, function (size, type, ...args) { if (type === eventName) newListener(size - 1); return ReflectApply(origNewListener, this, arguments); }); const origRemoveListener = eventEmitter[kRemoveListener]; setOwnProperty(eventEmitter, kRemoveListener, function (size, type, ...args) { if (type === eventName) removeListener(size); return ReflectApply(origRemoveListener, this, arguments); }); function newListener(size) { if (size === 0) { port.ref(); Function.prototype.call.call(MessagePortPrototype.start, port); } } function removeListener(size) { if (size === 0) { stopMessagePort(port); port.unref(); } } } class ReadableWorkerStdio extends Readable { constructor(port, name) { super(); this[kPort] = port; this[kName] = name; this[kIncrementsPortRef] = true; this[kStartedReading] = false; this.on("end", () => { if (this[kStartedReading] && this[kIncrementsPortRef]) { if (--this[kPort][kWaitingStreams] === 0) this[kPort].unref(); } }); } _read() { if (!this[kStartedReading] && this[kIncrementsPortRef]) { this[kStartedReading] = true; if (this[kPort][kWaitingStreams]++ === 0) this[kPort].ref(); } this[kPort].postMessage({ type: messageTypes.STDIO_WANTS_MORE_DATA, stream: this[kName], }); } } class WritableWorkerStdio extends Writable { constructor(port, name) { super({ decodeStrings: false }); this[kPort] = port; this[kName] = name; this[kWritableCallbacks] = []; } _writev(chunks, cb) { this[kPort].postMessage({ type: messageTypes.STDIO_PAYLOAD, stream: this[kName], chunks: Array.prototype.map.call(chunks, ({ chunk, encoding }) => ({ chunk, encoding, })), }); Array.prototype.push.call(this[kWritableCallbacks], cb); if (this[kPort][kWaitingStreams]++ === 0) this[kPort].ref(); } _final(cb) { this[kPort].postMessage({ type: messageTypes.STDIO_PAYLOAD, stream: this[kName], chunks: [{ chunk: null, encoding: "" }], }); cb(); } [kStdioWantsMoreDataCallback]() { const cbs = this[kWritableCallbacks]; this[kWritableCallbacks] = []; Array.prototype.forEach.call(cbs, (cb) => cb()); if ((this[kPort][kWaitingStreams] -= cbs.length) === 0) this[kPort].unref(); } } function createWorkerStdio() { const port = getEnvMessagePort(); port[kWaitingStreams] = 0; return { stdin: new ReadableWorkerStdio(port, "stdin"), stdout: new WritableWorkerStdio(port, "stdout"), stderr: new WritableWorkerStdio(port, "stderr"), }; } function receiveMessageOnPort(port) { const message = receiveMessageOnPort_(port?.[kHandle] ?? port); if (message === noMessageSymbol) return undefined; return { message }; } function onMessageEvent(type, data) { this.dispatchEvent(lazyMessageEvent(type, { data })); } function isBroadcastChannel(value) { return value?.[kType] === "BroadcastChannel"; } class BroadcastChannel extends EventTarget { /** * @param {string} name */ constructor(name) { if (arguments.length === 0) throw new ERR_MISSING_ARGS("name"); super(); this[kType] = "BroadcastChannel"; this[kName] = `${name}`; this[kHandle] = broadcastChannel(this[kName]); this[kOnMessage] = Function.prototype.bind.call( onMessageEvent, this, "message", ); this[kOnMessageError] = Function.prototype.bind.call( onMessageEvent, this, "messageerror", ); this[kHandle].on("message", this[kOnMessage]); this[kHandle].on("messageerror", this[kOnMessageError]); } [inspect.custom](depth, options) { if (!isBroadcastChannel(this)) throw new ERR_INVALID_THIS("BroadcastChannel"); if (depth < 0) return "BroadcastChannel"; const opts = { ...options, depth: options.depth == null ? null : options.depth - 1, }; return `BroadcastChannel ${inspect( { name: this[kName], active: this[kHandle] !== undefined, }, opts, )}`; } /** * @type {string} */ get name() { if (!isBroadcastChannel(this)) throw new ERR_INVALID_THIS("BroadcastChannel"); return this[kName]; } /** * @returns {void} */ close() { if (!isBroadcastChannel(this)) throw new ERR_INVALID_THIS("BroadcastChannel"); if (this[kHandle] === undefined) return; this[kHandle].off("message", this[kOnMessage]); this[kHandle].off("messageerror", this[kOnMessageError]); this[kOnMessage] = undefined; this[kOnMessageError] = undefined; this[kHandle].close(); this[kHandle] = undefined; } /** * * @param {any} message * @returns {void} */ postMessage(message) { if (!isBroadcastChannel(this)) throw new ERR_INVALID_THIS("BroadcastChannel"); if (arguments.length === 0) throw new ERR_MISSING_ARGS("message"); if (this[kHandle] === undefined) throw new DOMException("BroadcastChannel is closed."); if (this[kHandle].postMessage(message) === undefined) throw new DOMException("Message could not be posted."); } // The ref() method is Node.js specific and not part of the standard // BroadcastChannel API definition. Typically we shouldn't extend Web // Platform APIs with Node.js specific methods but ref and unref // are a bit special. /** * @returns {BroadcastChannel} */ ref() { if (!isBroadcastChannel(this)) throw new ERR_INVALID_THIS("BroadcastChannel"); if (this[kHandle]) this[kHandle].ref(); return this; } // The unref() method is Node.js specific and not part of the standard // BroadcastChannel API definition. Typically we shouldn't extend Web // Platform APIs with Node.js specific methods but ref and unref // are a bit special. /** * @returns {BroadcastChannel} */ unref() { if (!isBroadcastChannel(this)) throw new ERR_INVALID_THIS("BroadcastChannel"); if (this[kHandle]) this[kHandle].unref(); return this; } } Object.defineProperties(BroadcastChannel.prototype, { name: kEnumerableProperty, close: kEnumerableProperty, postMessage: kEnumerableProperty, }); defineEventHandler(BroadcastChannel.prototype, "message"); defineEventHandler(BroadcastChannel.prototype, "messageerror"); export { drainMessagePort }; export { messageTypes }; export { kPort }; export { kIncrementsPortRef }; export { kWaitingStreams }; export { kStdioWantsMoreDataCallback }; export { moveMessagePortToContext }; export { MessagePort }; export { MessageChannel }; export { receiveMessageOnPort }; export { setupPortReferencing }; export { ReadableWorkerStdio }; export { WritableWorkerStdio }; export { createWorkerStdio }; export { BroadcastChannel };