Skip to content

Commit c73f2fe

Browse files
committed
worker: add support for Web Workers
1 parent 8a1ca0f commit c73f2fe

18 files changed

Lines changed: 1014 additions & 22 deletions

doc/api/cli.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,6 +1527,14 @@ changes:
15271527

15281528
Enable experimental WebAssembly System Interface (WASI) support.
15291529

1530+
### `--experimental-web-worker`
1531+
1532+
<!-- YAML
1533+
added: REPLACEME
1534+
-->
1535+
1536+
Enable experimental support for the Web Worker API.
1537+
15301538
### `--experimental-worker-inspection`
15311539

15321540
<!-- YAML

doc/api/globals.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,6 +1310,15 @@ changes:
13101310
A browser-compatible implementation of {WebSocket}. Disable this API
13111311
with the [`--no-experimental-websocket`][] CLI flag.
13121312

1313+
## Class: `Worker`
1314+
1315+
<!-- YAML
1316+
added: REPLACEME
1317+
-->
1318+
1319+
A browser-compatible implementation of Web Workers. Enable this API
1320+
with the [`--experimental-web-worker`][] CLI flag.
1321+
13131322
## Class: `WritableStream`
13141323

13151324
<!-- YAML
@@ -1359,6 +1368,7 @@ A browser-compatible implementation of [`WritableStreamDefaultWriter`][].
13591368
[RFC 5646]: https://www.rfc-editor.org/rfc/rfc5646.txt
13601369
[Web Crypto API]: webcrypto.md
13611370
[`--experimental-eventsource`]: cli.md#--experimental-eventsource
1371+
[`--experimental-web-worker`]: cli.md#--experimental-web-worker
13621372
[`--localstorage-file`]: cli.md#--localstorage-filefile
13631373
[`--no-experimental-global-navigator`]: cli.md#--no-experimental-global-navigator
13641374
[`--no-experimental-websocket`]: cli.md#--no-experimental-websocket

doc/node.1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,9 @@ Enable experimental ES Module support in the \fBnode:vm\fR module.
820820
.It Fl -experimental-wasi-unstable-preview1
821821
Enable experimental WebAssembly System Interface (WASI) support.
822822
.
823+
.It Fl -experimental-web-worker
824+
Enable experimental support for the Web Worker API.
825+
.
823826
.It Fl -experimental-worker-inspection
824827
Enable experimental support for the worker inspection with Chrome DevTools.
825828
.

lib/internal/blob.js

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const {
3030
} = internalBinding('buffer');
3131

3232
const {
33-
TextDecoder,
33+
getUtf8Decoder,
3434
TextEncoder,
3535
} = require('internal/encoding');
3636
const { URL } = require('internal/url');
@@ -88,7 +88,6 @@ let ReadableStream;
8888
let TextDecoderStream;
8989

9090
const enc = new TextEncoder();
91-
let dec;
9291

9392
// Yes, lazy loading is annoying but because of circular
9493
// references between the url, internal/blob, and buffer
@@ -312,7 +311,7 @@ class Blob {
312311
if (!isBlob(this))
313312
return PromiseReject(new ERR_INVALID_THIS('Blob'));
314313

315-
dec ??= new TextDecoder();
314+
const dec = getUtf8Decoder();
316315

317316
return PromisePrototypeThen(
318317
arrayBuffer(this),
@@ -466,6 +465,43 @@ function arrayBuffer(blob) {
466465
return promise;
467466
}
468467

468+
/**
469+
* Read a blob's data synchronously. This is only possible when every part
470+
* of the blob is memory-resident, in which case the reader's pull callbacks
471+
* are invoked synchronously; otherwise (e.g. for file-backed blobs)
472+
* undefined is returned.
473+
* @param {Blob} blob
474+
* @returns {ArrayBuffer|undefined}
475+
*/
476+
function getBlobDataSync(blob) {
477+
const reader = blob[kHandle].getReader();
478+
const buffers = [];
479+
let result;
480+
let ended = false;
481+
while (!ended) {
482+
let sync = false;
483+
reader.pull((status, buffer) => {
484+
sync = true;
485+
if (status === 0) {
486+
// EOS; buffer should be undefined here.
487+
result = concat(buffers);
488+
ended = true;
489+
return;
490+
} else if (status < 0) {
491+
ended = true;
492+
return;
493+
}
494+
if (buffer !== undefined)
495+
ArrayPrototypePush(buffers, buffer);
496+
});
497+
if (!sync) {
498+
// The data is not available synchronously.
499+
break;
500+
}
501+
}
502+
return result;
503+
}
504+
469505
function createBlobReaderStream(reader) {
470506
return new lazyReadableStream({
471507
type: 'bytes',
@@ -644,6 +680,7 @@ module.exports = {
644680
createBlobFromFilePath,
645681
createBlobReaderIterable,
646682
createBlobReaderStream,
683+
getBlobDataSync,
647684
isBlob,
648685
kHandle,
649686
resolveObjectURL,

lib/internal/bootstrap/web/exposed-window-or-worker.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ exposeLazyInterfaces(globalThis, 'internal/worker/io', ['BroadcastChannel']);
5151
exposeLazyInterfaces(globalThis, 'internal/worker/io', [
5252
'MessageChannel', 'MessagePort',
5353
]);
54+
// https://html.spec.whatwg.org/multipage/workers.html#dedicated-workers-and-the-worker-interface
55+
exposeLazyInterfaces(globalThis, 'internal/webworker', ['Worker']);
5456
// https://www.w3.org/TR/FileAPI/#dfn-Blob
5557
exposeLazyInterfaces(globalThis, 'internal/blob', ['Blob']);
5658
// https://www.w3.org/TR/FileAPI/#dfn-file

lib/internal/encoding.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,8 +614,17 @@ ObjectDefineProperties(TextDecoder.prototype, {
614614
},
615615
});
616616

617+
// A lazily created TextDecoder for the common case of decoding UTF-8 with
618+
// the default options, shared between internal modules.
619+
let utf8Decoder;
620+
function getUtf8Decoder() {
621+
utf8Decoder ??= new TextDecoder();
622+
return utf8Decoder;
623+
}
624+
617625
module.exports = {
618626
getEncodingFromLabel,
627+
getUtf8Decoder,
619628
TextDecoder,
620629
TextEncoder,
621630
};

lib/internal/event_target.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,10 @@ class EventTarget {
722722
return;
723723

724724
type = webidl.converters.DOMString(type);
725-
const capture = options?.capture === true;
725+
// Flatten the options argument like addEventListener does.
726+
// Refs: https://dom.spec.whatwg.org/#concept-flatten-options
727+
const capture = typeof options === 'boolean' ?
728+
options : options?.capture === true;
726729

727730
if (this[kEvents] === undefined)
728731
return;
@@ -1163,6 +1166,13 @@ function defineEventHandler(emitter, name, event = name) {
11631166

11641167
function set(value) {
11651168
validateThisInternalField(this, kHandlers, 'EventTarget');
1169+
// Event handler IDL attributes are [LegacyTreatNonObjectAsNull]: values
1170+
// that are neither callable nor objects deactivate the handler.
1171+
// Refs: https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-idl-attributes
1172+
if (typeof value !== 'function' &&
1173+
(typeof value !== 'object' || value === null)) {
1174+
value = null;
1175+
}
11661176
if (this[kHandlers] === undefined)
11671177
this[kHandlers] = new SafeMap();
11681178
let wrappedHandler = this[kHandlers].get(event);

lib/internal/main/worker_thread.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ port.on('message', (message) => {
100100
hasStdin,
101101
publicPort,
102102
workerData,
103+
webWorkerData,
103104
mainThreadPort,
104105
} = message;
105106

@@ -116,6 +117,11 @@ port.on('message', (message) => {
116117
require('internal/worker').assignEnvironmentData(environmentData);
117118
setupMainThreadPort(mainThreadPort);
118119

120+
if (webWorkerData !== undefined) {
121+
require('internal/webworker')
122+
.installDedicatedWorkerGlobalScope(webWorkerData.url, webWorkerData);
123+
}
124+
119125
// The counter is only passed to the workers created by the main thread,
120126
// not to workers created by other workers.
121127
let cachedCwd = '';
@@ -155,7 +161,14 @@ port.on('message', (message) => {
155161
break;
156162
}
157163

158-
case 'classic': if (getOptionValue('--input-type') !== 'module') {
164+
case 'classic': if (webWorkerData?.source !== undefined) {
165+
// The source of a classic web Worker script loaded from a blob: or
166+
// data: URL. Unlike a worker eval, "run a classic script" evaluates
167+
// the source in the worker's global scope.
168+
require('internal/webworker')
169+
.runClassicScriptSource(webWorkerData.source, webWorkerData.url);
170+
break;
171+
} else if (getOptionValue('--input-type') !== 'module') {
159172
const name = '[worker eval]';
160173
// This is necessary for CJS module compilation.
161174
// TODO: pass this with something really internal.

lib/internal/modules/helpers.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -446,8 +446,6 @@ function assertBufferSource(body, allowString, hookName) {
446446
);
447447
}
448448

449-
let DECODER = null;
450-
451449
/**
452450
* Converts a buffer or buffer-like object to a string.
453451
* @param {string | ArrayBuffer | ArrayBufferView} body - The buffer or buffer-like object to convert to a string.
@@ -456,9 +454,7 @@ let DECODER = null;
456454
function stringify(body) {
457455
if (typeof body === 'string') { return body; }
458456
assertBufferSource(body, false, 'load');
459-
const { TextDecoder } = require('internal/encoding');
460-
DECODER = DECODER === null ? new TextDecoder() : DECODER;
461-
return DECODER.decode(body);
457+
return require('internal/encoding').getUtf8Decoder().decode(body);
462458
}
463459

464460
/**

lib/internal/navigator.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ ObjectDefineProperties(Navigator.prototype, {
159159

160160
module.exports = {
161161
getNavigatorPlatform,
162+
kInitialize,
162163
navigator: new Navigator(kInitialize),
163164
Navigator,
164165
};

0 commit comments

Comments
 (0)