Skip to content

Commit

Permalink
lib: convert transfer sequence to array in js
Browse files Browse the repository at this point in the history
  • Loading branch information
jazelly committed Oct 8, 2024
1 parent 8dbca2d commit 5f3c150
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 23 deletions.
7 changes: 5 additions & 2 deletions lib/internal/bootstrap/web/exposed-window-or-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@ const {
} = require('internal/process/task_queues');
defineOperation(globalThis, 'queueMicrotask', queueMicrotask);

const { structuredClone } = internalBinding('messaging');
defineOperation(globalThis, 'structuredClone', structuredClone);
defineLazyProperties(
globalThis,
'internal/structured_clone',
['structuredClone'],
);
defineLazyProperties(globalThis, 'buffer', ['atob', 'btoa']);

// https://html.spec.whatwg.org/multipage/web-messaging.html#broadcasting-to-other-browsing-contexts
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/perf/usertiming.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const {
},
} = require('internal/errors');

const { structuredClone } = internalBinding('messaging');
const { structuredClone } = require('internal/structured_clone');
const {
lazyDOMException,
kEnumerableProperty,
Expand Down
47 changes: 47 additions & 0 deletions lib/internal/structured_clone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use strict';

const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_MISSING_ARGS,
},
} = require('internal/errors');
const webidl = require('internal/webidl');

const {
structuredClone: nativeStructuredClone,
} = internalBinding('messaging');

webidl.converters['sequence<object>'] = webidl.createSequenceConverter(webidl.converters.any);

function structuredClone(value, options) {
if (arguments.length === 0) {
throw new ERR_MISSING_ARGS('The value argument must be specified');
}

// TODO(jazelly): implement generic webidl dictionary converter
const prefix = 'Options';
const optionsType = webidl.type(options);
if (optionsType !== 'Undefined' && optionsType !== 'Null' && optionsType !== 'Object') {
throw new ERR_INVALID_ARG_TYPE(
prefix,
['object', 'null', 'undefined'],
options,
);
}
const key = 'transfer';
const idlOptions = { __proto__: null, [key]: [] };
if (options !== undefined && options !== null && key in options && options[key] !== undefined) {
idlOptions[key] = webidl.converters['sequence<transfer>'](options[key], {
__proto__: null,
context: 'Transfer',
});
}

const serializedData = nativeStructuredClone(value, idlOptions);
return serializedData;
}

module.exports = {
structuredClone,
};
10 changes: 10 additions & 0 deletions lib/internal/webidl.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ converters.any = (V) => {
return V;
};

converters.object = (V, opts = kEmptyObject) => {
if (type(V) !== 'Object') {
throw makeException(
'is not an object',
kEmptyObject,
);
}
return V;
};

// https://webidl.spec.whatwg.org/#abstract-opdef-integerpart
const integerPart = MathTrunc;

Expand Down
2 changes: 1 addition & 1 deletion lib/internal/webstreams/readablestream.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const {
kControllerErrorFunction,
} = require('internal/streams/utils');

const { structuredClone } = internalBinding('messaging');
const { structuredClone } = require('internal/structured_clone');

const {
ArrayBufferViewGetBuffer,
Expand Down
29 changes: 11 additions & 18 deletions src/node_messaging.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1578,28 +1578,21 @@ static void StructuredClone(const FunctionCallbackInfo<Value>& args) {
Realm* realm = Realm::GetCurrent(context);
Environment* env = realm->env();

if (args.Length() == 0) {
return THROW_ERR_MISSING_ARGS(env, "The value argument must be specified");
}

Local<Value> value = args[0];

TransferList transfer_list;
if (!args[1]->IsNullOrUndefined()) {
if (!args[1]->IsObject()) {
return THROW_ERR_INVALID_ARG_TYPE(
env, "The options argument must be either an object or undefined");
}
Local<Object> options = args[1].As<Object>();
Local<Value> transfer_list_v;
if (!options->Get(context, env->transfer_string())
.ToLocal(&transfer_list_v)) {
return;
}
Local<Object> options = args[1].As<Object>();
Local<Value> transfer_list_v;
if (!options->Get(context, env->transfer_string())
.ToLocal(&transfer_list_v)) {
return;
}

// TODO(joyeecheung): implement this in JS land to avoid the C++ -> JS
// cost to convert a sequence into an array.
if (!GetTransferList(env, context, transfer_list_v, &transfer_list)) {
Local<Array> arr = transfer_list_v.As<Array>();
size_t length = arr->Length();
transfer_list.AllocateSufficientStorage(length);
for (size_t i = 0; i < length; i++) {
if (!arr->Get(context, i).ToLocal(&transfer_list[i])) {
return;
}
}
Expand Down
23 changes: 22 additions & 1 deletion test/parallel/test-structuredClone-global.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ assert.throws(() => structuredClone(undefined, ''), { code: 'ERR_INVALID_ARG_TYP
assert.throws(() => structuredClone(undefined, 1), { code: 'ERR_INVALID_ARG_TYPE' });
assert.throws(() => structuredClone(undefined, { transfer: 1 }), { code: 'ERR_INVALID_ARG_TYPE' });
assert.throws(() => structuredClone(undefined, { transfer: '' }), { code: 'ERR_INVALID_ARG_TYPE' });
assert.throws(() => structuredClone(undefined, { transfer: null }), { code: 'ERR_INVALID_ARG_TYPE' });

// Options can be null or undefined.
assert.strictEqual(structuredClone(undefined), undefined);
assert.strictEqual(structuredClone(undefined, null), undefined);
// Transfer can be null or undefined.
assert.strictEqual(structuredClone(undefined, { transfer: null }), undefined);
assert.strictEqual(structuredClone(undefined, { }), undefined);

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

// Transfer can be iterable
{
const value = {
a: new ReadableStream(),
b: new WritableStream(),
};
const cloned = structuredClone(value, {
transfer: {
*[Symbol.iterator]() {
for (const key in value) {
yield value[key];
}
}
}
});
for (const key in value) {
assert.ok(value[key].locked);
assert.ok(!cloned[key].locked);
}
}

{
// See: https://github.com/nodejs/node/issues/49940
const cloned = structuredClone({}, {
Expand Down

0 comments on commit 5f3c150

Please sign in to comment.