Skip to content

Commit d0a8986

Browse files
Ethan-Arrowoodjasnell
authored andcommitted
lib: add structuredClone() global
PR-URL: #39759 Fixes: #39713 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Filip Skokan <panva.ip@gmail.com>
1 parent 21cf618 commit d0a8986

16 files changed

+774
-1
lines changed

.eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -362,5 +362,6 @@ module.exports = {
362362
btoa: 'readable',
363363
atob: 'readable',
364364
performance: 'readable',
365+
structuredClone: 'readable',
365366
},
366367
};

lib/.eslintrc.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ rules:
8181
message: "Use `const { performance } = require('perf_hooks');` instead of the global."
8282
- name: queueMicrotask
8383
message: "Use `const { queueMicrotask } = require('internal/process/task_queues');` instead of the global."
84+
- name: structuredClone
85+
message: "Use `const { structuredClone } = require('internal/structured_clone');` instead of the global."
8486
# Custom rules in tools/eslint-rules
8587
node-core/lowercase-name-for-primitive: error
8688
node-core/non-ascii-character: error

lib/internal/bootstrap/node.js

+5
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,11 @@ if (!config.noBrowserGlobals) {
254254
// Non-standard extensions:
255255
defineOperation(globalThis, 'clearImmediate', timers.clearImmediate);
256256
defineOperation(globalThis, 'setImmediate', timers.setImmediate);
257+
258+
const {
259+
structuredClone,
260+
} = require('internal/structured_clone');
261+
defineOperation(globalThis, 'structuredClone', structuredClone);
257262
}
258263

259264
// Set the per-Environment callback that will be called

lib/internal/structured_clone.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
3+
const {
4+
MessageChannel,
5+
receiveMessageOnPort,
6+
} = require('internal/worker/io');
7+
8+
let channel;
9+
function structuredClone(value, transfer) {
10+
// TODO: Improve this with a more efficient solution that avoids
11+
// instantiating a MessageChannel
12+
channel ??= new MessageChannel();
13+
channel.port1.unref();
14+
channel.port2.unref();
15+
channel.port1.postMessage(value, transfer);
16+
return receiveMessageOnPort(channel.port2).message;
17+
}
18+
19+
module.exports = {
20+
structuredClone
21+
};

test/common/index.js

+7
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,13 @@ if (global.PerformanceMeasure) {
293293
knownGlobals.push(global.PerformanceMeasure);
294294
}
295295

296+
// TODO(@ethan-arrowood): Similar to previous checks, this can be temporary
297+
// until v16.x is EOL. Once all supported versions have structuredClone we
298+
// can add this to the list above instead.
299+
if (global.structuredClone) {
300+
knownGlobals.push(global.structuredClone);
301+
}
302+
296303
function allowGlobals(...allowlist) {
297304
knownGlobals = knownGlobals.concat(allowlist);
298305
}

test/fixtures/wpt/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Last update:
1818
- hr-time: https://github.com/web-platform-tests/wpt/tree/9910784394/hr-time
1919
- html/webappapis/atob: https://github.com/web-platform-tests/wpt/tree/f267e1dca6/html/webappapis/atob
2020
- html/webappapis/microtask-queuing: https://github.com/web-platform-tests/wpt/tree/2c5c3c4c27/html/webappapis/microtask-queuing
21+
- html/webappapis/structured-clone: https://github.com/web-platform-tests/wpt/tree/47d3fb280c/html/webappapis/structured-clone
2122
- html/webappapis/timers: https://github.com/web-platform-tests/wpt/tree/5873f2d8f1/html/webappapis/timers
2223
- interfaces: https://github.com/web-platform-tests/wpt/tree/fc086c82d5/interfaces
2324
- performance-timeline: https://github.com/web-platform-tests/wpt/tree/17ebc3aea0/performance-timeline
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Runs a collection of tests that determine if an API implements structured clone
3+
* correctly.
4+
*
5+
* The `runner` parameter has the following properties:
6+
* - `setup()`: An optional function run once before testing starts
7+
* - `teardown()`: An option function run once after all tests are done
8+
* - `preTest()`: An optional, async function run before a test
9+
* - `postTest()`: An optional, async function run after a test is done
10+
* - `structuredClone(obj, transferList)`: Required function that somehow
11+
* structurally clones an object.
12+
* - `hasDocument`: When true, disables tests that require a document. True by default.
13+
*/
14+
15+
function runStructuredCloneBatteryOfTests(runner) {
16+
const defaultRunner = {
17+
setup() {},
18+
preTest() {},
19+
postTest() {},
20+
teardown() {},
21+
hasDocument: true
22+
};
23+
runner = Object.assign({}, defaultRunner, runner);
24+
25+
let setupPromise = runner.setup();
26+
const allTests = structuredCloneBatteryOfTests.map(test => {
27+
28+
if (!runner.hasDocument && test.requiresDocument) {
29+
return;
30+
}
31+
32+
return new Promise(resolve => {
33+
promise_test(async _ => {
34+
test = await test;
35+
await setupPromise;
36+
await runner.preTest(test);
37+
await test.f(runner)
38+
await runner.postTest(test);
39+
resolve();
40+
}, test.description);
41+
}).catch(_ => {});
42+
});
43+
Promise.all(allTests).then(_ => runner.teardown());
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
structuredCloneBatteryOfTests.push({
2+
description: 'ArrayBuffer',
3+
async f(runner) {
4+
const buffer = new Uint8Array([1]).buffer;
5+
const copy = await runner.structuredClone(buffer, [buffer]);
6+
assert_equals(buffer.byteLength, 0);
7+
assert_equals(copy.byteLength, 1);
8+
}
9+
});
10+
11+
structuredCloneBatteryOfTests.push({
12+
description: 'MessagePort',
13+
async f(runner) {
14+
const {port1, port2} = new MessageChannel();
15+
const copy = await runner.structuredClone(port2, [port2]);
16+
const msg = new Promise(resolve => port1.onmessage = resolve);
17+
copy.postMessage('ohai');
18+
assert_equals((await msg).data, 'ohai');
19+
}
20+
});
21+
22+
// TODO: ImageBitmap

0 commit comments

Comments
 (0)