Skip to content

Commit a6a36fb

Browse files
committed
Merge branch 'master' into feat/ext-attr/legacy-factory-function
2 parents ced50d4 + 1933ade commit a6a36fb

19 files changed

+8294
-5676
lines changed

README.md

+27-2
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ stringifier DOMString operation(); // `toString()` is not needed.
385385

386386
IDL indexed and named properties require multiple things on the implementation class to work properly.
387387

388-
The first is the getters, (optional) setters, and (optional) deleters operations. Much like stringifiers, getters, setters, and deleters can either be standalone or aliased to a named operation (though not an attribute). If an operation is standalone, then the implementation class must implement following symbol-named methods. The `utils` object below refers to the default export from the generated utilities file `utils.js`.
388+
The first is the getters, (optional) setters, and (optional) deleters operations. Much like stringifiers, getters, setters, and deleters can either be standalone or aliased to a named operation (though not an attribute). If an operation is standalone, then the implementation class must implement the following symbol-named methods. The `utils` object below refers to the default export from the generated utilities file `utils.js`.
389389

390390
- Getters: `utils.indexedGet`, `utils.namedGet`
391391
- Setters: `utils.indexedSetNew`, `utils.indexedSetExisting`, `utils.namedSetNew`, `utils.namedSetExisting`
@@ -395,6 +395,20 @@ The second is the interface's supported property indices/names. By default, the
395395

396396
If the getter function always returns a constant value for unsupported properties, webidl2js also offers a non-standard extended attribute `[WebIDL2JSValueAsUnsupported]` (documented below) that would simply call the getter function to check if a property index/name is supported, so that `supportsPropertyIndex`/`supportsPropertyName` would not need to be implemented separately. However, when using the extended attribute, be very sure that the value specified in the attribute is returned *if and only if* the property is unsupported.
397397

398+
### Iterables
399+
400+
For synchronous value iterable declarations, there is no need to add implementation-class code: they will be automatically generated based on the indexed property getter and `length` property.
401+
402+
For synchronous pair iterable declarations, the implementation class needs to implement the `[Symbol.iterator]()` property, returning an iterable of `[key, value]` pairs. These can be impls; the generated code will convert them into wrappers as necessary.
403+
404+
### Async iterables
405+
406+
[Asynchronous iterable declarations](https://heycam.github.io/webidl/#idl-async-iterable) require the implementation class to implement the following symbol-named methods, corresponding to algorithms from the Web IDL specification. The `utils` object below refers to the default export from the generated utilities file `utils.js`.
407+
408+
- `utils.asyncIteratorNext`: corresponds to the [get the next iteration result](https://heycam.github.io/webidl/#dfn-get-the-next-iteration-result) algorithm, and receives a single argument containing an instance of the generated async iterator. For pair asynchronous iterables, the return value must be a `[key, value]` pair array, or `utils.asyncIteratorEOI` to signal the end of the iteration. For value asynchronous iterables, the return value must be the value, or `utils.asyncIteratorEOI` to signal the end of the iteration.
409+
- `utils.asyncIteratorInit`: corresponds to the [asynchronous iterator initialization steps](https://heycam.github.io/webidl/#asynchronous-iterator-initialization-steps), and receives two arguments: the instance of the generated async iterator, and an array containing the post-conversion arguments. This method is optional.
410+
- `utils.asyncIteratorReturn`: corresponds to the [asynchronous iterator return](https://heycam.github.io/webidl/#asynchronous-iterator-return) algorithm, and receives two arguments: the instance of the generated async iterator, and the argument passed to the `return()` method. This method is optional. Note that if you include it, you need to annote the async iterable declaration with [`[WebIDL2JSHasReturnSteps]`](#webidl2jshasreturnsteps).
411+
398412
### Other, non-exposed data and functionality
399413

400414
Your implementation class can contain other properties and methods in support of the wrapped properties and methods that the wrapper class calls into. These can be used to factor out common algorithms, or store private state, or keep caches, or anything of the sort.
@@ -459,6 +473,7 @@ webidl2js is implementing an ever-growing subset of the Web IDL specification. S
459473
- Stringifiers
460474
- Named and indexed `getter`/`setter`/`deleter` declarations
461475
- `iterable<>` declarations
476+
- `async iterable<>` declarations
462477
- Class strings (with the semantics of [heycam/webidl#357](https://github.com/heycam/webidl/pull/357))
463478
- Dictionary types
464479
- Enumeration types
@@ -512,7 +527,17 @@ Notable missing features include:
512527

513528
## Nonstandard extended attributes
514529

515-
One non-standard extended attribute is baked in to webidl2js:
530+
A couple of non-standard extended attributes are baked in to webidl2js:
531+
532+
### `[WebIDL2JSCallWithGlobal]`
533+
534+
When the `[WebIDL2JSCallWithGlobal]` extended attribute is specified on static IDL operations, the generated interface code passes the [current global object](https://html.spec.whatwg.org/multipage/webappapis.html#current-global-object) as the first parameter to the implementation code. All other parameters follow `globalObject` and are unchanged. This could be used to implement factory functions that create objects in the current realm.
535+
536+
### `[WebIDL2JSHasReturnSteps]`
537+
538+
This extended attribute can be applied to async iterable declarations. It declares that the implementation class will implement the `[idlUtils.asyncIteratorReturn]()` method.
539+
540+
This is necessary because we need to figure out at code-generation time whether to generate a `return()` method on the async iterator prototype. At that point, only the Web IDL is available, not the implementation class properties. So, we need a signal in the Web IDL itself.
516541

517542
### `[WebIDL2JSValueAsUnsupported=value]`
518543

lib/constructs/async-iterable.js

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"use strict";
2+
3+
const utils = require("../utils");
4+
const { generateAsyncIteratorArgConversions } = require("../parameters");
5+
6+
class AsyncIterable {
7+
constructor(ctx, I, idl) {
8+
this.ctx = ctx;
9+
this.interface = I;
10+
this.idl = idl;
11+
this.name = idl.type;
12+
}
13+
14+
get isValue() {
15+
return this.idl.idlType.length === 1;
16+
}
17+
18+
get isPair() {
19+
return this.idl.idlType.length === 2;
20+
}
21+
22+
get isAsync() {
23+
return true;
24+
}
25+
26+
get hasReturnSteps() {
27+
return Boolean(utils.getExtAttr(this.idl.extAttrs, "WebIDL2JSHasReturnSteps"));
28+
}
29+
30+
generateFunction(key, kind, requires) {
31+
const conv = generateAsyncIteratorArgConversions(
32+
this.ctx, this.idl, this.interface, `Failed to execute '${key}' on '${this.interface.name}': `);
33+
requires.merge(conv.requires);
34+
35+
this.interface.addMethod(this.interface.defaultWhence, key, [], `
36+
if (!exports.is(this)) {
37+
throw new TypeError("'${key}' called on an object that is not a valid instance of ${this.interface.name}.");
38+
}
39+
40+
${conv.body}
41+
42+
const asyncIterator = exports.createDefaultAsyncIterator(this, "${kind}");
43+
if (this[implSymbol][utils.asyncIteratorInit]) {
44+
this[implSymbol][utils.asyncIteratorInit](asyncIterator, args);
45+
}
46+
return asyncIterator;
47+
`);
48+
}
49+
50+
generate() {
51+
const whence = this.interface.defaultWhence;
52+
const requires = new utils.RequiresMap(this.ctx);
53+
54+
// https://heycam.github.io/webidl/#define-the-asynchronous-iteration-methods
55+
56+
if (this.isPair) {
57+
this.generateFunction("keys", "key", requires);
58+
this.generateFunction("values", "value", requires);
59+
this.generateFunction("entries", "key+value", requires);
60+
this.interface.addProperty(whence, Symbol.asyncIterator, `${this.interface.name}.prototype.entries`);
61+
} else {
62+
this.generateFunction("values", "value", requires);
63+
this.interface.addProperty(whence, Symbol.asyncIterator, `${this.interface.name}.prototype.values`);
64+
}
65+
66+
return { requires };
67+
}
68+
}
69+
70+
module.exports = AsyncIterable;

lib/constructs/attribute.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class Attribute {
2929

3030
let brandCheck = `
3131
if (!exports.is(esValue)) {
32-
throw new TypeError("Illegal invocation");
32+
throw new TypeError("'$KEYWORD$ ${this.idl.name}' called on an object that is not a valid instance of ${this.interface.name}.");
3333
}
3434
`;
3535
let getterBody = `return utils.tryWrapperForImpl(esValue[implSymbol]["${this.idl.name}"]);`;
@@ -78,11 +78,13 @@ class Attribute {
7878
addMethod(this.idl.name, [], `
7979
${promiseHandlingBefore}
8080
const esValue = this !== null && this !== undefined ? this : globalObject;
81-
${brandCheck}
81+
${brandCheck.replace("$KEYWORD$", "get")}
8282
${getterBody}
8383
${promiseHandlingAfter}
8484
`, "get", { configurable });
8585

86+
brandCheck = brandCheck.replace("$KEYWORD$", "set");
87+
8688
if (!this.idl.readonly) {
8789
if (async) {
8890
throw new Error(`Illegal promise-typed attribute "${this.idl.name}" in interface "${this.interface.idl.name}"`);
@@ -159,7 +161,7 @@ class Attribute {
159161
addMethod("toString", [], `
160162
const esValue = this;
161163
if (!exports.is(esValue)) {
162-
throw new TypeError("Illegal invocation");
164+
throw new TypeError("'toString' called on an object that is not a valid instance of ${this.interface.name}.");
163165
}
164166
165167
${getterBody}

0 commit comments

Comments
 (0)