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