Skip to content

Commit

Permalink
Preliminary support for callback interfaces
Browse files Browse the repository at this point in the history
Implemented JS-to-IDL type conversion but not:

- Saving callback context [1]
- Call a user object's operation [2]
- IDL-to-JS type conversion (#71)
- Legacy callback interface object [3]

Fixes jsdom/jsdom#2869.

[1]: https://heycam.github.io/webidl/#dfn-callback-context
[2]: https://heycam.github.io/webidl/#call-a-user-objects-operation
[3]: https://heycam.github.io/webidl/#dfn-legacy-callback-interface-object
  • Loading branch information
TimothyGu authored Mar 26, 2020
1 parent 36bd91c commit df5ad76
Show file tree
Hide file tree
Showing 5 changed files with 315 additions and 10 deletions.
4 changes: 4 additions & 0 deletions lib/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,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();

Expand All @@ -50,6 +51,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";
}
Expand Down
6 changes: 5 additions & 1 deletion lib/transformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,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) {
Expand All @@ -108,6 +108,10 @@ class Transformer {
obj = new InterfaceMixin(this.ctx, instruction);
interfaceMixins.set(obj.name, obj);
break;
case "callback interface":
obj = { name: instruction.name }; // Not fully implemented yet.
callbackInterfaces.set(obj.name, obj);
break;
case "includes":
break; // handled later
case "dictionary":
Expand Down
57 changes: 48 additions & 9 deletions lib/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ 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.
Expand Down Expand Up @@ -205,7 +212,7 @@ function generateTypeConversion(ctx, name, idlType, argAttrs = [], parentName, e
output.push(`if (typeof ${name} === "function") {}`);
}

if (union.sequenceLike || union.dictionary || union.record || union.object) {
if (union.sequenceLike || union.dictionary || union.record || union.object || union.callbackInterface) {
let code = `if (utils.isObject(${name})) {`;

if (union.sequenceLike) {
Expand All @@ -217,10 +224,19 @@ function generateTypeConversion(ctx, name, idlType, argAttrs = [], parentName, e
code += `} else {`;
}

if (union.dictionary || union.record) {
const prop = union.dictionary ? "dictionary" : "record";
const conv = generateTypeConversion(ctx, name, union[prop], [], parentName,
`${errPrefix} + " ${prop}"`);
if (union.dictionary) {
const conv = generateTypeConversion(ctx, name, union.dictionary, [], parentName,
`${errPrefix} + " dictionary"`);
requires.merge(conv.requires);
code += conv.body;
} else if (union.record) {
const conv = generateTypeConversion(ctx, name, union.record, [], parentName,
`${errPrefix} + " record"`);
requires.merge(conv.requires);
code += conv.body;
} else if (union.callbackInterface) {
const conv = generateTypeConversion(ctx, name, union.callbackInterface, [], parentName,
`${errPrefix} + " callback interface"`);
requires.merge(conv.requires);
code += conv.body;
} else if (union.object) {
Expand Down Expand Up @@ -381,7 +397,7 @@ function extractUnionInfo(ctx, idlType, errPrefix) {
sequenceLike: null,
record: null,
get dictionaryLike() {
return this.dictionary !== null || this.record !== null;
return this.dictionary !== null || this.record !== null || this.callbackInterface !== null;
},
ArrayBuffer: false,
ArrayBufferViews: new Set(),
Expand All @@ -395,6 +411,7 @@ function extractUnionInfo(ctx, idlType, errPrefix) {
// Callback function, not interface
callback: false,
dictionary: null,
callbackInterface: null,
interfaces: new Set(),
get interfaceLike() {
return this.interfaces.size > 0 || this.BufferSource;
Expand All @@ -408,7 +425,13 @@ function extractUnionInfo(ctx, idlType, errPrefix) {
}
seen.sequenceLike = item;
} else if (item.generic === "record") {
if (seen.record || seen.dictionary) {
if (seen.object) {
error("Dictionary-like types are not distinguishable with object type");
}
if (seen.callback) {
error("Dictionary-like types are not distinguishable with callback functions");
}
if (seen.dictionaryLike) {
error("There can only be one dictionary-like type in a union type");
}
seen.record = item;
Expand Down Expand Up @@ -474,6 +497,17 @@ function extractUnionInfo(ctx, idlType, errPrefix) {
error("There can only be one dictionary-like type in a union type");
}
seen.dictionary = item;
} else if (ctx.callbackInterfaces.has(item.idlType)) {
if (seen.object) {
error("Dictionary-like types are not distinguishable with object type");
}
if (seen.callback) {
error("Dictionary-like types are not distinguishable with callback functions");
}
if (seen.dictionaryLike) {
error("There can only be one dictionary-like type in a union type");
}
seen.callbackInterface = item.idlType;
} else if (ctx.interfaces.has(item.idlType)) {
if (seen.object) {
error("Interface types are not distinguishable with object type");
Expand Down Expand Up @@ -570,6 +604,7 @@ function sameType(ctx, type1, type2) {
sameType(ctx, extracted1.dictionary, extracted2.dictionary) &&
JSON.stringify([...extracted1.interfaces].sort()) ===
JSON.stringify([...extracted2.interfaces].sort()) &&
extracted1.callbackInterface === extracted2.callbackInterface &&
extracted1.unknown === extracted2.unknown;
}

Expand Down Expand Up @@ -621,8 +656,12 @@ function areDistinguishable(ctx, type1, type2) {
bufferSourceTypes.has(inner1.idlType);
const isInterfaceLike2 = ctx.interfaces.has(inner2.idlType) ||
bufferSourceTypes.has(inner2.idlType);
const isDictionaryLike1 = ctx.dictionaries.has(inner1.idlType) || inner1.generic === "record";
const isDictionaryLike2 = ctx.dictionaries.has(inner2.idlType) || inner2.generic === "record";
const isDictionaryLike1 = ctx.dictionaries.has(inner1.idlType) ||
ctx.callbackInterfaces.has(inner1.idlType) ||
inner1.generic === "record";
const isDictionaryLike2 = ctx.dictionaries.has(inner2.idlType) ||
ctx.callbackInterfaces.has(inner2.idlType) ||
inner2.generic === "record";
const isSequenceLike1 = inner1.generic === "sequence" || inner1.generic === "FrozenArray";
const isSequenceLike2 = inner2.generic === "sequence" || inner2.generic === "FrozenArray";

Expand Down
Loading

0 comments on commit df5ad76

Please sign in to comment.