Skip to content

Commit

Permalink
lib: implement webidl dictionary converter and use it in structuredClone
Browse files Browse the repository at this point in the history
This commit provides a factory to generate `dictionaryConverter`
compliant with the spec. The implemented factory function is used for
the `structuredClone` algorithm with updated test cases.

PR-URL: #55489
Reviewed-By: Matthew Aitken <[email protected]>
Reviewed-By: Yagiz Nizipli <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
  • Loading branch information
jazelly authored and ruyadorno committed Nov 27, 2024
1 parent bcead24 commit 18f0f07
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 23 deletions.
63 changes: 63 additions & 0 deletions lib/internal/webidl.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const {
ArrayPrototypePush,
ArrayPrototypeToSorted,
MathAbs,
MathMax,
MathMin,
Expand Down Expand Up @@ -270,6 +271,67 @@ function type(V) {
}
}

// https://webidl.spec.whatwg.org/#js-dictionary
function createDictionaryConverter(members) {
// The spec requires us to operate the members of a dictionary in
// lexicographical order. We are doing this in the outer scope to
// reduce the overhead that could happen in the returned function.
const sortedMembers = ArrayPrototypeToSorted(members, (a, b) => {
if (a.key === b.key) {
return 0;
}
return a.key < b.key ? -1 : 1;
});

return function(
V,
opts = kEmptyObject,
) {
if (V != null && type(V) !== OBJECT) {
throw makeException(
'cannot be converted to a dictionary',
opts,
);
}

const idlDict = { __proto__: null };
for (let i = 0; i < sortedMembers.length; i++) {
const member = sortedMembers[i];
const key = member.key;
let jsMemberValue;
if (V == null) {
jsMemberValue = undefined;
} else {
jsMemberValue = V[key];
}

if (jsMemberValue !== undefined) {
const memberContext = opts.context ? `${key} in ${opts.context}` : `${key}`;
const converter = member.converter;
const idlMemberValue = converter(
jsMemberValue,
{
__proto__: null,
prefix: opts.prefix,
context: memberContext,
},
);
idlDict[key] = idlMemberValue;
} else if (typeof member.defaultValue === 'function') {
const idlMemberValue = member.defaultValue();
idlDict[key] = idlMemberValue;
} else if (member.required) {
throw makeException(
`cannot be converted because of the missing '${key}'`,
opts,
);
}
}

return idlDict;
};
}

// https://webidl.spec.whatwg.org/#es-sequence
function createSequenceConverter(converter) {
return function(V, opts = kEmptyObject) {
Expand Down Expand Up @@ -311,6 +373,7 @@ module.exports = {
convertToInt,
createEnumConverter,
createSequenceConverter,
createDictionaryConverter,
evenRound,
makeException,
};
37 changes: 19 additions & 18 deletions lib/internal/worker/js_transferable.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const {
} = primordials;
const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_MISSING_ARGS,
},
} = require('internal/errors');
Expand Down Expand Up @@ -98,29 +97,31 @@ function markTransferMode(obj, cloneable = false, transferable = false) {
obj[transfer_mode_private_symbol] = mode;
}


webidl.converters.StructuredSerializeOptions = webidl
.createDictionaryConverter(
[
{
key: 'transfer',
converter: webidl.converters['sequence<object>'],
defaultValue: () => [],
},
],
);

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 != null && key in options && options[key] !== undefined) {
idlOptions[key] = webidl.converters['sequence<object>'](options[key], {
const idlOptions = webidl.converters.StructuredSerializeOptions(
options,
{
__proto__: null,
context: 'Transfer',
});
}
prefix: "Failed to execute 'structuredClone'",
context: 'Options',
},
);

const serializedData = nativeStructuredClone(value, idlOptions);
return serializedData;
Expand Down
21 changes: 16 additions & 5 deletions test/parallel/test-structuredClone-global.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,23 @@
require('../common');
const assert = require('assert');

const prefix = "Failed to execute 'structuredClone'";
const key = 'transfer';
const context = 'Options';
const memberConverterError = `${prefix}: ${key} in ${context} can not be converted to sequence.`;
const dictionaryConverterError = `${prefix}: ${context} cannot be converted to a dictionary`;

assert.throws(() => structuredClone(), { code: 'ERR_MISSING_ARGS' });
assert.throws(() => structuredClone(undefined, ''), { code: 'ERR_INVALID_ARG_TYPE' });
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' });
assert.throws(() => structuredClone(undefined, ''),
{ code: 'ERR_INVALID_ARG_TYPE', message: dictionaryConverterError });
assert.throws(() => structuredClone(undefined, 1),
{ code: 'ERR_INVALID_ARG_TYPE', message: dictionaryConverterError });
assert.throws(() => structuredClone(undefined, { transfer: 1 }),
{ code: 'ERR_INVALID_ARG_TYPE', message: memberConverterError });
assert.throws(() => structuredClone(undefined, { transfer: '' }),
{ code: 'ERR_INVALID_ARG_TYPE', message: memberConverterError });
assert.throws(() => structuredClone(undefined, { transfer: null }),
{ code: 'ERR_INVALID_ARG_TYPE', message: memberConverterError });

// Options can be null or undefined.
assert.strictEqual(structuredClone(undefined), undefined);
Expand Down

0 comments on commit 18f0f07

Please sign in to comment.