diff options
author | Ryan Dahl <ry@tinyclouds.org> | 2021-08-11 12:27:05 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-11 12:27:05 +0200 |
commit | a0285e2eb88f6254f6494b0ecd1878db3a3b2a58 (patch) | |
tree | 90671b004537e20f9493fd3277ffd21d30b39a0e /ext/webidl | |
parent | 3a6994115176781b3a93d70794b1b81bc95e42b4 (diff) |
Rename extensions/ directory to ext/ (#11643)
Diffstat (limited to 'ext/webidl')
-rw-r--r-- | ext/webidl/00_webidl.js | 1079 | ||||
-rw-r--r-- | ext/webidl/Cargo.toml | 17 | ||||
-rw-r--r-- | ext/webidl/README.md | 6 | ||||
-rw-r--r-- | ext/webidl/internal.d.ts | 341 | ||||
-rw-r--r-- | ext/webidl/lib.rs | 14 |
5 files changed, 1457 insertions, 0 deletions
diff --git a/ext/webidl/00_webidl.js b/ext/webidl/00_webidl.js new file mode 100644 index 000000000..40250afe1 --- /dev/null +++ b/ext/webidl/00_webidl.js @@ -0,0 +1,1079 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// Adapted from https://github.com/jsdom/webidl-conversions. +// Copyright Domenic Denicola. Licensed under BSD-2-Clause License. +// Original license at https://github.com/jsdom/webidl-conversions/blob/master/LICENSE.md. + +/// <reference path="../../core/internal.d.ts" /> + +"use strict"; + +((window) => { + const { + ArrayBuffer, + ArrayBufferIsView, + ArrayPrototypeForEach, + ArrayPrototypePush, + ArrayPrototypeSort, + ArrayIteratorPrototype, + BigInt, + BigIntAsIntN, + BigIntAsUintN, + DataView, + Float32Array, + Float64Array, + FunctionPrototypeBind, + Int16Array, + Int32Array, + Int8Array, + isNaN, + MathFloor, + MathFround, + MathMax, + MathMin, + MathPow, + MathRound, + MathTrunc, + Number, + NumberIsFinite, + NumberIsNaN, + // deno-lint-ignore camelcase + NumberMAX_SAFE_INTEGER, + // deno-lint-ignore camelcase + NumberMIN_SAFE_INTEGER, + ObjectCreate, + ObjectDefineProperties, + ObjectDefineProperty, + ObjectGetOwnPropertyDescriptor, + ObjectGetOwnPropertyDescriptors, + ObjectGetPrototypeOf, + ObjectIs, + PromisePrototypeThen, + PromiseReject, + PromiseResolve, + ReflectApply, + ReflectDefineProperty, + ReflectGetOwnPropertyDescriptor, + ReflectOwnKeys, + RegExpPrototypeTest, + Set, + // TODO(lucacasonato): add SharedArrayBuffer to primordials + // SharedArrayBuffer, + String, + StringFromCodePoint, + StringPrototypeCharCodeAt, + StringPrototypeCodePointAt, + Symbol, + SymbolIterator, + SymbolToStringTag, + TypeError, + Uint16Array, + Uint32Array, + Uint8Array, + Uint8ClampedArray, + } = window.__bootstrap.primordials; + + function makeException(ErrorType, message, opts = {}) { + return new ErrorType( + `${opts.prefix ? opts.prefix + ": " : ""}${ + opts.context ? opts.context : "Value" + } ${message}`, + ); + } + + function toNumber(value) { + if (typeof value === "bigint") { + throw TypeError("Cannot convert a BigInt value to a number"); + } + return Number(value); + } + + function type(V) { + if (V === null) { + return "Null"; + } + switch (typeof V) { + case "undefined": + return "Undefined"; + case "boolean": + return "Boolean"; + case "number": + return "Number"; + case "string": + return "String"; + case "symbol": + return "Symbol"; + case "bigint": + return "BigInt"; + case "object": + // Falls through + case "function": + // Falls through + default: + // Per ES spec, typeof returns an implemention-defined value that is not any of the existing ones for + // uncallable non-standard exotic objects. Yet Type() which the Web IDL spec depends on returns Object for + // such cases. So treat the default case as an object. + return "Object"; + } + } + + // Round x to the nearest integer, choosing the even integer if it lies halfway between two. + function evenRound(x) { + // There are four cases for numbers with fractional part being .5: + // + // case | x | floor(x) | round(x) | expected | x <> 0 | x % 1 | x & 1 | example + // 1 | 2n + 0.5 | 2n | 2n + 1 | 2n | > | 0.5 | 0 | 0.5 -> 0 + // 2 | 2n + 1.5 | 2n + 1 | 2n + 2 | 2n + 2 | > | 0.5 | 1 | 1.5 -> 2 + // 3 | -2n - 0.5 | -2n - 1 | -2n | -2n | < | -0.5 | 0 | -0.5 -> 0 + // 4 | -2n - 1.5 | -2n - 2 | -2n - 1 | -2n - 2 | < | -0.5 | 1 | -1.5 -> -2 + // (where n is a non-negative integer) + // + // Branch here for cases 1 and 4 + if ( + (x > 0 && x % 1 === +0.5 && (x & 1) === 0) || + (x < 0 && x % 1 === -0.5 && (x & 1) === 1) + ) { + return censorNegativeZero(MathFloor(x)); + } + + return censorNegativeZero(MathRound(x)); + } + + function integerPart(n) { + return censorNegativeZero(MathTrunc(n)); + } + + function sign(x) { + return x < 0 ? -1 : 1; + } + + function modulo(x, y) { + // https://tc39.github.io/ecma262/#eqn-modulo + // Note that http://stackoverflow.com/a/4467559/3191 does NOT work for large modulos + const signMightNotMatch = x % y; + if (sign(y) !== sign(signMightNotMatch)) { + return signMightNotMatch + y; + } + return signMightNotMatch; + } + + function censorNegativeZero(x) { + return x === 0 ? 0 : x; + } + + function createIntegerConversion(bitLength, typeOpts) { + const isSigned = !typeOpts.unsigned; + + let lowerBound; + let upperBound; + if (bitLength === 64) { + upperBound = NumberMAX_SAFE_INTEGER; + lowerBound = !isSigned ? 0 : NumberMIN_SAFE_INTEGER; + } else if (!isSigned) { + lowerBound = 0; + upperBound = MathPow(2, bitLength) - 1; + } else { + lowerBound = -MathPow(2, bitLength - 1); + upperBound = MathPow(2, bitLength - 1) - 1; + } + + const twoToTheBitLength = MathPow(2, bitLength); + const twoToOneLessThanTheBitLength = MathPow(2, bitLength - 1); + + return (V, opts = {}) => { + let x = toNumber(V, opts); + x = censorNegativeZero(x); + + if (opts.enforceRange) { + if (!NumberIsFinite(x)) { + throw makeException(TypeError, "is not a finite number", opts); + } + + x = integerPart(x); + + if (x < lowerBound || x > upperBound) { + throw makeException( + TypeError, + `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, + opts, + ); + } + + return x; + } + + if (!NumberIsNaN(x) && opts.clamp) { + x = MathMin(MathMax(x, lowerBound), upperBound); + x = evenRound(x); + return x; + } + + if (!NumberIsFinite(x) || x === 0) { + return 0; + } + x = integerPart(x); + + // Math.pow(2, 64) is not accurately representable in JavaScript, so try to avoid these per-spec operations if + // possible. Hopefully it's an optimization for the non-64-bitLength cases too. + if (x >= lowerBound && x <= upperBound) { + return x; + } + + // These will not work great for bitLength of 64, but oh well. See the README for more details. + x = modulo(x, twoToTheBitLength); + if (isSigned && x >= twoToOneLessThanTheBitLength) { + return x - twoToTheBitLength; + } + return x; + }; + } + + function createLongLongConversion(bitLength, { unsigned }) { + const upperBound = NumberMAX_SAFE_INTEGER; + const lowerBound = unsigned ? 0 : NumberMIN_SAFE_INTEGER; + const asBigIntN = unsigned ? BigIntAsUintN : BigIntAsIntN; + + return (V, opts = {}) => { + let x = toNumber(V, opts); + x = censorNegativeZero(x); + + if (opts.enforceRange) { + if (!NumberIsFinite(x)) { + throw makeException(TypeError, "is not a finite number", opts); + } + + x = integerPart(x); + + if (x < lowerBound || x > upperBound) { + throw makeException( + TypeError, + `is outside the accepted range of ${lowerBound} to ${upperBound}, inclusive`, + opts, + ); + } + + return x; + } + + if (!NumberIsNaN(x) && opts.clamp) { + x = MathMin(MathMax(x, lowerBound), upperBound); + x = evenRound(x); + return x; + } + + if (!NumberIsFinite(x) || x === 0) { + return 0; + } + + let xBigInt = BigInt(integerPart(x)); + xBigInt = asBigIntN(bitLength, xBigInt); + return Number(xBigInt); + }; + } + + const converters = []; + + converters.any = (V) => { + return V; + }; + + converters.boolean = function (val) { + return !!val; + }; + + converters.byte = createIntegerConversion(8, { unsigned: false }); + converters.octet = createIntegerConversion(8, { unsigned: true }); + + converters.short = createIntegerConversion(16, { unsigned: false }); + converters["unsigned short"] = createIntegerConversion(16, { + unsigned: true, + }); + + converters.long = createIntegerConversion(32, { unsigned: false }); + converters["unsigned long"] = createIntegerConversion(32, { unsigned: true }); + + converters["long long"] = createLongLongConversion(64, { unsigned: false }); + converters["unsigned long long"] = createLongLongConversion(64, { + unsigned: true, + }); + + converters.float = (V, opts) => { + const x = toNumber(V, opts); + + if (!NumberIsFinite(x)) { + throw makeException( + TypeError, + "is not a finite floating-point value", + opts, + ); + } + + if (ObjectIs(x, -0)) { + return x; + } + + const y = MathFround(x); + + if (!NumberIsFinite(y)) { + throw makeException( + TypeError, + "is outside the range of a single-precision floating-point value", + opts, + ); + } + + return y; + }; + + converters["unrestricted float"] = (V, opts) => { + const x = toNumber(V, opts); + + if (isNaN(x)) { + return x; + } + + if (ObjectIs(x, -0)) { + return x; + } + + return MathFround(x); + }; + + converters.double = (V, opts) => { + const x = toNumber(V, opts); + + if (!NumberIsFinite(x)) { + throw makeException( + TypeError, + "is not a finite floating-point value", + opts, + ); + } + + return x; + }; + + converters["unrestricted double"] = (V, opts) => { + const x = toNumber(V, opts); + + return x; + }; + + converters.DOMString = function (V, opts = {}) { + if (opts.treatNullAsEmptyString && V === null) { + return ""; + } + + if (typeof V === "symbol") { + throw makeException( + TypeError, + "is a symbol, which cannot be converted to a string", + opts, + ); + } + + return String(V); + }; + + converters.ByteString = (V, opts) => { + const x = converters.DOMString(V, opts); + let c; + for (let i = 0; (c = StringPrototypeCodePointAt(x, i)) !== undefined; ++i) { + if (c > 255) { + throw makeException(TypeError, "is not a valid ByteString", opts); + } + } + + return x; + }; + + converters.USVString = (V, opts) => { + const S = converters.DOMString(V, opts); + const n = S.length; + let U = ""; + for (let i = 0; i < n; ++i) { + const c = StringPrototypeCharCodeAt(S, i); + if (c < 0xd800 || c > 0xdfff) { + U += StringFromCodePoint(c); + } else if (0xdc00 <= c && c <= 0xdfff) { + U += StringFromCodePoint(0xfffd); + } else if (i === n - 1) { + U += StringFromCodePoint(0xfffd); + } else { + const d = StringPrototypeCharCodeAt(S, i + 1); + if (0xdc00 <= d && d <= 0xdfff) { + const a = c & 0x3ff; + const b = d & 0x3ff; + U += StringFromCodePoint((2 << 15) + (2 << 9) * a + b); + ++i; + } else { + U += StringFromCodePoint(0xfffd); + } + } + } + return U; + }; + + converters.object = (V, opts) => { + if (type(V) !== "Object") { + throw makeException(TypeError, "is not an object", opts); + } + + return V; + }; + + // Not exported, but used in Function and VoidFunction. + + // Neither Function nor VoidFunction is defined with [TreatNonObjectAsNull], so + // handling for that is omitted. + function convertCallbackFunction(V, opts) { + if (typeof V !== "function") { + throw makeException(TypeError, "is not a function", opts); + } + return V; + } + + function isNonSharedArrayBuffer(V) { + return V instanceof ArrayBuffer; + } + + function isSharedArrayBuffer(V) { + return V instanceof SharedArrayBuffer; + } + + function isArrayBufferDetached(V) { + try { + new Uint8Array(V); + return false; + } catch { + return true; + } + } + + converters.ArrayBuffer = (V, opts = {}) => { + if (!isNonSharedArrayBuffer(V)) { + if (opts.allowShared && !isSharedArrayBuffer(V)) { + throw makeException( + TypeError, + "is not an ArrayBuffer or SharedArrayBuffer", + opts, + ); + } + throw makeException(TypeError, "is not an ArrayBuffer", opts); + } + if (isArrayBufferDetached(V)) { + throw makeException(TypeError, "is a detached ArrayBuffer", opts); + } + + return V; + }; + + converters.DataView = (V, opts = {}) => { + if (!(V instanceof DataView)) { + throw makeException(TypeError, "is not a DataView", opts); + } + + if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + throw makeException( + TypeError, + "is backed by a SharedArrayBuffer, which is not allowed", + opts, + ); + } + if (isArrayBufferDetached(V.buffer)) { + throw makeException( + TypeError, + "is backed by a detached ArrayBuffer", + opts, + ); + } + + return V; + }; + + // Returns the unforgeable `TypedArray` constructor name or `undefined`, + // if the `this` value isn't a valid `TypedArray` object. + // + // https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag + const typedArrayNameGetter = ObjectGetOwnPropertyDescriptor( + ObjectGetPrototypeOf(Uint8Array).prototype, + SymbolToStringTag, + ).get; + ArrayPrototypeForEach( + [ + Int8Array, + Int16Array, + Int32Array, + Uint8Array, + Uint16Array, + Uint32Array, + Uint8ClampedArray, + Float32Array, + Float64Array, + ], + (func) => { + const name = func.name; + const article = RegExpPrototypeTest(/^[AEIOU]/, name) ? "an" : "a"; + converters[name] = (V, opts = {}) => { + if (!ArrayBufferIsView(V) || typedArrayNameGetter.call(V) !== name) { + throw makeException( + TypeError, + `is not ${article} ${name} object`, + opts, + ); + } + if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + throw makeException( + TypeError, + "is a view on a SharedArrayBuffer, which is not allowed", + opts, + ); + } + if (isArrayBufferDetached(V.buffer)) { + throw makeException( + TypeError, + "is a view on a detached ArrayBuffer", + opts, + ); + } + + return V; + }; + }, + ); + + // Common definitions + + converters.ArrayBufferView = (V, opts = {}) => { + if (!ArrayBufferIsView(V)) { + throw makeException( + TypeError, + "is not a view on an ArrayBuffer or SharedArrayBuffer", + opts, + ); + } + + if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + throw makeException( + TypeError, + "is a view on a SharedArrayBuffer, which is not allowed", + opts, + ); + } + + if (isArrayBufferDetached(V.buffer)) { + throw makeException( + TypeError, + "is a view on a detached ArrayBuffer", + opts, + ); + } + return V; + }; + + converters.BufferSource = (V, opts = {}) => { + if (ArrayBufferIsView(V)) { + if (!opts.allowShared && isSharedArrayBuffer(V.buffer)) { + throw makeException( + TypeError, + "is a view on a SharedArrayBuffer, which is not allowed", + opts, + ); + } + + if (isArrayBufferDetached(V.buffer)) { + throw makeException( + TypeError, + "is a view on a detached ArrayBuffer", + opts, + ); + } + return V; + } + + if (!opts.allowShared && !isNonSharedArrayBuffer(V)) { + throw makeException( + TypeError, + "is not an ArrayBuffer or a view on one", + opts, + ); + } + if ( + opts.allowShared && + !isSharedArrayBuffer(V) && + !isNonSharedArrayBuffer(V) + ) { + throw makeException( + TypeError, + "is not an ArrayBuffer, SharedArrayBuffer, or a view on one", + opts, + ); + } + if (isArrayBufferDetached(V)) { + throw makeException(TypeError, "is a detached ArrayBuffer", opts); + } + + return V; + }; + + converters.DOMTimeStamp = converters["unsigned long long"]; + converters.DOMHighResTimeStamp = converters["double"]; + + converters.Function = convertCallbackFunction; + + converters.VoidFunction = convertCallbackFunction; + + converters["UVString?"] = createNullableConverter( + converters.USVString, + ); + converters["sequence<double>"] = createSequenceConverter( + converters.double, + ); + converters["sequence<object>"] = createSequenceConverter( + converters.object, + ); + converters["Promise<undefined>"] = createPromiseConverter(() => undefined); + + converters["sequence<ByteString>"] = createSequenceConverter( + converters.ByteString, + ); + converters["sequence<sequence<ByteString>>"] = createSequenceConverter( + converters["sequence<ByteString>"], + ); + converters["record<ByteString, ByteString>"] = createRecordConverter( + converters.ByteString, + converters.ByteString, + ); + + converters["sequence<USVString>"] = createSequenceConverter( + converters.USVString, + ); + converters["sequence<sequence<USVString>>"] = createSequenceConverter( + converters["sequence<USVString>"], + ); + converters["record<USVString, USVString>"] = createRecordConverter( + converters.USVString, + converters.USVString, + ); + + converters["sequence<DOMString>"] = createSequenceConverter( + converters.DOMString, + ); + + function requiredArguments(length, required, opts = {}) { + if (length < required) { + const errMsg = `${ + opts.prefix ? opts.prefix + ": " : "" + }${required} argument${ + required === 1 ? "" : "s" + } required, but only ${length} present.`; + throw new TypeError(errMsg); + } + } + + function createDictionaryConverter(name, ...dictionaries) { + let hasRequiredKey = false; + const allMembers = []; + for (const members of dictionaries) { + for (const member of members) { + if (member.required) { + hasRequiredKey = true; + } + ArrayPrototypePush(allMembers, member); + } + } + ArrayPrototypeSort(allMembers, (a, b) => { + if (a.key == b.key) { + return 0; + } + return a.key < b.key ? -1 : 1; + }); + + const defaultValues = {}; + for (const member of allMembers) { + if ("defaultValue" in member) { + const idlMemberValue = member.defaultValue; + const imvType = typeof idlMemberValue; + // Copy by value types can be directly assigned, copy by reference types + // need to be re-created for each allocation. + if ( + imvType === "number" || imvType === "boolean" || + imvType === "string" || imvType === "bigint" || + imvType === "undefined" + ) { + defaultValues[member.key] = idlMemberValue; + } else { + ObjectDefineProperty(defaultValues, member.key, { + get() { + return member.defaultValue; + }, + enumerable: true, + }); + } + } + } + + return function (V, opts = {}) { + const typeV = type(V); + switch (typeV) { + case "Undefined": + case "Null": + case "Object": + break; + default: + throw makeException( + TypeError, + "can not be converted to a dictionary", + opts, + ); + } + const esDict = V; + + const idlDict = { ...defaultValues }; + + // NOTE: fast path Null and Undefined. + if ((V === undefined || V === null) && !hasRequiredKey) { + return idlDict; + } + + for (const member of allMembers) { + const key = member.key; + + let esMemberValue; + if (typeV === "Undefined" || typeV === "Null") { + esMemberValue = undefined; + } else { + esMemberValue = esDict[key]; + } + + if (esMemberValue !== undefined) { + const context = `'${key}' of '${name}'${ + opts.context ? ` (${opts.context})` : "" + }`; + const converter = member.converter; + const idlMemberValue = converter(esMemberValue, { ...opts, context }); + idlDict[key] = idlMemberValue; + } else if (member.required) { + throw makeException( + TypeError, + `can not be converted to '${name}' because '${key}' is required in '${name}'.`, + { ...opts }, + ); + } + } + + return idlDict; + }; + } + + // https://heycam.github.io/webidl/#es-enumeration + function createEnumConverter(name, values) { + const E = new Set(values); + + return function (V, opts = {}) { + const S = String(V); + + if (!E.has(S)) { + throw new TypeError( + `${ + opts.prefix ? opts.prefix + ": " : "" + }The provided value '${S}' is not a valid enum value of type ${name}.`, + ); + } + + return S; + }; + } + + function createNullableConverter(converter) { + return (V, opts = {}) => { + // FIXME: If Type(V) is not Object, and the conversion to an IDL value is + // being performed due to V being assigned to an attribute whose type is a + // nullable callback function that is annotated with + // [LegacyTreatNonObjectAsNull], then return the IDL nullable type T? + // value null. + + if (V === null || V === undefined) return null; + return converter(V, opts); + }; + } + + // https://heycam.github.io/webidl/#es-sequence + function createSequenceConverter(converter) { + return function (V, opts = {}) { + if (type(V) !== "Object") { + throw makeException( + TypeError, + "can not be converted to sequence.", + opts, + ); + } + const iter = V?.[SymbolIterator]?.(); + if (iter === undefined) { + throw makeException( + TypeError, + "can not be converted to sequence.", + opts, + ); + } + const array = []; + while (true) { + const res = iter?.next?.(); + if (res === undefined) { + throw makeException( + TypeError, + "can not be converted to sequence.", + opts, + ); + } + if (res.done === true) break; + const val = converter(res.value, { + ...opts, + context: `${opts.context}, index ${array.length}`, + }); + ArrayPrototypePush(array, val); + } + return array; + }; + } + + function createRecordConverter(keyConverter, valueConverter) { + return (V, opts) => { + if (type(V) !== "Object") { + throw makeException( + TypeError, + "can not be converted to dictionary.", + opts, + ); + } + const keys = ReflectOwnKeys(V); + const result = {}; + for (const key of keys) { + const desc = ObjectGetOwnPropertyDescriptor(V, key); + if (desc !== undefined && desc.enumerable === true) { + const typedKey = keyConverter(key, opts); + const value = V[key]; + const typedValue = valueConverter(value, opts); + result[typedKey] = typedValue; + } + } + return result; + }; + } + + function createPromiseConverter(converter) { + return (V, opts) => + PromisePrototypeThen(PromiseResolve(V), (V) => converter(V, opts)); + } + + function invokeCallbackFunction( + callable, + args, + thisArg, + returnValueConverter, + opts, + ) { + try { + const rv = ReflectApply(callable, thisArg, args); + return returnValueConverter(rv, { + prefix: opts.prefix, + context: "return value", + }); + } catch (err) { + if (opts.returnsPromise === true) { + return PromiseReject(err); + } + throw err; + } + } + + const brand = Symbol("[[webidl.brand]]"); + + function createInterfaceConverter(name, prototype) { + return (V, opts) => { + if (!(V instanceof prototype) || V[brand] !== brand) { + throw makeException(TypeError, `is not of type ${name}.`, opts); + } + return V; + }; + } + + // TODO(lucacasonato): have the user pass in the prototype, and not the type. + function createBranded(Type) { + const t = ObjectCreate(Type.prototype); + t[brand] = brand; + return t; + } + + function assertBranded(self, prototype) { + if (!(self instanceof prototype) || self[brand] !== brand) { + throw new TypeError("Illegal invocation"); + } + } + + function illegalConstructor() { + throw new TypeError("Illegal constructor"); + } + + function define(target, source) { + for (const key of ReflectOwnKeys(source)) { + const descriptor = ReflectGetOwnPropertyDescriptor(source, key); + if (descriptor && !ReflectDefineProperty(target, key, descriptor)) { + throw new TypeError(`Cannot redefine property: ${String(key)}`); + } + } + } + + const _iteratorInternal = Symbol("iterator internal"); + + const globalIteratorPrototype = ObjectGetPrototypeOf(ArrayIteratorPrototype); + + function mixinPairIterable(name, prototype, dataSymbol, keyKey, valueKey) { + const iteratorPrototype = ObjectCreate(globalIteratorPrototype, { + [SymbolToStringTag]: { configurable: true, value: `${name} Iterator` }, + }); + define(iteratorPrototype, { + next() { + const internal = this && this[_iteratorInternal]; + if (!internal) { + throw new TypeError( + `next() called on a value that is not a ${name} iterator object`, + ); + } + const { target, kind, index } = internal; + const values = target[dataSymbol]; + const len = values.length; + if (index >= len) { + return { value: undefined, done: true }; + } + const pair = values[index]; + internal.index = index + 1; + let result; + switch (kind) { + case "key": + result = pair[keyKey]; + break; + case "value": + result = pair[valueKey]; + break; + case "key+value": + result = [pair[keyKey], pair[valueKey]]; + break; + } + return { value: result, done: false }; + }, + }); + function createDefaultIterator(target, kind) { + const iterator = ObjectCreate(iteratorPrototype); + ObjectDefineProperty(iterator, _iteratorInternal, { + value: { target, kind, index: 0 }, + configurable: true, + }); + return iterator; + } + + function entries() { + assertBranded(this, prototype); + return createDefaultIterator(this, "key+value"); + } + + const properties = { + entries: { + value: entries, + writable: true, + enumerable: true, + configurable: true, + }, + [SymbolIterator]: { + value: entries, + writable: true, + enumerable: false, + configurable: true, + }, + keys: { + value: function keys() { + assertBranded(this, prototype); + return createDefaultIterator(this, "key"); + }, + writable: true, + enumerable: true, + configurable: true, + }, + values: { + value: function values() { + assertBranded(this, prototype); + return createDefaultIterator(this, "value"); + }, + writable: true, + enumerable: true, + configurable: true, + }, + forEach: { + value: function forEach(idlCallback, thisArg = undefined) { + assertBranded(this, prototype); + const prefix = `Failed to execute 'forEach' on '${name}'`; + requiredArguments(arguments.length, 1, { prefix }); + idlCallback = converters["Function"](idlCallback, { + prefix, + context: "Argument 1", + }); + idlCallback = FunctionPrototypeBind( + idlCallback, + thisArg ?? globalThis, + ); + const pairs = this[dataSymbol]; + for (let i = 0; i < pairs.length; i++) { + const entry = pairs[i]; + idlCallback(entry[valueKey], entry[keyKey], this); + } + }, + writable: true, + enumerable: true, + configurable: true, + }, + }; + return ObjectDefineProperties(prototype.prototype, properties); + } + + function configurePrototype(prototype) { + const descriptors = ObjectGetOwnPropertyDescriptors(prototype.prototype); + for (const key in descriptors) { + if (key === "constructor") continue; + const descriptor = descriptors[key]; + if ("value" in descriptor && typeof descriptor.value === "function") { + ObjectDefineProperty(prototype.prototype, key, { + enumerable: true, + writable: true, + configurable: true, + }); + } else if ("get" in descriptor) { + ObjectDefineProperty(prototype.prototype, key, { + enumerable: true, + configurable: true, + }); + } + } + } + + window.__bootstrap ??= {}; + window.__bootstrap.webidl = { + type, + makeException, + converters, + requiredArguments, + createDictionaryConverter, + createEnumConverter, + createNullableConverter, + createSequenceConverter, + createRecordConverter, + createPromiseConverter, + invokeCallbackFunction, + createInterfaceConverter, + brand, + createBranded, + assertBranded, + illegalConstructor, + mixinPairIterable, + configurePrototype, + }; +})(this); diff --git a/ext/webidl/Cargo.toml b/ext/webidl/Cargo.toml new file mode 100644 index 000000000..e1dc61feb --- /dev/null +++ b/ext/webidl/Cargo.toml @@ -0,0 +1,17 @@ +# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +[package] +name = "deno_webidl" +version = "0.14.0" +authors = ["the Deno authors"] +edition = "2018" +license = "MIT" +readme = "README.md" +repository = "https://github.com/denoland/deno" +description = "WebIDL implementation for Deno" + +[lib] +path = "lib.rs" + +[dependencies] +deno_core = { version = "0.96.0", path = "../../core" } diff --git a/ext/webidl/README.md b/ext/webidl/README.md new file mode 100644 index 000000000..ce2d661e3 --- /dev/null +++ b/ext/webidl/README.md @@ -0,0 +1,6 @@ +# deno_webidl + +This crate implements WebIDL for Deno. It consists of infrastructure to do ECMA +-> WebIDL conversions. + +Spec: https://heycam.github.io/webidl/ diff --git a/ext/webidl/internal.d.ts b/ext/webidl/internal.d.ts new file mode 100644 index 000000000..9d151a6d1 --- /dev/null +++ b/ext/webidl/internal.d.ts @@ -0,0 +1,341 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. +// deno-lint-ignore-file no-explicit-any ban-types + +/// <reference no-default-lib="true" /> +/// <reference lib="esnext" /> + +declare namespace globalThis { + declare namespace __bootstrap { + declare namespace webidl { + declare interface ConverterOpts { + /** + * The prefix for error messages created by this converter. + * Examples: + * - `Failed to construct 'Event'` + * - `Failed to execute 'removeEventListener' on 'EventTarget'` + */ + prefix: string; + } + declare interface ValueConverterOpts extends ConverterOpts { + /** + * The context of this value error messages created by this converter. + * Examples: + * - `Argument 1` + * - `Argument 3` + */ + context: string; + } + declare function makeException( + ErrorType: any, + message: string, + opts: ValueConverterOpts, + ): any; + declare interface IntConverterOpts extends ValueConverterOpts { + /** + * Wether to throw if the number is outside of the acceptable values for + * this type. + */ + enforceRange?: boolean; + /** + * Wether to clamp this number to the acceptable values for this type. + */ + clamp?: boolean; + } + declare interface StringConverterOpts extends ValueConverterOpts { + /** + * Wether to treat `null` value as an empty string. + */ + treatNullAsEmptyString?: boolean; + } + declare interface BufferConverterOpts extends ValueConverterOpts { + /** + * Wether to allow `SharedArrayBuffer` (not just `ArrayBuffer`). + */ + allowShared?: boolean; + } + declare const converters: { + any(v: any): any; + /** + * Convert a value into a `boolean` (bool). + */ + boolean(v: any, opts?: IntConverterOpts): boolean; + /** + * Convert a value into a `byte` (int8). + */ + byte(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `octet` (uint8). + */ + octet(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `short` (int16). + */ + short(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `unsigned short` (uint16). + */ + ["unsigned short"](v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `long` (int32). + */ + long(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `unsigned long` (uint32). + */ + ["unsigned long"](v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `long long` (int64). + * **Note this is truncated to a JS number (53 bit precision).** + */ + ["long long"](v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `unsigned long long` (uint64). + * **Note this is truncated to a JS number (53 bit precision).** + */ + ["unsigned long long"](v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `float` (f32). + */ + float(v: any, opts?: ValueConverterOpts): number; + /** + * Convert a value into a `unrestricted float` (f32, infinity, or NaN). + */ + ["unrestricted float"](v: any, opts?: ValueConverterOpts): number; + /** + * Convert a value into a `double` (f64). + */ + double(v: any, opts?: ValueConverterOpts): number; + /** + * Convert a value into a `unrestricted double` (f64, infinity, or NaN). + */ + ["unrestricted double"](v: any, opts?: ValueConverterOpts): number; + /** + * Convert a value into a `DOMString` (string). + */ + DOMString(v: any, opts?: StringConverterOpts): string; + /** + * Convert a value into a `ByteString` (string with only u8 codepoints). + */ + ByteString(v: any, opts?: StringConverterOpts): string; + /** + * Convert a value into a `USVString` (string with only valid non + * surrogate Unicode code points). + */ + USVString(v: any, opts?: StringConverterOpts): string; + /** + * Convert a value into an `object` (object). + */ + object(v: any, opts?: ValueConverterOpts): object; + /** + * Convert a value into an `ArrayBuffer` (ArrayBuffer). + */ + ArrayBuffer(v: any, opts?: BufferConverterOpts): ArrayBuffer; + /** + * Convert a value into a `DataView` (ArrayBuffer). + */ + DataView(v: any, opts?: BufferConverterOpts): DataView; + /** + * Convert a value into a `Int8Array` (Int8Array). + */ + Int8Array(v: any, opts?: BufferConverterOpts): Int8Array; + /** + * Convert a value into a `Int16Array` (Int16Array). + */ + Int16Array(v: any, opts?: BufferConverterOpts): Int16Array; + /** + * Convert a value into a `Int32Array` (Int32Array). + */ + Int32Array(v: any, opts?: BufferConverterOpts): Int32Array; + /** + * Convert a value into a `Uint8Array` (Uint8Array). + */ + Uint8Array(v: any, opts?: BufferConverterOpts): Uint8Array; + /** + * Convert a value into a `Uint16Array` (Uint16Array). + */ + Uint16Array(v: any, opts?: BufferConverterOpts): Uint16Array; + /** + * Convert a value into a `Uint32Array` (Uint32Array). + */ + Uint32Array(v: any, opts?: BufferConverterOpts): Uint32Array; + /** + * Convert a value into a `Uint8ClampedArray` (Uint8ClampedArray). + */ + Uint8ClampedArray( + v: any, + opts?: BufferConverterOpts, + ): Uint8ClampedArray; + /** + * Convert a value into a `Float32Array` (Float32Array). + */ + Float32Array(v: any, opts?: BufferConverterOpts): Float32Array; + /** + * Convert a value into a `Float64Array` (Float64Array). + */ + Float64Array(v: any, opts?: BufferConverterOpts): Float64Array; + /** + * Convert a value into an `ArrayBufferView` (ArrayBufferView). + */ + ArrayBufferView(v: any, opts?: BufferConverterOpts): ArrayBufferView; + /** + * Convert a value into a `BufferSource` (ArrayBuffer or ArrayBufferView). + */ + BufferSource( + v: any, + opts?: BufferConverterOpts, + ): ArrayBuffer | ArrayBufferView; + /** + * Convert a value into a `DOMTimeStamp` (u64). Alias for unsigned long long + */ + DOMTimeStamp(v: any, opts?: IntConverterOpts): number; + /** + * Convert a value into a `Function` ((...args: any[]) => any). + */ + Function(v: any, opts?: ValueConverterOpts): (...args: any) => any; + /** + * Convert a value into a `VoidFunction` (() => void). + */ + VoidFunction(v: any, opts?: ValueConverterOpts): () => void; + ["UVString?"](v: any, opts?: ValueConverterOpts): string | null; + ["sequence<double>"](v: any, opts?: ValueConverterOpts): number[]; + + [type: string]: (v: any, opts: ValueConverterOpts) => any; + }; + + /** + * Assert that the a function has at least a required amount of arguments. + */ + declare function requiredArguments( + length: number, + required: number, + opts: ConverterOpts, + ): void; + declare type Dictionary = DictionaryMember[]; + declare interface DictionaryMember { + key: string; + converter: (v: any, opts: ValueConverterOpts) => any; + defaultValue?: any; + required?: boolean; + } + + /** + * Create a converter for dictionaries. + */ + declare function createDictionaryConverter<T>( + name: string, + ...dictionaries: Dictionary[] + ): (v: any, opts: ValueConverterOpts) => T; + + /** + * Create a converter for enums. + */ + declare function createEnumConverter( + name: string, + values: string[], + ): (v: any, opts: ValueConverterOpts) => string; + + /** + * Create a converter that makes the contained type nullable. + */ + declare function createNullableConverter<T>( + converter: (v: any, opts: ValueConverterOpts) => T, + ): (v: any, opts: ValueConverterOpts) => T | null; + + /** + * Create a converter that converts a sequence of the inner type. + */ + declare function createSequenceConverter<T>( + converter: (v: any, opts: ValueConverterOpts) => T, + ): (v: any, opts: ValueConverterOpts) => T[]; + + /** + * Create a converter that converts a Promise of the inner type. + */ + declare function createPromiseConverter<T>( + converter: (v: any, opts: ValueConverterOpts) => T, + ): (v: any, opts: ValueConverterOpts) => Promise<T>; + + /** + * Invoke a callback function. + */ + declare function invokeCallbackFunction<T>( + callable: (...args: any) => any, + args: any[], + thisArg: any, + returnValueConverter: (v: any, opts: ValueConverterOpts) => T, + opts: ConverterOpts & { returnsPromise?: boolean }, + ): T; + + /** + * Throw an illegal constructor error. + */ + declare function illegalConstructor(): never; + + /** + * The branding symbol. + */ + declare const brand: unique symbol; + + /** + * Create a branded instance of an interface. + */ + declare function createBranded(self: any): any; + + /** + * Assert that self is branded. + */ + declare function assertBranded(self: any, type: any): void; + + /** + * Create a converter for interfaces. + */ + declare function createInterfaceConverter( + name: string, + prototype: any, + ): (v: any, opts: ValueConverterOpts) => any; + + declare function createRecordConverter< + K extends string | number | symbol, + V, + >( + keyConverter: (v: any, opts: ValueConverterOpts) => K, + valueConverter: (v: any, opts: ValueConverterOpts) => V, + ): ( + v: Record<K, V>, + opts: ValueConverterOpts, + ) => any; + + /** + * Mix in the iterable declarations defined in WebIDL. + * https://heycam.github.io/webidl/#es-iterable + */ + declare function mixinPairIterable( + name: string, + prototype: any, + dataSymbol: symbol, + keyKey: string | number | symbol, + valueKey: string | number | symbol, + ): void; + + /** + * Configure prototype properties enumerability / writability / configurability. + */ + declare function configurePrototype(prototype: any); + + /** + * Get the WebIDL / ES type of a value. + */ + declare function type( + v: any, + ): + | "Null" + | "Undefined" + | "Boolean" + | "Number" + | "String" + | "Symbol" + | "BigInt" + | "Object"; + } + } +} diff --git a/ext/webidl/lib.rs b/ext/webidl/lib.rs new file mode 100644 index 000000000..6dda68442 --- /dev/null +++ b/ext/webidl/lib.rs @@ -0,0 +1,14 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use deno_core::include_js_files; +use deno_core::Extension; + +/// Load and execute the javascript code. +pub fn init() -> Extension { + Extension::builder() + .js(include_js_files!( + prefix "deno:ext/webidl", + "00_webidl.js", + )) + .build() +} |