summaryrefslogtreecommitdiffstats
path: root/dom/promise/Promise.h
blob: f2b70a95cde05f61a75ae243507a86f6b09a2974 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_dom_Promise_h
#define mozilla_dom_Promise_h

#include <functional>
#include <type_traits>
#include <utility>
#include "ErrorList.h"
#include "js/RootingAPI.h"
#include "js/TypeDecls.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Assertions.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Result.h"
#include "mozilla/WeakPtr.h"
#include "mozilla/dom/AutoEntryScript.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/ToJSValue.h"
#include "nsCycleCollectionParticipant.h"
#include "nsError.h"
#include "nsISupports.h"
#include "nsString.h"

class nsCycleCollectionTraversalCallback;
class nsIGlobalObject;

namespace JS {
class Value;
}

namespace mozilla::dom {

class AnyCallback;
class MediaStreamError;
class PromiseInit;
class PromiseNativeHandler;
class PromiseDebugging;

class Promise : public SupportsWeakPtr {
  friend class PromiseTask;
  friend class PromiseWorkerProxy;
  friend class PromiseWorkerProxyRunnable;

 public:
  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Promise)
  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(Promise)

  enum PropagateUserInteraction {
    eDontPropagateUserInteraction,
    ePropagateUserInteraction
  };

  // Promise creation tries to create a JS reflector for the Promise, so is
  // fallible.  Furthermore, we don't want to do JS-wrapping on a 0-refcount
  // object, so we addref before doing that and return the addrefed pointer
  // here.
  // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want
  // the promise resolve handler to be called as if we were handling user
  // input events in case we are currently handling user input events.
  static already_AddRefed<Promise> Create(
      nsIGlobalObject* aGlobal, ErrorResult& aRv,
      PropagateUserInteraction aPropagateUserInteraction =
          eDontPropagateUserInteraction);

  // Same as Promise::Create but never throws, but instead:
  // 1. Causes crash on OOM (as nearly every other web APIs do)
  // 2. Silently creates a no-op Promise if the JS context is shut down
  // This can be useful for implementations that produce promises but do not
  // care whether the current global is alive to consume them.
  // Note that PromiseObj() can return a nullptr if created this way.
  static already_AddRefed<Promise> CreateInfallible(
      nsIGlobalObject* aGlobal,
      PropagateUserInteraction aPropagateUserInteraction =
          eDontPropagateUserInteraction);

  // Reports a rejected Promise by sending an error report.
  static void ReportRejectedPromise(JSContext* aCx,
                                    JS::Handle<JSObject*> aPromise);

  using MaybeFunc = void (Promise::*)(JSContext*, JS::Handle<JS::Value>);

  // Helpers for using Promise from C++.
  // Most DOM objects are handled already.  To add a new type T, add a
  // ToJSValue overload in ToJSValue.h.
  // aArg is a const reference so we can pass rvalues like integer constants
  template <typename T>
  void MaybeResolve(T&& aArg) {
    MaybeSomething(std::forward<T>(aArg), &Promise::MaybeResolve);
  }

  void MaybeResolveWithUndefined();

  void MaybeReject(JS::Handle<JS::Value> aValue) {
    MaybeSomething(aValue, &Promise::MaybeReject);
  }

  // This method is deprecated.  Consumers should MaybeRejectWithDOMException if
  // they are rejecting with a DOMException, or use one of the other
  // MaybeReject* methods otherwise.  If they have a random nsresult which may
  // or may not correspond to a DOMException type, they should consider using an
  // appropriate DOMException-type nsresult with an informative message and
  // calling MaybeRejectWithDOMException.
  inline void MaybeReject(nsresult aArg) {
    MOZ_ASSERT(NS_FAILED(aArg));
    MaybeSomething(aArg, &Promise::MaybeReject);
  }

  inline void MaybeReject(ErrorResult&& aArg) {
    MOZ_ASSERT(aArg.Failed());
    MaybeSomething(std::move(aArg), &Promise::MaybeReject);
    // That should have consumed aArg.
    MOZ_ASSERT(!aArg.Failed());
  }

  void MaybeReject(const RefPtr<MediaStreamError>& aArg);

  void MaybeRejectWithUndefined();

  void MaybeResolveWithClone(JSContext* aCx, JS::Handle<JS::Value> aValue);
  void MaybeRejectWithClone(JSContext* aCx, JS::Handle<JS::Value> aValue);

  // Facilities for rejecting with various spec-defined exception values.
#define DOMEXCEPTION(name, err)                                   \
  inline void MaybeRejectWith##name(const nsACString& aMessage) { \
    ErrorResult res;                                              \
    res.Throw##name(aMessage);                                    \
    MaybeReject(std::move(res));                                  \
  }                                                               \
  template <int N>                                                \
  void MaybeRejectWith##name(const char(&aMessage)[N]) {          \
    MaybeRejectWith##name(nsLiteralCString(aMessage));            \
  }

#include "mozilla/dom/DOMExceptionNames.h"

#undef DOMEXCEPTION

  template <ErrNum errorNumber, typename... Ts>
  void MaybeRejectWithTypeError(Ts&&... aMessageArgs) {
    ErrorResult res;
    res.ThrowTypeError<errorNumber>(std::forward<Ts>(aMessageArgs)...);
    MaybeReject(std::move(res));
  }

  inline void MaybeRejectWithTypeError(const nsACString& aMessage) {
    ErrorResult res;
    res.ThrowTypeError(aMessage);
    MaybeReject(std::move(res));
  }

  template <int N>
  void MaybeRejectWithTypeError(const char (&aMessage)[N]) {
    MaybeRejectWithTypeError(nsLiteralCString(aMessage));
  }

  template <ErrNum errorNumber, typename... Ts>
  void MaybeRejectWithRangeError(Ts&&... aMessageArgs) {
    ErrorResult res;
    res.ThrowRangeError<errorNumber>(std::forward<Ts>(aMessageArgs)...);
    MaybeReject(std::move(res));
  }

  inline void MaybeRejectWithRangeError(const nsACString& aMessage) {
    ErrorResult res;
    res.ThrowRangeError(aMessage);
    MaybeReject(std::move(res));
  }

  template <int N>
  void MaybeRejectWithRangeError(const char (&aMessage)[N]) {
    MaybeRejectWithRangeError(nsLiteralCString(aMessage));
  }

  // DO NOT USE MaybeRejectBrokenly with in new code.  Promises should be
  // rejected with Error instances.
  // Note: MaybeRejectBrokenly is a template so we can use it with DOMException
  // without instantiating the DOMException specialization of MaybeSomething in
  // every translation unit that includes this header, because that would
  // require use to include DOMException.h either here or in all those
  // translation units.
  template <typename T>
  void MaybeRejectBrokenly(const T& aArg);  // Not implemented by default; see
                                            // specializations in the .cpp for
                                            // the T values we support.

  // Mark a settled promise as already handled so that rejections will not
  // be reported as unhandled.
  bool SetSettledPromiseIsHandled();

  // Mark a promise as handled so that rejections will not be reported as
  // unhandled. Consider using SetSettledPromiseIsHandled if this promise is
  // expected to be settled.
  [[nodiscard]] bool SetAnyPromiseIsHandled();

  // WebIDL

  nsIGlobalObject* GetParentObject() const { return GetGlobalObject(); }

  // Do the equivalent of Promise.resolve in the compartment of aGlobal.  The
  // compartment of aCx is ignored.  Errors are reported on the ErrorResult; if
  // aRv comes back !Failed(), this function MUST return a non-null value.
  // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want
  // the promise resolve handler to be called as if we were handling user
  // input events in case we are currently handling user input events.
  static already_AddRefed<Promise> Resolve(
      nsIGlobalObject* aGlobal, JSContext* aCx, JS::Handle<JS::Value> aValue,
      ErrorResult& aRv,
      PropagateUserInteraction aPropagateUserInteraction =
          eDontPropagateUserInteraction);

  // Do the equivalent of Promise.reject in the compartment of aGlobal.  The
  // compartment of aCx is ignored.  Errors are reported on the ErrorResult; if
  // aRv comes back !Failed(), this function MUST return a non-null value.
  static already_AddRefed<Promise> Reject(nsIGlobalObject* aGlobal,
                                          JSContext* aCx,
                                          JS::Handle<JS::Value> aValue,
                                          ErrorResult& aRv);

  template <typename T>
  static already_AddRefed<Promise> Reject(nsIGlobalObject* aGlobal, T&& aValue,
                                          ErrorResult& aError) {
    AutoJSAPI jsapi;
    if (!jsapi.Init(aGlobal)) {
      aError.Throw(NS_ERROR_UNEXPECTED);
      return nullptr;
    }

    JSContext* cx = jsapi.cx();
    JS::Rooted<JS::Value> val(cx);
    if (!ToJSValue(cx, std::forward<T>(aValue), &val)) {
      return Promise::RejectWithExceptionFromContext(aGlobal, cx, aError);
    }

    return Reject(aGlobal, cx, val, aError);
  }

  static already_AddRefed<Promise> RejectWithExceptionFromContext(
      nsIGlobalObject* aGlobal, JSContext* aCx, ErrorResult& aError);

  // Do the equivalent of Promise.all in the current compartment of aCx.  Errors
  // are reported on the ErrorResult; if aRv comes back !Failed(), this function
  // MUST return a non-null value.
  // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want
  // the promise resolve handler to be called as if we were handling user
  // input events in case we are currently handling user input events.
  static already_AddRefed<Promise> All(
      JSContext* aCx, const nsTArray<RefPtr<Promise>>& aPromiseList,
      ErrorResult& aRv,
      PropagateUserInteraction aPropagateUserInteraction =
          eDontPropagateUserInteraction);

  template <typename Callback, typename... Args>
  using IsHandlerCallback =
      std::is_same<already_AddRefed<Promise>,
                   decltype(std::declval<Callback>()(
                       (JSContext*)(nullptr),
                       std::declval<JS::Handle<JS::Value>>(),
                       std::declval<ErrorResult&>(), std::declval<Args>()...))>;

  template <typename Callback, typename... Args>
  using ThenResult =
      std::enable_if_t<IsHandlerCallback<Callback, Args...>::value,
                       Result<RefPtr<Promise>, nsresult>>;

  // Similar to the JavaScript then() function. Accepts two lambda function
  // arguments, which it attaches as native resolve/reject handlers, and
  // returns a new promise which:
  // 1. if the ErrorResult contains an error value, rejects with it.
  // 2. else, resolves with a return value.
  //
  // Any additional arguments passed after the callback functions are stored and
  // passed as additional arguments to the functions when it is called. These
  // values will participate in cycle collection for the promise handler, and
  // therefore may safely form reference cycles with the promise chain.
  //
  // Any strong references required by the callbacks should be passed in this
  // manner, rather than using lambda capture, lambda captures do not support
  // cycle collection, and can easily lead to leaks.
  template <typename ResolveCallback, typename RejectCallback, typename... Args>
  ThenResult<ResolveCallback, Args...> ThenCatchWithCycleCollectedArgs(
      ResolveCallback&& aOnResolve, RejectCallback&& aOnReject,
      Args&&... aArgs);

  // Same as ThenCatchWithCycleCollectedArgs, except the rejection error will
  // simply be propagated.
  template <typename Callback, typename... Args>
  ThenResult<Callback, Args...> ThenWithCycleCollectedArgs(
      Callback&& aOnResolve, Args&&... aArgs);

  // Same as ThenCatchWithCycleCollectedArgs, except the resolved value will
  // simply be propagated.
  template <typename Callback, typename... Args>
  ThenResult<Callback, Args...> CatchWithCycleCollectedArgs(
      Callback&& aOnReject, Args&&... aArgs);

  // Same as Then[Catch]CycleCollectedArgs but the arguments are gathered into
  // an `std::tuple` and there is an additional `std::tuple` for JS arguments
  // after that.
  template <typename ResolveCallback, typename RejectCallback,
            typename ArgsTuple, typename JSArgsTuple>
  Result<RefPtr<Promise>, nsresult> ThenCatchWithCycleCollectedArgsJS(
      ResolveCallback&& aOnResolve, RejectCallback&& aOnReject,
      ArgsTuple&& aArgs, JSArgsTuple&& aJSArgs);
  template <typename Callback, typename ArgsTuple, typename JSArgsTuple>
  Result<RefPtr<Promise>, nsresult> ThenWithCycleCollectedArgsJS(
      Callback&& aOnResolve, ArgsTuple&& aArgs, JSArgsTuple&& aJSArgs);

  Result<RefPtr<Promise>, nsresult> ThenWithoutCycleCollection(
      const std::function<already_AddRefed<Promise>(
          JSContext*, JS::Handle<JS::Value>, ErrorResult& aRv)>& aCallback);

  // Similar to ThenCatchWithCycleCollectedArgs but doesn't care with return
  // values of the callbacks and does not return a new promise.
  template <typename ResolveCallback, typename RejectCallback, typename... Args>
  void AddCallbacksWithCycleCollectedArgs(ResolveCallback&& aOnResolve,
                                          RejectCallback&& aOnReject,
                                          Args&&... aArgs);

  // This can be null if this promise is made after the corresponding JSContext
  // is dead.
  JSObject* PromiseObj() const { return mPromiseObj; }

  void AppendNativeHandler(PromiseNativeHandler* aRunnable);

  nsIGlobalObject* GetGlobalObject() const { return mGlobal; }

  // Create a dom::Promise from a given SpiderMonkey Promise object.
  // aPromiseObj MUST be in the compartment of aGlobal's global JS object.
  // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want
  // the promise resolve handler to be called as if we were handling user
  // input events in case we are currently handling user input events.
  static already_AddRefed<Promise> CreateFromExisting(
      nsIGlobalObject* aGlobal, JS::Handle<JSObject*> aPromiseObj,
      PropagateUserInteraction aPropagateUserInteraction =
          eDontPropagateUserInteraction);

  enum class PromiseState { Pending, Resolved, Rejected };

  PromiseState State() const;

  static already_AddRefed<Promise> CreateResolvedWithUndefined(
      nsIGlobalObject* aGlobal, ErrorResult& aRv);

  static already_AddRefed<Promise> CreateRejected(
      nsIGlobalObject* aGlobal, JS::Handle<JS::Value> aRejectionError,
      ErrorResult& aRv);

  static already_AddRefed<Promise> CreateRejectedWithTypeError(
      nsIGlobalObject* aGlobal, const nsACString& aMessage, ErrorResult& aRv);

  // The rejection error will be consumed if the promise is successfully
  // created, else the error will remain and rv.Failed() will keep being true.
  // This intentionally is not an overload of CreateRejected to prevent
  // accidental omission of the second argument. (See also bug 1762233 about
  // removing its third argument.)
  static already_AddRefed<Promise> CreateRejectedWithErrorResult(
      nsIGlobalObject* aGlobal, ErrorResult& aRejectionError);

  // Converts an integer or DOMException to nsresult, or otherwise returns
  // NS_ERROR_DOM_NOT_NUMBER_ERR (which is exclusive for this function).
  // Can be used to convert JS::Value passed to rejection handler so that native
  // error handlers e.g. MozPromise can consume it.
  static nsresult TryExtractNSResultFromRejectionValue(
      JS::Handle<JS::Value> aValue);

 protected:
  template <typename ResolveCallback, typename RejectCallback, typename... Args,
            typename... JSArgs>
  Result<RefPtr<Promise>, nsresult> ThenCatchWithCycleCollectedArgsJSImpl(
      Maybe<ResolveCallback>&& aOnResolve, Maybe<RejectCallback>&& aOnReject,
      std::tuple<Args...>&& aArgs, std::tuple<JSArgs...>&& aJSArgs);
  template <typename ResolveCallback, typename RejectCallback, typename... Args>
  ThenResult<ResolveCallback, Args...> ThenCatchWithCycleCollectedArgsImpl(
      Maybe<ResolveCallback>&& aOnResolve, Maybe<RejectCallback>&& aOnReject,
      Args&&... aArgs);

  // Legacy method for throwing DOMExceptions.  Only used by media code at this
  // point, via DetailedPromise.  Do NOT add new uses!  When this is removed,
  // remove the friend declaration in ErrorResult.h.
  inline void MaybeRejectWithDOMException(nsresult rv,
                                          const nsACString& aMessage) {
    ErrorResult res;
    res.ThrowDOMException(rv, aMessage);
    MaybeReject(std::move(res));
  }

  struct PromiseCapability;

  // Do NOT call this unless you're Promise::Create or
  // Promise::CreateFromExisting.  I wish we could enforce that from inside this
  // class too, somehow.
  explicit Promise(nsIGlobalObject* aGlobal);

  virtual ~Promise();

  // Do JS-wrapping after Promise creation.
  // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want
  // the promise resolve handler to be called as if we were handling user
  // input events in case we are currently handling user input events.
  void CreateWrapper(ErrorResult& aRv,
                     PropagateUserInteraction aPropagateUserInteraction =
                         eDontPropagateUserInteraction);

 private:
  void MaybeResolve(JSContext* aCx, JS::Handle<JS::Value> aValue);
  void MaybeReject(JSContext* aCx, JS::Handle<JS::Value> aValue);

  template <typename T>
  void MaybeSomething(T&& aArgument, MaybeFunc aFunc) {
    MOZ_ASSERT(PromiseObj());  // It was preserved!

    AutoAllowLegacyScriptExecution exemption;
    AutoEntryScript aes(mGlobal, "Promise resolution or rejection");
    JSContext* cx = aes.cx();

    JS::Rooted<JS::Value> val(cx);
    if (!ToJSValue(cx, std::forward<T>(aArgument), &val)) {
      HandleException(cx);
      return;
    }

    (this->*aFunc)(cx, val);
  }

  void HandleException(JSContext* aCx);

  bool MaybePropagateUserInputEventHandling();

  RefPtr<nsIGlobalObject> mGlobal;

  JS::Heap<JSObject*> mPromiseObj;
};

}  // namespace mozilla::dom

extern "C" {
// These functions are used in the implementation of ffi bindings for
// dom::Promise from Rust in xpcom crate.
void DomPromise_AddRef(mozilla::dom::Promise* aPromise);
void DomPromise_Release(mozilla::dom::Promise* aPromise);
void DomPromise_RejectWithVariant(mozilla::dom::Promise* aPromise,
                                  nsIVariant* aVariant);
void DomPromise_ResolveWithVariant(mozilla::dom::Promise* aPromise,
                                   nsIVariant* aVariant);
}

#endif  // mozilla_dom_Promise_h