summaryrefslogtreecommitdiff
path: root/js/src/builtin/Promise.h
blob: 5e5c850d6002e333134f547ed82883d9f6132999 (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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * 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 builtin_Promise_h
#define builtin_Promise_h

#include "builtin/SelfHostingDefines.h"
#include "vm/NativeObject.h"

namespace js {

enum PromiseSlots {
    PromiseSlot_Flags = 0,
    PromiseSlot_ReactionsOrResult,
    PromiseSlot_RejectFunction,
    PromiseSlot_AwaitGenerator = PromiseSlot_RejectFunction,
    PromiseSlot_DebugInfo,
    PromiseSlots,
};

#define PROMISE_FLAG_RESOLVED  0x1
#define PROMISE_FLAG_FULFILLED 0x2
#define PROMISE_FLAG_HANDLED   0x4
#define PROMISE_FLAG_REPORTED  0x8
#define PROMISE_FLAG_DEFAULT_RESOLVING_FUNCTIONS 0x10
#define PROMISE_FLAG_ASYNC    0x20

class AutoSetNewObjectMetadata;

class PromiseObject : public NativeObject
{
  public:
    static const unsigned RESERVED_SLOTS = PromiseSlots;
    static const Class class_;
    static const Class protoClass_;
    static PromiseObject* create(JSContext* cx, HandleObject executor,
                                 HandleObject proto = nullptr, bool needsWrapping = false);

    static PromiseObject* createSkippingExecutor(JSContext* cx);

    static JSObject* unforgeableResolve(JSContext* cx, HandleValue value);
    static JSObject* unforgeableReject(JSContext* cx, HandleValue value);

    int32_t flags() {
        return getFixedSlot(PromiseSlot_Flags).toInt32();
    }
    JS::PromiseState state() {
        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;
    }
    Value reactions() {
        MOZ_ASSERT(state() == JS::PromiseState::Pending);
        return getFixedSlot(PromiseSlot_ReactionsOrResult);
    }
    Value value()  {
        MOZ_ASSERT(state() == JS::PromiseState::Fulfilled);
        return getFixedSlot(PromiseSlot_ReactionsOrResult);
    }
    Value reason() {
        MOZ_ASSERT(state() == JS::PromiseState::Rejected);
        return getFixedSlot(PromiseSlot_ReactionsOrResult);
    }
    Value valueOrReason()  {
        MOZ_ASSERT(state() != JS::PromiseState::Pending);
        return getFixedSlot(PromiseSlot_ReactionsOrResult);
    }

    static MOZ_MUST_USE bool resolve(JSContext* cx, Handle<PromiseObject*> promise,
                                     HandleValue resolutionValue);
    static MOZ_MUST_USE bool reject(JSContext* cx, Handle<PromiseObject*> promise,
                                    HandleValue rejectionValue);

    static void onSettled(JSContext* cx, Handle<PromiseObject*> promise);

    double allocationTime();
    double resolutionTime();
    JSObject* allocationSite();
    JSObject* resolutionSite();
    double lifetime();
    double timeToResolution() {
        MOZ_ASSERT(state() != JS::PromiseState::Pending);
        return resolutionTime() - allocationTime();
    }
    MOZ_MUST_USE bool dependentPromises(JSContext* cx, MutableHandle<GCVector<Value>> values);
    uint64_t getID();
    bool isUnhandled() {
        MOZ_ASSERT(state() == JS::PromiseState::Rejected);
        return !(flags() & PROMISE_FLAG_HANDLED);
    }
    void markAsReported() {
        MOZ_ASSERT(isUnhandled());
        int32_t flags = getFixedSlot(PromiseSlot_Flags).toInt32();
        setFixedSlot(PromiseSlot_Flags, Int32Value(flags | PROMISE_FLAG_REPORTED));
    }
};

/**
 * Unforgeable version of the JS builtin Promise.all.
 *
 * Takes an AutoObjectVector of Promise objects and returns a promise that's
 * resolved with an array of resolution values when all those promises have
 * been resolved, or rejected with the rejection value of the first rejected
 * promise.
 *
 * Asserts that all objects in the `promises` vector are, maybe wrapped,
 * instances of `Promise` or a subclass of `Promise`.
 */
MOZ_MUST_USE JSObject*
GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises);

enum class CreateDependentPromise {
    Always,
    SkipIfCtorUnobservable,
    Never
};

/**
 * Enqueues resolve/reject reactions in the given Promise's reactions lists
 * as though calling the original value of Promise.prototype.then.
 *
 * If the `createDependent` flag is not set, no dependent Promise will be
 * created. This is used internally to implement DOM functionality.
 * Note: In this case, the reactions pushed using this function contain a
 * `promise` field that can contain null. That field is only ever used by
 * devtools, which have to treat these reactions specially.
 */
MOZ_MUST_USE bool
OriginalPromiseThen(JSContext* cx, Handle<PromiseObject*> promise,
                    HandleValue onFulfilled, HandleValue onRejected,
                    MutableHandleObject dependent, CreateDependentPromise createDependent);

/**
 * PromiseResolve ( C, x )
 *
 * The abstract operation PromiseResolve, given a constructor and a value,
 * returns a new promise resolved with that value.
 */
MOZ_MUST_USE JSObject*
PromiseResolve(JSContext* cx, HandleObject constructor, HandleValue value);

MOZ_MUST_USE bool
RejectPromiseWithPendingError(JSContext* cx, Handle<PromiseObject*> promise);

MOZ_MUST_USE PromiseObject*
CreatePromiseObjectForAsync(JSContext* cx, HandleValue generatorVal);

MOZ_MUST_USE bool
IsPromiseForAsync(JSObject* promise);

MOZ_MUST_USE bool
AsyncFunctionReturned(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value);

MOZ_MUST_USE bool
AsyncFunctionThrown(JSContext* cx, Handle<PromiseObject*> resultPromise);

MOZ_MUST_USE bool
AsyncFunctionAwait(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleValue value);

class AsyncGeneratorObject;

MOZ_MUST_USE bool
AsyncGeneratorAwait(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj, HandleValue value);

MOZ_MUST_USE bool
AsyncGeneratorResolve(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
                      HandleValue value, bool done);

MOZ_MUST_USE bool
AsyncGeneratorReject(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj,
                     HandleValue exception);

MOZ_MUST_USE bool
AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal, CompletionKind completionKind,
                      HandleValue completionValue, MutableHandleValue result);

bool
AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind completionKind);

class MOZ_NON_TEMPORARY_CLASS PromiseLookup final
{
    /*
     * A PromiseLookup holds the following:
     *
     *  Promise's shape (promiseConstructorShape_)
     *       To ensure that Promise has not been modified.
     *
     *  Promise.prototype's shape (promiseProtoShape_)
     *      To ensure that Promise.prototype has not been modified.
     *
     *  Promise's shape for the @@species getter. (promiseSpeciesShape_)
     *      To quickly retrieve the @@species getter for Promise.
     *
     *  Promise's slot number for resolve (promiseResolveSlot_)
     *      To quickly retrieve the Promise.resolve function.
     *
     *  Promise.prototype's slot number for constructor (promiseProtoConstructorSlot_)
     *      To quickly retrieve the Promise.prototype.constructor property.
     *
     *  Promise.prototype's slot number for then (promiseProtoThenSlot_)
     *      To quickly retrieve the Promise.prototype.then function.
     *
     * MOZ_INIT_OUTSIDE_CTOR fields below are set in |initialize()|.  The
     * constructor only initializes a |state_| field, that defines whether the
     * other fields are accessible.
     */

    // Shape of matching Promise object.
    MOZ_INIT_OUTSIDE_CTOR Shape* promiseConstructorShape_;

#ifdef DEBUG
    // Accessor Shape containing the @@species property.
    // See isPromiseStateStillSane() for why this field is debug-only.
    MOZ_INIT_OUTSIDE_CTOR Shape* promiseSpeciesShape_;
#endif

    // Shape of matching Promise.prototype object.
    MOZ_INIT_OUTSIDE_CTOR Shape* promiseProtoShape_;

    // Slots Promise.resolve, Promise.prototype.constructor, and
    // Promise.prototype.then.
    MOZ_INIT_OUTSIDE_CTOR uint32_t promiseResolveSlot_;
    MOZ_INIT_OUTSIDE_CTOR uint32_t promiseProtoConstructorSlot_;
    MOZ_INIT_OUTSIDE_CTOR uint32_t promiseProtoThenSlot_;

    enum class State : uint8_t {
        // Flags marking the lazy initialization of the above fields.
        Uninitialized,
        Initialized,

        // The disabled flag is set when we don't want to try optimizing
        // anymore because core objects were changed.
        Disabled
    };

    State state_ = State::Uninitialized;

    // Initialize the internal fields.
    //
    // The cache is successfully initialized iff
    // 1. Promise and Promise.prototype classes are initialized.
    // 2. Promise.prototype.constructor is equal to Promise.
    // 3. Promise.prototype.then is the original `then` function.
    // 4. Promise[@@species] is the original @@species getter.
    // 5. Promise.resolve is the original `resolve` function.
    void initialize(JSContext* cx);

    // Reset the cache.
    void reset();

    // Check if the global promise-related objects have not been messed with
    // in a way that would disable this cache.
    bool isPromiseStateStillSane(JSContext* cx);

    // Flags to control whether or not ensureInitialized() is allowed to
    // reinitialize the cache when the Promise state is no longer sane.
    enum class Reinitialize : bool {
        Allowed,
        Disallowed
    };

    // Return true if the lookup cache is properly initialized for usage.
    bool ensureInitialized(JSContext* cx, Reinitialize reinitialize);

    // Return true if the prototype of the given Promise object is
    // Promise.prototype and the object doesn't shadow properties from
    // Promise.prototype.
    bool hasDefaultProtoAndNoShadowedProperties(JSContext* cx, PromiseObject* promise);

    // Return true if the given Promise object uses the default @@species,
    // "constructor", and "then" properties.
    bool isDefaultInstance(JSContext* cx, PromiseObject* promise, Reinitialize reinitialize);

    // Return the built-in Promise constructor or null if not yet initialized.
    static JSFunction* getPromiseConstructor(JSContext* cx);

    // Return the built-in Promise prototype or null if not yet initialized.
    static NativeObject* getPromisePrototype(JSContext* cx);

    // Return true if the slot contains the given native.
    static bool isDataPropertyNative(JSContext* cx, NativeObject* obj, uint32_t slot,
                                     JSNative native);

    // Return true if the accessor shape contains the given native.
    static bool isAccessorPropertyNative(JSContext* cx, Shape* shape, JSNative native);

  public:
    /** Construct a |PromiseSpeciesLookup| in the uninitialized state. */
    PromiseLookup() {
        reset();
    }

    // Return true if the Promise constructor and Promise.prototype still use
    // the default built-in functions.
    bool isDefaultPromiseState(JSContext* cx);

    // Return true if the given Promise object uses the default @@species,
    // "constructor", and "then" properties.
    bool isDefaultInstance(JSContext* cx, PromiseObject* promise) {
        return isDefaultInstance(cx, promise, Reinitialize::Allowed);
    }

    // Return true if the given Promise object uses the default @@species,
    // "constructor", and "then" properties.
    bool isDefaultInstanceWhenPromiseStateIsSane(JSContext* cx, PromiseObject* promise) {
        return isDefaultInstance(cx, promise, Reinitialize::Disallowed);
    }

    // Purge the cache and all info associated with it.
    void purge() {
        if (state_ == State::Initialized)
            reset();
    }
};

/**
 * A PromiseTask represents a task that can be dispatched to a helper thread
 * (via StartPromiseTask), executed (by implementing PromiseTask::execute()),
 * and then resolved back on the original JSContext owner thread.
 * Because it contains a PersistentRooted, a PromiseTask will only be destroyed
 * on the JSContext's owner thread.
 */
class PromiseTask : public JS::AsyncTask
{
    JSRuntime* runtime_;
    PersistentRooted<PromiseObject*> promise_;

    // PromiseTask implements JS::AsyncTask and prevents derived classes from
    // overriding; derived classes should implement the new pure virtual
    // functions introduced below. Both of these methods 'delete this'.
    void finish(JSContext* cx) override final;
    void cancel(JSContext* cx) override final;

  protected:
    // Called by PromiseTask on the JSContext's owner thread after execute()
    // completes on the helper thread, assuming JS::FinishAsyncTaskCallback
    // succeeds. After this method returns, the task will be deleted.
    virtual bool finishPromise(JSContext* cx, Handle<PromiseObject*> promise) = 0;

  public:
    PromiseTask(JSContext* cx, Handle<PromiseObject*> promise);
    ~PromiseTask();
    JSRuntime* runtime() const { return runtime_; }

    // Called on a helper thread after StartAsyncTask. After execute()
    // completes, the JS::FinishAsyncTaskCallback will be called. If this fails
    // the task will be enqueued for deletion at some future point without ever
    // calling finishPromise().
    virtual void execute() = 0;

    // May be called in the absence of helper threads to synchronously execute
    // and finish a PromiseTask.
    bool executeAndFinish(JSContext* cx);
};

} // namespace js

#endif /* builtin_Promise_h */