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/BindingUtils.h"
11 #include "mozilla/dom/DOMError.h"
12 #include "mozilla/dom/OwningNonNull.h"
13 #include "mozilla/dom/PromiseBinding.h"
14 #include "mozilla/dom/ScriptSettings.h"
15 #include "mozilla/CycleCollectedJSRuntime.h"
16 #include "mozilla/Preferences.h"
17 #include "PromiseCallback.h"
18 #include "PromiseNativeHandler.h"
19 #include "PromiseWorkerProxy.h"
20 #include "nsContentUtils.h"
21 #include "WorkerPrivate.h"
22 #include "WorkerRunnable.h"
23 #include "nsJSPrincipals.h"
24 #include "nsJSUtils.h"
25 #include "nsPIDOMWindow.h"
26 #include "nsJSEnvironment.h"
27 #include "nsIScriptObjectPrincipal.h"
28 #include "xpcpublic.h"
29 #include "nsGlobalWindow.h"
34 using namespace workers
;
36 NS_IMPL_ISUPPORTS0(PromiseNativeHandler
)
40 // This class processes the promise's callbacks with promise's result.
41 class PromiseTask MOZ_FINAL
: public nsRunnable
44 PromiseTask(Promise
* aPromise
)
48 MOZ_COUNT_CTOR(PromiseTask
);
54 NS_ASSERT_OWNINGTHREAD(PromiseTask
);
55 MOZ_COUNT_DTOR(PromiseTask
);
62 NS_ASSERT_OWNINGTHREAD(PromiseTask
);
63 mPromise
->mTaskPending
= false;
69 nsRefPtr
<Promise
> mPromise
;
73 // This class processes the promise's callbacks with promise's result.
74 class PromiseResolverTask MOZ_FINAL
: public nsRunnable
77 PromiseResolverTask(Promise
* aPromise
,
78 JS::Handle
<JS::Value
> aValue
,
79 Promise::PromiseState aState
)
81 , mValue(CycleCollectedJSRuntime::Get()->Runtime(), aValue
)
85 MOZ_ASSERT(mState
!= Promise::Pending
);
86 MOZ_COUNT_CTOR(PromiseResolverTask
);
90 ~PromiseResolverTask()
92 NS_ASSERT_OWNINGTHREAD(PromiseResolverTask
);
93 MOZ_COUNT_DTOR(PromiseResolverTask
);
99 NS_ASSERT_OWNINGTHREAD(PromiseResolverTask
);
100 mPromise
->RunResolveTask(
101 JS::Handle
<JS::Value
>::fromMarkedLocation(mValue
.address()),
102 mState
, Promise::SyncTask
);
107 nsRefPtr
<Promise
> mPromise
;
108 JS::PersistentRooted
<JS::Value
> mValue
;
109 Promise::PromiseState mState
;
110 NS_DECL_OWNINGTHREAD
;
119 * Utilities for thenable callbacks.
121 * A thenable is a { then: function(resolve, reject) { } }.
122 * `then` is called with a resolve and reject callback pair.
123 * Since only one of these should be called at most once (first call wins), the
124 * two keep a reference to each other in SLOT_DATA. When either of them is
125 * called, the references are cleared. Further calls are ignored.
129 LinkThenableCallables(JSContext
* aCx
, JS::Handle
<JSObject
*> aResolveFunc
,
130 JS::Handle
<JSObject
*> aRejectFunc
)
132 js::SetFunctionNativeReserved(aResolveFunc
, SLOT_DATA
,
133 JS::ObjectValue(*aRejectFunc
));
134 js::SetFunctionNativeReserved(aRejectFunc
, SLOT_DATA
,
135 JS::ObjectValue(*aResolveFunc
));
139 * Returns false if callback was already called before, otherwise breaks the
140 * links and returns true.
143 MarkAsCalledIfNotCalledBefore(JSContext
* aCx
, JS::Handle
<JSObject
*> aFunc
)
145 JS::Value otherFuncVal
= js::GetFunctionNativeReserved(aFunc
, SLOT_DATA
);
147 if (!otherFuncVal
.isObject()) {
151 JSObject
* otherFuncObj
= &otherFuncVal
.toObject();
152 MOZ_ASSERT(js::GetFunctionNativeReserved(otherFuncObj
, SLOT_DATA
).isObject());
154 // Break both references.
155 js::SetFunctionNativeReserved(aFunc
, SLOT_DATA
, JS::UndefinedValue());
156 js::SetFunctionNativeReserved(otherFuncObj
, SLOT_DATA
, JS::UndefinedValue());
162 GetPromise(JSContext
* aCx
, JS::Handle
<JSObject
*> aFunc
)
164 JS::Value promiseVal
= js::GetFunctionNativeReserved(aFunc
, SLOT_PROMISE
);
166 MOZ_ASSERT(promiseVal
.isObject());
169 UNWRAP_OBJECT(Promise
, &promiseVal
.toObject(), promise
);
174 // Main thread runnable to resolve thenables.
175 // Equivalent to the specification's ResolvePromiseViaThenableTask.
176 class ThenableResolverTask MOZ_FINAL
: public nsRunnable
179 ThenableResolverTask(Promise
* aPromise
,
180 JS::Handle
<JSObject
*> aThenable
,
183 , mThenable(CycleCollectedJSRuntime::Get()->Runtime(), aThenable
)
186 MOZ_ASSERT(aPromise
);
187 MOZ_COUNT_CTOR(ThenableResolverTask
);
191 ~ThenableResolverTask()
193 NS_ASSERT_OWNINGTHREAD(ThenableResolverTask
);
194 MOZ_COUNT_DTOR(ThenableResolverTask
);
201 NS_ASSERT_OWNINGTHREAD(ThenableResolverTask
);
202 ThreadsafeAutoJSContext cx
;
203 JS::Rooted
<JSObject
*> wrapper(cx
, mPromise
->GetWrapper());
204 MOZ_ASSERT(wrapper
); // It was preserved!
208 JSAutoCompartment
ac(cx
, wrapper
);
210 JS::Rooted
<JSObject
*> resolveFunc(cx
,
211 mPromise
->CreateThenableFunction(cx
, mPromise
, PromiseCallback::Resolve
));
214 mPromise
->HandleException(cx
);
218 JS::Rooted
<JSObject
*> rejectFunc(cx
,
219 mPromise
->CreateThenableFunction(cx
, mPromise
, PromiseCallback::Reject
));
221 mPromise
->HandleException(cx
);
225 LinkThenableCallables(cx
, resolveFunc
, rejectFunc
);
229 JS::Rooted
<JSObject
*> rootedThenable(cx
, mThenable
);
231 mThen
->Call(rootedThenable
, resolveFunc
, rejectFunc
, rv
,
232 CallbackObject::eRethrowExceptions
);
234 rv
.WouldReportJSException();
235 if (rv
.IsJSException()) {
236 JS::Rooted
<JS::Value
> exn(cx
);
237 rv
.StealJSException(cx
, &exn
);
239 bool couldMarkAsCalled
= MarkAsCalledIfNotCalledBefore(cx
, resolveFunc
);
241 // If we could mark as called, neither of the callbacks had been called
242 // when the exception was thrown. So we can reject the Promise.
243 if (couldMarkAsCalled
) {
244 bool ok
= JS_WrapValue(cx
, &exn
);
247 NS_WARNING("Failed to wrap value into the right compartment.");
250 mPromise
->RejectInternal(cx
, exn
, Promise::SyncTask
);
252 // At least one of resolveFunc or rejectFunc have been called, so ignore
253 // the exception. FIXME(nsm): This should be reported to the error
254 // console though, for debugging.
261 nsRefPtr
<Promise
> mPromise
;
262 JS::PersistentRooted
<JSObject
*> mThenable
;
263 nsRefPtr
<PromiseInit
> mThen
;
264 NS_DECL_OWNINGTHREAD
;
269 NS_IMPL_CYCLE_COLLECTION_CLASS(Promise
)
271 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise
)
272 tmp
->MaybeReportRejectedOnce();
273 NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal
)
274 NS_IMPL_CYCLE_COLLECTION_UNLINK(mResolveCallbacks
)
275 NS_IMPL_CYCLE_COLLECTION_UNLINK(mRejectCallbacks
)
276 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
277 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
279 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Promise
)
280 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal
)
281 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResolveCallbacks
)
282 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRejectCallbacks
)
283 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
284 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
286 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Promise
)
287 NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mResult
)
288 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
289 NS_IMPL_CYCLE_COLLECTION_TRACE_END
291 NS_IMPL_CYCLE_COLLECTING_ADDREF(Promise
)
292 NS_IMPL_CYCLE_COLLECTING_RELEASE(Promise
)
294 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Promise
)
295 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
296 NS_INTERFACE_MAP_ENTRY(nsISupports
)
299 Promise::Promise(nsIGlobalObject
* aGlobal
)
301 , mResult(JS::UndefinedValue())
303 , mTaskPending(false)
304 , mHadRejectCallback(false)
305 , mResolvePending(false)
309 mozilla::HoldJSObjects(this);
315 MaybeReportRejectedOnce();
316 mozilla::DropJSObjects(this);
320 Promise::WrapObject(JSContext
* aCx
)
322 return PromiseBinding::Wrap(aCx
, this);
325 already_AddRefed
<Promise
>
326 Promise::Create(nsIGlobalObject
* aGlobal
, ErrorResult
& aRv
)
329 if (!jsapi
.Init(aGlobal
)) {
330 aRv
.Throw(NS_ERROR_UNEXPECTED
);
333 JSContext
* cx
= jsapi
.cx();
335 nsRefPtr
<Promise
> p
= new Promise(aGlobal
);
337 JS::Rooted
<JS::Value
> ignored(cx
);
338 if (!WrapNewBindingObject(cx
, p
, &ignored
)) {
339 JS_ClearPendingException(cx
);
340 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
344 // Need the .get() bit here to get template deduction working right
345 dom::PreserveWrapper(p
.get());
351 Promise::MaybeResolve(JSContext
* aCx
,
352 JS::Handle
<JS::Value
> aValue
)
354 MaybeResolveInternal(aCx
, aValue
);
358 Promise::MaybeReject(JSContext
* aCx
,
359 JS::Handle
<JS::Value
> aValue
)
361 MaybeRejectInternal(aCx
, aValue
);
365 Promise::JSCallback(JSContext
* aCx
, unsigned aArgc
, JS::Value
* aVp
)
367 JS::CallArgs args
= CallArgsFromVp(aArgc
, aVp
);
369 JS::Rooted
<JS::Value
> v(aCx
,
370 js::GetFunctionNativeReserved(&args
.callee(),
372 MOZ_ASSERT(v
.isObject());
375 if (NS_FAILED(UNWRAP_OBJECT(Promise
, &v
.toObject(), promise
))) {
376 return Throw(aCx
, NS_ERROR_UNEXPECTED
);
379 v
= js::GetFunctionNativeReserved(&args
.callee(), SLOT_DATA
);
380 PromiseCallback::Task task
= static_cast<PromiseCallback::Task
>(v
.toInt32());
382 if (task
== PromiseCallback::Resolve
) {
383 promise
->MaybeResolveInternal(aCx
, args
.get(0));
385 promise
->MaybeRejectInternal(aCx
, args
.get(0));
392 * Common bits of (JSCallbackThenableResolver/JSCallbackThenableRejecter).
393 * Resolves/rejects the Promise if it is ok to do so, based on whether either of
394 * the callbacks have been called before or not.
397 Promise::ThenableResolverCommon(JSContext
* aCx
, uint32_t aTask
,
398 unsigned aArgc
, JS::Value
* aVp
)
400 JS::CallArgs args
= CallArgsFromVp(aArgc
, aVp
);
401 JS::Rooted
<JSObject
*> thisFunc(aCx
, &args
.callee());
402 if (!MarkAsCalledIfNotCalledBefore(aCx
, thisFunc
)) {
403 // A function from this pair has been called before.
407 Promise
* promise
= GetPromise(aCx
, thisFunc
);
410 if (aTask
== PromiseCallback::Resolve
) {
411 promise
->ResolveInternal(aCx
, args
.get(0));
413 promise
->RejectInternal(aCx
, args
.get(0));
419 Promise::JSCallbackThenableResolver(JSContext
* aCx
,
420 unsigned aArgc
, JS::Value
* aVp
)
422 return ThenableResolverCommon(aCx
, PromiseCallback::Resolve
, aArgc
, aVp
);
426 Promise::JSCallbackThenableRejecter(JSContext
* aCx
,
427 unsigned aArgc
, JS::Value
* aVp
)
429 return ThenableResolverCommon(aCx
, PromiseCallback::Reject
, aArgc
, aVp
);
432 /* static */ JSObject
*
433 Promise::CreateFunction(JSContext
* aCx
, JSObject
* aParent
, Promise
* aPromise
,
436 JSFunction
* func
= js::NewFunctionWithReserved(aCx
, JSCallback
,
437 1 /* nargs */, 0 /* flags */,
443 JS::Rooted
<JSObject
*> obj(aCx
, JS_GetFunctionObject(func
));
445 JS::Rooted
<JS::Value
> promiseObj(aCx
);
446 if (!dom::WrapNewBindingObject(aCx
, aPromise
, &promiseObj
)) {
450 js::SetFunctionNativeReserved(obj
, SLOT_PROMISE
, promiseObj
);
451 js::SetFunctionNativeReserved(obj
, SLOT_DATA
, JS::Int32Value(aTask
));
456 /* static */ JSObject
*
457 Promise::CreateThenableFunction(JSContext
* aCx
, Promise
* aPromise
, uint32_t aTask
)
460 aTask
== PromiseCallback::Resolve
? JSCallbackThenableResolver
:
461 JSCallbackThenableRejecter
;
463 JSFunction
* func
= js::NewFunctionWithReserved(aCx
, whichFunc
,
464 1 /* nargs */, 0 /* flags */,
470 JS::Rooted
<JSObject
*> obj(aCx
, JS_GetFunctionObject(func
));
472 JS::Rooted
<JS::Value
> promiseObj(aCx
);
473 if (!dom::WrapNewBindingObject(aCx
, aPromise
, &promiseObj
)) {
477 js::SetFunctionNativeReserved(obj
, SLOT_PROMISE
, promiseObj
);
482 /* static */ already_AddRefed
<Promise
>
483 Promise::Constructor(const GlobalObject
& aGlobal
,
484 PromiseInit
& aInit
, ErrorResult
& aRv
)
486 JSContext
* cx
= aGlobal
.Context();
488 nsCOMPtr
<nsIGlobalObject
> global
;
489 global
= do_QueryInterface(aGlobal
.GetAsSupports());
491 aRv
.Throw(NS_ERROR_UNEXPECTED
);
495 nsRefPtr
<Promise
> promise
= Create(global
, aRv
);
500 JS::Rooted
<JSObject
*> resolveFunc(cx
,
501 CreateFunction(cx
, aGlobal
.Get(), promise
,
502 PromiseCallback::Resolve
));
504 aRv
.Throw(NS_ERROR_UNEXPECTED
);
508 JS::Rooted
<JSObject
*> rejectFunc(cx
,
509 CreateFunction(cx
, aGlobal
.Get(), promise
,
510 PromiseCallback::Reject
));
512 aRv
.Throw(NS_ERROR_UNEXPECTED
);
516 aInit
.Call(resolveFunc
, rejectFunc
, aRv
, CallbackObject::eRethrowExceptions
);
517 aRv
.WouldReportJSException();
519 if (aRv
.IsJSException()) {
520 JS::Rooted
<JS::Value
> value(cx
);
521 aRv
.StealJSException(cx
, &value
);
523 // we want the same behavior as this JS implementation:
524 // function Promise(arg) { try { arg(a, b); } catch (e) { this.reject(e); }}
525 if (!JS_WrapValue(cx
, &value
)) {
526 aRv
.Throw(NS_ERROR_UNEXPECTED
);
530 promise
->MaybeRejectInternal(cx
, value
);
533 return promise
.forget();
536 /* static */ already_AddRefed
<Promise
>
537 Promise::Resolve(const GlobalObject
& aGlobal
,
538 JS::Handle
<JS::Value
> aValue
, ErrorResult
& aRv
)
540 // If a Promise was passed, just return it.
541 if (aValue
.isObject()) {
542 JS::Rooted
<JSObject
*> valueObj(aGlobal
.Context(), &aValue
.toObject());
543 Promise
* nextPromise
;
544 nsresult rv
= UNWRAP_OBJECT(Promise
, valueObj
, nextPromise
);
546 if (NS_SUCCEEDED(rv
)) {
547 nsRefPtr
<Promise
> addRefed
= nextPromise
;
548 return addRefed
.forget();
552 nsCOMPtr
<nsIGlobalObject
> global
=
553 do_QueryInterface(aGlobal
.GetAsSupports());
555 aRv
.Throw(NS_ERROR_UNEXPECTED
);
559 return Resolve(global
, aGlobal
.Context(), aValue
, aRv
);
562 /* static */ already_AddRefed
<Promise
>
563 Promise::Resolve(nsIGlobalObject
* aGlobal
, JSContext
* aCx
,
564 JS::Handle
<JS::Value
> aValue
, ErrorResult
& aRv
)
566 nsRefPtr
<Promise
> promise
= Create(aGlobal
, aRv
);
571 promise
->MaybeResolveInternal(aCx
, aValue
);
572 return promise
.forget();
575 /* static */ already_AddRefed
<Promise
>
576 Promise::Reject(const GlobalObject
& aGlobal
,
577 JS::Handle
<JS::Value
> aValue
, ErrorResult
& aRv
)
579 nsCOMPtr
<nsIGlobalObject
> global
=
580 do_QueryInterface(aGlobal
.GetAsSupports());
582 aRv
.Throw(NS_ERROR_UNEXPECTED
);
586 return Reject(global
, aGlobal
.Context(), aValue
, aRv
);
589 /* static */ already_AddRefed
<Promise
>
590 Promise::Reject(nsIGlobalObject
* aGlobal
, JSContext
* aCx
,
591 JS::Handle
<JS::Value
> aValue
, ErrorResult
& aRv
)
593 nsRefPtr
<Promise
> promise
= Create(aGlobal
, aRv
);
598 promise
->MaybeRejectInternal(aCx
, aValue
);
599 return promise
.forget();
602 already_AddRefed
<Promise
>
603 Promise::Then(JSContext
* aCx
, AnyCallback
* aResolveCallback
,
604 AnyCallback
* aRejectCallback
, ErrorResult
& aRv
)
606 nsRefPtr
<Promise
> promise
= Create(GetParentObject(), aRv
);
611 JS::Rooted
<JSObject
*> global(aCx
, JS::CurrentGlobalOrNull(aCx
));
613 nsRefPtr
<PromiseCallback
> resolveCb
=
614 PromiseCallback::Factory(promise
, global
, aResolveCallback
,
615 PromiseCallback::Resolve
);
617 nsRefPtr
<PromiseCallback
> rejectCb
=
618 PromiseCallback::Factory(promise
, global
, aRejectCallback
,
619 PromiseCallback::Reject
);
621 AppendCallbacks(resolveCb
, rejectCb
);
623 return promise
.forget();
626 already_AddRefed
<Promise
>
627 Promise::Catch(JSContext
* aCx
, AnyCallback
* aRejectCallback
, ErrorResult
& aRv
)
629 nsRefPtr
<AnyCallback
> resolveCb
;
630 return Then(aCx
, resolveCb
, aRejectCallback
, aRv
);
634 * The CountdownHolder class encapsulates Promise.all countdown functions and
635 * the countdown holder parts of the Promises spec. It maintains the result
636 * array and AllResolveHandlers use SetValue() to set the array indices.
638 class CountdownHolder MOZ_FINAL
: public nsISupports
641 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
642 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CountdownHolder
)
644 CountdownHolder(const GlobalObject
& aGlobal
, Promise
* aPromise
,
646 : mPromise(aPromise
), mCountdown(aCountdown
)
648 MOZ_ASSERT(aCountdown
!= 0);
649 JSContext
* cx
= aGlobal
.Context();
651 // The only time aGlobal.Context() and aGlobal.Get() are not
652 // same-compartment is when we're called via Xrays, and in that situation we
653 // in fact want to create the array in the callee compartment
655 JSAutoCompartment
ac(cx
, aGlobal
.Get());
656 mValues
= JS_NewArrayObject(cx
, aCountdown
);
657 mozilla::HoldJSObjects(this);
663 mozilla::DropJSObjects(this);
667 void SetValue(uint32_t index
, const JS::Handle
<JS::Value
> aValue
)
669 MOZ_ASSERT(mCountdown
> 0);
671 ThreadsafeAutoSafeJSContext cx
;
672 JSAutoCompartment
ac(cx
, mValues
);
675 AutoDontReportUncaught
silenceReporting(cx
);
676 JS::Rooted
<JS::Value
> value(cx
, aValue
);
677 JS::Rooted
<JSObject
*> values(cx
, mValues
);
678 if (!JS_WrapValue(cx
, &value
) ||
679 !JS_DefineElement(cx
, values
, index
, value
, JSPROP_ENUMERATE
)) {
680 MOZ_ASSERT(JS_IsExceptionPending(cx
));
681 JS::Rooted
<JS::Value
> exn(cx
);
682 JS_GetPendingException(cx
, &exn
);
684 mPromise
->MaybeReject(cx
, exn
);
689 if (mCountdown
== 0) {
690 JS::Rooted
<JS::Value
> result(cx
, JS::ObjectValue(*mValues
));
691 mPromise
->MaybeResolve(cx
, result
);
696 nsRefPtr
<Promise
> mPromise
;
698 JS::Heap
<JSObject
*> mValues
;
701 NS_IMPL_CYCLE_COLLECTING_ADDREF(CountdownHolder
)
702 NS_IMPL_CYCLE_COLLECTING_RELEASE(CountdownHolder
)
703 NS_IMPL_CYCLE_COLLECTION_CLASS(CountdownHolder
)
705 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CountdownHolder
)
706 NS_INTERFACE_MAP_ENTRY(nsISupports
)
709 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CountdownHolder
)
710 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mValues
)
711 NS_IMPL_CYCLE_COLLECTION_TRACE_END
713 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CountdownHolder
)
714 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
715 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise
)
716 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
718 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CountdownHolder
)
719 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise
)
720 tmp
->mValues
= nullptr;
721 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
724 * An AllResolveHandler is the per-promise part of the Promise.all() algorithm.
725 * Every Promise in the handler is handed an instance of this as a resolution
726 * handler and it sets the relevant index in the CountdownHolder.
728 class AllResolveHandler MOZ_FINAL
: public PromiseNativeHandler
731 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
732 NS_DECL_CYCLE_COLLECTION_CLASS(AllResolveHandler
)
734 AllResolveHandler(CountdownHolder
* aHolder
, uint32_t aIndex
)
735 : mCountdownHolder(aHolder
), mIndex(aIndex
)
741 ResolvedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
)
743 mCountdownHolder
->SetValue(mIndex
, aValue
);
747 RejectedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
)
749 // Should never be attached to Promise as a reject handler.
750 MOZ_ASSERT(false, "AllResolveHandler should never be attached to a Promise's reject handler!");
758 nsRefPtr
<CountdownHolder
> mCountdownHolder
;
762 NS_IMPL_CYCLE_COLLECTING_ADDREF(AllResolveHandler
)
763 NS_IMPL_CYCLE_COLLECTING_RELEASE(AllResolveHandler
)
765 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AllResolveHandler
)
766 NS_INTERFACE_MAP_END_INHERITING(PromiseNativeHandler
)
768 NS_IMPL_CYCLE_COLLECTION(AllResolveHandler
, mCountdownHolder
)
770 /* static */ already_AddRefed
<Promise
>
771 Promise::All(const GlobalObject
& aGlobal
,
772 const Sequence
<JS::Value
>& aIterable
, ErrorResult
& aRv
)
774 nsCOMPtr
<nsIGlobalObject
> global
=
775 do_QueryInterface(aGlobal
.GetAsSupports());
777 aRv
.Throw(NS_ERROR_UNEXPECTED
);
781 JSContext
* cx
= aGlobal
.Context();
783 if (aIterable
.Length() == 0) {
784 JS::Rooted
<JSObject
*> empty(cx
, JS_NewArrayObject(cx
, 0));
786 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
789 JS::Rooted
<JS::Value
> value(cx
, JS::ObjectValue(*empty
));
790 return Promise::Resolve(aGlobal
, value
, aRv
);
793 nsRefPtr
<Promise
> promise
= Create(global
, aRv
);
797 nsRefPtr
<CountdownHolder
> holder
=
798 new CountdownHolder(aGlobal
, promise
, aIterable
.Length());
800 JS::Rooted
<JSObject
*> obj(cx
, JS::CurrentGlobalOrNull(cx
));
802 aRv
.Throw(NS_ERROR_UNEXPECTED
);
806 nsRefPtr
<PromiseCallback
> rejectCb
= new RejectPromiseCallback(promise
, obj
);
808 for (uint32_t i
= 0; i
< aIterable
.Length(); ++i
) {
809 JS::Rooted
<JS::Value
> value(cx
, aIterable
.ElementAt(i
));
810 nsRefPtr
<Promise
> nextPromise
= Promise::Resolve(aGlobal
, value
, aRv
);
812 MOZ_ASSERT(!aRv
.Failed());
814 nsRefPtr
<PromiseNativeHandler
> resolveHandler
=
815 new AllResolveHandler(holder
, i
);
817 nsRefPtr
<PromiseCallback
> resolveCb
=
818 new NativePromiseCallback(resolveHandler
, Resolved
);
819 // Every promise gets its own resolve callback, which will set the right
820 // index in the array to the resolution value.
821 nextPromise
->AppendCallbacks(resolveCb
, rejectCb
);
824 return promise
.forget();
827 /* static */ already_AddRefed
<Promise
>
828 Promise::Race(const GlobalObject
& aGlobal
,
829 const Sequence
<JS::Value
>& aIterable
, ErrorResult
& aRv
)
831 nsCOMPtr
<nsIGlobalObject
> global
=
832 do_QueryInterface(aGlobal
.GetAsSupports());
834 aRv
.Throw(NS_ERROR_UNEXPECTED
);
838 JSContext
* cx
= aGlobal
.Context();
840 JS::Rooted
<JSObject
*> obj(cx
, JS::CurrentGlobalOrNull(cx
));
842 aRv
.Throw(NS_ERROR_UNEXPECTED
);
846 nsRefPtr
<Promise
> promise
= Create(global
, aRv
);
851 nsRefPtr
<PromiseCallback
> resolveCb
=
852 new ResolvePromiseCallback(promise
, obj
);
854 nsRefPtr
<PromiseCallback
> rejectCb
= new RejectPromiseCallback(promise
, obj
);
856 for (uint32_t i
= 0; i
< aIterable
.Length(); ++i
) {
857 JS::Rooted
<JS::Value
> value(cx
, aIterable
.ElementAt(i
));
858 nsRefPtr
<Promise
> nextPromise
= Promise::Resolve(aGlobal
, value
, aRv
);
859 // According to spec, Resolve can throw, but our implementation never does.
860 // Well it does when window isn't passed on the main thread, but that is an
861 // implementation detail which should never be reached since we are checking
862 // for window above. Remove this when subclassing is supported.
863 MOZ_ASSERT(!aRv
.Failed());
864 nextPromise
->AppendCallbacks(resolveCb
, rejectCb
);
867 return promise
.forget();
871 Promise::AppendNativeHandler(PromiseNativeHandler
* aRunnable
)
873 nsRefPtr
<PromiseCallback
> resolveCb
=
874 new NativePromiseCallback(aRunnable
, Resolved
);
876 nsRefPtr
<PromiseCallback
> rejectCb
=
877 new NativePromiseCallback(aRunnable
, Rejected
);
879 AppendCallbacks(resolveCb
, rejectCb
);
883 Promise::AppendCallbacks(PromiseCallback
* aResolveCallback
,
884 PromiseCallback
* aRejectCallback
)
886 if (aResolveCallback
) {
887 mResolveCallbacks
.AppendElement(aResolveCallback
);
890 if (aRejectCallback
) {
891 mHadRejectCallback
= true;
892 mRejectCallbacks
.AppendElement(aRejectCallback
);
894 // Now that there is a callback, we don't need to report anymore.
898 // If promise's state is resolved, queue a task to process our resolve
899 // callbacks with promise's result. If promise's state is rejected, queue a
900 // task to process our reject callbacks with promise's result.
901 if (mState
!= Pending
&& !mTaskPending
) {
902 nsRefPtr
<PromiseTask
> task
= new PromiseTask(this);
903 DispatchToMainOrWorkerThread(task
);
908 class WrappedWorkerRunnable MOZ_FINAL
: public WorkerSameThreadRunnable
911 WrappedWorkerRunnable(WorkerPrivate
* aWorkerPrivate
, nsIRunnable
* aRunnable
)
912 : WorkerSameThreadRunnable(aWorkerPrivate
)
913 , mRunnable(aRunnable
)
915 MOZ_ASSERT(aRunnable
);
916 MOZ_COUNT_CTOR(WrappedWorkerRunnable
);
920 WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) MOZ_OVERRIDE
922 NS_ASSERT_OWNINGTHREAD(WrappedWorkerRunnable
);
929 ~WrappedWorkerRunnable()
931 MOZ_COUNT_DTOR(WrappedWorkerRunnable
);
932 NS_ASSERT_OWNINGTHREAD(WrappedWorkerRunnable
);
935 nsCOMPtr
<nsIRunnable
> mRunnable
;
940 Promise::DispatchToMainOrWorkerThread(nsIRunnable
* aRunnable
)
942 MOZ_ASSERT(aRunnable
);
943 if (NS_IsMainThread()) {
944 NS_DispatchToCurrentThread(aRunnable
);
947 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
949 nsRefPtr
<WrappedWorkerRunnable
> task
= new WrappedWorkerRunnable(worker
, aRunnable
);
950 task
->Dispatch(worker
->GetJSContext());
956 MOZ_ASSERT(mState
!= Pending
);
958 nsTArray
<nsRefPtr
<PromiseCallback
>> callbacks
;
959 callbacks
.SwapElements(mState
== Resolved
? mResolveCallbacks
961 mResolveCallbacks
.Clear();
962 mRejectCallbacks
.Clear();
964 ThreadsafeAutoJSContext cx
;
965 JS::Rooted
<JS::Value
> value(cx
, mResult
);
966 JS::Rooted
<JSObject
*> wrapper(cx
, GetWrapper());
967 MOZ_ASSERT(wrapper
); // We preserved it
969 JSAutoCompartment
ac(cx
, wrapper
);
970 if (!MaybeWrapValue(cx
, &value
)) {
974 for (uint32_t i
= 0; i
< callbacks
.Length(); ++i
) {
975 callbacks
[i
]->Call(cx
, value
);
980 Promise::MaybeReportRejected()
982 if (mState
!= Rejected
|| mHadRejectCallback
|| mResult
.isUndefined()) {
987 // We may not have a usable global by now (if it got unlinked
988 // already), so don't init with it.
990 JSContext
* cx
= jsapi
.cx();
991 JS::Rooted
<JSObject
*> obj(cx
, GetWrapper());
992 MOZ_ASSERT(obj
); // We preserve our wrapper, so should always have one here.
993 JS::Rooted
<JS::Value
> val(cx
, mResult
);
994 JS::ExposeValueToActiveJS(val
);
996 JSAutoCompartment
ac(cx
, obj
);
997 if (!JS_WrapValue(cx
, &val
)) {
998 JS_ClearPendingException(cx
);
1002 js::ErrorReport
report(cx
);
1003 if (!report
.init(cx
, val
)) {
1004 JS_ClearPendingException(cx
);
1008 // Remains null in case of worker.
1009 nsCOMPtr
<nsPIDOMWindow
> win
;
1010 bool isChromeError
= false;
1012 if (MOZ_LIKELY(NS_IsMainThread())) {
1013 nsIPrincipal
* principal
;
1014 win
= xpc::WindowGlobalOrNull(obj
);
1015 principal
= nsContentUtils::ObjectPrincipal(obj
);
1016 isChromeError
= nsContentUtils::IsSystemPrincipal(principal
);
1018 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
1020 isChromeError
= worker
->IsChromeWorker();
1023 // Now post an event to do the real reporting async
1024 // Since Promises preserve their wrapper, it is essential to nsRefPtr<> the
1025 // AsyncErrorReporter, otherwise if the call to DispatchToMainThread fails, it
1026 // will leak. See Bug 958684.
1027 nsRefPtr
<AsyncErrorReporter
> r
=
1028 new AsyncErrorReporter(CycleCollectedJSRuntime::Get()->Runtime(),
1033 NS_DispatchToMainThread(r
);
1037 Promise::MaybeResolveInternal(JSContext
* aCx
,
1038 JS::Handle
<JS::Value
> aValue
,
1039 PromiseTaskSync aAsynchronous
)
1041 if (mResolvePending
) {
1045 ResolveInternal(aCx
, aValue
, aAsynchronous
);
1049 Promise::MaybeRejectInternal(JSContext
* aCx
,
1050 JS::Handle
<JS::Value
> aValue
,
1051 PromiseTaskSync aAsynchronous
)
1053 if (mResolvePending
) {
1057 RejectInternal(aCx
, aValue
, aAsynchronous
);
1061 Promise::HandleException(JSContext
* aCx
)
1063 JS::Rooted
<JS::Value
> exn(aCx
);
1064 if (JS_GetPendingException(aCx
, &exn
)) {
1065 JS_ClearPendingException(aCx
);
1066 RejectInternal(aCx
, exn
, SyncTask
);
1071 Promise::ResolveInternal(JSContext
* aCx
,
1072 JS::Handle
<JS::Value
> aValue
,
1073 PromiseTaskSync aAsynchronous
)
1075 mResolvePending
= true;
1077 if (aValue
.isObject()) {
1078 AutoDontReportUncaught
silenceReporting(aCx
);
1079 JS::Rooted
<JSObject
*> valueObj(aCx
, &aValue
.toObject());
1082 JS::Rooted
<JS::Value
> then(aCx
);
1083 if (!JS_GetProperty(aCx
, valueObj
, "then", &then
)) {
1084 HandleException(aCx
);
1088 if (then
.isObject() && JS_ObjectIsCallable(aCx
, &then
.toObject())) {
1089 // This is the then() function of the thenable aValueObj.
1090 JS::Rooted
<JSObject
*> thenObj(aCx
, &then
.toObject());
1091 nsRefPtr
<PromiseInit
> thenCallback
=
1092 new PromiseInit(thenObj
, mozilla::dom::GetIncumbentGlobal());
1093 nsRefPtr
<ThenableResolverTask
> task
=
1094 new ThenableResolverTask(this, valueObj
, thenCallback
);
1095 DispatchToMainOrWorkerThread(task
);
1100 // If the synchronous flag is set, process our resolve callbacks with
1101 // value. Otherwise, the synchronous flag is unset, queue a task to process
1102 // own resolve callbacks with value. Otherwise, the synchronous flag is
1103 // unset, queue a task to process our resolve callbacks with value.
1104 RunResolveTask(aValue
, Resolved
, aAsynchronous
);
1108 Promise::RejectInternal(JSContext
* aCx
,
1109 JS::Handle
<JS::Value
> aValue
,
1110 PromiseTaskSync aAsynchronous
)
1112 mResolvePending
= true;
1114 // If the synchronous flag is set, process our reject callbacks with
1115 // value. Otherwise, the synchronous flag is unset, queue a task to process
1116 // promise's reject callbacks with value.
1117 RunResolveTask(aValue
, Rejected
, aAsynchronous
);
1121 Promise::RunResolveTask(JS::Handle
<JS::Value
> aValue
,
1122 PromiseState aState
,
1123 PromiseTaskSync aAsynchronous
)
1125 // If the synchronous flag is unset, queue a task to process our
1126 // accept callbacks with value.
1127 if (aAsynchronous
== AsyncTask
) {
1128 nsRefPtr
<PromiseResolverTask
> task
=
1129 new PromiseResolverTask(this, aValue
, aState
);
1130 DispatchToMainOrWorkerThread(task
);
1134 // Promise.all() or Promise.race() implementations will repeatedly call
1135 // Resolve/RejectInternal rather than using the Maybe... forms. Stop SetState
1137 if (mState
!= Pending
) {
1144 // If the Promise was rejected, and there is no reject handler already setup,
1145 // watch for thread shutdown.
1146 if (aState
== PromiseState::Rejected
&&
1147 !mHadRejectCallback
&&
1148 !NS_IsMainThread()) {
1149 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
1151 worker
->AssertIsOnWorkerThread();
1153 mFeature
= new PromiseReportRejectFeature(this);
1154 if (NS_WARN_IF(!worker
->AddFeature(worker
->GetJSContext(), mFeature
))) {
1155 // To avoid a false RemoveFeature().
1157 // Worker is shutting down, report rejection immediately since it is
1158 // unlikely that reject callbacks will be added after this point.
1159 MaybeReportRejectedOnce();
1167 Promise::RemoveFeature()
1170 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
1172 worker
->RemoveFeature(worker
->GetJSContext(), mFeature
);
1178 PromiseReportRejectFeature::Notify(JSContext
* aCx
, workers::Status aStatus
)
1180 MOZ_ASSERT(aStatus
> workers::Running
);
1181 mPromise
->MaybeReportRejectedOnce();
1182 // After this point, `this` has been deleted by RemoveFeature!
1186 // A WorkerRunnable to resolve/reject the Promise on the worker thread.
1188 class PromiseWorkerProxyRunnable
: public workers::WorkerRunnable
1191 PromiseWorkerProxyRunnable(PromiseWorkerProxy
* aPromiseWorkerProxy
,
1192 JSStructuredCloneCallbacks
* aCallbacks
,
1193 JSAutoStructuredCloneBuffer
&& aBuffer
,
1194 PromiseWorkerProxy::RunCallbackFunc aFunc
)
1195 : WorkerRunnable(aPromiseWorkerProxy
->GetWorkerPrivate(),
1196 WorkerThreadUnchangedBusyCount
)
1197 , mPromiseWorkerProxy(aPromiseWorkerProxy
)
1198 , mCallbacks(aCallbacks
)
1199 , mBuffer(Move(aBuffer
))
1202 MOZ_ASSERT(NS_IsMainThread());
1203 MOZ_ASSERT(mPromiseWorkerProxy
);
1207 WorkerRun(JSContext
* aCx
, workers::WorkerPrivate
* aWorkerPrivate
)
1209 MOZ_ASSERT(aWorkerPrivate
);
1210 aWorkerPrivate
->AssertIsOnWorkerThread();
1211 MOZ_ASSERT(aWorkerPrivate
== mWorkerPrivate
);
1213 MOZ_ASSERT(mPromiseWorkerProxy
);
1214 nsRefPtr
<Promise
> workerPromise
= mPromiseWorkerProxy
->GetWorkerPromise();
1215 MOZ_ASSERT(workerPromise
);
1217 // Here we convert the buffer to a JS::Value.
1218 JS::Rooted
<JS::Value
> value(aCx
);
1219 if (!mBuffer
.read(aCx
, &value
, mCallbacks
, mPromiseWorkerProxy
)) {
1220 JS_ClearPendingException(aCx
);
1224 // TODO Bug 975246 - nsRefPtr should support operator |nsRefPtr->*funcType|.
1225 (workerPromise
.get()->*mFunc
)(aCx
,
1227 Promise::PromiseTaskSync::SyncTask
);
1229 // Release the Promise because it has been resolved/rejected for sure.
1230 mPromiseWorkerProxy
->CleanUp(aCx
);
1235 ~PromiseWorkerProxyRunnable() {}
1238 nsRefPtr
<PromiseWorkerProxy
> mPromiseWorkerProxy
;
1239 JSStructuredCloneCallbacks
* mCallbacks
;
1240 JSAutoStructuredCloneBuffer mBuffer
;
1242 // Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
1243 PromiseWorkerProxy::RunCallbackFunc mFunc
;
1246 PromiseWorkerProxy::PromiseWorkerProxy(WorkerPrivate
* aWorkerPrivate
,
1247 Promise
* aWorkerPromise
,
1248 JSStructuredCloneCallbacks
* aCallbacks
)
1249 : mWorkerPrivate(aWorkerPrivate
)
1250 , mWorkerPromise(aWorkerPromise
)
1252 , mCallbacks(aCallbacks
)
1253 , mCleanUpLock("cleanUpLock")
1255 MOZ_ASSERT(mWorkerPrivate
);
1256 mWorkerPrivate
->AssertIsOnWorkerThread();
1257 MOZ_ASSERT(mWorkerPromise
);
1259 // We do this to make sure the worker thread won't shut down before the
1260 // promise is resolved/rejected on the worker thread.
1261 if (!mWorkerPrivate
->AddFeature(mWorkerPrivate
->GetJSContext(), this)) {
1262 MOZ_ASSERT(false, "cannot add the worker feature!");
1267 PromiseWorkerProxy::~PromiseWorkerProxy()
1269 MOZ_ASSERT(mCleanedUp
);
1270 MOZ_ASSERT(!mWorkerPromise
);
1274 PromiseWorkerProxy::GetWorkerPrivate() const
1276 // It's ok to race on |mCleanedUp|, because it will never cause us to fire
1277 // the assertion when we should not.
1278 MOZ_ASSERT(!mCleanedUp
);
1280 return mWorkerPrivate
;
1284 PromiseWorkerProxy::GetWorkerPromise() const
1286 return mWorkerPromise
;
1290 PromiseWorkerProxy::StoreISupports(nsISupports
* aSupports
)
1292 MOZ_ASSERT(NS_IsMainThread());
1294 nsMainThreadPtrHandle
<nsISupports
> supports(
1295 new nsMainThreadPtrHolder
<nsISupports
>(aSupports
));
1296 mSupportsArray
.AppendElement(supports
);
1300 PromiseWorkerProxy::RunCallback(JSContext
* aCx
,
1301 JS::Handle
<JS::Value
> aValue
,
1302 RunCallbackFunc aFunc
)
1304 MOZ_ASSERT(NS_IsMainThread());
1306 MutexAutoLock
lock(mCleanUpLock
);
1307 // If the worker thread's been cancelled we don't need to resolve the Promise.
1312 // The |aValue| is written into the buffer. Note that we also pass |this|
1313 // into the structured-clone write in order to set its |mSupportsArray| to
1314 // keep objects alive until the structured-clone read/write is done.
1315 JSAutoStructuredCloneBuffer buffer
;
1316 if (!buffer
.write(aCx
, aValue
, mCallbacks
, this)) {
1317 JS_ClearPendingException(aCx
);
1318 MOZ_ASSERT(false, "cannot write the JSAutoStructuredCloneBuffer!");
1321 nsRefPtr
<PromiseWorkerProxyRunnable
> runnable
=
1322 new PromiseWorkerProxyRunnable(this,
1327 runnable
->Dispatch(aCx
);
1331 PromiseWorkerProxy::ResolvedCallback(JSContext
* aCx
,
1332 JS::Handle
<JS::Value
> aValue
)
1334 RunCallback(aCx
, aValue
, &Promise::ResolveInternal
);
1338 PromiseWorkerProxy::RejectedCallback(JSContext
* aCx
,
1339 JS::Handle
<JS::Value
> aValue
)
1341 RunCallback(aCx
, aValue
, &Promise::RejectInternal
);
1345 PromiseWorkerProxy::Notify(JSContext
* aCx
, Status aStatus
)
1347 MOZ_ASSERT(mWorkerPrivate
);
1348 mWorkerPrivate
->AssertIsOnWorkerThread();
1349 MOZ_ASSERT(mWorkerPrivate
->GetJSContext() == aCx
);
1351 if (aStatus
>= Canceling
) {
1359 PromiseWorkerProxy::CleanUp(JSContext
* aCx
)
1361 MutexAutoLock
lock(mCleanUpLock
);
1363 // |mWorkerPrivate| might not be safe to use anymore if we have already
1364 // cleaned up and RemoveFeature(), so we need to check |mCleanedUp| first.
1369 MOZ_ASSERT(mWorkerPrivate
);
1370 mWorkerPrivate
->AssertIsOnWorkerThread();
1371 MOZ_ASSERT(mWorkerPrivate
->GetJSContext() == aCx
);
1373 // Release the Promise and remove the PromiseWorkerProxy from the features of
1374 // the worker thread since the Promise has been resolved/rejected or the
1375 // worker thread has been cancelled.
1376 mWorkerPromise
= nullptr;
1377 mWorkerPrivate
->RemoveFeature(aCx
, this);
1381 // Specializations of MaybeRejectBrokenly we actually support.
1383 void Promise::MaybeRejectBrokenly(const nsRefPtr
<DOMError
>& aArg
) {
1384 MaybeSomething(aArg
, &Promise::MaybeReject
);
1387 void Promise::MaybeRejectBrokenly(const nsAString
& aArg
) {
1388 MaybeSomething(aArg
, &Promise::MaybeReject
);
1392 } // namespace mozilla