summaryrefslogtreecommitdiffstats
path: root/js/src/vm/PromiseObject.h
blob: cdf0e661015649238c3007e12bbe49ac6b30292f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef vm_PromiseObject_h
#define vm_PromiseObject_h

#include "mozilla/Assertions.h"  // MOZ_ASSERT

#include <stdint.h>  // int32_t, uint64_t

#include "js/Class.h"       // JSClass
#include "js/Promise.h"     // JS::PromiseState
#include "js/RootingAPI.h"  // JS::{,Mutable}Handle
#include "js/Value.h"  // JS::Value, JS::Int32Value, JS::UndefinedHandleValue
#include "vm/NativeObject.h"  // js::NativeObject

class JS_PUBLIC_API JSObject;

namespace js {

class JS_PUBLIC_API GenericPrinter;
class JSONPrinter;

class SavedFrame;

enum PromiseSlots {
  // Int32 value with PROMISE_FLAG_* flags below.
  PromiseSlot_Flags = 0,

  // * if this promise is pending, reaction objects
  //     * undefined if there's no reaction
  //     * maybe-wrapped PromiseReactionRecord if there's only one reacion
  //     * dense array if there are two or more more reactions
  // * if this promise is fulfilled, the resolution value
  // * if this promise is rejected, the reason for the rejection
  PromiseSlot_ReactionsOrResult,

  // * if this promise is pending, resolve/reject functions.
  //   This slot holds only the reject function. The resolve function is
  //   reachable from the reject function's extended slot.
  // * if this promise is either fulfilled or rejected, undefined
  PromiseSlot_RejectFunction,

  // Promise object's debug info, which is created on demand.
  // * if this promise has no debug info, undefined
  // * if this promise contains only its process-unique ID, the ID's number
  //   value
  // * otherwise a PromiseDebugInfo object
  PromiseSlot_DebugInfo,

  PromiseSlots,
};

// This promise is either fulfilled or rejected.
// If this flag is not set, this promise is pending.
#define PROMISE_FLAG_RESOLVED 0x1

// If this flag and PROMISE_FLAG_RESOLVED are set, this promise is fulfilled.
// If only PROMISE_FLAG_RESOLVED is set, this promise is rejected.
#define PROMISE_FLAG_FULFILLED 0x2

// Indicates the promise has ever had a fulfillment or rejection handler;
// used in unhandled rejection tracking.
#define PROMISE_FLAG_HANDLED 0x4

// This promise uses the default resolving functions.
// The PromiseSlot_RejectFunction slot is not used.
#define PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS 0x08

// This promise's Promise Resolve Function's [[AlreadyResolved]].[[Value]] is
// set to true.
//
// Valid only for promises with PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS.
// For promises without PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS, Promise
// Resolve/Reject Function's "Promise" slot represents the value.
#define PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS_ALREADY_RESOLVED 0x10

// This promise is either the return value of an async function invocation or
// an async generator's method.
#define PROMISE_FLAG_ASYNC 0x20

// This promise knows how to propagate information required to keep track of
// whether an activation behavior was in progress when the original promise in
// the promise chain was created.  This is a concept defined in the HTML spec:
// https://html.spec.whatwg.org/multipage/interaction.html#triggered-by-user-activation
// It is used by the embedder in order to request SpiderMonkey to keep track of
// this information in a Promise, and also to propagate it to newly created
// promises while processing Promise#then.
#define PROMISE_FLAG_REQUIRES_USER_INTERACTION_HANDLING 0x40

// This flag indicates whether an activation behavior was in progress when the
// original promise in the promise chain was created.  Activation behavior is a
// concept defined by the HTML spec:
// https://html.spec.whatwg.org/multipage/interaction.html#triggered-by-user-activation
// This flag is only effective when the
// PROMISE_FLAG_REQUIRES_USER_INTERACTION_HANDLING is set.
#define PROMISE_FLAG_HAD_USER_INTERACTION_UPON_CREATION 0x80

struct PromiseReactionRecordBuilder;

class PromiseObject : public NativeObject {
 public:
  static const unsigned RESERVED_SLOTS = PromiseSlots;
  static const JSClass class_;
  static const JSClass protoClass_;
  static PromiseObject* create(JSContext* cx, JS::Handle<JSObject*> executor,
                               JS::Handle<JSObject*> proto = nullptr,
                               bool needsWrapping = false);

  static PromiseObject* createSkippingExecutor(JSContext* cx);

  // Create an instance of the original Promise binding, rejected with the given
  // value.
  static PromiseObject* unforgeableReject(JSContext* cx,
                                          JS::Handle<JS::Value> value);

  // Create an instance of the original Promise binding, resolved with the given
  // value.
  //
  // However, if |value| is itself a promise -- including from another realm --
  // |value| itself will in some circumstances be returned.  This sadly means
  // this function must return |JSObject*| and can't return |PromiseObject*|.
  static JSObject* unforgeableResolve(JSContext* cx,
                                      JS::Handle<JS::Value> value);

  // Create an instance of the original Promise binding, resolved with the given
  // value *that is not a promise* -- from this realm/compartment or from any
  // other.
  //
  // If you don't know for certain that your value will never be a promise, use
  // |PromiseObject::unforgeableResolve| instead.
  //
  // Use |PromiseResolvedWithUndefined| (defined below) if your value is always
  // |undefined|.
  static PromiseObject* unforgeableResolveWithNonPromise(
      JSContext* cx, JS::Handle<JS::Value> value);

  int32_t flags() const { return getFixedSlot(PromiseSlot_Flags).toInt32(); }

  void setHandled() {
    setFixedSlot(PromiseSlot_Flags,
                 JS::Int32Value(flags() | PROMISE_FLAG_HANDLED));
  }

  JS::PromiseState state() const {
    int32_t flags = this->flags();
    if (!(flags & PROMISE_FLAG_RESOLVED)) {
      MOZ_ASSERT(!(flags & PROMISE_FLAG_FULFILLED));
      return JS::PromiseState::Pending;
    }
    if (flags & PROMISE_FLAG_FULFILLED) {
      return JS::PromiseState::Fulfilled;
    }
    return JS::PromiseState::Rejected;
  }

  JS::Value reactions() const {
    MOZ_ASSERT(state() == JS::PromiseState::Pending);
    return getFixedSlot(PromiseSlot_ReactionsOrResult);
  }

  JS::Value value() const {
    MOZ_ASSERT(state() == JS::PromiseState::Fulfilled);
    return getFixedSlot(PromiseSlot_ReactionsOrResult);
  }

  JS::Value reason() const {
    MOZ_ASSERT(state() == JS::PromiseState::Rejected);
    return getFixedSlot(PromiseSlot_ReactionsOrResult);
  }

  JS::Value valueOrReason() const {
    MOZ_ASSERT(state() != JS::PromiseState::Pending);
    return getFixedSlot(PromiseSlot_ReactionsOrResult);
  }

  [[nodiscard]] static bool resolve(JSContext* cx,
                                    JS::Handle<PromiseObject*> promise,
                                    JS::Handle<JS::Value> resolutionValue);
  [[nodiscard]] static bool reject(JSContext* cx,
                                   JS::Handle<PromiseObject*> promise,
                                   JS::Handle<JS::Value> rejectionValue);

  static void onSettled(JSContext* cx, JS::Handle<PromiseObject*> promise,
                        JS::Handle<js::SavedFrame*> rejectionStack);

  double allocationTime();
  double resolutionTime();
  JSObject* allocationSite();
  JSObject* resolutionSite();
  double lifetime();
  double timeToResolution() {
    MOZ_ASSERT(state() != JS::PromiseState::Pending);
    return resolutionTime() - allocationTime();
  }

  [[nodiscard]] bool dependentPromises(
      JSContext* cx, JS::MutableHandle<GCVector<Value>> values);

  // Return the process-unique ID of this promise. Only used by the debugger.
  uint64_t getID();

  // Apply 'builder' to each reaction record in this promise's list. Used only
  // by the Debugger API.
  //
  // The context cx need not be same-compartment with this promise. (In typical
  // use, cx is in a debugger compartment, and this promise is in a debuggee
  // compartment.) This function presents data to builder exactly as it appears
  // in the reaction records, so the values passed to builder methods could
  // potentially be cross-compartment with both cx and this promise.
  //
  // If this function encounters an error, it will report it to 'cx' and return
  // false. If a builder call returns false, iteration stops, and this function
  // returns false; the build should set an error on 'cx' as appropriate.
  // Otherwise, this function returns true.
  [[nodiscard]] bool forEachReactionRecord(
      JSContext* cx, PromiseReactionRecordBuilder& builder);

  bool isUnhandled() {
    MOZ_ASSERT(state() == JS::PromiseState::Rejected);
    return !(flags() & PROMISE_FLAG_HANDLED);
  }

  bool requiresUserInteractionHandling() {
    return (flags() & PROMISE_FLAG_REQUIRES_USER_INTERACTION_HANDLING);
  }

  void setRequiresUserInteractionHandling(bool state);

  bool hadUserInteractionUponCreation() {
    return (flags() & PROMISE_FLAG_HAD_USER_INTERACTION_UPON_CREATION);
  }

  void setHadUserInteractionUponCreation(bool state);

  void copyUserInteractionFlagsFrom(PromiseObject& rhs);

#if defined(DEBUG) || defined(JS_JITSPEW)
  void dumpOwnFields(js::JSONPrinter& json) const;
  void dumpOwnStringContent(js::GenericPrinter& out) const;
#endif
};

/**
 * Create an instance of the original Promise binding, resolved with the value
 * |undefined|.
 */
inline PromiseObject* PromiseResolvedWithUndefined(JSContext* cx) {
  return PromiseObject::unforgeableResolveWithNonPromise(
      cx, JS::UndefinedHandleValue);
}

}  // namespace js

#endif  // vm_PromiseObject_h