codekingpro/portable-devtools
114k
1'use strict'
2
3const { types, inspect } = require('node:util')
4const { markAsUncloneable } = require('node:worker_threads')
5const { toUSVString } = require('../../core/util')
6
7/** @type {import('../../../types/webidl').Webidl} */
8const webidl = {}
9webidl.converters = {}
10webidl.util = {}
11webidl.errors = {}
12
13webidl.errors.exception = function (message) {
14 return new TypeError(`${message.header}: ${message.message}`)
15}
16
17webidl.errors.conversionFailed = function (context) {
18 const plural = context.types.length === 1 ? '' : ' one of'
19 const message =
20 `${context.argument} could not be converted to` +
21 `${plural}: ${context.types.join(', ')}.`
22
23 return webidl.errors.exception({
24 header: context.prefix,
25 message
26 })
27}
28
29webidl.errors.invalidArgument = function (context) {
30 return webidl.errors.exception({
31 header: context.prefix,
32 message: `"${context.value}" is an invalid ${context.type}.`
33 })
34}
35
36// https://webidl.spec.whatwg.org/#implements
37webidl.brandCheck = function (V, I, opts) {
38 if (opts?.strict !== false) {
39 if (!(V instanceof I)) {
40 const err = new TypeError('Illegal invocation')
41 err.code = 'ERR_INVALID_THIS' // node compat.
42 throw err
43 }
44 } else {
45 if (V?.[Symbol.toStringTag] !== I.prototype[Symbol.toStringTag]) {
46 const err = new TypeError('Illegal invocation')
47 err.code = 'ERR_INVALID_THIS' // node compat.
48 throw err
49 }
50 }
51}
52
53webidl.argumentLengthCheck = function ({ length }, min, ctx) {
54 if (length < min) {
55 throw webidl.errors.exception({
56 message: `${min} argument${min !== 1 ? 's' : ''} required, ` +
57 `but${length ? ' only' : ''} ${length} found.`,
58 header: ctx
59 })
60 }
61}
62
63webidl.illegalConstructor = function () {
64 throw webidl.errors.exception({
65 header: 'TypeError',
66 message: 'Illegal constructor'
67 })
68}
69
70// https://tc39.es/ecma262/#sec-ecmascript-data-types-and-values
71webidl.util.Type = function (V) {
72 switch (typeof V) {
73 case 'undefined': return 'Undefined'
74 case 'boolean': return 'Boolean'
75 case 'string': return 'String'
76 case 'symbol': return 'Symbol'
77 case 'number': return 'Number'
78 case 'bigint': return 'BigInt'
79 case 'function':
80 case 'object': {
81 if (V === null) {
82 return 'Null'
83 }
84
85 return 'Object'
86 }
87 }
88}
89
90webidl.util.markAsUncloneable = markAsUncloneable || (() => {})
91// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint
92webidl.util.ConvertToInt = function (V, bitLength, signedness, opts) {
93 let upperBound
94 let lowerBound
95
96 // 1. If bitLength is 64, then:
97 if (bitLength === 64) {
98 // 1. Let upperBound be 2^53 − 1.
99 upperBound = Math.pow(2, 53) - 1
100
101 // 2. If signedness is "unsigned", then let lowerBound be 0.
102 if (signedness === 'unsigned') {
103 lowerBound = 0
104 } else {
105 // 3. Otherwise let lowerBound be −2^53 + 1.
106 lowerBound = Math.pow(-2, 53) + 1
107 }
108 } else if (signedness === 'unsigned') {
109 // 2. Otherwise, if signedness is "unsigned", then:
110
111 // 1. Let lowerBound be 0.
112 lowerBound = 0
113
114 // 2. Let upperBound be 2^bitLength − 1.
115 upperBound = Math.pow(2, bitLength) - 1
116 } else {
117 // 3. Otherwise:
118
119 // 1. Let lowerBound be -2^bitLength − 1.
120 lowerBound = Math.pow(-2, bitLength) - 1
121
122 // 2. Let upperBound be 2^bitLength − 1 − 1.
123 upperBound = Math.pow(2, bitLength - 1) - 1
124 }
125
126 // 4. Let x be ? ToNumber(V).
127 let x = Number(V)
128
129 // 5. If x is −0, then set x to +0.
130 if (x === 0) {
131 x = 0
132 }
133
134 // 6. If the conversion is to an IDL type associated
135 // with the [EnforceRange] extended attribute, then:
136 if (opts?.enforceRange === true) {
137 // 1. If x is NaN, +∞, or −∞, then throw a TypeError.
138 if (
139 Number.isNaN(x) ||
140 x === Number.POSITIVE_INFINITY ||
141 x === Number.NEGATIVE_INFINITY
142 ) {
143 throw webidl.errors.exception({
144 header: 'Integer conversion',
145 message: `Could not convert ${webidl.util.Stringify(V)} to an integer.`
146 })
147 }
148
149 // 2. Set x to IntegerPart(x).
150 x = webidl.util.IntegerPart(x)
151
152 // 3. If x < lowerBound or x > upperBound, then
153 // throw a TypeError.
154 if (x < lowerBound || x > upperBound) {
155 throw webidl.errors.exception({
156 header: 'Integer conversion',
157 message: `Value must be between ${lowerBound}-${upperBound}, got ${x}.`
158 })
159 }
160
161 // 4. Return x.
162 return x
163 }
164
165 // 7. If x is not NaN and the conversion is to an IDL
166 // type associated with the [Clamp] extended
167 // attribute, then:
168 if (!Number.isNaN(x) && opts?.clamp === true) {
169 // 1. Set x to min(max(x, lowerBound), upperBound).
170 x = Math.min(Math.max(x, lowerBound), upperBound)
171
172 // 2. Round x to the nearest integer, choosing the
173 // even integer if it lies halfway between two,
174 // and choosing +0 rather than −0.
175 if (Math.floor(x) % 2 === 0) {
176 x = Math.floor(x)
177 } else {
178 x = Math.ceil(x)
179 }
180
181 // 3. Return x.
182 return x
183 }
184
185 // 8. If x is NaN, +0, +∞, or −∞, then return +0.
186 if (
187 Number.isNaN(x) ||
188 (x === 0 && Object.is(0, x)) ||
189 x === Number.POSITIVE_INFINITY ||
190 x === Number.NEGATIVE_INFINITY
191 ) {
192 return 0
193 }
194
195 // 9. Set x to IntegerPart(x).
196 x = webidl.util.IntegerPart(x)
197
198 // 10. Set x to x modulo 2^bitLength.
199 x = x % Math.pow(2, bitLength)
200
201 // 11. If signedness is "signed" and x ≥ 2^bitLength − 1,
202 // then return x − 2^bitLength.
203 if (signedness === 'signed' && x >= Math.pow(2, bitLength) - 1) {
204 return x - Math.pow(2, bitLength)
205 }
206
207 // 12. Otherwise, return x.
208 return x
209}
210
211// https://webidl.spec.whatwg.org/#abstract-opdef-integerpart
212webidl.util.IntegerPart = function (n) {
213 // 1. Let r be floor(abs(n)).
214 const r = Math.floor(Math.abs(n))
215
216 // 2. If n < 0, then return -1 × r.
217 if (n < 0) {
218 return -1 * r
219 }
220
221 // 3. Otherwise, return r.
222 return r
223}
224
225webidl.util.Stringify = function (V) {
226 const type = webidl.util.Type(V)
227
228 switch (type) {
229 case 'Symbol':
230 return `Symbol(${V.description})`
231 case 'Object':
232 return inspect(V)
233 case 'String':
234 return `"${V}"`
235 default:
236 return `${V}`
237 }
238}
239
240// https://webidl.spec.whatwg.org/#es-sequence
241webidl.sequenceConverter = function (converter) {
242 return (V, prefix, argument, Iterable) => {
243 // 1. If Type(V) is not Object, throw a TypeError.
244 if (webidl.util.Type(V) !== 'Object') {
245 throw webidl.errors.exception({
246 header: prefix,
247 message: `${argument} (${webidl.util.Stringify(V)}) is not iterable.`
248 })
249 }
250
251 // 2. Let method be ? GetMethod(V, @@iterator).
252 /** @type {Generator} */
253 const method = typeof Iterable === 'function' ? Iterable() : V?.[Symbol.iterator]?.()
254 const seq = []
255 let index = 0
256
257 // 3. If method is undefined, throw a TypeError.
258 if (
259 method === undefined ||
260 typeof method.next !== 'function'
261 ) {
262 throw webidl.errors.exception({
263 header: prefix,
264 message: `${argument} is not iterable.`
265 })
266 }
267
268 // https://webidl.spec.whatwg.org/#create-sequence-from-iterable
269 while (true) {
270 const { done, value } = method.next()
271
272 if (done) {
273 break
274 }
275
276 seq.push(converter(value, prefix, `${argument}[${index++}]`))
277 }
278
279 return seq
280 }
281}
282
283// https://webidl.spec.whatwg.org/#es-to-record
284webidl.recordConverter = function (keyConverter, valueConverter) {
285 return (O, prefix, argument) => {
286 // 1. If Type(O) is not Object, throw a TypeError.
287 if (webidl.util.Type(O) !== 'Object') {
288 throw webidl.errors.exception({
289 header: prefix,
290 message: `${argument} ("${webidl.util.Type(O)}") is not an Object.`
291 })
292 }
293
294 // 2. Let result be a new empty instance of record<K, V>.
295 const result = {}
296
297 if (!types.isProxy(O)) {
298 // 1. Let desc be ? O.[[GetOwnProperty]](key).
299 const keys = [...Object.getOwnPropertyNames(O), ...Object.getOwnPropertySymbols(O)]
300
301 for (const key of keys) {
302 // 1. Let typedKey be key converted to an IDL value of type K.
303 const typedKey = keyConverter(key, prefix, argument)
304
305 // 2. Let value be ? Get(O, key).
306 // 3. Let typedValue be value converted to an IDL value of type V.
307 const typedValue = valueConverter(O[key], prefix, argument)
308
309 // 4. Set result[typedKey] to typedValue.
310 result[typedKey] = typedValue
311 }
312
313 // 5. Return result.
314 return result
315 }
316
317 // 3. Let keys be ? O.[[OwnPropertyKeys]]().
318 const keys = Reflect.ownKeys(O)
319
320 // 4. For each key of keys.
321 for (const key of keys) {
322 // 1. Let desc be ? O.[[GetOwnProperty]](key).
323 const desc = Reflect.getOwnPropertyDescriptor(O, key)
324
325 // 2. If desc is not undefined and desc.[[Enumerable]] is true:
326 if (desc?.enumerable) {
327 // 1. Let typedKey be key converted to an IDL value of type K.
328 const typedKey = keyConverter(key, prefix, argument)
329
330 // 2. Let value be ? Get(O, key).
331 // 3. Let typedValue be value converted to an IDL value of type V.
332 const typedValue = valueConverter(O[key], prefix, argument)
333
334 // 4. Set result[typedKey] to typedValue.
335 result[typedKey] = typedValue
336 }
337 }
338
339 // 5. Return result.
340 return result
341 }
342}
343
344webidl.interfaceConverter = function (i) {
345 return (V, prefix, argument, opts) => {
346 if (opts?.strict !== false && !(V instanceof i)) {
347 throw webidl.errors.exception({
348 header: prefix,
349 message: `Expected ${argument} ("${webidl.util.Stringify(V)}") to be an instance of ${i.name}.`
350 })
351 }
352
353 return V
354 }
355}
356
357webidl.dictionaryConverter = function (converters) {
358 return (dictionary, prefix, argument) => {
359 const type = webidl.util.Type(dictionary)
360 const dict = {}
361
362 if (type === 'Null' || type === 'Undefined') {
363 return dict
364 } else if (type !== 'Object') {
365 throw webidl.errors.exception({
366 header: prefix,
367 message: `Expected ${dictionary} to be one of: Null, Undefined, Object.`
368 })
369 }
370
371 for (const options of converters) {
372 const { key, defaultValue, required, converter } = options
373
374 if (required === true) {
375 if (!Object.hasOwn(dictionary, key)) {
376 throw webidl.errors.exception({
377 header: prefix,
378 message: `Missing required key "${key}".`
379 })
380 }
381 }
382
383 let value = dictionary[key]
384 const hasDefault = Object.hasOwn(options, 'defaultValue')
385
386 // Only use defaultValue if value is undefined and
387 // a defaultValue options was provided.
388 if (hasDefault && value !== null) {
389 value ??= defaultValue()
390 }
391
392 // A key can be optional and have no default value.
393 // When this happens, do not perform a conversion,
394 // and do not assign the key a value.
395 if (required || hasDefault || value !== undefined) {
396 value = converter(value, prefix, `${argument}.${key}`)
397
398 if (
399 options.allowedValues &&
400 !options.allowedValues.includes(value)
401 ) {
402 throw webidl.errors.exception({
403 header: prefix,
404 message: `${value} is not an accepted type. Expected one of ${options.allowedValues.join(', ')}.`
405 })
406 }
407
408 dict[key] = value
409 }
410 }
411
412 return dict
413 }
414}
415
416webidl.nullableConverter = function (converter) {
417 return (V, prefix, argument) => {
418 if (V === null) {
419 return V
420 }
421
422 return converter(V, prefix, argument)
423 }
424}
425
426// https://webidl.spec.whatwg.org/#es-DOMString
427webidl.converters.DOMString = function (V, prefix, argument, opts) {
428 // 1. If V is null and the conversion is to an IDL type
429 // associated with the [LegacyNullToEmptyString]
430 // extended attribute, then return the DOMString value
431 // that represents the empty string.
432 if (V === null && opts?.legacyNullToEmptyString) {
433 return ''
434 }
435
436 // 2. Let x be ? ToString(V).
437 if (typeof V === 'symbol') {
438 throw webidl.errors.exception({
439 header: prefix,
440 message: `${argument} is a symbol, which cannot be converted to a DOMString.`
441 })
442 }
443
444 // 3. Return the IDL DOMString value that represents the
445 // same sequence of code units as the one the
446 // ECMAScript String value x represents.
447 return String(V)
448}
449
450// https://webidl.spec.whatwg.org/#es-ByteString
451webidl.converters.ByteString = function (V, prefix, argument) {
452 // 1. Let x be ? ToString(V).
453 // Note: DOMString converter perform ? ToString(V)
454 const x = webidl.converters.DOMString(V, prefix, argument)
455
456 // 2. If the value of any element of x is greater than
457 // 255, then throw a TypeError.
458 for (let index = 0; index < x.length; index++) {
459 if (x.charCodeAt(index) > 255) {
460 throw new TypeError(
461 'Cannot convert argument to a ByteString because the character at ' +
462 `index ${index} has a value of ${x.charCodeAt(index)} which is greater than 255.`
463 )
464 }
465 }
466
467 // 3. Return an IDL ByteString value whose length is the
468 // length of x, and where the value of each element is
469 // the value of the corresponding element of x.
470 return x
471}
472
473// https://webidl.spec.whatwg.org/#es-USVString
474// TODO: rewrite this so we can control the errors thrown
475webidl.converters.USVString = toUSVString
476
477// https://webidl.spec.whatwg.org/#es-boolean
478webidl.converters.boolean = function (V) {
479 // 1. Let x be the result of computing ToBoolean(V).
480 const x = Boolean(V)
481
482 // 2. Return the IDL boolean value that is the one that represents
483 // the same truth value as the ECMAScript Boolean value x.
484 return x
485}
486
487// https://webidl.spec.whatwg.org/#es-any
488webidl.converters.any = function (V) {
489 return V
490}
491
492// https://webidl.spec.whatwg.org/#es-long-long
493webidl.converters['long long'] = function (V, prefix, argument) {
494 // 1. Let x be ? ConvertToInt(V, 64, "signed").
495 const x = webidl.util.ConvertToInt(V, 64, 'signed', undefined, prefix, argument)
496
497 // 2. Return the IDL long long value that represents
498 // the same numeric value as x.
499 return x
500}
501
502// https://webidl.spec.whatwg.org/#es-unsigned-long-long
503webidl.converters['unsigned long long'] = function (V, prefix, argument) {
504 // 1. Let x be ? ConvertToInt(V, 64, "unsigned").
505 const x = webidl.util.ConvertToInt(V, 64, 'unsigned', undefined, prefix, argument)
506
507 // 2. Return the IDL unsigned long long value that
508 // represents the same numeric value as x.
509 return x
510}
511
512// https://webidl.spec.whatwg.org/#es-unsigned-long
513webidl.converters['unsigned long'] = function (V, prefix, argument) {
514 // 1. Let x be ? ConvertToInt(V, 32, "unsigned").
515 const x = webidl.util.ConvertToInt(V, 32, 'unsigned', undefined, prefix, argument)
516
517 // 2. Return the IDL unsigned long value that
518 // represents the same numeric value as x.
519 return x
520}
521
522// https://webidl.spec.whatwg.org/#es-unsigned-short
523webidl.converters['unsigned short'] = function (V, prefix, argument, opts) {
524 // 1. Let x be ? ConvertToInt(V, 16, "unsigned").
525 const x = webidl.util.ConvertToInt(V, 16, 'unsigned', opts, prefix, argument)
526
527 // 2. Return the IDL unsigned short value that represents
528 // the same numeric value as x.
529 return x
530}
531
532// https://webidl.spec.whatwg.org/#idl-ArrayBuffer
533webidl.converters.ArrayBuffer = function (V, prefix, argument, opts) {
534 // 1. If Type(V) is not Object, or V does not have an
535 // [[ArrayBufferData]] internal slot, then throw a
536 // TypeError.
537 // see: https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-instances
538 // see: https://tc39.es/ecma262/#sec-properties-of-the-sharedarraybuffer-instances
539 if (
540 webidl.util.Type(V) !== 'Object' ||
541 !types.isAnyArrayBuffer(V)
542 ) {
543 throw webidl.errors.conversionFailed({
544 prefix,
545 argument: `${argument} ("${webidl.util.Stringify(V)}")`,
546 types: ['ArrayBuffer']
547 })
548 }
549
550 // 2. If the conversion is not to an IDL type associated
551 // with the [AllowShared] extended attribute, and
552 // IsSharedArrayBuffer(V) is true, then throw a
553 // TypeError.
554 if (opts?.allowShared === false && types.isSharedArrayBuffer(V)) {
555 throw webidl.errors.exception({
556 header: 'ArrayBuffer',
557 message: 'SharedArrayBuffer is not allowed.'
558 })
559 }
560
561 // 3. If the conversion is not to an IDL type associated
562 // with the [AllowResizable] extended attribute, and
563 // IsResizableArrayBuffer(V) is true, then throw a
564 // TypeError.
565 if (V.resizable || V.growable) {
566 throw webidl.errors.exception({
567 header: 'ArrayBuffer',
568 message: 'Received a resizable ArrayBuffer.'
569 })
570 }
571
572 // 4. Return the IDL ArrayBuffer value that is a
573 // reference to the same object as V.
574 return V
575}
576
577webidl.converters.TypedArray = function (V, T, prefix, name, opts) {
578 // 1. Let T be the IDL type V is being converted to.
579
580 // 2. If Type(V) is not Object, or V does not have a
581 // [[TypedArrayName]] internal slot with a value
582 // equal to T’s name, then throw a TypeError.
583 if (
584 webidl.util.Type(V) !== 'Object' ||
585 !types.isTypedArray(V) ||
586 V.constructor.name !== T.name
587 ) {
588 throw webidl.errors.conversionFailed({
589 prefix,
590 argument: `${name} ("${webidl.util.Stringify(V)}")`,
591 types: [T.name]
592 })
593 }
594
595 // 3. If the conversion is not to an IDL type associated
596 // with the [AllowShared] extended attribute, and
597 // IsSharedArrayBuffer(V.[[ViewedArrayBuffer]]) is
598 // true, then throw a TypeError.
599 if (opts?.allowShared === false && types.isSharedArrayBuffer(V.buffer)) {
600 throw webidl.errors.exception({
601 header: 'ArrayBuffer',
602 message: 'SharedArrayBuffer is not allowed.'
603 })
604 }
605
606 // 4. If the conversion is not to an IDL type associated
607 // with the [AllowResizable] extended attribute, and
608 // IsResizableArrayBuffer(V.[[ViewedArrayBuffer]]) is
609 // true, then throw a TypeError.
610 if (V.buffer.resizable || V.buffer.growable) {
611 throw webidl.errors.exception({
612 header: 'ArrayBuffer',
613 message: 'Received a resizable ArrayBuffer.'
614 })
615 }
616
617 // 5. Return the IDL value of type T that is a reference
618 // to the same object as V.
619 return V
620}
621
622webidl.converters.DataView = function (V, prefix, name, opts) {
623 // 1. If Type(V) is not Object, or V does not have a
624 // [[DataView]] internal slot, then throw a TypeError.
625 if (webidl.util.Type(V) !== 'Object' || !types.isDataView(V)) {
626 throw webidl.errors.exception({
627 header: prefix,
628 message: `${name} is not a DataView.`
629 })
630 }
631
632 // 2. If the conversion is not to an IDL type associated
633 // with the [AllowShared] extended attribute, and
634 // IsSharedArrayBuffer(V.[[ViewedArrayBuffer]]) is true,
635 // then throw a TypeError.
636 if (opts?.allowShared === false && types.isSharedArrayBuffer(V.buffer)) {
637 throw webidl.errors.exception({
638 header: 'ArrayBuffer',
639 message: 'SharedArrayBuffer is not allowed.'
640 })
641 }
642
643 // 3. If the conversion is not to an IDL type associated
644 // with the [AllowResizable] extended attribute, and
645 // IsResizableArrayBuffer(V.[[ViewedArrayBuffer]]) is
646 // true, then throw a TypeError.
647 if (V.buffer.resizable || V.buffer.growable) {
648 throw webidl.errors.exception({
649 header: 'ArrayBuffer',
650 message: 'Received a resizable ArrayBuffer.'
651 })
652 }
653
654 // 4. Return the IDL DataView value that is a reference
655 // to the same object as V.
656 return V
657}
658
659// https://webidl.spec.whatwg.org/#BufferSource
660webidl.converters.BufferSource = function (V, prefix, name, opts) {
661 if (types.isAnyArrayBuffer(V)) {
662 return webidl.converters.ArrayBuffer(V, prefix, name, { ...opts, allowShared: false })
663 }
664
665 if (types.isTypedArray(V)) {
666 return webidl.converters.TypedArray(V, V.constructor, prefix, name, { ...opts, allowShared: false })
667 }
668
669 if (types.isDataView(V)) {
670 return webidl.converters.DataView(V, prefix, name, { ...opts, allowShared: false })
671 }
672
673 throw webidl.errors.conversionFailed({
674 prefix,
675 argument: `${name} ("${webidl.util.Stringify(V)}")`,
676 types: ['BufferSource']
677 })
678}
679
680webidl.converters['sequence<ByteString>'] = webidl.sequenceConverter(
681 webidl.converters.ByteString
682)
683
684webidl.converters['sequence<sequence<ByteString>>'] = webidl.sequenceConverter(
685 webidl.converters['sequence<ByteString>']
686)
687
688webidl.converters['record<ByteString, ByteString>'] = webidl.recordConverter(
689 webidl.converters.ByteString,
690 webidl.converters.ByteString
691)
692
693module.exports = {
694 webidl
695}
696 