forked from nodejs/node
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwebidl.js
More file actions
1087 lines (999 loc) · 35.4 KB
/
Copy pathwebidl.js
File metadata and controls
1087 lines (999 loc) · 35.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
'use strict';
const {
ArrayBufferIsView,
ArrayBufferPrototypeGetResizable,
ArrayIsArray,
ArrayPrototypePush,
ArrayPrototypeToSorted,
BigInt,
DataViewPrototypeGetBuffer,
FunctionPrototypeCall,
MathAbs,
MathMax,
MathMin,
MathPow,
MathSign,
MathTrunc,
Number,
NumberIsFinite,
NumberIsNaN,
NumberMAX_SAFE_INTEGER,
NumberMIN_SAFE_INTEGER,
ObjectPrototypeIsPrototypeOf,
SafeArrayIterator,
SafeSet,
String,
SymbolIterator,
TypeError,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetSymbolToStringTag,
} = primordials;
const { kEmptyObject, setOwnProperty } = require('internal/util');
const {
isSharedArrayBuffer,
isTypedArray,
} = require('internal/util/types');
const BIGINT_2_63 = 1n << 63n;
const BIGINT_2_64 = 1n << 64n;
const converters = { __proto__: null };
/**
* @typedef {object} ConversionOptions
* @property {string} [prefix] Message prefix for operation failures.
* @property {string} [context] Message context for the converted value.
* @property {string} [code] Node.js error code to assign to TypeError.
* @property {boolean} [enforceRange] Web IDL [EnforceRange] attribute.
* @property {boolean} [clamp] Web IDL [Clamp] attribute.
* @property {boolean} [allowShared] Web IDL [AllowShared] attribute for
* buffer view types.
* @property {boolean} [allowResizable] Web IDL [AllowResizable] attribute.
*/
/**
* @callback Converter
* @param {any} V JavaScript value to convert to an IDL value.
* @param {ConversionOptions} [options] Conversion options.
* @returns {any}
*/
/**
* @callback DictionaryMemberValidator
* @param {any} idlMemberValue Converted IDL member value.
* @param {object} jsDict Original JavaScript dictionary object.
* @returns {void}
*/
/**
* @typedef {object} DictionaryMember
* @property {string} key Dictionary member identifier.
* @property {Converter} converter Converter for the member type.
* @property {boolean} [required] Whether the member is required.
* @property {() => any} [defaultValue] Function returning the default value.
* @property {DictionaryMemberValidator} [validator] Optional Node.js
* extension point invoked after conversion and before storing the member.
* This is for early semantic validation of known unsupported IDL values,
* especially in Web Crypto, where SubtleCrypto.supports() needs to answer
* from normalized dictionaries without running the requested operation.
*/
/**
* Creates a TypeError with a Node.js error code.
* @param {string} message Error message.
* @param {string} code Node.js error code to assign.
* @returns {TypeError}
*/
function codedTypeError(message, code) {
// eslint-disable-next-line no-restricted-syntax
const err = new TypeError(message);
setOwnProperty(err, 'code', code);
return err;
}
/**
* Creates the exception thrown by Web IDL converters.
* @param {string} message Unprefixed conversion failure message.
* @param {ConversionOptions} [options] Conversion options.
* @returns {TypeError}
*/
function makeException(message, options = kEmptyObject) {
const prefix = options.prefix ? options.prefix + ': ' : '';
const context = options.context?.length === 0 ?
'' : (options.context ?? 'Value') + ' ';
return codedTypeError(
`${prefix}${context}${message}`,
options.code || 'ERR_INVALID_ARG_TYPE',
);
}
/**
* Builds derived conversion options for nested converter calls and adjusted
* error codes. These objects are allocated on dictionary/sequence conversion
* hot paths, so keep their shape stable and avoid object spread and
* null-prototype objects.
* @param {ConversionOptions} options Parent conversion options.
* @param {string} [context] Replacement context.
* @param {string} [code] Replacement error code.
* @returns {ConversionOptions}
*/
function makeOptions(options, context = options.context, code = options.code) {
return {
prefix: options.prefix,
context,
code,
enforceRange: options.enforceRange,
clamp: options.clamp,
allowShared: options.allowShared,
allowResizable: options.allowResizable,
};
}
/**
* Returns the ECMAScript specification type of a JavaScript value.
* @see https://tc39.es/ecma262/#sec-ecmascript-data-types-and-values
* @param {any} V JavaScript value.
* @returns {'Undefined'|'Null'|'Boolean'|'String'|'Symbol'|'Number'|'BigInt'|'Object'}
*/
function type(V) {
// ECMA-262 6.1: map JavaScript values to language type names.
switch (typeof V) {
case 'undefined':
return 'Undefined';
case 'boolean':
return 'Boolean';
case 'string':
return 'String';
case 'symbol':
return 'Symbol';
case 'number':
return 'Number';
case 'bigint':
return 'BigInt';
case 'object':
case 'function':
default:
// ECMA-262 6.1.2: null is its own language type.
// ECMA-262 6.1.7: functions are Object values.
if (V === null) {
return 'Null';
}
return 'Object';
}
}
/**
* Returns IntegerPart(n).
* @see https://webidl.spec.whatwg.org/#abstract-opdef-integerpart
* @param {number} n Numeric value.
* @returns {number}
*/
function integerPart(n) {
// Web IDL IntegerPart steps 1-3: floor(abs(n)), restore the sign,
// and choose +0 rather than -0.
const integer = MathTrunc(n);
return integer === 0 ? 0 : integer;
}
/**
* Rounds to the nearest integer, choosing the even integer on ties.
* @param {number} x Numeric value.
* @returns {number}
*/
function evenRound(x) {
// Web IDL ConvertToInt step 7.2: round to the nearest integer,
// choosing the even integer on ties and +0 rather than -0.
const i = integerPart(x);
const remainder = MathAbs(x % 1);
const sign = MathSign(x);
if (remainder === 0.5) {
return i % 2 === 0 ? i : i + sign;
}
const r = remainder < 0.5 ? i : i + sign;
if (r === 0) {
return 0;
}
return r;
}
/**
* Returns 2 to the power of the given exponent.
* @param {number} exponent Non-negative integer exponent.
* @returns {number}
*/
function pow2(exponent) {
if (exponent < 31) {
return 1 << exponent;
}
if (exponent === 31) {
return 0x8000_0000;
}
if (exponent === 32) {
return 0x1_0000_0000;
}
return MathPow(2, exponent);
}
/**
* Returns x modulo y for Web IDL ConvertToInt step 10.
*
* This is intentionally not a general modulo helper. ConvertToInt only calls
* it with a positive power-of-two modulus, and the implementation assumes
* that. It converts JavaScript remainder into mathematical modulo and
* normalizes -0 to +0.
* @param {number} x Dividend.
* @param {number} y Positive divisor.
* @returns {number}
*/
function modulo(x, y) {
// Web IDL ConvertToInt step 10 uses mathematical modulo.
const r = x % y;
if (r === 0) {
return 0;
}
return r > 0 ? r : r + y;
}
/**
* Returns x modulo y for Web IDL ConvertToInt step 10.
*
* This is intentionally not a general modulo helper. ConvertToInt only calls
* it with a positive power-of-two modulus, and the implementation assumes
* that. BigInt has no -0, but this mirrors modulo()'s mathematical modulo
* behavior for the 64-bit path.
* @param {bigint} x Dividend.
* @param {bigint} y Positive divisor.
* @returns {bigint}
*/
function bigIntModulo(x, y) {
// Web IDL ConvertToInt step 10 uses mathematical modulo.
const r = x % y;
return r >= 0n ? r : r + y;
}
/**
* Returns ToNumber(V).
* @see https://tc39.es/ecma262/#sec-tonumber
* @param {any} V JavaScript value.
* @param {ConversionOptions} [options] Conversion options.
* @returns {number}
*/
function toNumber(V, options = kEmptyObject) {
if (typeof V === 'bigint') {
// ECMA-262 ToNumber step 2: BigInt values throw.
throw makeException(
'is a BigInt and cannot be converted to a number.',
options);
}
if (typeof V === 'symbol') {
// ECMA-262 ToNumber step 2: Symbol values throw.
throw makeException(
'is a Symbol and cannot be converted to a number.',
options);
}
// Unary plus performs ToNumber, including ToPrimitive(V, number) for
// objects. Number(V) is not equivalent because it converts BigInt values,
// including BigInt values produced by ToPrimitive.
// Abrupt completions and native TypeErrors propagate unchanged. This is an
// intentional diagnostics tradeoff: decorating object conversion failures
// would require maintaining local ECMA-262 ToPrimitive and
// OrdinaryToPrimitive implementations.
return +V;
}
/**
* Returns ToString(V).
* @see https://tc39.es/ecma262/#sec-tostring
* @param {any} V JavaScript value.
* @param {ConversionOptions} [options] Conversion options.
* @returns {string}
*/
function toString(V, options = kEmptyObject) {
if (typeof V === 'symbol') {
// ECMA-262 ToString step 2: Symbol values throw.
throw makeException(
'is a Symbol and cannot be converted to a string.',
options);
}
// The String function performs ToString for all non-Symbol primitives and
// objects, including ToPrimitive(V, string). String concatenation is not
// equivalent because it uses ToPrimitive(V, default). Abrupt completions
// and native TypeErrors propagate unchanged. This is an intentional
// diagnostics tradeoff: decorating object conversion failures would require
// maintaining local ECMA-262 ToPrimitive and OrdinaryToPrimitive
// implementations.
return String(V);
}
/**
* Converts a JavaScript value to a Web IDL integer value.
* @see https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
* @param {any} V JavaScript value.
* @param {number} bitLength Integer bit length.
* @param {'signed'|'unsigned'} [signedness] Integer signedness.
* @param {ConversionOptions} [options] Conversion options.
* @returns {number}
*/
function convertToInt(
V,
bitLength,
signedness = 'unsigned',
options = kEmptyObject,
) {
const signed = signedness === 'signed';
let upperBound;
let lowerBound;
// Web IDL ConvertToInt steps 1-3: determine lower/upper bounds.
if (bitLength === 64) {
// Steps 1.1-1.3 set upperBound to 2^53 - 1 and lowerBound to 0
// for unsigned, or -2^53 + 1 for signed. This ensures 64-bit
// integer types associated with [EnforceRange] or [Clamp] are
// representable in JavaScript's Number type as unambiguous integers.
upperBound = NumberMAX_SAFE_INTEGER;
lowerBound = signed ? NumberMIN_SAFE_INTEGER : 0;
} else if (!signed) {
// Spell out the common Web IDL integer sizes so hot converters avoid
// recomputing powers of two on every call.
lowerBound = 0;
if (bitLength === 8) {
upperBound = 0xff;
} else if (bitLength === 16) {
upperBound = 0xffff;
} else if (bitLength === 32) {
upperBound = 0xffff_ffff;
} else {
upperBound = pow2(bitLength) - 1;
}
} else if (bitLength === 8) {
// Signed 8/16/32-bit conversions are mostly exercised through direct
// convertToInt() calls, but keep their common bounds cheap too.
lowerBound = -0x80;
upperBound = 0x7f;
} else if (bitLength === 16) {
lowerBound = -0x8000;
upperBound = 0x7fff;
} else if (bitLength === 32) {
lowerBound = -0x8000_0000;
upperBound = 0x7fff_ffff;
} else {
lowerBound = -pow2(bitLength - 1);
upperBound = pow2(bitLength - 1) - 1;
}
// Common case: primitive Number values that already fit the Web IDL
// range and have no fractional part are returned unchanged by every
// ConvertToInt path, except that -0 must become +0. This skips the
// generic ToNumber and option handling without skipping observable
// object coercion.
let x;
if (typeof V === 'number') {
// For primitive Numbers, in-range non-[Clamp] conversion is either
// identity or IntegerPart(V). This keeps the default and [EnforceRange]
// paths out of the generic ToNumber/options flow.
if (V >= lowerBound && V <= upperBound) {
const integer = MathTrunc(V);
if (integer === V) {
return V === 0 ? 0 : V;
}
if (options !== kEmptyObject && options.clamp) {
return evenRound(V);
}
return integer === 0 ? 0 : integer;
}
if (options !== kEmptyObject && options.enforceRange) {
// Keep [EnforceRange] ahead of [Clamp] without falling through to
// the shared check, which would observe options.enforceRange again.
if (!NumberIsFinite(V)) {
throw makeException(
'is not a finite number.',
options);
}
const integer = integerPart(V);
if (integer < lowerBound || integer > upperBound) {
throw makeException(
`is outside the expected range of ${lowerBound} to ${upperBound}.`,
makeOptions(options, options.context, 'ERR_OUT_OF_RANGE'));
}
return integer;
}
if (options !== kEmptyObject && options.clamp && !NumberIsNaN(V)) {
// Out-of-range [Clamp] returns one of the already-computed bounds.
if (V <= lowerBound) {
return lowerBound === 0 ? 0 : lowerBound;
}
if (V >= upperBound) {
return upperBound === 0 ? 0 : upperBound;
}
}
x = V;
} else {
// Step 4: convert V with ECMA-262 ToNumber.
x = toNumber(V, options);
}
// Step 5: normalize -0 to +0.
if (x === 0) {
x = 0;
}
// Step 6: [EnforceRange] rejects non-finite and out-of-range values.
if (options.enforceRange) {
// Step 6.1: reject NaN and infinities.
if (!NumberIsFinite(x)) {
throw makeException(
'is not a finite number.',
options);
}
// Step 6.2: truncate to IntegerPart(x).
x = integerPart(x);
// Steps 6.3-6.4: reject out-of-range values, otherwise return.
if (x < lowerBound || x > upperBound) {
throw makeException(
`is outside the expected range of ${lowerBound} to ${upperBound}.`,
makeOptions(options, options.context, 'ERR_OUT_OF_RANGE'));
}
return x;
}
// Step 7: [Clamp] clamps, rounds, and returns non-NaN values.
if (options.clamp && !NumberIsNaN(x)) {
// Step 7.1: clamp x into the supported bounds.
x = MathMin(MathMax(x, lowerBound), upperBound);
// Steps 7.2-7.3: round ties to even and return.
return evenRound(x);
}
// Step 8: NaN, +0, -0, and infinities become +0.
if (!NumberIsFinite(x) || x === 0) {
return 0;
}
// Step 9: truncate to IntegerPart(x).
x = integerPart(x);
// Steps 10-12 are an identity for values already in the step 1-3
// bounds. For 64-bit conversions this only skips the safe-integer
// subset; values outside it still need exact BigInt modulo and the
// final Number approximation.
if (x >= lowerBound && x <= upperBound) {
return x;
}
if (bitLength === 64) {
// Steps 10-12 still wrap over the full 2^64 IDL integer range.
// BigInt keeps x modulo 2^64 and the signed high-bit adjustment exact
// before this helper returns the JavaScript binding result.
let xBigInt = BigInt(x);
xBigInt = bigIntModulo(xBigInt, BIGINT_2_64);
// For long long and unsigned long long values outside the safe-integer
// range, Web IDL says the JS Number value represents the closest numeric
// value, choosing the value with an even significand if there are two
// equally close values. Number(BigInt) performs that final approximation.
// Step 11: wrap into the signed range when the high bit is set.
if (signed && xBigInt >= BIGINT_2_63) {
return Number(xBigInt - BIGINT_2_64);
}
// Step 12: return the unsigned value.
return Number(xBigInt);
}
// For 8/16/32-bit conversions, bitwise operators perform the same
// power-of-two wrapping as Web IDL step 10 for finite integer Numbers.
// The shifts narrow the unsigned value into the signed range when needed.
if (bitLength === 8) {
return signed ? (x << 24) >> 24 : x & 0xff;
}
if (bitLength === 16) {
return signed ? (x << 16) >> 16 : x & 0xffff;
}
if (bitLength === 32) {
return signed ? x | 0 : x >>> 0;
}
// Step 10: reduce modulo 2^bitLength.
const twoToTheBitLength = pow2(bitLength);
x = modulo(x, twoToTheBitLength);
// Step 11: wrap into the signed range when the high bit is set.
if (signed && x >= pow2(bitLength - 1)) {
return x - twoToTheBitLength;
}
// Step 12: return the unsigned value.
return x;
}
/**
* Creates a converter for a Web IDL integer type.
* @param {number} bitLength Integer bit length.
* @param {'signed'|'unsigned'} [signedness] Integer signedness.
* @returns {Converter}
*/
function createIntegerConverter(bitLength, signedness = 'unsigned') {
return (V, options = kEmptyObject) => {
// Integer conversion step 1 calls ConvertToInt; step 2 returns
// the IDL value with the same numeric value.
return convertToInt(V, bitLength, signedness, options);
};
}
/**
* Converts a JavaScript value to the IDL boolean type.
* @see https://webidl.spec.whatwg.org/#es-boolean
* @param {any} V JavaScript value.
* @returns {boolean}
*/
converters.boolean = (V) => {
// Web IDL boolean steps 1-2: ToBoolean(V), then return the same
// truth value as an IDL boolean.
return !!V;
};
/**
* Converts a JavaScript value to the IDL object type.
* @see https://webidl.spec.whatwg.org/#es-object
* @param {any} V JavaScript value.
* @param {ConversionOptions} [options] Conversion options.
* @returns {object|Function}
*/
converters.object = (V, options = kEmptyObject) => {
// Web IDL object step 1: throw unless V is an ECMA-262 Object.
if (type(V) !== 'Object') {
throw makeException(
'is not an object.',
options,
);
}
// Step 2: return a reference to the same object.
return V;
};
/**
* Converts a JavaScript value to the IDL octet type.
* @see https://webidl.spec.whatwg.org/#es-octet
* @type {Converter}
*/
converters.octet = createIntegerConverter(8);
/**
* Converts a JavaScript value to the IDL unsigned short type.
* @see https://webidl.spec.whatwg.org/#es-unsigned-short
* @type {Converter}
*/
converters['unsigned short'] = createIntegerConverter(16);
/**
* Converts a JavaScript value to the IDL unsigned long type.
* @see https://webidl.spec.whatwg.org/#es-unsigned-long
* @type {Converter}
*/
converters['unsigned long'] = createIntegerConverter(32);
/**
* Converts a JavaScript value to the IDL long long type.
* @see https://webidl.spec.whatwg.org/#es-long-long
* @type {Converter}
*/
converters['long long'] = createIntegerConverter(64, 'signed');
/**
* Converts a JavaScript value to the IDL DOMString type.
* @see https://webidl.spec.whatwg.org/#es-DOMString
* @param {any} V JavaScript value.
* @param {ConversionOptions} [options] Conversion options.
* @returns {string}
*/
converters.DOMString = function DOMString(V, options = kEmptyObject) {
// Step 1 only applies to [LegacyNullToEmptyString], which this core
// converter does not implement. Steps 2-3 apply ToString(V) and
// return a DOMString with the same code units.
return toString(V, options);
};
/**
* Throws when a Web IDL operation receives too few arguments.
* @param {number} length Actual argument count.
* @param {number} required Required argument count.
* @param {ConversionOptions} [options] Conversion options.
* @returns {void}
*/
function requiredArguments(length, required, options = kEmptyObject) {
if (length < required) {
throw makeException(
`${required} argument${
required === 1 ? '' : 's'
} required, but only ${length} present.`,
makeOptions(options, '', 'ERR_MISSING_ARGS'));
}
}
/**
* Creates a converter for a Web IDL enum type.
* @see https://webidl.spec.whatwg.org/#es-enumeration
* @param {string} name Enum identifier.
* @param {string[]} values Enum values.
* @returns {Converter}
*/
function createEnumConverter(name, values) {
const E = new SafeSet(new SafeArrayIterator(values));
return function(V, options = kEmptyObject) {
// Web IDL enumeration step 1: convert V with ToString.
const S = toString(V, options);
// Step 2: throw unless S is one of the enumeration values.
if (!E.has(S)) {
throw makeException(
`'${S}' is not a valid enum value of type ${name}.`,
makeOptions(options, options.context, 'ERR_INVALID_ARG_VALUE'));
}
// Step 3: return the matching enumeration value.
return S;
};
}
/**
* Returns the context used when converting a dictionary member.
* @param {string} key Dictionary member identifier.
* @param {ConversionOptions} options Conversion options.
* @returns {string}
*/
function dictionaryMemberContext(key, options) {
return options.context ? `${key} in ${options.context}` : key;
}
/**
* Returns the context used when converting a sequence element.
* @param {number} index Sequence element index.
* @param {ConversionOptions} options Conversion options.
* @returns {string}
*/
function sequenceElementContext(index, options) {
return `${options.context ?? 'Value'}[${index}]`;
}
/**
* Returns the message used for a missing required dictionary member.
* @param {string} dictionaryName Dictionary identifier.
* @param {string} key Dictionary member identifier.
* @returns {string}
*/
function missingDictionaryMemberMessage(dictionaryName, key) {
return `cannot be converted to '${dictionaryName}' because ` +
`'${key}' is required in '${dictionaryName}'.`;
}
/**
* Creates a converter for a Web IDL dictionary type.
* @see https://webidl.spec.whatwg.org/#js-dictionary
* @param {string} dictionaryName Dictionary identifier.
* @param {DictionaryMember[]|DictionaryMember[][]} members Dictionary members,
* either for a single dictionary or grouped from least-derived to
* most-derived dictionary.
* @returns {Converter}
*/
function createDictionaryConverter(
dictionaryName,
members,
) {
const compareMembers = (a, b) => {
if (a.key === b.key) {
return 0;
}
return a.key < b.key ? -1 : 1;
};
const dictionaries = ArrayIsArray(members[0]) ? members : [members];
const sortedDictionaries = [];
// Web IDL dictionary conversion steps 3-4 process inherited dictionaries
// from least-derived to most-derived and sort only within each dictionary.
// Callers with inheritance pass one member array per dictionary level.
for (let i = 0; i < dictionaries.length; i++) {
ArrayPrototypePush(
sortedDictionaries,
ArrayPrototypeToSorted(dictionaries[i], compareMembers),
);
}
return function(jsDict, options = kEmptyObject) {
// Step 1: reject non-object, non-null, non-undefined values.
if (jsDict != null && type(jsDict) !== 'Object') {
throw makeException(
'cannot be converted to a dictionary',
options,
);
}
// Step 2: create the IDL dictionary value.
const idlDict = { __proto__: null };
// Steps 3-4: iterate each dictionary level, then its sorted members.
for (let i = 0; i < sortedDictionaries.length; i++) {
const sortedMembers = sortedDictionaries[i];
for (let j = 0; j < sortedMembers.length; j++) {
const member = sortedMembers[j];
// Step 4.1.1: get the dictionary member identifier.
const key = member.key;
// Steps 4.1.2-4.1.3: read the JavaScript member value.
const jsMemberValue = jsDict == null ? undefined : jsDict[key];
// Step 4.1.4: convert and store present member values.
if (jsMemberValue !== undefined) {
const converter = member.converter;
// Step 4.1.4.1: convert the JavaScript value to IDL.
const idlMemberValue = converter(
jsMemberValue,
makeOptions(options, dictionaryMemberContext(key, options)),
);
// Validators are a Node.js extension after conversion. They let
// consumers reject known unsupported values while dictionary
// conversion still has precise member context. Web Crypto uses this
// so SubtleCrypto.supports() can make accurate decisions from
// normalized dictionaries instead of probing by running operations.
member.validator?.(idlMemberValue, jsDict);
// Step 4.1.4.2: set idlDict[key] to the IDL value.
idlDict[key] = idlMemberValue;
} else if (typeof member.defaultValue === 'function') {
// Step 4.1.5: store the member default value.
idlDict[key] = member.defaultValue();
} else if (member.required) {
// Step 4.1.6: required missing members throw.
throw makeException(
missingDictionaryMemberMessage(dictionaryName, key),
makeOptions(options, options.context, 'ERR_MISSING_OPTION'));
}
}
}
// Step 5: return the IDL dictionary.
return idlDict;
};
}
/**
* Creates a converter for a Web IDL sequence type.
* @see https://webidl.spec.whatwg.org/#es-sequence
* @param {Converter} converter Element converter.
* @returns {Converter}
*/
function createSequenceConverter(converter) {
return function(V, options = kEmptyObject) {
// Web IDL sequence conversion step 1: require an ECMA-262 Object.
if (type(V) !== 'Object') {
throw makeException(
'cannot be converted to sequence.',
options);
}
// Step 2: GetMethod(V, %Symbol.iterator%).
const method = V[SymbolIterator];
// Step 3: throw if the iterator method is undefined, null, or not callable.
if (typeof method !== 'function') {
throw makeException(
'cannot be converted to sequence.',
options);
}
// Step 4 and create-sequence step 1: get the iterator record.
const iterator = FunctionPrototypeCall(method, V);
const nextMethod = iterator?.next;
if (typeof nextMethod !== 'function') {
throw makeException(
'cannot be converted to sequence.',
options);
}
// Create-sequence step 2: initialize i to 0.
const idlSequence = [];
while (true) {
// Step 3.1: IteratorStepValue(iteratorRecord).
const next = FunctionPrototypeCall(nextMethod, iterator);
if (type(next) !== 'Object') {
throw makeException(
'cannot be converted to sequence.',
options);
}
// Step 3.2: IteratorComplete applies ToBoolean(done).
if (next.done) {
break;
}
// Step 3.3: convert next to an IDL value of type T.
const idlValue = converter(
next.value,
makeOptions(options, sequenceElementContext(idlSequence.length, options)),
);
// Step 3.4: store the value and advance i.
ArrayPrototypePush(idlSequence, idlValue);
}
return idlSequence;
};
}
/**
* Creates a converter for a Web IDL interface type.
* @see https://webidl.spec.whatwg.org/#js-interface
* @param {string} name Interface identifier.
* @param {object} prototype Interface prototype object.
* @returns {Converter}
*/
function createInterfaceConverter(name, prototype) {
return (V, options = kEmptyObject) => {
// Web IDL interface conversion step 1: return V if it implements I.
if (ObjectPrototypeIsPrototypeOf(prototype, V)) {
return V;
}
// Step 2: otherwise throw.
throw makeException(
`is not of type ${name}.`,
options);
};
}
/**
* Returns the ArrayBuffer or SharedArrayBuffer viewed by a typed array or
* DataView.
* @param {ArrayBufferView} V TypedArray or DataView.
* @returns {ArrayBuffer|SharedArrayBuffer}
*/
function getViewedArrayBuffer(V) {
// Buffer view conversion steps read V.[[ViewedArrayBuffer]].
return isTypedArray(V) ?
TypedArrayPrototypeGetBuffer(V) : DataViewPrototypeGetBuffer(V);
}
/**
* Validates [AllowShared] and [AllowResizable] backing-store constraints.
* @param {ArrayBuffer|SharedArrayBuffer} buffer Backing buffer.
* @param {ConversionOptions} options Conversion options.
* @returns {void}
*/
function validateBufferSourceBacking(buffer, options) {
let resizable;
try {
// ArrayBuffer.prototype.resizable is an ArrayBuffer brand check and the
// [AllowResizable] value we need. For SharedArrayBuffer-backed views it
// throws, which lets this path avoid a separate IsSharedArrayBuffer check.
// BufferSource has separate inline logic because [AllowShared] cannot be
// used with that typedef.
resizable = ArrayBufferPrototypeGetResizable(buffer);
} catch {
// ArrayBufferView conversion step 2: reject SharedArrayBuffer
// backing stores unless [AllowShared] is present.
if (!options.allowShared) {
throw makeException(
'is a view on a SharedArrayBuffer, which is not allowed.',
options);
}
// Step 3: reject non-fixed SharedArrayBuffer backing stores unless
// [AllowResizable] is present.
validateAllowGrowableSharedArrayBuffer(buffer, options);
return;
}
// ArrayBuffer conversion step 3 and ArrayBufferView conversion step 3:
// reject non-fixed ArrayBuffer backing stores unless [AllowResizable]
// is present.
validateAllowResizableArrayBuffer(resizable, options);
}
/**
* Validates the [AllowResizable] constraint for growable SharedArrayBuffer.
* @param {SharedArrayBuffer} buffer SharedArrayBuffer backing buffer.
* @param {ConversionOptions} options Conversion options.
* @returns {void}
*/
function validateAllowGrowableSharedArrayBuffer(buffer, options) {
// SharedArrayBuffer and ArrayBufferView conversion step 3:
// IsFixedLengthArrayBuffer(buffer) must be true without [AllowResizable].
// Do not use a primordial getter here. When this module is included in the
// startup snapshot, an early-captured SharedArrayBuffer.prototype.growable
// getter does not detect growable buffers created after deserialization.
// Lazily capturing the getter would work, but it would observe the runtime
// prototype at first comparison, so it would not be an actual primordial.
if (!options.allowResizable && buffer.growable) {
throw makeException(
'is backed by a growable SharedArrayBuffer, which is not allowed.',
options);
}
}
/**
* Validates the [AllowResizable] constraint for resizable ArrayBuffer.
* @param {boolean} resizable ArrayBuffer [[ArrayBufferResizable]] value.
* @param {ConversionOptions} options Conversion options.
* @returns {void}
*/
function validateAllowResizableArrayBuffer(resizable, options) {
// ArrayBuffer and ArrayBufferView conversion step 3:
// IsFixedLengthArrayBuffer(buffer) must be true without [AllowResizable].
// Read [[ArrayBufferResizable]] first so fixed buffers skip the options
// property lookup on this hot path.
if (resizable && !options.allowResizable) {
throw makeException(
'is backed by a resizable ArrayBuffer, which is not allowed.',
options);
}
}
/**
* Converts a JavaScript value to the IDL Uint8Array type.
* @param {any} V JavaScript value.
* @param {ConversionOptions} [options] Conversion options.
* @returns {Uint8Array}
*/
converters.Uint8Array = (V, options = kEmptyObject) => {
// Typed array conversion steps 1-2: T is Uint8Array, and V must
// have [[TypedArrayName]] equal to "Uint8Array".
if (!ArrayBufferIsView(V) ||
TypedArrayPrototypeGetSymbolToStringTag(V) !== 'Uint8Array') {
throw makeException(
'is not an Uint8Array object.',
options);
}
// Steps 3-4: validate [AllowShared] and [AllowResizable].
validateBufferSourceBacking(TypedArrayPrototypeGetBuffer(V), options);
// Step 5: return a reference to the same object.
return V;
};
/**
* Converts a JavaScript value to the IDL BufferSource typedef.
* @see https://webidl.spec.whatwg.org/#BufferSource
* @param {any} V JavaScript value.
* @param {ConversionOptions} [options] Conversion options.
* @returns {ArrayBuffer|ArrayBufferView<ArrayBuffer>}
*/
converters.BufferSource = (V, options = kEmptyObject) => {
// BufferSource is a typedef for (ArrayBufferView or ArrayBuffer).
// [AllowShared] cannot be used with BufferSource because the ArrayBuffer
// union branch does not support it. Use AllowSharedBufferSource instead.
if (ArrayBufferIsView(V)) {
const buffer = getViewedArrayBuffer(V);
// ArrayBufferView conversion steps 2-4: validate the viewed buffer
// and return a reference to the same view.
// Keep this logic inline instead of calling validateBufferSourceBacking().
// BufferSource conversion is hot, and this avoids a helper call while still
// using a primordial getter for the backing-buffer internal-slot check.
// Unlike validateBufferSourceBacking(), this intentionally ignores
// options.allowShared because [AllowShared] cannot be used with
// BufferSource.
let resizable;
try {
// ArrayBuffer.prototype.resizable is both the ArrayBuffer brand check
// and the step 3 value. It throws for SharedArrayBuffer, which lets this
// path reject SAB-backed views without an extra byteLength getter call.
resizable = ArrayBufferPrototypeGetResizable(buffer);
} catch {
throw makeException(
'is a view on a SharedArrayBuffer, which is not allowed.',
options);
}
if (resizable && !options.allowResizable) {
throw makeException(
'is backed by a resizable ArrayBuffer, which is not allowed.',
options);
}
return V;
}
// ArrayBuffer conversion steps 1-2: require a non-shared ArrayBuffer.
// Use the primordial resizable getter as both the ArrayBuffer brand check
// and the step 3 value. This avoids isArrayBuffer(V) followed by another
// getter call, and rejects SharedArrayBuffer on this union branch.
let resizable;
try {
resizable = ArrayBufferPrototypeGetResizable(V);