dom/promise/Promise.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
     2 /* vim: set ts=2 et sw=2 tw=80: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include "mozilla/dom/Promise.h"
     9 #include "jsfriendapi.h"
    10 #include "mozilla/dom/OwningNonNull.h"
    11 #include "mozilla/dom/PromiseBinding.h"
    12 #include "mozilla/CycleCollectedJSRuntime.h"
    13 #include "mozilla/Preferences.h"
    14 #include "PromiseCallback.h"
    15 #include "PromiseNativeHandler.h"
    16 #include "nsContentUtils.h"
    17 #include "WorkerPrivate.h"
    18 #include "WorkerRunnable.h"
    19 #include "nsJSPrincipals.h"
    20 #include "nsJSUtils.h"
    21 #include "nsPIDOMWindow.h"
    22 #include "nsJSEnvironment.h"
    24 namespace mozilla {
    25 namespace dom {
    27 using namespace workers;
    29 NS_IMPL_ISUPPORTS0(PromiseNativeHandler)
    31 // PromiseTask
    33 // This class processes the promise's callbacks with promise's result.
    34 class PromiseTask MOZ_FINAL : public nsRunnable
    35 {
    36 public:
    37   PromiseTask(Promise* aPromise)
    38     : mPromise(aPromise)
    39   {
    40     MOZ_ASSERT(aPromise);
    41     MOZ_COUNT_CTOR(PromiseTask);
    42   }
    44   ~PromiseTask()
    45   {
    46     MOZ_COUNT_DTOR(PromiseTask);
    47   }
    49   NS_IMETHOD Run()
    50   {
    51     mPromise->mTaskPending = false;
    52     mPromise->RunTask();
    53     return NS_OK;
    54   }
    56 private:
    57   nsRefPtr<Promise> mPromise;
    58 };
    60 class WorkerPromiseTask MOZ_FINAL : public WorkerSameThreadRunnable
    61 {
    62 public:
    63   WorkerPromiseTask(WorkerPrivate* aWorkerPrivate, Promise* aPromise)
    64     : WorkerSameThreadRunnable(aWorkerPrivate)
    65     , mPromise(aPromise)
    66   {
    67     MOZ_ASSERT(aPromise);
    68     MOZ_COUNT_CTOR(WorkerPromiseTask);
    69   }
    71   ~WorkerPromiseTask()
    72   {
    73     MOZ_COUNT_DTOR(WorkerPromiseTask);
    74   }
    76   bool
    77   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
    78   {
    79     mPromise->mTaskPending = false;
    80     mPromise->RunTask();
    81     return true;
    82   }
    84 private:
    85   nsRefPtr<Promise> mPromise;
    86 };
    88 class PromiseResolverMixin
    89 {
    90 public:
    91   PromiseResolverMixin(Promise* aPromise,
    92                        JS::Handle<JS::Value> aValue,
    93                        Promise::PromiseState aState)
    94     : mPromise(aPromise)
    95     , mValue(CycleCollectedJSRuntime::Get()->Runtime(), aValue)
    96     , mState(aState)
    97   {
    98     MOZ_ASSERT(aPromise);
    99     MOZ_ASSERT(mState != Promise::Pending);
   100     MOZ_COUNT_CTOR(PromiseResolverMixin);
   101   }
   103   virtual ~PromiseResolverMixin()
   104   {
   105     NS_ASSERT_OWNINGTHREAD(PromiseResolverMixin);
   106     MOZ_COUNT_DTOR(PromiseResolverMixin);
   107   }
   109 protected:
   110   void
   111   RunInternal()
   112   {
   113     NS_ASSERT_OWNINGTHREAD(PromiseResolverMixin);
   114     mPromise->RunResolveTask(
   115       JS::Handle<JS::Value>::fromMarkedLocation(mValue.address()),
   116       mState, Promise::SyncTask);
   117   }
   119 private:
   120   nsRefPtr<Promise> mPromise;
   121   JS::PersistentRooted<JS::Value> mValue;
   122   Promise::PromiseState mState;
   123   NS_DECL_OWNINGTHREAD;
   124 };
   126 // This class processes the promise's callbacks with promise's result.
   127 class PromiseResolverTask MOZ_FINAL : public nsRunnable,
   128                                       public PromiseResolverMixin
   129 {
   130 public:
   131   PromiseResolverTask(Promise* aPromise,
   132                       JS::Handle<JS::Value> aValue,
   133                       Promise::PromiseState aState)
   134     : PromiseResolverMixin(aPromise, aValue, aState)
   135   {}
   137   ~PromiseResolverTask()
   138   {}
   140   NS_IMETHOD Run()
   141   {
   142     RunInternal();
   143     return NS_OK;
   144   }
   145 };
   147 class WorkerPromiseResolverTask MOZ_FINAL : public WorkerSameThreadRunnable,
   148                                             public PromiseResolverMixin
   149 {
   150 public:
   151   WorkerPromiseResolverTask(WorkerPrivate* aWorkerPrivate,
   152                             Promise* aPromise,
   153                             JS::Handle<JS::Value> aValue,
   154                             Promise::PromiseState aState)
   155     : WorkerSameThreadRunnable(aWorkerPrivate),
   156       PromiseResolverMixin(aPromise, aValue, aState)
   157   {}
   159   ~WorkerPromiseResolverTask()
   160   {}
   162   bool
   163   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
   164   {
   165     RunInternal();
   166     return true;
   167   }
   168 };
   170 // Promise
   172 NS_IMPL_CYCLE_COLLECTION_CLASS(Promise)
   174 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise)
   175   tmp->MaybeReportRejectedOnce();
   176   NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
   177   NS_IMPL_CYCLE_COLLECTION_UNLINK(mResolveCallbacks)
   178   NS_IMPL_CYCLE_COLLECTION_UNLINK(mRejectCallbacks)
   179   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
   180 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
   182 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Promise)
   183   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
   184   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResolveCallbacks)
   185   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRejectCallbacks)
   186   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
   187 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
   189 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Promise)
   190   NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mResult)
   191   NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
   192 NS_IMPL_CYCLE_COLLECTION_TRACE_END
   194 NS_IMPL_CYCLE_COLLECTING_ADDREF(Promise)
   195 NS_IMPL_CYCLE_COLLECTING_RELEASE(Promise)
   197 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Promise)
   198   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   199   NS_INTERFACE_MAP_ENTRY(nsISupports)
   200 NS_INTERFACE_MAP_END
   202 Promise::Promise(nsIGlobalObject* aGlobal)
   203   : mGlobal(aGlobal)
   204   , mResult(JS::UndefinedValue())
   205   , mState(Pending)
   206   , mTaskPending(false)
   207   , mHadRejectCallback(false)
   208   , mResolvePending(false)
   209 {
   210   MOZ_ASSERT(mGlobal);
   212   mozilla::HoldJSObjects(this);
   213   SetIsDOMBinding();
   214 }
   216 Promise::~Promise()
   217 {
   218   MaybeReportRejectedOnce();
   219   mozilla::DropJSObjects(this);
   220 }
   222 JSObject*
   223 Promise::WrapObject(JSContext* aCx)
   224 {
   225   return PromiseBinding::Wrap(aCx, this);
   226 }
   228 JSObject*
   229 Promise::GetOrCreateWrapper(JSContext* aCx)
   230 {
   231   if (JSObject* wrapper = GetWrapper()) {
   232     return wrapper;
   233   }
   235   nsIGlobalObject* global = GetParentObject();
   236   MOZ_ASSERT(global);
   238   JS::Rooted<JSObject*> scope(aCx, global->GetGlobalJSObject());
   239   if (!scope) {
   240     JS_ReportError(aCx, "can't get scope");
   241     return nullptr;
   242   }
   244   JSAutoCompartment ac(aCx, scope);
   246   JS::Rooted<JS::Value> val(aCx);
   247   if (!WrapNewBindingObject(aCx, this, &val)) {
   248     MOZ_ASSERT(JS_IsExceptionPending(aCx));
   249     return nullptr;
   250   }
   252   return GetWrapper();
   253 }
   255 void
   256 Promise::MaybeResolve(JSContext* aCx,
   257                       JS::Handle<JS::Value> aValue)
   258 {
   259   MaybeResolveInternal(aCx, aValue);
   260 }
   262 void
   263 Promise::MaybeReject(JSContext* aCx,
   264                      JS::Handle<JS::Value> aValue)
   265 {
   266   MaybeRejectInternal(aCx, aValue);
   267 }
   269 enum {
   270   SLOT_PROMISE = 0,
   271   SLOT_DATA
   272 };
   274 /* static */ bool
   275 Promise::JSCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
   276 {
   277   JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
   279   JS::Rooted<JS::Value> v(aCx,
   280                           js::GetFunctionNativeReserved(&args.callee(),
   281                                                         SLOT_PROMISE));
   282   MOZ_ASSERT(v.isObject());
   284   Promise* promise;
   285   if (NS_FAILED(UNWRAP_OBJECT(Promise, &v.toObject(), promise))) {
   286     return Throw(aCx, NS_ERROR_UNEXPECTED);
   287   }
   289   v = js::GetFunctionNativeReserved(&args.callee(), SLOT_DATA);
   290   PromiseCallback::Task task = static_cast<PromiseCallback::Task>(v.toInt32());
   292   if (task == PromiseCallback::Resolve) {
   293     promise->MaybeResolveInternal(aCx, args.get(0));
   294   } else {
   295     promise->MaybeRejectInternal(aCx, args.get(0));
   296   }
   298   return true;
   299 }
   301 /*
   302  * Utilities for thenable callbacks.
   303  *
   304  * A thenable is a { then: function(resolve, reject) { } }.
   305  * `then` is called with a resolve and reject callback pair.
   306  * Since only one of these should be called at most once (first call wins), the
   307  * two keep a reference to each other in SLOT_DATA. When either of them is
   308  * called, the references are cleared. Further calls are ignored.
   309  */
   310 namespace {
   311 void
   312 LinkThenableCallables(JSContext* aCx, JS::Handle<JSObject*> aResolveFunc,
   313                       JS::Handle<JSObject*> aRejectFunc)
   314 {
   315   js::SetFunctionNativeReserved(aResolveFunc, SLOT_DATA,
   316                                 JS::ObjectValue(*aRejectFunc));
   317   js::SetFunctionNativeReserved(aRejectFunc, SLOT_DATA,
   318                                 JS::ObjectValue(*aResolveFunc));
   319 }
   321 /*
   322  * Returns false if callback was already called before, otherwise breaks the
   323  * links and returns true.
   324  */
   325 bool
   326 MarkAsCalledIfNotCalledBefore(JSContext* aCx, JS::Handle<JSObject*> aFunc)
   327 {
   328   JS::Value otherFuncVal = js::GetFunctionNativeReserved(aFunc, SLOT_DATA);
   330   if (!otherFuncVal.isObject()) {
   331     return false;
   332   }
   334   JSObject* otherFuncObj = &otherFuncVal.toObject();
   335   MOZ_ASSERT(js::GetFunctionNativeReserved(otherFuncObj, SLOT_DATA).isObject());
   337   // Break both references.
   338   js::SetFunctionNativeReserved(aFunc, SLOT_DATA, JS::UndefinedValue());
   339   js::SetFunctionNativeReserved(otherFuncObj, SLOT_DATA, JS::UndefinedValue());
   341   return true;
   342 }
   344 Promise*
   345 GetPromise(JSContext* aCx, JS::Handle<JSObject*> aFunc)
   346 {
   347   JS::Value promiseVal = js::GetFunctionNativeReserved(aFunc, SLOT_PROMISE);
   349   MOZ_ASSERT(promiseVal.isObject());
   351   Promise* promise;
   352   UNWRAP_OBJECT(Promise, &promiseVal.toObject(), promise);
   353   return promise;
   354 }
   355 };
   357 /*
   358  * Common bits of (JSCallbackThenableResolver/JSCallbackThenableRejecter).
   359  * Resolves/rejects the Promise if it is ok to do so, based on whether either of
   360  * the callbacks have been called before or not.
   361  */
   362 /* static */ bool
   363 Promise::ThenableResolverCommon(JSContext* aCx, uint32_t aTask,
   364                                 unsigned aArgc, JS::Value* aVp)
   365 {
   366   JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
   367   JS::Rooted<JSObject*> thisFunc(aCx, &args.callee());
   368   if (!MarkAsCalledIfNotCalledBefore(aCx, thisFunc)) {
   369     // A function from this pair has been called before.
   370     return true;
   371   }
   373   Promise* promise = GetPromise(aCx, thisFunc);
   374   MOZ_ASSERT(promise);
   376   if (aTask == PromiseCallback::Resolve) {
   377     promise->ResolveInternal(aCx, args.get(0), SyncTask);
   378   } else {
   379     promise->RejectInternal(aCx, args.get(0), SyncTask);
   380   }
   381   return true;
   382 }
   384 /* static */ bool
   385 Promise::JSCallbackThenableResolver(JSContext* aCx,
   386                                     unsigned aArgc, JS::Value* aVp)
   387 {
   388   return ThenableResolverCommon(aCx, PromiseCallback::Resolve, aArgc, aVp);
   389 }
   391 /* static */ bool
   392 Promise::JSCallbackThenableRejecter(JSContext* aCx,
   393                                     unsigned aArgc, JS::Value* aVp)
   394 {
   395   return ThenableResolverCommon(aCx, PromiseCallback::Reject, aArgc, aVp);
   396 }
   398 /* static */ JSObject*
   399 Promise::CreateFunction(JSContext* aCx, JSObject* aParent, Promise* aPromise,
   400                         int32_t aTask)
   401 {
   402   JSFunction* func = js::NewFunctionWithReserved(aCx, JSCallback,
   403                                                  1 /* nargs */, 0 /* flags */,
   404                                                  aParent, nullptr);
   405   if (!func) {
   406     return nullptr;
   407   }
   409   JS::Rooted<JSObject*> obj(aCx, JS_GetFunctionObject(func));
   411   JS::Rooted<JS::Value> promiseObj(aCx);
   412   if (!dom::WrapNewBindingObject(aCx, aPromise, &promiseObj)) {
   413     return nullptr;
   414   }
   416   js::SetFunctionNativeReserved(obj, SLOT_PROMISE, promiseObj);
   417   js::SetFunctionNativeReserved(obj, SLOT_DATA, JS::Int32Value(aTask));
   419   return obj;
   420 }
   422 /* static */ JSObject*
   423 Promise::CreateThenableFunction(JSContext* aCx, Promise* aPromise, uint32_t aTask)
   424 {
   425   JSNative whichFunc =
   426     aTask == PromiseCallback::Resolve ? JSCallbackThenableResolver :
   427                                         JSCallbackThenableRejecter ;
   429   JSFunction* func = js::NewFunctionWithReserved(aCx, whichFunc,
   430                                                  1 /* nargs */, 0 /* flags */,
   431                                                  nullptr, nullptr);
   432   if (!func) {
   433     return nullptr;
   434   }
   436   JS::Rooted<JSObject*> obj(aCx, JS_GetFunctionObject(func));
   438   JS::Rooted<JS::Value> promiseObj(aCx);
   439   if (!dom::WrapNewBindingObject(aCx, aPromise, &promiseObj)) {
   440     return nullptr;
   441   }
   443   js::SetFunctionNativeReserved(obj, SLOT_PROMISE, promiseObj);
   445   return obj;
   446 }
   448 /* static */ already_AddRefed<Promise>
   449 Promise::Constructor(const GlobalObject& aGlobal,
   450                      PromiseInit& aInit, ErrorResult& aRv)
   451 {
   452   JSContext* cx = aGlobal.GetContext();
   454   nsCOMPtr<nsIGlobalObject> global;
   455   global = do_QueryInterface(aGlobal.GetAsSupports());
   456   if (!global) {
   457     aRv.Throw(NS_ERROR_UNEXPECTED);
   458     return nullptr;
   459   }
   461   nsRefPtr<Promise> promise = new Promise(global);
   463   JS::Rooted<JSObject*> resolveFunc(cx,
   464                                     CreateFunction(cx, aGlobal.Get(), promise,
   465                                                    PromiseCallback::Resolve));
   466   if (!resolveFunc) {
   467     aRv.Throw(NS_ERROR_UNEXPECTED);
   468     return nullptr;
   469   }
   471   JS::Rooted<JSObject*> rejectFunc(cx,
   472                                    CreateFunction(cx, aGlobal.Get(), promise,
   473                                                   PromiseCallback::Reject));
   474   if (!rejectFunc) {
   475     aRv.Throw(NS_ERROR_UNEXPECTED);
   476     return nullptr;
   477   }
   479   aInit.Call(resolveFunc, rejectFunc, aRv, CallbackObject::eRethrowExceptions);
   480   aRv.WouldReportJSException();
   482   if (aRv.IsJSException()) {
   483     JS::Rooted<JS::Value> value(cx);
   484     aRv.StealJSException(cx, &value);
   486     // we want the same behavior as this JS implementation:
   487     // function Promise(arg) { try { arg(a, b); } catch (e) { this.reject(e); }}
   488     if (!JS_WrapValue(cx, &value)) {
   489       aRv.Throw(NS_ERROR_UNEXPECTED);
   490       return nullptr;
   491     }
   493     promise->MaybeRejectInternal(cx, value);
   494   }
   496   return promise.forget();
   497 }
   499 /* static */ already_AddRefed<Promise>
   500 Promise::Resolve(const GlobalObject& aGlobal, JSContext* aCx,
   501                  JS::Handle<JS::Value> aValue, ErrorResult& aRv)
   502 {
   503   // If a Promise was passed, just return it.
   504   if (aValue.isObject()) {
   505     JS::Rooted<JSObject*> valueObj(aCx, &aValue.toObject());
   506     Promise* nextPromise;
   507     nsresult rv = UNWRAP_OBJECT(Promise, valueObj, nextPromise);
   509     if (NS_SUCCEEDED(rv)) {
   510       nsRefPtr<Promise> addRefed = nextPromise;
   511       return addRefed.forget();
   512     }
   513   }
   515   nsCOMPtr<nsIGlobalObject> global =
   516     do_QueryInterface(aGlobal.GetAsSupports());
   517   if (!global) {
   518     aRv.Throw(NS_ERROR_UNEXPECTED);
   519     return nullptr;
   520   }
   522   return Resolve(global, aCx, aValue, aRv);
   523 }
   525 /* static */ already_AddRefed<Promise>
   526 Promise::Resolve(nsIGlobalObject* aGlobal, JSContext* aCx,
   527                  JS::Handle<JS::Value> aValue, ErrorResult& aRv)
   528 {
   529   nsRefPtr<Promise> promise = new Promise(aGlobal);
   531   promise->MaybeResolveInternal(aCx, aValue);
   532   return promise.forget();
   533 }
   535 /* static */ already_AddRefed<Promise>
   536 Promise::Reject(const GlobalObject& aGlobal, JSContext* aCx,
   537                 JS::Handle<JS::Value> aValue, ErrorResult& aRv)
   538 {
   539   nsCOMPtr<nsIGlobalObject> global =
   540     do_QueryInterface(aGlobal.GetAsSupports());
   541   if (!global) {
   542     aRv.Throw(NS_ERROR_UNEXPECTED);
   543     return nullptr;
   544   }
   546   return Reject(global, aCx, aValue, aRv);
   547 }
   549 /* static */ already_AddRefed<Promise>
   550 Promise::Reject(nsIGlobalObject* aGlobal, JSContext* aCx,
   551                 JS::Handle<JS::Value> aValue, ErrorResult& aRv)
   552 {
   553   nsRefPtr<Promise> promise = new Promise(aGlobal);
   555   promise->MaybeRejectInternal(aCx, aValue);
   556   return promise.forget();
   557 }
   559 already_AddRefed<Promise>
   560 Promise::Then(JSContext* aCx, AnyCallback* aResolveCallback,
   561               AnyCallback* aRejectCallback)
   562 {
   563   nsRefPtr<Promise> promise = new Promise(GetParentObject());
   565   JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
   567   nsRefPtr<PromiseCallback> resolveCb =
   568     PromiseCallback::Factory(promise, global, aResolveCallback,
   569                              PromiseCallback::Resolve);
   571   nsRefPtr<PromiseCallback> rejectCb =
   572     PromiseCallback::Factory(promise, global, aRejectCallback,
   573                              PromiseCallback::Reject);
   575   AppendCallbacks(resolveCb, rejectCb);
   577   return promise.forget();
   578 }
   580 already_AddRefed<Promise>
   581 Promise::Catch(JSContext* aCx, AnyCallback* aRejectCallback)
   582 {
   583   nsRefPtr<AnyCallback> resolveCb;
   584   return Then(aCx, resolveCb, aRejectCallback);
   585 }
   587 /**
   588  * The CountdownHolder class encapsulates Promise.all countdown functions and
   589  * the countdown holder parts of the Promises spec. It maintains the result
   590  * array and AllResolveHandlers use SetValue() to set the array indices.
   591  */
   592 class CountdownHolder MOZ_FINAL : public nsISupports
   593 {
   594 public:
   595   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   596   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CountdownHolder)
   598   CountdownHolder(const GlobalObject& aGlobal, Promise* aPromise,
   599                   uint32_t aCountdown)
   600     : mPromise(aPromise), mCountdown(aCountdown)
   601   {
   602     MOZ_ASSERT(aCountdown != 0);
   603     JSContext* cx = aGlobal.GetContext();
   605     // The only time aGlobal.GetContext() and aGlobal.Get() are not
   606     // same-compartment is when we're called via Xrays, and in that situation we
   607     // in fact want to create the array in the callee compartment
   609     JSAutoCompartment ac(cx, aGlobal.Get());
   610     mValues = JS_NewArrayObject(cx, aCountdown);
   611     mozilla::HoldJSObjects(this);
   612   }
   614   ~CountdownHolder()
   615   {
   616     mozilla::DropJSObjects(this);
   617   }
   619   void SetValue(uint32_t index, const JS::Handle<JS::Value> aValue)
   620   {
   621     MOZ_ASSERT(mCountdown > 0);
   623     ThreadsafeAutoSafeJSContext cx;
   624     JSAutoCompartment ac(cx, mValues);
   625     {
   627       AutoDontReportUncaught silenceReporting(cx);
   628       JS::Rooted<JS::Value> value(cx, aValue);
   629       if (!JS_WrapValue(cx, &value) ||
   630           !JS_DefineElement(cx, mValues, index, value, nullptr, nullptr,
   631                             JSPROP_ENUMERATE)) {
   632         MOZ_ASSERT(JS_IsExceptionPending(cx));
   633         JS::Rooted<JS::Value> exn(cx);
   634         JS_GetPendingException(cx, &exn);
   636         mPromise->MaybeReject(cx, exn);
   637       }
   638     }
   640     --mCountdown;
   641     if (mCountdown == 0) {
   642       JS::Rooted<JS::Value> result(cx, JS::ObjectValue(*mValues));
   643       mPromise->MaybeResolve(cx, result);
   644     }
   645   }
   647 private:
   648   nsRefPtr<Promise> mPromise;
   649   uint32_t mCountdown;
   650   JS::Heap<JSObject*> mValues;
   651 };
   653 NS_IMPL_CYCLE_COLLECTING_ADDREF(CountdownHolder)
   654 NS_IMPL_CYCLE_COLLECTING_RELEASE(CountdownHolder)
   655 NS_IMPL_CYCLE_COLLECTION_CLASS(CountdownHolder)
   657 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CountdownHolder)
   658   NS_INTERFACE_MAP_ENTRY(nsISupports)
   659 NS_INTERFACE_MAP_END
   661 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CountdownHolder)
   662   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mValues)
   663 NS_IMPL_CYCLE_COLLECTION_TRACE_END
   665 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CountdownHolder)
   666   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
   667   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
   668 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
   670 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CountdownHolder)
   671   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
   672   tmp->mValues = nullptr;
   673 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
   675 /**
   676  * An AllResolveHandler is the per-promise part of the Promise.all() algorithm.
   677  * Every Promise in the handler is handed an instance of this as a resolution
   678  * handler and it sets the relevant index in the CountdownHolder.
   679  */
   680 class AllResolveHandler MOZ_FINAL : public PromiseNativeHandler
   681 {
   682 public:
   683   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   684   NS_DECL_CYCLE_COLLECTION_CLASS(AllResolveHandler)
   686   AllResolveHandler(CountdownHolder* aHolder, uint32_t aIndex)
   687     : mCountdownHolder(aHolder), mIndex(aIndex)
   688   {
   689     MOZ_ASSERT(aHolder);
   690   }
   692   ~AllResolveHandler()
   693   {
   694   }
   696   void
   697   ResolvedCallback(JS::Handle<JS::Value> aValue)
   698   {
   699     mCountdownHolder->SetValue(mIndex, aValue);
   700   }
   702   void
   703   RejectedCallback(JS::Handle<JS::Value> aValue)
   704   {
   705     // Should never be attached to Promise as a reject handler.
   706     MOZ_ASSERT(false, "AllResolveHandler should never be attached to a Promise's reject handler!");
   707   }
   709 private:
   710   nsRefPtr<CountdownHolder> mCountdownHolder;
   711   uint32_t mIndex;
   712 };
   714 NS_IMPL_CYCLE_COLLECTING_ADDREF(AllResolveHandler)
   715 NS_IMPL_CYCLE_COLLECTING_RELEASE(AllResolveHandler)
   717 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AllResolveHandler)
   718 NS_INTERFACE_MAP_END_INHERITING(PromiseNativeHandler)
   720 NS_IMPL_CYCLE_COLLECTION(AllResolveHandler, mCountdownHolder)
   722 /* static */ already_AddRefed<Promise>
   723 Promise::All(const GlobalObject& aGlobal, JSContext* aCx,
   724              const Sequence<JS::Value>& aIterable, ErrorResult& aRv)
   725 {
   726   nsCOMPtr<nsIGlobalObject> global =
   727     do_QueryInterface(aGlobal.GetAsSupports());
   728   if (!global) {
   729     aRv.Throw(NS_ERROR_UNEXPECTED);
   730     return nullptr;
   731   }
   733   if (aIterable.Length() == 0) {
   734     JS::Rooted<JSObject*> empty(aCx, JS_NewArrayObject(aCx, 0));
   735     if (!empty) {
   736       aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
   737       return nullptr;
   738     }
   739     JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*empty));
   740     return Promise::Resolve(aGlobal, aCx, value, aRv);
   741   }
   743   nsRefPtr<Promise> promise = new Promise(global);
   744   nsRefPtr<CountdownHolder> holder =
   745     new CountdownHolder(aGlobal, promise, aIterable.Length());
   747   JS::Rooted<JSObject*> obj(aCx, JS::CurrentGlobalOrNull(aCx));
   748   if (!obj) {
   749     aRv.Throw(NS_ERROR_UNEXPECTED);
   750     return nullptr;
   751   }
   753   nsRefPtr<PromiseCallback> rejectCb = new RejectPromiseCallback(promise, obj);
   755   for (uint32_t i = 0; i < aIterable.Length(); ++i) {
   756     JS::Rooted<JS::Value> value(aCx, aIterable.ElementAt(i));
   757     nsRefPtr<Promise> nextPromise = Promise::Resolve(aGlobal, aCx, value, aRv);
   759     MOZ_ASSERT(!aRv.Failed());
   761     nsRefPtr<PromiseNativeHandler> resolveHandler =
   762       new AllResolveHandler(holder, i);
   764     nsRefPtr<PromiseCallback> resolveCb =
   765       new NativePromiseCallback(resolveHandler, Resolved);
   766     // Every promise gets its own resolve callback, which will set the right
   767     // index in the array to the resolution value.
   768     nextPromise->AppendCallbacks(resolveCb, rejectCb);
   769   }
   771   return promise.forget();
   772 }
   774 /* static */ already_AddRefed<Promise>
   775 Promise::Race(const GlobalObject& aGlobal, JSContext* aCx,
   776               const Sequence<JS::Value>& aIterable, ErrorResult& aRv)
   777 {
   778   nsCOMPtr<nsIGlobalObject> global =
   779     do_QueryInterface(aGlobal.GetAsSupports());
   780   if (!global) {
   781     aRv.Throw(NS_ERROR_UNEXPECTED);
   782     return nullptr;
   783   }
   785   JS::Rooted<JSObject*> obj(aCx, JS::CurrentGlobalOrNull(aCx));
   786   if (!obj) {
   787     aRv.Throw(NS_ERROR_UNEXPECTED);
   788     return nullptr;
   789   }
   791   nsRefPtr<Promise> promise = new Promise(global);
   793   nsRefPtr<PromiseCallback> resolveCb =
   794     new ResolvePromiseCallback(promise, obj);
   796   nsRefPtr<PromiseCallback> rejectCb = new RejectPromiseCallback(promise, obj);
   798   for (uint32_t i = 0; i < aIterable.Length(); ++i) {
   799     JS::Rooted<JS::Value> value(aCx, aIterable.ElementAt(i));
   800     nsRefPtr<Promise> nextPromise = Promise::Resolve(aGlobal, aCx, value, aRv);
   801     // According to spec, Resolve can throw, but our implementation never does.
   802     // Well it does when window isn't passed on the main thread, but that is an
   803     // implementation detail which should never be reached since we are checking
   804     // for window above. Remove this when subclassing is supported.
   805     MOZ_ASSERT(!aRv.Failed());
   806     nextPromise->AppendCallbacks(resolveCb, rejectCb);
   807   }
   809   return promise.forget();
   810 }
   812 void
   813 Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable)
   814 {
   815   nsRefPtr<PromiseCallback> resolveCb =
   816     new NativePromiseCallback(aRunnable, Resolved);
   818   nsRefPtr<PromiseCallback> rejectCb =
   819     new NativePromiseCallback(aRunnable, Rejected);
   821   AppendCallbacks(resolveCb, rejectCb);
   822 }
   824 void
   825 Promise::AppendCallbacks(PromiseCallback* aResolveCallback,
   826                          PromiseCallback* aRejectCallback)
   827 {
   828   if (aResolveCallback) {
   829     mResolveCallbacks.AppendElement(aResolveCallback);
   830   }
   832   if (aRejectCallback) {
   833     mHadRejectCallback = true;
   834     mRejectCallbacks.AppendElement(aRejectCallback);
   836     // Now that there is a callback, we don't need to report anymore.
   837     RemoveFeature();
   838   }
   840   // If promise's state is resolved, queue a task to process our resolve
   841   // callbacks with promise's result. If promise's state is rejected, queue a
   842   // task to process our reject callbacks with promise's result.
   843   if (mState != Pending && !mTaskPending) {
   844     if (MOZ_LIKELY(NS_IsMainThread())) {
   845       nsRefPtr<PromiseTask> task = new PromiseTask(this);
   846       NS_DispatchToCurrentThread(task);
   847     } else {
   848       WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
   849       MOZ_ASSERT(worker);
   850       nsRefPtr<WorkerPromiseTask> task = new WorkerPromiseTask(worker, this);
   851       task->Dispatch(worker->GetJSContext());
   852     }
   853     mTaskPending = true;
   854   }
   855 }
   857 void
   858 Promise::RunTask()
   859 {
   860   MOZ_ASSERT(mState != Pending);
   862   nsTArray<nsRefPtr<PromiseCallback>> callbacks;
   863   callbacks.SwapElements(mState == Resolved ? mResolveCallbacks
   864                                             : mRejectCallbacks);
   865   mResolveCallbacks.Clear();
   866   mRejectCallbacks.Clear();
   868   ThreadsafeAutoJSContext cx; // Just for rooting.
   869   JS::Rooted<JS::Value> value(cx, mResult);
   871   for (uint32_t i = 0; i < callbacks.Length(); ++i) {
   872     callbacks[i]->Call(value);
   873   }
   874 }
   876 void
   877 Promise::MaybeReportRejected()
   878 {
   879   if (mState != Rejected || mHadRejectCallback || mResult.isUndefined()) {
   880     return;
   881   }
   883   if (!mResult.isObject()) {
   884     return;
   885   }
   886   ThreadsafeAutoJSContext cx;
   887   JS::Rooted<JSObject*> obj(cx, &mResult.toObject());
   888   JSAutoCompartment ac(cx, obj);
   889   JSErrorReport* report = JS_ErrorFromException(cx, obj);
   890   if (!report) {
   891     return;
   892   }
   894   // Remains null in case of worker.
   895   nsCOMPtr<nsPIDOMWindow> win;
   896   bool isChromeError = false;
   898   if (MOZ_LIKELY(NS_IsMainThread())) {
   899     win =
   900       do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(obj));
   901     nsIPrincipal* principal = nsContentUtils::GetObjectPrincipal(obj);
   902     isChromeError = nsContentUtils::IsSystemPrincipal(principal);
   903   } else {
   904     WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
   905     MOZ_ASSERT(worker);
   906     isChromeError = worker->IsChromeWorker();
   907   }
   909   // Now post an event to do the real reporting async
   910   // Since Promises preserve their wrapper, it is essential to nsRefPtr<> the
   911   // AsyncErrorReporter, otherwise if the call to DispatchToMainThread fails, it
   912   // will leak. See Bug 958684.
   913   nsRefPtr<AsyncErrorReporter> r =
   914     new AsyncErrorReporter(JS_GetObjectRuntime(obj),
   915                            report,
   916                            nullptr,
   917                            isChromeError,
   918                            win);
   919   NS_DispatchToMainThread(r);
   920 }
   922 void
   923 Promise::MaybeResolveInternal(JSContext* aCx,
   924                               JS::Handle<JS::Value> aValue,
   925                               PromiseTaskSync aAsynchronous)
   926 {
   927   if (mResolvePending) {
   928     return;
   929   }
   931   ResolveInternal(aCx, aValue, aAsynchronous);
   932 }
   934 void
   935 Promise::MaybeRejectInternal(JSContext* aCx,
   936                              JS::Handle<JS::Value> aValue,
   937                              PromiseTaskSync aAsynchronous)
   938 {
   939   if (mResolvePending) {
   940     return;
   941   }
   943   RejectInternal(aCx, aValue, aAsynchronous);
   944 }
   946 void
   947 Promise::HandleException(JSContext* aCx)
   948 {
   949   JS::Rooted<JS::Value> exn(aCx);
   950   if (JS_GetPendingException(aCx, &exn)) {
   951     JS_ClearPendingException(aCx);
   952     RejectInternal(aCx, exn, SyncTask);
   953   }
   954 }
   956 void
   957 Promise::ResolveInternal(JSContext* aCx,
   958                          JS::Handle<JS::Value> aValue,
   959                          PromiseTaskSync aAsynchronous)
   960 {
   961   mResolvePending = true;
   963   if (aValue.isObject()) {
   964     AutoDontReportUncaught silenceReporting(aCx);
   965     JS::Rooted<JSObject*> valueObj(aCx, &aValue.toObject());
   967     // Thenables.
   968     JS::Rooted<JS::Value> then(aCx);
   969     if (!JS_GetProperty(aCx, valueObj, "then", &then)) {
   970       HandleException(aCx);
   971       return;
   972     }
   974     if (then.isObject() && JS_ObjectIsCallable(aCx, &then.toObject())) {
   975       JS::Rooted<JSObject*> resolveFunc(aCx,
   976         CreateThenableFunction(aCx, this, PromiseCallback::Resolve));
   978       if (!resolveFunc) {
   979         HandleException(aCx);
   980         return;
   981       }
   983       JS::Rooted<JSObject*> rejectFunc(aCx,
   984         CreateThenableFunction(aCx, this, PromiseCallback::Reject));
   985       if (!rejectFunc) {
   986         HandleException(aCx);
   987         return;
   988       }
   990       LinkThenableCallables(aCx, resolveFunc, rejectFunc);
   992       JS::Rooted<JSObject*> thenObj(aCx, &then.toObject());
   993       nsRefPtr<PromiseInit> thenCallback =
   994         new PromiseInit(thenObj, mozilla::dom::GetIncumbentGlobal());
   996       ErrorResult rv;
   997       thenCallback->Call(valueObj, resolveFunc, rejectFunc,
   998                          rv, CallbackObject::eRethrowExceptions);
   999       rv.WouldReportJSException();
  1001       if (rv.IsJSException()) {
  1002         JS::Rooted<JS::Value> exn(aCx);
  1003         rv.StealJSException(aCx, &exn);
  1005         bool couldMarkAsCalled = MarkAsCalledIfNotCalledBefore(aCx, resolveFunc);
  1007         // If we could mark as called, neither of the callbacks had been called
  1008         // when the exception was thrown. So we can reject the Promise.
  1009         if (couldMarkAsCalled) {
  1010           bool ok = JS_WrapValue(aCx, &exn);
  1011           MOZ_ASSERT(ok);
  1012           if (!ok) {
  1013             NS_WARNING("Failed to wrap value into the right compartment.");
  1016           RejectInternal(aCx, exn, Promise::SyncTask);
  1018         // At least one of resolveFunc or rejectFunc have been called, so ignore
  1019         // the exception. FIXME(nsm): This should be reported to the error
  1020         // console though, for debugging.
  1023       return;
  1027   // If the synchronous flag is set, process our resolve callbacks with
  1028   // value. Otherwise, the synchronous flag is unset, queue a task to process
  1029   // own resolve callbacks with value. Otherwise, the synchronous flag is
  1030   // unset, queue a task to process our resolve callbacks with value.
  1031   RunResolveTask(aValue, Resolved, aAsynchronous);
  1034 void
  1035 Promise::RejectInternal(JSContext* aCx,
  1036                         JS::Handle<JS::Value> aValue,
  1037                         PromiseTaskSync aAsynchronous)
  1039   mResolvePending = true;
  1041   // If the synchronous flag is set, process our reject callbacks with
  1042   // value. Otherwise, the synchronous flag is unset, queue a task to process
  1043   // promise's reject callbacks with value.
  1044   RunResolveTask(aValue, Rejected, aAsynchronous);
  1047 void
  1048 Promise::RunResolveTask(JS::Handle<JS::Value> aValue,
  1049                         PromiseState aState,
  1050                         PromiseTaskSync aAsynchronous)
  1052   // If the synchronous flag is unset, queue a task to process our
  1053   // accept callbacks with value.
  1054   if (aAsynchronous == AsyncTask) {
  1055     if (MOZ_LIKELY(NS_IsMainThread())) {
  1056       nsRefPtr<PromiseResolverTask> task =
  1057         new PromiseResolverTask(this, aValue, aState);
  1058       NS_DispatchToCurrentThread(task);
  1059     } else {
  1060       WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
  1061       MOZ_ASSERT(worker);
  1062       nsRefPtr<WorkerPromiseResolverTask> task =
  1063         new WorkerPromiseResolverTask(worker, this, aValue, aState);
  1064       task->Dispatch(worker->GetJSContext());
  1066     return;
  1069   // Promise.all() or Promise.race() implementations will repeatedly call
  1070   // Resolve/RejectInternal rather than using the Maybe... forms. Stop SetState
  1071   // from asserting.
  1072   if (mState != Pending) {
  1073     return;
  1076   SetResult(aValue);
  1077   SetState(aState);
  1079   // If the Promise was rejected, and there is no reject handler already setup,
  1080   // watch for thread shutdown.
  1081   if (aState == PromiseState::Rejected &&
  1082       !mHadRejectCallback &&
  1083       !NS_IsMainThread()) {
  1084     WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
  1085     MOZ_ASSERT(worker);
  1086     worker->AssertIsOnWorkerThread();
  1088     mFeature = new PromiseReportRejectFeature(this);
  1089     if (NS_WARN_IF(!worker->AddFeature(worker->GetJSContext(), mFeature))) {
  1090       // To avoid a false RemoveFeature().
  1091       mFeature = nullptr;
  1092       // Worker is shutting down, report rejection immediately since it is
  1093       // unlikely that reject callbacks will be added after this point.
  1094       MaybeReportRejectedOnce();
  1098   RunTask();
  1101 void
  1102 Promise::RemoveFeature()
  1104   if (mFeature) {
  1105     WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
  1106     MOZ_ASSERT(worker);
  1107     worker->RemoveFeature(worker->GetJSContext(), mFeature);
  1108     mFeature = nullptr;
  1112 bool
  1113 PromiseReportRejectFeature::Notify(JSContext* aCx, workers::Status aStatus)
  1115   MOZ_ASSERT(aStatus > workers::Running);
  1116   mPromise->MaybeReportRejectedOnce();
  1117   // After this point, `this` has been deleted by RemoveFeature!
  1118   return true;
  1121 } // namespace dom
  1122 } // namespace mozilla

mercurial