Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add structuredClone global method #39759

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -362,5 +362,6 @@ module.exports = {
btoa: 'readable',
atob: 'readable',
performance: 'readable',
structuredClone: 'readable',
},
};
2 changes: 2 additions & 0 deletions lib/.eslintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ rules:
message: "Use `const { performance } = require('perf_hooks');` instead of the global."
- name: queueMicrotask
message: "Use `const { queueMicrotask } = require('internal/process/task_queues');` instead of the global."
- name: structuredClone
message: "Use `const { structuredClone } = require('internal/structured_clone');` instead of the global."
# Custom rules in tools/eslint-rules
node-core/lowercase-name-for-primitive: error
node-core/non-ascii-character: error
Expand Down
5 changes: 5 additions & 0 deletions lib/internal/bootstrap/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,11 @@ if (!config.noBrowserGlobals) {
// Non-standard extensions:
defineOperation(globalThis, 'clearImmediate', timers.clearImmediate);
defineOperation(globalThis, 'setImmediate', timers.setImmediate);

const {
structuredClone,
} = require('internal/structured_clone');
defineOperation(globalThis, 'structuredClone', structuredClone);
}

// Set the per-Environment callback that will be called
Expand Down
21 changes: 21 additions & 0 deletions lib/internal/structured_clone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict';

const {
MessageChannel,
receiveMessageOnPort,
} = require('internal/worker/io');

let channel;
function structuredClone(value, transfer) {
// TODO: Improve this with a more efficient solution that avoids
// instantiating a MessageChannel
channel ??= new MessageChannel();
channel.port1.unref();
channel.port2.unref();
channel.port1.postMessage(value, transfer);
return receiveMessageOnPort(channel.port2).message;
}

module.exports = {
structuredClone
};
7 changes: 7 additions & 0 deletions test/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,13 @@ if (global.PerformanceMeasure) {
knownGlobals.push(global.PerformanceMeasure);
}

// TODO(@ethan-arrowood): Similar to previous checks, this can be temporary
// until v16.x is EOL. Once all supported versions have structuredClone we
// can add this to the list above instead.
if (global.structuredClone) {
knownGlobals.push(global.structuredClone);
}

function allowGlobals(...allowlist) {
knownGlobals = knownGlobals.concat(allowlist);
}
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/wpt/LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# The 3-Clause BSD License

Copyright 2019 web-platform-tests contributors
Copyright © web-platform-tests contributors

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

Expand Down
1 change: 1 addition & 0 deletions test/fixtures/wpt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Last update:
- hr-time: https://github.com/web-platform-tests/wpt/tree/9910784394/hr-time
- html/webappapis/atob: https://github.com/web-platform-tests/wpt/tree/f267e1dca6/html/webappapis/atob
- html/webappapis/microtask-queuing: https://github.com/web-platform-tests/wpt/tree/2c5c3c4c27/html/webappapis/microtask-queuing
- html/webappapis/structured-clone: https://github.com/web-platform-tests/wpt/tree/47d3fb280c/html/webappapis/structured-clone
- html/webappapis/timers: https://github.com/web-platform-tests/wpt/tree/5873f2d8f1/html/webappapis/timers
- interfaces: https://github.com/web-platform-tests/wpt/tree/fc086c82d5/interfaces
- performance-timeline: https://github.com/web-platform-tests/wpt/tree/17ebc3aea0/performance-timeline
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Runs a collection of tests that determine if an API implements structured clone
* correctly.
*
* The `runner` parameter has the following properties:
* - `setup()`: An optional function run once before testing starts
* - `teardown()`: An option function run once after all tests are done
* - `preTest()`: An optional, async function run before a test
* - `postTest()`: An optional, async function run after a test is done
* - `structuredClone(obj, transferList)`: Required function that somehow
* structurally clones an object.
Comment on lines +10 to +11
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious: why is this called with await in all of the tests? structuredClone doesn't return a promise, does it?

* - `hasDocument`: When true, disables tests that require a document. True by default.
*/

function runStructuredCloneBatteryOfTests(runner) {
const defaultRunner = {
setup() {},
preTest() {},
postTest() {},
teardown() {},
hasDocument: true
};
runner = Object.assign({}, defaultRunner, runner);

let setupPromise = runner.setup();
const allTests = structuredCloneBatteryOfTests.map(test => {

if (!runner.hasDocument && test.requiresDocument) {
return;
}

return new Promise(resolve => {
promise_test(async _ => {
test = await test;
await setupPromise;
await runner.preTest(test);
await test.f(runner)
await runner.postTest(test);
resolve();
}, test.description);
}).catch(_ => {});
});
Promise.all(allTests).then(_ => runner.teardown());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
structuredCloneBatteryOfTests.push({
description: 'ArrayBuffer',
async f(runner) {
const buffer = new Uint8Array([1]).buffer;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of using Buffer.allocUnsafe to introduce some randomness to the test?

const copy = await runner.structuredClone(buffer, [buffer]);
assert_equals(buffer.byteLength, 0);
assert_equals(copy.byteLength, 1);
}
});

structuredCloneBatteryOfTests.push({
description: 'MessagePort',
async f(runner) {
const {port1, port2} = new MessageChannel();
const copy = await runner.structuredClone(port2, [port2]);
const msg = new Promise(resolve => port1.onmessage = resolve);
copy.postMessage('ohai');
assert_equals((await msg).data, 'ohai');
}
});

// TODO: ImageBitmap
Loading