Skip to content

Commit 996606f

Browse files
committed
lib: refactor internal webidl converters
Rework lib/internal/webidl.js into a documented shared converter module that follows the Web IDL conversion algorithms more closely. Improvements: - Add documented converters and helper factories for primitive values, dictionaries, enums, sequences, interfaces, required arguments, integers, `Uint8Array`, and `BufferSource`. - Move WebCrypto onto the shared converters, while keeping compatibility wrappers for its existing `BufferSource` and `BigInteger` behavior. - Use shared converters from Blob, Performance, Web Locks, and structured clone option handling. - Add benchmarks for `ConvertToInt` and WebCrypto Web IDL converter hot paths. - Add focused tests for core converters, WebCrypto converters, integer conversion, and buffer source behavior. Fixes: - Make the shared `BufferSource` and `Uint8Array` converters reject resizable `ArrayBuffer` and growable `SharedArrayBuffer` backing stores unless explicitly allowed. WebCrypto preserves its legacy resizable backing-store behavior through compatibility wrappers until a semver-major follow-up can opt in to the stricter behavior. - Use Web IDL `ToNumber` and `ToString` behavior for BigInt, Symbol, and object primitive conversion. - Use exact BigInt modulo for 64-bit `ConvertToInt` wrapping and document the final Number approximation behavior. - Normalize mathematical modulo results to `+0` where Web IDL requires it. - Process inherited dictionaries in least-derived to most-derived order, sorting members only within each dictionary level. - Use `IteratorComplete` truthiness for sequence conversion. - Cover detached buffers, resizable-backed views, growable-backed views, cross-realm buffer sources, mutation-after-call behavior, inherited dictionary member order, and sequence iterator completion behavior. Signed-off-by: Filip Skokan <[email protected]>
1 parent 21436f0 commit 996606f

17 files changed

Lines changed: 2591 additions & 1063 deletions

benchmark/misc/webcrypto-webidl.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
5+
const bench = common.createBenchmark(main, {
6+
op: [
7+
'normalizeAlgorithm-string',
8+
'normalizeAlgorithm-dict',
9+
'webidl-dict',
10+
'webidl-algorithm-identifier-string',
11+
'webidl-algorithm-identifier-object',
12+
'webidl-dict-enforce-range',
13+
'webidl-dict-ensure-sha',
14+
'webidl-dict-null',
15+
],
16+
n: [1e6],
17+
}, { flags: ['--expose-internals'] });
18+
19+
function main({ n, op }) {
20+
const { normalizeAlgorithm } = require('internal/crypto/util');
21+
22+
switch (op) {
23+
case 'normalizeAlgorithm-string': {
24+
// String shortcut + null dictionary (cheapest path).
25+
bench.start();
26+
for (let i = 0; i < n; i++)
27+
normalizeAlgorithm('SHA-256', 'digest');
28+
bench.end(n);
29+
break;
30+
}
31+
case 'normalizeAlgorithm-dict': {
32+
// Object input with a dictionary type and no BufferSource members.
33+
const alg = { name: 'ECDSA', hash: 'SHA-256' };
34+
bench.start();
35+
for (let i = 0; i < n; i++)
36+
normalizeAlgorithm(alg, 'sign');
37+
bench.end(n);
38+
break;
39+
}
40+
case 'webidl-dict': {
41+
// WebIDL dictionary converter in isolation.
42+
const webidl = require('internal/crypto/webidl');
43+
const input = { name: 'AES-GCM', iv: new Uint8Array(12) };
44+
const opts = { prefix: 'test', context: 'test' };
45+
bench.start();
46+
for (let i = 0; i < n; i++)
47+
webidl.converters.AeadParams(input, opts);
48+
bench.end(n);
49+
break;
50+
}
51+
case 'webidl-algorithm-identifier-string': {
52+
// Exercises converters.AlgorithmIdentifier string path.
53+
const webidl = require('internal/crypto/webidl');
54+
const opts = { prefix: 'test', context: 'test' };
55+
bench.start();
56+
for (let i = 0; i < n; i++)
57+
webidl.converters.AlgorithmIdentifier('SHA-256', opts);
58+
bench.end(n);
59+
break;
60+
}
61+
case 'webidl-algorithm-identifier-object': {
62+
// Exercises converters.AlgorithmIdentifier object path.
63+
const webidl = require('internal/crypto/webidl');
64+
const input = { name: 'SHA-256' };
65+
const opts = { prefix: 'test', context: 'test' };
66+
bench.start();
67+
for (let i = 0; i < n; i++)
68+
webidl.converters.AlgorithmIdentifier(input, opts);
69+
bench.end(n);
70+
break;
71+
}
72+
case 'webidl-dict-enforce-range': {
73+
// Exercises [EnforceRange] integer dictionary members.
74+
const webidl = require('internal/crypto/webidl');
75+
const input = {
76+
name: 'RSASSA-PKCS1-v1_5',
77+
modulusLength: 2048,
78+
publicExponent: new Uint8Array([1, 0, 1]),
79+
};
80+
const opts = { prefix: 'test', context: 'test' };
81+
bench.start();
82+
for (let i = 0; i < n; i++)
83+
webidl.converters.RsaKeyGenParams(input, opts);
84+
bench.end(n);
85+
break;
86+
}
87+
case 'webidl-dict-ensure-sha': {
88+
// Exercises ensureSHA on a hash member.
89+
const webidl = require('internal/crypto/webidl');
90+
const input = { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' };
91+
const opts = { prefix: 'test', context: 'test' };
92+
bench.start();
93+
for (let i = 0; i < n; i++)
94+
webidl.converters.RsaHashedImportParams(input, opts);
95+
bench.end(n);
96+
break;
97+
}
98+
case 'webidl-dict-null': {
99+
// Exercises the null/undefined path in createDictionaryConverter().
100+
const webidl = require('internal/crypto/webidl');
101+
const opts = { prefix: 'test', context: 'test' };
102+
bench.start();
103+
for (let i = 0; i < n; i++)
104+
webidl.converters.JsonWebKey(undefined, opts);
105+
bench.end(n);
106+
break;
107+
}
108+
}
109+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
const common = require('../common.js');
5+
6+
const bench = common.createBenchmark(main, {
7+
converter: [
8+
'byte',
9+
'octet',
10+
'unsigned short',
11+
'unsigned long',
12+
'long long',
13+
],
14+
input: [
15+
'integer',
16+
'fractional',
17+
'wrap',
18+
'clamp',
19+
'enforce-range',
20+
'object',
21+
],
22+
n: [1e6],
23+
}, { flags: ['--expose-internals'] });
24+
25+
function getConverter(converter) {
26+
switch (converter) {
27+
case 'byte':
28+
return { bitLength: 8, signedness: 'signed' };
29+
case 'octet':
30+
return { bitLength: 8 };
31+
case 'unsigned short':
32+
return { bitLength: 16 };
33+
case 'unsigned long':
34+
return { bitLength: 32 };
35+
case 'long long':
36+
return { bitLength: 64, signedness: 'signed' };
37+
default:
38+
throw new Error(`Unsupported converter: ${converter}`);
39+
}
40+
}
41+
42+
function getInput(input) {
43+
switch (input) {
44+
case 'integer':
45+
return { value: 7 };
46+
case 'fractional':
47+
return { value: 7.9 };
48+
case 'wrap':
49+
return { value: 2 ** 63 + 2 ** 11 };
50+
case 'clamp':
51+
return { value: 300.8, options: { clamp: true } };
52+
case 'enforce-range':
53+
return { value: 7.9, options: { enforceRange: true } };
54+
case 'object':
55+
return {
56+
value: {
57+
valueOf() { return 7; },
58+
},
59+
};
60+
default:
61+
throw new Error(`Unsupported input: ${input}`);
62+
}
63+
}
64+
65+
function main({ n, converter, input }) {
66+
const { convertToInt } = require('internal/webidl');
67+
const { bitLength, signedness } = getConverter(converter);
68+
const { value, options } = getInput(input);
69+
70+
let noDead;
71+
bench.start();
72+
if (options === undefined) {
73+
for (let i = 0; i < n; i++)
74+
noDead = convertToInt(value, bitLength, signedness);
75+
} else {
76+
for (let i = 0; i < n; i++)
77+
noDead = convertToInt(value, bitLength, signedness, options);
78+
}
79+
bench.end(n);
80+
81+
assert.strictEqual(typeof noDead, 'number');
82+
}

lib/internal/blob.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ const {
5454
const { inspect } = require('internal/util/inspect');
5555
const {
5656
converters,
57-
convertToInt,
5857
createSequenceConverter,
5958
} = require('internal/webidl');
6059

@@ -245,10 +244,14 @@ class Blob {
245244
if (!isBlob(this))
246245
throw new ERR_INVALID_THIS('Blob');
247246

248-
// Coerce values to int
249-
const opts = { __proto__: null, signed: true };
250-
start = convertToInt('start', start, 64, opts);
251-
end = convertToInt('end', end, 64, opts);
247+
start = converters['long long'](
248+
start,
249+
{ __proto__: null, context: 'start' },
250+
);
251+
end = converters['long long'](
252+
end,
253+
{ __proto__: null, context: 'end' },
254+
);
252255

253256
if (start < 0) {
254257
start = MathMax(this[kLength] + start, 0);

0 commit comments

Comments
 (0)