Skip to content

Commit 18f0f07

Browse files
jazellyruyadorno
authored andcommitted
lib: implement webidl dictionary converter and use it in structuredClone
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 <maitken033380023@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent bcead24 commit 18f0f07

File tree

3 files changed

+98
-23
lines changed

3 files changed

+98
-23
lines changed

lib/internal/webidl.js

+63
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const {
44
ArrayPrototypePush,
5+
ArrayPrototypeToSorted,
56
MathAbs,
67
MathMax,
78
MathMin,
@@ -270,6 +271,67 @@ function type(V) {
270271
}
271272
}
272273

274+
// https://webidl.spec.whatwg.org/#js-dictionary
275+
function createDictionaryConverter(members) {
276+
// The spec requires us to operate the members of a dictionary in
277+
// lexicographical order. We are doing this in the outer scope to
278+
// reduce the overhead that could happen in the returned function.
279+
const sortedMembers = ArrayPrototypeToSorted(members, (a, b) => {
280+
if (a.key === b.key) {
281+
return 0;
282+
}
283+
return a.key < b.key ? -1 : 1;
284+
});
285+
286+
return function(
287+
V,
288+
opts = kEmptyObject,
289+
) {
290+
if (V != null && type(V) !== OBJECT) {
291+
throw makeException(
292+
'cannot be converted to a dictionary',
293+
opts,
294+
);
295+
}
296+
297+
const idlDict = { __proto__: null };
298+
for (let i = 0; i < sortedMembers.length; i++) {
299+
const member = sortedMembers[i];
300+
const key = member.key;
301+
let jsMemberValue;
302+
if (V == null) {
303+
jsMemberValue = undefined;
304+
} else {
305+
jsMemberValue = V[key];
306+
}
307+
308+
if (jsMemberValue !== undefined) {
309+
const memberContext = opts.context ? `${key} in ${opts.context}` : `${key}`;
310+
const converter = member.converter;
311+
const idlMemberValue = converter(
312+
jsMemberValue,
313+
{
314+
__proto__: null,
315+
prefix: opts.prefix,
316+
context: memberContext,
317+
},
318+
);
319+
idlDict[key] = idlMemberValue;
320+
} else if (typeof member.defaultValue === 'function') {
321+
const idlMemberValue = member.defaultValue();
322+
idlDict[key] = idlMemberValue;
323+
} else if (member.required) {
324+
throw makeException(
325+
`cannot be converted because of the missing '${key}'`,
326+
opts,
327+
);
328+
}
329+
}
330+
331+
return idlDict;
332+
};
333+
}
334+
273335
// https://webidl.spec.whatwg.org/#es-sequence
274336
function createSequenceConverter(converter) {
275337
return function(V, opts = kEmptyObject) {
@@ -311,6 +373,7 @@ module.exports = {
311373
convertToInt,
312374
createEnumConverter,
313375
createSequenceConverter,
376+
createDictionaryConverter,
314377
evenRound,
315378
makeException,
316379
};

lib/internal/worker/js_transferable.js

+19-18
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ const {
55
} = primordials;
66
const {
77
codes: {
8-
ERR_INVALID_ARG_TYPE,
98
ERR_MISSING_ARGS,
109
},
1110
} = require('internal/errors');
@@ -98,29 +97,31 @@ function markTransferMode(obj, cloneable = false, transferable = false) {
9897
obj[transfer_mode_private_symbol] = mode;
9998
}
10099

100+
101+
webidl.converters.StructuredSerializeOptions = webidl
102+
.createDictionaryConverter(
103+
[
104+
{
105+
key: 'transfer',
106+
converter: webidl.converters['sequence<object>'],
107+
defaultValue: () => [],
108+
},
109+
],
110+
);
111+
101112
function structuredClone(value, options) {
102113
if (arguments.length === 0) {
103114
throw new ERR_MISSING_ARGS('The value argument must be specified');
104115
}
105116

106-
// TODO(jazelly): implement generic webidl dictionary converter
107-
const prefix = 'Options';
108-
const optionsType = webidl.type(options);
109-
if (optionsType !== 'Undefined' && optionsType !== 'Null' && optionsType !== 'Object') {
110-
throw new ERR_INVALID_ARG_TYPE(
111-
prefix,
112-
['object', 'null', 'undefined'],
113-
options,
114-
);
115-
}
116-
const key = 'transfer';
117-
const idlOptions = { __proto__: null, [key]: [] };
118-
if (options != null && key in options && options[key] !== undefined) {
119-
idlOptions[key] = webidl.converters['sequence<object>'](options[key], {
117+
const idlOptions = webidl.converters.StructuredSerializeOptions(
118+
options,
119+
{
120120
__proto__: null,
121-
context: 'Transfer',
122-
});
123-
}
121+
prefix: "Failed to execute 'structuredClone'",
122+
context: 'Options',
123+
},
124+
);
124125

125126
const serializedData = nativeStructuredClone(value, idlOptions);
126127
return serializedData;

test/parallel/test-structuredClone-global.js

+16-5
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,23 @@
33
require('../common');
44
const assert = require('assert');
55

6+
const prefix = "Failed to execute 'structuredClone'";
7+
const key = 'transfer';
8+
const context = 'Options';
9+
const memberConverterError = `${prefix}: ${key} in ${context} can not be converted to sequence.`;
10+
const dictionaryConverterError = `${prefix}: ${context} cannot be converted to a dictionary`;
11+
612
assert.throws(() => structuredClone(), { code: 'ERR_MISSING_ARGS' });
7-
assert.throws(() => structuredClone(undefined, ''), { code: 'ERR_INVALID_ARG_TYPE' });
8-
assert.throws(() => structuredClone(undefined, 1), { code: 'ERR_INVALID_ARG_TYPE' });
9-
assert.throws(() => structuredClone(undefined, { transfer: 1 }), { code: 'ERR_INVALID_ARG_TYPE' });
10-
assert.throws(() => structuredClone(undefined, { transfer: '' }), { code: 'ERR_INVALID_ARG_TYPE' });
11-
assert.throws(() => structuredClone(undefined, { transfer: null }), { code: 'ERR_INVALID_ARG_TYPE' });
13+
assert.throws(() => structuredClone(undefined, ''),
14+
{ code: 'ERR_INVALID_ARG_TYPE', message: dictionaryConverterError });
15+
assert.throws(() => structuredClone(undefined, 1),
16+
{ code: 'ERR_INVALID_ARG_TYPE', message: dictionaryConverterError });
17+
assert.throws(() => structuredClone(undefined, { transfer: 1 }),
18+
{ code: 'ERR_INVALID_ARG_TYPE', message: memberConverterError });
19+
assert.throws(() => structuredClone(undefined, { transfer: '' }),
20+
{ code: 'ERR_INVALID_ARG_TYPE', message: memberConverterError });
21+
assert.throws(() => structuredClone(undefined, { transfer: null }),
22+
{ code: 'ERR_INVALID_ARG_TYPE', message: memberConverterError });
1223

1324
// Options can be null or undefined.
1425
assert.strictEqual(structuredClone(undefined), undefined);

0 commit comments

Comments
 (0)