Skip to content

Commit 916878f

Browse files
committed
lib: implement webidl dictionary converter
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.
1 parent 7ae193d commit 916878f

File tree

3 files changed

+108
-23
lines changed

3 files changed

+108
-23
lines changed

lib/internal/webidl.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const {
44
ArrayPrototypePush,
5+
ArrayPrototypeSort,
56
MathAbs,
67
MathMax,
78
MathMin,
@@ -12,6 +13,7 @@ const {
1213
NumberMAX_SAFE_INTEGER,
1314
NumberMIN_SAFE_INTEGER,
1415
ObjectAssign,
16+
ObjectPrototypeHasOwnProperty,
1517
ObjectPrototypeIsPrototypeOf,
1618
SafeSet,
1719
String,
@@ -263,6 +265,74 @@ function type(V) {
263265
}
264266
}
265267

268+
// https://webidl.spec.whatwg.org/#js-dictionary
269+
function createDictionaryConverter(members) {
270+
// The spec requires us to operate members of each dictionary in
271+
// lexicographical order. We are doing all of these in the outer scope to
272+
// reduce the overhead that could happen in the returned function.
273+
const sortedMembers = [];
274+
for (let i = 0; i < members.length; i++) {
275+
const member = members[i];
276+
ArrayPrototypePush(sortedMembers, member);
277+
}
278+
279+
ArrayPrototypeSort(sortedMembers, (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+
const typeV = type(V);
291+
if (typeV !== 'Undefined' && typeV !== 'Null' && typeV !== 'Object') {
292+
throw makeException(
293+
'can not be converted to a dictionary',
294+
opts,
295+
);
296+
}
297+
298+
const idlDict = { __proto__: null };
299+
for (let i = 0; i < sortedMembers.length; i++) {
300+
const member = sortedMembers[i];
301+
const key = member.key;
302+
let jsMemberValue;
303+
if (typeV === 'Undefined' || typeV === 'Null') {
304+
jsMemberValue = undefined;
305+
} else {
306+
jsMemberValue = V[key];
307+
}
308+
309+
if (jsMemberValue !== undefined) {
310+
const memberContext = opts.context ? `${key} in ${opts.context}` : `${key}`;
311+
const converter = member.converter;
312+
const idlMemberValue = converter(
313+
jsMemberValue,
314+
{
315+
__proto__: null,
316+
prefix: opts.prefix,
317+
context: memberContext,
318+
},
319+
);
320+
idlDict[key] = idlMemberValue;
321+
} else if (ObjectPrototypeHasOwnProperty(member, 'defaultValue')) {
322+
const idlMemberValue = member.defaultValue;
323+
idlDict[key] = idlMemberValue;
324+
} else if (member.required) {
325+
throw makeException(
326+
`can not be converted because of the missing '${key}'`,
327+
opts,
328+
);
329+
}
330+
}
331+
332+
return idlDict;
333+
};
334+
}
335+
266336
// https://webidl.spec.whatwg.org/#es-sequence
267337
function createSequenceConverter(converter) {
268338
return function(V, opts = kEmptyObject) {
@@ -318,6 +388,7 @@ module.exports = {
318388
createEnumConverter,
319389
createInterfaceConverter,
320390
createSequenceConverter,
391+
createDictionaryConverter,
321392
evenRound,
322393
makeException,
323394
};

lib/internal/worker/js_transferable.js

Lines changed: 21 additions & 18 deletions
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,33 @@ function markTransferMode(obj, cloneable = false, transferable = false) {
9897
obj[transfer_mode_private_symbol] = mode;
9998
}
10099

100+
101+
webidl.converters.StructuredCloneOptions = webidl
102+
.createDictionaryConverter(
103+
[
104+
{
105+
key: 'transfer',
106+
converter: webidl.converters['sequence<object>'],
107+
get defaultValue() {
108+
return [];
109+
},
110+
},
111+
],
112+
);
113+
101114
function structuredClone(value, options) {
102115
if (arguments.length === 0) {
103116
throw new ERR_MISSING_ARGS('The value argument must be specified');
104117
}
105118

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], {
119+
const idlOptions = webidl.converters.StructuredCloneOptions(
120+
options,
121+
{
120122
__proto__: null,
121-
context: 'Transfer',
122-
});
123-
}
123+
prefix: "Failed to execute 'structuredClone'",
124+
context: 'Options',
125+
},
126+
);
124127

125128
const serializedData = nativeStructuredClone(value, idlOptions);
126129
return serializedData;

test/parallel/test-structuredClone-global.js

Lines changed: 16 additions & 5 deletions
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} can not 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)