tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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