forked from Floorp-Projects/Floorp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPromise.h
510 lines (415 loc) · 19.5 KB
/
Promise.h
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
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
/* -*- 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 builtin_Promise_h
#define builtin_Promise_h
#include "builtin/SelfHostingDefines.h"
#include "threading/ConditionVariable.h"
#include "threading/Mutex.h"
#include "vm/NativeObject.h"
namespace js {
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
// * (special case) if this promise is the return value of an async function
// invocation, the generator object for the function's internal generator
PromiseSlot_RejectFunction,
PromiseSlot_AwaitGenerator = 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 is the return value of an async function invocation.
#define PROMISE_FLAG_ASYNC 0x10
// 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 0x20
// 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 0x40
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);
// Return the process-unique ID of this promise. Only used by the debugger.
uint64_t getID();
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);
};
/**
* 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);
// Whether to create a promise as the return value of Promise#{then,catch}.
// If the return value is known to be unused, and if the operation is known
// to be unobservable, we can skip creating the promise.
enum class CreateDependentPromise {
// The return value is not known to be unused.
Always,
// The return value is known to be unused.
SkipIfCtorUnobservable,
// The return value is known to be unused, and the operation is known
// to be unobservable.
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.
*
* Asserts that `promiseObj` is a, maybe wrapped, instance of Promise.
*/
MOZ_MUST_USE bool OriginalPromiseThen(JSContext* cx, HandleObject promiseObj,
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);
/**
* Create the promise object which will be used as the return value of an async
* function.
*/
MOZ_MUST_USE PromiseObject* CreatePromiseObjectForAsync(
JSContext* cx, HandleValue generatorVal);
/**
* Returns true if the given object is a promise created by
* CreatePromiseObjectForAsync function.
*/
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);
// If the await operation can be skipped and the resolution value for `val` can
// be acquired, stored the resolved value to `resolved` and `true` to
// `*canSkip`. Otherwise, stores `false` to `*canSkip`.
MOZ_MUST_USE bool TrySkipAwait(JSContext* cx, HandleValue val, bool* canSkip,
MutableHandleValue resolved);
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 {
// clang-format off
/*
* 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.
*/
// clang-format on
// 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();
}
}
};
// An OffThreadPromiseTask holds a rooted Promise JSObject while executing an
// off-thread task (defined by the subclass) that needs to resolve the Promise
// on completion. Because OffThreadPromiseTask contains a PersistentRooted, it
// must be destroyed on an active JSContext thread of the Promise's JSRuntime.
// OffThreadPromiseTasks may be run off-thread in various ways (e.g., see
// PromiseHelperTask). At any time, the task can be dispatched to an active
// JSContext of the Promise's JSRuntime by calling dispatchResolve().
class OffThreadPromiseTask : public JS::Dispatchable {
friend class OffThreadPromiseRuntimeState;
JSRuntime* runtime_;
PersistentRooted<PromiseObject*> promise_;
bool registered_;
void operator=(const OffThreadPromiseTask&) = delete;
OffThreadPromiseTask(const OffThreadPromiseTask&) = delete;
protected:
OffThreadPromiseTask(JSContext* cx, Handle<PromiseObject*> promise);
// To be called by OffThreadPromiseTask and implemented by the derived class.
virtual bool resolve(JSContext* cx, Handle<PromiseObject*> promise) = 0;
// JS::Dispatchable implementation. Ends with 'delete this'.
void run(JSContext* cx, MaybeShuttingDown maybeShuttingDown) final;
public:
~OffThreadPromiseTask() override;
// Initializing an OffThreadPromiseTask informs the runtime that it must
// wait on shutdown for this task to rejoin the active JSContext by calling
// dispatchResolveAndDestroy().
bool init(JSContext* cx);
// An initialized OffThreadPromiseTask can be dispatched to an active
// JSContext of its Promise's JSRuntime from any thread. Normally, this will
// lead to resolve() being called on JSContext thread, given the Promise.
// However, if shutdown interrupts, resolve() may not be called, though the
// OffThreadPromiseTask will be destroyed on a JSContext thread.
void dispatchResolveAndDestroy();
};
using OffThreadPromiseTaskSet =
HashSet<OffThreadPromiseTask*, DefaultHasher<OffThreadPromiseTask*>,
SystemAllocPolicy>;
using DispatchableVector = Vector<JS::Dispatchable*, 0, SystemAllocPolicy>;
class OffThreadPromiseRuntimeState {
friend class OffThreadPromiseTask;
// These fields are initialized once before any off-thread usage and thus do
// not require a lock.
JS::DispatchToEventLoopCallback dispatchToEventLoopCallback_;
void* dispatchToEventLoopClosure_;
// These fields are mutated by any thread and are guarded by mutex_.
Mutex mutex_;
ConditionVariable allCanceled_;
OffThreadPromiseTaskSet live_;
size_t numCanceled_;
DispatchableVector internalDispatchQueue_;
ConditionVariable internalDispatchQueueAppended_;
bool internalDispatchQueueClosed_;
static bool internalDispatchToEventLoop(void*, JS::Dispatchable*);
bool usingInternalDispatchQueue() const;
void operator=(const OffThreadPromiseRuntimeState&) = delete;
OffThreadPromiseRuntimeState(const OffThreadPromiseRuntimeState&) = delete;
public:
OffThreadPromiseRuntimeState();
~OffThreadPromiseRuntimeState();
void init(JS::DispatchToEventLoopCallback callback, void* closure);
void initInternalDispatchQueue();
bool initialized() const;
// If initInternalDispatchQueue() was called, internalDrain() can be
// called to periodically drain the dispatch queue before shutdown.
void internalDrain(JSContext* cx);
bool internalHasPending();
// shutdown() must be called by the JSRuntime while the JSRuntime is valid.
void shutdown(JSContext* cx);
};
} // namespace js
#endif /* builtin_Promise_h */