Bug 1874684 - Part 31: Correctly reject invalid durations in some RoundDuration calls...
[gecko.git] / js / public / Promise.h
blob75bc7fd8370df674417adc801430300a6277280d
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef js_Promise_h
8 #define js_Promise_h
10 #include "mozilla/Attributes.h"
12 #include "jstypes.h"
14 #include "js/RootingAPI.h"
15 #include "js/TypeDecls.h"
16 #include "js/UniquePtr.h"
18 namespace JS {
20 class JS_PUBLIC_API AutoDebuggerJobQueueInterruption;
22 /**
23 * Abstract base class for an ECMAScript Job Queue:
24 * https://www.ecma-international.org/ecma-262/9.0/index.html#sec-jobs-and-job-queues
26 * SpiderMonkey doesn't schedule Promise resolution jobs itself; instead, the
27 * embedding can provide an instance of this class SpiderMonkey can use to do
28 * that scheduling.
30 * The JavaScript shell includes a simple implementation adequate for running
31 * tests. Browsers need to augment job handling to meet their own additional
32 * requirements, so they can provide their own implementation.
34 class JS_PUBLIC_API JobQueue {
35 public:
36 virtual ~JobQueue() = default;
38 /**
39 * Ask the embedding for the incumbent global.
41 * SpiderMonkey doesn't itself have a notion of incumbent globals as defined
42 * by the HTML spec, so we need the embedding to provide this. See
43 * dom/script/ScriptSettings.h for details.
45 virtual JSObject* getIncumbentGlobal(JSContext* cx) = 0;
47 /**
48 * Enqueue a reaction job `job` for `promise`, which was allocated at
49 * `allocationSite`. Provide `incumbentGlobal` as the incumbent global for
50 * the reaction job's execution.
52 * `promise` can be null if the promise is optimized out.
53 * `promise` is guaranteed not to be optimized out if the promise has
54 * non-default user-interaction flag.
56 virtual bool enqueuePromiseJob(JSContext* cx, JS::HandleObject promise,
57 JS::HandleObject job,
58 JS::HandleObject allocationSite,
59 JS::HandleObject incumbentGlobal) = 0;
61 /**
62 * Run all jobs in the queue. Running one job may enqueue others; continue to
63 * run jobs until the queue is empty.
65 * Calling this method at the wrong time can break the web. The HTML spec
66 * indicates exactly when the job queue should be drained (in HTML jargon,
67 * when it should "perform a microtask checkpoint"), and doing so at other
68 * times can incompatibly change the semantics of programs that use promises
69 * or other microtask-based features.
71 * This method is called only via AutoDebuggerJobQueueInterruption, used by
72 * the Debugger API implementation to ensure that the debuggee's job queue is
73 * protected from the debugger's own activity. See the comments on
74 * AutoDebuggerJobQueueInterruption.
76 virtual void runJobs(JSContext* cx) = 0;
78 /**
79 * Return true if the job queue is empty, false otherwise.
81 virtual bool empty() const = 0;
83 /**
84 * Returns true if the job queue stops draining, which results in `empty()`
85 * being false after `runJobs()`.
87 virtual bool isDrainingStopped() const = 0;
89 protected:
90 friend class AutoDebuggerJobQueueInterruption;
92 /**
93 * A saved job queue, represented however the JobQueue implementation pleases.
94 * Use AutoDebuggerJobQueueInterruption rather than trying to construct one of
95 * these directly; see documentation there.
97 * Destructing an instance of this class should assert that the current queue
98 * is empty, and then restore the queue the instance captured.
100 class SavedJobQueue {
101 public:
102 virtual ~SavedJobQueue() = default;
106 * Capture this JobQueue's current job queue as a SavedJobQueue and return it,
107 * leaving the JobQueue's job queue empty. Destroying the returned object
108 * should assert that this JobQueue's current job queue is empty, and restore
109 * the original queue.
111 * On OOM, this should call JS_ReportOutOfMemory on the given JSContext,
112 * and return a null UniquePtr.
114 virtual js::UniquePtr<SavedJobQueue> saveJobQueue(JSContext*) = 0;
118 * Tell SpiderMonkey to use `queue` to schedule promise reactions.
120 * SpiderMonkey does not take ownership of the queue; it is the embedding's
121 * responsibility to clean it up after the runtime is destroyed.
123 extern JS_PUBLIC_API void SetJobQueue(JSContext* cx, JobQueue* queue);
126 * [SMDOC] Protecting the debuggee's job/microtask queue from debugger activity.
128 * When the JavaScript debugger interrupts the execution of some debuggee code
129 * (for a breakpoint, for example), the debuggee's execution must be paused
130 * while the developer takes time to look at it. During this interruption, other
131 * tabs should remain active and usable. If the debuggee shares a main thread
132 * with non-debuggee tabs, that means that the thread will have to process
133 * non-debuggee HTML tasks and microtasks as usual, even as the debuggee's are
134 * on hold until the debugger lets it continue execution. (Letting debuggee
135 * microtasks run during the interruption would mean that, from the debuggee's
136 * point of view, their side effects would take place wherever the breakpoint
137 * was set - in general, not a place other code should ever run, and a violation
138 * of the run-to-completion rule.)
140 * This means that, even though the timing and ordering of microtasks is
141 * carefully specified by the standard - and important to preserve for
142 * compatibility and predictability - debugger use may, correctly, have the
143 * effect of reordering microtasks. During the interruption, microtasks enqueued
144 * by non-debuggee tabs must run immediately alongside their HTML tasks as
145 * usual, whereas any debuggee microtasks that were in the queue when the
146 * interruption began must wait for the debuggee to be continued - and thus run
147 * after microtasks enqueued after they were.
149 * Fortunately, this reordering is visible only at the global level: when
150 * implemented correctly, it is not detectable by an individual debuggee. Note
151 * that a debuggee should generally be a complete unit of similar-origin related
152 * browsing contexts. Since non-debuggee activity falls outside that unit, it
153 * should never be visible to the debuggee (except via mechanisms that are
154 * already asynchronous, like events), so the debuggee should be unable to
155 * detect non-debuggee microtasks running when they normally would not. As long
156 * as behavior *visible to the debuggee* is unaffected by the interruption, we
157 * have respected the spirit of the rule.
159 * Of course, even as we accept the general principle that interrupting the
160 * debuggee should have as little detectable effect as possible, we still permit
161 * the developer to do things like evaluate expressions at the console that have
162 * arbitrary effects on the debuggee's state—effects that could never occur
163 * naturally at that point in the program. But since these are explicitly
164 * requested by the developer, who presumably knows what they're doing, we
165 * support this as best we can. If the developer evaluates an expression in the
166 * console that resolves a promise, it seems most natural for the promise's
167 * reaction microtasks to run immediately, within the interruption. This is an
168 * 'unnatural' time for the microtasks to run, but no more unnatural than the
169 * evaluation that triggered them.
171 * So the overall behavior we need is as follows:
173 * - When the debugger interrupts a debuggee, the debuggee's microtask queue
174 * must be saved.
176 * - When debuggee execution resumes, the debuggee's microtask queue must be
177 * restored exactly as it was when the interruption occurred.
179 * - Non-debuggee task and microtask execution must take place normally during
180 * the interruption.
182 * Since each HTML task begins with an empty microtask queue, and it should not
183 * be possible for a task to mix debuggee and non-debuggee code, interrupting a
184 * debuggee should always find a microtask queue containing exclusively debuggee
185 * microtasks, if any. So saving and restoring the microtask queue should affect
186 * only the debuggee, not any non-debuggee content.
188 * AutoDebuggerJobQueueInterruption
189 * --------------------------------
191 * AutoDebuggerJobQueueInterruption is an RAII class, meant for use by the
192 * Debugger API implementation, that takes care of saving and restoring the
193 * queue.
195 * Constructing and initializing an instance of AutoDebuggerJobQueueInterruption
196 * sets aside the given JSContext's job queue, leaving the JSContext's queue
197 * empty. When the AutoDebuggerJobQueueInterruption instance is destroyed, it
198 * asserts that the JSContext's current job queue (holding jobs enqueued while
199 * the AutoDebuggerJobQueueInterruption was alive) is empty, and restores the
200 * saved queue to the JSContext.
202 * Since the Debugger API's behavior is up to us, we can specify that Debugger
203 * hooks begin execution with an empty job queue, and that we drain the queue
204 * after each hook function has run. This drain will be visible to debugger
205 * hooks, and makes hook calls resemble HTML tasks, with their own automatic
206 * microtask checkpoint. But, the drain will be invisible to the debuggee, as
207 * its queue is preserved across the hook invocation.
209 * To protect the debuggee's job queue, Debugger takes care to invoke callback
210 * functions only within the scope of an AutoDebuggerJobQueueInterruption
211 * instance.
213 * Why not let the hook functions themselves take care of this?
214 * ------------------------------------------------------------
216 * Certainly, we could leave responsibility for saving and restoring the job
217 * queue to the Debugger hook functions themselves.
219 * In fact, early versions of this change tried making the devtools server save
220 * and restore the queue explicitly, but because hooks are set and changed in
221 * numerous places, it was hard to be confident that every case had been
222 * covered, and it seemed that future changes could easily introduce new holes.
224 * Later versions of this change modified the accessor properties on the
225 * Debugger objects' prototypes to automatically protect the job queue when
226 * calling hooks, but the effect was essentially a monkeypatch applied to an API
227 * we defined and control, which doesn't make sense.
229 * In the end, since promises have become such a pervasive part of JavaScript
230 * programming, almost any imaginable use of Debugger would need to provide some
231 * kind of protection for the debuggee's job queue, so it makes sense to simply
232 * handle it once, carefully, in the implementation of Debugger itself.
234 class MOZ_RAII JS_PUBLIC_API AutoDebuggerJobQueueInterruption {
235 public:
236 explicit AutoDebuggerJobQueueInterruption();
237 ~AutoDebuggerJobQueueInterruption();
239 bool init(JSContext* cx);
240 bool initialized() const { return !!saved; }
243 * Drain the job queue. (In HTML terminology, perform a microtask checkpoint.)
245 * To make Debugger hook calls more like HTML tasks or ECMAScript jobs,
246 * Debugger promises that each hook begins execution with a clean microtask
247 * queue, and that a microtask checkpoint (queue drain) takes place after each
248 * hook returns, successfully or otherwise.
250 * To ensure these debugger-introduced microtask checkpoints serve only the
251 * hook's microtasks, and never affect the debuggee's, the Debugger API
252 * implementation uses only this method to perform the checkpoints, thereby
253 * statically ensuring that an AutoDebuggerJobQueueInterruption is in scope to
254 * protect the debuggee.
256 * SavedJobQueue implementations are required to assert that the queue is
257 * empty before restoring the debuggee's queue. If the Debugger API ever fails
258 * to perform a microtask checkpoint after calling a hook, that assertion will
259 * fail, catching the mistake.
261 void runJobs();
263 private:
264 JSContext* cx;
265 js::UniquePtr<JobQueue::SavedJobQueue> saved;
268 enum class PromiseRejectionHandlingState { Unhandled, Handled };
270 typedef void (*PromiseRejectionTrackerCallback)(
271 JSContext* cx, bool mutedErrors, JS::HandleObject promise,
272 JS::PromiseRejectionHandlingState state, void* data);
275 * Sets the callback that's invoked whenever a Promise is rejected without
276 * a rejection handler, and when a Promise that was previously rejected
277 * without a handler gets a handler attached.
279 extern JS_PUBLIC_API void SetPromiseRejectionTrackerCallback(
280 JSContext* cx, PromiseRejectionTrackerCallback callback,
281 void* data = nullptr);
284 * Inform the runtime that the job queue is empty and the embedding is going to
285 * execute its last promise job. The runtime may now choose to skip creating
286 * promise jobs for asynchronous execution and instead continue execution
287 * synchronously. More specifically, this optimization is used to skip the
288 * standard job queuing behavior for `await` operations in async functions.
290 * This function may be called before executing the last job in the job queue.
291 * When it was called, JobQueueMayNotBeEmpty must be called in order to restore
292 * the default job queuing behavior before the embedding enqueues its next job
293 * into the job queue.
295 extern JS_PUBLIC_API void JobQueueIsEmpty(JSContext* cx);
298 * Inform the runtime that job queue is no longer empty. The runtime can now no
299 * longer skip creating promise jobs for asynchronous execution, because
300 * pending jobs in the job queue must be executed first to preserve the FIFO
301 * (first in - first out) property of the queue. This effectively undoes
302 * JobQueueIsEmpty and re-enables the standard job queuing behavior.
304 * This function must be called whenever enqueuing a job to the job queue when
305 * JobQueueIsEmpty was called previously.
307 extern JS_PUBLIC_API void JobQueueMayNotBeEmpty(JSContext* cx);
310 * Returns a new instance of the Promise builtin class in the current
311 * compartment, with the right slot layout.
313 * The `executor` can be a `nullptr`. In that case, the only way to resolve or
314 * reject the returned promise is via the `JS::ResolvePromise` and
315 * `JS::RejectPromise` JSAPI functions.
317 extern JS_PUBLIC_API JSObject* NewPromiseObject(JSContext* cx,
318 JS::HandleObject executor);
321 * Returns true if the given object is an unwrapped PromiseObject, false
322 * otherwise.
324 extern JS_PUBLIC_API bool IsPromiseObject(JS::HandleObject obj);
327 * Returns the current compartment's original Promise constructor.
329 extern JS_PUBLIC_API JSObject* GetPromiseConstructor(JSContext* cx);
332 * Returns the current compartment's original Promise.prototype.
334 extern JS_PUBLIC_API JSObject* GetPromisePrototype(JSContext* cx);
336 // Keep this in sync with the PROMISE_STATE defines in SelfHostingDefines.h.
337 enum class PromiseState { Pending, Fulfilled, Rejected };
340 * Returns the given Promise's state as a JS::PromiseState enum value.
342 * Returns JS::PromiseState::Pending if the given object is a wrapper that
343 * can't safely be unwrapped.
345 extern JS_PUBLIC_API PromiseState GetPromiseState(JS::HandleObject promise);
348 * Returns the given Promise's process-unique ID.
350 JS_PUBLIC_API uint64_t GetPromiseID(JS::HandleObject promise);
353 * Returns the given Promise's result: either the resolution value for
354 * fulfilled promises, or the rejection reason for rejected ones.
356 extern JS_PUBLIC_API JS::Value GetPromiseResult(JS::HandleObject promise);
359 * Returns whether the given promise's rejection is already handled or not.
361 * The caller must check the given promise is rejected before checking it's
362 * handled or not.
364 extern JS_PUBLIC_API bool GetPromiseIsHandled(JS::HandleObject promise);
367 * Given a settled (i.e. fulfilled or rejected, not pending) promise, sets
368 * |promise.[[PromiseIsHandled]]| to true and removes it from the list of
369 * unhandled rejected promises.
371 extern JS_PUBLIC_API bool SetSettledPromiseIsHandled(JSContext* cx,
372 JS::HandleObject promise);
375 * Given a promise (settled or not), sets |promise.[[PromiseIsHandled]]| to true
376 * and removes it from the list of unhandled rejected promises if it's settled.
378 [[nodiscard]] extern JS_PUBLIC_API bool SetAnyPromiseIsHandled(
379 JSContext* cx, JS::HandleObject promise);
382 * Returns a js::SavedFrame linked list of the stack that lead to the given
383 * Promise's allocation.
385 extern JS_PUBLIC_API JSObject* GetPromiseAllocationSite(
386 JS::HandleObject promise);
388 extern JS_PUBLIC_API JSObject* GetPromiseResolutionSite(
389 JS::HandleObject promise);
391 #ifdef DEBUG
392 extern JS_PUBLIC_API void DumpPromiseAllocationSite(JSContext* cx,
393 JS::HandleObject promise);
395 extern JS_PUBLIC_API void DumpPromiseResolutionSite(JSContext* cx,
396 JS::HandleObject promise);
397 #endif
400 * Calls the current compartment's original Promise.resolve on the original
401 * Promise constructor, with `resolutionValue` passed as an argument.
403 extern JS_PUBLIC_API JSObject* CallOriginalPromiseResolve(
404 JSContext* cx, JS::HandleValue resolutionValue);
407 * Calls the current compartment's original Promise.reject on the original
408 * Promise constructor, with `resolutionValue` passed as an argument.
410 extern JS_PUBLIC_API JSObject* CallOriginalPromiseReject(
411 JSContext* cx, JS::HandleValue rejectionValue);
414 * Resolves the given Promise with the given `resolutionValue`.
416 * Calls the `resolve` function that was passed to the executor function when
417 * the Promise was created.
419 extern JS_PUBLIC_API bool ResolvePromise(JSContext* cx,
420 JS::HandleObject promiseObj,
421 JS::HandleValue resolutionValue);
424 * Rejects the given `promise` with the given `rejectionValue`.
426 * Calls the `reject` function that was passed to the executor function when
427 * the Promise was created.
429 extern JS_PUBLIC_API bool RejectPromise(JSContext* cx,
430 JS::HandleObject promiseObj,
431 JS::HandleValue rejectionValue);
434 * Create a Promise with the given fulfill/reject handlers, that will be
435 * fulfilled/rejected with the value/reason that the promise `promise` is
436 * fulfilled/rejected with.
438 * This function basically acts like `promise.then(onFulfilled, onRejected)`,
439 * except that its behavior is unaffected by changes to `Promise`,
440 * `Promise[Symbol.species]`, `Promise.prototype.then`, `promise.constructor`,
441 * `promise.then`, and so on.
443 * This function throws if `promise` is not a Promise from this or another
444 * realm.
446 * This function will assert if `onFulfilled` or `onRejected` is non-null and
447 * also not IsCallable.
449 extern JS_PUBLIC_API JSObject* CallOriginalPromiseThen(
450 JSContext* cx, JS::HandleObject promise, JS::HandleObject onFulfilled,
451 JS::HandleObject onRejected);
454 * Unforgeable, optimized version of the JS builtin Promise.prototype.then.
456 * Takes a Promise instance and nullable `onFulfilled`/`onRejected` callables to
457 * enqueue as reactions for that promise. In contrast to Promise.prototype.then,
458 * this doesn't create and return a new Promise instance.
460 * Throws a TypeError if `promise` isn't a Promise (or possibly a different
461 * error if it's a security wrapper or dead object proxy).
463 extern JS_PUBLIC_API bool AddPromiseReactions(JSContext* cx,
464 JS::HandleObject promise,
465 JS::HandleObject onFulfilled,
466 JS::HandleObject onRejected);
469 * Unforgeable, optimized version of the JS builtin Promise.prototype.then.
471 * Takes a Promise instance and nullable `onFulfilled`/`onRejected` callables to
472 * enqueue as reactions for that promise. In contrast to Promise.prototype.then,
473 * this doesn't create and return a new Promise instance.
475 * Throws a TypeError if `promise` isn't a Promise (or possibly a different
476 * error if it's a security wrapper or dead object proxy).
478 * If `onRejected` is null and `promise` is rejected, this function -- unlike
479 * the function above -- will not report an unhandled rejection.
481 extern JS_PUBLIC_API bool AddPromiseReactionsIgnoringUnhandledRejection(
482 JSContext* cx, JS::HandleObject promise, JS::HandleObject onFulfilled,
483 JS::HandleObject onRejected);
485 // This enum specifies whether a promise is expected to keep track of
486 // information that is useful for embedders to implement user activation
487 // behavior handling as specified in the HTML spec:
488 // https://html.spec.whatwg.org/multipage/interaction.html#triggered-by-user-activation
489 // By default, promises created by SpiderMonkey do not make any attempt to keep
490 // track of information about whether an activation behavior was being processed
491 // when the original promise in a promise chain was created. If the embedder
492 // sets either of the HadUserInteractionAtCreation or
493 // DidntHaveUserInteractionAtCreation flags on a promise after creating it,
494 // SpiderMonkey will propagate that flag to newly created promises when
495 // processing Promise#then and will make it possible to query this flag off of a
496 // promise further down the chain later using the
497 // GetPromiseUserInputEventHandlingState() API.
498 enum class PromiseUserInputEventHandlingState {
499 // Don't keep track of this state (default for all promises)
500 DontCare,
501 // Keep track of this state, the original promise in the chain was created
502 // while an activation behavior was being processed.
503 HadUserInteractionAtCreation,
504 // Keep track of this state, the original promise in the chain was created
505 // while an activation behavior was not being processed.
506 DidntHaveUserInteractionAtCreation
510 * Returns the given Promise's activation behavior state flag per above as a
511 * JS::PromiseUserInputEventHandlingState value. All promises are created with
512 * the DontCare state by default.
514 * Returns JS::PromiseUserInputEventHandlingState::DontCare if the given object
515 * is a wrapper that can't safely be unwrapped.
517 extern JS_PUBLIC_API PromiseUserInputEventHandlingState
518 GetPromiseUserInputEventHandlingState(JS::HandleObject promise);
521 * Sets the given Promise's activation behavior state flag per above as a
522 * JS::PromiseUserInputEventHandlingState value.
524 * Returns false if the given object is a wrapper that can't safely be
525 * unwrapped.
527 extern JS_PUBLIC_API bool SetPromiseUserInputEventHandlingState(
528 JS::HandleObject promise, JS::PromiseUserInputEventHandlingState state);
531 * Unforgeable version of the JS builtin Promise.all.
533 * Takes a HandleObjectVector of Promise objects and returns a promise that's
534 * resolved with an array of resolution values when all those promises have
535 * been resolved, or rejected with the rejection value of the first rejected
536 * promise.
538 * Asserts that all objects in the `promises` vector are, maybe wrapped,
539 * instances of `Promise` or a subclass of `Promise`.
541 extern JS_PUBLIC_API JSObject* GetWaitForAllPromise(
542 JSContext* cx, JS::HandleObjectVector promises);
545 * The Dispatchable interface allows the embedding to call SpiderMonkey
546 * on a JSContext thread when requested via DispatchToEventLoopCallback.
548 class JS_PUBLIC_API Dispatchable {
549 protected:
550 // Dispatchables are created and destroyed by SpiderMonkey.
551 Dispatchable() = default;
552 virtual ~Dispatchable() = default;
554 public:
555 // ShuttingDown indicates that SpiderMonkey should abort async tasks to
556 // expedite shutdown.
557 enum MaybeShuttingDown { NotShuttingDown, ShuttingDown };
559 // Called by the embedding after DispatchToEventLoopCallback succeeds.
560 virtual void run(JSContext* cx, MaybeShuttingDown maybeShuttingDown) = 0;
564 * Callback to dispatch a JS::Dispatchable to a JSContext's thread's event loop.
566 * The DispatchToEventLoopCallback set on a particular JSContext must accept
567 * JS::Dispatchable instances and arrange for their `run` methods to be called
568 * eventually on the JSContext's thread. This is used for cross-thread dispatch,
569 * so the callback itself must be safe to call from any thread.
571 * If the callback returns `true`, it must eventually run the given
572 * Dispatchable; otherwise, SpiderMonkey may leak memory or hang.
574 * The callback may return `false` to indicate that the JSContext's thread is
575 * shutting down and is no longer accepting runnables. Shutting down is a
576 * one-way transition: once the callback has rejected a runnable, it must reject
577 * all subsequently submitted runnables as well.
579 * To establish a DispatchToEventLoopCallback, the embedding may either call
580 * InitDispatchToEventLoop to provide its own, or call js::UseInternalJobQueues
581 * to select a default implementation built into SpiderMonkey. This latter
582 * depends on the embedding to call js::RunJobs on the JavaScript thread to
583 * process queued Dispatchables at appropriate times.
586 typedef bool (*DispatchToEventLoopCallback)(void* closure,
587 Dispatchable* dispatchable);
589 extern JS_PUBLIC_API void InitDispatchToEventLoop(
590 JSContext* cx, DispatchToEventLoopCallback callback, void* closure);
593 * When a JSRuntime is destroyed it implicitly cancels all async tasks in
594 * progress, releasing any roots held by the task. However, this is not soon
595 * enough for cycle collection, which needs to have roots dropped earlier so
596 * that the cycle collector can transitively remove roots for a future GC. For
597 * these and other cases, the set of pending async tasks can be canceled
598 * with this call earlier than JSRuntime destruction.
601 extern JS_PUBLIC_API void ShutdownAsyncTasks(JSContext* cx);
603 } // namespace JS
605 #endif // js_Promise_h