mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
Bug 1788969 - Align async iterable code more closely with the spec. r=edgar
Implement the common steps for the next method from https://webidl.spec.whatwg.org/#es-asynchronous-iterator-prototype-object in a base class, that all async iterable iterator objects inherit from. Natives that implement an async iterable only need to implement the "getting the next iteration result" part in their GetNextPromise method. This means they don't have to create the object according to "CreateIterResultObject" themselves, but can just create promise and often resolve it with a native value directly. We've switched to a special JS::Value to signal "end of iteration", but that's hidden inside the iterator_utils::ResolvePromiseForFinished helper. The WebIDL parser now uses the right return type for the generated "next" method, which means that any exceptions in the binding code itself will actually be correctly converted to a rejected promise instead of being rethrown. This also uses a class for the generated iterable iterator that's not exposed outside the binding code. No other code should create and/or wrap these anyway. Differential Revision: https://phabricator.services.mozilla.com/D156323
This commit is contained in:
parent
bd1c6c4d03
commit
3e507adf99
@ -35,7 +35,6 @@ from Configuration import (
|
||||
getAllTypes,
|
||||
Descriptor,
|
||||
MemberIsLegacyUnforgeable,
|
||||
iteratorNativeType,
|
||||
)
|
||||
|
||||
AUTOGENERATED_WARNING_COMMENT = (
|
||||
@ -5305,7 +5304,7 @@ class CastableObjectUnwrapper:
|
||||
}
|
||||
""",
|
||||
exceptionCode=exceptionCode,
|
||||
**self.substitution
|
||||
**self.substitution,
|
||||
)
|
||||
else:
|
||||
self.substitution["codeOnFailure"] = codeOnFailure
|
||||
@ -5325,7 +5324,7 @@ class CastableObjectUnwrapper:
|
||||
}
|
||||
}
|
||||
""",
|
||||
**substitution
|
||||
**substitution,
|
||||
)
|
||||
|
||||
|
||||
@ -12812,7 +12811,7 @@ class CGUnionStruct(CGThing):
|
||||
mType = e${name};
|
||||
return mValue.m${name}.SetValue(${ctorArgs});
|
||||
""",
|
||||
**vars
|
||||
**vars,
|
||||
)
|
||||
|
||||
# bodyInHeader must be false for return values because they own
|
||||
@ -12879,7 +12878,7 @@ class CGUnionStruct(CGThing):
|
||||
mValue.m${name}.Destroy();
|
||||
mType = eUninitialized;
|
||||
""",
|
||||
**vars
|
||||
**vars,
|
||||
)
|
||||
methods.append(
|
||||
ClassMethod(
|
||||
@ -12897,7 +12896,7 @@ class CGUnionStruct(CGThing):
|
||||
MOZ_RELEASE_ASSERT(Is${name}(), "Wrong type!");
|
||||
return mValue.m${name}.Value();
|
||||
""",
|
||||
**vars
|
||||
**vars,
|
||||
)
|
||||
# The non-const version of GetAs* returns our internal type
|
||||
getterReturnType = "%s&" % vars["structType"]
|
||||
@ -13244,7 +13243,7 @@ class CGUnionConversionStruct(CGThing):
|
||||
mUnion.mType = mUnion.e${name};
|
||||
return mUnion.mValue.m${name}.SetValue(${ctorArgs});
|
||||
""",
|
||||
**vars
|
||||
**vars,
|
||||
)
|
||||
methods.append(
|
||||
ClassMethod(
|
||||
@ -22229,14 +22228,17 @@ class CGIterableMethodGenerator(CGGeneric):
|
||||
return
|
||||
|
||||
if descriptor.interface.isIterable():
|
||||
assert descriptor.interface.maplikeOrSetlikeOrIterable.isPairIterator()
|
||||
assert len(args) == 0
|
||||
|
||||
binding = descriptor.interface.identifier.name + "Iterator_Binding"
|
||||
iterClass = f"mozilla::dom::binding_detail::WrappableIterableIterator<{descriptor.nativeType}>"
|
||||
init = ""
|
||||
else:
|
||||
assert descriptor.interface.isAsyncIterable()
|
||||
|
||||
binding = descriptor.interface.identifier.name + "AsyncIterator_Binding"
|
||||
iterClass = f"mozilla::dom::binding_detail::WrappableAsyncIterableIterator<{descriptor.nativeType}>"
|
||||
init = fill(
|
||||
"""
|
||||
{
|
||||
@ -22260,7 +22262,7 @@ class CGIterableMethodGenerator(CGGeneric):
|
||||
&${binding}::Wrap));
|
||||
$*{init}
|
||||
""",
|
||||
iterClass=iteratorNativeType(descriptor),
|
||||
iterClass=iterClass,
|
||||
itrMethod=methodName.title(),
|
||||
binding=binding,
|
||||
init=init,
|
||||
|
@ -757,6 +757,8 @@ class Descriptor(DescriptorProvider):
|
||||
if name in self.implicitJSContext:
|
||||
attrs.append("implicitJSContext")
|
||||
if member.isMethod():
|
||||
if self.interface.isAsyncIteratorInterface() and name == "next":
|
||||
attrs.append("implicitJSContext")
|
||||
# JSObject-returning [NewObject] methods must be fallible,
|
||||
# since they have to (fallibly) allocate the new JSObject.
|
||||
if member.getExtendedAttribute("NewObject"):
|
||||
@ -1050,7 +1052,10 @@ def iteratorNativeType(descriptor):
|
||||
assert iterableDecl.isPairIterator() or descriptor.interface.isAsyncIterable()
|
||||
if descriptor.interface.isIterable():
|
||||
return "mozilla::dom::IterableIterator<%s>" % descriptor.nativeType
|
||||
return "mozilla::dom::AsyncIterableIterator<%s>" % descriptor.nativeType
|
||||
return (
|
||||
"mozilla::dom::binding_detail::AsyncIterableIteratorNoReturn<%s>"
|
||||
% descriptor.nativeType
|
||||
)
|
||||
|
||||
|
||||
def findInnermostType(t):
|
||||
|
@ -5,6 +5,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/dom/IterableIterator.h"
|
||||
#include "mozilla/dom/Promise-inl.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
@ -31,7 +32,8 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IterableIteratorBase)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
namespace iterator_utils {
|
||||
void DictReturn(JSContext* aCx, JS::MutableHandle<JSObject*> aResult,
|
||||
|
||||
void DictReturn(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
|
||||
bool aDone, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
|
||||
RootedDictionary<IterableKeyOrValueResult> dict(aCx);
|
||||
dict.mDone = aDone;
|
||||
@ -41,6 +43,16 @@ void DictReturn(JSContext* aCx, JS::MutableHandle<JSObject*> aResult,
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
aResult.set(dictValue);
|
||||
}
|
||||
|
||||
void DictReturn(JSContext* aCx, JS::MutableHandle<JSObject*> aResult,
|
||||
bool aDone, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
|
||||
JS::Rooted<JS::Value> dictValue(aCx);
|
||||
DictReturn(aCx, &dictValue, aDone, aValue, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
aResult.set(&dictValue.toObject());
|
||||
}
|
||||
|
||||
@ -67,46 +79,153 @@ void KeyAndValueReturn(JSContext* aCx, JS::Handle<JS::Value> aKey,
|
||||
aResult.set(&dictValue.toObject());
|
||||
}
|
||||
|
||||
void ResolvePromiseForFinished(JSContext* aCx, Promise* aPromise) {
|
||||
MOZ_ASSERT(aPromise);
|
||||
|
||||
ErrorResult error;
|
||||
JS::Rooted<JSObject*> dict(aCx);
|
||||
DictReturn(aCx, &dict, true, JS::UndefinedHandleValue, error);
|
||||
if (error.Failed()) {
|
||||
aPromise->MaybeReject(std::move(error));
|
||||
return;
|
||||
}
|
||||
aPromise->MaybeResolve(dict);
|
||||
}
|
||||
|
||||
void ResolvePromiseWithKeyOrValue(JSContext* aCx, Promise* aPromise,
|
||||
JS::Handle<JS::Value> aKeyOrValue) {
|
||||
MOZ_ASSERT(aPromise);
|
||||
|
||||
ErrorResult error;
|
||||
JS::Rooted<JSObject*> dict(aCx);
|
||||
DictReturn(aCx, &dict, false, aKeyOrValue, error);
|
||||
if (error.Failed()) {
|
||||
aPromise->MaybeReject(std::move(error));
|
||||
return;
|
||||
}
|
||||
aPromise->MaybeResolve(dict);
|
||||
}
|
||||
|
||||
void ResolvePromiseWithKeyAndValue(JSContext* aCx, Promise* aPromise,
|
||||
JS::Handle<JS::Value> aKey,
|
||||
JS::Handle<JS::Value> aValue) {
|
||||
MOZ_ASSERT(aPromise);
|
||||
|
||||
ErrorResult error;
|
||||
JS::Rooted<JSObject*> dict(aCx);
|
||||
KeyAndValueReturn(aCx, aKey, aValue, &dict, error);
|
||||
if (error.Failed()) {
|
||||
aPromise->MaybeReject(std::move(error));
|
||||
return;
|
||||
}
|
||||
aPromise->MaybeResolve(dict);
|
||||
}
|
||||
} // namespace iterator_utils
|
||||
|
||||
namespace binding_detail {
|
||||
|
||||
static already_AddRefed<Promise> PromiseOrErr(
|
||||
Result<RefPtr<Promise>, nsresult>&& aResult, ErrorResult& aError) {
|
||||
if (aResult.isErr()) {
|
||||
aError.Throw(aResult.unwrapErr());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return aResult.unwrap().forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> AsyncIterableNextImpl::NextSteps(
|
||||
JSContext* aCx, AsyncIterableIteratorBase* aObject,
|
||||
nsIGlobalObject* aGlobalObject, ErrorResult& aRv) {
|
||||
// 2. If object’s is finished is true, then:
|
||||
if (aObject->mIsFinished) {
|
||||
// 1. Let result be CreateIterResultObject(undefined, true).
|
||||
JS::Rooted<JS::Value> dict(aCx);
|
||||
iterator_utils::DictReturn(aCx, &dict, true, JS::UndefinedHandleValue, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return Promise::CreateRejectedWithErrorResult(aGlobalObject, aRv);
|
||||
}
|
||||
|
||||
// 2. Perform ! Call(nextPromiseCapability.[[Resolve]], undefined,
|
||||
// «result»).
|
||||
// 3. Return nextPromiseCapability.[[Promise]].
|
||||
return Promise::Resolve(aGlobalObject, aCx, dict, aRv);
|
||||
}
|
||||
|
||||
// 4. Let nextPromise be the result of getting the next iteration result with
|
||||
// object’s target and object.
|
||||
RefPtr<Promise> nextPromise = GetNextPromise(aRv);
|
||||
|
||||
// 5. Let fulfillSteps be the following steps, given next:
|
||||
auto fulfillSteps = [](JSContext* aCx, JS::Handle<JS::Value> aNext,
|
||||
ErrorResult& aRv,
|
||||
const RefPtr<AsyncIterableIteratorBase>& aObject,
|
||||
const nsCOMPtr<nsIGlobalObject>& aGlobalObject)
|
||||
-> already_AddRefed<Promise> {
|
||||
// 1. Set object’s ongoing promise to null.
|
||||
aObject->mOngoingPromise = nullptr;
|
||||
|
||||
// 2. If next is end of iteration, then:
|
||||
JS::Rooted<JS::Value> dict(aCx);
|
||||
if (aNext.isMagic(binding_details::END_OF_ITERATION)) {
|
||||
// 1. Set object’s is finished to true.
|
||||
aObject->mIsFinished = true;
|
||||
// 2. Return CreateIterResultObject(undefined, true).
|
||||
iterator_utils::DictReturn(aCx, &dict, true, JS::UndefinedHandleValue,
|
||||
aRv);
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
// 3. Otherwise, if interface has a pair asynchronously iterable
|
||||
// declaration:
|
||||
// 1. Assert: next is a value pair.
|
||||
// 2. Return the iterator result for next and kind.
|
||||
// 4. Otherwise:
|
||||
// 1. Assert: interface has a value asynchronously iterable declaration.
|
||||
// 2. Assert: next is a value of the type that appears in the
|
||||
// declaration.
|
||||
// 3. Let value be next, converted to an ECMAScript value.
|
||||
// 4. Return CreateIterResultObject(value, false).
|
||||
iterator_utils::DictReturn(aCx, &dict, false, aNext, aRv);
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
// Note that ThenCatchWithCycleCollectedArgs expects a Promise, so
|
||||
// we use Promise::Resolve here. The specs do convert this to a
|
||||
// promise too at another point, but the end result should be the
|
||||
// same.
|
||||
return Promise::Resolve(aGlobalObject, aCx, dict, aRv);
|
||||
};
|
||||
// 7. Let rejectSteps be the following steps, given reason:
|
||||
auto rejectSteps = [](JSContext* aCx, JS::Handle<JS::Value> aReason,
|
||||
ErrorResult& aRv,
|
||||
const RefPtr<AsyncIterableIteratorBase>& aObject,
|
||||
const nsCOMPtr<nsIGlobalObject>& aGlobalObject) {
|
||||
// 1. Set object’s ongoing promise to null.
|
||||
aObject->mOngoingPromise = nullptr;
|
||||
// 2. Set object’s is finished to true.
|
||||
aObject->mIsFinished = true;
|
||||
// 3. Throw reason.
|
||||
return Promise::Reject(aGlobalObject, aCx, aReason, aRv);
|
||||
};
|
||||
// 9. Perform PerformPromiseThen(nextPromise, onFulfilled, onRejected,
|
||||
// nextPromiseCapability).
|
||||
Result<RefPtr<Promise>, nsresult> result =
|
||||
nextPromise->ThenCatchWithCycleCollectedArgs(
|
||||
std::move(fulfillSteps), std::move(rejectSteps), RefPtr{aObject},
|
||||
nsCOMPtr{aGlobalObject});
|
||||
|
||||
// 10. Return nextPromiseCapability.[[Promise]].
|
||||
return PromiseOrErr(std::move(result), aRv);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> AsyncIterableNextImpl::Next(
|
||||
JSContext* aCx, AsyncIterableIteratorBase* aObject,
|
||||
nsISupports* aGlobalObject, ErrorResult& aRv) {
|
||||
nsCOMPtr<nsIGlobalObject> globalObject = do_QueryInterface(aGlobalObject);
|
||||
|
||||
// 3.7.10.2. Asynchronous iterator prototype object
|
||||
// …
|
||||
// 10. If ongoingPromise is not null, then:
|
||||
if (aObject->mOngoingPromise) {
|
||||
// 1. Let afterOngoingPromiseCapability be
|
||||
// ! NewPromiseCapability(%Promise%).
|
||||
// 2. Let onSettled be CreateBuiltinFunction(nextSteps, « »).
|
||||
|
||||
// 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) {
|
||||
return NextSteps(aCx, aObject, aGlobalObject, aRv);
|
||||
};
|
||||
|
||||
// 3. Perform PerformPromiseThen(ongoingPromise, onSettled, onSettled,
|
||||
// afterOngoingPromiseCapability).
|
||||
Result<RefPtr<Promise>, nsresult> afterOngoingPromise =
|
||||
aObject->mOngoingPromise->ThenCatchWithCycleCollectedArgs(
|
||||
onSettled, onSettled, RefPtr{aObject}, std::move(globalObject));
|
||||
if (afterOngoingPromise.isErr()) {
|
||||
aRv.Throw(afterOngoingPromise.unwrapErr());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 4. Set object’s ongoing promise to
|
||||
// afterOngoingPromiseCapability.[[Promise]].
|
||||
aObject->mOngoingPromise = afterOngoingPromise.unwrap().forget();
|
||||
} else {
|
||||
// 11. Otherwise:
|
||||
// 1. Set object’s ongoing promise to the result of running nextSteps.
|
||||
aObject->mOngoingPromise = NextSteps(aCx, aObject, globalObject, aRv);
|
||||
}
|
||||
|
||||
// 12. Return object’s ongoing promise.
|
||||
return do_AddRef(aObject->mOngoingPromise);
|
||||
}
|
||||
|
||||
} // namespace binding_detail
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "js/TypeDecls.h"
|
||||
#include "js/Value.h"
|
||||
#include "nsISupports.h"
|
||||
#include "mozilla/AlreadyAddRefed.h"
|
||||
#include "mozilla/dom/IterableIteratorBinding.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/RootedDictionary.h"
|
||||
@ -39,7 +40,19 @@
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
namespace binding_details {
|
||||
|
||||
// JS::MagicValue(END_OF_ITERATION) is the value we use for
|
||||
// https://webidl.spec.whatwg.org/#end-of-iteration. It shouldn't be returned to
|
||||
// JS, because AsyncIterableIteratorBase::NextSteps will detect it and will
|
||||
// return the result of CreateIterResultObject(undefined, true) instead
|
||||
// (discarding the magic value).
|
||||
static const JSWhyMagic END_OF_ITERATION = JS_GENERIC_MAGIC;
|
||||
|
||||
} // namespace binding_details
|
||||
|
||||
namespace iterator_utils {
|
||||
|
||||
void DictReturn(JSContext* aCx, JS::MutableHandle<JSObject*> aResult,
|
||||
bool aDone, JS::Handle<JS::Value> aValue, ErrorResult& aRv);
|
||||
|
||||
@ -47,14 +60,16 @@ void KeyAndValueReturn(JSContext* aCx, JS::Handle<JS::Value> aKey,
|
||||
JS::Handle<JS::Value> aValue,
|
||||
JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv);
|
||||
|
||||
void ResolvePromiseForFinished(JSContext* aCx, Promise* aPromise);
|
||||
inline void ResolvePromiseForFinished(Promise* aPromise) {
|
||||
aPromise->MaybeResolve(JS::MagicValue(binding_details::END_OF_ITERATION));
|
||||
}
|
||||
|
||||
void ResolvePromiseWithKeyOrValue(JSContext* aCx, Promise* aPromise,
|
||||
JS::Handle<JS::Value> aKeyOrValue);
|
||||
template <typename Key, typename Value>
|
||||
void ResolvePromiseWithKeyAndValue(Promise* aPromise, const Key& aKey,
|
||||
const Value& aValue) {
|
||||
aPromise->MaybeResolve(MakeTuple(aKey, aValue));
|
||||
}
|
||||
|
||||
void ResolvePromiseWithKeyAndValue(JSContext* aCx, Promise* aPromise,
|
||||
JS::Handle<JS::Value> aKey,
|
||||
JS::Handle<JS::Value> aValue);
|
||||
} // namespace iterator_utils
|
||||
|
||||
class IterableIteratorBase : public nsISupports {
|
||||
@ -209,44 +224,46 @@ class IterableIterator final : public IterableIteratorBase {
|
||||
uint32_t mIndex;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class AsyncIterableIterator final : public IterableIteratorBase,
|
||||
public SupportsWeakPtr {
|
||||
public:
|
||||
typedef bool (*WrapFunc)(JSContext* aCx, AsyncIterableIterator<T>* aObject,
|
||||
JS::Handle<JSObject*> aGivenProto,
|
||||
JS::MutableHandle<JSObject*> aReflector);
|
||||
namespace binding_detail {
|
||||
|
||||
explicit AsyncIterableIterator(T* aIterableObj, IteratorType aIteratorType,
|
||||
WrapFunc aWrapFunc)
|
||||
: mIterableObj(aIterableObj),
|
||||
mIteratorType(aIteratorType),
|
||||
mWrapFunc(aWrapFunc) {
|
||||
class AsyncIterableNextImpl;
|
||||
|
||||
} // namespace binding_detail
|
||||
|
||||
class AsyncIterableIteratorBase : public IterableIteratorBase {
|
||||
public:
|
||||
IteratorType GetIteratorType() { return mIteratorType; }
|
||||
|
||||
protected:
|
||||
explicit AsyncIterableIteratorBase(IteratorType aIteratorType)
|
||||
: mIteratorType(aIteratorType) {}
|
||||
|
||||
private:
|
||||
friend class binding_detail::AsyncIterableNextImpl;
|
||||
|
||||
// 3.7.10.1. Default asynchronous iterator objects
|
||||
// Target is in AsyncIterableIterator
|
||||
// Kind
|
||||
IteratorType mIteratorType;
|
||||
// Ongoing promise
|
||||
RefPtr<Promise> mOngoingPromise;
|
||||
// Is finished
|
||||
bool mIsFinished = false;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class AsyncIterableIterator : public AsyncIterableIteratorBase,
|
||||
public SupportsWeakPtr {
|
||||
public:
|
||||
AsyncIterableIterator(T* aIterableObj, IteratorType aIteratorType)
|
||||
: AsyncIterableIteratorBase(aIteratorType), mIterableObj(aIterableObj) {
|
||||
MOZ_ASSERT(mIterableObj);
|
||||
MOZ_ASSERT(mWrapFunc);
|
||||
}
|
||||
|
||||
void SetData(void* aData) { mData = aData; }
|
||||
|
||||
void* GetData() { return mData; }
|
||||
|
||||
IteratorType GetIteratorType() { return mIteratorType; }
|
||||
|
||||
void Next(JSContext* aCx, JS::MutableHandle<JSObject*> aResult,
|
||||
ErrorResult& aRv) {
|
||||
RefPtr<Promise> promise = mIterableObj->GetNextPromise(aCx, this, aRv);
|
||||
if (!promise) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
aResult.set(promise->PromiseObj());
|
||||
}
|
||||
|
||||
bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
|
||||
JS::MutableHandle<JSObject*> aObj) {
|
||||
return (*mWrapFunc)(aCx, this, aGivenProto, aObj);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ~AsyncIterableIterator() {
|
||||
// As long as iterable object does not hold strong ref to its iterators,
|
||||
@ -272,16 +289,80 @@ class AsyncIterableIterator final : public IterableIteratorBase,
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIterableObj);
|
||||
}
|
||||
|
||||
// Binding Implementation object that we're iterating over.
|
||||
// 3.7.10.1. Default asynchronous iterator objects
|
||||
// Target
|
||||
RefPtr<T> mIterableObj;
|
||||
// Tells whether this is a key, value, or entries iterator.
|
||||
IteratorType mIteratorType;
|
||||
// Function pointer to binding-type-specific Wrap() call for this iterator.
|
||||
WrapFunc mWrapFunc;
|
||||
// Kind
|
||||
// Ongoing promise
|
||||
// Is finished
|
||||
// See AsyncIterableIteratorBase
|
||||
|
||||
// Opaque data of the backing object.
|
||||
void* mData{nullptr};
|
||||
};
|
||||
|
||||
namespace binding_detail {
|
||||
|
||||
template <typename T>
|
||||
using WrappableIterableIterator = IterableIterator<T>;
|
||||
|
||||
class AsyncIterableNextImpl {
|
||||
protected:
|
||||
already_AddRefed<Promise> Next(JSContext* aCx,
|
||||
AsyncIterableIteratorBase* aObject,
|
||||
nsISupports* aGlobalObject, ErrorResult& aRv);
|
||||
virtual already_AddRefed<Promise> GetNextPromise(ErrorResult& aRv) = 0;
|
||||
|
||||
private:
|
||||
already_AddRefed<Promise> NextSteps(JSContext* aCx,
|
||||
AsyncIterableIteratorBase* aObject,
|
||||
nsIGlobalObject* aGlobalObject,
|
||||
ErrorResult& aRv);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class AsyncIterableIteratorNoReturn : public AsyncIterableIterator<T>,
|
||||
public AsyncIterableNextImpl {
|
||||
public:
|
||||
using WrapFunc = bool (*)(JSContext* aCx,
|
||||
AsyncIterableIteratorNoReturn<T>* aObject,
|
||||
JS::Handle<JSObject*> aGivenProto,
|
||||
JS::MutableHandle<JSObject*> aReflector);
|
||||
using AsyncIterableIteratorBase::IteratorType;
|
||||
|
||||
AsyncIterableIteratorNoReturn(T* aIterableObj, IteratorType aIteratorType,
|
||||
WrapFunc aWrapFunc)
|
||||
: AsyncIterableIterator<T>(aIterableObj, aIteratorType),
|
||||
mWrapFunc(aWrapFunc) {
|
||||
MOZ_ASSERT(mWrapFunc);
|
||||
}
|
||||
|
||||
bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
|
||||
JS::MutableHandle<JSObject*> aObj) {
|
||||
return (*mWrapFunc)(aCx, this, aGivenProto, aObj);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> Next(JSContext* aCx, ErrorResult& aRv) {
|
||||
return AsyncIterableNextImpl::Next(
|
||||
aCx, this, this->mIterableObj->GetParentObject(), aRv);
|
||||
}
|
||||
|
||||
protected:
|
||||
already_AddRefed<Promise> GetNextPromise(ErrorResult& aRv) override {
|
||||
return this->mIterableObj->GetNextPromise(
|
||||
static_cast<AsyncIterableIterator<T>*>(this), aRv);
|
||||
}
|
||||
|
||||
private:
|
||||
// Function pointer to binding-type-specific Wrap() call for this iterator.
|
||||
WrapFunc mWrapFunc;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using WrappableAsyncIterableIterator = AsyncIterableIteratorNoReturn<T>;
|
||||
|
||||
} // namespace binding_detail
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
||||
#endif // mozilla_dom_IterableIterator_h
|
||||
|
@ -406,6 +406,34 @@ template <typename T>
|
||||
return true;
|
||||
}
|
||||
|
||||
// Accept tuple of other things we accept. The result will be a JS array object.
|
||||
template <typename... Elements>
|
||||
[[nodiscard]] bool ToJSValue(JSContext* aCx,
|
||||
const Tuple<Elements...>& aArguments,
|
||||
JS::MutableHandle<JS::Value> aValue) {
|
||||
// Make sure we're called in a compartment
|
||||
MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
|
||||
|
||||
JS::RootedVector<JS::Value> v(aCx);
|
||||
if (!v.resize(sizeof...(Elements))) {
|
||||
return false;
|
||||
}
|
||||
bool ok = true;
|
||||
size_t i = 0;
|
||||
ForEach(aArguments, [aCx, &ok, &v, &i](auto& aElem) {
|
||||
ok = ok && ToJSValue(aCx, aElem, v[i++]);
|
||||
});
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
JSObject* arrayObj = JS::NewArrayObject(aCx, v);
|
||||
if (!arrayObj) {
|
||||
return false;
|
||||
}
|
||||
aValue.setObject(*arrayObj);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Accept records of other things we accept. N.B. This assumes that
|
||||
// keys are either UTF-8 or UTF-16-ish. See Bug 1706058.
|
||||
template <typename K, typename V>
|
||||
|
@ -8902,10 +8902,16 @@ class Parser(Tokenizer):
|
||||
def simpleExtendedAttr(str):
|
||||
return IDLExtendedAttribute(iface.location, (str,))
|
||||
|
||||
if isinstance(iterable, IDLAsyncIterable):
|
||||
nextReturnType = IDLPromiseType(
|
||||
iterable.location, BuiltinTypes[IDLBuiltinType.Types.any]
|
||||
)
|
||||
else:
|
||||
nextReturnType = BuiltinTypes[IDLBuiltinType.Types.object]
|
||||
nextMethod = IDLMethod(
|
||||
iface.location,
|
||||
IDLUnresolvedIdentifier(iface.location, "next"),
|
||||
BuiltinTypes[IDLBuiltinType.Types.object],
|
||||
nextReturnType,
|
||||
[],
|
||||
)
|
||||
nextMethod.addExtendedAttributes([simpleExtendedAttr("Throws")])
|
||||
|
@ -67,61 +67,42 @@ void TestInterfaceAsyncIterableDouble::DestroyAsyncIterator(
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> TestInterfaceAsyncIterableDouble::GetNextPromise(
|
||||
JSContext* aCx, Iterator* aIterator, ErrorResult& aRv) {
|
||||
Iterator* aIterator, ErrorResult& aRv) {
|
||||
RefPtr<Promise> promise = Promise::Create(mParent->AsGlobal(), aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* data = reinterpret_cast<IteratorData*>(aIterator->GetData());
|
||||
data->mPromise = promise;
|
||||
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
||||
"TestInterfaceAsyncIterableDouble::GetNextPromise",
|
||||
[self = RefPtr{this}, iterator = RefPtr{aIterator}] {
|
||||
self->ResolvePromise(iterator);
|
||||
}));
|
||||
NS_DispatchToMainThread(NewRunnableMethod<RefPtr<Iterator>, RefPtr<Promise>>(
|
||||
"TestInterfaceAsyncIterableDouble::GetNextPromise", this,
|
||||
&TestInterfaceAsyncIterableDouble::ResolvePromise, aIterator, promise));
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
void TestInterfaceAsyncIterableDouble::ResolvePromise(Iterator* aIterator) {
|
||||
void TestInterfaceAsyncIterableDouble::ResolvePromise(Iterator* aIterator,
|
||||
Promise* aPromise) {
|
||||
IteratorData* data = reinterpret_cast<IteratorData*>(aIterator->GetData());
|
||||
|
||||
AutoJSAPI jsapi;
|
||||
if (NS_WARN_IF(!jsapi.Init(mParent))) {
|
||||
data->mPromise->MaybeRejectWithInvalidStateError(
|
||||
"Couldn't get the global.");
|
||||
return;
|
||||
}
|
||||
JSContext* cx = jsapi.cx();
|
||||
|
||||
// Test data: ['a', 'b'], ['c', 'd'], ['e', 'f']
|
||||
uint32_t idx = data->mIndex;
|
||||
if (idx >= mValues.Length()) {
|
||||
iterator_utils::ResolvePromiseForFinished(cx, data->mPromise);
|
||||
iterator_utils::ResolvePromiseForFinished(aPromise);
|
||||
} else {
|
||||
JS::Rooted<JS::Value> key(cx);
|
||||
JS::Rooted<JS::Value> value(cx);
|
||||
switch (aIterator->GetIteratorType()) {
|
||||
case IterableIteratorBase::IteratorType::Keys:
|
||||
Unused << ToJSValue(cx, mValues[idx].first, &key);
|
||||
iterator_utils::ResolvePromiseWithKeyOrValue(cx, data->mPromise, key);
|
||||
aPromise->MaybeResolve(mValues[idx].first);
|
||||
break;
|
||||
case IterableIteratorBase::IteratorType::Values:
|
||||
Unused << ToJSValue(cx, mValues[idx].second, &value);
|
||||
iterator_utils::ResolvePromiseWithKeyOrValue(cx, data->mPromise, value);
|
||||
aPromise->MaybeResolve(mValues[idx].second);
|
||||
break;
|
||||
case IterableIteratorBase::IteratorType::Entries:
|
||||
Unused << ToJSValue(cx, mValues[idx].first, &key);
|
||||
Unused << ToJSValue(cx, mValues[idx].second, &value);
|
||||
iterator_utils::ResolvePromiseWithKeyAndValue(cx, data->mPromise, key,
|
||||
value);
|
||||
iterator_utils::ResolvePromiseWithKeyAndValue(
|
||||
aPromise, mValues[idx].first, mValues[idx].second);
|
||||
break;
|
||||
}
|
||||
|
||||
data->mIndex++;
|
||||
data->mPromise = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ class TestInterfaceAsyncIterableDouble final : public nsISupports,
|
||||
using Iterator = AsyncIterableIterator<TestInterfaceAsyncIterableDouble>;
|
||||
void InitAsyncIterator(Iterator* aIterator, ErrorResult& aError);
|
||||
void DestroyAsyncIterator(Iterator* aIterator);
|
||||
already_AddRefed<Promise> GetNextPromise(JSContext* aCx, Iterator* aIterator,
|
||||
already_AddRefed<Promise> GetNextPromise(Iterator* aIterator,
|
||||
ErrorResult& aRv);
|
||||
|
||||
private:
|
||||
@ -57,7 +57,7 @@ class TestInterfaceAsyncIterableDouble final : public nsISupports,
|
||||
uint32_t mIndex;
|
||||
};
|
||||
virtual ~TestInterfaceAsyncIterableDouble() = default;
|
||||
void ResolvePromise(Iterator* aIterator);
|
||||
void ResolvePromise(Iterator* aIterator, Promise* aPromise);
|
||||
|
||||
nsCOMPtr<nsPIDOMWindowInner> mParent;
|
||||
nsTArray<std::pair<nsString, nsString>> mValues;
|
||||
|
@ -73,63 +73,44 @@ void TestInterfaceAsyncIterableDoubleUnion::DestroyAsyncIterator(
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> TestInterfaceAsyncIterableDoubleUnion::GetNextPromise(
|
||||
JSContext* aCx, Iterator* aIterator, ErrorResult& aRv) {
|
||||
Iterator* aIterator, ErrorResult& aRv) {
|
||||
RefPtr<Promise> promise = Promise::Create(mParent->AsGlobal(), aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* data = reinterpret_cast<IteratorData*>(aIterator->GetData());
|
||||
data->mPromise = promise;
|
||||
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
||||
"TestInterfaceAsyncIterableDoubleUnion::GetNextPromise",
|
||||
[self = RefPtr{this}, iterator = RefPtr{aIterator}] {
|
||||
self->ResolvePromise(iterator);
|
||||
}));
|
||||
NS_DispatchToMainThread(NewRunnableMethod<RefPtr<Iterator>, RefPtr<Promise>>(
|
||||
"TestInterfaceAsyncIterableDoubleUnion::GetNextPromise", this,
|
||||
&TestInterfaceAsyncIterableDoubleUnion::ResolvePromise, aIterator,
|
||||
promise));
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
void TestInterfaceAsyncIterableDoubleUnion::ResolvePromise(
|
||||
Iterator* aIterator) {
|
||||
void TestInterfaceAsyncIterableDoubleUnion::ResolvePromise(Iterator* aIterator,
|
||||
Promise* aPromise) {
|
||||
IteratorData* data = reinterpret_cast<IteratorData*>(aIterator->GetData());
|
||||
|
||||
AutoJSAPI jsapi;
|
||||
if (NS_WARN_IF(!jsapi.Init(mParent))) {
|
||||
data->mPromise->MaybeRejectWithInvalidStateError(
|
||||
"Couldn't get the global.");
|
||||
return;
|
||||
}
|
||||
JSContext* cx = jsapi.cx();
|
||||
|
||||
// Test data:
|
||||
// [long, 1], [string, "a"]
|
||||
uint32_t idx = data->mIndex;
|
||||
if (idx >= mValues.Length()) {
|
||||
iterator_utils::ResolvePromiseForFinished(cx, data->mPromise);
|
||||
iterator_utils::ResolvePromiseForFinished(aPromise);
|
||||
} else {
|
||||
JS::Rooted<JS::Value> key(cx);
|
||||
JS::Rooted<JS::Value> value(cx);
|
||||
switch (aIterator->GetIteratorType()) {
|
||||
case IterableIteratorBase::IteratorType::Keys:
|
||||
Unused << ToJSValue(cx, mValues[idx].first, &key);
|
||||
iterator_utils::ResolvePromiseWithKeyOrValue(cx, data->mPromise, key);
|
||||
aPromise->MaybeResolve(mValues[idx].first);
|
||||
break;
|
||||
case IterableIteratorBase::IteratorType::Values:
|
||||
Unused << ToJSValue(cx, mValues[idx].second, &value);
|
||||
iterator_utils::ResolvePromiseWithKeyOrValue(cx, data->mPromise, value);
|
||||
aPromise->MaybeResolve(mValues[idx].second);
|
||||
break;
|
||||
case IterableIteratorBase::IteratorType::Entries:
|
||||
Unused << ToJSValue(cx, mValues[idx].first, &key);
|
||||
Unused << ToJSValue(cx, mValues[idx].second, &value);
|
||||
iterator_utils::ResolvePromiseWithKeyAndValue(cx, data->mPromise, key,
|
||||
value);
|
||||
iterator_utils::ResolvePromiseWithKeyAndValue(
|
||||
aPromise, mValues[idx].first, mValues[idx].second);
|
||||
break;
|
||||
}
|
||||
|
||||
data->mIndex++;
|
||||
data->mPromise = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,24 +41,18 @@ class TestInterfaceAsyncIterableDoubleUnion final : public nsISupports,
|
||||
using Iterator = AsyncIterableIterator<TestInterfaceAsyncIterableDoubleUnion>;
|
||||
void InitAsyncIterator(Iterator* aIterator, ErrorResult& aError);
|
||||
void DestroyAsyncIterator(Iterator* aIterator);
|
||||
already_AddRefed<Promise> GetNextPromise(JSContext* aCx, Iterator* aIterator,
|
||||
already_AddRefed<Promise> GetNextPromise(Iterator* aIterator,
|
||||
ErrorResult& aRv);
|
||||
|
||||
private:
|
||||
struct IteratorData {
|
||||
explicit IteratorData(int32_t aIndex) : mIndex(aIndex) {}
|
||||
~IteratorData() {
|
||||
if (mPromise) {
|
||||
mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
|
||||
mPromise = nullptr;
|
||||
}
|
||||
}
|
||||
RefPtr<Promise> mPromise;
|
||||
|
||||
uint32_t mIndex;
|
||||
};
|
||||
|
||||
virtual ~TestInterfaceAsyncIterableDoubleUnion() = default;
|
||||
void ResolvePromise(Iterator* aIterator);
|
||||
void ResolvePromise(Iterator* aIterator, Promise* aPromise);
|
||||
|
||||
nsCOMPtr<nsPIDOMWindowInner> mParent;
|
||||
nsTArray<std::pair<nsString, OwningStringOrLong>> mValues;
|
||||
|
@ -9,6 +9,8 @@
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "mozilla/dom/BindingUtils.h"
|
||||
#include "mozilla/dom/IterableIterator.h"
|
||||
#include "mozilla/dom/Promise-inl.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
@ -59,7 +61,7 @@ void TestInterfaceAsyncIterableSingle::InitAsyncIterator(Iterator* aIterator,
|
||||
return;
|
||||
}
|
||||
|
||||
UniquePtr<IteratorData> data(new IteratorData(0, 1));
|
||||
UniquePtr<IteratorData> data(new IteratorData());
|
||||
aIterator->SetData((void*)data.release());
|
||||
}
|
||||
|
||||
@ -70,51 +72,51 @@ void TestInterfaceAsyncIterableSingle::DestroyAsyncIterator(
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> TestInterfaceAsyncIterableSingle::GetNextPromise(
|
||||
JSContext* aCx, Iterator* aIterator, ErrorResult& aRv) {
|
||||
return GetNextPromise(aCx, aIterator,
|
||||
reinterpret_cast<IteratorData*>(aIterator->GetData()),
|
||||
aRv);
|
||||
Iterator* aIterator, ErrorResult& aRv) {
|
||||
return GetNextPromise(
|
||||
aIterator, reinterpret_cast<IteratorData*>(aIterator->GetData()), aRv);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> TestInterfaceAsyncIterableSingle::GetNextPromise(
|
||||
JSContext* aCx, IterableIteratorBase* aIterator, IteratorData* aData,
|
||||
ErrorResult& aRv) {
|
||||
IterableIteratorBase* aIterator, IteratorData* aData, ErrorResult& aRv) {
|
||||
RefPtr<Promise> promise = Promise::Create(mParent->AsGlobal(), aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
aData->mPromise = promise;
|
||||
|
||||
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
||||
"TestInterfaceAsyncIterableSingle::GetNextPromise",
|
||||
[iterator = RefPtr{aIterator}, aData, self = RefPtr{this}] {
|
||||
self->ResolvePromise(iterator, aData);
|
||||
}));
|
||||
nsCOMPtr<nsIRunnable> callResolvePromise =
|
||||
NewRunnableMethod<RefPtr<IterableIteratorBase>, IteratorData*,
|
||||
RefPtr<Promise>>(
|
||||
"TestInterfaceAsyncIterableSingle::GetNextPromise", this,
|
||||
&TestInterfaceAsyncIterableSingle::ResolvePromise, aIterator, aData,
|
||||
promise);
|
||||
if (aData->mBlockingPromisesIndex < aData->mBlockingPromises.Length()) {
|
||||
aData->mBlockingPromises[aData->mBlockingPromisesIndex]
|
||||
->AddCallbacksWithCycleCollectedArgs(
|
||||
[](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
|
||||
nsIRunnable* aCallResolvePromise) {
|
||||
NS_DispatchToMainThread(aCallResolvePromise);
|
||||
},
|
||||
[](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
|
||||
nsIRunnable* aCallResolvePromise) {},
|
||||
std::move(callResolvePromise));
|
||||
++aData->mBlockingPromisesIndex;
|
||||
} else {
|
||||
NS_DispatchToMainThread(callResolvePromise);
|
||||
}
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
void TestInterfaceAsyncIterableSingle::ResolvePromise(
|
||||
IterableIteratorBase* aIterator, IteratorData* aData) {
|
||||
AutoJSAPI jsapi;
|
||||
if (NS_WARN_IF(!jsapi.Init(mParent))) {
|
||||
aData->mPromise->MaybeRejectWithInvalidStateError(
|
||||
"Couldn't get the global.");
|
||||
return;
|
||||
}
|
||||
JSContext* cx = jsapi.cx();
|
||||
|
||||
IterableIteratorBase* aIterator, IteratorData* aData, Promise* aPromise) {
|
||||
if (aData->mIndex >= 10) {
|
||||
iterator_utils::ResolvePromiseForFinished(cx, aData->mPromise);
|
||||
iterator_utils::ResolvePromiseForFinished(aPromise);
|
||||
} else {
|
||||
JS::Rooted<JS::Value> value(cx);
|
||||
Unused << ToJSValue(
|
||||
cx, (int32_t)(aData->mIndex * 9 % 7 * aData->mMultiplier), &value);
|
||||
iterator_utils::ResolvePromiseWithKeyOrValue(cx, aData->mPromise, value);
|
||||
aPromise->MaybeResolve(int32_t(aData->mIndex * 9 % 7 * aData->mMultiplier));
|
||||
|
||||
aData->mIndex++;
|
||||
}
|
||||
aData->mPromise = nullptr;
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
@ -43,33 +43,26 @@ class TestInterfaceAsyncIterableSingle : public nsISupports,
|
||||
using Iterator = AsyncIterableIterator<TestInterfaceAsyncIterableSingle>;
|
||||
void InitAsyncIterator(Iterator* aIterator, ErrorResult& aError);
|
||||
void DestroyAsyncIterator(Iterator* aIterator);
|
||||
already_AddRefed<Promise> GetNextPromise(JSContext* aCx, Iterator* aIterator,
|
||||
already_AddRefed<Promise> GetNextPromise(Iterator* aIterator,
|
||||
ErrorResult& aRv);
|
||||
|
||||
protected:
|
||||
struct IteratorData {
|
||||
IteratorData(int32_t aIndex, uint32_t aMultiplier)
|
||||
: mIndex(aIndex), mMultiplier(aMultiplier) {}
|
||||
~IteratorData() {
|
||||
if (mPromise) {
|
||||
mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
|
||||
mPromise = nullptr;
|
||||
}
|
||||
}
|
||||
RefPtr<Promise> mPromise;
|
||||
uint32_t mIndex;
|
||||
uint32_t mMultiplier;
|
||||
uint32_t mIndex = 0;
|
||||
uint32_t mMultiplier = 1;
|
||||
Sequence<OwningNonNull<Promise>> mBlockingPromises;
|
||||
size_t mBlockingPromisesIndex = 0;
|
||||
};
|
||||
|
||||
already_AddRefed<Promise> GetNextPromise(JSContext* aCx,
|
||||
IterableIteratorBase* aIterator,
|
||||
already_AddRefed<Promise> GetNextPromise(IterableIteratorBase* aIterator,
|
||||
IteratorData* aData,
|
||||
ErrorResult& aRv);
|
||||
|
||||
virtual ~TestInterfaceAsyncIterableSingle() = default;
|
||||
|
||||
private:
|
||||
void ResolvePromise(IterableIteratorBase* aIterator, IteratorData* aData);
|
||||
void ResolvePromise(IterableIteratorBase* aIterator, IteratorData* aData,
|
||||
Promise* aPromise);
|
||||
|
||||
nsCOMPtr<nsPIDOMWindowInner> mParent;
|
||||
bool mFailToInit;
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "mozilla/dom/BindingUtils.h"
|
||||
#include "mozilla/dom/IterableIterator.h"
|
||||
#include "mozilla/dom/Promise-inl.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
@ -37,7 +38,8 @@ JSObject* TestInterfaceAsyncIterableSingleWithArgs::WrapObject(
|
||||
void TestInterfaceAsyncIterableSingleWithArgs::InitAsyncIterator(
|
||||
Iterator* aIterator, const TestInterfaceAsyncIteratorOptions& aOptions,
|
||||
ErrorResult& aError) {
|
||||
UniquePtr<IteratorData> data(new IteratorData(0, aOptions.mMultiplier));
|
||||
UniquePtr<IteratorData> data(
|
||||
new IteratorData{0, aOptions.mMultiplier, aOptions.mBlockingPromises});
|
||||
aIterator->SetData((void*)data.release());
|
||||
}
|
||||
|
||||
@ -48,12 +50,10 @@ void TestInterfaceAsyncIterableSingleWithArgs::DestroyAsyncIterator(
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
TestInterfaceAsyncIterableSingleWithArgs::GetNextPromise(JSContext* aCx,
|
||||
Iterator* aIterator,
|
||||
TestInterfaceAsyncIterableSingleWithArgs::GetNextPromise(Iterator* aIterator,
|
||||
ErrorResult& aRv) {
|
||||
return TestInterfaceAsyncIterableSingle::GetNextPromise(
|
||||
aCx, aIterator, reinterpret_cast<IteratorData*>(aIterator->GetData()),
|
||||
aRv);
|
||||
aIterator, reinterpret_cast<IteratorData*>(aIterator->GetData()), aRv);
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom
|
||||
|
@ -31,7 +31,7 @@ class TestInterfaceAsyncIterableSingleWithArgs final
|
||||
ErrorResult& aError);
|
||||
void DestroyAsyncIterator(Iterator* aIterator);
|
||||
|
||||
already_AddRefed<Promise> GetNextPromise(JSContext* aCx, Iterator* aIterator,
|
||||
already_AddRefed<Promise> GetNextPromise(Iterator* aIterator,
|
||||
ErrorResult& aRv);
|
||||
};
|
||||
|
||||
|
@ -14,12 +14,9 @@ add_task(async function init() {
|
||||
await SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]});
|
||||
});
|
||||
|
||||
async function check_single_result(itr, multiplier = 1) {
|
||||
let values = [];
|
||||
for await (let v of itr) {
|
||||
values.push(v);
|
||||
}
|
||||
is(values.length, 10, `AsyncIterableSingle: should returns 10 elements`);
|
||||
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;
|
||||
is(values[i], expected,
|
||||
@ -27,6 +24,14 @@ async function check_single_result(itr, multiplier = 1) {
|
||||
}
|
||||
}
|
||||
|
||||
async function check_single_result(itr, multiplier = 1) {
|
||||
let values = [];
|
||||
for await (let v of itr) {
|
||||
values.push(v);
|
||||
}
|
||||
check_single_result_values(values, multiplier);
|
||||
}
|
||||
|
||||
async function test_data_single() {
|
||||
info(`AsyncIterableSingle: Testing simple iterable creation and functionality`);
|
||||
|
||||
@ -58,6 +63,50 @@ async function test_data_single() {
|
||||
|
||||
await check_single_result(itr, 1);
|
||||
await check_single_result(itr.values({ multiplier: 2 }), 2);
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
itr = new TestInterfaceAsyncIterableSingle();
|
||||
let itrValues = itr.values();
|
||||
let values = [];
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
values.push(itrValues.next());
|
||||
}
|
||||
check_single_result_values(await Promise.all(values).then(v => v.map(w => w.value)));
|
||||
|
||||
// Test that there is only one ongoing promise at a time.
|
||||
// Async iterables return a promise that is then resolved with the iterator
|
||||
// value. We create an array of unresolved promises here, one promise for
|
||||
// every result that we expect from the iterator. We pass that array of
|
||||
// promises to the .value() method of the
|
||||
// TestInterfaceAsyncIterableSingleWithArgs, and it will chain the resolving
|
||||
// of each resulting iterator value on the corresponding promise from this
|
||||
// array. We then resolve the promises in the array one by one in reverse
|
||||
// order. This tries to make sure that the iterator always resolves the
|
||||
// promises in the order of iteration.
|
||||
let unblockers = [];
|
||||
let blockingPromises = [];
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
let unblocker;
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
unblocker = resolve;
|
||||
});
|
||||
unblockers.push(unblocker);
|
||||
blockingPromises.push(promise);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
itr = new TestInterfaceAsyncIterableSingleWithArgs();
|
||||
itrValues = itr.values({ blockingPromises });
|
||||
values = [];
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
values.push(itrValues.next());
|
||||
}
|
||||
unblockers.reverse();
|
||||
for (let unblocker of unblockers) {
|
||||
unblocker();
|
||||
}
|
||||
|
||||
check_single_result_values(await Promise.all(values).then(v => v.map(w => w.value)));
|
||||
}
|
||||
|
||||
async function test_data_double() {
|
||||
|
@ -68,8 +68,7 @@ void FileSystemDirectoryHandle::DestroyAsyncIterator(
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> FileSystemDirectoryHandle::GetNextPromise(
|
||||
JSContext* /* aCx */, FileSystemDirectoryHandle::iterator_t* aIterator,
|
||||
ErrorResult& aError) {
|
||||
FileSystemDirectoryHandle::iterator_t* aIterator, ErrorResult& aError) {
|
||||
return static_cast<FileSystemDirectoryIterator::Impl*>(aIterator->GetData())
|
||||
->Next(mGlobal, mManager, aError);
|
||||
}
|
||||
|
@ -50,8 +50,7 @@ class FileSystemDirectoryHandle final : public FileSystemHandle {
|
||||
|
||||
void DestroyAsyncIterator(iterator_t* aIterator);
|
||||
|
||||
[[nodiscard]] already_AddRefed<Promise> GetNextPromise(JSContext* aCx,
|
||||
iterator_t* aIterator,
|
||||
[[nodiscard]] already_AddRefed<Promise> GetNextPromise(iterator_t* aIterator,
|
||||
ErrorResult& aError);
|
||||
|
||||
already_AddRefed<Promise> GetFileHandle(
|
||||
|
@ -22,51 +22,23 @@ namespace mozilla::dom::fs {
|
||||
|
||||
namespace {
|
||||
|
||||
inline JSContext* GetContext(const RefPtr<Promise>& aPromise) {
|
||||
AutoJSAPI jsapi;
|
||||
if (NS_WARN_IF(!jsapi.Init(aPromise->GetGlobalObject()))) {
|
||||
return nullptr;
|
||||
}
|
||||
return jsapi.cx();
|
||||
}
|
||||
|
||||
template <IterableIteratorBase::IteratorType Type>
|
||||
struct ValueResolver;
|
||||
|
||||
template <>
|
||||
struct ValueResolver<IterableIteratorBase::Keys> {
|
||||
nsresult operator()(nsIGlobalObject* aGlobal,
|
||||
RefPtr<FileSystemManager>& aManager,
|
||||
const FileSystemEntryMetadata& aValue,
|
||||
const RefPtr<Promise>& aPromise, ErrorResult& aError) {
|
||||
JSContext* cx = GetContext(aPromise);
|
||||
if (!cx) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> key(cx);
|
||||
|
||||
if (!ToJSValue(cx, aValue.entryName(), &key)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
iterator_utils::ResolvePromiseWithKeyOrValue(cx, aPromise.get(), key);
|
||||
|
||||
return NS_OK;
|
||||
void operator()(nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
|
||||
const FileSystemEntryMetadata& aValue,
|
||||
const RefPtr<Promise>& aPromise) {
|
||||
aPromise->MaybeResolve(aValue.entryName());
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ValueResolver<IterableIteratorBase::Values> {
|
||||
nsresult operator()(nsIGlobalObject* aGlobal,
|
||||
RefPtr<FileSystemManager>& aManager,
|
||||
const FileSystemEntryMetadata& aValue,
|
||||
const RefPtr<Promise>& aPromise, ErrorResult& aError) {
|
||||
JSContext* cx = GetContext(aPromise);
|
||||
if (!cx) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
void operator()(nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
|
||||
const FileSystemEntryMetadata& aValue,
|
||||
const RefPtr<Promise>& aPromise) {
|
||||
RefPtr<FileSystemHandle> handle;
|
||||
|
||||
if (aValue.directory()) {
|
||||
@ -75,33 +47,15 @@ struct ValueResolver<IterableIteratorBase::Values> {
|
||||
handle = new FileSystemFileHandle(aGlobal, aManager, aValue);
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> value(cx);
|
||||
if (!ToJSValue(cx, handle, &value)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
iterator_utils::ResolvePromiseWithKeyOrValue(cx, aPromise.get(), value);
|
||||
|
||||
return NS_OK;
|
||||
aPromise->MaybeResolve(std::move(handle));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct ValueResolver<IterableIteratorBase::Entries> {
|
||||
nsresult operator()(nsIGlobalObject* aGlobal,
|
||||
RefPtr<FileSystemManager>& aManager,
|
||||
const FileSystemEntryMetadata& aValue,
|
||||
const RefPtr<Promise>& aPromise, ErrorResult& aError) {
|
||||
JSContext* cx = GetContext(aPromise);
|
||||
if (!cx) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> key(cx);
|
||||
if (!ToJSValue(cx, aValue.entryName(), &key)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
void operator()(nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
|
||||
const FileSystemEntryMetadata& aValue,
|
||||
const RefPtr<Promise>& aPromise) {
|
||||
RefPtr<FileSystemHandle> handle;
|
||||
|
||||
if (aValue.directory()) {
|
||||
@ -110,15 +64,8 @@ struct ValueResolver<IterableIteratorBase::Entries> {
|
||||
handle = new FileSystemFileHandle(aGlobal, aManager, aValue);
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> value(cx);
|
||||
if (!ToJSValue(cx, handle, &value)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
iterator_utils::ResolvePromiseWithKeyAndValue(cx, aPromise.get(), key,
|
||||
value);
|
||||
|
||||
return NS_OK;
|
||||
iterator_utils::ResolvePromiseWithKeyAndValue(aPromise, aValue.entryName(),
|
||||
handle);
|
||||
}
|
||||
};
|
||||
|
||||
@ -141,27 +88,16 @@ class DoubleBufferQueueImpl
|
||||
// XXX This doesn't have to be public
|
||||
void ResolveValue(nsIGlobalObject* aGlobal,
|
||||
RefPtr<FileSystemManager>& aManager,
|
||||
const Maybe<DataType>& aValue, RefPtr<Promise> aPromise,
|
||||
ErrorResult& aError) {
|
||||
const Maybe<DataType>& aValue, RefPtr<Promise> aPromise) {
|
||||
MOZ_ASSERT(aPromise);
|
||||
MOZ_ASSERT(aPromise.get());
|
||||
|
||||
AutoJSAPI jsapi;
|
||||
if (NS_WARN_IF(!jsapi.Init(aPromise->GetGlobalObject()))) {
|
||||
aPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
|
||||
aError = NS_ERROR_DOM_UNKNOWN_ERR;
|
||||
return;
|
||||
}
|
||||
|
||||
JSContext* aCx = jsapi.cx();
|
||||
MOZ_ASSERT(aCx);
|
||||
|
||||
if (!aValue) {
|
||||
iterator_utils::ResolvePromiseForFinished(aCx, aPromise.get());
|
||||
iterator_utils::ResolvePromiseForFinished(aPromise);
|
||||
return;
|
||||
}
|
||||
|
||||
ValueResolver{}(aGlobal, aManager, *aValue, aPromise, aError);
|
||||
ValueResolver{}(aGlobal, aManager, *aValue, aPromise);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> Next(nsIGlobalObject* aGlobal,
|
||||
@ -172,7 +108,7 @@ class DoubleBufferQueueImpl
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
next(aGlobal, aManager, promise, aError);
|
||||
next(aGlobal, aManager, promise);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
@ -181,7 +117,7 @@ class DoubleBufferQueueImpl
|
||||
|
||||
protected:
|
||||
void next(nsIGlobalObject* aGlobal, RefPtr<FileSystemManager>& aManager,
|
||||
RefPtr<Promise> aResult, ErrorResult& aError) {
|
||||
RefPtr<Promise> aResult) {
|
||||
MOZ_ASSERT(aResult);
|
||||
|
||||
Maybe<DataType> rawValue;
|
||||
@ -193,7 +129,7 @@ class DoubleBufferQueueImpl
|
||||
ErrorResult rv;
|
||||
RefPtr<Promise> promise = Promise::Create(aGlobal, rv);
|
||||
if (rv.Failed()) {
|
||||
aResult->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
|
||||
aResult->MaybeReject(std::move(rv));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -223,7 +159,7 @@ class DoubleBufferQueueImpl
|
||||
}
|
||||
|
||||
IgnoredErrorResult rv;
|
||||
ResolveValue(global, manager, value, aResult, rv);
|
||||
ResolveValue(global, manager, value, aResult);
|
||||
},
|
||||
[](nsresult aRv) {});
|
||||
promise->AppendNativeHandler(listener);
|
||||
@ -237,7 +173,7 @@ class DoubleBufferQueueImpl
|
||||
|
||||
nextInternal(rawValue);
|
||||
|
||||
ResolveValue(aGlobal, aManager, rawValue, aResult, aError);
|
||||
ResolveValue(aGlobal, aManager, rawValue, aResult);
|
||||
}
|
||||
|
||||
bool nextInternal(Maybe<DataType>& aNext) {
|
||||
|
@ -30,12 +30,6 @@ class TestFileSystemDirectoryHandle : public ::testing::Test {
|
||||
mManager = MakeAndAddRef<FileSystemManager>(mGlobal, nullptr);
|
||||
}
|
||||
|
||||
FileSystemDirectoryHandle::iterator_t::WrapFunc GetWrapFunc() const {
|
||||
return [](JSContext*, AsyncIterableIterator<FileSystemDirectoryHandle>*,
|
||||
JS::Handle<JSObject*>,
|
||||
JS::MutableHandle<JSObject*>) -> bool { return true; };
|
||||
}
|
||||
|
||||
nsIGlobalObject* mGlobal = GetGlobal();
|
||||
const IterableIteratorBase::IteratorType mIteratorType =
|
||||
IterableIteratorBase::IteratorType::Keys;
|
||||
@ -60,8 +54,7 @@ TEST_F(TestFileSystemDirectoryHandle, initAndDestroyIterator) {
|
||||
ASSERT_TRUE(dirHandle);
|
||||
|
||||
RefPtr<FileSystemDirectoryHandle::iterator_t> iterator =
|
||||
new FileSystemDirectoryHandle::iterator_t(dirHandle.get(), mIteratorType,
|
||||
GetWrapFunc());
|
||||
new FileSystemDirectoryHandle::iterator_t(dirHandle.get(), mIteratorType);
|
||||
IgnoredErrorResult rv;
|
||||
dirHandle->InitAsyncIterator(iterator, rv);
|
||||
ASSERT_TRUE(iterator->GetData());
|
||||
@ -97,13 +90,12 @@ TEST_F(TestFileSystemDirectoryHandle, isNextPromiseReturned) {
|
||||
.WillOnce(::testing::Return(Promise::Create(mGlobal, error)));
|
||||
|
||||
RefPtr<FileSystemDirectoryHandle::iterator_t> iterator =
|
||||
MakeAndAddRef<FileSystemDirectoryHandle::iterator_t>(
|
||||
dirHandle.get(), mIteratorType, GetWrapFunc());
|
||||
MakeAndAddRef<FileSystemDirectoryHandle::iterator_t>(dirHandle.get(),
|
||||
mIteratorType);
|
||||
iterator->SetData(static_cast<void*>(mockIter));
|
||||
|
||||
IgnoredErrorResult rv;
|
||||
RefPtr<Promise> promise =
|
||||
dirHandle->GetNextPromise(nullptr, iterator.get(), rv);
|
||||
RefPtr<Promise> promise = dirHandle->GetNextPromise(iterator.get(), rv);
|
||||
ASSERT_TRUE(promise);
|
||||
|
||||
dirHandle->DestroyAsyncIterator(iterator.get());
|
||||
|
@ -111,6 +111,7 @@ interface TestInterfaceAsyncIterableSingle {
|
||||
|
||||
dictionary TestInterfaceAsyncIteratorOptions {
|
||||
unsigned long multiplier = 1;
|
||||
sequence<Promise<any>> blockingPromises = [];
|
||||
};
|
||||
|
||||
[Pref="dom.expose_test_interfaces",
|
||||
|
Loading…
Reference in New Issue
Block a user