nstdlib-nightly
Version:
Node.js standard library converted to runtime-agnostic ES modules.
445 lines (403 loc) • 13.5 kB
JavaScript
// 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 };