Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#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:
// 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:
// 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