Bug 1170760 part 9. Stop using Promise::Resolve in the bindings for PromiseDebugging. r=baku
authorBoris Zbarsky <bzbarsky@mit.edu>
Wed, 25 Nov 2015 15:48:09 -0500
changeset 308967 d282d800811ea5c01de02075b954077c8e1412c8
parent 308966 fed3e6ac1affd2773441a28b9170c47eaf80e6d1
child 308968 f540f2da98a582b15bc2032f62d2db6e9c039f3a
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1170760
milestone45.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 1170760 part 9. Stop using Promise::Resolve in the bindings for PromiseDebugging. r=baku
dom/bindings/Errors.msg
dom/promise/PromiseDebugging.cpp
dom/promise/PromiseDebugging.h
dom/webidl/PromiseDebugging.webidl
--- a/dom/bindings/Errors.msg
+++ b/dom/bindings/Errors.msg
@@ -83,10 +83,11 @@ MSG_DEF(MSG_NOTIFICATION_PERMISSION_DENI
 MSG_DEF(MSG_NOTIFICATION_NO_CONSTRUCTOR_IN_SERVICEWORKER, 0, JSEXN_TYPEERR, "Notification constructor cannot be used in ServiceWorkerGlobalScope. Use registration.showNotification() instead.")
 MSG_DEF(MSG_INVALID_SCOPE, 2, JSEXN_TYPEERR, "Invalid scope trying to resolve {0} with base URL {1}.")
 MSG_DEF(MSG_INVALID_KEYFRAME_OFFSETS, 0, JSEXN_TYPEERR, "Keyframes with specified offsets must be in order and all be in the range [0, 1].")
 MSG_DEF(MSG_ILLEGAL_PROMISE_CONSTRUCTOR, 0, JSEXN_TYPEERR, "Non-constructor value passed to NewPromiseCapability.")
 MSG_DEF(MSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY, 0, JSEXN_TYPEERR, "GetCapabilitiesExecutor function already invoked with non-undefined values.")
 MSG_DEF(MSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the resolve function.")
 MSG_DEF(MSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the reject function.")
 MSG_DEF(MSG_PROMISE_ARG_NOT_ITERABLE, 1, JSEXN_TYPEERR, "{0} is not iterable")
+MSG_DEF(MSG_IS_NOT_PROMISE, 1, JSEXN_TYPEERR, "{0} is not a Promise")
 MSG_DEF(MSG_SW_INSTALL_ERROR, 2, JSEXN_TYPEERR, "ServiceWorker script at {0} for scope {1} encountered an error during installation.")
 MSG_DEF(MSG_SW_SCRIPT_THREW, 2, JSEXN_TYPEERR, "ServiceWorker script at {0} for scope {1} threw an exception during script evaluation.")
--- a/dom/promise/PromiseDebugging.cpp
+++ b/dom/promise/PromiseDebugging.cpp
@@ -9,16 +9,17 @@
 
 #include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/ThreadLocal.h"
 #include "mozilla/TimeStamp.h"
 
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseBinding.h"
 #include "mozilla/dom/PromiseDebugging.h"
 #include "mozilla/dom/PromiseDebuggingBinding.h"
 
 namespace mozilla {
 namespace dom {
 
 class FlushRejections: public nsCancelableRunnable
 {
@@ -60,33 +61,49 @@ private:
   // `true` if an instance of `FlushRejections` is currently dispatched
   // and has not been executed yet.
   static ThreadLocal<bool> sDispatched;
 };
 
 /* static */ ThreadLocal<bool>
 FlushRejections::sDispatched;
 
+static Promise*
+UnwrapPromise(JS::Handle<JSObject*> aPromise, ErrorResult& aRv)
+{
+  Promise* promise;
+  if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Promise, aPromise, promise)))) {
+    aRv.ThrowTypeError<MSG_IS_NOT_PROMISE>(NS_LITERAL_STRING("Argument"));
+    return nullptr;
+  }
+  return promise;
+}
+
 /* static */ void
-PromiseDebugging::GetState(GlobalObject&, Promise& aPromise,
-                           PromiseDebuggingStateHolder& aState)
+PromiseDebugging::GetState(GlobalObject&, JS::Handle<JSObject*> aPromise,
+                           PromiseDebuggingStateHolder& aState,
+                           ErrorResult& aRv)
 {
-  switch (aPromise.mState) {
+  Promise* promise = UnwrapPromise(aPromise, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+  switch (promise->mState) {
   case Promise::Pending:
     aState.mState = PromiseDebuggingState::Pending;
     break;
   case Promise::Resolved:
     aState.mState = PromiseDebuggingState::Fulfilled;
-    JS::ExposeValueToActiveJS(aPromise.mResult);
-    aState.mValue = aPromise.mResult;
+    JS::ExposeValueToActiveJS(promise->mResult);
+    aState.mValue = promise->mResult;
     break;
   case Promise::Rejected:
     aState.mState = PromiseDebuggingState::Rejected;
-    JS::ExposeValueToActiveJS(aPromise.mResult);
-    aState.mReason = aPromise.mResult;
+    JS::ExposeValueToActiveJS(promise->mResult);
+    aState.mReason = promise->mResult;
     break;
   }
 }
 
 /*static */ nsString
 PromiseDebugging::sIDPrefix;
 
 /* static */ void
@@ -113,59 +130,89 @@ PromiseDebugging::Shutdown()
 /* static */ void
 PromiseDebugging::FlushUncaughtRejections()
 {
   MOZ_ASSERT(!NS_IsMainThread());
   FlushRejections::FlushSync();
 }
 
 /* static */ void
-PromiseDebugging::GetAllocationStack(GlobalObject&, Promise& aPromise,
-                                     JS::MutableHandle<JSObject*> aStack)
+PromiseDebugging::GetAllocationStack(GlobalObject&, JS::Handle<JSObject*> aPromise,
+                                     JS::MutableHandle<JSObject*> aStack,
+                                     ErrorResult& aRv)
 {
-  aStack.set(aPromise.mAllocationStack);
+  Promise* promise = UnwrapPromise(aPromise, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+  aStack.set(promise->mAllocationStack);
 }
 
 /* static */ void
-PromiseDebugging::GetRejectionStack(GlobalObject&, Promise& aPromise,
-                                    JS::MutableHandle<JSObject*> aStack)
+PromiseDebugging::GetRejectionStack(GlobalObject&, JS::Handle<JSObject*> aPromise,
+                                    JS::MutableHandle<JSObject*> aStack,
+                                    ErrorResult& aRv)
 {
-  aStack.set(aPromise.mRejectionStack);
+  Promise* promise = UnwrapPromise(aPromise, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+  aStack.set(promise->mRejectionStack);
 }
 
 /* static */ void
-PromiseDebugging::GetFullfillmentStack(GlobalObject&, Promise& aPromise,
-                                       JS::MutableHandle<JSObject*> aStack)
+PromiseDebugging::GetFullfillmentStack(GlobalObject&, JS::Handle<JSObject*> aPromise,
+                                       JS::MutableHandle<JSObject*> aStack,
+                                       ErrorResult& aRv)
 {
-  aStack.set(aPromise.mFullfillmentStack);
+  Promise* promise = UnwrapPromise(aPromise, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+  aStack.set(promise->mFullfillmentStack);
 }
 
 /* static */ void
-PromiseDebugging::GetDependentPromises(GlobalObject&, Promise& aPromise,
-                                       nsTArray<RefPtr<Promise>>& aPromises)
+PromiseDebugging::GetDependentPromises(GlobalObject&, JS::Handle<JSObject*> aPromise,
+                                       nsTArray<RefPtr<Promise>>& aPromises,
+                                       ErrorResult& aRv)
 {
-  aPromise.GetDependentPromises(aPromises);
+  Promise* promise = UnwrapPromise(aPromise, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+  promise->GetDependentPromises(aPromises);
 }
 
 /* static */ double
-PromiseDebugging::GetPromiseLifetime(GlobalObject&, Promise& aPromise)
+PromiseDebugging::GetPromiseLifetime(GlobalObject&,
+                                     JS::Handle<JSObject*> aPromise,
+                                     ErrorResult& aRv)
 {
-  return (TimeStamp::Now() - aPromise.mCreationTimestamp).ToMilliseconds();
+  Promise* promise = UnwrapPromise(aPromise, aRv);
+  if (aRv.Failed()) {
+    return 0;
+  }
+  return (TimeStamp::Now() - promise->mCreationTimestamp).ToMilliseconds();
 }
 
 /* static */ double
-PromiseDebugging::GetTimeToSettle(GlobalObject&, Promise& aPromise,
+PromiseDebugging::GetTimeToSettle(GlobalObject&, JS::Handle<JSObject*> aPromise,
                                   ErrorResult& aRv)
 {
-  if (aPromise.mState == Promise::Pending) {
+  Promise* promise = UnwrapPromise(aPromise, aRv);
+  if (aRv.Failed()) {
+    return 0;
+  }
+  if (promise->mState == Promise::Pending) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return 0;
   }
-  return (aPromise.mSettlementTimestamp -
-          aPromise.mCreationTimestamp).ToMilliseconds();
+  return (promise->mSettlementTimestamp -
+          promise->mCreationTimestamp).ToMilliseconds();
 }
 
 /* static */ void
 PromiseDebugging::AddUncaughtRejectionObserver(GlobalObject&,
                                                UncaughtRejectionObserver& aObserver)
 {
   CycleCollectedJSRuntime* storage = CycleCollectedJSRuntime::Get();
   nsTArray<nsCOMPtr<nsISupports>>& observers = storage->mUncaughtRejectionObservers;
@@ -199,20 +246,25 @@ PromiseDebugging::AddUncaughtRejection(P
 PromiseDebugging::AddConsumedRejection(Promise& aPromise)
 {
   CycleCollectedJSRuntime::Get()->mConsumedRejections.AppendElement(&aPromise);
   FlushRejections::DispatchNeeded();
 }
 
 /* static */ void
 PromiseDebugging::GetPromiseID(GlobalObject&,
-                               Promise& aPromise,
-                               nsString& aID)
+                               JS::Handle<JSObject*> aPromise,
+                               nsString& aID,
+                               ErrorResult& aRv)
 {
-  uint64_t promiseID = aPromise.GetID();
+  Promise* promise = UnwrapPromise(aPromise, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+  uint64_t promiseID = promise->GetID();
   aID = sIDPrefix;
   aID.AppendInt(promiseID);
 }
 
 /* static */ void
 PromiseDebugging::FlushUncaughtRejectionsInternal()
 {
   CycleCollectedJSRuntime* storage = CycleCollectedJSRuntime::Get();
--- a/dom/promise/PromiseDebugging.h
+++ b/dom/promise/PromiseDebugging.h
@@ -27,32 +27,42 @@ class UncaughtRejectionObserver;
 class FlushRejections;
 
 class PromiseDebugging
 {
 public:
   static void Init();
   static void Shutdown();
 
-  static void GetState(GlobalObject&, Promise& aPromise,
-                       PromiseDebuggingStateHolder& aState);
+  static void GetState(GlobalObject&, JS::Handle<JSObject*> aPromise,
+                       PromiseDebuggingStateHolder& aState,
+                       ErrorResult& aRv);
 
-  static void GetAllocationStack(GlobalObject&, Promise& aPromise,
-                                 JS::MutableHandle<JSObject*> aStack);
-  static void GetRejectionStack(GlobalObject&, Promise& aPromise,
-                                JS::MutableHandle<JSObject*> aStack);
-  static void GetFullfillmentStack(GlobalObject&, Promise& aPromise,
-                                   JS::MutableHandle<JSObject*> aStack);
-  static void GetDependentPromises(GlobalObject&, Promise& aPromise,
-                                   nsTArray<RefPtr<Promise>>& aPromises);
-  static double GetPromiseLifetime(GlobalObject&, Promise& aPromise);
-  static double GetTimeToSettle(GlobalObject&, Promise& aPromise,
+  static void GetAllocationStack(GlobalObject&, JS::Handle<JSObject*> aPromise,
+                                 JS::MutableHandle<JSObject*> aStack,
+                                 ErrorResult& aRv);
+  static void GetRejectionStack(GlobalObject&, JS::Handle<JSObject*> aPromise,
+                                JS::MutableHandle<JSObject*> aStack,
+                                ErrorResult& aRv);
+  static void GetFullfillmentStack(GlobalObject&,
+                                   JS::Handle<JSObject*> aPromise,
+                                   JS::MutableHandle<JSObject*> aStack,
+                                   ErrorResult& aRv);
+  static void GetDependentPromises(GlobalObject&,
+                                   JS::Handle<JSObject*> aPromise,
+                                   nsTArray<RefPtr<Promise>>& aPromises,
+                                   ErrorResult& aRv);
+  static double GetPromiseLifetime(GlobalObject&,
+                                   JS::Handle<JSObject*> aPromise,
+                                   ErrorResult& aRv);
+  static double GetTimeToSettle(GlobalObject&, JS::Handle<JSObject*> aPromise,
                                 ErrorResult& aRv);
 
-  static void GetPromiseID(GlobalObject&, Promise&, nsString&);
+  static void GetPromiseID(GlobalObject&, JS::Handle<JSObject*>, nsString&,
+                           ErrorResult&);
 
   // Mechanism for watching uncaught instances of Promise.
   static void AddUncaughtRejectionObserver(GlobalObject&,
                                            UncaughtRejectionObserver& aObserver);
   static bool RemoveUncaughtRejectionObserver(GlobalObject&,
                                               UncaughtRejectionObserver& aObserver);
 
   // Mark a Promise as having been left uncaught at script completion.
--- a/dom/webidl/PromiseDebugging.webidl
+++ b/dom/webidl/PromiseDebugging.webidl
@@ -47,43 +47,60 @@ callback interface UncaughtRejectionObse
    * @param p A Promise that was previously left in uncaught state is
    * now caught, i.e. it is not the last in its chain anymore.
    */
   void onConsumed(Promise<any> p);
 };
 
 [ChromeOnly, Exposed=(Window,System)]
 interface PromiseDebugging {
-  static PromiseDebuggingStateHolder getState(Promise<any> p);
+  /**
+   * The various functions on this interface all expect to take promises but
+   * don't want the WebIDL behavior of assimilating random passed-in objects
+   * into promises.  They also want to treat Promise subclass instances as
+   * promises instead of wrapping them in a vanilla Promise, which is what the
+   * IDL spec says to do.  So we list all our arguments as "object" instead of
+   * "Promise" and check for them being a Promise internally.
+   */
+
+  /**
+   * Get the current state of the given promise.
+   */
+  [Throws]
+  static PromiseDebuggingStateHolder getState(object p);
 
   /**
    * Return the stack to the promise's allocation point.  This can
    * return null if the promise was not created from script.
    */
-  static object? getAllocationStack(Promise<any> p);
+  [Throws]
+  static object? getAllocationStack(object p);
 
   /**
    * Return the stack to the promise's rejection point, if the
    * rejection happened from script.  This can return null if the
    * promise has not been rejected or was not rejected from script.
    */
-  static object? getRejectionStack(Promise<any> p);
+  [Throws]
+  static object? getRejectionStack(object p);
 
   /**
    * Return the stack to the promise's fulfillment point, if the
    * fulfillment happened from script.  This can return null if the
    * promise has not been fulfilled or was not fulfilled from script.
    */
-  static object? getFullfillmentStack(Promise<any> p);
+  [Throws]
+  static object? getFullfillmentStack(object p);
 
   /**
    * Return an identifier for a promise. This identifier is guaranteed
    * to be unique to this instance of Firefox.
    */
-  static DOMString getPromiseID(Promise<any> p);
+  [Throws]
+  static DOMString getPromiseID(object p);
 
   /**
    * Get the promises directly depending on a given promise.  These are:
    *
    * 1) Return values of then() calls on the promise
    * 2) Return values of Promise.all() if the given promise was passed in as one
    *    of the arguments.
    * 3) Return values of Promise.race() if the given promise was passed in as
@@ -91,30 +108,32 @@ interface PromiseDebugging {
    *
    * Once a promise is settled, it will generally notify its dependent promises
    * and forget about them, so this is most useful on unsettled promises.
    *
    * Note that this function only returns the promises that directly depend on
    * p.  It does not recursively return promises that depend on promises that
    * depend on p.
    */
-  static sequence<Promise<any>> getDependentPromises(Promise<any> p);
+  [Throws]
+  static sequence<Promise<any>> getDependentPromises(object p);
 
   /**
    * Get the number of milliseconds elapsed since the given promise was created.
    */
-  static DOMHighResTimeStamp getPromiseLifetime(Promise<any> p);
+  [Throws]
+  static DOMHighResTimeStamp getPromiseLifetime(object p);
 
   /*
    * Get the number of milliseconds elapsed between the promise being created
    * and being settled.  Throws NS_ERROR_UNEXPECTED if the promise has not
    * settled.
    */
   [Throws]
-  static DOMHighResTimeStamp getTimeToSettle(Promise<any> p);
+  static DOMHighResTimeStamp getTimeToSettle(object p);
 
   /**
    * Watching uncaught rejections on the current thread.
    *
    * Adding an observer twice will cause it to be notified twice
    * of events.
    */
   static void addUncaughtRejectionObserver(UncaughtRejectionObserver o);