'use strict'; const { types, inspect } = require('node:util'); const { markAsUncloneable } = require('node:worker_threads'); const { toUSVString } = require('../../core/util'); const UNDEFINED = 1; const BOOLEAN = 2; const STRING = 3; const SYMBOL = 4; const NUMBER = 5; const BIGINT = 6; const NULL = 7; const OBJECT = 8; // function and object const FunctionPrototypeSymbolHasInstance = Function.call.bind( Function.prototype[Symbol.hasInstance] ); /** @type {import('../../../types/webidl').Webidl} */ const webidl = { converters: {}, util: {}, errors: {}, is: {}, }; webidl.errors.exception = function (message) { return new TypeError(`${message.header}: ${message.message}`); }; webidl.errors.conversionFailed = function (context) { const plural = context.types.length === 1 ? '' : ' one of'; const message = `${context.argument} could not be converted to` + `${plural}: ${context.types.join(', ')}.`; return webidl.errors.exception({ header: context.prefix, message, }); }; webidl.errors.invalidArgument = function (context) { return webidl.errors.exception({ header: context.prefix, message: `"${context.value}" is an invalid ${context.type}.`, }); }; // https://webidl.spec.whatwg.org/#implements webidl.brandCheck = function (V, I) { if (!FunctionPrototypeSymbolHasInstance(I, V)) { const err = new TypeError('Illegal invocation'); err.code = 'ERR_INVALID_THIS'; // node compat. throw err; } }; webidl.brandCheckMultiple = function (List) { const prototypes = List.map((c) => webidl.util.MakeTypeAssertion(c)); return (V) => { if (prototypes.every((typeCheck) => !typeCheck(V))) { const err = new TypeError('Illegal invocation'); err.code = 'ERR_INVALID_THIS'; // node compat. throw err; } }; }; webidl.argumentLengthCheck = function ({ length }, min, ctx) { if (length < min) { throw webidl.errors.exception({ message: `${min} argument${min !== 1 ? 's' : ''} required, ` + `but${length ? ' only' : ''} ${length} found.`, header: ctx, }); } }; webidl.illegalConstructor = function () { throw webidl.errors.exception({ header: 'TypeError', message: 'Illegal constructor', }); }; webidl.util.MakeTypeAssertion = function (I) { return (O) => FunctionPrototypeSymbolHasInstance(I, O); }; // https://tc39.es/ecma262/#sec-ecmascript-data-types-and-values webidl.util.Type = function (V) { 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 'function': case 'object': { if (V === null) { return NULL; } return OBJECT; } } }; webidl.util.Types = { UNDEFINED, BOOLEAN, STRING, SYMBOL, NUMBER, BIGINT, NULL, OBJECT, }; webidl.util.TypeValueToString = function (o) { switch (webidl.util.Type(o)) { 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 NULL: return 'Null'; case OBJECT: return 'Object'; } }; webidl.util.markAsUncloneable = markAsUncloneable || (() => {}); // https://webidl.spec.whatwg.org/#abstract-opdef-converttoint webidl.util.ConvertToInt = function (V, bitLength, signedness, opts) { let upperBound; let lowerBound; // 1. If bitLength is 64, then: if (bitLength === 64) { // 1. Let upperBound be 2^53 − 1. upperBound = Math.pow(2, 53) - 1; // 2. If signedness is "unsigned", then let lowerBound be 0. if (signedness === 'unsigned') { lowerBound = 0; } else { // 3. Otherwise let lowerBound be −2^53 + 1. lowerBound = Math.pow(-2, 53) + 1; } } else if (signedness === 'unsigned') { // 2. Otherwise, if signedness is "unsigned", then: // 1. Let lowerBound be 0. lowerBound = 0; // 2. Let upperBound be 2^bitLength − 1. upperBound = Math.pow(2, bitLength) - 1; } else { // 3. Otherwise: // 1. Let lowerBound be -2^bitLength − 1. lowerBound = Math.pow(-2, bitLength) - 1; // 2. Let upperBound be 2^bitLength − 1 − 1. upperBound = Math.pow(2, bitLength - 1) - 1; } // 4. Let x be ? ToNumber(V). let x = Number(V); // 5. If x is −0, then set x to +0. if (x === 0) { x = 0; } // 6. If the conversion is to an IDL type associated // with the [EnforceRange] extended attribute, then: if (opts?.enforceRange === true) { // 1. If x is NaN, +∞, or −∞, then throw a TypeError. if ( Number.isNaN(x) || x === Number.POSITIVE_INFINITY || x === Number.NEGATIVE_INFINITY ) { throw webidl.errors.exception({ header: 'Integer conversion', message: `Could not convert ${webidl.util.Stringify(V)} to an integer.`, }); } // 2. Set x to IntegerPart(x). x = webidl.util.IntegerPart(x); // 3. If x < lowerBound or x > upperBound, then // throw a TypeError. if (x < lowerBound || x > upperBound) { throw webidl.errors.exception({ header: 'Integer conversion', message: `Value must be between ${lowerBound}-${upperBound}, got ${x}.`, }); } // 4. Return x. return x; } // 7. If x is not NaN and the conversion is to an IDL // type associated with the [Clamp] extended // attribute, then: if (!Number.isNaN(x) && opts?.clamp === true) { // 1. Set x to min(max(x, lowerBound), upperBound). x = Math.min(Math.max(x, lowerBound), upperBound); // 2. Round x to the nearest integer, choosing the // even integer if it lies halfway between two, // and choosing +0 rather than −0. if (Math.floor(x) % 2 === 0) { x = Math.floor(x); } else { x = Math.ceil(x); } // 3. Return x. return x; } // 8. If x is NaN, +0, +∞, or −∞, then return +0. if ( Number.isNaN(x) || (x === 0 && Object.is(0, x)) || x === Number.POSITIVE_INFINITY || x === Number.NEGATIVE_INFINITY ) { return 0; } // 9. Set x to IntegerPart(x). x = webidl.util.IntegerPart(x); // 10. Set x to x modulo 2^bitLength. x = x % Math.pow(2, bitLength); // 11. If signedness is "signed" and x ≥ 2^bitLength − 1, // then return x − 2^bitLength. if (signedness === 'signed' && x >= Math.pow(2, bitLength) - 1) { return x - Math.pow(2, bitLength); } // 12. Otherwise, return x. return x; }; // https://webidl.spec.whatwg.org/#abstract-opdef-integerpart webidl.util.IntegerPart = function (n) { // 1. Let r be floor(abs(n)). const r = Math.floor(Math.abs(n)); // 2. If n < 0, then return -1 × r. if (n < 0) { return -1 * r; } // 3. Otherwise, return r. return r; }; webidl.util.Stringify = function (V) { const type = webidl.util.Type(V); switch (type) { case SYMBOL: return `Symbol(${V.description})`; case OBJECT: return inspect(V); case STRING: return `"${V}"`; default: return `${V}`; } }; // https://webidl.spec.whatwg.org/#es-sequence webidl.sequenceConverter = function (converter) { return (V, prefix, argument, Iterable) => { // 1. If Type(V) is not Object, throw a TypeError. if (webidl.util.Type(V) !== OBJECT) { throw webidl.errors.exception({ header: prefix, message: `${argument} (${webidl.util.Stringify(V)}) is not iterable.`, }); } // 2. Let method be ? GetMethod(V, @@iterator). /** @type {Generator} */ const method = typeof Iterable === 'function' ? Iterable() : V?.[Symbol.iterator]?.(); const seq = []; let index = 0; // 3. If method is undefined, throw a TypeError. if (method === undefined || typeof method.next !== 'function') { throw webidl.errors.exception({ header: prefix, message: `${argument} is not iterable.`, }); } // https://webidl.spec.whatwg.org/#create-sequence-from-iterable while (true) { const { done, value } = method.next(); if (done) { break; } seq.push(converter(value, prefix, `${argument}[${index++}]`)); } return seq; }; }; // https://webidl.spec.whatwg.org/#es-to-record webidl.recordConverter = function (keyConverter, valueConverter) { return (O, prefix, argument) => { // 1. If Type(O) is not Object, throw a TypeError. if (webidl.util.Type(O) !== OBJECT) { throw webidl.errors.exception({ header: prefix, message: `${argument} ("${webidl.util.TypeValueToString(O)}") is not an Object.`, }); } // 2. Let result be a new empty instance of record. const result = {}; if (!types.isProxy(O)) { // 1. Let desc be ? O.[[GetOwnProperty]](key). const keys = [ ...Object.getOwnPropertyNames(O), ...Object.getOwnPropertySymbols(O), ]; for (const key of keys) { const keyName = webidl.util.Stringify(key); // 1. Let typedKey be key converted to an IDL value of type K. const typedKey = keyConverter( key, prefix, `Key ${keyName} in ${argument}` ); // 2. Let value be ? Get(O, key). // 3. Let typedValue be value converted to an IDL value of type V. const typedValue = valueConverter( O[key], prefix, `${argument}[${keyName}]` ); // 4. Set result[typedKey] to typedValue. result[typedKey] = typedValue; } // 5. Return result. return result; } // 3. Let keys be ? O.[[OwnPropertyKeys]](). const keys = Reflect.ownKeys(O); // 4. For each key of keys. for (const key of keys) { // 1. Let desc be ? O.[[GetOwnProperty]](key). const desc = Reflect.getOwnPropertyDescriptor(O, key); // 2. If desc is not undefined and desc.[[Enumerable]] is true: if (desc?.enumerable) { // 1. Let typedKey be key converted to an IDL value of type K. const typedKey = keyConverter(key, prefix, argument); // 2. Let value be ? Get(O, key). // 3. Let typedValue be value converted to an IDL value of type V. const typedValue = valueConverter(O[key], prefix, argument); // 4. Set result[typedKey] to typedValue. result[typedKey] = typedValue; } } // 5. Return result. return result; }; }; webidl.interfaceConverter = function (TypeCheck, name) { return (V, prefix, argument) => { if (!TypeCheck(V)) { throw webidl.errors.exception({ header: prefix, message: `Expected ${argument} ("${webidl.util.Stringify(V)}") to be an instance of ${name}.`, }); } return V; }; }; webidl.dictionaryConverter = function (converters) { return (dictionary, prefix, argument) => { const dict = {}; if (dictionary != null && webidl.util.Type(dictionary) !== OBJECT) { throw webidl.errors.exception({ header: prefix, message: `Expected ${dictionary} to be one of: Null, Undefined, Object.`, }); } for (const options of converters) { const { key, defaultValue, required, converter } = options; if (required === true) { if (dictionary == null || !Object.hasOwn(dictionary, key)) { throw webidl.errors.exception({ header: prefix, message: `Missing required key "${key}".`, }); } } let value = dictionary?.[key]; const hasDefault = defaultValue !== undefined; // Only use defaultValue if value is undefined and // a defaultValue options was provided. if (hasDefault && value === undefined) { value = defaultValue(); } // A key can be optional and have no default value. // When this happens, do not perform a conversion, // and do not assign the key a value. if (required || hasDefault || value !== undefined) { value = converter(value, prefix, `${argument}.${key}`); if (options.allowedValues && !options.allowedValues.includes(value)) { throw webidl.errors.exception({ header: prefix, message: `${value} is not an accepted type. Expected one of ${options.allowedValues.join(', ')}.`, }); } dict[key] = value; } } return dict; }; }; webidl.nullableConverter = function (converter) { return (V, prefix, argument) => { if (V === null) { return V; } return converter(V, prefix, argument); }; }; webidl.is.ReadableStream = webidl.util.MakeTypeAssertion(ReadableStream); webidl.is.Blob = webidl.util.MakeTypeAssertion(Blob); webidl.is.URLSearchParams = webidl.util.MakeTypeAssertion(URLSearchParams); webidl.is.File = webidl.util.MakeTypeAssertion( globalThis.File ?? require('node:buffer').File ); webidl.is.URL = webidl.util.MakeTypeAssertion(URL); webidl.is.AbortSignal = webidl.util.MakeTypeAssertion(AbortSignal); webidl.is.MessagePort = webidl.util.MakeTypeAssertion(MessagePort); // https://webidl.spec.whatwg.org/#es-DOMString webidl.converters.DOMString = function (V, prefix, argument, opts) { // 1. If V is null and the conversion is to an IDL type // associated with the [LegacyNullToEmptyString] // extended attribute, then return the DOMString value // that represents the empty string. if (V === null && opts?.legacyNullToEmptyString) { return ''; } // 2. Let x be ? ToString(V). if (typeof V === 'symbol') { throw webidl.errors.exception({ header: prefix, message: `${argument} is a symbol, which cannot be converted to a DOMString.`, }); } // 3. Return the IDL DOMString value that represents the // same sequence of code units as the one the // ECMAScript String value x represents. return String(V); }; // https://webidl.spec.whatwg.org/#es-ByteString webidl.converters.ByteString = function (V, prefix, argument) { // 1. Let x be ? ToString(V). if (typeof V === 'symbol') { throw webidl.errors.exception({ header: prefix, message: `${argument} is a symbol, which cannot be converted to a ByteString.`, }); } const x = String(V); // 2. If the value of any element of x is greater than // 255, then throw a TypeError. for (let index = 0; index < x.length; index++) { if (x.charCodeAt(index) > 255) { throw new TypeError( 'Cannot convert argument to a ByteString because the character at ' + `index ${index} has a value of ${x.charCodeAt(index)} which is greater than 255.` ); } } // 3. Return an IDL ByteString value whose length is the // length of x, and where the value of each element is // the value of the corresponding element of x. return x; }; // https://webidl.spec.whatwg.org/#es-USVString // TODO: rewrite this so we can control the errors thrown webidl.converters.USVString = toUSVString; // https://webidl.spec.whatwg.org/#es-boolean webidl.converters.boolean = function (V) { // 1. Let x be the result of computing ToBoolean(V). const x = Boolean(V); // 2. Return the IDL boolean value that is the one that represents // the same truth value as the ECMAScript Boolean value x. return x; }; // https://webidl.spec.whatwg.org/#es-any webidl.converters.any = function (V) { return V; }; // https://webidl.spec.whatwg.org/#es-long-long webidl.converters['long long'] = function (V, prefix, argument) { // 1. Let x be ? ConvertToInt(V, 64, "signed"). const x = webidl.util.ConvertToInt( V, 64, 'signed', undefined, prefix, argument ); // 2. Return the IDL long long value that represents // the same numeric value as x. return x; }; // https://webidl.spec.whatwg.org/#es-unsigned-long-long webidl.converters['unsigned long long'] = function (V, prefix, argument) { // 1. Let x be ? ConvertToInt(V, 64, "unsigned"). const x = webidl.util.ConvertToInt( V, 64, 'unsigned', undefined, prefix, argument ); // 2. Return the IDL unsigned long long value that // represents the same numeric value as x. return x; }; // https://webidl.spec.whatwg.org/#es-unsigned-long webidl.converters['unsigned long'] = function (V, prefix, argument) { // 1. Let x be ? ConvertToInt(V, 32, "unsigned"). const x = webidl.util.ConvertToInt( V, 32, 'unsigned', undefined, prefix, argument ); // 2. Return the IDL unsigned long value that // represents the same numeric value as x. return x; }; // https://webidl.spec.whatwg.org/#es-unsigned-short webidl.converters['unsigned short'] = function (V, prefix, argument, opts) { // 1. Let x be ? ConvertToInt(V, 16, "unsigned"). const x = webidl.util.ConvertToInt(V, 16, 'unsigned', opts, prefix, argument); // 2. Return the IDL unsigned short value that represents // the same numeric value as x. return x; }; // https://webidl.spec.whatwg.org/#idl-ArrayBuffer webidl.converters.ArrayBuffer = function (V, prefix, argument, opts) { // 1. If Type(V) is not Object, or V does not have an // [[ArrayBufferData]] internal slot, then throw a // TypeError. // see: https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-instances // see: https://tc39.es/ecma262/#sec-properties-of-the-sharedarraybuffer-instances if (webidl.util.Type(V) !== OBJECT || !types.isAnyArrayBuffer(V)) { throw webidl.errors.conversionFailed({ prefix, argument: `${argument} ("${webidl.util.Stringify(V)}")`, types: ['ArrayBuffer'], }); } // 2. If the conversion is not to an IDL type associated // with the [AllowShared] extended attribute, and // IsSharedArrayBuffer(V) is true, then throw a // TypeError. if (opts?.allowShared === false && types.isSharedArrayBuffer(V)) { throw webidl.errors.exception({ header: 'ArrayBuffer', message: 'SharedArrayBuffer is not allowed.', }); } // 3. If the conversion is not to an IDL type associated // with the [AllowResizable] extended attribute, and // IsResizableArrayBuffer(V) is true, then throw a // TypeError. if (V.resizable || V.growable) { throw webidl.errors.exception({ header: 'ArrayBuffer', message: 'Received a resizable ArrayBuffer.', }); } // 4. Return the IDL ArrayBuffer value that is a // reference to the same object as V. return V; }; webidl.converters.TypedArray = function (V, T, prefix, name, opts) { // 1. Let T be the IDL type V is being converted to. // 2. If Type(V) is not Object, or V does not have a // [[TypedArrayName]] internal slot with a value // equal to T’s name, then throw a TypeError. if ( webidl.util.Type(V) !== OBJECT || !types.isTypedArray(V) || V.constructor.name !== T.name ) { throw webidl.errors.conversionFailed({ prefix, argument: `${name} ("${webidl.util.Stringify(V)}")`, types: [T.name], }); } // 3. If the conversion is not to an IDL type associated // with the [AllowShared] extended attribute, and // IsSharedArrayBuffer(V.[[ViewedArrayBuffer]]) is // true, then throw a TypeError. if (opts?.allowShared === false && types.isSharedArrayBuffer(V.buffer)) { throw webidl.errors.exception({ header: 'ArrayBuffer', message: 'SharedArrayBuffer is not allowed.', }); } // 4. If the conversion is not to an IDL type associated // with the [AllowResizable] extended attribute, and // IsResizableArrayBuffer(V.[[ViewedArrayBuffer]]) is // true, then throw a TypeError. if (V.buffer.resizable || V.buffer.growable) { throw webidl.errors.exception({ header: 'ArrayBuffer', message: 'Received a resizable ArrayBuffer.', }); } // 5. Return the IDL value of type T that is a reference // to the same object as V. return V; }; webidl.converters.DataView = function (V, prefix, name, opts) { // 1. If Type(V) is not Object, or V does not have a // [[DataView]] internal slot, then throw a TypeError. if (webidl.util.Type(V) !== OBJECT || !types.isDataView(V)) { throw webidl.errors.exception({ header: prefix, message: `${name} is not a DataView.`, }); } // 2. If the conversion is not to an IDL type associated // with the [AllowShared] extended attribute, and // IsSharedArrayBuffer(V.[[ViewedArrayBuffer]]) is true, // then throw a TypeError. if (opts?.allowShared === false && types.isSharedArrayBuffer(V.buffer)) { throw webidl.errors.exception({ header: 'ArrayBuffer', message: 'SharedArrayBuffer is not allowed.', }); } // 3. If the conversion is not to an IDL type associated // with the [AllowResizable] extended attribute, and // IsResizableArrayBuffer(V.[[ViewedArrayBuffer]]) is // true, then throw a TypeError. if (V.buffer.resizable || V.buffer.growable) { throw webidl.errors.exception({ header: 'ArrayBuffer', message: 'Received a resizable ArrayBuffer.', }); } // 4. Return the IDL DataView value that is a reference // to the same object as V. return V; }; webidl.converters['sequence'] = webidl.sequenceConverter( webidl.converters.ByteString ); webidl.converters['sequence>'] = webidl.sequenceConverter( webidl.converters['sequence'] ); webidl.converters['record'] = webidl.recordConverter( webidl.converters.ByteString, webidl.converters.ByteString ); webidl.converters.Blob = webidl.interfaceConverter(webidl.is.Blob, 'Blob'); webidl.converters.AbortSignal = webidl.interfaceConverter( webidl.is.AbortSignal, 'AbortSignal' ); module.exports = { webidl, };