Skip to content

Commit

Permalink
Add webidl2js‑globals.js to handle setup of [Global]s
Browse files Browse the repository at this point in the history
  • Loading branch information
ExE-Boss committed Apr 3, 2020
1 parent 1455648 commit 3853cad
Show file tree
Hide file tree
Showing 16 changed files with 6,717 additions and 3,956 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ webidl2js is implementing an ever-growing subset of the Web IDL specification. S
- Variadic arguments
- `[Clamp]`
- `[EnforceRange]`
- `[Exposed]`
- `[LegacyArrayClass]`
- `[LegacyUnenumerableNamedProperties]`
- `[OverrideBuiltins]`
Expand All @@ -476,7 +477,6 @@ Notable missing features include:
- `[AllowShared]`
- `[Default]` (for `toJSON()` operations)
- `[Global]`'s various consequences, including the named properties object and `[[SetPrototypeOf]]`
- `[Exposed]`
- `[LegacyWindowAlias]`
- `[LenientSetter]`
- `[LenientThis]`
Expand Down
15 changes: 15 additions & 0 deletions lib/constructs/callback-interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ class CallbackInterface {

this._analyzed = false;
this._outputStaticProperties = new Map();

const exposed = utils.getExtAttr(this.idl.extAttrs, "Exposed");
if (this.idl.members.some(member => member.type === "const") && !exposed) {
throw new Error(`Callback interface ${this.name} with defined constants lacks the [Exposed] extended attribute`);
}

if (exposed) {
if (!exposed.rhs || (exposed.rhs.type !== "identifier" && exposed.rhs.type !== "identifier-list")) {
throw new Error(`[Exposed] must take an identifier or an identifier list in interface ${this.name}`);
}

this.ctx.globals.addGlobalNames(exposed.rhs.value);
}
}

_analyzeMembers() {
Expand Down Expand Up @@ -234,4 +247,6 @@ class CallbackInterface {
}
}

CallbackInterface.prototype.type = "callback interface";

module.exports = CallbackInterface;
58 changes: 56 additions & 2 deletions lib/constructs/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,25 @@ class Interface {

const global = utils.getExtAttr(this.idl.extAttrs, "Global");
this.isGlobal = Boolean(global);
if (global && !global.rhs) {
throw new Error(`[Global] must take an identifier or an identifier list in interface ${this.name}`);
if (global) {
if (!global.rhs || (global.rhs.type !== "identifier" && global.rhs.type !== "identifier-list")) {
throw new Error(`[Global] must take an identifier or an identifier list in interface ${this.name}`);
}

this.ctx.globals.addGlobalNames(global.rhs.value);
}

const exposed = utils.getExtAttr(this.idl.extAttrs, "Exposed");
if (exposed) {
if (!exposed.rhs || (exposed.rhs.type !== "identifier" && exposed.rhs.type !== "identifier-list")) {
throw new Error(`[Exposed] must take an identifier or an identifier list in interface ${this.name}`);
}

this.ctx.globals.addGlobalNames(exposed.rhs.value);
}

if (!exposed && !utils.getExtAttr(this.idl.extAttrs, "NoInterfaceObject")) {
throw new Error(`Interface ${this.name} has neither [Exposed] nor [NoInterfaceObject]`);
}
}

Expand Down Expand Up @@ -1150,6 +1167,36 @@ class Interface {
return obj;
};
`;

if (this.isGlobal) {
const globalAttr = utils.getExtAttr(this.idl.extAttrs, "Global");
const globalNames = globalAttr.rhs.type === "identifier-list" ?
globalAttr.rhs.value.map(token => token.value) :
[globalAttr.rhs.value];
const isMultiGlobal = globalNames.length > 1;

const bundleEntry = this.requires.add("./webidl2js-globals.js");
this.str += `
/**
* Initialises the passed obj as a new global.
*
* The obj is expected to contain all the global object properties
* as specified in the ECMAScript specification.
*/
exports.setupGlobal = (obj, constructorArgs = [], privateData = {}`;

if (isMultiGlobal) {
this.str += `, globalName = "${globalNames[0]}"`;
}

this.str += `) => {
${bundleEntry}.setupGlobal(obj, ${isMultiGlobal ? "globalName" : `"${globalNames[0]}"`});
Object.setPrototypeOf(obj, obj[interfaceName].prototype);
obj = exports.setup(obj, obj, constructorArgs, privateData);
};
`;
}
}

addConstructor() {
Expand Down Expand Up @@ -1444,12 +1491,19 @@ class Interface {
globalObject[ctorRegistrySymbol] = Object.create(null);
}
globalObject[ctorRegistrySymbol][interfaceName] = ${name};
`;

if (!utils.getExtAttr(this.idl.extAttrs, "NoInterfaceObject")) {
this.str += `
Object.defineProperty(globalObject, interfaceName, {
configurable: true,
writable: true,
value: ${name}
});
`;
}

this.str += `
};
`;
}
Expand Down
2 changes: 2 additions & 0 deletions lib/context.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use strict";
const webidl = require("webidl2");
const Globals = require("./globals.js");
const Typedef = require("./constructs/typedef");

const builtinTypedefs = webidl.parse(`
Expand Down Expand Up @@ -38,6 +39,7 @@ class Context {
this.callbackInterfaces = new Map();
this.dictionaries = new Map();
this.enumerations = new Map();
this.globals = new Globals(this);

for (const typedef of builtinTypedefs) {
this.typedefs.set(typedef.name, new Typedef(this, typedef));
Expand Down
143 changes: 143 additions & 0 deletions lib/globals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
"use strict";
const utils = require("./utils.js");

class Globals {
constructor(ctx) {
this.ctx = ctx;
this.requires = new utils.RequiresMap(ctx);

this.str = null;
this._globalNames = new Set();
}

addGlobalNames(globalNames) {
if (typeof globalNames === "string") {
this._globalNames.add(globalNames);
} else {
for (const globalName of globalNames) {
this._globalNames.add(globalName.value);
}
}
}

getConstructsForGlobal(globalName) {
const { ctx } = this;

const constructs = [];

for (const iface of [...ctx.interfaces.values(), ...ctx.callbackInterfaces.values()]) {
const exposed = utils.getExtAttr(iface.idl.extAttrs, "Exposed");

if (!exposed) {
continue;
} else {
const { rhs } = exposed;
switch (rhs.type) {
case "identifier":
if (rhs.value !== globalName) {
continue;
}
break;
case "identifier-list":
if (!rhs.value.reduce((r, t) => (t.value === globalName) || r, false)) {
continue;
}
break;
default:
if (!ctx.options.suppressErrors) {
throw new Error(
`Unsupported ${iface.type} ${iface.name}'s [Exposed] extended attribute value type "${rhs.type}"`
);
}
continue;
}
}

constructs.push(iface);
}

// FIXME: `Element` currently gets incorrectly placed before `Node` and `EventTarget`
constructs.sort((a, b) => {
if (a.idl.inheritance === b.name) {
return 1;
}

if (b.idl.inheritance === a.name) {
return -1;
}

return 0;
});

return constructs;
}

generate() {
this.generateGlobals();
this.generateRequires();
}

generateGlobals() {
this.str += `
/**
* Initialises the passed object as a new global.
*
* The object is expected to contain all the global object properties
* as specified in the ECMAScript specification.
*
* This function has to be added to the exports object
* to avoid circular dependency issues.
*
* @param {object} globalObject
* @param {string} globalName
*/
exports.setupGlobal = (globalObject, globalName) => {
Object.defineProperty(globalObject, utils.ctorRegistrySymbol, { value: Object.create(null) });
switch(globalName) {
`;


for (const globalName of this._globalNames) {
this.generateGlobal(globalName);
}

this.str += `
default:
throw new Error(\`Internal error: Unknown global name "\${globalName}"\`);
}
};
`;
}

generateGlobal(globalName) {
this.str += `
case "${globalName}": {
`;

for (const construct of this.getConstructsForGlobal(globalName)) {
const imported = this.requires.addRelative(construct.name);
this.str += `${imported}.install(globalObject);\n`;
}

this.str += `break;
}
`;
}

generateRequires() {
this.str = `
${this.requires.generate()}
${this.str}
`;
}

toString() {
this.str = "";
this.generate();
return this.str;
}
}

module.exports = Globals;
8 changes: 8 additions & 0 deletions lib/transformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,14 @@ class Transformer {
`);
await fs.writeFile(path.join(outputDir, obj.name + ".js"), source);
}

const source = this._prettify(`
"use strict";
const utils = require("${relativeUtils}");
${this.ctx.globals.toString()}
`);
await fs.writeFile(path.join(outputDir, "webidl2js-globals.js"), source);
}

_prettify(source) {
Expand Down
Loading

0 comments on commit 3853cad

Please sign in to comment.