diff options
Diffstat (limited to 'op_crates/web')
-rw-r--r-- | op_crates/web/00_webidl.js | 98 | ||||
-rw-r--r-- | op_crates/web/internal.d.ts | 39 |
2 files changed, 125 insertions, 12 deletions
diff --git a/op_crates/web/00_webidl.js b/op_crates/web/00_webidl.js index 8831cb71e..ab3047391 100644 --- a/op_crates/web/00_webidl.js +++ b/op_crates/web/00_webidl.js @@ -14,7 +14,7 @@ return new ErrorType( `${opts.prefix ? opts.prefix + ": " : ""}${ opts.context ? opts.context : "Value" - } ${message}.`, + } ${message}`, ); } @@ -602,7 +602,7 @@ opts.prefix ? opts.prefix + ": " : "" }${required} argument${ required === 1 ? "" : "s" - }, but only ${length} present.`; + } required, but only ${length} present.`; throw new TypeError(errMsg); } } @@ -637,13 +637,15 @@ esMemberValue = esDict[key]; } + const context = `'${key}' of '${name}'${ + opts.context ? ` (${opts.context})` : "" + }`; + if (esMemberValue !== undefined) { const converter = member.converter; const idlMemberValue = converter(esMemberValue, { ...opts, - context: `${key} of '${name}'${ - opts.context ? `(${opts.context})` : "" - }`, + context, }); idlDict[key] = idlMemberValue; } else if ("defaultValue" in member) { @@ -651,8 +653,10 @@ const idlMemberValue = defaultValue; idlDict[key] = idlMemberValue; } else if (member.required) { - throw new TypeError( - `can not be converted to '${name}' because ${key} is required in '${name}'.`, + throw makeException( + TypeError, + `can not be converted to '${name}' because '${key}' is required in '${name}'.`, + { ...opts }, ); } } @@ -670,10 +674,10 @@ const S = String(V); if (!E.has(S)) { - throw makeException( - TypeError, - `The provided value '${V}' is not a valid enum value of type ${name}.`, - opts, + throw new TypeError( + `${ + opts.prefix ? opts.prefix + ": " : "" + }The provided value '${S}' is not a valid enum value of type ${name}.`, ); } @@ -694,6 +698,72 @@ }; } + // https://heycam.github.io/webidl/#es-sequence + function createSequenceConverter(converter) { + return function (V, opts = {}) { + if (typeof V !== "object") { + throw makeException( + TypeError, + "can not be converted to sequence.", + opts, + ); + } + const iter = V?.[Symbol.iterator]?.(); + 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}`, + }); + array.push(val); + } + return array; + }; + } + + 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; + }; + } + + function createBranded(Type) { + const t = Object.create(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"); + } + window.__bootstrap ??= {}; window.__bootstrap.webidl = { converters, @@ -701,5 +771,11 @@ createDictionaryConverter, createEnumConverter, createNullableConverter, + createSequenceConverter, + createInterfaceConverter, + brand, + createBranded, + assertBranded, + illegalConstructor, }; })(this); diff --git a/op_crates/web/internal.d.ts b/op_crates/web/internal.d.ts index 04fc061bb..d43332e4b 100644 --- a/op_crates/web/internal.d.ts +++ b/op_crates/web/internal.d.ts @@ -191,6 +191,8 @@ declare namespace globalThis { * Convert a value into a `VoidFunction` (() => void). */ VoidFunction(v: any, opts?: ValueConverterOpts): () => void; + + [type: string]: (v: any, opts: ValueConverterOpts) => any; }; /** @@ -205,7 +207,7 @@ declare namespace globalThis { declare interface DictionaryMember { key: string; converter: (v: any, opts: ValueConverterOpts) => any; - defaultValue?: boolean; + defaultValue?: any; required?: boolean; } @@ -231,6 +233,41 @@ declare namespace globalThis { 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[]; + + /** + * 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 var url: { |