From 06eb4ae246d8c3bbb853d24bc7eac7a38e468225 Mon Sep 17 00:00:00 2001 From: ExE Boss <3889017+ExE-Boss@users.noreply.github.com> Date: Fri, 3 Apr 2020 15:22:29 +0200 Subject: [PATCH] =?UTF-8?q?Implement=C2=A0support=20for=C2=A0callback?= =?UTF-8?q?=C2=A0interfaces=20(#172)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #178 by superseding it. Co-authored-by: Domenic Denicola --- README.md | 16 +- lib/constructs/callback-interface.js | 237 ++++++++++++++++ lib/constructs/interface.js | 41 +-- lib/transformer.js | 22 +- lib/types.js | 11 +- lib/utils.js | 30 ++ test/__snapshots__/test.js.snap | 344 ++++++++++++++++++++++- test/cases/AsyncCallbackInterface.webidl | 3 + test/cases/EventListener.webidl | 3 + test/cases/EventTarget.webidl | 4 - test/cases/NodeFilter.webidl | 24 ++ 11 files changed, 664 insertions(+), 71 deletions(-) create mode 100644 lib/constructs/callback-interface.js create mode 100644 test/cases/AsyncCallbackInterface.webidl create mode 100644 test/cases/EventListener.webidl create mode 100644 test/cases/NodeFilter.webidl diff --git a/README.md b/README.md index dca99b5a..cd245a4e 100644 --- a/README.md +++ b/README.md @@ -286,6 +286,20 @@ This function is mostly used internally, and almost never should be called by yo jsdom does this for `Window`, which is written in custom, non-webidl2js-generated code, but inherits from `EventTarget`, which is generated by webidl2js. +### For callback interfaces + +#### `convert(value, { context })` + +Performs the Web IDL conversion algorithm for this callback interface, converting _value_ into a function that performs [call a user object's operation](https://heycam.github.io/webidl/#call-a-user-objects-operation) when called, with _thisArg_ being the `this` value of the converted function. + +The resulting function has an _objectReference_ property, which is the same object as _value_ and can be used to perform identity checks, as `convert` returns a new function object every time. + +If any part of the conversion fails, _context_ can be used to describe the provided value in any resulting error message. + +#### `install(globalObject)` + +If this callback interface has constants, then this method creates a brand new legacy callback interface object and attaches it to the passed `globalObject`. Otherwise, this method is a no-op. + ### For dictionaries #### `convert(value, { context })` @@ -425,6 +439,7 @@ webidl2js is implementing an ever-growing subset of the Web IDL specification. S - Dictionary types - Enumeration types - Union types +- Callback interfaces - Callback function types, somewhat - Nullable types - `sequence<>` types @@ -457,7 +472,6 @@ Supported Web IDL extensions defined in HTML: Notable missing features include: - Namespaces -- Callback interfaces - `maplike<>` and `setlike<>` - `[AllowShared]` - `[Default]` (for `toJSON()` operations) diff --git a/lib/constructs/callback-interface.js b/lib/constructs/callback-interface.js new file mode 100644 index 00000000..ff17639b --- /dev/null +++ b/lib/constructs/callback-interface.js @@ -0,0 +1,237 @@ +"use strict"; + +const conversions = require("webidl-conversions"); + +const utils = require("../utils.js"); +const Types = require("../types.js"); +const Constant = require("./constant.js"); + +class CallbackInterface { + constructor(ctx, idl) { + this.ctx = ctx; + this.idl = idl; + this.name = idl.name; + this.str = null; + + this.requires = new utils.RequiresMap(ctx); + + this.operation = null; + this.constants = new Map(); + + this._analyzed = false; + this._outputStaticProperties = new Map(); + } + + _analyzeMembers() { + for (const member of this.idl.members) { + switch (member.type) { + case "operation": + if (this.operation !== null) { + throw new Error( + `Callback interface ${this.name} has more than one operation` + ); + } + this.operation = member; + break; + case "const": + this.constants.set(member.name, new Constant(this.ctx, this, member)); + break; + default: + throw new Error( + `Illegal IDL member type "${member.type}" in callback interface ${this.name}` + ); + } + } + + if (this.operation === null) { + throw new Error(`Callback interface ${this.name} has no operation`); + } + } + + addAllProperties() { + for (const member of this.constants.values()) { + const data = member.generate(); + this.requires.merge(data.requires); + } + } + + addStaticProperty(name, body, { configurable = true, enumerable = typeof name === "string", writable = true } = {}) { + const descriptor = { configurable, enumerable, writable }; + this._outputStaticProperties.set(name, { body, descriptor }); + } + + // This is necessary due to usage in the `Constant` and other classes + // It's empty because callback interfaces don't generate platform objects + addProperty() {} + + generateConversion() { + const { operation, name } = this; + const opName = operation.name; + const isAsync = operation.idlType.generic === "Promise"; + + const argNames = operation.arguments.map(arg => arg.name); + if (operation.arguments.some(arg => arg.optional || arg.variadic)) { + throw new Error("Internal error: optional/variadic arguments are not implemented for callback interfaces"); + } + + this.str += ` + exports.convert = function convert(value, { context = "The provided value" } = {}) { + if (!utils.isObject(value)) { + throw new TypeError(\`\${context} is not an object.\`); + } + + function callTheUserObjectsOperation(${argNames.join(", ")}) { + let thisArg = this; + let O = value; + let X = O; + `; + + if (isAsync) { + this.str += ` + try { + `; + } + + this.str += ` + if (typeof O !== "function") { + X = O[${utils.stringifyPropertyName(opName)}]; + if (typeof X !== "function") { + throw new TypeError(\`\${context} does not correctly implement ${name}.\`) + } + thisArg = O; + } + `; + + // We don't implement all of https://heycam.github.io/webidl/#web-idl-arguments-list-converting since the callers + // are assumed to always pass the correct number of arguments and we don't support optional/variadic arguments. + // See also: https://github.com/jsdom/webidl2js/issues/71 + for (const arg of operation.arguments) { + const argName = arg.name; + if (arg.idlType.union ? + arg.idlType.idlType.some(type => !conversions[type]) : + !conversions[arg.idlType.idlType]) { + this.str += ` + ${argName} = utils.tryWrapperForImpl(${argName}); + `; + } + } + + this.str += ` + let callResult = Reflect.apply(X, thisArg, [${argNames.join(", ")}]); + `; + + if (operation.idlType.idlType !== "void") { + const conv = Types.generateTypeConversion(this.ctx, "callResult", operation.idlType, [], name, "context"); + this.requires.merge(conv.requires); + this.str += ` + ${conv.body} + return callResult; + `; + } + + if (isAsync) { + this.str += ` + } catch (err) { + return Promise.reject(err); + } + `; + } + + this.str += ` + }; + `; + + // The wrapperSymbol ensures that if the callback interface is used as a return value, e.g. in NodeIterator's filter + // attribute, that it exposes the original callback back. I.e. it implements the conversion from IDL to JS value in + // https://heycam.github.io/webidl/#es-callback-interface. + // + // The objectReference is used to implement spec text such as that discussed in + // https://github.com/whatwg/dom/issues/842. + this.str += ` + callTheUserObjectsOperation[utils.wrapperSymbol] = value; + callTheUserObjectsOperation.objectReference = value; + + return callTheUserObjectsOperation; + }; + `; + } + + generateOffInstanceAfterClass() { + const classProps = new Map(); + + for (const [name, { body, descriptor }] of this._outputStaticProperties) { + const descriptorModifier = utils.getPropertyDescriptorModifier( + utils.defaultDefinePropertyDescriptor, + descriptor, + "regular", + body + ); + classProps.set(utils.stringifyPropertyKey(name), descriptorModifier); + } + + if (classProps.size > 0) { + const props = [...classProps].map(([name, body]) => `${name}: ${body}`); + this.str += ` + Object.defineProperties(${this.name}, { ${props.join(", ")} }); + `; + } + } + + generateInstall() { + this.str += ` + exports.install = function install(globalObject) { + `; + + if (this.constants.size > 0) { + const { name } = this; + + this.str += ` + const ${name} = () => { + throw new TypeError("Illegal invocation"); + }; + `; + + this.generateOffInstanceAfterClass(); + + this.str += ` + Object.defineProperty(globalObject, ${JSON.stringify(name)}, { + configurable: true, + writable: true, + value: ${name} + }); + `; + } + + this.str += ` + }; + `; + } + + generateRequires() { + this.str = ` + ${this.requires.generate()} + + ${this.str} + `; + } + + generate() { + this.generateConversion(); + this.generateInstall(); + + this.generateRequires(); + } + + toString() { + this.str = ""; + if (!this._analyzed) { + this._analyzed = true; + this._analyzeMembers(); + } + this.addAllProperties(); + this.generate(); + return this.str; + } +} + +module.exports = CallbackInterface; diff --git a/lib/constructs/interface.js b/lib/constructs/interface.js index bf90aab7..3989f6aa 100644 --- a/lib/constructs/interface.js +++ b/lib/constructs/interface.js @@ -21,12 +21,6 @@ function formatArgs(args) { return args.map(name => name + (keywords.has(name) ? "_" : "")).join(", "); } -const defaultDefinePropertyDescriptor = { - configurable: false, - enumerable: false, - writable: false -}; - const defaultObjectLiteralDescriptor = { configurable: true, enumerable: true, @@ -39,28 +33,6 @@ const defaultClassMethodDescriptor = { writable: true }; -// type can be "accessor" or "regular" -function getPropertyDescriptorModifier(currentDesc, targetDesc, type, value = undefined) { - const changes = []; - if (value !== undefined) { - changes.push(`value: ${value}`); - } - if (currentDesc.configurable !== targetDesc.configurable) { - changes.push(`configurable: ${targetDesc.configurable}`); - } - if (currentDesc.enumerable !== targetDesc.enumerable) { - changes.push(`enumerable: ${targetDesc.enumerable}`); - } - if (type !== "accessor" && currentDesc.writable !== targetDesc.writable) { - changes.push(`writable: ${targetDesc.writable}`); - } - - if (changes.length === 0) { - return undefined; - } - return `{ ${changes.join(", ")} }`; -} - class Interface { constructor(ctx, idl, opts) { this.ctx = ctx; @@ -1333,7 +1305,7 @@ class Interface { continue; } - const descriptorModifier = getPropertyDescriptorModifier(defaultClassMethodDescriptor, descriptor, type); + const descriptorModifier = utils.getPropertyDescriptorModifier(defaultClassMethodDescriptor, descriptor, type); if (descriptorModifier === undefined) { continue; } @@ -1341,7 +1313,7 @@ class Interface { } for (const [name, { type, descriptor }] of this._outputStaticMethods) { - const descriptorModifier = getPropertyDescriptorModifier(defaultClassMethodDescriptor, descriptor, type); + const descriptorModifier = utils.getPropertyDescriptorModifier(defaultClassMethodDescriptor, descriptor, type); if (descriptorModifier === undefined) { continue; } @@ -1354,13 +1326,13 @@ class Interface { } const descriptorModifier = - getPropertyDescriptorModifier(defaultDefinePropertyDescriptor, descriptor, "regular", body); + utils.getPropertyDescriptorModifier(utils.defaultDefinePropertyDescriptor, descriptor, "regular", body); protoProps.set(utils.stringifyPropertyKey(name), descriptorModifier); } for (const [name, { body, descriptor }] of this._outputStaticProperties) { const descriptorModifier = - getPropertyDescriptorModifier(defaultDefinePropertyDescriptor, descriptor, "regular", body); + utils.getPropertyDescriptorModifier(utils.defaultDefinePropertyDescriptor, descriptor, "regular", body); classProps.set(utils.stringifyPropertyKey(name), descriptorModifier); } @@ -1402,7 +1374,7 @@ class Interface { } } - const descriptorModifier = getPropertyDescriptorModifier(defaultObjectLiteralDescriptor, descriptor, type); + const descriptorModifier = utils.getPropertyDescriptorModifier(defaultObjectLiteralDescriptor, descriptor, type); if (descriptorModifier === undefined) { continue; } @@ -1417,7 +1389,8 @@ class Interface { const propName = utils.stringifyPropertyKey(name); methods.push(`${propName}: ${body}`); - const descriptorModifier = getPropertyDescriptorModifier(defaultObjectLiteralDescriptor, descriptor, "regular"); + const descriptorModifier = + utils.getPropertyDescriptorModifier(defaultObjectLiteralDescriptor, descriptor, "regular"); if (descriptorModifier === undefined) { continue; } diff --git a/lib/transformer.js b/lib/transformer.js index 58c3f1d1..02211d7a 100644 --- a/lib/transformer.js +++ b/lib/transformer.js @@ -10,6 +10,7 @@ const Context = require("./context"); const Typedef = require("./constructs/typedef"); const Interface = require("./constructs/interface"); const InterfaceMixin = require("./constructs/interface-mixin"); +const CallbackInterface = require("./constructs/callback-interface.js"); const Dictionary = require("./constructs/dictionary"); const Enumeration = require("./constructs/enumeration"); @@ -109,7 +110,7 @@ class Transformer { interfaceMixins.set(obj.name, obj); break; case "callback interface": - obj = { name: instruction.name }; // Not fully implemented yet. + obj = new CallbackInterface(this.ctx, instruction); callbackInterfaces.set(obj.name, obj); break; case "includes": @@ -197,7 +198,12 @@ class Transformer { const utilsText = await fs.readFile(path.resolve(__dirname, "output/utils.js")); await fs.writeFile(this.utilPath, utilsText); - const { interfaces, dictionaries, enumerations } = this.ctx; + const { interfaces, callbackInterfaces, dictionaries, enumerations } = this.ctx; + + let relativeUtils = path.relative(outputDir, this.utilPath).replace(/\\/g, "/"); + if (relativeUtils[0] !== ".") { + relativeUtils = "./" + relativeUtils; + } for (const obj of interfaces.values()) { let source = obj.toString(); @@ -208,11 +214,6 @@ class Transformer { implFile = "./" + implFile; } - let relativeUtils = path.relative(outputDir, this.utilPath).replace(/\\/g, "/"); - if (relativeUtils[0] !== ".") { - relativeUtils = "./" + relativeUtils; - } - source = ` "use strict"; @@ -227,14 +228,9 @@ class Transformer { await fs.writeFile(path.join(outputDir, obj.name + ".js"), source); } - for (const obj of dictionaries.values()) { + for (const obj of [...callbackInterfaces.values(), ...dictionaries.values()]) { let source = obj.toString(); - let relativeUtils = path.relative(outputDir, this.utilPath).replace(/\\/g, "/"); - if (relativeUtils[0] !== ".") { - relativeUtils = "./" + relativeUtils; - } - source = ` "use strict"; diff --git a/lib/types.js b/lib/types.js index 4ad217bb..09e66556 100644 --- a/lib/types.js +++ b/lib/types.js @@ -26,7 +26,7 @@ function mergeExtAttrs(a = [], b = []) { } // Types of types that generate an output file. -const resolvedTypes = new Set(["dictionary", "enumeration", "interface"]); +const resolvedTypes = new Set(["callback interface", "dictionary", "enumeration", "interface"]); function resolveType(ctx, idlType, stack = []) { if (resolvedMap.has(idlType)) { @@ -117,7 +117,7 @@ function generateTypeConversion(ctx, name, idlType, argAttrs = [], parentName, e // string or number type compatible with webidl-conversions generateGeneric(`conversions["${idlType.idlType}"]`); } else if (resolvedTypes.has(ctx.typeOf(idlType.idlType))) { - // dictionaries, enumerations, and interfaces + // callback interfaces, dictionaries, enumerations, and interfaces let fn; // Avoid requiring the interface itself if (idlType.idlType !== parentName) { @@ -127,13 +127,6 @@ function generateTypeConversion(ctx, name, idlType, argAttrs = [], parentName, e fn = `exports.convert`; } generateGeneric(fn); - } else if (ctx.typeOf(idlType.idlType) === "callback interface") { - // We do not save the callback context yet. - str += ` - if (!utils.isObject(${name})) { - throw new TypeError(${errPrefix} + " is not an object"); - } - `; } else { // unknown // Try to get the impl anyway. diff --git a/lib/utils.js b/lib/utils.js index d83285bb..cde06397 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -66,6 +66,34 @@ function stringifyPropertyName(prop) { return typeof prop === "symbol" ? symbolName(prop) : JSON.stringify(propertyName(prop)); } +// type can be "accessor" or "regular" +function getPropertyDescriptorModifier(currentDesc, targetDesc, type, value = undefined) { + const changes = []; + if (value !== undefined) { + changes.push(`value: ${value}`); + } + if (currentDesc.configurable !== targetDesc.configurable) { + changes.push(`configurable: ${targetDesc.configurable}`); + } + if (currentDesc.enumerable !== targetDesc.enumerable) { + changes.push(`enumerable: ${targetDesc.enumerable}`); + } + if (type !== "accessor" && currentDesc.writable !== targetDesc.writable) { + changes.push(`writable: ${targetDesc.writable}`); + } + + if (changes.length === 0) { + return undefined; + } + return `{ ${changes.join(", ")} }`; +} + +const defaultDefinePropertyDescriptor = { + configurable: false, + enumerable: false, + writable: false +}; + function toKey(type, func = "") { return String(func + type).replace(/[./-]+/g, " ").trim().replace(/ /g, "_"); } @@ -137,5 +165,7 @@ module.exports = { isOnInstance, stringifyPropertyKey, stringifyPropertyName, + getPropertyDescriptorModifier, + defaultDefinePropertyDescriptor, RequiresMap }; diff --git a/test/__snapshots__/test.js.snap b/test/__snapshots__/test.js.snap index 3d96d4ed..d7e3045b 100644 --- a/test/__snapshots__/test.js.snap +++ b/test/__snapshots__/test.js.snap @@ -1,5 +1,57 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`with processors AsyncCallbackInterface.webidl 1`] = ` +"\\"use strict\\"; + +const conversions = require(\\"webidl-conversions\\"); +const utils = require(\\"./utils.js\\"); + +exports.convert = function convert(value, { context = \\"The provided value\\" } = {}) { + if (!utils.isObject(value)) { + throw new TypeError(\`\${context} is not an object.\`); + } + + function callTheUserObjectsOperation() { + let thisArg = this; + let O = value; + let X = O; + + try { + if (typeof O !== \\"function\\") { + X = O[\\"asyncMethod\\"]; + if (typeof X !== \\"function\\") { + throw new TypeError(\`\${context} does not correctly implement AsyncCallbackInterface.\`); + } + thisArg = O; + } + + let callResult = Reflect.apply(X, thisArg, []); + + callResult = Promise.resolve(callResult).then( + value => { + value = conversions[\\"any\\"](value, { context: context + \\" promise value\\" }); + + return value; + }, + reason => reason + ); + + return callResult; + } catch (err) { + return Promise.reject(err); + } + } + + callTheUserObjectsOperation[utils.wrapperSymbol] = value; + callTheUserObjectsOperation.objectReference = value; + + return callTheUserObjectsOperation; +}; + +exports.install = function install(globalObject) {}; +" +`; + exports[`with processors BufferSourceTypes.webidl 1`] = ` "\\"use strict\\"; @@ -1112,12 +1164,52 @@ const Impl = require(\\"../implementations/Enum.js\\"); " `; +exports[`with processors EventListener.webidl 1`] = ` +"\\"use strict\\"; + +const conversions = require(\\"webidl-conversions\\"); +const utils = require(\\"./utils.js\\"); + +exports.convert = function convert(value, { context = \\"The provided value\\" } = {}) { + if (!utils.isObject(value)) { + throw new TypeError(\`\${context} is not an object.\`); + } + + function callTheUserObjectsOperation(event) { + let thisArg = this; + let O = value; + let X = O; + + if (typeof O !== \\"function\\") { + X = O[\\"handleEvent\\"]; + if (typeof X !== \\"function\\") { + throw new TypeError(\`\${context} does not correctly implement EventListener.\`); + } + thisArg = O; + } + + event = utils.tryWrapperForImpl(event); + + let callResult = Reflect.apply(X, thisArg, [event]); + } + + callTheUserObjectsOperation[utils.wrapperSymbol] = value; + callTheUserObjectsOperation.objectReference = value; + + return callTheUserObjectsOperation; +}; + +exports.install = function install(globalObject) {}; +" +`; + exports[`with processors EventTarget.webidl 1`] = ` "\\"use strict\\"; const conversions = require(\\"webidl-conversions\\"); const utils = require(\\"./utils.js\\"); +const EventListener = require(\\"./EventListener.js\\"); const implSymbol = utils.implSymbol; const ctorRegistrySymbol = utils.ctorRegistrySymbol; @@ -1203,11 +1295,9 @@ exports.install = function install(globalObject) { if (curArg === null || curArg === undefined) { curArg = null; } else { - if (!utils.isObject(curArg)) { - throw new TypeError( - \\"Failed to execute 'addEventListener' on 'EventTarget': parameter 2\\" + \\" is not an object\\" - ); - } + curArg = EventListener.convert(curArg, { + context: \\"Failed to execute 'addEventListener' on 'EventTarget': parameter 2\\" + }); } args.push(curArg); } @@ -1773,6 +1863,78 @@ const Impl = require(\\"../implementations/MixedIn.js\\"); " `; +exports[`with processors NodeFilter.webidl 1`] = ` +"\\"use strict\\"; + +const conversions = require(\\"webidl-conversions\\"); +const utils = require(\\"./utils.js\\"); + +exports.convert = function convert(value, { context = \\"The provided value\\" } = {}) { + if (!utils.isObject(value)) { + throw new TypeError(\`\${context} is not an object.\`); + } + + function callTheUserObjectsOperation(node) { + let thisArg = this; + let O = value; + let X = O; + + if (typeof O !== \\"function\\") { + X = O[\\"acceptNode\\"]; + if (typeof X !== \\"function\\") { + throw new TypeError(\`\${context} does not correctly implement NodeFilter.\`); + } + thisArg = O; + } + + node = utils.tryWrapperForImpl(node); + + let callResult = Reflect.apply(X, thisArg, [node]); + + callResult = conversions[\\"unsigned short\\"](callResult, { context: context }); + + return callResult; + } + + callTheUserObjectsOperation[utils.wrapperSymbol] = value; + callTheUserObjectsOperation.objectReference = value; + + return callTheUserObjectsOperation; +}; + +exports.install = function install(globalObject) { + const NodeFilter = () => { + throw new TypeError(\\"Illegal invocation\\"); + }; + + Object.defineProperties(NodeFilter, { + FILTER_ACCEPT: { value: 1, enumerable: true }, + FILTER_REJECT: { value: 2, enumerable: true }, + FILTER_SKIP: { value: 3, enumerable: true }, + SHOW_ALL: { value: 0xffffffff, enumerable: true }, + SHOW_ELEMENT: { value: 0x1, enumerable: true }, + SHOW_ATTRIBUTE: { value: 0x2, enumerable: true }, + SHOW_TEXT: { value: 0x4, enumerable: true }, + SHOW_CDATA_SECTION: { value: 0x8, enumerable: true }, + SHOW_ENTITY_REFERENCE: { value: 0x10, enumerable: true }, + SHOW_ENTITY: { value: 0x20, enumerable: true }, + SHOW_PROCESSING_INSTRUCTION: { value: 0x40, enumerable: true }, + SHOW_COMMENT: { value: 0x80, enumerable: true }, + SHOW_DOCUMENT: { value: 0x100, enumerable: true }, + SHOW_DOCUMENT_TYPE: { value: 0x200, enumerable: true }, + SHOW_DOCUMENT_FRAGMENT: { value: 0x400, enumerable: true }, + SHOW_NOTATION: { value: 0x800, enumerable: true } + }); + + Object.defineProperty(globalObject, \\"NodeFilter\\", { + configurable: true, + writable: true, + value: NodeFilter + }); +}; +" +`; + exports[`with processors Overloads.webidl 1`] = ` "\\"use strict\\"; @@ -7179,6 +7341,58 @@ const Impl = require(\\"../implementations/ZeroArgConstructor.js\\"); " `; +exports[`without processors AsyncCallbackInterface.webidl 1`] = ` +"\\"use strict\\"; + +const conversions = require(\\"webidl-conversions\\"); +const utils = require(\\"./utils.js\\"); + +exports.convert = function convert(value, { context = \\"The provided value\\" } = {}) { + if (!utils.isObject(value)) { + throw new TypeError(\`\${context} is not an object.\`); + } + + function callTheUserObjectsOperation() { + let thisArg = this; + let O = value; + let X = O; + + try { + if (typeof O !== \\"function\\") { + X = O[\\"asyncMethod\\"]; + if (typeof X !== \\"function\\") { + throw new TypeError(\`\${context} does not correctly implement AsyncCallbackInterface.\`); + } + thisArg = O; + } + + let callResult = Reflect.apply(X, thisArg, []); + + callResult = Promise.resolve(callResult).then( + value => { + value = conversions[\\"any\\"](value, { context: context + \\" promise value\\" }); + + return value; + }, + reason => reason + ); + + return callResult; + } catch (err) { + return Promise.reject(err); + } + } + + callTheUserObjectsOperation[utils.wrapperSymbol] = value; + callTheUserObjectsOperation.objectReference = value; + + return callTheUserObjectsOperation; +}; + +exports.install = function install(globalObject) {}; +" +`; + exports[`without processors BufferSourceTypes.webidl 1`] = ` "\\"use strict\\"; @@ -8260,12 +8474,52 @@ const Impl = require(\\"../implementations/Enum.js\\"); " `; +exports[`without processors EventListener.webidl 1`] = ` +"\\"use strict\\"; + +const conversions = require(\\"webidl-conversions\\"); +const utils = require(\\"./utils.js\\"); + +exports.convert = function convert(value, { context = \\"The provided value\\" } = {}) { + if (!utils.isObject(value)) { + throw new TypeError(\`\${context} is not an object.\`); + } + + function callTheUserObjectsOperation(event) { + let thisArg = this; + let O = value; + let X = O; + + if (typeof O !== \\"function\\") { + X = O[\\"handleEvent\\"]; + if (typeof X !== \\"function\\") { + throw new TypeError(\`\${context} does not correctly implement EventListener.\`); + } + thisArg = O; + } + + event = utils.tryWrapperForImpl(event); + + let callResult = Reflect.apply(X, thisArg, [event]); + } + + callTheUserObjectsOperation[utils.wrapperSymbol] = value; + callTheUserObjectsOperation.objectReference = value; + + return callTheUserObjectsOperation; +}; + +exports.install = function install(globalObject) {}; +" +`; + exports[`without processors EventTarget.webidl 1`] = ` "\\"use strict\\"; const conversions = require(\\"webidl-conversions\\"); const utils = require(\\"./utils.js\\"); +const EventListener = require(\\"./EventListener.js\\"); const implSymbol = utils.implSymbol; const ctorRegistrySymbol = utils.ctorRegistrySymbol; @@ -8351,11 +8605,9 @@ exports.install = function install(globalObject) { if (curArg === null || curArg === undefined) { curArg = null; } else { - if (!utils.isObject(curArg)) { - throw new TypeError( - \\"Failed to execute 'addEventListener' on 'EventTarget': parameter 2\\" + \\" is not an object\\" - ); - } + curArg = EventListener.convert(curArg, { + context: \\"Failed to execute 'addEventListener' on 'EventTarget': parameter 2\\" + }); } args.push(curArg); } @@ -8920,6 +9172,78 @@ const Impl = require(\\"../implementations/MixedIn.js\\"); " `; +exports[`without processors NodeFilter.webidl 1`] = ` +"\\"use strict\\"; + +const conversions = require(\\"webidl-conversions\\"); +const utils = require(\\"./utils.js\\"); + +exports.convert = function convert(value, { context = \\"The provided value\\" } = {}) { + if (!utils.isObject(value)) { + throw new TypeError(\`\${context} is not an object.\`); + } + + function callTheUserObjectsOperation(node) { + let thisArg = this; + let O = value; + let X = O; + + if (typeof O !== \\"function\\") { + X = O[\\"acceptNode\\"]; + if (typeof X !== \\"function\\") { + throw new TypeError(\`\${context} does not correctly implement NodeFilter.\`); + } + thisArg = O; + } + + node = utils.tryWrapperForImpl(node); + + let callResult = Reflect.apply(X, thisArg, [node]); + + callResult = conversions[\\"unsigned short\\"](callResult, { context: context }); + + return callResult; + } + + callTheUserObjectsOperation[utils.wrapperSymbol] = value; + callTheUserObjectsOperation.objectReference = value; + + return callTheUserObjectsOperation; +}; + +exports.install = function install(globalObject) { + const NodeFilter = () => { + throw new TypeError(\\"Illegal invocation\\"); + }; + + Object.defineProperties(NodeFilter, { + FILTER_ACCEPT: { value: 1, enumerable: true }, + FILTER_REJECT: { value: 2, enumerable: true }, + FILTER_SKIP: { value: 3, enumerable: true }, + SHOW_ALL: { value: 0xffffffff, enumerable: true }, + SHOW_ELEMENT: { value: 0x1, enumerable: true }, + SHOW_ATTRIBUTE: { value: 0x2, enumerable: true }, + SHOW_TEXT: { value: 0x4, enumerable: true }, + SHOW_CDATA_SECTION: { value: 0x8, enumerable: true }, + SHOW_ENTITY_REFERENCE: { value: 0x10, enumerable: true }, + SHOW_ENTITY: { value: 0x20, enumerable: true }, + SHOW_PROCESSING_INSTRUCTION: { value: 0x40, enumerable: true }, + SHOW_COMMENT: { value: 0x80, enumerable: true }, + SHOW_DOCUMENT: { value: 0x100, enumerable: true }, + SHOW_DOCUMENT_TYPE: { value: 0x200, enumerable: true }, + SHOW_DOCUMENT_FRAGMENT: { value: 0x400, enumerable: true }, + SHOW_NOTATION: { value: 0x800, enumerable: true } + }); + + Object.defineProperty(globalObject, \\"NodeFilter\\", { + configurable: true, + writable: true, + value: NodeFilter + }); +}; +" +`; + exports[`without processors Overloads.webidl 1`] = ` "\\"use strict\\"; diff --git a/test/cases/AsyncCallbackInterface.webidl b/test/cases/AsyncCallbackInterface.webidl new file mode 100644 index 00000000..b4fbd75d --- /dev/null +++ b/test/cases/AsyncCallbackInterface.webidl @@ -0,0 +1,3 @@ +callback interface AsyncCallbackInterface { + Promise asyncMethod(); +}; diff --git a/test/cases/EventListener.webidl b/test/cases/EventListener.webidl new file mode 100644 index 00000000..080ccb14 --- /dev/null +++ b/test/cases/EventListener.webidl @@ -0,0 +1,3 @@ +callback interface EventListener { + void handleEvent(Event event); +}; diff --git a/test/cases/EventTarget.webidl b/test/cases/EventTarget.webidl index 6d327d91..489f361e 100644 --- a/test/cases/EventTarget.webidl +++ b/test/cases/EventTarget.webidl @@ -8,7 +8,3 @@ interface EventTarget { // void removeEventListener(DOMString type, EventListener? callback); // boolean dispatchEvent(Event event); }; - -callback interface EventListener { - void handleEvent(/* Event event */); -}; diff --git a/test/cases/NodeFilter.webidl b/test/cases/NodeFilter.webidl new file mode 100644 index 00000000..61639d6d --- /dev/null +++ b/test/cases/NodeFilter.webidl @@ -0,0 +1,24 @@ +[Exposed=Window] +callback interface NodeFilter { + // Constants for acceptNode() + const unsigned short FILTER_ACCEPT = 1; + const unsigned short FILTER_REJECT = 2; + const unsigned short FILTER_SKIP = 3; + + // Constants for whatToShow + const unsigned long SHOW_ALL = 0xFFFFFFFF; + const unsigned long SHOW_ELEMENT = 0x1; + const unsigned long SHOW_ATTRIBUTE = 0x2; + const unsigned long SHOW_TEXT = 0x4; + const unsigned long SHOW_CDATA_SECTION = 0x8; + const unsigned long SHOW_ENTITY_REFERENCE = 0x10; // historical + const unsigned long SHOW_ENTITY = 0x20; // historical + const unsigned long SHOW_PROCESSING_INSTRUCTION = 0x40; + const unsigned long SHOW_COMMENT = 0x80; + const unsigned long SHOW_DOCUMENT = 0x100; + const unsigned long SHOW_DOCUMENT_TYPE = 0x200; + const unsigned long SHOW_DOCUMENT_FRAGMENT = 0x400; + const unsigned long SHOW_NOTATION = 0x800; // historical + + unsigned short acceptNode(Node node); +};