'use strict';
// The Web IDL and specification prose quoted below is reproduced verbatim
// from the specification, including its original (lowercase) casing.
// Refs: https://html.spec.whatwg.org/multipage/workers.html
/* eslint-disable capitalized-comments */
const {
ArrayPrototypePush,
FunctionPrototypeBind,
ObjectDefineProperties,
ObjectDefineProperty,
ObjectGetPrototypeOf,
ObjectSetPrototypeOf,
ReflectDeleteProperty,
RegExpPrototypeExec,
SafeSet,
StringPrototypeSlice,
Symbol,
SymbolFor,
SymbolIterator,
SymbolToStringTag,
globalThis,
} = primordials;
const { extname } = require('path');
const {
ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_STATE,
ERR_NO_CRYPTO,
} = require('internal/errors').codes;
const {
EventTarget,
defineEventHandler,
initEventTarget,
} = require('internal/event_target');
const { getOptionValue } = require('internal/options');
const {
assignFunctionName,
defineOperation,
exposeInterface,
getCWDURL,
getLazy,
kEmptyObject,
kEnumerableProperty,
lazyDOMException,
} = require('internal/util');
const {
validateThisInternalField,
} = require('internal/validators');
const {
converters,
createDictionaryConverter,
createEnumConverter,
requiredArguments,
} = require('internal/webidl');
const {
Worker: NodeWorker,
kPublicPort,
kWebWorkerData,
} = require('internal/worker');
const {
MessageChannel,
lazyMessageEvent,
} = require('internal/worker/io');
const {
vm_dynamic_import_default_internal,
} = internalBinding('symbols');
const {
hasOpenSSL,
} = internalBinding('config');
const {
Navigator,
kInitialize: kInitializeNavigator,
} = require('internal/navigator');
const {
URL,
URLParse,
fileURLToPath,
} = require('internal/url');
const {
kType: kBlobType,
getBlobDataSync,
resolveObjectURL,
} = require('internal/blob');
const kCurrentlyReceivingPorts =
SymbolFor('nodejs.internal.kCurrentlyReceivingPorts');
const kRef = SymbolFor('nodejs.ref');
const kUnref = SymbolFor('nodejs.unref');
const kCreate = Symbol('kCreate');
const kInsidePort = Symbol('kInsidePort');
const kLocation = Symbol('kLocation');
const kName = Symbol('kName');
const kNavigator = Symbol('kNavigator');
const kNavigatorBrand = Symbol('kNavigatorBrand');
const kOutsidePort = Symbol('kOutsidePort');
const kRemoveMessageListeners = Symbol('kRemoveMessageListeners');
const kType = Symbol('kType');
const kURL = Symbol('kURL');
const kWorker = Symbol('kWorker');
let scopeBaseURL = null;
const lazyErrorEvent =
getLazy(() => require('internal/deps/undici/undici').ErrorEvent);
const lazyStripTypeScriptModuleTypes = getLazy(
() => require('internal/modules/typescript').stripTypeScriptModuleTypes);
function createErrorEvent(init) {
const ErrorEvent = lazyErrorEvent();
return new ErrorEvent('error', init);
}
// "Fire an event named message at messageEventTarget, using MessageEvent,
// with the data attribute initialized to messageClone and the ports
// attribute initialized to newPorts."
//
// "If this throws an exception, catch it, fire an event named messageerror
// at messageEventTarget, using MessageEvent, and then return."
function forwardMessageEvents(port, target) {
const onMessage = (data) =>
target.dispatchEvent(lazyMessageEvent('message', {
data,
ports: port[kCurrentlyReceivingPorts],
}));
const onMessageError = (data) =>
target.dispatchEvent(lazyMessageEvent('messageerror', {
data,
}));
port.on('message', onMessage);
port.on('messageerror', onMessageError);
return () => {
port.removeListener('message', onMessage);
port.removeListener('messageerror', onMessageError);
};
}
// Web IDL [Replaceable]: the setter replaces the accessor with an own
// data property on the receiver.
// Refs: https://webidl.spec.whatwg.org/#Replaceable
function replaceAttribute(receiver, key, value) {
ObjectDefineProperty(receiver, key, {
__proto__: null,
configurable: true,
enumerable: true,
writable: true,
value,
});
}
// https://mimesniff.spec.whatwg.org/#javascript-mime-type
const kJavaScriptMIMETypes = new SafeSet([
'application/ecmascript',
'application/javascript',
'application/x-ecmascript',
'application/x-javascript',
'text/ecmascript',
'text/javascript',
'text/javascript1.0',
'text/javascript1.1',
'text/javascript1.2',
'text/javascript1.3',
'text/javascript1.4',
'text/javascript1.5',
'text/jscript',
'text/livescript',
'text/x-ecmascript',
'text/x-javascript',
]);
function isJavaScriptMIMEType(essence) {
return kJavaScriptMIMETypes.has(essence);
}
// Extracts the essence ("type/subtype") of a raw MIME type string, or
// returns undefined if the string is not a parsable MIME type.
function mimeTypeEssence(type) {
const { MIMEType } = require('internal/mime');
try {
return new MIMEType(type).essence;
} catch {
return undefined;
}
}
function utf8Decode(bytes) {
return require('internal/encoding').getUtf8Decoder().decode(bytes);
}
// Reads and decodes the source text of a JavaScript data: URL, or returns
// undefined if the URL fails to parse as a data: URL or its MIME type is
// not a JavaScript MIME type.
function readDataURLScript(url) {
const { dataURLProcessor } = require('internal/data_url');
const data = dataURLProcessor(url);
if (data === 'failure' || !isJavaScriptMIMEType(data.mimeType.essence)) {
return undefined;
}
return utf8Decode(data.body);
}
// Reads the data of a blob whose contents are to be run as a script, or
// returns undefined if the blob's MIME type is not a JavaScript MIME type
// or its data is not synchronously available.
function getScriptBlobData(blob, allowEmptyType = false) {
if (blob === undefined ||
(!(allowEmptyType && blob[kBlobType] === '') &&
!isJavaScriptMIMEType(mimeTypeEssence(blob[kBlobType])))) {
return undefined;
}
return getBlobDataSync(blob);
}
/**
* Runs the source text of a classic script in the current global scope,
* emulating "run a classic script". Dynamic import() from the script is
* resolved by the ESM loader relative to the script's URL.
* @param {string} source The source text of the script.
* @param {string} url The URL of the script, used as its filename and as
* the base URL for dynamic imports.
* @returns {any}
*/
function runClassicScriptSource(source, url) {
const {
makeContextifyScript,
runScriptInThisContext,
} = require('internal/vm');
const script = makeContextifyScript(
source, // code
url, // filename
0, // lineOffset
0, // columnOffset
undefined, // cachedData
false, // produceCachedData
undefined, // parsingContext
vm_dynamic_import_default_internal, // hostDefinedOptionId
vm_dynamic_import_default_internal, // importModuleDynamically
);
// "Run the classic script script, with rethrow errors set to true."
return runScriptInThisContext(script, true, false);
}
/**
* Runs the source text of a module script in the current global scope.
* @param {string} source The source text of the script.
* @param {string} url The URL of the script, used as its identifier and as
* the base URL for imports.
* @returns {Promise}
*/
function runModuleScriptSource(source, url) {
return require('internal/modules/run_main').runEntryPointWithESMLoader(
(loader) => {
const parsedURL = new URL(url);
if (parsedURL.protocol === 'file:' && getOptionValue('--strip-types')) {
const filename = fileURLToPath(parsedURL);
const extension = extname(filename);
if (extension === '.ts' || extension === '.mts' || extension === '.cts') {
source = lazyStripTypeScriptModuleTypes()(source, filename, url);
}
}
// Necessary to reset RegExp statics before user code runs.
RegExpPrototypeExec(/^/, '');
return loader.eval(source, url, true);
},
);
}
/**
* Synchronously obtains the source text of a classic script, emulating
* "fetch a classic worker-imported script" for locally resolvable URLs.
* @param {URL} url The parsed URL of the script.
* @param {Blob|undefined} blob The blob URL entry captured when url was
* parsed, for blob: URLs.
* @returns {string}
*/
function fetchClassicScriptSourceSync(url, blob) {
switch (url.protocol) {
case 'file:':
try {
return require('fs').readFileSync(url, 'utf8');
} catch {
break;
}
case 'data:': {
const source = readDataURLScript(url);
if (source !== undefined) {
return source;
}
break;
}
case 'blob:': {
const data = getScriptBlobData(blob);
if (data !== undefined) {
return utf8Decode(data);
}
break;
}
}
// "If response's ... or its Content-Type metadata is not a JavaScript
// MIME type, then throw a "NetworkError" DOMException."
throw lazyDOMException(`Failed to load script: ${url.href}`, 'NetworkError');
}
// Web IDL overload resolution for the postMessage(message, transfer) /
// postMessage(message, options) overload pair: iterable objects select the
// sequence<object> overload, everything else is converted as a
// StructuredSerializeOptions dictionary.
function normalizeTransfer(transferOrOptions, options) {
if ((typeof transferOrOptions === 'object' && transferOrOptions !== null) ||
typeof transferOrOptions === 'function') {
const method = transferOrOptions[SymbolIterator];
if (method != null) {
return converters['sequence<object>'](transferOrOptions, options, method);
}
}
return converters.StructuredSerializeOptions(transferOrOptions, options)
.transfer;
}
const kScopePostMessageOptions = {
__proto__: null,
prefix: "Failed to execute 'postMessage' on 'DedicatedWorkerGlobalScope'",
context: 'Argument 2',
};
const kWorkerPostMessageOptions = {
__proto__: null,
prefix: "Failed to execute 'postMessage' on 'Worker'",
context: 'Argument 2',
};
// https://html.spec.whatwg.org/multipage/workers.html#the-workernavigator-interface
//
// [Exposed=Worker]
// interface WorkerNavigator {};
//
// The NavigatorLanguage, NavigatorConcurrentHardware and NavigatorLocks
// mixins, as well as userAgent and platform of NavigatorID, are inherited
// from the Node.js Navigator implementation; the members below are the
// remainder of the mixins WorkerNavigator includes.
class WorkerNavigator extends Navigator {
constructor(guard = undefined) {
if (guard !== kCreate)
throw new ERR_ILLEGAL_CONSTRUCTOR();
super(kInitializeNavigator);
this[kNavigatorBrand] = true;
}
// WorkerNavigator includes NavigatorID;
// Refs: https://html.spec.whatwg.org/multipage/system-state.html#navigatorid
//
// interface mixin NavigatorID {
// readonly attribute DOMString appVersion;
get appVersion() {
validateThisInternalField(this, kNavigatorBrand, 'WorkerNavigator');
return StringPrototypeSlice(process.version, 1);
}
// };
// WorkerNavigator includes NavigatorLocks;
get locks() {
return super.locks;
}
}
// Refs: https://html.spec.whatwg.org/multipage/system-state.html#navigatoronline
//
// interface mixin NavigatorOnLine {
// readonly attribute boolean onLine;
// "Returns false if the user agent is definitely offline. Returns true
// if the user agent might be online."
// };
const kNavigatorConstants = [
['appCodeName', 'Mozilla'],
['appName', 'Netscape'],
['product', 'Gecko'],
['onLine', true],
];
for (let i = 0; i < kNavigatorConstants.length; i++) {
const { 0: key, 1: value } = kNavigatorConstants[i];
ObjectDefineProperty(WorkerNavigator.prototype, key, {
__proto__: null,
configurable: true,
enumerable: true,
get: assignFunctionName(`get ${key}`, function() {
validateThisInternalField(this, kNavigatorBrand, 'WorkerNavigator');
return value;
}),
});
}
ObjectDefineProperties(WorkerNavigator.prototype, {
appVersion: kEnumerableProperty,
locks: kEnumerableProperty,
[SymbolToStringTag]: {
__proto__: null,
configurable: true,
value: 'WorkerNavigator',
},
});
// https://html.spec.whatwg.org/multipage/workers.html#worker-locations
//
// [Exposed=Worker]
// interface WorkerLocation {
class WorkerLocation {
constructor(guard = undefined, url = undefined) {
if (guard !== kCreate)
throw new ERR_ILLEGAL_CONSTRUCTOR();
// "A WorkerLocation object has an associated WorkerGlobalScope object
// (a WorkerGlobalScope object)."
this[kURL] = url;
}
// "stringifier", for the href attribute defined below
toString() {
validateThisInternalField(this, kURL, 'WorkerLocation');
return this[kURL].href;
}
}
// };
// stringifier readonly attribute USVString href;
// readonly attribute USVString origin;
// readonly attribute USVString protocol;
// readonly attribute USVString host;
// readonly attribute USVString hostname;
// readonly attribute USVString port;
// readonly attribute USVString pathname;
// readonly attribute USVString search;
// readonly attribute USVString hash;
const kWorkerLocationAttributes = [
'href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname',
'search', 'hash',
];
for (let i = 0; i < kWorkerLocationAttributes.length; i++) {
const key = kWorkerLocationAttributes[i];
ObjectDefineProperty(WorkerLocation.prototype, key, {
__proto__: null,
configurable: true,
enumerable: true,
get: assignFunctionName(`get ${key}`, function() {
validateThisInternalField(this, kURL, 'WorkerLocation');
return this[kURL][key];
}),
});
}
ObjectDefineProperties(WorkerLocation.prototype, {
toString: kEnumerableProperty,
[SymbolToStringTag]: {
__proto__: null,
configurable: true,
value: 'WorkerLocation',
},
});
// https://html.spec.whatwg.org/multipage/workers.html#the-workerglobalscope-common-interface
//
// [Exposed=Worker]
// interface WorkerGlobalScope : EventTarget {
class WorkerGlobalScope extends EventTarget {
constructor(guard = undefined) {
if (guard !== kCreate)
throw new ERR_ILLEGAL_CONSTRUCTOR();
super();
}
// readonly attribute WorkerGlobalScope self;
get self() {
validateThisInternalField(this, kLocation, 'WorkerGlobalScope');
// "The self attribute must return the WorkerGlobalScope object itself."
return this;
}
// readonly attribute WorkerLocation location;
get location() {
validateThisInternalField(this, kLocation, 'WorkerGlobalScope');
// "The location attribute must return the WorkerLocation object whose
// associated WorkerGlobalScope object is the WorkerGlobalScope object."
return this[kLocation];
}
// readonly attribute WorkerNavigator navigator;
get navigator() {
validateThisInternalField(this, kLocation, 'WorkerGlobalScope');
// "The navigator attribute of the WorkerGlobalScope interface must
// return an instance of the WorkerNavigator interface, which represents
// the identity and state of the user agent (the client)."
return this[kNavigator] ??= new WorkerNavigator(kCreate);
}
// undefined importScripts((TrustedScriptURL or USVString)... urls);
importScripts(...urls) {
validateThisInternalField(this, kLocation, 'WorkerGlobalScope');
const prefix = "Failed to execute 'importScripts' on 'WorkerGlobalScope'";
// Web IDL converts every argument before running the method steps.
for (let i = 0; i < urls.length; i++) {
urls[i] = converters.USVString(
urls[i], { prefix, context: `Argument ${i + 1}` });
}
// "To import scripts into worker global scope, given a
// WorkerGlobalScope object worker global scope, a list of scalar value
// strings urls, and an optional perform the fetch hook performFetch:"
//
// "If worker global scope's type is "module", throw a TypeError
// exception."
if (this[kType] === 'module') {
throw new ERR_INVALID_STATE.TypeError(
'importScripts() cannot be used in a module worker');
}
// "If urls is empty, return."
if (urls.length === 0)
return;
// "Let urlRecords be [an empty list]"
const urlRecords = [];
// "For each url of urls:"
for (let i = 0; i < urls.length; i++) {
const url = urls[i];
// "Let urlRecord be the result of encoding-parsing a URL given url,
// relative to settings object."
const urlRecord = URLParse(url, this[kLocation].href);
if (urlRecord === null) {
// "If urlRecord is failure, then throw a "SyntaxError"
// DOMException."
throw lazyDOMException(`Failed to parse URL: ${url}`, 'SyntaxError');
}
// Refs: https://url.spec.whatwg.org/#concept-url-parser
const blob = urlRecord.protocol === 'blob:' ?
resolveObjectURL(urlRecord.href) : undefined;
// "Append urlRecord to urlRecords."
ArrayPrototypePush(urlRecords, { url: urlRecord, blob });
}
// "For each urlRecord of urlRecords:"
for (let i = 0; i < urlRecords.length; i++) {
// "Fetch a classic worker-imported script given urlRecord and
// settings object, passing along performFetch if provided. If this
// succeeds, let script be the result. Otherwise, rethrow the
// exception."
const { url, blob } = urlRecords[i];
const script = fetchClassicScriptSourceSync(url, blob);
// "Run the classic script script, with rethrow errors set to true."
runClassicScriptSource(script, url.href);
}
}
}
ObjectDefineProperties(WorkerGlobalScope.prototype, {
self: kEnumerableProperty,
location: kEnumerableProperty,
navigator: kEnumerableProperty,
importScripts: kEnumerableProperty,
[SymbolToStringTag]: {
__proto__: null,
configurable: true,
value: 'WorkerGlobalScope',
},
});
// attribute OnErrorEventHandler onerror;
defineEventHandler(WorkerGlobalScope.prototype, 'error');
// attribute EventHandler onlanguagechange;
defineEventHandler(WorkerGlobalScope.prototype, 'languagechange');
// attribute EventHandler onoffline;
defineEventHandler(WorkerGlobalScope.prototype, 'offline');
// attribute EventHandler ononline;
defineEventHandler(WorkerGlobalScope.prototype, 'online');
// attribute EventHandler onrejectionhandled;
defineEventHandler(WorkerGlobalScope.prototype, 'rejectionhandled');
// attribute EventHandler onunhandledrejection;
defineEventHandler(WorkerGlobalScope.prototype, 'unhandledrejection');
// };
// https://html.spec.whatwg.org/multipage/webappapis.html#windoworworkerglobalscope
//
// WorkerGlobalScope includes WindowOrWorkerGlobalScope;
// https://w3c.github.io/webcrypto/#crypto-interface
//
// partial interface mixin WindowOrWorkerGlobalScope {
// [SameObject] readonly attribute Crypto crypto;
// };
const lazyCrypto = getLazy(() => require('internal/crypto/webcrypto').crypto);
ObjectDefineProperty(WorkerGlobalScope.prototype, 'crypto', {
__proto__: null,
configurable: true,
enumerable: true,
get: assignFunctionName('get crypto', function() {
validateThisInternalField(this, kLocation, 'WorkerGlobalScope');
if (!hasOpenSSL)
throw new ERR_NO_CRYPTO();
return lazyCrypto();
}),
});
// https://w3c.github.io/hr-time/#the-performance-attribute
//
// partial interface mixin WindowOrWorkerGlobalScope {
// [Replaceable] readonly attribute Performance performance;
// };
const lazyPerformance = getLazy(() => require('perf_hooks').performance);
ObjectDefineProperty(WorkerGlobalScope.prototype, 'performance', {
__proto__: null,
configurable: true,
enumerable: true,
get: assignFunctionName('get performance', function() {
validateThisInternalField(this, kLocation, 'WorkerGlobalScope');
return lazyPerformance();
}),
set: assignFunctionName('set performance', function(value) {
validateThisInternalField(this, kLocation, 'WorkerGlobalScope');
replaceAttribute(this, 'performance', value);
}),
});
// Refs: https://html.spec.whatwg.org/multipage/web-messaging.html#messageeventtarget
//
// interface mixin MessageEventTarget {
function includeMessageEventTarget(prototype) {
// attribute EventHandler onmessage;
defineEventHandler(prototype, 'message');
// attribute EventHandler onmessageerror;
defineEventHandler(prototype, 'messageerror');
}
// };
// https://html.spec.whatwg.org/multipage/workers.html#dedicated-workers-and-the-dedicatedworkerglobalscope-interface
//
// [Global=(Worker,DedicatedWorker),Exposed=DedicatedWorker]
// interface DedicatedWorkerGlobalScope : WorkerGlobalScope {
class DedicatedWorkerGlobalScope extends WorkerGlobalScope {
// [Replaceable] readonly attribute DOMString name;
get name() {
validateThisInternalField(this, kInsidePort, 'DedicatedWorkerGlobalScope');
// "The name getter steps are to return this's name. Its value
// represents the name given to the worker using the Worker constructor,
// used primarily for debugging purposes."
return this[kName];
}
set name(value) {
validateThisInternalField(this, kInsidePort, 'DedicatedWorkerGlobalScope');
replaceAttribute(this, 'name', value);
}
// undefined postMessage(any message, sequence<object> transfer);
// undefined postMessage(any message, optional StructuredSerializeOptions options = {});
postMessage(message, transferOrOptions = kEmptyObject) {
validateThisInternalField(this, kInsidePort, 'DedicatedWorkerGlobalScope');
requiredArguments(arguments.length, 1, kScopePostMessageOptions);
// When the second argument was omitted there is nothing to convert.
const transfer = transferOrOptions === kEmptyObject ? undefined :
normalizeTransfer(transferOrOptions, kScopePostMessageOptions);
// "The postMessage(message, transfer) and postMessage(message, options)
// methods on DedicatedWorkerGlobalScope objects act as if, when
// invoked, it immediately invoked the respective postMessage(message,
// transfer) and postMessage(message, options) on the port, with the
// same arguments, and returned the same return value."
this[kInsidePort]?.postMessage(message, transfer);
}
// undefined close();
close() {
validateThisInternalField(this, kInsidePort, 'DedicatedWorkerGlobalScope');
// "The close() method steps are to close a worker given this."
//
// "To close a worker, given a workerGlobal, run these steps:
// 1. Discard any tasks that have been added to workerGlobal's relevant
// agent's event loop's task queues.
// 2. Set workerGlobal's closing flag to true. (This prevents any
// further tasks from being queued.)"
process.exit();
}
}
ObjectDefineProperties(DedicatedWorkerGlobalScope.prototype, {
name: kEnumerableProperty,
postMessage: kEnumerableProperty,
close: kEnumerableProperty,
[SymbolToStringTag]: {
__proto__: null,
configurable: true,
value: 'DedicatedWorkerGlobalScope',
},
});
// };
// DedicatedWorkerGlobalScope includes MessageEventTarget;
includeMessageEventTarget(DedicatedWorkerGlobalScope.prototype);
// https://html.spec.whatwg.org/multipage/workers.html#abstractworker
//
// interface mixin AbstractWorker {
function includeAbstractWorker(prototype) {
// attribute EventHandler onerror;
defineEventHandler(prototype, 'error');
}
// };
// https://html.spec.whatwg.org/multipage/workers.html#worker-options
//
// enum WorkerType { "classic", "module" };
const convertWorkerType = createEnumConverter('WorkerType', [
'classic',
'module',
]);
// RequestCredentials is defined by the Fetch Standard.
// Refs: https://fetch.spec.whatwg.org/#requestcredentials
//
// enum RequestCredentials { "omit", "same-origin", "include" };
const convertRequestCredentials = createEnumConverter('RequestCredentials', [
'omit',
'same-origin',
'include',
]);
// dictionary WorkerOptions {
const convertWorkerOptions = createDictionaryConverter(
'WorkerOptions', [
{
// DOMString name = "";
key: 'name',
converter: converters.DOMString,
defaultValue: () => '',
},
{
// WorkerType type = "classic";
key: 'type',
converter: convertWorkerType,
defaultValue: () => 'classic',
},
{
// RequestCredentials credentials = "same-origin"; // credentials is only used if type is "module"
// (We don't actually use this, but parity with the spec is good, right?)
key: 'credentials',
converter: convertRequestCredentials,
defaultValue: () => 'same-origin',
},
]);
// };
/**
* @param {URL} workerURL The parsed URL of the worker script.
* @returns {{ value?: URL, source?: string }|null}
*/
function resolveWorkerEntry(workerURL) {
switch (workerURL.protocol) {
case 'file:':
return { value: workerURL };
case 'data:': {
const source = readDataURLScript(workerURL);
if (source === undefined) {
return null;
}
return { source };
}
case 'blob:': {
const blob = resolveObjectURL(workerURL.href);
// Blobs are commonly created without an explicit content type, so an
// empty type is accepted in addition to JavaScript MIME types.
const data = getScriptBlobData(blob, true);
if (data === undefined) {
return null;
}
return { source: utf8Decode(data) };
}
default:
throw lazyDOMException(
`Worker scripts must be file:, data: or blob: URLs: ${workerURL.href}`,
'NotSupportedError');
}
}
// https://html.spec.whatwg.org/multipage/workers.html#dedicated-workers-and-the-worker-interface
//
// [Exposed=(Window,DedicatedWorker,SharedWorker)]
// interface Worker : EventTarget {
class Worker extends EventTarget {
// constructor((TrustedScriptURL or USVString) scriptURL, optional WorkerOptions options = {});
constructor(scriptURL, options = kEmptyObject) {
const prefix = "Failed to construct 'Worker'";
requiredArguments(arguments.length, 1, { prefix });
super();
scriptURL = converters.USVString(
scriptURL, { prefix, context: 'Argument 1' });
options = convertWorkerOptions(options, { prefix, context: 'Argument 2' });
// "Let workerURL be the result of encoding-parsing a URL given
// compliantScriptURL, relative to outsideSettings."
const workerURL = URLParse(scriptURL, scopeBaseURL ?? getCWDURL());
if (workerURL === null) {
// "If workerURL is failure, then throw a "SyntaxError" DOMException."
throw lazyDOMException(
`Failed to parse URL: ${scriptURL}`, 'SyntaxError');
}
this[kWorker] = null;
const entry = resolveWorkerEntry(workerURL);
if (entry === null) {
// Even a failed worker has an outside port. Keep a closed port so
// postMessage() still serializes its message and transfers objects.
const { port1, port2 } = new MessageChannel();
port1.close();
port2.close();
this[kOutsidePort] = port1;
// "If the algorithm asynchronously completes with null or with a
// script whose error to rethrow is non-null, then: Queue a global
// task on the DOM manipulation task source given worker's relevant
// global object to fire an event named error at worker."
const { setImmediate } = require('timers');
setImmediate(() => {
this.dispatchEvent(createErrorEvent({
message: `Failed to fetch the worker script: ${workerURL.href}`,
cancelable: true,
}));
});
return;
}
// "Run this step in parallel:"
//
// "Run a worker given worker, workerURL, outsideSettings, outsidePort,
// and options."
this[kWorker] = new NodeWorker(entry.value ?? '', {
name: options.name,
eval: entry.source !== undefined,
[kWebWorkerData]: {
url: workerURL.href,
name: options.name,
type: options.type,
source: entry.source,
},
});
this[kOutsidePort] = this[kWorker][kPublicPort];
this[kRemoveMessageListeners] = forwardMessageEvents(this[kOutsidePort], this);
// "Set notHandled to the result of firing an event named error at
// workerObject, using ErrorEvent, with the cancelable attribute
// initialized to true, and additional attributes initialized according
// to errorInfo."
this[kWorker].on('error', (error) =>
this.dispatchEvent(createErrorEvent({
message: `${error?.message ?? error}`,
error,
cancelable: true,
})));
}
// undefined terminate();
terminate() {
validateThisInternalField(this, kWorker, 'Worker');
// "The terminate() method steps are to terminate a worker given this's
// worker."
// Closing a port is asynchronous. Remove its forwarding listeners too,
// since terminate() can run while the backing thread drains queued messages.
this[kRemoveMessageListeners]?.();
this[kOutsidePort].close();
this[kWorker]?.terminate();
}
// undefined postMessage(any message, sequence<object> transfer);
// undefined postMessage(any message, optional StructuredSerializeOptions options = {});
postMessage(message, transferOrOptions = kEmptyObject) {
validateThisInternalField(this, kWorker, 'Worker');
requiredArguments(arguments.length, 1, kWorkerPostMessageOptions);
// When the second argument was omitted there is nothing to convert.
const transfer = transferOrOptions === kEmptyObject ? undefined :
normalizeTransfer(transferOrOptions, kWorkerPostMessageOptions);
// "The postMessage(message, transfer) and postMessage(message, options)
// methods on Worker objects act as if, when invoked, they immediately
// invoked the respective postMessage(message, transfer) and
// postMessage(message, options) on this's outside port, with the same
// arguments, and returned the same return value."
this[kOutsidePort].postMessage(message, transfer);
}
// The following properties are non-standard, Node.js extensions
[kRef]() {
validateThisInternalField(this, kWorker, 'Worker');
this[kWorker]?.ref();
}
[kUnref]() {
validateThisInternalField(this, kWorker, 'Worker');
this[kWorker]?.unref();
}
}
ObjectDefineProperties(Worker.prototype, {
terminate: kEnumerableProperty,
postMessage: kEnumerableProperty,
[SymbolToStringTag]: {
__proto__: null,
configurable: true,
value: 'Worker',
},
});
// };
// Worker includes AbstractWorker;
includeAbstractWorker(Worker.prototype);
// Worker includes MessageEventTarget;
includeMessageEventTarget(Worker.prototype);
// Operations of the worker global scope that are additionally exposed as
// own properties of the global object
const kScopeOperations = [
// EventTarget
'addEventListener', 'dispatchEvent', 'removeEventListener',
// WorkerGlobalScope
'importScripts',
// DedicatedWorkerGlobalScope
'postMessage', 'close',
];
/**
* @param {URL | string} url The worker's script URL.
* @param {{ name?: string, type?: string, port?: MessagePort }} [options]
* @returns {DedicatedWorkerGlobalScope} globalThis
*/
function installDedicatedWorkerGlobalScope(url, {
name = '',
type = 'classic',
port = require('worker_threads').parentPort,
} = kEmptyObject) {
const locationURL = new URL(url);
if (type === 'classic' && locationURL.protocol === 'file:') {
const { Module } = require('internal/modules/cjs/loader');
ObjectDefineProperty(globalThis, 'require', {
__proto__: null,
configurable: true,
writable: true,
value: Module.createRequire(locationURL),
});
}
const globalProto = ObjectGetPrototypeOf(globalThis);
ObjectSetPrototypeOf(globalProto, DedicatedWorkerGlobalScope.prototype);
ObjectDefineProperty(globalProto, 'constructor', {
__proto__: null,
configurable: true,
writable: true,
value: DedicatedWorkerGlobalScope,
});
initEventTarget(globalThis);
// "Create a new WorkerLocation object and associate it with worker global
// scope."
globalThis[kLocation] = new WorkerLocation(kCreate, locationURL);
globalThis[kName] = `${name}`;
globalThis[kType] = type;
// "DedicatedWorkerGlobalScope objects have an associated inside port (a
// MessagePort)."
// "Set worker global scope's inside port to inside port."
globalThis[kInsidePort] = port;
scopeBaseURL = locationURL;
if (port !== null) {
// "Set inside port's message event target to worker global scope."
forwardMessageEvents(port, globalThis);
}
// We install our own navigator, crypto and performance, see
// WorkerGlobalScope
ReflectDeleteProperty(globalThis, 'navigator');
ReflectDeleteProperty(globalThis, 'Navigator');
ReflectDeleteProperty(globalThis, 'crypto');
ReflectDeleteProperty(globalThis, 'performance');
for (let i = 0; i < kScopeOperations.length; i++) {
const key = kScopeOperations[i];
defineOperation(globalThis, key, assignFunctionName(
key, FunctionPrototypeBind(globalThis[key], globalThis)));
}
// [Exposed=Worker] and [Exposed=DedicatedWorker]
exposeInterface(globalThis, 'WorkerGlobalScope', WorkerGlobalScope);
exposeInterface(
globalThis, 'DedicatedWorkerGlobalScope', DedicatedWorkerGlobalScope);
exposeInterface(globalThis, 'WorkerNavigator', WorkerNavigator);
exposeInterface(globalThis, 'WorkerLocation', WorkerLocation);
return globalThis;
}
module.exports = {
Worker,
installDedicatedWorkerGlobalScope,
runClassicScriptSource,
runModuleScriptSource,
};