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