Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
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.");
1014 }
1016 RejectInternal(aCx, exn, Promise::SyncTask);
1017 }
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.
1021 }
1023 return;
1024 }
1025 }
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);
1032 }
1034 void
1035 Promise::RejectInternal(JSContext* aCx,
1036 JS::Handle<JS::Value> aValue,
1037 PromiseTaskSync aAsynchronous)
1038 {
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);
1045 }
1047 void
1048 Promise::RunResolveTask(JS::Handle<JS::Value> aValue,
1049 PromiseState aState,
1050 PromiseTaskSync aAsynchronous)
1051 {
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());
1065 }
1066 return;
1067 }
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;
1074 }
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();
1095 }
1096 }
1098 RunTask();
1099 }
1101 void
1102 Promise::RemoveFeature()
1103 {
1104 if (mFeature) {
1105 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
1106 MOZ_ASSERT(worker);
1107 worker->RemoveFeature(worker->GetJSContext(), mFeature);
1108 mFeature = nullptr;
1109 }
1110 }
1112 bool
1113 PromiseReportRejectFeature::Notify(JSContext* aCx, workers::Status aStatus)
1114 {
1115 MOZ_ASSERT(aStatus > workers::Running);
1116 mPromise->MaybeReportRejectedOnce();
1117 // After this point, `this` has been deleted by RemoveFeature!
1118 return true;
1119 }
1121 } // namespace dom
1122 } // namespace mozilla