summaryrefslogtreecommitdiffstats
path: root/js/src/vm/PromiseObject.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/PromiseObject.h')
-rw-r--r--js/src/vm/PromiseObject.h250
1 files changed, 250 insertions, 0 deletions
diff --git a/js/src/vm/PromiseObject.h b/js/src/vm/PromiseObject.h
new file mode 100644
index 0000000000..7294724510
--- /dev/null
+++ b/js/src/vm/PromiseObject.h
@@ -0,0 +1,250 @@
+/* -*- 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 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() { return getFixedSlot(PromiseSlot_Flags).toInt32(); }
+
+ void setHandled() {
+ setFixedSlot(PromiseSlot_Flags,
+ JS::Int32Value(flags() | PROMISE_FLAG_HANDLED));
+ }
+
+ 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;
+ }
+
+ JS::Value reactions() {
+ MOZ_ASSERT(state() == JS::PromiseState::Pending);
+ return getFixedSlot(PromiseSlot_ReactionsOrResult);
+ }
+
+ JS::Value value() {
+ MOZ_ASSERT(state() == JS::PromiseState::Fulfilled);
+ return getFixedSlot(PromiseSlot_ReactionsOrResult);
+ }
+
+ JS::Value reason() {
+ MOZ_ASSERT(state() == JS::PromiseState::Rejected);
+ return getFixedSlot(PromiseSlot_ReactionsOrResult);
+ }
+
+ JS::Value valueOrReason() {
+ 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);
+};
+
+/**
+ * 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