Bug 1322964 - MozPromise.Then() taking only one resolve+reject function - r=jwwang
Then and ThenPromise can now be given only one function object, which takes a
`const MozPromise::ResolveOrRejectValue&`.
MozReview-Commit-ID: BEtc3spK9Yh
--- a/dom/media/gtest/TestMozPromise.cpp
+++ b/dom/media/gtest/TestMozPromise.cpp
@@ -107,16 +107,50 @@ TEST(MozPromise, BasicReject)
RefPtr<TaskQueue> queue = atq.Queue();
RunOnTaskQueue(queue, [queue] () -> void {
TestPromise::CreateAndReject(42.0, __func__)->Then(queue, __func__,
DO_FAIL,
[queue] (int aRejectValue) -> void { EXPECT_EQ(aRejectValue, 42.0); queue->BeginShutdown(); });
});
}
+TEST(MozPromise, BasicResolveOrRejectResolved)
+{
+ AutoTaskQueue atq;
+ RefPtr<TaskQueue> queue = atq.Queue();
+ RunOnTaskQueue(queue, [queue] () -> void {
+ TestPromise::CreateAndResolve(42, __func__)->Then(queue, __func__,
+ [queue] (const TestPromise::ResolveOrRejectValue& aValue) -> void
+ {
+ EXPECT_TRUE(aValue.IsResolve());
+ EXPECT_FALSE(aValue.IsReject());
+ EXPECT_FALSE(aValue.IsNothing());
+ EXPECT_EQ(aValue.ResolveValue(), 42);
+ queue->BeginShutdown();
+ });
+ });
+}
+
+TEST(MozPromise, BasicResolveOrRejectRejected)
+{
+ AutoTaskQueue atq;
+ RefPtr<TaskQueue> queue = atq.Queue();
+ RunOnTaskQueue(queue, [queue] () -> void {
+ TestPromise::CreateAndReject(42.0, __func__)->Then(queue, __func__,
+ [queue] (const TestPromise::ResolveOrRejectValue& aValue) -> void
+ {
+ EXPECT_TRUE(aValue.IsReject());
+ EXPECT_FALSE(aValue.IsResolve());
+ EXPECT_FALSE(aValue.IsNothing());
+ EXPECT_EQ(aValue.RejectValue(), 42.0);
+ queue->BeginShutdown();
+ });
+ });
+}
+
TEST(MozPromise, AsyncResolve)
{
AutoTaskQueue atq;
RefPtr<TaskQueue> queue = atq.Queue();
RunOnTaskQueue(queue, [queue] () -> void {
RefPtr<TestPromise::Private> p = new TestPromise::Private(__func__);
// Kick off three racing tasks, and make sure we get the one that finishes earliest.
--- a/xpcom/threads/MozPromise.h
+++ b/xpcom/threads/MozPromise.h
@@ -573,16 +573,67 @@ protected:
return completion.forget();
}
private:
Maybe<ResolveFunction> mResolveFunction; // Only accessed and deleted on dispatch thread.
Maybe<RejectFunction> mRejectFunction; // Only accessed and deleted on dispatch thread.
};
+ // Specialization of FunctionThenValue (with 2nd template arg being 'void')
+ // that only takes one function, to be called with a ResolveOrRejectValue.
+ template<typename ResolveRejectFunction>
+ class FunctionThenValue<ResolveRejectFunction, void> : public ThenValueBase
+ {
+ public:
+ FunctionThenValue(AbstractThread* aResponseTarget,
+ ResolveRejectFunction&& aResolveRejectFunction,
+ const char* aCallSite)
+ : ThenValueBase(aResponseTarget, aCallSite)
+ {
+ mResolveRejectFunction.emplace(Move(aResolveRejectFunction));
+ }
+
+ void Disconnect() override
+ {
+ ThenValueBase::Disconnect();
+
+ // If a Request has been disconnected, we don't guarantee that the
+ // resolve/reject runnable will be dispatched. Destroy our callbacks
+ // now so that any references in closures are released predictable on
+ // the dispatch thread.
+ mResolveRejectFunction.reset();
+ }
+
+ protected:
+ already_AddRefed<MozPromise> DoResolveOrRejectInternal(const 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.
+ RefPtr<MozPromise> completion =
+ InvokeCallbackMethod(mResolveRejectFunction.ptr(),
+ &ResolveRejectFunction::operator(),
+ 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();
+
+ return completion.forget();
+ }
+
+ private:
+ Maybe<ResolveRejectFunction> mResolveRejectFunction; // Only accessed and deleted on dispatch thread.
+ };
+
public:
void ThenInternal(AbstractThread* aResponseThread, ThenValueBase* aThenValue,
const char* aCallSite)
{
MutexAutoLock lock(mMutex);
MOZ_ASSERT(aResponseThread->IsDispatchReliable());
MOZ_DIAGNOSTIC_ASSERT(!IsExclusive || !mHaveRequest);
mHaveRequest = true;
@@ -683,16 +734,26 @@ public:
ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction)
{
using ThenType = FunctionThenValue<ResolveFunction, RejectFunction>;
RefPtr<ThenValueBase> thenValue = new ThenType(aResponseThread,
Move(aResolveFunction), Move(aRejectFunction), aCallSite);
return ThenCommand(aResponseThread, aCallSite, thenValue.forget(), this);
}
+ template<typename ResolveRejectFunction>
+ ThenCommand Then(AbstractThread* aResponseThread, const char* aCallSite,
+ ResolveRejectFunction&& aResolveRejectFunction)
+ {
+ using ThenType = FunctionThenValue<ResolveRejectFunction, void>;
+ RefPtr<ThenValueBase> thenValue = new ThenType(aResponseThread,
+ Move(aResolveRejectFunction), aCallSite);
+ return ThenCommand(aResponseThread, aCallSite, thenValue.forget(), this);
+ }
+
void ChainTo(already_AddRefed<Private> aChainedPromise, const char* aCallSite)
{
MutexAutoLock lock(mMutex);
MOZ_DIAGNOSTIC_ASSERT(!IsExclusive || !mHaveRequest);
mHaveRequest = true;
RefPtr<Private> chainedPromise = aChainedPromise;
PROMISE_LOG("%s invoking Chain() [this=%p, chainedPromise=%p, isPending=%d]",
aCallSite, this, chainedPromise.get(), (int) IsPending());