diff --git a/lib/constructs/attribute.js b/lib/constructs/attribute.js index b42da313..174df9a1 100644 --- a/lib/constructs/attribute.js +++ b/lib/constructs/attribute.js @@ -23,6 +23,10 @@ class Attribute { const onInstance = utils.isOnInstance(this.idl, this.interface.idl); + const async = this.idl.idlType.generic === "Promise"; + const promiseHandlingBefore = async ? `try {` : ``; + const promiseHandlingAfter = async ? `} catch (e) { return Promise.reject(e); }` : ``; + let brandCheck = ` if (!exports.is(esValue)) { throw new TypeError("Illegal invocation"); @@ -72,12 +76,18 @@ class Attribute { } addMethod(this.idl.name, [], ` + ${promiseHandlingBefore} const esValue = this !== null && this !== undefined ? this : globalObject; ${brandCheck} ${getterBody} + ${promiseHandlingAfter} `, "get", { configurable }); if (!this.idl.readonly) { + if (async) { + throw new Error(`Illegal promise-typed attribute "${this.idl.name}" in interface "${this.interface.idl.name}"`); + } + let idlConversion; if (typeof this.idl.idlType.idlType === "string" && !this.idl.idlType.nullable && this.ctx.enumerations.has(this.idl.idlType.idlType)) { diff --git a/lib/constructs/operation.js b/lib/constructs/operation.js index 53fb33c8..b163d137 100644 --- a/lib/constructs/operation.js +++ b/lib/constructs/operation.js @@ -27,6 +27,22 @@ class Operation { return firstOverloadOnInstance; } + isAsync() { + // As of the time of this writing, the current spec does not disallow such overloads, but the intention is to do so: + // https://github.com/heycam/webidl/pull/776 + const firstAsync = this.idls[0].idlType.generic === "Promise"; + for (const overload of this.idls.slice(1)) { + const isAsync = overload.idlType.generic === "Promise"; + if (isAsync !== firstAsync) { + throw new Error( + `Overloading between Promise and non-Promise return types is not allowed: operation ` + + `"${this.name}" on ${this.interface.name}` + ); + } + } + return firstAsync; + } + fixUpArgsExtAttrs() { for (const idl of this.idls) { for (const arg of idl.arguments) { @@ -50,6 +66,9 @@ class Operation { } const onInstance = this.isOnInstance(); + const async = this.isAsync(); + const promiseHandlingBefore = async ? `try {` : ``; + const promiseHandlingAfter = async ? `} catch (e) { return Promise.reject(e); }` : ``; const type = this.static ? "static operation" : "regular operation"; const overloads = Overloads.getEffectiveOverloads(type, this.name, 0, this.interface); @@ -100,6 +119,8 @@ class Operation { } str += invocation; + str = promiseHandlingBefore + str + promiseHandlingAfter; + if (this.static) { this.interface.addStaticMethod(this.name, argNames, str); } else { diff --git a/test/__snapshots__/test.js.snap b/test/__snapshots__/test.js.snap index 7f3f3c24..46cbd7b4 100644 --- a/test/__snapshots__/test.js.snap +++ b/test/__snapshots__/test.js.snap @@ -446,6 +446,24 @@ exports.install = (globalObject, globalNames) => { } } + promiseOperation() { + try { + const esValue = this !== null && this !== undefined ? this : globalObject; + if (!exports.is(esValue)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + CEReactions.preSteps(globalObject); + try { + return utils.tryWrapperForImpl(esValue[implSymbol].promiseOperation()); + } finally { + CEReactions.postSteps(globalObject); + } + } catch (e) { + return Promise.reject(e); + } + } + get attr() { const esValue = this !== null && this !== undefined ? this : globalObject; @@ -479,10 +497,31 @@ exports.install = (globalObject, globalNames) => { CEReactions.postSteps(globalObject); } } + + get promiseAttribute() { + try { + const esValue = this !== null && this !== undefined ? this : globalObject; + + if (!exports.is(esValue)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + CEReactions.preSteps(globalObject); + try { + return utils.tryWrapperForImpl(esValue[implSymbol][\\"promiseAttribute\\"]); + } finally { + CEReactions.postSteps(globalObject); + } + } catch (e) { + return Promise.reject(e); + } + } } Object.defineProperties(CEReactions.prototype, { method: { enumerable: true }, + promiseOperation: { enumerable: true }, attr: { enumerable: true }, + promiseAttribute: { enumerable: true }, [Symbol.toStringTag]: { value: \\"CEReactions\\", configurable: true } }); if (globalObject[ctorRegistrySymbol] === undefined) { @@ -3555,7 +3594,43 @@ exports.createImpl = (globalObject, constructorArgs, privateData) => { return utils.implForWrapper(wrapper); }; -exports._internalSetup = (wrapper, globalObject) => {}; +exports._internalSetup = (wrapper, globalObject) => { + Object.defineProperties( + wrapper, + Object.getOwnPropertyDescriptors({ + unforgeablePromiseOperation() { + try { + const esValue = this !== null && this !== undefined ? this : globalObject; + if (!exports.is(esValue)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + return utils.tryWrapperForImpl(esValue[implSymbol].unforgeablePromiseOperation()); + } catch (e) { + return Promise.reject(e); + } + }, + get unforgeablePromiseAttribute() { + try { + const esValue = this !== null && this !== undefined ? this : globalObject; + + if (!exports.is(esValue)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + return utils.tryWrapperForImpl(esValue[implSymbol][\\"unforgeablePromiseAttribute\\"]); + } catch (e) { + return Promise.reject(e); + } + } + }) + ); + + Object.defineProperties(wrapper, { + unforgeablePromiseOperation: { configurable: false, writable: false }, + unforgeablePromiseAttribute: { configurable: false } + }); +}; exports.setup = (wrapper, globalObject, constructorArgs = [], privateData = {}) => { privateData.wrapper = wrapper; @@ -3655,12 +3730,63 @@ exports.install = (globalObject, globalNames) => { } return esValue[implSymbol].promiseConsumer(...args); } + + promiseOperation() { + try { + const esValue = this !== null && this !== undefined ? this : globalObject; + if (!exports.is(esValue)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + return utils.tryWrapperForImpl(esValue[implSymbol].promiseOperation()); + } catch (e) { + return Promise.reject(e); + } + } + + get promiseAttribute() { + try { + const esValue = this !== null && this !== undefined ? this : globalObject; + + if (!exports.is(esValue)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + return utils.tryWrapperForImpl(esValue[implSymbol][\\"promiseAttribute\\"]); + } catch (e) { + return Promise.reject(e); + } + } + + static staticPromiseOperation() { + try { + return utils.tryWrapperForImpl(Impl.implementation.staticPromiseOperation()); + } catch (e) { + return Promise.reject(e); + } + } + + static get staticPromiseAttribute() { + try { + const esValue = this !== null && this !== undefined ? this : globalObject; + + return Impl.implementation[\\"staticPromiseAttribute\\"]; + } catch (e) { + return Promise.reject(e); + } + } } Object.defineProperties(PromiseTypes.prototype, { voidPromiseConsumer: { enumerable: true }, promiseConsumer: { enumerable: true }, + promiseOperation: { enumerable: true }, + promiseAttribute: { enumerable: true }, [Symbol.toStringTag]: { value: \\"PromiseTypes\\", configurable: true } }); + Object.defineProperties(PromiseTypes, { + staticPromiseOperation: { enumerable: true }, + staticPromiseAttribute: { enumerable: true } + }); if (globalObject[ctorRegistrySymbol] === undefined) { globalObject[ctorRegistrySymbol] = Object.create(null); } @@ -9166,6 +9292,19 @@ exports.install = (globalObject, globalNames) => { return esValue[implSymbol].method(); } + promiseOperation() { + try { + const esValue = this !== null && this !== undefined ? this : globalObject; + if (!exports.is(esValue)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + return utils.tryWrapperForImpl(esValue[implSymbol].promiseOperation()); + } catch (e) { + return Promise.reject(e); + } + } + get attr() { const esValue = this !== null && this !== undefined ? this : globalObject; @@ -9189,10 +9328,26 @@ exports.install = (globalObject, globalNames) => { esValue[implSymbol][\\"attr\\"] = V; } + + get promiseAttribute() { + try { + const esValue = this !== null && this !== undefined ? this : globalObject; + + if (!exports.is(esValue)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + return utils.tryWrapperForImpl(esValue[implSymbol][\\"promiseAttribute\\"]); + } catch (e) { + return Promise.reject(e); + } + } } Object.defineProperties(CEReactions.prototype, { method: { enumerable: true }, + promiseOperation: { enumerable: true }, attr: { enumerable: true }, + promiseAttribute: { enumerable: true }, [Symbol.toStringTag]: { value: \\"CEReactions\\", configurable: true } }); if (globalObject[ctorRegistrySymbol] === undefined) { @@ -12249,7 +12404,43 @@ exports.createImpl = (globalObject, constructorArgs, privateData) => { return utils.implForWrapper(wrapper); }; -exports._internalSetup = (wrapper, globalObject) => {}; +exports._internalSetup = (wrapper, globalObject) => { + Object.defineProperties( + wrapper, + Object.getOwnPropertyDescriptors({ + unforgeablePromiseOperation() { + try { + const esValue = this !== null && this !== undefined ? this : globalObject; + if (!exports.is(esValue)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + return utils.tryWrapperForImpl(esValue[implSymbol].unforgeablePromiseOperation()); + } catch (e) { + return Promise.reject(e); + } + }, + get unforgeablePromiseAttribute() { + try { + const esValue = this !== null && this !== undefined ? this : globalObject; + + if (!exports.is(esValue)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + return utils.tryWrapperForImpl(esValue[implSymbol][\\"unforgeablePromiseAttribute\\"]); + } catch (e) { + return Promise.reject(e); + } + } + }) + ); + + Object.defineProperties(wrapper, { + unforgeablePromiseOperation: { configurable: false, writable: false }, + unforgeablePromiseAttribute: { configurable: false } + }); +}; exports.setup = (wrapper, globalObject, constructorArgs = [], privateData = {}) => { privateData.wrapper = wrapper; @@ -12349,12 +12540,63 @@ exports.install = (globalObject, globalNames) => { } return esValue[implSymbol].promiseConsumer(...args); } + + promiseOperation() { + try { + const esValue = this !== null && this !== undefined ? this : globalObject; + if (!exports.is(esValue)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + return utils.tryWrapperForImpl(esValue[implSymbol].promiseOperation()); + } catch (e) { + return Promise.reject(e); + } + } + + get promiseAttribute() { + try { + const esValue = this !== null && this !== undefined ? this : globalObject; + + if (!exports.is(esValue)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + return utils.tryWrapperForImpl(esValue[implSymbol][\\"promiseAttribute\\"]); + } catch (e) { + return Promise.reject(e); + } + } + + static staticPromiseOperation() { + try { + return utils.tryWrapperForImpl(Impl.implementation.staticPromiseOperation()); + } catch (e) { + return Promise.reject(e); + } + } + + static get staticPromiseAttribute() { + try { + const esValue = this !== null && this !== undefined ? this : globalObject; + + return Impl.implementation[\\"staticPromiseAttribute\\"]; + } catch (e) { + return Promise.reject(e); + } + } } Object.defineProperties(PromiseTypes.prototype, { voidPromiseConsumer: { enumerable: true }, promiseConsumer: { enumerable: true }, + promiseOperation: { enumerable: true }, + promiseAttribute: { enumerable: true }, [Symbol.toStringTag]: { value: \\"PromiseTypes\\", configurable: true } }); + Object.defineProperties(PromiseTypes, { + staticPromiseOperation: { enumerable: true }, + staticPromiseAttribute: { enumerable: true } + }); if (globalObject[ctorRegistrySymbol] === undefined) { globalObject[ctorRegistrySymbol] = Object.create(null); } diff --git a/test/cases/CEReactions.webidl b/test/cases/CEReactions.webidl index 655fc097..3d686a2e 100644 --- a/test/cases/CEReactions.webidl +++ b/test/cases/CEReactions.webidl @@ -6,4 +6,7 @@ interface CEReactions { getter DOMString (DOMString name); [CEReactions] setter void (DOMString name, DOMString value); [CEReactions] deleter void (DOMString name); + + [CEReactions] Promise promiseOperation(); + [CEReactions] readonly attribute Promise promiseAttribute; }; diff --git a/test/cases/Overloads.webidl b/test/cases/Overloads.webidl index f4d53363..175789ee 100644 --- a/test/cases/Overloads.webidl +++ b/test/cases/Overloads.webidl @@ -6,7 +6,7 @@ interface Overloads { DOMString compatible(DOMString arg1); byte compatible(DOMString arg1, DOMString arg2); - Promise compatible(DOMString arg1, DOMString arg2, optional long arg3 = 0); + URL compatible(DOMString arg1, DOMString arg2, optional long arg3 = 0); DOMString incompatible1(DOMString arg1); byte incompatible1(long arg1); diff --git a/test/cases/PromiseTypes.webidl b/test/cases/PromiseTypes.webidl index fb79c67e..8400c49a 100644 --- a/test/cases/PromiseTypes.webidl +++ b/test/cases/PromiseTypes.webidl @@ -2,4 +2,13 @@ interface PromiseTypes { void voidPromiseConsumer(Promise p); void promiseConsumer(Promise p); + + Promise promiseOperation(); + readonly attribute Promise promiseAttribute; + + [LegacyUnforgeable] Promise unforgeablePromiseOperation(); + [LegacyUnforgeable] readonly attribute Promise unforgeablePromiseAttribute; + + static Promise staticPromiseOperation(); + static readonly attribute Promise staticPromiseAttribute; };