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