Skip to content

Commit 464a43f

Browse files
authored
Implement support for the [Exposed] extended attribute (#192)
This also fixes a bug where `install` takes `globalName` instead of `globalNames`.
1 parent 5af205c commit 464a43f

File tree

4 files changed

+463
-81
lines changed

4 files changed

+463
-81
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,10 +264,12 @@ Performs the Web IDL conversion algorithm for this interface, converting _value_
264264

265265
In practice, this means doing a type-check equivalent to `is(value)`, and if it passes, returns the corresponding impl. If the type-check fails, it throws an informative exception. _context_ can be used to describe the provided value in any resulting error message.
266266

267-
#### `install(globalObject)`
267+
#### `install(globalObject, globalNames)`
268268

269269
This method creates a brand new wrapper constructor and prototype and attach it to the passed `globalObject`. It also registers the created constructor with the `globalObject`'s global constructor registry, which makes `create()`, `createImpl()`, and `setup()` work. (Thus, it is important to invoke `install()` before invoking those methods, as otherwise they will throw.)
270270

271+
The second argument `globalNames` is an array containing the [global names](https://heycam.github.io/webidl/#dfn-global-name) of the interface that `globalObject` implements. This is used for the purposes of deciding which interfaces are [exposed](https://heycam.github.io/webidl/#dfn-exposed). For example, this array should be `["Window"]` for a [`Window`](https://html.spec.whatwg.org/multipage/window-object.html#window) global object. But for a [`DedicatedWorkerGlobalScope`](https://html.spec.whatwg.org/multipage/workers.html#dedicatedworkerglobalscope) global object, this array should be `["Worker", "DedicatedWorker"]`. Note that we do not yet implement [`[SecureContext]`](https://heycam.github.io/webidl/#SecureContext), so the "exposed" check is not fully implemented.
272+
271273
#### `create(globalObject, constructorArgs, privateData)`
272274

273275
Creates a new instance of the wrapper class and corresponding implementation class, passing in the `globalObject`, the `constructorArgs` array and `privateData` object to the implementation class constructor. Then returns the wrapper class.
@@ -454,6 +456,7 @@ webidl2js is implementing an ever-growing subset of the Web IDL specification. S
454456
- Variadic arguments
455457
- `[Clamp]`
456458
- `[EnforceRange]`
459+
- `[Exposed]`
457460
- `[LegacyArrayClass]`
458461
- `[LegacyUnenumerableNamedProperties]`
459462
- `[LegacyWindowAlias]`
@@ -478,7 +481,6 @@ Notable missing features include:
478481
- `[AllowShared]`
479482
- `[Default]` (for `toJSON()` operations)
480483
- `[Global]`'s various consequences, including the named properties object and `[[SetPrototypeOf]]`
481-
- `[Exposed]`
482484
- `[LenientSetter]`
483485
- `[LenientThis]`
484486
- `[NamedConstructor]`

lib/constructs/callback-interface.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,25 @@ class CallbackInterface {
2020

2121
this._analyzed = false;
2222
this._outputStaticProperties = new Map();
23+
24+
const exposed = utils.getExtAttr(this.idl.extAttrs, "Exposed");
25+
if (this.idl.members.some(member => member.type === "const") && !exposed) {
26+
throw new Error(`Callback interface ${this.name} with defined constants lacks the [Exposed] extended attribute`);
27+
}
28+
29+
if (exposed) {
30+
if (!exposed.rhs || (exposed.rhs.type !== "identifier" && exposed.rhs.type !== "identifier-list")) {
31+
throw new Error(`[Exposed] must take an identifier or an identifier list in callback interface ${this.name}`);
32+
}
33+
34+
if (exposed.rhs.type === "identifier") {
35+
this.exposed = new Set([exposed.rhs.value]);
36+
} else {
37+
this.exposed = new Set(exposed.rhs.value.map(token => token.value));
38+
}
39+
} else {
40+
this.exposed = new Set();
41+
}
2342
}
2443

2544
_analyzeMembers() {
@@ -178,14 +197,24 @@ class CallbackInterface {
178197
}
179198

180199
generateInstall() {
200+
if (this.constants.size > 0) {
201+
this.str += `
202+
const exposed = new Set(${JSON.stringify([...this.exposed])});
203+
`;
204+
}
205+
181206
this.str += `
182-
exports.install = function install(globalObject) {
207+
exports.install = (globalObject, globalNames) => {
183208
`;
184209

185210
if (this.constants.size > 0) {
186211
const { name } = this;
187212

188213
this.str += `
214+
if (!globalNames.some(globalName => exposed.has(globalName))) {
215+
return;
216+
}
217+
189218
const ${name} = () => {
190219
throw new TypeError("Illegal invocation");
191220
};
@@ -234,4 +263,6 @@ class CallbackInterface {
234263
}
235264
}
236265

266+
CallbackInterface.prototype.type = "callback interface";
267+
237268
module.exports = CallbackInterface;

lib/constructs/interface.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,7 +1463,12 @@ class Interface {
14631463
const { idl, name } = this;
14641464

14651465
this.str += `
1466-
exports.install = (globalObject, globalName) => {
1466+
const exposed = new Set(${JSON.stringify([...this.exposed])});
1467+
1468+
exports.install = (globalObject, globalNames) => {
1469+
if (!globalNames.some(globalName => exposed.has(globalName))) {
1470+
return;
1471+
}
14671472
`;
14681473

14691474
if (idl.inheritance) {
@@ -1500,7 +1505,7 @@ class Interface {
15001505

15011506
if (this.legacyWindowAliases) {
15021507
this.str += `
1503-
if (globalName === "Window") {
1508+
if (globalNames.includes("Window")) {
15041509
`;
15051510

15061511
for (const legacyWindowAlias of this.legacyWindowAliases) {

0 commit comments

Comments
 (0)