webidl2js
Version:
Auto-generates class structures for WebIDL specifications
205 lines (168 loc) • 5.34 kB
JavaScript
;
const conversions = require("webidl-conversions");
const utils = require("../utils.js");
const Types = require("../types.js");
class CallbackFunction {
constructor(ctx, idl) {
this.ctx = ctx;
this.idl = idl;
this.name = idl.name;
this.str = null;
this.requires = new utils.RequiresMap(ctx);
this.legacyTreatNonObjectAsNull = Boolean(utils.getExtAttr(idl.extAttrs, "LegacyTreatNonObjectAsNull"));
}
generateConversion() {
const { idl, legacyTreatNonObjectAsNull } = this;
const isAsync = idl.idlType.generic === "Promise";
const assertCallable = legacyTreatNonObjectAsNull ?
"" :
`
if (typeof value !== "function") {
throw new globalObject.TypeError(context + " is not a function");
}
`;
let returnIDL = "";
if (idl.idlType.idlType !== "undefined") {
const conv = Types.generateTypeConversion(this.ctx, "callResult", idl.idlType, [], this.name, "context");
this.requires.merge(conv.requires);
returnIDL = `
${conv.body}
return callResult;
`;
}
// This is a simplification of https://heycam.github.io/webidl/#web-idl-arguments-list-converting that currently
// fits our needs.
let argsToES = "";
let inputArgs = "";
let applyArgs = "[]";
if (idl.arguments.length > 0) {
if (idl.arguments.every(arg => !arg.optional && !arg.variadic)) {
const argNames = idl.arguments.map(arg => arg.name);
inputArgs = argNames.join(", ");
applyArgs = `[${inputArgs}]`;
for (const arg of idl.arguments) {
const argName = arg.name;
if (arg.idlType.union ?
arg.idlType.idlType.some(type => !conversions[type.idlType]) :
!conversions[arg.idlType.idlType]) {
argsToES += `
${argName} = utils.tryWrapperForImpl(${argName});
`;
}
}
} else {
const maxArgs = idl.arguments.some(arg => arg.variadic) ? Infinity : idl.arguments.length;
let minArgs = 0;
for (const arg of idl.arguments) {
if (arg.optional || arg.variadic) {
break;
}
minArgs++;
}
if (maxArgs > 0) {
inputArgs = "...args";
applyArgs = "args";
const maxArgsLoop = Number.isFinite(maxArgs) ?
`Math.min(args.length, ${maxArgs})` :
"args.length";
argsToES += `
for (let i = 0; i < ${maxArgsLoop}; i++) {
args[i] = utils.tryWrapperForImpl(args[i]);
}
`;
if (minArgs > 0) {
argsToES += `
if (args.length < ${minArgs}) {
for (let i = args.length; i < ${minArgs}; i++) {
args[i] = undefined;
}
}
`;
}
if (Number.isFinite(maxArgs)) {
argsToES += `
${minArgs > 0 ? "else" : ""} if (args.length > ${maxArgs}) {
args.length = ${maxArgs};
}
`;
}
}
}
}
this.str += `
exports.convert = (globalObject, value, { context = "The provided value" } = {}) => {
${assertCallable}
function invokeTheCallbackFunction(${inputArgs}) {
const thisArg = utils.tryWrapperForImpl(this);
let callResult;
`;
if (isAsync) {
this.str += `
try {
`;
}
if (legacyTreatNonObjectAsNull) {
this.str += `
if (typeof value === "function") {
`;
}
this.str += `
${argsToES}
callResult = Reflect.apply(value, thisArg, ${applyArgs});
`;
if (legacyTreatNonObjectAsNull) {
this.str += "}";
}
this.str += `
${returnIDL}
`;
if (isAsync) {
this.str += `
} catch (err) {
return globalObject.Promise.reject(err);
}
`;
}
this.str += `
};
`;
// `[TreatNonObjctAsNull]` and `isAsync` don't apply to
// https://heycam.github.io/webidl/#construct-a-callback-function.
this.str += `
invokeTheCallbackFunction.construct = (${inputArgs}) => {
${argsToES}
let callResult = Reflect.construct(value, ${applyArgs});
${returnIDL}
};
`;
// The wrapperSymbol ensures that if the callback function is used as a return value, 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-function.
//
// The objectReference is used to implement spec text such as that discussed in
// https://github.com/whatwg/dom/issues/842.
this.str += `
invokeTheCallbackFunction[utils.wrapperSymbol] = value;
invokeTheCallbackFunction.objectReference = value;
return invokeTheCallbackFunction;
};
`;
}
generateRequires() {
this.str = `
${this.requires.generate()}
${this.str}
`;
}
generate() {
this.generateConversion();
this.generateRequires();
}
toString() {
this.str = "";
this.generate();
return this.str;
}
}
CallbackFunction.prototype.type = "callback";
module.exports = CallbackFunction;