dom/promise/Promise.cpp

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

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

mercurial