--- a/dom/media/gtest/TestMozPromise.cpp
+++ b/dom/media/gtest/TestMozPromise.cpp
@@ -345,66 +345,9 @@ TEST(MozPromise, MoveOnlyType)
EXPECT_TRUE(aVal.IsResolve());
EXPECT_EQ(nullptr, aVal.ResolveValue().get());
EXPECT_EQ(87, *val.ResolveValue());
queue->BeginShutdown();
});
}
-TEST(MozPromise, HeterogeneousChaining)
-{
- using Promise1 = MozPromise<UniquePtr<char>, bool, true>;
- using Promise2 = MozPromise<UniquePtr<int>, bool, true>;
- using RRValue1 = Promise1::ResolveOrRejectValue;
- using RRValue2 = Promise2::ResolveOrRejectValue;
-
- MozPromiseRequestHolder<Promise2> holder;
-
- AutoTaskQueue atq;
- RefPtr<TaskQueue> queue = atq.Queue();
-
- RunOnTaskQueue(queue, [queue, &holder]() {
- Promise1::CreateAndResolve(MakeUnique<char>(0), __func__)
- ->Then(queue,
- __func__,
- [&holder]() {
- holder.Disconnect();
- return Promise2::CreateAndResolve(MakeUnique<int>(0), __func__);
- })
- ->Then(queue,
- __func__,
- []() {
- // Shouldn't be called for we've disconnected the request.
- EXPECT_FALSE(true);
- })
- ->Track(holder);
- });
-
- Promise1::CreateAndResolve(MakeUnique<char>(87), __func__)
- ->Then(queue,
- __func__,
- [](UniquePtr<char> aVal) {
- EXPECT_EQ(87, *aVal);
- return Promise2::CreateAndResolve(MakeUnique<int>(94), __func__);
- },
- []() {
- return Promise2::CreateAndResolve(MakeUnique<int>(95), __func__);
- })
- ->Then(queue,
- __func__,
- [](UniquePtr<int> aVal) { EXPECT_EQ(94, *aVal); },
- []() { EXPECT_FALSE(true); });
-
- Promise1::CreateAndResolve(MakeUnique<char>(87), __func__)
- ->Then(queue,
- __func__,
- [](RRValue1&& aVal) {
- EXPECT_EQ(87, *aVal.ResolveValue());
- return Promise2::CreateAndResolve(MakeUnique<int>(94), __func__);
- })
- ->Then(queue, __func__, [queue](RRValue2&& aVal) {
- EXPECT_EQ(94, *aVal.ResolveValue());
- queue->BeginShutdown();
- });
-}
-
#undef DO_FAIL
--- a/xpcom/threads/MozPromise.h
+++ b/xpcom/threads/MozPromise.h
@@ -76,30 +76,16 @@ struct MethodTrait : MethodTraitsHelper<
template<typename MethodType>
using TakesArgument =
IntegralConstant<bool, detail::MethodTrait<MethodType>::ArgSize != 0>;
template<typename MethodType, typename TargetType>
using ReturnTypeIs =
IsConvertible<typename detail::MethodTrait<MethodType>::ReturnType, TargetType>;
-template<typename ResolveValueT, typename RejectValueT, bool IsExclusive>
-class MozPromise;
-
-template<typename Return>
-struct IsMozPromise : FalseType
-{
-};
-
-template<typename ResolveValueT, typename RejectValueT, bool IsExclusive>
-struct IsMozPromise<MozPromise<ResolveValueT, RejectValueT, IsExclusive>>
- : TrueType
-{
-};
-
/*
* A promise manages an asynchronous request that may or may not be able to be
* fulfilled immediately. When an API returns a promise, the consumer may attach
* callbacks to be invoked (asynchronously, on a specified thread) when the
* request is either completed (resolved) or cannot be completed (rejected).
* Whereas JS promise callbacks are dispatched from Microtask checkpoints,
* MozPromises resolution/rejection make a normal round-trip through the event
* loop, which simplifies their ordering semantics relative to other native code.
@@ -512,94 +498,72 @@ protected:
uint32_t mMagic2 = sMagic;
#endif
};
/*
* We create two overloads for invoking Resolve/Reject Methods so as to
* make the resolve/reject value argument "optional".
*/
+
template<typename ThisType, typename MethodType, typename ValueType>
- static typename EnableIf<
- TakesArgument<MethodType>::value,
- typename detail::MethodTrait<MethodType>::ReturnType>::Type
- InvokeMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue)
+ static typename EnableIf<ReturnTypeIs<MethodType, RefPtr<MozPromise>>::value &&
+ TakesArgument<MethodType>::value,
+ already_AddRefed<MozPromise>>::Type
+ InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue)
{
- return (aThisVal->*aMethod)(Forward<ValueType>(aValue));
+ return ((*aThisVal).*aMethod)(Forward<ValueType>(aValue)).forget();
}
template<typename ThisType, typename MethodType, typename ValueType>
- static typename EnableIf<
- !TakesArgument<MethodType>::value,
- typename detail::MethodTrait<MethodType>::ReturnType>::Type
- InvokeMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue)
+ static typename EnableIf<ReturnTypeIs<MethodType, void>::value &&
+ TakesArgument<MethodType>::value,
+ already_AddRefed<MozPromise>>::Type
+ InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue)
{
- return (aThisVal->*aMethod)();
+ ((*aThisVal).*aMethod)(Forward<ValueType>(aValue));
+ return nullptr;
}
- // Called when promise chaining is supported.
- template<bool SupportChaining,
- typename ThisType,
- typename MethodType,
- typename ValueType,
- typename CompletionPromiseType>
- static typename EnableIf<SupportChaining, void>::Type InvokeCallbackMethod(
- ThisType* aThisVal,
- MethodType aMethod,
- ValueType&& aValue,
- CompletionPromiseType&& aCompletionPromise)
+ template<typename ThisType, typename MethodType, typename ValueType>
+ static typename EnableIf<ReturnTypeIs<MethodType, RefPtr<MozPromise>>::value &&
+ !TakesArgument<MethodType>::value,
+ already_AddRefed<MozPromise>>::Type
+ InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue)
{
- auto p = InvokeMethod(aThisVal, aMethod, Forward<ValueType>(aValue));
- if (aCompletionPromise) {
- p->ChainTo(aCompletionPromise.forget(), "<chained completion promise>");
- }
+ return ((*aThisVal).*aMethod)().forget();
}
- // Called when promise chaining is not supported.
- template<bool SupportChaining,
- typename ThisType,
- typename MethodType,
- typename ValueType,
- typename CompletionPromiseType>
- static typename EnableIf<!SupportChaining, void>::Type InvokeCallbackMethod(
- ThisType* aThisVal,
- MethodType aMethod,
- ValueType&& aValue,
- CompletionPromiseType&& aCompletionPromise)
+ template<typename ThisType, typename MethodType, typename ValueType>
+ static typename EnableIf<ReturnTypeIs<MethodType, void>::value &&
+ !TakesArgument<MethodType>::value,
+ already_AddRefed<MozPromise>>::Type
+ InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue)
{
- MOZ_DIAGNOSTIC_ASSERT(
- !aCompletionPromise,
- "Can't do promise chaining for a non-promise-returning method.");
- InvokeMethod(aThisVal, aMethod, Forward<ValueType>(aValue));
+ ((*aThisVal).*aMethod)();
+ return nullptr;
}
template<typename>
class ThenCommand;
template<typename...>
class ThenValue;
template<typename ThisType,
typename ResolveMethodType,
typename RejectMethodType>
class ThenValue<ThisType*, ResolveMethodType, RejectMethodType>
: public ThenValueBase
{
friend class ThenCommand<ThenValue>;
-
- using R1 = typename RemoveSmartPointer<
- typename detail::MethodTrait<ResolveMethodType>::ReturnType>::Type;
- using R2 = typename RemoveSmartPointer<
- typename detail::MethodTrait<RejectMethodType>::ReturnType>::Type;
- using SupportChaining =
- IntegralConstant<bool, IsMozPromise<R1>::value && IsSame<R1, R2>::value>;
-
- // Fall back to MozPromise when promise chaining is not supported to make code compile.
- using PromiseType =
- typename Conditional<SupportChaining::value, R1, MozPromise>::Type;
+ using SupportChaining = IntegralConstant<
+ bool,
+ ReturnTypeIs<ResolveMethodType, RefPtr<MozPromise>>::value &&
+ ReturnTypeIs<RejectMethodType, RefPtr<MozPromise>>::value>;
public:
ThenValue(AbstractThread* aResponseTarget,
ThisType* aThisVal,
ResolveMethodType aResolveMethod,
RejectMethodType aRejectMethod,
const char* aCallSite)
: ThenValueBase(aResponseTarget, aCallSite)
@@ -622,56 +586,54 @@ protected:
protected:
MozPromiseBase* CompletionPromise() const override
{
return mCompletionPromise;
}
void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
{
+ RefPtr<MozPromise> result;
if (aValue.IsResolve()) {
- InvokeCallbackMethod<SupportChaining::value>(
- mThisVal.get(),
- mResolveMethod,
- MaybeMove(aValue.ResolveValue()),
- Move(mCompletionPromise));
+ result = InvokeCallbackMethod(
+ mThisVal.get(), mResolveMethod, MaybeMove(aValue.ResolveValue()));
} else {
- InvokeCallbackMethod<SupportChaining::value>(
- mThisVal.get(),
- mRejectMethod,
- MaybeMove(aValue.RejectValue()),
- Move(mCompletionPromise));
+ result = InvokeCallbackMethod(
+ mThisVal.get(), mRejectMethod, MaybeMove(aValue.RejectValue()));
}
// Null out mThisVal after invoking the callback so that any references are
// released predictably on the dispatch thread. Otherwise, it would be
// released on whatever thread last drops its reference to the ThenValue,
// which may or may not be ok.
mThisVal = nullptr;
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mCompletionPromise || result,
+ "Can't do promise chaining for a non-promise-returning method.");
+
+ if (mCompletionPromise && result) {
+ result->ChainTo(mCompletionPromise.forget(),
+ "<chained completion promise>");
+ }
}
private:
RefPtr<ThisType> mThisVal; // Only accessed and refcounted on dispatch thread.
ResolveMethodType mResolveMethod;
RejectMethodType mRejectMethod;
- RefPtr<typename PromiseType::Private> mCompletionPromise;
+ RefPtr<Private> mCompletionPromise;
};
template<typename ThisType, typename ResolveRejectMethodType>
class ThenValue<ThisType*, ResolveRejectMethodType> : public ThenValueBase
{
friend class ThenCommand<ThenValue>;
-
- using R1 = typename RemoveSmartPointer<
- typename detail::MethodTrait<ResolveRejectMethodType>::ReturnType>::Type;
- using SupportChaining = IntegralConstant<bool, IsMozPromise<R1>::value>;
-
- // Fall back to MozPromise when promise chaining is not supported to make code compile.
- using PromiseType =
- typename Conditional<SupportChaining::value, R1, MozPromise>::Type;
+ using SupportChaining =
+ ReturnTypeIs<ResolveRejectMethodType, RefPtr<MozPromise>>;
public:
ThenValue(AbstractThread* aResponseTarget,
ThisType* aThisVal,
ResolveRejectMethodType aResolveRejectMethod,
const char* aCallSite)
: ThenValueBase(aResponseTarget, aCallSite)
, mThisVal(aThisVal)
@@ -691,50 +653,50 @@ protected:
protected:
MozPromiseBase* CompletionPromise() const override
{
return mCompletionPromise;
}
void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
{
- InvokeCallbackMethod<SupportChaining::value>(mThisVal.get(),
- mResolveRejectMethod,
- MaybeMove(aValue),
- Move(mCompletionPromise));
+ RefPtr<MozPromise> result = InvokeCallbackMethod(
+ mThisVal.get(), mResolveRejectMethod, MaybeMove(aValue));
// Null out mThisVal after invoking the callback so that any references are
// released predictably on the dispatch thread. Otherwise, it would be
// released on whatever thread last drops its reference to the ThenValue,
// which may or may not be ok.
mThisVal = nullptr;
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mCompletionPromise || result,
+ "Can't do promise chaining for a non-promise-returning method.");
+
+ if (mCompletionPromise && result) {
+ result->ChainTo(mCompletionPromise.forget(),
+ "<chained completion promise>");
+ }
}
private:
RefPtr<ThisType> mThisVal; // Only accessed and refcounted on dispatch thread.
ResolveRejectMethodType mResolveRejectMethod;
- RefPtr<typename PromiseType::Private> mCompletionPromise;
+ RefPtr<Private> mCompletionPromise;
};
// NB: We could use std::function here instead of a template if it were supported. :-(
template<typename ResolveFunction, typename RejectFunction>
class ThenValue<ResolveFunction, RejectFunction> : public ThenValueBase
{
friend class ThenCommand<ThenValue>;
-
- using R1 = typename RemoveSmartPointer<
- typename detail::MethodTrait<ResolveFunction>::ReturnType>::Type;
- using R2 = typename RemoveSmartPointer<
- typename detail::MethodTrait<RejectFunction>::ReturnType>::Type;
- using SupportChaining =
- IntegralConstant<bool, IsMozPromise<R1>::value && IsSame<R1, R2>::value>;
-
- // Fall back to MozPromise when promise chaining is not supported to make code compile.
- using PromiseType =
- typename Conditional<SupportChaining::value, R1, MozPromise>::Type;
+ using SupportChaining = IntegralConstant<
+ bool,
+ ReturnTypeIs<ResolveFunction, RefPtr<MozPromise>>::value &&
+ ReturnTypeIs<RejectFunction, RefPtr<MozPromise>>::value>;
public:
ThenValue(AbstractThread* aResponseTarget,
ResolveFunction&& aResolveFunction,
RejectFunction&& aRejectFunction,
const char* aCallSite)
: ThenValueBase(aResponseTarget, aCallSite)
{
@@ -762,56 +724,56 @@ protected:
void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
{
// Note: The usage of InvokeCallbackMethod here requires that
// ResolveFunction/RejectFunction are capture-lambdas (i.e. anonymous
// classes with ::operator()), since it allows us to share code more easily.
// We could fix this if need be, though it's quite easy to work around by
// just capturing something.
+ RefPtr<MozPromise> result;
if (aValue.IsResolve()) {
- InvokeCallbackMethod<SupportChaining::value>(
- mResolveFunction.ptr(),
- &ResolveFunction::operator(),
- MaybeMove(aValue.ResolveValue()),
- Move(mCompletionPromise));
+ result = InvokeCallbackMethod(mResolveFunction.ptr(),
+ &ResolveFunction::operator(),
+ MaybeMove(aValue.ResolveValue()));
} else {
- InvokeCallbackMethod<SupportChaining::value>(
- mRejectFunction.ptr(),
- &RejectFunction::operator(),
- MaybeMove(aValue.RejectValue()),
- Move(mCompletionPromise));
+ result = InvokeCallbackMethod(mRejectFunction.ptr(),
+ &RejectFunction::operator(),
+ MaybeMove(aValue.RejectValue()));
}
// Destroy callbacks after invocation so that any references in closures are
// released predictably on the dispatch thread. Otherwise, they would be
// released on whatever thread last drops its reference to the ThenValue,
// which may or may not be ok.
mResolveFunction.reset();
mRejectFunction.reset();
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mCompletionPromise || result,
+ "Can't do promise chaining for a non-promise-returning method.");
+
+ if (mCompletionPromise && result) {
+ result->ChainTo(mCompletionPromise.forget(),
+ "<chained completion promise>");
+ }
}
private:
Maybe<ResolveFunction> mResolveFunction; // Only accessed and deleted on dispatch thread.
Maybe<RejectFunction> mRejectFunction; // Only accessed and deleted on dispatch thread.
- RefPtr<typename PromiseType::Private> mCompletionPromise;
+ RefPtr<Private> mCompletionPromise;
};
template<typename ResolveRejectFunction>
class ThenValue<ResolveRejectFunction> : public ThenValueBase
{
friend class ThenCommand<ThenValue>;
-
- using R1 = typename RemoveSmartPointer<
- typename detail::MethodTrait<ResolveRejectFunction>::ReturnType>::Type;
- using SupportChaining = IntegralConstant<bool, IsMozPromise<R1>::value>;
-
- // Fall back to MozPromise when promise chaining is not supported to make code compile.
- using PromiseType =
- typename Conditional<SupportChaining::value, R1, MozPromise>::Type;
+ using SupportChaining =
+ ReturnTypeIs<ResolveRejectFunction, RefPtr<MozPromise>>;
public:
ThenValue(AbstractThread* aResponseTarget,
ResolveRejectFunction&& aResolveRejectFunction,
const char* aCallSite)
: ThenValueBase(aResponseTarget, aCallSite)
{
mResolveRejectFunction.emplace(Move(aResolveRejectFunction));
@@ -836,32 +798,40 @@ protected:
void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
{
// Note: The usage of InvokeCallbackMethod here requires that
// ResolveRejectFunction is capture-lambdas (i.e. anonymous
// classes with ::operator()), since it allows us to share code more easily.
// We could fix this if need be, though it's quite easy to work around by
// just capturing something.
- InvokeCallbackMethod<SupportChaining::value>(
- mResolveRejectFunction.ptr(),
- &ResolveRejectFunction::operator(),
- MaybeMove(aValue),
- Move(mCompletionPromise));
+ RefPtr<MozPromise> result =
+ InvokeCallbackMethod(mResolveRejectFunction.ptr(),
+ &ResolveRejectFunction::operator(),
+ MaybeMove(aValue));
// Destroy callbacks after invocation so that any references in closures are
// released predictably on the dispatch thread. Otherwise, they would be
// released on whatever thread last drops its reference to the ThenValue,
// which may or may not be ok.
mResolveRejectFunction.reset();
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mCompletionPromise || result,
+ "Can't do promise chaining for a non-promise-returning method.");
+
+ if (mCompletionPromise && result) {
+ result->ChainTo(mCompletionPromise.forget(),
+ "<chained completion promise>");
+ }
}
private:
Maybe<ResolveRejectFunction> mResolveRejectFunction; // Only accessed and deleted on dispatch thread.
- RefPtr<typename PromiseType::Private> mCompletionPromise;
+ RefPtr<Private> mCompletionPromise;
};
public:
void ThenInternal(AbstractThread* aResponseThread, ThenValueBase* aThenValue,
const char* aCallSite)
{
PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == &mMutex);
MOZ_ASSERT(aResponseThread);
@@ -885,24 +855,18 @@ protected:
* to terminate chaining and issue the request).
*
* This allows a unified syntax for promise chaining and disconnection
* and feels more like its JS counterpart.
*/
template<typename ThenValueType>
class ThenCommand
{
- // Allow Promise1::ThenCommand to access the private constructor,
- // Promise2::ThenCommand(ThenCommand&&).
- template<typename, typename, bool>
friend class MozPromise;
- using PromiseType = typename ThenValueType::PromiseType;
- using Private = typename PromiseType::Private;
-
ThenCommand(AbstractThread* aResponseThread,
const char* aCallSite,
already_AddRefed<ThenValueType> aThenValue,
MozPromise* aReceiver)
: mResponseThread(aResponseThread)
, mCallSite(aCallSite)
, mThenValue(aThenValue)
, mReceiver(aReceiver)
@@ -919,40 +883,40 @@ protected:
if (mThenValue) {
mReceiver->ThenInternal(mResponseThread, mThenValue, mCallSite);
}
}
// Allow RefPtr<MozPromise> p = somePromise->Then();
// p->Then(thread1, ...);
// p->Then(thread2, ...);
- operator RefPtr<PromiseType>()
+ template <typename...>
+ operator RefPtr<MozPromise>()
{
static_assert(
ThenValueType::SupportChaining::value,
"The resolve/reject callback needs to return a RefPtr<MozPromise> "
"in order to do promise chaining.");
RefPtr<ThenValueType> thenValue = mThenValue.forget();
// mCompletionPromise must be created before ThenInternal() to avoid race.
- RefPtr<Private> p =
- new Private("<completion promise>", true /* aIsCompletionPromise */);
+ RefPtr<MozPromise::Private> p = new MozPromise::Private(
+ "<completion promise>", true /* aIsCompletionPromise */);
thenValue->mCompletionPromise = p;
// Note ThenInternal() might nullify mCompletionPromise before return.
// So we need to return p instead of mCompletionPromise.
mReceiver->ThenInternal(mResponseThread, thenValue, mCallSite);
return p;
}
- template<typename... Ts>
+ template <typename... Ts>
auto Then(Ts&&... aArgs)
- -> decltype(DeclVal<PromiseType>().Then(Forward<Ts>(aArgs)...))
+ -> decltype(DeclVal<MozPromise>().Then(Forward<Ts>(aArgs)...))
{
- return static_cast<RefPtr<PromiseType>>(*this)->Then(
- Forward<Ts>(aArgs)...);
+ return static_cast<RefPtr<MozPromise>>(*this)->Then(Forward<Ts>(aArgs)...);
}
void Track(MozPromiseRequestHolder<MozPromise>& aRequestHolder)
{
RefPtr<ThenValueType> thenValue = mThenValue.forget();
mReceiver->ThenInternal(mResponseThread, thenValue, mCallSite);
aRequestHolder.Track(thenValue.forget());
}
@@ -1334,16 +1298,26 @@ public:
}
bool Exists() const { return !!mRequest; }
private:
RefPtr<typename PromiseType::Request> mRequest;
};
+template <typename Return>
+struct IsMozPromise
+ : FalseType
+{};
+
+template<typename ResolveValueT, typename RejectValueT, bool IsExclusive>
+struct IsMozPromise<MozPromise<ResolveValueT, RejectValueT, IsExclusive>>
+ : TrueType
+{};
+
// Asynchronous Potentially-Cross-Thread Method Calls.
//
// This machinery allows callers to schedule a promise-returning function
// (a method and object, or a function object like a lambda) to be invoked
// asynchronously on a given thread, while at the same time receiving a
// promise upon which to invoke Then() immediately. InvokeAsync dispatches a
// task to invoke the function on the proper thread and also chain the
// resulting promise to the one that the caller received, so that resolve/