From 69e8a9458cc6ee34ab186d6af924a67fd953e0fe Mon Sep 17 00:00:00 2001 From: Peter Van der Beken Date: Thu, 22 Sep 2022 18:28:18 +0000 Subject: [PATCH] Bug 1782400 - Implement "return" method for WebIDL async iterable iterator. r=edgar Differential Revision: https://phabricator.services.mozilla.com/D156350 --- dom/bindings/Codegen.py | 24 +++-- dom/bindings/Configuration.py | 19 +++- dom/bindings/IterableIterator.cpp | 98 ++++++++++++++++++- dom/bindings/IterableIterator.h | 67 +++++++++++-- dom/bindings/parser/WebIDL.py | 33 ++++++- .../test/TestInterfaceAsyncIterableDouble.cpp | 7 +- .../test/TestInterfaceAsyncIterableDouble.h | 4 +- .../TestInterfaceAsyncIterableDoubleUnion.cpp | 5 +- .../TestInterfaceAsyncIterableDoubleUnion.h | 4 +- .../test/TestInterfaceAsyncIterableSingle.cpp | 12 ++- .../test/TestInterfaceAsyncIterableSingle.h | 9 +- ...stInterfaceAsyncIterableSingleWithArgs.cpp | 47 ++++++++- ...TestInterfaceAsyncIterableSingleWithArgs.h | 22 ++++- dom/bindings/test/test_async_iterable.html | 58 ++++++++++- dom/fs/api/FileSystemDirectoryHandle.cpp | 2 +- dom/fs/api/FileSystemDirectoryHandle.h | 4 +- .../api/TestFileSystemDirectoryHandle.cpp | 3 +- ...stInterfaceJSMaplikeSetlikeIterable.webidl | 5 + 18 files changed, 372 insertions(+), 51 deletions(-) diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index f525ebf76cd81..acbf49e9e7c6b 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -4501,9 +4501,17 @@ 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); + """, + 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" @@ -4511,7 +4519,6 @@ def AssertInheritanceChain(descriptor): ' "Multiple inheritance for %s is broken.");\n' % (desc.nativeType, desc.nativeType, desc.nativeType) ) - iface = iface.parent asserts += "MOZ_ASSERT(ToSupportsIsCorrect(aObject));\n" return asserts @@ -9574,7 +9581,6 @@ def __init__( cgThings.append( CGIterableMethodGenerator( descriptor, - idlNode.maplikeOrSetlikeOrIterable, idlNode.identifier.name, self.getArgumentNames(), ) @@ -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 @@ -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( """ diff --git a/dom/bindings/Configuration.py b/dom/bindings/Configuration.py index 67d9e18c1cec8..c1eb524b407f3 100644 --- a/dom/bindings/Configuration.py +++ b/dom/bindings/Configuration.py @@ -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 @@ -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, ) diff --git a/dom/bindings/IterableIterator.cpp b/dom/bindings/IterableIterator.cpp index 1bbdffbf5534c..e2183865f7e1f 100644 --- a/dom/bindings/IterableIterator.cpp +++ b/dom/bindings/IterableIterator.cpp @@ -109,7 +109,7 @@ already_AddRefed AsyncIterableNextImpl::NextSteps( // 4. Let nextPromise be the result of getting the next iteration result with // object’s target and object. - RefPtr nextPromise = GetNextPromise(aRv); + RefPtr nextPromise = GetNextResult(aRv); // 5. Let fulfillSteps be the following steps, given next: auto fulfillSteps = [](JSContext* aCx, JS::Handle aNext, @@ -222,6 +222,102 @@ already_AddRefed AsyncIterableNextImpl::Next( return do_AddRef(aObject->mOngoingPromise); } +already_AddRefed AsyncIterableReturnImpl::ReturnSteps( + JSContext* aCx, AsyncIterableIteratorBase* aObject, + nsIGlobalObject* aGlobalObject, JS::Handle aValue, + ErrorResult& aRv) { + // 2. If object’s is finished is true, then: + if (aObject->mIsFinished) { + // 1. Let result be CreateIterResultObject(value, true). + JS::Rooted 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 AsyncIterableReturnImpl::Return( + JSContext* aCx, AsyncIterableIteratorBase* aObject, + nsISupports* aGlobalObject, JS::Handle aValue, + ErrorResult& aRv) { + nsCOMPtr globalObject = do_QueryInterface(aGlobalObject); + + // 3.7.10.2. Asynchronous iterator prototype object + // … + RefPtr 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 aValue, + ErrorResult& aRv, + const RefPtr& aObject, + const nsCOMPtr& aGlobalObject, + JS::Handle aVal) { + return ReturnSteps(aCx, aObject, aGlobalObject, aVal, aRv); + }; + + // 3. Perform PerformPromiseThen(ongoingPromise, onSettled, onSettled, + // afterOngoingPromiseCapability). + Result, 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, + ErrorResult& aRv, + const nsCOMPtr& aGlobalObject, + JS::Handle aVal) { + // 1. Return CreateIterResultObject(value, true). + JS::Rooted 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, 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 diff --git a/dom/bindings/IterableIterator.h b/dom/bindings/IterableIterator.h index 4d8066b67bb3c..2ca32750f3d6b 100644 --- a/dom/bindings/IterableIterator.h +++ b/dom/bindings/IterableIterator.h @@ -212,6 +212,7 @@ class IterableIterator : public IterableIteratorBase { namespace binding_detail { class AsyncIterableNextImpl; +class AsyncIterableReturnImpl; } // namespace binding_detail @@ -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 @@ -320,7 +322,7 @@ class AsyncIterableNextImpl { already_AddRefed Next(JSContext* aCx, AsyncIterableIteratorBase* aObject, nsISupports* aGlobalObject, ErrorResult& aRv); - virtual already_AddRefed GetNextPromise(ErrorResult& aRv) = 0; + virtual already_AddRefed GetNextResult(ErrorResult& aRv) = 0; private: already_AddRefed NextSteps(JSContext* aCx, @@ -329,6 +331,24 @@ class AsyncIterableNextImpl { ErrorResult& aRv); }; +class AsyncIterableReturnImpl { + protected: + already_AddRefed Return(JSContext* aCx, + AsyncIterableIteratorBase* aObject, + nsISupports* aGlobalObject, + JS::Handle aValue, + ErrorResult& aRv); + virtual already_AddRefed GetReturnPromise( + JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) = 0; + + private: + already_AddRefed ReturnSteps(JSContext* aCx, + AsyncIterableIteratorBase* aObject, + nsIGlobalObject* aGlobalObject, + JS::Handle aValue, + ErrorResult& aRv); +}; + template class AsyncIterableIteratorNoReturn : public AsyncIterableIterator, public AsyncIterableNextImpl { @@ -341,23 +361,50 @@ class AsyncIterableIteratorNoReturn : public AsyncIterableIterator, } protected: - already_AddRefed GetNextPromise(ErrorResult& aRv) override { - return this->mIterableObj->GetNextPromise( + already_AddRefed GetNextResult(ErrorResult& aRv) override { + return this->mIterableObj->GetNextIterationResult( static_cast*>(this), aRv); } }; template -using AsyncIterableIteratorWrapFunc = - bool (*)(JSContext* aCx, AsyncIterableIteratorNoReturn* aObject, - JS::MutableHandle aReflector); - -template WrapFunc> -class WrappableAsyncIterableIterator final - : public AsyncIterableIteratorNoReturn { +class AsyncIterableIteratorWithReturn : public AsyncIterableIteratorNoReturn, + public AsyncIterableReturnImpl { public: + already_AddRefed Return(JSContext* aCx, JS::Handle aValue, + ErrorResult& aRv) { + return AsyncIterableReturnImpl::Return( + aCx, this, this->mIterableObj->GetParentObject(), aValue, aRv); + } + + protected: using AsyncIterableIteratorNoReturn::AsyncIterableIteratorNoReturn; + already_AddRefed GetReturnPromise(JSContext* aCx, + JS::Handle aValue, + ErrorResult& aRv) override { + return this->mIterableObj->IteratorReturn( + aCx, static_cast*>(this), aValue, aRv); + } +}; + +template +using AsyncIterableIteratorNative = + std::conditional_t, + AsyncIterableIteratorNoReturn>; + +template +using AsyncIterableIteratorWrapFunc = bool (*)( + JSContext* aCx, AsyncIterableIteratorNative* aObject, + JS::MutableHandle aReflector); + +template WrapFunc, + typename Base = AsyncIterableIteratorNative> +class WrappableAsyncIterableIterator final : public Base { + public: + using Base::Base; + bool WrapObject(JSContext* aCx, JS::Handle aGivenProto, JS::MutableHandle aObj) { MOZ_ASSERT(!aGivenProto); diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index 838529ba44bcc..ddfce6488bf10 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -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(""), + "value", + ), + BuiltinTypes[IDLBuiltinType.Types.any], + optional=True, + ), + ], + ) + returnMethod.addExtendedAttributes([simpleExtendedAttr("Throws")]) + methods.append(returnMethod) + if iterable.isIterable(): itr_suffix = "Iterator" else: @@ -8930,7 +8957,7 @@ def simpleExtendedAttr(str): self.globalScope(), itr_ident, None, - [nextMethod], + methods, isKnownNonPartial=True, classNameOverride=classNameOverride, ) diff --git a/dom/bindings/test/TestInterfaceAsyncIterableDouble.cpp b/dom/bindings/test/TestInterfaceAsyncIterableDouble.cpp index fab2c3a8212f8..a0ec0d64f0914 100644 --- a/dom/bindings/test/TestInterfaceAsyncIterableDouble.cpp +++ b/dom/bindings/test/TestInterfaceAsyncIterableDouble.cpp @@ -54,15 +54,16 @@ nsPIDOMWindowInner* TestInterfaceAsyncIterableDouble::GetParentObject() const { return mParent; } -already_AddRefed TestInterfaceAsyncIterableDouble::GetNextPromise( - Iterator* aIterator, ErrorResult& aRv) { +already_AddRefed +TestInterfaceAsyncIterableDouble::GetNextIterationResult(Iterator* aIterator, + ErrorResult& aRv) { RefPtr promise = Promise::Create(mParent->AsGlobal(), aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } NS_DispatchToMainThread(NewRunnableMethod, RefPtr>( - "TestInterfaceAsyncIterableDouble::GetNextPromise", this, + "TestInterfaceAsyncIterableDouble::GetNextIterationResult", this, &TestInterfaceAsyncIterableDouble::ResolvePromise, aIterator, promise)); return promise.forget(); diff --git a/dom/bindings/test/TestInterfaceAsyncIterableDouble.h b/dom/bindings/test/TestInterfaceAsyncIterableDouble.h index 34361df88d986..4b458617dc677 100644 --- a/dom/bindings/test/TestInterfaceAsyncIterableDouble.h +++ b/dom/bindings/test/TestInterfaceAsyncIterableDouble.h @@ -47,8 +47,8 @@ class TestInterfaceAsyncIterableDouble final : public nsISupports, void InitAsyncIteratorData(IteratorData& aData, Iterator::IteratorType aType, ErrorResult& aError) {} - already_AddRefed GetNextPromise(Iterator* aIterator, - ErrorResult& aRv); + already_AddRefed GetNextIterationResult(Iterator* aIterator, + ErrorResult& aRv); private: virtual ~TestInterfaceAsyncIterableDouble() = default; diff --git a/dom/bindings/test/TestInterfaceAsyncIterableDoubleUnion.cpp b/dom/bindings/test/TestInterfaceAsyncIterableDoubleUnion.cpp index ebebf290fb780..300574d7d70f0 100644 --- a/dom/bindings/test/TestInterfaceAsyncIterableDoubleUnion.cpp +++ b/dom/bindings/test/TestInterfaceAsyncIterableDoubleUnion.cpp @@ -60,7 +60,8 @@ nsPIDOMWindowInner* TestInterfaceAsyncIterableDoubleUnion::GetParentObject() return mParent; } -already_AddRefed TestInterfaceAsyncIterableDoubleUnion::GetNextPromise( +already_AddRefed +TestInterfaceAsyncIterableDoubleUnion::GetNextIterationResult( Iterator* aIterator, ErrorResult& aRv) { RefPtr promise = Promise::Create(mParent->AsGlobal(), aRv); if (NS_WARN_IF(aRv.Failed())) { @@ -68,7 +69,7 @@ already_AddRefed TestInterfaceAsyncIterableDoubleUnion::GetNextPromise( } NS_DispatchToMainThread(NewRunnableMethod, RefPtr>( - "TestInterfaceAsyncIterableDoubleUnion::GetNextPromise", this, + "TestInterfaceAsyncIterableDoubleUnion::GetNextIterationResult", this, &TestInterfaceAsyncIterableDoubleUnion::ResolvePromise, aIterator, promise)); diff --git a/dom/bindings/test/TestInterfaceAsyncIterableDoubleUnion.h b/dom/bindings/test/TestInterfaceAsyncIterableDoubleUnion.h index 79efb01270a52..902c99b1a91e6 100644 --- a/dom/bindings/test/TestInterfaceAsyncIterableDoubleUnion.h +++ b/dom/bindings/test/TestInterfaceAsyncIterableDoubleUnion.h @@ -47,8 +47,8 @@ class TestInterfaceAsyncIterableDoubleUnion final : public nsISupports, void InitAsyncIteratorData(IteratorData& aData, Iterator::IteratorType aType, ErrorResult& aError) {} - already_AddRefed GetNextPromise(Iterator* aIterator, - ErrorResult& aRv); + already_AddRefed GetNextIterationResult(Iterator* aIterator, + ErrorResult& aRv); private: virtual ~TestInterfaceAsyncIterableDoubleUnion() = default; diff --git a/dom/bindings/test/TestInterfaceAsyncIterableSingle.cpp b/dom/bindings/test/TestInterfaceAsyncIterableSingle.cpp index 694e88c6cc9f1..5c77ef868250f 100644 --- a/dom/bindings/test/TestInterfaceAsyncIterableSingle.cpp +++ b/dom/bindings/test/TestInterfaceAsyncIterableSingle.cpp @@ -66,12 +66,14 @@ void TestInterfaceAsyncIterableSingle::InitAsyncIteratorData( MOZ_ASSERT(aData.mMultiplier == 1); } -already_AddRefed TestInterfaceAsyncIterableSingle::GetNextPromise( - Iterator* aIterator, ErrorResult& aRv) { - return GetNextPromise(aIterator, aIterator->Data(), aRv); +already_AddRefed +TestInterfaceAsyncIterableSingle::GetNextIterationResult(Iterator* aIterator, + ErrorResult& aRv) { + return GetNextIterationResult(aIterator, aIterator->Data(), aRv); } -already_AddRefed TestInterfaceAsyncIterableSingle::GetNextPromise( +already_AddRefed +TestInterfaceAsyncIterableSingle::GetNextIterationResult( IterableIteratorBase* aIterator, IteratorData& aData, ErrorResult& aRv) { RefPtr promise = Promise::Create(mParent->AsGlobal(), aRv); if (NS_WARN_IF(aRv.Failed())) { @@ -81,7 +83,7 @@ already_AddRefed TestInterfaceAsyncIterableSingle::GetNextPromise( nsCOMPtr callResolvePromise = NewRunnableMethod, IteratorData&, RefPtr>( - "TestInterfaceAsyncIterableSingle::GetNextPromise", this, + "TestInterfaceAsyncIterableSingle::GetNextIterationResult", this, &TestInterfaceAsyncIterableSingle::ResolvePromise, aIterator, aData, promise); if (aData.mBlockingPromisesIndex < aData.mBlockingPromises.Length()) { diff --git a/dom/bindings/test/TestInterfaceAsyncIterableSingle.h b/dom/bindings/test/TestInterfaceAsyncIterableSingle.h index 5dbb52e90ed29..a8da2336d6cad 100644 --- a/dom/bindings/test/TestInterfaceAsyncIterableSingle.h +++ b/dom/bindings/test/TestInterfaceAsyncIterableSingle.h @@ -41,8 +41,8 @@ class TestInterfaceAsyncIterableSingle : public nsISupports, const TestInterfaceAsyncIterableSingleOptions& aOptions, ErrorResult& rv); using Iterator = AsyncIterableIterator; - already_AddRefed GetNextPromise(Iterator* aIterator, - ErrorResult& aRv); + already_AddRefed GetNextIterationResult(Iterator* aIterator, + ErrorResult& aRv); struct IteratorData { void Traverse(nsCycleCollectionTraversalCallback& cb); @@ -58,9 +58,8 @@ class TestInterfaceAsyncIterableSingle : public nsISupports, ErrorResult& aError); protected: - already_AddRefed GetNextPromise(IterableIteratorBase* aIterator, - IteratorData& aData, - ErrorResult& aRv); + already_AddRefed GetNextIterationResult( + IterableIteratorBase* aIterator, IteratorData& aData, ErrorResult& aRv); virtual ~TestInterfaceAsyncIterableSingle() = default; diff --git a/dom/bindings/test/TestInterfaceAsyncIterableSingleWithArgs.cpp b/dom/bindings/test/TestInterfaceAsyncIterableSingleWithArgs.cpp index 0ac35615ed97e..02e459fccf4cb 100644 --- a/dom/bindings/test/TestInterfaceAsyncIterableSingleWithArgs.cpp +++ b/dom/bindings/test/TestInterfaceAsyncIterableSingleWithArgs.cpp @@ -5,6 +5,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/TestInterfaceAsyncIterableSingleWithArgs.h" +#include "js/Value.h" #include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h" #include "nsPIDOMWindow.h" #include "mozilla/dom/BindingUtils.h" @@ -13,6 +14,30 @@ namespace mozilla::dom { +NS_IMPL_CYCLE_COLLECTION_CLASS(TestInterfaceAsyncIterableSingleWithArgs) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED( + TestInterfaceAsyncIterableSingleWithArgs, TestInterfaceAsyncIterableSingle) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED( + TestInterfaceAsyncIterableSingleWithArgs, TestInterfaceAsyncIterableSingle) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED( + TestInterfaceAsyncIterableSingleWithArgs, TestInterfaceAsyncIterableSingle) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReturnLastCalledWith) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_ADDREF_INHERITED(TestInterfaceAsyncIterableSingleWithArgs, + TestInterfaceAsyncIterableSingle) +NS_IMPL_RELEASE_INHERITED(TestInterfaceAsyncIterableSingleWithArgs, + TestInterfaceAsyncIterableSingle) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( + TestInterfaceAsyncIterableSingleWithArgs) +NS_INTERFACE_MAP_END_INHERITING(TestInterfaceAsyncIterableSingle) + // static already_AddRefed TestInterfaceAsyncIterableSingleWithArgs::Constructor( @@ -43,10 +68,26 @@ void TestInterfaceAsyncIterableSingleWithArgs::InitAsyncIteratorData( } already_AddRefed -TestInterfaceAsyncIterableSingleWithArgs::GetNextPromise(Iterator* aIterator, - ErrorResult& aRv) { - return TestInterfaceAsyncIterableSingle::GetNextPromise( +TestInterfaceAsyncIterableSingleWithArgs::GetNextIterationResult( + Iterator* aIterator, ErrorResult& aRv) { + return TestInterfaceAsyncIterableSingle::GetNextIterationResult( aIterator, aIterator->Data(), aRv); } +already_AddRefed +TestInterfaceAsyncIterableSingleWithArgs::IteratorReturn( + JSContext* aCx, Iterator* aIterator, JS::Handle aValue, + ErrorResult& aRv) { + ++mReturnCallCount; + + RefPtr promise = Promise::Create(GetParentObject()->AsGlobal(), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + mReturnLastCalledWith = aValue; + promise->MaybeResolve(JS::UndefinedHandleValue); + return promise.forget(); +} + } // namespace mozilla::dom diff --git a/dom/bindings/test/TestInterfaceAsyncIterableSingleWithArgs.h b/dom/bindings/test/TestInterfaceAsyncIterableSingleWithArgs.h index f654cc39d940f..a632f8b6eee7b 100644 --- a/dom/bindings/test/TestInterfaceAsyncIterableSingleWithArgs.h +++ b/dom/bindings/test/TestInterfaceAsyncIterableSingleWithArgs.h @@ -18,6 +18,11 @@ struct TestInterfaceAsyncIteratorOptions; class TestInterfaceAsyncIterableSingleWithArgs final : public TestInterfaceAsyncIterableSingle { public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED( + TestInterfaceAsyncIterableSingleWithArgs, + TestInterfaceAsyncIterableSingle) + using TestInterfaceAsyncIterableSingle::TestInterfaceAsyncIterableSingle; virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; @@ -31,8 +36,23 @@ class TestInterfaceAsyncIterableSingleWithArgs final const TestInterfaceAsyncIteratorOptions& aOptions, ErrorResult& aError); - already_AddRefed GetNextPromise(Iterator* aIterator, + already_AddRefed GetNextIterationResult(Iterator* aIterator, + ErrorResult& aRv); + already_AddRefed IteratorReturn(JSContext* aCx, Iterator* aIterator, + JS::Handle aValue, ErrorResult& aRv); + + uint32_t ReturnCallCount() { return mReturnCallCount; } + void GetReturnLastCalledWith(JSContext* aCx, + JS::MutableHandle aReturnCalledWith) { + aReturnCalledWith.set(mReturnLastCalledWith); + } + + private: + ~TestInterfaceAsyncIterableSingleWithArgs() = default; + + JS::Heap mReturnLastCalledWith; + uint32_t mReturnCallCount = 0; }; } // namespace mozilla::dom diff --git a/dom/bindings/test/test_async_iterable.html b/dom/bindings/test/test_async_iterable.html index 9393ab43dd0a1..8dabf157d8e1c 100644 --- a/dom/bindings/test/test_async_iterable.html +++ b/dom/bindings/test/test_async_iterable.html @@ -14,11 +14,13 @@ await SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]}); }); +const singleValues = Array(10).fill(0).map((_, i) => i * 9 % 7); + async function check_single_result_values(values, multiplier = 1) { dump(JSON.stringify(values)); is(values.length, 10, `AsyncIterableSingle: should return 10 elements`); for (let i = 0; i < 10; i++) { - let expected = i * 9 % 7 * multiplier; + let expected = singleValues[i] * multiplier; is(values[i], expected, `AsyncIterableSingle: should be ${expected}, get ${values[i]}`); } @@ -107,6 +109,60 @@ } check_single_result_values(await Promise.all(values).then(v => v.map(w => w.value))); + + // eslint-disable-next-line no-undef + itr = new TestInterfaceAsyncIterableSingleWithArgs(); + + let callCount = itr.returnCallCount; + + let i = 0; + for await (let v of itr) { + if (++i > 1) { + break; + } + values.push(v); + } + + is(itr.returnCallCount, callCount + 1, + `AsyncIterableSingle: breaking out of for-await-of loop should call "return"`); + is(itr.returnLastCalledWith, undefined, + `AsyncIterableSingleWithArgs: the asynchronous iterator return algorithm should be called with the argument that was passed in.`); + + // eslint-disable-next-line no-undef + itr = new TestInterfaceAsyncIterableSingleWithArgs(); + + async function * yieldFromIterator () { + yield * itr + } + + let yieldingIterator = yieldFromIterator(); + + let result = await yieldingIterator.next(); + is(result.value, singleValues[0], + `AsyncIterableSingle: should be ${singleValues[0]}, get ${result.value}`); + result = await yieldingIterator.next(); + is(result.value, singleValues[1], + `AsyncIterableSingle: should be ${singleValues[1]}, get ${result.value}`); + + result = await yieldingIterator.return("abcd"); + is(typeof result, "object", + `AsyncIterableSingleWithArgs: "return("abcd")" should return { done: true, value: "abcd" }`); + is(result.done, true, + `AsyncIterableSingleWithArgs: "return("abcd")" should return { done: true, value: "abcd" }`); + is(result.value, "abcd", + `AsyncIterableSingleWithArgs: "return("abcd")" should return { done: true, value: "abcd" }`); + is(itr.returnLastCalledWith, "abcd", + `AsyncIterableSingleWithArgs: the asynchronous iterator return algorithm should be called with the argument that was passed in.`); + + result = await yieldingIterator.return("efgh"); + is(typeof result, "object", + `AsyncIterableSingleWithArgs: "return("efgh")" should return { done: true, value: "efgh" }`); + is(result.done, true, + `AsyncIterableSingleWithArgs: "return("efgh")" should return { done: true, value: "efgh" }`); + is(result.value, "efgh", + `AsyncIterableSingleWithArgs: "return("efgh")" should return { done: true, value: "efgh" }`); + is(itr.returnLastCalledWith, "abcd", + `AsyncIterableSingleWithArgs: the asynchronous iterator return algorithm shouldn't be called if the iterator's 'is finished' flag is true already.`); } async function test_data_double() { diff --git a/dom/fs/api/FileSystemDirectoryHandle.cpp b/dom/fs/api/FileSystemDirectoryHandle.cpp index 3ec9b7dcbd28d..a2d4bfaad86e3 100644 --- a/dom/fs/api/FileSystemDirectoryHandle.cpp +++ b/dom/fs/api/FileSystemDirectoryHandle.cpp @@ -56,7 +56,7 @@ void FileSystemDirectoryHandle::InitAsyncIteratorData( fs::FileSystemDirectoryIteratorFactory::Create(mMetadata, aType); } -already_AddRefed FileSystemDirectoryHandle::GetNextPromise( +already_AddRefed FileSystemDirectoryHandle::GetNextIterationResult( FileSystemDirectoryHandle::iterator_t* aIterator, ErrorResult& aError) { return aIterator->Data().mImpl->Next(mGlobal, mManager, aError); } diff --git a/dom/fs/api/FileSystemDirectoryHandle.h b/dom/fs/api/FileSystemDirectoryHandle.h index 99c7a8defcefd..36ac7455c55be 100644 --- a/dom/fs/api/FileSystemDirectoryHandle.h +++ b/dom/fs/api/FileSystemDirectoryHandle.h @@ -53,8 +53,8 @@ class FileSystemDirectoryHandle final : public FileSystemHandle { iterator_t::IteratorType aType, ErrorResult& aError); - [[nodiscard]] already_AddRefed GetNextPromise(iterator_t* aIterator, - ErrorResult& aError); + [[nodiscard]] already_AddRefed GetNextIterationResult( + iterator_t* aIterator, ErrorResult& aError); already_AddRefed GetFileHandle( const nsAString& aName, const FileSystemGetFileOptions& aOptions, diff --git a/dom/fs/test/gtest/api/TestFileSystemDirectoryHandle.cpp b/dom/fs/test/gtest/api/TestFileSystemDirectoryHandle.cpp index 89662916607a1..647f289bbd24d 100644 --- a/dom/fs/test/gtest/api/TestFileSystemDirectoryHandle.cpp +++ b/dom/fs/test/gtest/api/TestFileSystemDirectoryHandle.cpp @@ -86,7 +86,8 @@ TEST_F(TestFileSystemDirectoryHandle, isNextPromiseReturned) { iterator->Data().mImpl = std::move(mockIter); IgnoredErrorResult rv; - RefPtr promise = dirHandle->GetNextPromise(iterator.get(), rv); + RefPtr promise = + dirHandle->GetNextIterationResult(iterator.get(), rv); ASSERT_TRUE(promise); } diff --git a/dom/webidl/TestInterfaceJSMaplikeSetlikeIterable.webidl b/dom/webidl/TestInterfaceJSMaplikeSetlikeIterable.webidl index e9c88dd08da47..2363a8930b29a 100644 --- a/dom/webidl/TestInterfaceJSMaplikeSetlikeIterable.webidl +++ b/dom/webidl/TestInterfaceJSMaplikeSetlikeIterable.webidl @@ -120,7 +120,12 @@ interface TestInterfaceAsyncIterableSingleWithArgs { [Throws] constructor(); + [GenerateReturnMethod] async iterable(optional TestInterfaceAsyncIteratorOptions options = {}); + + readonly attribute long returnCallCount; + + readonly attribute any returnLastCalledWith; }; [Pref="dom.expose_test_interfaces",