Bug 1322964 - MozPromise.Then() taking only one resolve+reject function - r=jwwang draft
authorGerald Squelart <gsquelart@mozilla.com>
Tue, 03 Jan 2017 16:15:14 +1100
changeset 457224 1b16ad15ebfcdfb653d8d98073adee0f8b27b46e
parent 457223 7b25abacc7cfc1a6898963bc58120204c70bee45
child 457225 1ed068681616fa0aa5fa77f8dbf78ccbd727f363
push id40717
push usergsquelart@mozilla.com
push dateSat, 07 Jan 2017 05:09:22 +0000
reviewersjwwang
bugs1322964
milestone53.0a1
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
dom/media/gtest/TestMozPromise.cpp
xpcom/threads/MozPromise.h
--- 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());