Skip to content

Commit

Permalink
Bug 1782400 - Implement "return" method for WebIDL async iterable ite…
Browse files Browse the repository at this point in the history
…rator. r=edgar

Differential Revision: https://phabricator.services.mozilla.com/D156350
  • Loading branch information
petervanderbeken committed Sep 22, 2022
1 parent ffadfd2 commit 69e8a94
Show file tree
Hide file tree
Showing 18 changed files with 372 additions and 51 deletions.
24 changes: 18 additions & 6 deletions dom/bindings/Codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -4501,17 +4501,24 @@ def CopyUnforgeablePropertiesToInstance(descriptor, failureCode):


def AssertInheritanceChain(descriptor):
asserts = ""
# We can skip the reinterpret_cast check for the descriptor's nativeType
# if aObject is a pointer of that type.
asserts = fill(
"""
static_assert(std::is_same_v<decltype(aObject), ${nativeType}*>);
""",
nativeType=descriptor.nativeType,
)
iface = descriptor.interface
while iface:
while iface.parent:
iface = iface.parent
desc = descriptor.getDescriptor(iface.identifier.name)
asserts += (
"MOZ_ASSERT(static_cast<%s*>(aObject) == \n"
" reinterpret_cast<%s*>(aObject),\n"
' "Multiple inheritance for %s is broken.");\n'
% (desc.nativeType, desc.nativeType, desc.nativeType)
)
iface = iface.parent
asserts += "MOZ_ASSERT(ToSupportsIsCorrect(aObject));\n"
return asserts

Expand Down Expand Up @@ -9574,7 +9581,6 @@ def __init__(
cgThings.append(
CGIterableMethodGenerator(
descriptor,
idlNode.maplikeOrSetlikeOrIterable,
idlNode.identifier.name,
self.getArgumentNames(),
)
Expand Down Expand Up @@ -22274,7 +22280,7 @@ class CGIterableMethodGenerator(CGGeneric):
using CGCallGenerator.
"""

def __init__(self, descriptor, iterable, methodName, args):
def __init__(self, descriptor, methodName, args):
if methodName == "forEach":
assert len(args) == 2

Expand Down Expand Up @@ -22319,8 +22325,14 @@ def __init__(self, descriptor, iterable, methodName, args):
wrap = f"{descriptor.interface.identifier.name}Iterator_Binding::Wrap"
iterClass = f"mozilla::dom::binding_detail::WrappableIterableIterator<{descriptor.nativeType}, &{wrap}>"
else:
needReturnMethod = toStringBool(
descriptor.interface.maplikeOrSetlikeOrIterable.getExtendedAttribute(
"GenerateReturnMethod"
)
is not None
)
wrap = f"{descriptor.interface.identifier.name}AsyncIterator_Binding::Wrap"
iterClass = f"mozilla::dom::binding_detail::WrappableAsyncIterableIterator<{descriptor.nativeType}, &{wrap}>"
iterClass = f"mozilla::dom::binding_detail::WrappableAsyncIterableIterator<{descriptor.nativeType}, {needReturnMethod}, &{wrap}>"

createIterator = fill(
"""
Expand Down
19 changes: 16 additions & 3 deletions dom/bindings/Configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@
autogenerated_comment = "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n"


def toStringBool(arg):
"""
Converts IDL/Python Boolean (True/False) to C++ Boolean (true/false)
"""
return str(not not arg).lower()


class DescriptorProvider:
"""
A way of getting descriptors for interface names. Subclasses must
Expand Down Expand Up @@ -1052,9 +1059,15 @@ def iteratorNativeType(descriptor):
assert iterableDecl.isPairIterator() or descriptor.interface.isAsyncIterable()
if descriptor.interface.isIterable():
return "mozilla::dom::IterableIterator<%s>" % descriptor.nativeType
return (
"mozilla::dom::binding_detail::AsyncIterableIteratorNoReturn<%s>"
% descriptor.nativeType
needReturnMethod = toStringBool(
descriptor.interface.maplikeOrSetlikeOrIterable.getExtendedAttribute(
"GenerateReturnMethod"
)
is not None
)
return "mozilla::dom::binding_detail::AsyncIterableIteratorNative<%s, %s>" % (
descriptor.nativeType,
needReturnMethod,
)


Expand Down
98 changes: 97 additions & 1 deletion dom/bindings/IterableIterator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ already_AddRefed<Promise> AsyncIterableNextImpl::NextSteps(

// 4. Let nextPromise be the result of getting the next iteration result with
// object’s target and object.
RefPtr<Promise> nextPromise = GetNextPromise(aRv);
RefPtr<Promise> nextPromise = GetNextResult(aRv);

// 5. Let fulfillSteps be the following steps, given next:
auto fulfillSteps = [](JSContext* aCx, JS::Handle<JS::Value> aNext,
Expand Down Expand Up @@ -222,6 +222,102 @@ already_AddRefed<Promise> AsyncIterableNextImpl::Next(
return do_AddRef(aObject->mOngoingPromise);
}

already_AddRefed<Promise> AsyncIterableReturnImpl::ReturnSteps(
JSContext* aCx, AsyncIterableIteratorBase* aObject,
nsIGlobalObject* aGlobalObject, JS::Handle<JS::Value> aValue,
ErrorResult& aRv) {
// 2. If object’s is finished is true, then:
if (aObject->mIsFinished) {
// 1. Let result be CreateIterResultObject(value, true).
JS::Rooted<JS::Value> dict(aCx);
iterator_utils::DictReturn(aCx, &dict, true, aValue, aRv);
if (aRv.Failed()) {
return Promise::CreateRejectedWithErrorResult(aGlobalObject, aRv);
}

// 2. Perform ! Call(returnPromiseCapability.[[Resolve]], undefined,
// «result»).
// 3. Return returnPromiseCapability.[[Promise]].
return Promise::Resolve(aGlobalObject, aCx, dict, aRv);
}

// 3. Set object’s is finished to true.
aObject->mIsFinished = true;

// 4. Return the result of running the asynchronous iterator return algorithm
// for interface, given object’s target, object, and value.
return GetReturnPromise(aCx, aValue, aRv);
}

already_AddRefed<Promise> AsyncIterableReturnImpl::Return(
JSContext* aCx, AsyncIterableIteratorBase* aObject,
nsISupports* aGlobalObject, JS::Handle<JS::Value> aValue,
ErrorResult& aRv) {
nsCOMPtr<nsIGlobalObject> globalObject = do_QueryInterface(aGlobalObject);

// 3.7.10.2. Asynchronous iterator prototype object
//
RefPtr<Promise> returnStepsPromise;
// 11. If ongoingPromise is not null, then:
if (aObject->mOngoingPromise) {
// 1. Let afterOngoingPromiseCapability be
// ! NewPromiseCapability(%Promise%).
// 2. Let onSettled be CreateBuiltinFunction(returnSteps, « »).

// aObject is the same object as 'this', so it's fine to capture 'this'
// without taking a strong reference, because we already take a strong
// reference to it through aObject.
auto onSettled = [this](JSContext* aCx, JS::Handle<JS::Value> aValue,
ErrorResult& aRv,
const RefPtr<AsyncIterableIteratorBase>& aObject,
const nsCOMPtr<nsIGlobalObject>& aGlobalObject,
JS::Handle<JS::Value> aVal) {
return ReturnSteps(aCx, aObject, aGlobalObject, aVal, aRv);
};

// 3. Perform PerformPromiseThen(ongoingPromise, onSettled, onSettled,
// afterOngoingPromiseCapability).
Result<RefPtr<Promise>, nsresult> afterOngoingPromise =
aObject->mOngoingPromise->ThenCatchWithCycleCollectedArgsJS(
onSettled, onSettled,
std::make_tuple(RefPtr{aObject}, nsCOMPtr{globalObject}),
std::make_tuple(aValue));
if (afterOngoingPromise.isErr()) {
aRv.Throw(afterOngoingPromise.unwrapErr());
return nullptr;
}

// 4. Set returnStepsPromise to afterOngoingPromiseCapability.[[Promise]].
returnStepsPromise = afterOngoingPromise.unwrap().forget();
} else {
// 12. Otherwise:
// 1. Set returnStepsPromise to the result of running returnSteps.
returnStepsPromise = ReturnSteps(aCx, aObject, globalObject, aValue, aRv);
}

// 13. Let fulfillSteps be the following steps:
auto onFullFilled = [](JSContext* aCx, JS::Handle<JS::Value>,
ErrorResult& aRv,
const nsCOMPtr<nsIGlobalObject>& aGlobalObject,
JS::Handle<JS::Value> aVal) {
// 1. Return CreateIterResultObject(value, true).
JS::Rooted<JS::Value> dict(aCx);
iterator_utils::DictReturn(aCx, &dict, true, aVal, aRv);
return Promise::Resolve(aGlobalObject, aCx, dict, aRv);
};

// 14. Let onFulfilled be CreateBuiltinFunction(fulfillSteps, « »).
// 15. Perform PerformPromiseThen(returnStepsPromise, onFulfilled, undefined,
// returnPromiseCapability).
Result<RefPtr<Promise>, nsresult> returnPromise =
returnStepsPromise->ThenWithCycleCollectedArgsJS(
onFullFilled, std::make_tuple(std::move(globalObject)),
std::make_tuple(aValue));

// 16. Return returnPromiseCapability.[[Promise]].
return PromiseOrErr(std::move(returnPromise), aRv);
}

} // namespace binding_detail

} // namespace mozilla::dom
67 changes: 57 additions & 10 deletions dom/bindings/IterableIterator.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ class IterableIterator : public IterableIteratorBase {
namespace binding_detail {

class AsyncIterableNextImpl;
class AsyncIterableReturnImpl;

} // namespace binding_detail

Expand All @@ -225,6 +226,7 @@ class AsyncIterableIteratorBase : public IterableIteratorBase {

private:
friend class binding_detail::AsyncIterableNextImpl;
friend class binding_detail::AsyncIterableReturnImpl;

// 3.7.10.1. Default asynchronous iterator objects
// Target is in AsyncIterableIterator
Expand Down Expand Up @@ -320,7 +322,7 @@ class AsyncIterableNextImpl {
already_AddRefed<Promise> Next(JSContext* aCx,
AsyncIterableIteratorBase* aObject,
nsISupports* aGlobalObject, ErrorResult& aRv);
virtual already_AddRefed<Promise> GetNextPromise(ErrorResult& aRv) = 0;
virtual already_AddRefed<Promise> GetNextResult(ErrorResult& aRv) = 0;

private:
already_AddRefed<Promise> NextSteps(JSContext* aCx,
Expand All @@ -329,6 +331,24 @@ class AsyncIterableNextImpl {
ErrorResult& aRv);
};

class AsyncIterableReturnImpl {
protected:
already_AddRefed<Promise> Return(JSContext* aCx,
AsyncIterableIteratorBase* aObject,
nsISupports* aGlobalObject,
JS::Handle<JS::Value> aValue,
ErrorResult& aRv);
virtual already_AddRefed<Promise> GetReturnPromise(
JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) = 0;

private:
already_AddRefed<Promise> ReturnSteps(JSContext* aCx,
AsyncIterableIteratorBase* aObject,
nsIGlobalObject* aGlobalObject,
JS::Handle<JS::Value> aValue,
ErrorResult& aRv);
};

template <typename T>
class AsyncIterableIteratorNoReturn : public AsyncIterableIterator<T>,
public AsyncIterableNextImpl {
Expand All @@ -341,23 +361,50 @@ class AsyncIterableIteratorNoReturn : public AsyncIterableIterator<T>,
}

protected:
already_AddRefed<Promise> GetNextPromise(ErrorResult& aRv) override {
return this->mIterableObj->GetNextPromise(
already_AddRefed<Promise> GetNextResult(ErrorResult& aRv) override {
return this->mIterableObj->GetNextIterationResult(
static_cast<AsyncIterableIterator<T>*>(this), aRv);
}
};

template <typename T>
using AsyncIterableIteratorWrapFunc =
bool (*)(JSContext* aCx, AsyncIterableIteratorNoReturn<T>* aObject,
JS::MutableHandle<JSObject*> aReflector);

template <typename T, AsyncIterableIteratorWrapFunc<T> WrapFunc>
class WrappableAsyncIterableIterator final
: public AsyncIterableIteratorNoReturn<T> {
class AsyncIterableIteratorWithReturn : public AsyncIterableIteratorNoReturn<T>,
public AsyncIterableReturnImpl {
public:
already_AddRefed<Promise> Return(JSContext* aCx, JS::Handle<JS::Value> aValue,
ErrorResult& aRv) {
return AsyncIterableReturnImpl::Return(
aCx, this, this->mIterableObj->GetParentObject(), aValue, aRv);
}

protected:
using AsyncIterableIteratorNoReturn<T>::AsyncIterableIteratorNoReturn;

already_AddRefed<Promise> GetReturnPromise(JSContext* aCx,
JS::Handle<JS::Value> aValue,
ErrorResult& aRv) override {
return this->mIterableObj->IteratorReturn(
aCx, static_cast<AsyncIterableIterator<T>*>(this), aValue, aRv);
}
};

template <typename T, bool NeedReturnMethod>
using AsyncIterableIteratorNative =
std::conditional_t<NeedReturnMethod, AsyncIterableIteratorWithReturn<T>,
AsyncIterableIteratorNoReturn<T>>;

template <typename T, bool NeedReturnMethod>
using AsyncIterableIteratorWrapFunc = bool (*)(
JSContext* aCx, AsyncIterableIteratorNative<T, NeedReturnMethod>* aObject,
JS::MutableHandle<JSObject*> aReflector);

template <typename T, bool NeedReturnMethod,
AsyncIterableIteratorWrapFunc<T, NeedReturnMethod> WrapFunc,
typename Base = AsyncIterableIteratorNative<T, NeedReturnMethod>>
class WrappableAsyncIterableIterator final : public Base {
public:
using Base::Base;

bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
JS::MutableHandle<JSObject*> aObj) {
MOZ_ASSERT(!aGivenProto);
Expand Down
33 changes: 30 additions & 3 deletions dom/bindings/parser/WebIDL.py
Original file line number Diff line number Diff line change
Expand Up @@ -8908,12 +8908,39 @@ def simpleExtendedAttr(str):
else:
nextReturnType = BuiltinTypes[IDLBuiltinType.Types.object]
nextMethod = IDLMethod(
iface.location,
IDLUnresolvedIdentifier(iface.location, "next"),
iterable.location,
IDLUnresolvedIdentifier(iterable.location, "next"),
nextReturnType,
[],
)
nextMethod.addExtendedAttributes([simpleExtendedAttr("Throws")])

methods = [nextMethod]

if iterable.getExtendedAttribute("GenerateReturnMethod"):
assert isinstance(iterable, IDLAsyncIterable)

returnMethod = IDLMethod(
iterable.location,
IDLUnresolvedIdentifier(iterable.location, "return"),
IDLPromiseType(
iterable.location, BuiltinTypes[IDLBuiltinType.Types.any]
),
[
IDLArgument(
iterable.location,
IDLUnresolvedIdentifier(
BuiltinLocation("<auto-generated-identifier>"),
"value",
),
BuiltinTypes[IDLBuiltinType.Types.any],
optional=True,
),
],
)
returnMethod.addExtendedAttributes([simpleExtendedAttr("Throws")])
methods.append(returnMethod)

if iterable.isIterable():
itr_suffix = "Iterator"
else:
Expand All @@ -8930,7 +8957,7 @@ def simpleExtendedAttr(str):
self.globalScope(),
itr_ident,
None,
[nextMethod],
methods,
isKnownNonPartial=True,
classNameOverride=classNameOverride,
)
Expand Down
7 changes: 4 additions & 3 deletions dom/bindings/test/TestInterfaceAsyncIterableDouble.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,16 @@ nsPIDOMWindowInner* TestInterfaceAsyncIterableDouble::GetParentObject() const {
return mParent;
}

already_AddRefed<Promise> TestInterfaceAsyncIterableDouble::GetNextPromise(
Iterator* aIterator, ErrorResult& aRv) {
already_AddRefed<Promise>
TestInterfaceAsyncIterableDouble::GetNextIterationResult(Iterator* aIterator,
ErrorResult& aRv) {
RefPtr<Promise> promise = Promise::Create(mParent->AsGlobal(), aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}

NS_DispatchToMainThread(NewRunnableMethod<RefPtr<Iterator>, RefPtr<Promise>>(
"TestInterfaceAsyncIterableDouble::GetNextPromise", this,
"TestInterfaceAsyncIterableDouble::GetNextIterationResult", this,
&TestInterfaceAsyncIterableDouble::ResolvePromise, aIterator, promise));

return promise.forget();
Expand Down
4 changes: 2 additions & 2 deletions dom/bindings/test/TestInterfaceAsyncIterableDouble.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ class TestInterfaceAsyncIterableDouble final : public nsISupports,
void InitAsyncIteratorData(IteratorData& aData, Iterator::IteratorType aType,
ErrorResult& aError) {}

already_AddRefed<Promise> GetNextPromise(Iterator* aIterator,
ErrorResult& aRv);
already_AddRefed<Promise> GetNextIterationResult(Iterator* aIterator,
ErrorResult& aRv);

private:
virtual ~TestInterfaceAsyncIterableDouble() = default;
Expand Down
Loading

0 comments on commit 69e8a94

Please sign in to comment.