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