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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #ifndef vm_PromiseObject_h
8 #define vm_PromiseObject_h
9
10 #include "mozilla/Assertions.h" // MOZ_ASSERT
11
12 #include <stdint.h> // int32_t, uint64_t
13
14 #include "js/Class.h" // JSClass
15 #include "js/Promise.h" // JS::PromiseState
16 #include "js/RootingAPI.h" // JS::{,Mutable}Handle
17 #include "js/Value.h" // JS::Value, JS::Int32Value, JS::UndefinedHandleValue
18 #include "vm/NativeObject.h" // js::NativeObject
19
20 class JS_PUBLIC_API JSObject;
21
22 namespace js {
23
24 class SavedFrame;
25
26 enum PromiseSlots {
27 // Int32 value with PROMISE_FLAG_* flags below.
28 PromiseSlot_Flags = 0,
29
30 // * if this promise is pending, reaction objects
31 // * undefined if there's no reaction
32 // * maybe-wrapped PromiseReactionRecord if there's only one reacion
33 // * dense array if there are two or more more reactions
34 // * if this promise is fulfilled, the resolution value
35 // * if this promise is rejected, the reason for the rejection
36 PromiseSlot_ReactionsOrResult,
37
38 // * if this promise is pending, resolve/reject functions.
39 // This slot holds only the reject function. The resolve function is
40 // reachable from the reject function's extended slot.
41 // * if this promise is either fulfilled or rejected, undefined
42 PromiseSlot_RejectFunction,
43
44 // Promise object's debug info, which is created on demand.
45 // * if this promise has no debug info, undefined
46 // * if this promise contains only its process-unique ID, the ID's number
47 // value
48 // * otherwise a PromiseDebugInfo object
49 PromiseSlot_DebugInfo,
50
51 PromiseSlots,
52 };
53
54 // This promise is either fulfilled or rejected.
55 // If this flag is not set, this promise is pending.
56 #define PROMISE_FLAG_RESOLVED 0x1
57
58 // If this flag and PROMISE_FLAG_RESOLVED are set, this promise is fulfilled.
59 // If only PROMISE_FLAG_RESOLVED is set, this promise is rejected.
60 #define PROMISE_FLAG_FULFILLED 0x2
61
62 // Indicates the promise has ever had a fulfillment or rejection handler;
63 // used in unhandled rejection tracking.
64 #define PROMISE_FLAG_HANDLED 0x4
65
66 // This promise uses the default resolving functions.
67 // The PromiseSlot_RejectFunction slot is not used.
68 #define PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS 0x08
69
70 // This promise is either the return value of an async function invocation or
71 // an async generator's method.
72 #define PROMISE_FLAG_ASYNC 0x10
73
74 // This promise knows how to propagate information required to keep track of
75 // whether an activation behavior was in progress when the original promise in
76 // the promise chain was created. This is a concept defined in the HTML spec:
77 // https://html.spec.whatwg.org/multipage/interaction.html#triggered-by-user-activation
78 // It is used by the embedder in order to request SpiderMonkey to keep track of
79 // this information in a Promise, and also to propagate it to newly created
80 // promises while processing Promise#then.
81 #define PROMISE_FLAG_REQUIRES_USER_INTERACTION_HANDLING 0x20
82
83 // This flag indicates whether an activation behavior was in progress when the
84 // original promise in the promise chain was created. Activation behavior is a
85 // concept defined by the HTML spec:
86 // https://html.spec.whatwg.org/multipage/interaction.html#triggered-by-user-activation
87 // This flag is only effective when the
88 // PROMISE_FLAG_REQUIRES_USER_INTERACTION_HANDLING is set.
89 #define PROMISE_FLAG_HAD_USER_INTERACTION_UPON_CREATION 0x40
90
91 struct PromiseReactionRecordBuilder;
92
93 class PromiseObject : public NativeObject {
94 public:
95 static const unsigned RESERVED_SLOTS = PromiseSlots;
96 static const JSClass class_;
97 static const JSClass protoClass_;
98 static PromiseObject* create(JSContext* cx, JS::Handle<JSObject*> executor,
99 JS::Handle<JSObject*> proto = nullptr,
100 bool needsWrapping = false);
101
102 static PromiseObject* createSkippingExecutor(JSContext* cx);
103
104 // Create an instance of the original Promise binding, rejected with the given
105 // value.
106 static PromiseObject* unforgeableReject(JSContext* cx,
107 JS::Handle<JS::Value> value);
108
109 // Create an instance of the original Promise binding, resolved with the given
110 // value.
111 //
112 // However, if |value| is itself a promise -- including from another realm --
113 // |value| itself will in some circumstances be returned. This sadly means
114 // this function must return |JSObject*| and can't return |PromiseObject*|.
115 static JSObject* unforgeableResolve(JSContext* cx,
116 JS::Handle<JS::Value> value);
117
118 // Create an instance of the original Promise binding, resolved with the given
119 // value *that is not a promise* -- from this realm/compartment or from any
120 // other.
121 //
122 // If you don't know for certain that your value will never be a promise, use
123 // |PromiseObject::unforgeableResolve| instead.
124 //
125 // Use |PromiseResolvedWithUndefined| (defined below) if your value is always
126 // |undefined|.
127 static PromiseObject* unforgeableResolveWithNonPromise(
128 JSContext* cx, JS::Handle<JS::Value> value);
129
flags()130 int32_t flags() { return getFixedSlot(PromiseSlot_Flags).toInt32(); }
131
setHandled()132 void setHandled() {
133 setFixedSlot(PromiseSlot_Flags,
134 JS::Int32Value(flags() | PROMISE_FLAG_HANDLED));
135 }
136
state()137 JS::PromiseState state() {
138 int32_t flags = this->flags();
139 if (!(flags & PROMISE_FLAG_RESOLVED)) {
140 MOZ_ASSERT(!(flags & PROMISE_FLAG_FULFILLED));
141 return JS::PromiseState::Pending;
142 }
143 if (flags & PROMISE_FLAG_FULFILLED) {
144 return JS::PromiseState::Fulfilled;
145 }
146 return JS::PromiseState::Rejected;
147 }
148
reactions()149 JS::Value reactions() {
150 MOZ_ASSERT(state() == JS::PromiseState::Pending);
151 return getFixedSlot(PromiseSlot_ReactionsOrResult);
152 }
153
value()154 JS::Value value() {
155 MOZ_ASSERT(state() == JS::PromiseState::Fulfilled);
156 return getFixedSlot(PromiseSlot_ReactionsOrResult);
157 }
158
reason()159 JS::Value reason() {
160 MOZ_ASSERT(state() == JS::PromiseState::Rejected);
161 return getFixedSlot(PromiseSlot_ReactionsOrResult);
162 }
163
valueOrReason()164 JS::Value valueOrReason() {
165 MOZ_ASSERT(state() != JS::PromiseState::Pending);
166 return getFixedSlot(PromiseSlot_ReactionsOrResult);
167 }
168
169 [[nodiscard]] static bool resolve(JSContext* cx,
170 JS::Handle<PromiseObject*> promise,
171 JS::Handle<JS::Value> resolutionValue);
172 [[nodiscard]] static bool reject(JSContext* cx,
173 JS::Handle<PromiseObject*> promise,
174 JS::Handle<JS::Value> rejectionValue);
175
176 static void onSettled(JSContext* cx, JS::Handle<PromiseObject*> promise,
177 JS::Handle<js::SavedFrame*> rejectionStack);
178
179 double allocationTime();
180 double resolutionTime();
181 JSObject* allocationSite();
182 JSObject* resolutionSite();
183 double lifetime();
timeToResolution()184 double timeToResolution() {
185 MOZ_ASSERT(state() != JS::PromiseState::Pending);
186 return resolutionTime() - allocationTime();
187 }
188
189 [[nodiscard]] bool dependentPromises(
190 JSContext* cx, JS::MutableHandle<GCVector<Value>> values);
191
192 // Return the process-unique ID of this promise. Only used by the debugger.
193 uint64_t getID();
194
195 // Apply 'builder' to each reaction record in this promise's list. Used only
196 // by the Debugger API.
197 //
198 // The context cx need not be same-compartment with this promise. (In typical
199 // use, cx is in a debugger compartment, and this promise is in a debuggee
200 // compartment.) This function presents data to builder exactly as it appears
201 // in the reaction records, so the values passed to builder methods could
202 // potentially be cross-compartment with both cx and this promise.
203 //
204 // If this function encounters an error, it will report it to 'cx' and return
205 // false. If a builder call returns false, iteration stops, and this function
206 // returns false; the build should set an error on 'cx' as appropriate.
207 // Otherwise, this function returns true.
208 [[nodiscard]] bool forEachReactionRecord(
209 JSContext* cx, PromiseReactionRecordBuilder& builder);
210
isUnhandled()211 bool isUnhandled() {
212 MOZ_ASSERT(state() == JS::PromiseState::Rejected);
213 return !(flags() & PROMISE_FLAG_HANDLED);
214 }
215
requiresUserInteractionHandling()216 bool requiresUserInteractionHandling() {
217 return (flags() & PROMISE_FLAG_REQUIRES_USER_INTERACTION_HANDLING);
218 }
219
220 void setRequiresUserInteractionHandling(bool state);
221
hadUserInteractionUponCreation()222 bool hadUserInteractionUponCreation() {
223 return (flags() & PROMISE_FLAG_HAD_USER_INTERACTION_UPON_CREATION);
224 }
225
226 void setHadUserInteractionUponCreation(bool state);
227
228 void copyUserInteractionFlagsFrom(PromiseObject& rhs);
229 };
230
231 /**
232 * Create an instance of the original Promise binding, resolved with the value
233 * |undefined|.
234 */
PromiseResolvedWithUndefined(JSContext * cx)235 inline PromiseObject* PromiseResolvedWithUndefined(JSContext* cx) {
236 return PromiseObject::unforgeableResolveWithNonPromise(
237 cx, JS::UndefinedHandleValue);
238 }
239
240 } // namespace js
241
242 #endif // vm_PromiseObject_h
243