Skip to content

Commit e53fe03

Browse files
jazellyaduh95
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 270da88 commit e53fe03

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,
@@ -272,6 +273,67 @@ function type(V) {
272273
}
273274
}
274275

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

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)