summaryrefslogtreecommitdiffstats
path: root/js/public/Promise.h
blob: 221d6fdfeebd84c224b745324f9836640e845ee7 (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
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * 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 js_Promise_h
#define js_Promise_h

#include "mozilla/Attributes.h"

#include "jstypes.h"

#include "js/RootingAPI.h"
#include "js/TypeDecls.h"
#include "js/UniquePtr.h"

namespace JS {

class JS_PUBLIC_API AutoDebuggerJobQueueInterruption;

/**
 * Abstract base class for an ECMAScript Job Queue:
 * https://www.ecma-international.org/ecma-262/9.0/index.html#sec-jobs-and-job-queues
 *
 * SpiderMonkey doesn't schedule Promise resolution jobs itself; instead, the
 * embedding can provide an instance of this class SpiderMonkey can use to do
 * that scheduling.
 *
 * The JavaScript shell includes a simple implementation adequate for running
 * tests. Browsers need to augment job handling to meet their own additional
 * requirements, so they can provide their own implementation.
 */
class JS_PUBLIC_API JobQueue {
 public:
  virtual ~JobQueue() = default;

  /**
   * Ask the embedding for the incumbent global.
   *
   * SpiderMonkey doesn't itself have a notion of incumbent globals as defined
   * by the HTML spec, so we need the embedding to provide this. See
   * dom/script/ScriptSettings.h for details.
   */
  virtual JSObject* getIncumbentGlobal(JSContext* cx) = 0;

  /**
   * Enqueue a reaction job `job` for `promise`, which was allocated at
   * `allocationSite`. Provide `incumbentGlobal` as the incumbent global for
   * the reaction job's execution.
   *
   * `promise` can be null if the promise is optimized out.
   * `promise` is guaranteed not to be optimized out if the promise has
   * non-default user-interaction flag.
   */
  virtual bool enqueuePromiseJob(JSContext* cx, JS::HandleObject promise,
                                 JS::HandleObject job,
                                 JS::HandleObject allocationSite,
                                 JS::HandleObject incumbentGlobal) = 0;

  /**
   * Run all jobs in the queue. Running one job may enqueue others; continue to
   * run jobs until the queue is empty.
   *
   * Calling this method at the wrong time can break the web. The HTML spec
   * indicates exactly when the job queue should be drained (in HTML jargon,
   * when it should "perform a microtask checkpoint"), and doing so at other
   * times can incompatibly change the semantics of programs that use promises
   * or other microtask-based features.
   *
   * This method is called only via AutoDebuggerJobQueueInterruption, used by
   * the Debugger API implementation to ensure that the debuggee's job queue is
   * protected from the debugger's own activity. See the comments on
   * AutoDebuggerJobQueueInterruption.
   */
  virtual void runJobs(JSContext* cx) = 0;

  /**
   * Return true if the job queue is empty, false otherwise.
   */
  virtual bool empty() const = 0;

 protected:
  friend class AutoDebuggerJobQueueInterruption;

  /**
   * A saved job queue, represented however the JobQueue implementation pleases.
   * Use AutoDebuggerJobQueueInterruption rather than trying to construct one of
   * these directly; see documentation there.
   *
   * Destructing an instance of this class should assert that the current queue
   * is empty, and then restore the queue the instance captured.
   */
  class SavedJobQueue {
   public:
    virtual ~SavedJobQueue() = default;
  };

  /**
   * Capture this JobQueue's current job queue as a SavedJobQueue and return it,
   * leaving the JobQueue's job queue empty. Destroying the returned object
   * should assert that this JobQueue's current job queue is empty, and restore
   * the original queue.
   *
   * On OOM, this should call JS_ReportOutOfMemory on the given JSContext,
   * and return a null UniquePtr.
   */
  virtual js::UniquePtr<SavedJobQueue> saveJobQueue(JSContext*) = 0;
};

/**
 * Tell SpiderMonkey to use `queue` to schedule promise reactions.
 *
 * SpiderMonkey does not take ownership of the queue; it is the embedding's
 * responsibility to clean it up after the runtime is destroyed.
 */
extern JS_PUBLIC_API void SetJobQueue(JSContext* cx, JobQueue* queue);

/**
 * [SMDOC] Protecting the debuggee's job/microtask queue from debugger activity.
 *
 * When the JavaScript debugger interrupts the execution of some debuggee code
 * (for a breakpoint, for example), the debuggee's execution must be paused
 * while the developer takes time to look at it. During this interruption, other
 * tabs should remain active and usable. If the debuggee shares a main thread
 * with non-debuggee tabs, that means that the thread will have to process
 * non-debuggee HTML tasks and microtasks as usual, even as the debuggee's are
 * on hold until the debugger lets it continue execution. (Letting debuggee
 * microtasks run during the interruption would mean that, from the debuggee's
 * point of view, their side effects would take place wherever the breakpoint
 * was set - in general, not a place other code should ever run, and a violation
 * of the run-to-completion rule.)
 *
 * This means that, even though the timing and ordering of microtasks is
 * carefully specified by the standard - and important to preserve for
 * compatibility and predictability - debugger use may, correctly, have the
 * effect of reordering microtasks. During the interruption, microtasks enqueued
 * by non-debuggee tabs must run immediately alongside their HTML tasks as
 * usual, whereas any debuggee microtasks that were in the queue when the
 * interruption began must wait for the debuggee to be continued - and thus run
 * after microtasks enqueued after they were.
 *
 * Fortunately, this reordering is visible only at the global level: when
 * implemented correctly, it is not detectable by an individual debuggee. Note
 * that a debuggee should generally be a complete unit of similar-origin related
 * browsing contexts. Since non-debuggee activity falls outside that unit, it
 * should never be visible to the debuggee (except via mechanisms that are
 * already asynchronous, like events), so the debuggee should be unable to
 * detect non-debuggee microtasks running when they normally would not. As long
 * as behavior *visible to the debuggee* is unaffected by the interruption, we
 * have respected the spirit of the rule.
 *
 * Of course, even as we accept the general principle that interrupting the
 * debuggee should have as little detectable effect as possible, we still permit
 * the developer to do things like evaluate expressions at the console that have
 * arbitrary effects on the debuggee's state—effects that could never occur
 * naturally at that point in the program. But since these are explicitly
 * requested by the developer, who presumably knows what they're doing, we
 * support this as best we can. If the developer evaluates an expression in the
 * console that resolves a promise, it seems most natural for the promise's
 * reaction microtasks to run immediately, within the interruption. This is an
 * 'unnatural' time for the microtasks to run, but no more unnatural than the
 * evaluation that triggered them.
 *
 * So the overall behavior we need is as follows:
 *
 * - When the debugger interrupts a debuggee, the debuggee's microtask queue
 *   must be saved.
 *
 * - When debuggee execution resumes, the debuggee's microtask queue must be
 *   restored exactly as it was when the interruption occurred.
 *
 * - Non-debuggee task and microtask execution must take place normally during
 *   the interruption.
 *
 * Since each HTML task begins with an empty microtask queue, and it should not
 * be possible for a task to mix debuggee and non-debuggee code, interrupting a
 * debuggee should always find a microtask queue containing exclusively debuggee
 * microtasks, if any. So saving and restoring the microtask queue should affect
 * only the debuggee, not any non-debuggee content.
 *
 * AutoDebuggerJobQueueInterruption
 * --------------------------------
 *
 * AutoDebuggerJobQueueInterruption is an RAII class, meant for use by the
 * Debugger API implementation, that takes care of saving and restoring the
 * queue.
 *
 * Constructing and initializing an instance of AutoDebuggerJobQueueInterruption
 * sets aside the given JSContext's job queue, leaving the JSContext's queue
 * empty. When the AutoDebuggerJobQueueInterruption instance is destroyed, it
 * asserts that the JSContext's current job queue (holding jobs enqueued while
 * the AutoDebuggerJobQueueInterruption was alive) is empty, and restores the
 * saved queue to the JSContext.
 *
 * Since the Debugger API's behavior is up to us, we can specify that Debugger
 * hooks begin execution with an empty job queue, and that we drain the queue
 * after each hook function has run. This drain will be visible to debugger
 * hooks, and makes hook calls resemble HTML tasks, with their own automatic
 * microtask checkpoint. But, the drain will be invisible to the debuggee, as
 * its queue is preserved across the hook invocation.
 *
 * To protect the debuggee's job queue, Debugger takes care to invoke callback
 * functions only within the scope of an AutoDebuggerJobQueueInterruption
 * instance.
 *
 * Why not let the hook functions themselves take care of this?
 * ------------------------------------------------------------
 *
 * Certainly, we could leave responsibility for saving and restoring the job
 * queue to the Debugger hook functions themselves.
 *
 * In fact, early versions of this change tried making the devtools server save
 * and restore the queue explicitly, but because hooks are set and changed in
 * numerous places, it was hard to be confident that every case had been
 * covered, and it seemed that future changes could easily introduce new holes.
 *
 * Later versions of this change modified the accessor properties on the
 * Debugger objects' prototypes to automatically protect the job queue when
 * calling hooks, but the effect was essentially a monkeypatch applied to an API
 * we defined and control, which doesn't make sense.
 *
 * In the end, since promises have become such a pervasive part of JavaScript
 * programming, almost any imaginable use of Debugger would need to provide some
 * kind of protection for the debuggee's job queue, so it makes sense to simply
 * handle it once, carefully, in the implementation of Debugger itself.
 */
class MOZ_RAII JS_PUBLIC_API AutoDebuggerJobQueueInterruption {
 public:
  explicit AutoDebuggerJobQueueInterruption();
  ~AutoDebuggerJobQueueInterruption();

  bool init(JSContext* cx);
  bool initialized() const { return !!saved; }

  /**
   * Drain the job queue. (In HTML terminology, perform a microtask checkpoint.)
   *
   * To make Debugger hook calls more like HTML tasks or ECMAScript jobs,
   * Debugger promises that each hook begins execution with a clean microtask
   * queue, and that a microtask checkpoint (queue drain) takes place after each
   * hook returns, successfully or otherwise.
   *
   * To ensure these debugger-introduced microtask checkpoints serve only the
   * hook's microtasks, and never affect the debuggee's, the Debugger API
   * implementation uses only this method to perform the checkpoints, thereby
   * statically ensuring that an AutoDebuggerJobQueueInterruption is in scope to
   * protect the debuggee.
   *
   * SavedJobQueue implementations are required to assert that the queue is
   * empty before restoring the debuggee's queue. If the Debugger API ever fails
   * to perform a microtask checkpoint after calling a hook, that assertion will
   * fail, catching the mistake.
   */
  void runJobs();

 private:
  JSContext* cx;
  js::UniquePtr<JobQueue::SavedJobQueue> saved;
};

enum class PromiseRejectionHandlingState { Unhandled, Handled };

typedef void (*PromiseRejectionTrackerCallback)(
    JSContext* cx, bool mutedErrors, JS::HandleObject promise,
    JS::PromiseRejectionHandlingState state, void* data);

/**
 * Sets the callback that's invoked whenever a Promise is rejected without
 * a rejection handler, and when a Promise that was previously rejected
 * without a handler gets a handler attached.
 */
extern JS_PUBLIC_API void SetPromiseRejectionTrackerCallback(
    JSContext* cx, PromiseRejectionTrackerCallback callback,
    void* data = nullptr);

/**
 * Inform the runtime that the job queue is empty and the embedding is going to
 * execute its last promise job. The runtime may now choose to skip creating
 * promise jobs for asynchronous execution and instead continue execution
 * synchronously. More specifically, this optimization is used to skip the
 * standard job queuing behavior for `await` operations in async functions.
 *
 * This function may be called before executing the last job in the job queue.
 * When it was called, JobQueueMayNotBeEmpty must be called in order to restore
 * the default job queuing behavior before the embedding enqueues its next job
 * into the job queue.
 */
extern JS_PUBLIC_API void JobQueueIsEmpty(JSContext* cx);

/**
 * Inform the runtime that job queue is no longer empty. The runtime can now no
 * longer skip creating promise jobs for asynchronous execution, because
 * pending jobs in the job queue must be executed first to preserve the FIFO
 * (first in - first out) property of the queue. This effectively undoes
 * JobQueueIsEmpty and re-enables the standard job queuing behavior.
 *
 * This function must be called whenever enqueuing a job to the job queue when
 * JobQueueIsEmpty was called previously.
 */
extern JS_PUBLIC_API void JobQueueMayNotBeEmpty(JSContext* cx);

/**
 * Returns a new instance of the Promise builtin class in the current
 * compartment, with the right slot layout.
 *
 * The `executor` can be a `nullptr`. In that case, the only way to resolve or
 * reject the returned promise is via the `JS::ResolvePromise` and
 * `JS::RejectPromise` JSAPI functions.
 */
extern JS_PUBLIC_API JSObject* NewPromiseObject(JSContext* cx,
                                                JS::HandleObject executor);

/**
 * Returns true if the given object is an unwrapped PromiseObject, false
 * otherwise.
 */
extern JS_PUBLIC_API bool IsPromiseObject(JS::HandleObject obj);

/**
 * Returns the current compartment's original Promise constructor.
 */
extern JS_PUBLIC_API JSObject* GetPromiseConstructor(JSContext* cx);

/**
 * Returns the current compartment's original Promise.prototype.
 */
extern JS_PUBLIC_API JSObject* GetPromisePrototype(JSContext* cx);

// Keep this in sync with the PROMISE_STATE defines in SelfHostingDefines.h.
enum class PromiseState { Pending, Fulfilled, Rejected };

/**
 * Returns the given Promise's state as a JS::PromiseState enum value.
 *
 * Returns JS::PromiseState::Pending if the given object is a wrapper that
 * can't safely be unwrapped.
 */
extern JS_PUBLIC_API PromiseState GetPromiseState(JS::HandleObject promise);

/**
 * Returns the given Promise's process-unique ID.
 */
JS_PUBLIC_API uint64_t GetPromiseID(JS::HandleObject promise);

/**
 * Returns the given Promise's result: either the resolution value for
 * fulfilled promises, or the rejection reason for rejected ones.
 */
extern JS_PUBLIC_API JS::Value GetPromiseResult(JS::HandleObject promise);

/**
 * Returns whether the given promise's rejection is already handled or not.
 *
 * The caller must check the given promise is rejected before checking it's
 * handled or not.
 */
extern JS_PUBLIC_API bool GetPromiseIsHandled(JS::HandleObject promise);

/*
 * Given a settled (i.e. fulfilled or rejected, not pending) promise, sets
 * |promise.[[PromiseIsHandled]]| to true and removes it from the list of
 * unhandled rejected promises.
 */
extern JS_PUBLIC_API bool SetSettledPromiseIsHandled(JSContext* cx,
                                                     JS::HandleObject promise);

/*
 * Given a promise (settled or not), sets |promise.[[PromiseIsHandled]]| to true
 * and removes it from the list of unhandled rejected promises if it's settled.
 */
[[nodiscard]] extern JS_PUBLIC_API bool SetAnyPromiseIsHandled(
    JSContext* cx, JS::HandleObject promise);

/**
 * Returns a js::SavedFrame linked list of the stack that lead to the given
 * Promise's allocation.
 */
extern JS_PUBLIC_API JSObject* GetPromiseAllocationSite(
    JS::HandleObject promise);

extern JS_PUBLIC_API JSObject* GetPromiseResolutionSite(
    JS::HandleObject promise);

#ifdef DEBUG
extern JS_PUBLIC_API void DumpPromiseAllocationSite(JSContext* cx,
                                                    JS::HandleObject promise);

extern JS_PUBLIC_API void DumpPromiseResolutionSite(JSContext* cx,
                                                    JS::HandleObject promise);
#endif

/**
 * Calls the current compartment's original Promise.resolve on the original
 * Promise constructor, with `resolutionValue` passed as an argument.
 */
extern JS_PUBLIC_API JSObject* CallOriginalPromiseResolve(
    JSContext* cx, JS::HandleValue resolutionValue);

/**
 * Calls the current compartment's original Promise.reject on the original
 * Promise constructor, with `resolutionValue` passed as an argument.
 */
extern JS_PUBLIC_API JSObject* CallOriginalPromiseReject(
    JSContext* cx, JS::HandleValue rejectionValue);

/**
 * Resolves the given Promise with the given `resolutionValue`.
 *
 * Calls the `resolve` function that was passed to the executor function when
 * the Promise was created.
 */
extern JS_PUBLIC_API bool ResolvePromise(JSContext* cx,
                                         JS::HandleObject promiseObj,
                                         JS::HandleValue resolutionValue);

/**
 * Rejects the given `promise` with the given `rejectionValue`.
 *
 * Calls the `reject` function that was passed to the executor function when
 * the Promise was created.
 */
extern JS_PUBLIC_API bool RejectPromise(JSContext* cx,
                                        JS::HandleObject promiseObj,
                                        JS::HandleValue rejectionValue);

/**
 * Create a Promise with the given fulfill/reject handlers, that will be
 * fulfilled/rejected with the value/reason that the promise `promise` is
 * fulfilled/rejected with.
 *
 * This function basically acts like `promise.then(onFulfilled, onRejected)`,
 * except that its behavior is unaffected by changes to `Promise`,
 * `Promise[Symbol.species]`, `Promise.prototype.then`, `promise.constructor`,
 * `promise.then`, and so on.
 *
 * This function throws if `promise` is not a Promise from this or another
 * realm.
 *
 * This function will assert if `onFulfilled` or `onRejected` is non-null and
 * also not IsCallable.
 */
extern JS_PUBLIC_API JSObject* CallOriginalPromiseThen(
    JSContext* cx, JS::HandleObject promise, JS::HandleObject onFulfilled,
    JS::HandleObject onRejected);

/**
 * Unforgeable, optimized version of the JS builtin Promise.prototype.then.
 *
 * Takes a Promise instance and nullable `onFulfilled`/`onRejected` callables to
 * enqueue as reactions for that promise. In contrast to Promise.prototype.then,
 * this doesn't create and return a new Promise instance.
 *
 * Throws a TypeError if `promise` isn't a Promise (or possibly a different
 * error if it's a security wrapper or dead object proxy).
 */
extern JS_PUBLIC_API bool AddPromiseReactions(JSContext* cx,
                                              JS::HandleObject promise,
                                              JS::HandleObject onFulfilled,
                                              JS::HandleObject onRejected);

/**
 * Unforgeable, optimized version of the JS builtin Promise.prototype.then.
 *
 * Takes a Promise instance and nullable `onFulfilled`/`onRejected` callables to
 * enqueue as reactions for that promise. In contrast to Promise.prototype.then,
 * this doesn't create and return a new Promise instance.
 *
 * Throws a TypeError if `promise` isn't a Promise (or possibly a different
 * error if it's a security wrapper or dead object proxy).
 *
 * If `onRejected` is null and `promise` is rejected, this function -- unlike
 * the function above -- will not report an unhandled rejection.
 */
extern JS_PUBLIC_API bool AddPromiseReactionsIgnoringUnhandledRejection(
    JSContext* cx, JS::HandleObject promise, JS::HandleObject onFulfilled,
    JS::HandleObject onRejected);

// This enum specifies whether a promise is expected to keep track of
// information that is useful for embedders to implement user activation
// behavior handling as specified in the HTML spec:
// https://html.spec.whatwg.org/multipage/interaction.html#triggered-by-user-activation
// By default, promises created by SpiderMonkey do not make any attempt to keep
// track of information about whether an activation behavior was being processed
// when the original promise in a promise chain was created.  If the embedder
// sets either of the HadUserInteractionAtCreation or
// DidntHaveUserInteractionAtCreation flags on a promise after creating it,
// SpiderMonkey will propagate that flag to newly created promises when
// processing Promise#then and will make it possible to query this flag off of a
// promise further down the chain later using the
// GetPromiseUserInputEventHandlingState() API.
enum class PromiseUserInputEventHandlingState {
  // Don't keep track of this state (default for all promises)
  DontCare,
  // Keep track of this state, the original promise in the chain was created
  // while an activation behavior was being processed.
  HadUserInteractionAtCreation,
  // Keep track of this state, the original promise in the chain was created
  // while an activation behavior was not being processed.
  DidntHaveUserInteractionAtCreation
};

/**
 * Returns the given Promise's activation behavior state flag per above as a
 * JS::PromiseUserInputEventHandlingState value.  All promises are created with
 * the DontCare state by default.
 *
 * Returns JS::PromiseUserInputEventHandlingState::DontCare if the given object
 * is a wrapper that can't safely be unwrapped.
 */
extern JS_PUBLIC_API PromiseUserInputEventHandlingState
GetPromiseUserInputEventHandlingState(JS::HandleObject promise);

/**
 * Sets the given Promise's activation behavior state flag per above as a
 * JS::PromiseUserInputEventHandlingState value.
 *
 * Returns false if the given object is a wrapper that can't safely be
 * unwrapped.
 */
extern JS_PUBLIC_API bool SetPromiseUserInputEventHandlingState(
    JS::HandleObject promise, JS::PromiseUserInputEventHandlingState state);

/**
 * Unforgeable version of the JS builtin Promise.all.
 *
 * Takes a HandleObjectVector of Promise objects and returns a promise that's
 * resolved with an array of resolution values when all those promises have
 * been resolved, or rejected with the rejection value of the first rejected
 * promise.
 *
 * Asserts that all objects in the `promises` vector are, maybe wrapped,
 * instances of `Promise` or a subclass of `Promise`.
 */
extern JS_PUBLIC_API JSObject* GetWaitForAllPromise(
    JSContext* cx, JS::HandleObjectVector promises);

/**
 * The Dispatchable interface allows the embedding to call SpiderMonkey
 * on a JSContext thread when requested via DispatchToEventLoopCallback.
 */
class JS_PUBLIC_API Dispatchable {
 protected:
  // Dispatchables are created and destroyed by SpiderMonkey.
  Dispatchable() = default;
  virtual ~Dispatchable() = default;

 public:
  // ShuttingDown indicates that SpiderMonkey should abort async tasks to
  // expedite shutdown.
  enum MaybeShuttingDown { NotShuttingDown, ShuttingDown };

  // Called by the embedding after DispatchToEventLoopCallback succeeds.
  virtual void run(JSContext* cx, MaybeShuttingDown maybeShuttingDown) = 0;
};

/**
 * Callback to dispatch a JS::Dispatchable to a JSContext's thread's event loop.
 *
 * The DispatchToEventLoopCallback set on a particular JSContext must accept
 * JS::Dispatchable instances and arrange for their `run` methods to be called
 * eventually on the JSContext's thread. This is used for cross-thread dispatch,
 * so the callback itself must be safe to call from any thread.
 *
 * If the callback returns `true`, it must eventually run the given
 * Dispatchable; otherwise, SpiderMonkey may leak memory or hang.
 *
 * The callback may return `false` to indicate that the JSContext's thread is
 * shutting down and is no longer accepting runnables. Shutting down is a
 * one-way transition: once the callback has rejected a runnable, it must reject
 * all subsequently submitted runnables as well.
 *
 * To establish a DispatchToEventLoopCallback, the embedding may either call
 * InitDispatchToEventLoop to provide its own, or call js::UseInternalJobQueues
 * to select a default implementation built into SpiderMonkey. This latter
 * depends on the embedding to call js::RunJobs on the JavaScript thread to
 * process queued Dispatchables at appropriate times.
 */

typedef bool (*DispatchToEventLoopCallback)(void* closure,
                                            Dispatchable* dispatchable);

extern JS_PUBLIC_API void InitDispatchToEventLoop(
    JSContext* cx, DispatchToEventLoopCallback callback, void* closure);

/**
 * When a JSRuntime is destroyed it implicitly cancels all async tasks in
 * progress, releasing any roots held by the task. However, this is not soon
 * enough for cycle collection, which needs to have roots dropped earlier so
 * that the cycle collector can transitively remove roots for a future GC. For
 * these and other cases, the set of pending async tasks can be canceled
 * with this call earlier than JSRuntime destruction.
 */

extern JS_PUBLIC_API void ShutdownAsyncTasks(JSContext* cx);

}  // namespace JS

#endif  // js_Promise_h