diff --git a/README.md b/README.md index 523cee26..ed2aea1c 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,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. + +The resulting function has an `objectReference` property, which 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 })` @@ -367,6 +381,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 @@ -399,7 +414,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..e8ec206f --- /dev/null +++ b/lib/constructs/callback-interface.js @@ -0,0 +1,233 @@ +"use strict"; + +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: + if (!this.ctx.options.suppressErrors) { + throw new Error( + `Unknown 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 }); + } + + addProperty() {} + + generateConversion() { + const { operation, name } = this; + const opName = operation.name; + + const isAsyncInUnion = operation.idlType.union && + operation.idlType.idlType.some(idlType => idlType.generic === "Promise"); + const isAsync = isAsyncInUnion || 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. + for (const argName of argNames) { + this.str += ` + ${argName} = utils.tryWrapperForImpl(${argName}); + `; + } + + const argsToReflectCall = argNames.length > 0 ? `, ${argNames.join(", ")}` : ""; + this.str += ` + let callResult = Reflect.call(X, thisArg${argsToReflectCall}); + `; + + 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 eaef2824..54c5245c 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/context.js b/lib/context.js index 48343ca0..c7045a31 100644 --- a/lib/context.js +++ b/lib/context.js @@ -33,6 +33,7 @@ class Context { this.typedefs = new Map(); this.interfaces = new Map(); this.interfaceMixins = new Map(); + this.callbackInterfaces = new Map(); this.dictionaries = new Map(); this.enumerations = new Map(); @@ -48,6 +49,9 @@ class Context { if (this.interfaces.has(name)) { return "interface"; } + if (this.callbackInterfaces.has(name)) { + return "callback interface"; + } if (this.dictionaries.has(name)) { return "dictionary"; } diff --git a/lib/transformer.js b/lib/transformer.js index b7f04c2a..cb4a3d45 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"); @@ -82,7 +83,7 @@ class Transformer { })); this.ctx.initialize(); - const { interfaces, interfaceMixins, dictionaries, enumerations, typedefs } = this.ctx; + const { interfaces, interfaceMixins, callbackInterfaces, dictionaries, enumerations, typedefs } = this.ctx; // first we're gathering all full interfaces and ignore partial ones for (const file of parsed) { @@ -107,6 +108,10 @@ class Transformer { obj = new InterfaceMixin(this.ctx, instruction); interfaceMixins.set(obj.name, obj); break; + case "callback interface": + obj = new CallbackInterface(this.ctx, instruction); + callbackInterfaces.set(obj.name, obj); + break; case "includes": break; // handled later case "dictionary": @@ -192,7 +197,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(); @@ -203,11 +213,6 @@ class Transformer { implFile = "./" + implFile; } - let relativeUtils = path.relative(outputDir, this.utilPath).replace(/\\/g, "/"); - if (relativeUtils[0] !== ".") { - relativeUtils = "./" + relativeUtils; - } - source = ` "use strict"; @@ -222,14 +227,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 9f572155..34575e09 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 + // callbacks, dictionaries, enumerations, and interfaces let fn; // Avoid requiring the interface itself if (idlType.idlType !== parentName) { @@ -454,7 +454,7 @@ function extractUnionInfo(ctx, idlType, errPrefix) { seen.object = true; } else if (item.idlType === "boolean") { seen.boolean = item; - } else if (item.idlType === "Function") { + } else if (item.idlType === "Function" || ctx.callbackInterfaces.has(item.idlType)) { // TODO: add full support for callback functions if (seen.object) { error("Callback functions are not distinguishable with object type"); diff --git a/lib/utils.js b/lib/utils.js index 598d2f10..e3df8263 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -65,6 +65,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 +}; + class RequiresMap extends Map { constructor(ctx) { super(); @@ -114,5 +142,7 @@ module.exports = { isOnInstance, stringifyPropertyKey, stringifyPropertyName, + getPropertyDescriptorModifier, + defaultDefinePropertyDescriptor, RequiresMap }; diff --git a/test/__snapshots__/test.js.snap b/test/__snapshots__/test.js.snap index 68a51927..e2d10939 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.call(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\\"; @@ -1091,6 +1143,240 @@ 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.call(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 impl = utils.implSymbol; +const ctorRegistry = utils.ctorRegistrySymbol; + +const interfaceName = \\"EventTarget\\"; + +exports.is = function is(obj) { + return utils.isObject(obj) && utils.hasOwn(obj, impl) && obj[impl] instanceof Impl.implementation; +}; +exports.isImpl = function isImpl(obj) { + return utils.isObject(obj) && obj instanceof Impl.implementation; +}; +exports.convert = function convert(obj, { context = \\"The provided value\\" } = {}) { + if (exports.is(obj)) { + return utils.implForWrapper(obj); + } + throw new TypeError(\`\${context} is not of type 'EventTarget'.\`); +}; + +exports.create = function create(globalObject, constructorArgs, privateData) { + if (globalObject[ctorRegistry] === undefined) { + throw new Error(\\"Internal error: invalid global object\\"); + } + + const ctor = globalObject[ctorRegistry][\\"EventTarget\\"]; + if (ctor === undefined) { + throw new Error(\\"Internal error: constructor EventTarget is not installed on the passed global object\\"); + } + + let obj = Object.create(ctor.prototype); + obj = exports.setup(obj, globalObject, constructorArgs, privateData); + return obj; +}; +exports.createImpl = function createImpl(globalObject, constructorArgs, privateData) { + const obj = exports.create(globalObject, constructorArgs, privateData); + return utils.implForWrapper(obj); +}; +exports._internalSetup = function _internalSetup(obj) {}; +exports.setup = function setup(obj, globalObject, constructorArgs = [], privateData = {}) { + privateData.wrapper = obj; + + exports._internalSetup(obj); + Object.defineProperty(obj, impl, { + value: new Impl.implementation(globalObject, constructorArgs, privateData), + configurable: true + }); + + obj[impl][utils.wrapperSymbol] = obj; + if (Impl.init) { + Impl.init(obj[impl], privateData); + } + return obj; +}; + +exports.install = function install(globalObject) { + class EventTarget { + constructor() { + return exports.setup(Object.create(new.target.prototype), globalObject, undefined); + } + + addEventListener(type, callback) { + if (!this || !exports.is(this)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + if (arguments.length < 2) { + throw new TypeError( + \\"Failed to execute 'addEventListener' on 'EventTarget': 2 arguments required, but only \\" + + arguments.length + + \\" present.\\" + ); + } + const args = []; + { + let curArg = arguments[0]; + curArg = conversions[\\"DOMString\\"](curArg, { + context: \\"Failed to execute 'addEventListener' on 'EventTarget': parameter 1\\" + }); + args.push(curArg); + } + { + let curArg = arguments[1]; + if (curArg === null || curArg === undefined) { + curArg = null; + } else { + curArg = EventListener.convert(curArg, { + context: \\"Failed to execute 'addEventListener' on 'EventTarget': parameter 2\\" + }); + } + args.push(curArg); + } + { + let curArg = arguments[2]; + if (curArg !== undefined) { + curArg = conversions[\\"any\\"](curArg, { + context: \\"Failed to execute 'addEventListener' on 'EventTarget': parameter 3\\" + }); + } + args.push(curArg); + } + return this[impl].addEventListener(...args); + } + + removeEventListener(type, callback) { + if (!this || !exports.is(this)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + if (arguments.length < 2) { + throw new TypeError( + \\"Failed to execute 'removeEventListener' on 'EventTarget': 2 arguments required, but only \\" + + arguments.length + + \\" present.\\" + ); + } + const args = []; + { + let curArg = arguments[0]; + curArg = conversions[\\"DOMString\\"](curArg, { + context: \\"Failed to execute 'removeEventListener' on 'EventTarget': parameter 1\\" + }); + args.push(curArg); + } + { + let curArg = arguments[1]; + if (curArg === null || curArg === undefined) { + curArg = null; + } else { + curArg = EventListener.convert(curArg, { + context: \\"Failed to execute 'removeEventListener' on 'EventTarget': parameter 2\\" + }); + } + args.push(curArg); + } + { + let curArg = arguments[2]; + if (curArg !== undefined) { + curArg = conversions[\\"any\\"](curArg, { + context: \\"Failed to execute 'removeEventListener' on 'EventTarget': parameter 3\\" + }); + } + args.push(curArg); + } + return this[impl].removeEventListener(...args); + } + + dispatchEvent(event) { + if (!this || !exports.is(this)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + if (arguments.length < 1) { + throw new TypeError( + \\"Failed to execute 'dispatchEvent' on 'EventTarget': 1 argument required, but only \\" + + arguments.length + + \\" present.\\" + ); + } + const args = []; + { + let curArg = arguments[0]; + curArg = utils.tryImplForWrapper(curArg); + args.push(curArg); + } + return this[impl].dispatchEvent(...args); + } + } + Object.defineProperties(EventTarget.prototype, { + addEventListener: { enumerable: true }, + removeEventListener: { enumerable: true }, + dispatchEvent: { enumerable: true }, + [Symbol.toStringTag]: { value: \\"EventTarget\\", configurable: true } + }); + if (globalObject[ctorRegistry] === undefined) { + globalObject[ctorRegistry] = Object.create(null); + } + globalObject[ctorRegistry][interfaceName] = EventTarget; + + Object.defineProperty(globalObject, interfaceName, { + configurable: true, + writable: true, + value: EventTarget + }); +}; + +const Impl = require(\\"../implementations/EventTarget.js\\"); +" +`; + exports[`with processors Global.webidl 1`] = ` "\\"use strict\\"; @@ -1587,6 +1873,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.call(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\\"; @@ -6792,6 +7150,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.call(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\\"; @@ -7852,6 +8262,240 @@ 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.call(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 impl = utils.implSymbol; +const ctorRegistry = utils.ctorRegistrySymbol; + +const interfaceName = \\"EventTarget\\"; + +exports.is = function is(obj) { + return utils.isObject(obj) && utils.hasOwn(obj, impl) && obj[impl] instanceof Impl.implementation; +}; +exports.isImpl = function isImpl(obj) { + return utils.isObject(obj) && obj instanceof Impl.implementation; +}; +exports.convert = function convert(obj, { context = \\"The provided value\\" } = {}) { + if (exports.is(obj)) { + return utils.implForWrapper(obj); + } + throw new TypeError(\`\${context} is not of type 'EventTarget'.\`); +}; + +exports.create = function create(globalObject, constructorArgs, privateData) { + if (globalObject[ctorRegistry] === undefined) { + throw new Error(\\"Internal error: invalid global object\\"); + } + + const ctor = globalObject[ctorRegistry][\\"EventTarget\\"]; + if (ctor === undefined) { + throw new Error(\\"Internal error: constructor EventTarget is not installed on the passed global object\\"); + } + + let obj = Object.create(ctor.prototype); + obj = exports.setup(obj, globalObject, constructorArgs, privateData); + return obj; +}; +exports.createImpl = function createImpl(globalObject, constructorArgs, privateData) { + const obj = exports.create(globalObject, constructorArgs, privateData); + return utils.implForWrapper(obj); +}; +exports._internalSetup = function _internalSetup(obj) {}; +exports.setup = function setup(obj, globalObject, constructorArgs = [], privateData = {}) { + privateData.wrapper = obj; + + exports._internalSetup(obj); + Object.defineProperty(obj, impl, { + value: new Impl.implementation(globalObject, constructorArgs, privateData), + configurable: true + }); + + obj[impl][utils.wrapperSymbol] = obj; + if (Impl.init) { + Impl.init(obj[impl], privateData); + } + return obj; +}; + +exports.install = function install(globalObject) { + class EventTarget { + constructor() { + return exports.setup(Object.create(new.target.prototype), globalObject, undefined); + } + + addEventListener(type, callback) { + if (!this || !exports.is(this)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + if (arguments.length < 2) { + throw new TypeError( + \\"Failed to execute 'addEventListener' on 'EventTarget': 2 arguments required, but only \\" + + arguments.length + + \\" present.\\" + ); + } + const args = []; + { + let curArg = arguments[0]; + curArg = conversions[\\"DOMString\\"](curArg, { + context: \\"Failed to execute 'addEventListener' on 'EventTarget': parameter 1\\" + }); + args.push(curArg); + } + { + let curArg = arguments[1]; + if (curArg === null || curArg === undefined) { + curArg = null; + } else { + curArg = EventListener.convert(curArg, { + context: \\"Failed to execute 'addEventListener' on 'EventTarget': parameter 2\\" + }); + } + args.push(curArg); + } + { + let curArg = arguments[2]; + if (curArg !== undefined) { + curArg = conversions[\\"any\\"](curArg, { + context: \\"Failed to execute 'addEventListener' on 'EventTarget': parameter 3\\" + }); + } + args.push(curArg); + } + return this[impl].addEventListener(...args); + } + + removeEventListener(type, callback) { + if (!this || !exports.is(this)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + if (arguments.length < 2) { + throw new TypeError( + \\"Failed to execute 'removeEventListener' on 'EventTarget': 2 arguments required, but only \\" + + arguments.length + + \\" present.\\" + ); + } + const args = []; + { + let curArg = arguments[0]; + curArg = conversions[\\"DOMString\\"](curArg, { + context: \\"Failed to execute 'removeEventListener' on 'EventTarget': parameter 1\\" + }); + args.push(curArg); + } + { + let curArg = arguments[1]; + if (curArg === null || curArg === undefined) { + curArg = null; + } else { + curArg = EventListener.convert(curArg, { + context: \\"Failed to execute 'removeEventListener' on 'EventTarget': parameter 2\\" + }); + } + args.push(curArg); + } + { + let curArg = arguments[2]; + if (curArg !== undefined) { + curArg = conversions[\\"any\\"](curArg, { + context: \\"Failed to execute 'removeEventListener' on 'EventTarget': parameter 3\\" + }); + } + args.push(curArg); + } + return this[impl].removeEventListener(...args); + } + + dispatchEvent(event) { + if (!this || !exports.is(this)) { + throw new TypeError(\\"Illegal invocation\\"); + } + + if (arguments.length < 1) { + throw new TypeError( + \\"Failed to execute 'dispatchEvent' on 'EventTarget': 1 argument required, but only \\" + + arguments.length + + \\" present.\\" + ); + } + const args = []; + { + let curArg = arguments[0]; + curArg = utils.tryImplForWrapper(curArg); + args.push(curArg); + } + return this[impl].dispatchEvent(...args); + } + } + Object.defineProperties(EventTarget.prototype, { + addEventListener: { enumerable: true }, + removeEventListener: { enumerable: true }, + dispatchEvent: { enumerable: true }, + [Symbol.toStringTag]: { value: \\"EventTarget\\", configurable: true } + }); + if (globalObject[ctorRegistry] === undefined) { + globalObject[ctorRegistry] = Object.create(null); + } + globalObject[ctorRegistry][interfaceName] = EventTarget; + + Object.defineProperty(globalObject, interfaceName, { + configurable: true, + writable: true, + value: EventTarget + }); +}; + +const Impl = require(\\"../implementations/EventTarget.js\\"); +" +`; + exports[`without processors Global.webidl 1`] = ` "\\"use strict\\"; @@ -8347,6 +8991,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.call(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 new file mode 100644 index 00000000..a5a6c90c --- /dev/null +++ b/test/cases/EventTarget.webidl @@ -0,0 +1,8 @@ +[Exposed=(Window,Worker,AudioWorklet)] +interface EventTarget { + constructor(); + + void addEventListener(DOMString type, EventListener? callback, optional any options); + void removeEventListener(DOMString type, EventListener? callback, optional any options); + boolean dispatchEvent(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); +};