webidl.js 18 KB

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