Bug 1484373: Part 2c - Add Promise::Then handler which accepts a lambda. r=smaug
authorKris Maglione <maglione.k@gmail.com>
Sat, 18 Aug 2018 09:43:22 -0700
changeset 490958 721d186ac3c3ecdf7a8e3fb1a2f4b6052d56e725
parent 490957 13e34da7e745043689e2bda3adbc889b25782f71
child 490959 010feaaca7deeddb0ae13e5e899988af089ae241
push id1815
push userffxbld-merge
push dateMon, 15 Oct 2018 10:40:45 +0000
treeherdermozilla-release@18d4c09e9378 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1484373
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1484373: Part 2c - Add Promise::Then handler which accepts a lambda. r=smaug This makes it easier to add promise handlers from C++ in a manner similar to JavaScript. Differential Revision: https://phabricator.services.mozilla.com/D3692
dom/promise/Promise-inl.h
dom/promise/Promise.cpp
dom/promise/Promise.h
dom/promise/moz.build
new file mode 100644
--- /dev/null
+++ b/dom/promise/Promise-inl.h
@@ -0,0 +1,166 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_Promise_inl_h
+#define mozilla_dom_Promise_inl_h
+
+#include "mozilla/TupleCycleCollection.h"
+#include "mozilla/TypeTraits.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+
+namespace mozilla {
+namespace dom {
+
+class PromiseNativeThenHandlerBase : public PromiseNativeHandler
+{
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(PromiseNativeThenHandlerBase)
+
+  PromiseNativeThenHandlerBase(Promise& aPromise)
+    : mPromise(&aPromise)
+  {}
+
+  void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+  void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+protected:
+  virtual ~PromiseNativeThenHandlerBase() = default;
+
+  virtual already_AddRefed<Promise>
+  CallResolveCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) = 0;
+
+  virtual void Traverse(nsCycleCollectionTraversalCallback&) = 0;
+  virtual void Unlink() = 0;
+
+  RefPtr<Promise> mPromise;
+};
+
+namespace {
+
+template <typename T,
+          bool = IsRefcounted<typename RemovePointer<T>::Type>::value,
+          bool = (IsConvertible<T, nsISupports*>::value ||
+                  IsConvertible<T*, nsISupports*>::value)>
+struct StorageTypeHelper
+{
+  using Type = T;
+};
+
+template <typename T>
+struct StorageTypeHelper<T, true, true>
+{
+  using Type = nsCOMPtr<T>;
+};
+
+template <typename T>
+struct StorageTypeHelper<nsCOMPtr<T>, true, true>
+{
+  using Type = nsCOMPtr<T>;
+};
+
+template <typename T>
+struct StorageTypeHelper<T*, true, false>
+{
+  using Type = RefPtr<T>;
+};
+
+template <template <typename> class SmartPtr, typename T>
+struct StorageTypeHelper<SmartPtr<T>, true, false>
+  : EnableIf<IsConvertible<SmartPtr<T>, T*>::value,
+             RefPtr<T>>
+{
+};
+
+template <typename T>
+using StorageType = typename StorageTypeHelper<typename Decay<T>::Type>::Type;
+
+using ::ImplCycleCollectionUnlink;
+
+template <typename Callback, typename... Args>
+class NativeThenHandler final : public PromiseNativeThenHandlerBase
+{
+public:
+  NativeThenHandler(Promise& aPromise, Callback&& aOnResolve,
+                    Args&&... aArgs)
+    : PromiseNativeThenHandlerBase(aPromise)
+    , mOnResolve(std::forward<Callback>(aOnResolve))
+    , mArgs(std::forward<Args>(aArgs)...)
+  {}
+
+protected:
+  void Traverse(nsCycleCollectionTraversalCallback& cb) override
+  {
+    auto* tmp = this;
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mArgs)
+  }
+
+  void Unlink() override
+  {
+    auto* tmp = this;
+    NS_IMPL_CYCLE_COLLECTION_UNLINK(mArgs)
+  }
+
+  already_AddRefed<Promise>
+  CallResolveCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    return CallCallback(aCx, mOnResolve, aValue);
+  }
+
+  template <size_t... Indices>
+  already_AddRefed<Promise>
+  CallCallback(JSContext* aCx, const Callback& aHandler, JS::Handle<JS::Value> aValue,
+               std::index_sequence<Indices...>)
+  {
+    return mOnResolve(aCx, aValue, std::forward<Args>(Get<Indices>(mArgs))...);
+  }
+
+  already_AddRefed<Promise>
+  CallCallback(JSContext* aCx, const Callback& aHandler, JS::Handle<JS::Value> aValue)
+  {
+    return CallCallback(aCx, aHandler, aValue, std::index_sequence_for<Args...>{});
+  }
+
+  Callback mOnResolve;
+
+  Tuple<StorageType<Args>...> mArgs;
+};
+
+} // anonymous namespace
+
+template <typename Callback, typename... Args>
+typename EnableIf<
+  Promise::IsHandlerCallback<Callback, Args...>::value,
+  Result<RefPtr<Promise>, nsresult>>::Type
+Promise::ThenWithCycleCollectedArgs(Callback&& aOnResolve, Args&&... aArgs)
+{
+  using HandlerType = NativeThenHandler<Callback, Args...>;
+
+  ErrorResult rv;
+  RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv);
+  if (rv.Failed()) {
+    return Err(rv.StealNSResult());
+  }
+
+  auto* handler = new (fallible) HandlerType(
+    *promise, std::forward<Callback>(aOnResolve),
+    std::forward<Args>(aArgs)...);
+
+  if (!handler) {
+    return Err(NS_ERROR_OUT_OF_MEMORY);
+  }
+
+  AppendNativeHandler(handler);
+  return std::move(promise);
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Promise_inl_h
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -1,22 +1,24 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Promise-inl.h"
 
 #include "js/Debug.h"
 
 #include "mozilla/Atomics.h"
 #include "mozilla/CycleCollectedJSContext.h"
 #include "mozilla/OwningNonNull.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/ResultExtensions.h"
 
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/DOMExceptionBinding.h"
 #include "mozilla/dom/MediaStreamError.h"
 #include "mozilla/dom/PromiseBinding.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/WorkerPrivate.h"
@@ -222,16 +224,58 @@ Promise::Then(JSContext* aCx,
     aRv.NoteJSContextException(aCx);
     return;
   }
 
   aRetval.setObject(*retval);
 }
 
 void
+PromiseNativeThenHandlerBase::ResolvedCallback(JSContext* aCx,
+                                               JS::Handle<JS::Value> aValue)
+{
+  RefPtr<Promise> promise = CallResolveCallback(aCx, aValue);
+  mPromise->MaybeResolve(promise);
+}
+
+void
+PromiseNativeThenHandlerBase::RejectedCallback(JSContext* aCx,
+                                               JS::Handle<JS::Value> aValue)
+{
+  mPromise->MaybeReject(aCx, aValue);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(PromiseNativeThenHandlerBase)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PromiseNativeThenHandlerBase)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
+  tmp->Traverse(cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PromiseNativeThenHandlerBase)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
+  tmp->Unlink();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeThenHandlerBase)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeThenHandlerBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeThenHandlerBase)
+
+Result<RefPtr<Promise>, nsresult>
+Promise::ThenWithoutCycleCollection(
+  const std::function<already_AddRefed<Promise>(JSContext* aCx,
+                                                JS::HandleValue aValue)>& aCallback)
+{
+  return ThenWithCycleCollectedArgs(aCallback);
+}
+
+void
 Promise::CreateWrapper(JS::Handle<JSObject*> aDesiredProto, ErrorResult& aRv)
 {
   AutoJSAPI jsapi;
   if (!jsapi.Init(mGlobal)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return;
   }
   JSContext* cx = jsapi.cx();
--- a/dom/promise/Promise.h
+++ b/dom/promise/Promise.h
@@ -20,16 +20,17 @@
 #include "nsWrapperCache.h"
 #include "nsAutoPtr.h"
 #include "js/TypeDecls.h"
 #include "jspubtd.h"
 
 class nsIGlobalObject;
 
 namespace mozilla {
+
 namespace dom {
 
 class AnyCallback;
 class MediaStreamError;
 class PromiseInit;
 class PromiseNativeHandler;
 class PromiseDebugging;
 
@@ -137,16 +138,50 @@ public:
   Then(JSContext* aCx,
        // aCalleeGlobal may not be in the compartment of aCx, when called over
        // Xrays.
        JS::Handle<JSObject*> aCalleeGlobal,
        AnyCallback* aResolveCallback, AnyCallback* aRejectCallback,
        JS::MutableHandle<JS::Value> aRetval,
        ErrorResult& aRv);
 
+  template <typename Callback, typename... Args>
+  using IsHandlerCallback =
+      IsSame<already_AddRefed<Promise>,
+             decltype(DeclVal<Callback>()(
+                (JSContext*)(nullptr),
+                DeclVal<JS::Handle<JS::Value>>(),
+                DeclVal<Args>()...))>;
+
+  // Similar to the JavaScript Then() function. Accepts a single lambda function
+  // argument, which it attaches as a native resolution handler, and returns a
+  // new promise which resolves with that handler's return value, or propagates
+  // any rejections from this promise.
+  //
+  // Any additional arguments passed after the callback function are stored and
+  // passed as additional arguments to the function when it is called. These
+  // values will participate in cycle collection for the promise handler, and
+  // therefore may safely form reference cycles with the promise chain.
+  //
+  // Any strong references required by the callback should be passed in this
+  // manner, rather than using lambda capture, lambda captures do not support
+  // cycle collection, and can easily lead to leaks.
+  //
+  // Does not currently support rejection handlers.
+  template <typename Callback, typename... Args>
+  typename EnableIf<
+    IsHandlerCallback<Callback, Args...>::value,
+    Result<RefPtr<Promise>, nsresult>>::Type
+  ThenWithCycleCollectedArgs(Callback&& aOnResolve, Args&&... aArgs);
+
+  Result<RefPtr<Promise>, nsresult>
+  ThenWithoutCycleCollection(
+    const std::function<already_AddRefed<Promise>(JSContext*,
+                                                  JS::HandleValue)>& aCallback);
+
   JSObject* PromiseObj() const
   {
     return mPromiseObj;
   }
 
   void AppendNativeHandler(PromiseNativeHandler* aRunnable);
 
   JSObject* GlobalJSObject() const;
--- a/dom/promise/moz.build
+++ b/dom/promise/moz.build
@@ -3,16 +3,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "DOM")
 
 EXPORTS.mozilla.dom += [
+    'Promise-inl.h',
     'Promise.h',
     'PromiseDebugging.h',
     'PromiseNativeHandler.h',
     'PromiseWorkerProxy.h',
 ]
 
 UNIFIED_SOURCES += [
     'Promise.cpp',