Skip to content

Commit 5f3c150

Browse files
committed
lib: convert transfer sequence to array in js
1 parent 8dbca2d commit 5f3c150

File tree

7 files changed

+97
-23
lines changed

7 files changed

+97
-23
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ const {
3838
} = require('internal/process/task_queues');
3939
defineOperation(globalThis, 'queueMicrotask', queueMicrotask);
4040

41-
const { structuredClone } = internalBinding('messaging');
42-
defineOperation(globalThis, 'structuredClone', structuredClone);
41+
defineLazyProperties(
42+
globalThis,
43+
'internal/structured_clone',
44+
['structuredClone'],
45+
);
4346
defineLazyProperties(globalThis, 'buffer', ['atob', 'btoa']);
4447

4548
// https://html.spec.whatwg.org/multipage/web-messaging.html#broadcasting-to-other-browsing-contexts

lib/internal/perf/usertiming.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const {
3131
},
3232
} = require('internal/errors');
3333

34-
const { structuredClone } = internalBinding('messaging');
34+
const { structuredClone } = require('internal/structured_clone');
3535
const {
3636
lazyDOMException,
3737
kEnumerableProperty,

lib/internal/structured_clone.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
3+
const {
4+
codes: {
5+
ERR_INVALID_ARG_TYPE,
6+
ERR_MISSING_ARGS,
7+
},
8+
} = require('internal/errors');
9+
const webidl = require('internal/webidl');
10+
11+
const {
12+
structuredClone: nativeStructuredClone,
13+
} = internalBinding('messaging');
14+
15+
webidl.converters['sequence<object>'] = webidl.createSequenceConverter(webidl.converters.any);
16+
17+
function structuredClone(value, options) {
18+
if (arguments.length === 0) {
19+
throw new ERR_MISSING_ARGS('The value argument must be specified');
20+
}
21+
22+
// TODO(jazelly): implement generic webidl dictionary converter
23+
const prefix = 'Options';
24+
const optionsType = webidl.type(options);
25+
if (optionsType !== 'Undefined' && optionsType !== 'Null' && optionsType !== 'Object') {
26+
throw new ERR_INVALID_ARG_TYPE(
27+
prefix,
28+
['object', 'null', 'undefined'],
29+
options,
30+
);
31+
}
32+
const key = 'transfer';
33+
const idlOptions = { __proto__: null, [key]: [] };
34+
if (options !== undefined && options !== null && key in options && options[key] !== undefined) {
35+
idlOptions[key] = webidl.converters['sequence<transfer>'](options[key], {
36+
__proto__: null,
37+
context: 'Transfer',
38+
});
39+
}
40+
41+
const serializedData = nativeStructuredClone(value, idlOptions);
42+
return serializedData;
43+
}
44+
45+
module.exports = {
46+
structuredClone,
47+
};

lib/internal/webidl.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ converters.any = (V) => {
3838
return V;
3939
};
4040

41+
converters.object = (V, opts = kEmptyObject) => {
42+
if (type(V) !== 'Object') {
43+
throw makeException(
44+
'is not an object',
45+
kEmptyObject,
46+
);
47+
}
48+
return V;
49+
};
50+
4151
// https://webidl.spec.whatwg.org/#abstract-opdef-integerpart
4252
const integerPart = MathTrunc;
4353

lib/internal/webstreams/readablestream.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ const {
8888
kControllerErrorFunction,
8989
} = require('internal/streams/utils');
9090

91-
const { structuredClone } = internalBinding('messaging');
91+
const { structuredClone } = require('internal/structured_clone');
9292

9393
const {
9494
ArrayBufferViewGetBuffer,

src/node_messaging.cc

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,28 +1578,21 @@ static void StructuredClone(const FunctionCallbackInfo<Value>& args) {
15781578
Realm* realm = Realm::GetCurrent(context);
15791579
Environment* env = realm->env();
15801580

1581-
if (args.Length() == 0) {
1582-
return THROW_ERR_MISSING_ARGS(env, "The value argument must be specified");
1583-
}
1584-
15851581
Local<Value> value = args[0];
15861582

15871583
TransferList transfer_list;
1588-
if (!args[1]->IsNullOrUndefined()) {
1589-
if (!args[1]->IsObject()) {
1590-
return THROW_ERR_INVALID_ARG_TYPE(
1591-
env, "The options argument must be either an object or undefined");
1592-
}
1593-
Local<Object> options = args[1].As<Object>();
1594-
Local<Value> transfer_list_v;
1595-
if (!options->Get(context, env->transfer_string())
1596-
.ToLocal(&transfer_list_v)) {
1597-
return;
1598-
}
1584+
Local<Object> options = args[1].As<Object>();
1585+
Local<Value> transfer_list_v;
1586+
if (!options->Get(context, env->transfer_string())
1587+
.ToLocal(&transfer_list_v)) {
1588+
return;
1589+
}
15991590

1600-
// TODO(joyeecheung): implement this in JS land to avoid the C++ -> JS
1601-
// cost to convert a sequence into an array.
1602-
if (!GetTransferList(env, context, transfer_list_v, &transfer_list)) {
1591+
Local<Array> arr = transfer_list_v.As<Array>();
1592+
size_t length = arr->Length();
1593+
transfer_list.AllocateSufficientStorage(length);
1594+
for (size_t i = 0; i < length; i++) {
1595+
if (!arr->Get(context, i).ToLocal(&transfer_list[i])) {
16031596
return;
16041597
}
16051598
}

test/parallel/test-structuredClone-global.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ assert.throws(() => structuredClone(undefined, ''), { code: 'ERR_INVALID_ARG_TYP
88
assert.throws(() => structuredClone(undefined, 1), { code: 'ERR_INVALID_ARG_TYPE' });
99
assert.throws(() => structuredClone(undefined, { transfer: 1 }), { code: 'ERR_INVALID_ARG_TYPE' });
1010
assert.throws(() => structuredClone(undefined, { transfer: '' }), { code: 'ERR_INVALID_ARG_TYPE' });
11+
assert.throws(() => structuredClone(undefined, { transfer: null }), { code: 'ERR_INVALID_ARG_TYPE' });
1112

1213
// Options can be null or undefined.
1314
assert.strictEqual(structuredClone(undefined), undefined);
1415
assert.strictEqual(structuredClone(undefined, null), undefined);
1516
// Transfer can be null or undefined.
16-
assert.strictEqual(structuredClone(undefined, { transfer: null }), undefined);
1717
assert.strictEqual(structuredClone(undefined, { }), undefined);
1818

1919
// Transferables or its subclasses should be received with its closest transferable superclass
@@ -43,6 +43,27 @@ for (const Transferrable of [File, Blob]) {
4343
assert.ok(extendedTransfer instanceof Transferrable);
4444
}
4545

46+
// Transfer can be iterable
47+
{
48+
const value = {
49+
a: new ReadableStream(),
50+
b: new WritableStream(),
51+
};
52+
const cloned = structuredClone(value, {
53+
transfer: {
54+
*[Symbol.iterator]() {
55+
for (const key in value) {
56+
yield value[key];
57+
}
58+
}
59+
}
60+
});
61+
for (const key in value) {
62+
assert.ok(value[key].locked);
63+
assert.ok(!cloned[key].locked);
64+
}
65+
}
66+
4667
{
4768
// See: https://github.com/nodejs/node/issues/49940
4869
const cloned = structuredClone({}, {

0 commit comments

Comments
 (0)