1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=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 #ifndef mozilla_dom_Promise_h
8 #define mozilla_dom_Promise_h
11 #include <type_traits>
13 #include "ErrorList.h"
14 #include "js/RootingAPI.h"
15 #include "js/TypeDecls.h"
16 #include "mozilla/AlreadyAddRefed.h"
17 #include "mozilla/Assertions.h"
18 #include "mozilla/ErrorResult.h"
19 #include "mozilla/RefPtr.h"
20 #include "mozilla/Result.h"
21 #include "mozilla/WeakPtr.h"
22 #include "mozilla/dom/AutoEntryScript.h"
23 #include "mozilla/dom/ScriptSettings.h"
24 #include "mozilla/dom/ToJSValue.h"
25 #include "nsCycleCollectionParticipant.h"
27 #include "nsISupports.h"
30 class nsCycleCollectionTraversalCallback
;
31 class nsIGlobalObject
;
37 namespace mozilla::dom
{
40 class MediaStreamError
;
42 class PromiseNativeHandler
;
43 class PromiseDebugging
;
45 class Promise
: public SupportsWeakPtr
{
46 friend class PromiseTask
;
47 friend class PromiseWorkerProxy
;
48 friend class PromiseWorkerProxyRunnable
;
51 NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Promise
)
52 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(Promise
)
54 enum PropagateUserInteraction
{
55 eDontPropagateUserInteraction
,
56 ePropagateUserInteraction
59 // Promise creation tries to create a JS reflector for the Promise, so is
60 // fallible. Furthermore, we don't want to do JS-wrapping on a 0-refcount
61 // object, so we addref before doing that and return the addrefed pointer
63 // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want
64 // the promise resolve handler to be called as if we were handling user
65 // input events in case we are currently handling user input events.
66 static already_AddRefed
<Promise
> Create(
67 nsIGlobalObject
* aGlobal
, ErrorResult
& aRv
,
68 PropagateUserInteraction aPropagateUserInteraction
=
69 eDontPropagateUserInteraction
);
71 // Same as Promise::Create but never throws, but instead:
72 // 1. Causes crash on OOM (as nearly every other web APIs do)
73 // 2. Silently creates a no-op Promise if the JS context is shut down
74 // This can be useful for implementations that produce promises but do not
75 // care whether the current global is alive to consume them.
76 // Note that PromiseObj() can return a nullptr if created this way.
77 static already_AddRefed
<Promise
> CreateInfallible(
78 nsIGlobalObject
* aGlobal
,
79 PropagateUserInteraction aPropagateUserInteraction
=
80 eDontPropagateUserInteraction
);
82 // Reports a rejected Promise by sending an error report.
83 static void ReportRejectedPromise(JSContext
* aCx
,
84 JS::Handle
<JSObject
*> aPromise
);
86 using MaybeFunc
= void (Promise::*)(JSContext
*, JS::Handle
<JS::Value
>);
88 // Helpers for using Promise from C++.
89 // Most DOM objects are handled already. To add a new type T, add a
90 // ToJSValue overload in ToJSValue.h.
91 // aArg is a const reference so we can pass rvalues like integer constants
93 void MaybeResolve(T
&& aArg
) {
94 MaybeSomething(std::forward
<T
>(aArg
), &Promise::MaybeResolve
);
97 void MaybeResolveWithUndefined();
99 void MaybeReject(JS::Handle
<JS::Value
> aValue
) {
100 MaybeSomething(aValue
, &Promise::MaybeReject
);
103 // This method is deprecated. Consumers should MaybeRejectWithDOMException if
104 // they are rejecting with a DOMException, or use one of the other
105 // MaybeReject* methods otherwise. If they have a random nsresult which may
106 // or may not correspond to a DOMException type, they should consider using an
107 // appropriate DOMException-type nsresult with an informative message and
108 // calling MaybeRejectWithDOMException.
109 inline void MaybeReject(nsresult aArg
) {
110 MOZ_ASSERT(NS_FAILED(aArg
));
111 MaybeSomething(aArg
, &Promise::MaybeReject
);
114 inline void MaybeReject(ErrorResult
&& aArg
) {
115 MOZ_ASSERT(aArg
.Failed());
116 MaybeSomething(std::move(aArg
), &Promise::MaybeReject
);
117 // That should have consumed aArg.
118 MOZ_ASSERT(!aArg
.Failed());
121 void MaybeReject(const RefPtr
<MediaStreamError
>& aArg
);
123 void MaybeRejectWithUndefined();
125 void MaybeResolveWithClone(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
);
126 void MaybeRejectWithClone(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
);
128 // Facilities for rejecting with various spec-defined exception values.
129 #define DOMEXCEPTION(name, err) \
130 inline void MaybeRejectWith##name(const nsACString& aMessage) { \
132 res.Throw##name(aMessage); \
133 MaybeReject(std::move(res)); \
136 void MaybeRejectWith##name(const char(&aMessage)[N]) { \
137 MaybeRejectWith##name(nsLiteralCString(aMessage)); \
140 #include "mozilla/dom/DOMExceptionNames.h"
144 template <ErrNum errorNumber
, typename
... Ts
>
145 void MaybeRejectWithTypeError(Ts
&&... aMessageArgs
) {
147 res
.ThrowTypeError
<errorNumber
>(std::forward
<Ts
>(aMessageArgs
)...);
148 MaybeReject(std::move(res
));
151 inline void MaybeRejectWithTypeError(const nsACString
& aMessage
) {
153 res
.ThrowTypeError(aMessage
);
154 MaybeReject(std::move(res
));
158 void MaybeRejectWithTypeError(const char (&aMessage
)[N
]) {
159 MaybeRejectWithTypeError(nsLiteralCString(aMessage
));
162 template <ErrNum errorNumber
, typename
... Ts
>
163 void MaybeRejectWithRangeError(Ts
&&... aMessageArgs
) {
165 res
.ThrowRangeError
<errorNumber
>(std::forward
<Ts
>(aMessageArgs
)...);
166 MaybeReject(std::move(res
));
169 inline void MaybeRejectWithRangeError(const nsACString
& aMessage
) {
171 res
.ThrowRangeError(aMessage
);
172 MaybeReject(std::move(res
));
176 void MaybeRejectWithRangeError(const char (&aMessage
)[N
]) {
177 MaybeRejectWithRangeError(nsLiteralCString(aMessage
));
180 // DO NOT USE MaybeRejectBrokenly with in new code. Promises should be
181 // rejected with Error instances.
182 // Note: MaybeRejectBrokenly is a template so we can use it with DOMException
183 // without instantiating the DOMException specialization of MaybeSomething in
184 // every translation unit that includes this header, because that would
185 // require use to include DOMException.h either here or in all those
186 // translation units.
187 template <typename T
>
188 void MaybeRejectBrokenly(const T
& aArg
); // Not implemented by default; see
189 // specializations in the .cpp for
190 // the T values we support.
192 // Mark a settled promise as already handled so that rejections will not
193 // be reported as unhandled.
194 bool SetSettledPromiseIsHandled();
196 // Mark a promise as handled so that rejections will not be reported as
197 // unhandled. Consider using SetSettledPromiseIsHandled if this promise is
198 // expected to be settled.
199 [[nodiscard
]] bool SetAnyPromiseIsHandled();
203 nsIGlobalObject
* GetParentObject() const { return GetGlobalObject(); }
205 // Do the equivalent of Promise.resolve in the compartment of aGlobal. The
206 // compartment of aCx is ignored. Errors are reported on the ErrorResult; if
207 // aRv comes back !Failed(), this function MUST return a non-null value.
208 // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want
209 // the promise resolve handler to be called as if we were handling user
210 // input events in case we are currently handling user input events.
211 static already_AddRefed
<Promise
> Resolve(
212 nsIGlobalObject
* aGlobal
, JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
,
214 PropagateUserInteraction aPropagateUserInteraction
=
215 eDontPropagateUserInteraction
);
217 // Do the equivalent of Promise.reject in the compartment of aGlobal. The
218 // compartment of aCx is ignored. Errors are reported on the ErrorResult; if
219 // aRv comes back !Failed(), this function MUST return a non-null value.
220 static already_AddRefed
<Promise
> Reject(nsIGlobalObject
* aGlobal
,
222 JS::Handle
<JS::Value
> aValue
,
225 template <typename T
>
226 static already_AddRefed
<Promise
> Reject(nsIGlobalObject
* aGlobal
, T
&& aValue
,
227 ErrorResult
& aError
) {
229 if (!jsapi
.Init(aGlobal
)) {
230 aError
.Throw(NS_ERROR_UNEXPECTED
);
234 JSContext
* cx
= jsapi
.cx();
235 JS::Rooted
<JS::Value
> val(cx
);
236 if (!ToJSValue(cx
, std::forward
<T
>(aValue
), &val
)) {
237 return Promise::RejectWithExceptionFromContext(aGlobal
, cx
, aError
);
240 return Reject(aGlobal
, cx
, val
, aError
);
243 static already_AddRefed
<Promise
> RejectWithExceptionFromContext(
244 nsIGlobalObject
* aGlobal
, JSContext
* aCx
, ErrorResult
& aError
);
246 // Do the equivalent of Promise.all in the current compartment of aCx. Errors
247 // are reported on the ErrorResult; if aRv comes back !Failed(), this function
248 // MUST return a non-null value.
249 // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want
250 // the promise resolve handler to be called as if we were handling user
251 // input events in case we are currently handling user input events.
252 static already_AddRefed
<Promise
> All(
253 JSContext
* aCx
, const nsTArray
<RefPtr
<Promise
>>& aPromiseList
,
255 PropagateUserInteraction aPropagateUserInteraction
=
256 eDontPropagateUserInteraction
);
258 void Then(JSContext
* aCx
,
259 // aCalleeGlobal may not be in the compartment of aCx, when called
261 JS::Handle
<JSObject
*> aCalleeGlobal
, AnyCallback
* aResolveCallback
,
262 AnyCallback
* aRejectCallback
, JS::MutableHandle
<JS::Value
> aRetval
,
265 template <typename Callback
, typename
... Args
>
266 using IsHandlerCallback
=
267 std::is_same
<already_AddRefed
<Promise
>,
268 decltype(std::declval
<Callback
>()(
269 (JSContext
*)(nullptr),
270 std::declval
<JS::Handle
<JS::Value
>>(),
271 std::declval
<ErrorResult
&>(), std::declval
<Args
>()...))>;
273 template <typename Callback
, typename
... Args
>
275 std::enable_if_t
<IsHandlerCallback
<Callback
, Args
...>::value
,
276 Result
<RefPtr
<Promise
>, nsresult
>>;
278 // Similar to the JavaScript then() function. Accepts two lambda function
279 // arguments, which it attaches as native resolve/reject handlers, and
280 // returns a new promise which:
281 // 1. if the ErrorResult contains an error value, rejects with it.
282 // 2. else, resolves with a return value.
284 // Any additional arguments passed after the callback functions are stored and
285 // passed as additional arguments to the functions when it is called. These
286 // values will participate in cycle collection for the promise handler, and
287 // therefore may safely form reference cycles with the promise chain.
289 // Any strong references required by the callbacks should be passed in this
290 // manner, rather than using lambda capture, lambda captures do not support
291 // cycle collection, and can easily lead to leaks.
292 template <typename ResolveCallback
, typename RejectCallback
, typename
... Args
>
293 ThenResult
<ResolveCallback
, Args
...> ThenCatchWithCycleCollectedArgs(
294 ResolveCallback
&& aOnResolve
, RejectCallback
&& aOnReject
,
297 // Same as ThenCatchWithCycleCollectedArgs, except the rejection error will
298 // simply be propagated.
299 template <typename Callback
, typename
... Args
>
300 ThenResult
<Callback
, Args
...> ThenWithCycleCollectedArgs(
301 Callback
&& aOnResolve
, Args
&&... aArgs
);
303 // Same as ThenCatchWithCycleCollectedArgs, except the resolved value will
304 // simply be propagated.
305 template <typename Callback
, typename
... Args
>
306 ThenResult
<Callback
, Args
...> CatchWithCycleCollectedArgs(
307 Callback
&& aOnReject
, Args
&&... aArgs
);
309 // Same as Then[Catch]CycleCollectedArgs but the arguments are gathered into
310 // an `std::tuple` and there is an additional `std::tuple` for JS arguments
312 template <typename ResolveCallback
, typename RejectCallback
,
313 typename ArgsTuple
, typename JSArgsTuple
>
314 Result
<RefPtr
<Promise
>, nsresult
> ThenCatchWithCycleCollectedArgsJS(
315 ResolveCallback
&& aOnResolve
, RejectCallback
&& aOnReject
,
316 ArgsTuple
&& aArgs
, JSArgsTuple
&& aJSArgs
);
317 template <typename Callback
, typename ArgsTuple
, typename JSArgsTuple
>
318 Result
<RefPtr
<Promise
>, nsresult
> ThenWithCycleCollectedArgsJS(
319 Callback
&& aOnResolve
, ArgsTuple
&& aArgs
, JSArgsTuple
&& aJSArgs
);
321 Result
<RefPtr
<Promise
>, nsresult
> ThenWithoutCycleCollection(
322 const std::function
<already_AddRefed
<Promise
>(
323 JSContext
*, JS::Handle
<JS::Value
>, ErrorResult
& aRv
)>& aCallback
);
325 // Similar to ThenCatchWithCycleCollectedArgs but doesn't care with return
326 // values of the callbacks and does not return a new promise.
327 template <typename ResolveCallback
, typename RejectCallback
, typename
... Args
>
328 void AddCallbacksWithCycleCollectedArgs(ResolveCallback
&& aOnResolve
,
329 RejectCallback
&& aOnReject
,
332 // This can be null if this promise is made after the corresponding JSContext
334 JSObject
* PromiseObj() const { return mPromiseObj
; }
336 void AppendNativeHandler(PromiseNativeHandler
* aRunnable
);
338 nsIGlobalObject
* GetGlobalObject() const { return mGlobal
; }
340 // Create a dom::Promise from a given SpiderMonkey Promise object.
341 // aPromiseObj MUST be in the compartment of aGlobal's global JS object.
342 // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want
343 // the promise resolve handler to be called as if we were handling user
344 // input events in case we are currently handling user input events.
345 static already_AddRefed
<Promise
> CreateFromExisting(
346 nsIGlobalObject
* aGlobal
, JS::Handle
<JSObject
*> aPromiseObj
,
347 PropagateUserInteraction aPropagateUserInteraction
=
348 eDontPropagateUserInteraction
);
350 enum class PromiseState
{ Pending
, Resolved
, Rejected
};
352 PromiseState
State() const;
354 static already_AddRefed
<Promise
> CreateResolvedWithUndefined(
355 nsIGlobalObject
* aGlobal
, ErrorResult
& aRv
);
357 static already_AddRefed
<Promise
> CreateRejected(
358 nsIGlobalObject
* aGlobal
, JS::Handle
<JS::Value
> aRejectionError
,
361 static already_AddRefed
<Promise
> CreateRejectedWithTypeError(
362 nsIGlobalObject
* aGlobal
, const nsACString
& aMessage
, ErrorResult
& aRv
);
364 // The rejection error will be consumed if the promise is successfully
365 // created, else the error will remain and rv.Failed() will keep being true.
366 // This intentionally is not an overload of CreateRejected to prevent
367 // accidental omission of the second argument. (See also bug 1762233 about
368 // removing its third argument.)
369 static already_AddRefed
<Promise
> CreateRejectedWithErrorResult(
370 nsIGlobalObject
* aGlobal
, ErrorResult
& aRejectionError
);
372 // Converts an integer or DOMException to nsresult, or otherwise returns
373 // NS_ERROR_DOM_NOT_NUMBER_ERR (which is exclusive for this function).
374 // Can be used to convert JS::Value passed to rejection handler so that native
375 // error handlers e.g. MozPromise can consume it.
376 static nsresult
TryExtractNSResultFromRejectionValue(
377 JS::Handle
<JS::Value
> aValue
);
380 template <typename ResolveCallback
, typename RejectCallback
, typename
... Args
,
382 Result
<RefPtr
<Promise
>, nsresult
> ThenCatchWithCycleCollectedArgsJSImpl(
383 Maybe
<ResolveCallback
>&& aOnResolve
, Maybe
<RejectCallback
>&& aOnReject
,
384 std::tuple
<Args
...>&& aArgs
, std::tuple
<JSArgs
...>&& aJSArgs
);
385 template <typename ResolveCallback
, typename RejectCallback
, typename
... Args
>
386 ThenResult
<ResolveCallback
, Args
...> ThenCatchWithCycleCollectedArgsImpl(
387 Maybe
<ResolveCallback
>&& aOnResolve
, Maybe
<RejectCallback
>&& aOnReject
,
390 // Legacy method for throwing DOMExceptions. Only used by media code at this
391 // point, via DetailedPromise. Do NOT add new uses! When this is removed,
392 // remove the friend declaration in ErrorResult.h.
393 inline void MaybeRejectWithDOMException(nsresult rv
,
394 const nsACString
& aMessage
) {
396 res
.ThrowDOMException(rv
, aMessage
);
397 MaybeReject(std::move(res
));
400 struct PromiseCapability
;
402 // Do NOT call this unless you're Promise::Create or
403 // Promise::CreateFromExisting. I wish we could enforce that from inside this
404 // class too, somehow.
405 explicit Promise(nsIGlobalObject
* aGlobal
);
409 // Do JS-wrapping after Promise creation.
410 // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want
411 // the promise resolve handler to be called as if we were handling user
412 // input events in case we are currently handling user input events.
413 void CreateWrapper(ErrorResult
& aRv
,
414 PropagateUserInteraction aPropagateUserInteraction
=
415 eDontPropagateUserInteraction
);
418 void MaybeResolve(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
);
419 void MaybeReject(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
);
421 template <typename T
>
422 void MaybeSomething(T
&& aArgument
, MaybeFunc aFunc
) {
423 MOZ_ASSERT(PromiseObj()); // It was preserved!
425 AutoAllowLegacyScriptExecution exemption
;
426 AutoEntryScript
aes(mGlobal
, "Promise resolution or rejection");
427 JSContext
* cx
= aes
.cx();
429 JS::Rooted
<JS::Value
> val(cx
);
430 if (!ToJSValue(cx
, std::forward
<T
>(aArgument
), &val
)) {
435 (this->*aFunc
)(cx
, val
);
438 void HandleException(JSContext
* aCx
);
440 bool MaybePropagateUserInputEventHandling();
442 RefPtr
<nsIGlobalObject
> mGlobal
;
444 JS::Heap
<JSObject
*> mPromiseObj
;
447 } // namespace mozilla::dom
450 // These functions are used in the implementation of ffi bindings for
451 // dom::Promise from Rust in xpcom crate.
452 void DomPromise_AddRef(mozilla::dom::Promise
* aPromise
);
453 void DomPromise_Release(mozilla::dom::Promise
* aPromise
);
454 void DomPromise_RejectWithVariant(mozilla::dom::Promise
* aPromise
,
455 nsIVariant
* aVariant
);
456 void DomPromise_ResolveWithVariant(mozilla::dom::Promise
* aPromise
,
457 nsIVariant
* aVariant
);
460 #endif // mozilla_dom_Promise_h