Skip to content

Commit

Permalink
Add [WebIDL2JSHasReturnSteps]
Browse files Browse the repository at this point in the history
  • Loading branch information
domenic committed May 18, 2020
1 parent 0cfd2df commit 8e30d3c
Show file tree
Hide file tree
Showing 5 changed files with 759 additions and 390 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,12 @@ A couple of non-standard extended attributes are baked in to webidl2js:

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.

### `[WebIDL2JSHasReturnSteps]`

This extended attribute can be applied to async iterable declarations. It declares that the implementation class will implement the `[idlUtils.asyncIteratorReturn]()` method.

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.

### `[WebIDL2JSValueAsUnsupported=value]`

This extended attribute can be applied to named or indexed getters or setters. It says that whether the interface supports a given property name/index can be automatically derived by looking at the return value of its indexed getter/setter: whenever `value` is returned, the name/index is unsupported. Typically, `value` is either `undefined` or `_null`.
Expand Down
4 changes: 4 additions & 0 deletions lib/constructs/async-iterable.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ class Iterable {
return true;
}

get hasReturnSteps() {
return Boolean(utils.getExtAttr(this.idl.extAttrs, "WebIDL2JSHasReturnSteps"));
}

generateFunction(key, kind, requires) {
const conv = generateAsyncIteratorArgConversions(
this.ctx, this.idl, this.interface, `Failed to execute '${key}' on '${this.interface.name}': `);
Expand Down
83 changes: 51 additions & 32 deletions lib/constructs/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -428,51 +428,70 @@ class Interface {
if (this.iterable.isAsync) {
this.str += `
const AsyncIteratorPrototype = Object.create(utils.AsyncIteratorPrototype, {
next: {
value: function next() {
const internal = this[utils.iterInternalSymbol];
if (!internal) {
return Promise.reject(new TypeError("next() called on an object that is not an async iterator prototype object"));
[Symbol.toStringTag]: {
value: "${this.name} AsyncIterator",
configurable: true
}
});
AsyncIteratorPrototype.next = function () {
const internal = this[utils.iterInternalSymbol];
if (!internal) {
return Promise.reject(new TypeError("next() called on an object that is not an async iterator prototype object"));
}
const nextSteps = () => {
if (internal.isFinished) {
return Promise.resolve({ value: undefined, done: true });
}
const nextSteps = () => {
if (internal.isFinished) {
return Promise.resolve({ value: undefined, done: true });
}
const nextPromise = internal.target[implSymbol][utils.asyncIteratorNext](this);
return nextPromise.then(next => {
internal.ongoingPromise = null;
if (next === undefined) {
internal.isFinished = true;
return { value: undefined, done: true };
}`;
const nextPromise = internal.target[implSymbol][utils.asyncIteratorNext](this);
return nextPromise.then(next => {
internal.ongoingPromise = null;
if (next === undefined) {
internal.isFinished = true;
return { value: undefined, done: true };
}`;
if (this.iterable.isPair) {
this.str += `
return utils.iteratorResult(next.map(utils.tryWrapperForImpl), kind);
return utils.iteratorResult(next.map(utils.tryWrapperForImpl), kind);
`;
} else {
this.str += `
return { value: utils.tryWrapperForImpl(next), done: false };
return { value: utils.tryWrapperForImpl(next), done: false };
`;
}
this.str += `
});
};
});
};
internal.ongoingPromise = internal.ongoingPromise ? internal.ongoingPromise.then(nextSteps) : nextSteps();
return internal.ongoingPromise;
},
writable: true,
enumerable: true,
configurable: true
},
[Symbol.toStringTag]: {
value: "${this.name} AsyncIterator",
configurable: true
internal.ongoingPromise = internal.ongoingPromise ? internal.ongoingPromise.then(nextSteps) : nextSteps();
return internal.ongoingPromise;
}
});
`;

if (this.iterable.hasReturnSteps) {
this.str += `
AsyncIteratorPrototype.return = function (value) {
const internal = this[utils.iterInternalSymbol];
if (!internal) {
return Promise.reject(new TypeError("return() called on an object that is not an async iterator prototype object"));
}
if (internal.ongoingPromise) {
return Promise.reject(new TypeError("return() cannot be called while an ongoing call to next() has not settled"));
}
if (internal.isFinished) {
return Promise.reject(new TypeError("return() cannot be called after async iteration is already finished"));
}
internal.isFinished = true;
const returnPromise = internal.target[implSymbol][utils.asyncIteratorReturn](this, value);
return returnPromise.then(() => ({ value, done: true }));
};
`;
}
} else if (this.iterable.isPair) {
this.str += `
const IteratorPrototype = Object.create(utils.IteratorPrototype, {
Expand Down
Loading

0 comments on commit 8e30d3c

Please sign in to comment.