// Copyright (c) 2017-2022 Cloudflare, Inc. // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 #pragma once #include "jsg.h" #include "util.h" #include "wrappable.h" #include #include #include #include namespace workerd::jsg { // ======================================================================================= // Utilities for wrapping arbitrary C++ in an opaque way. See wrapOpaque(). // // At present this is used privately in the Promise implementation, but we could consider making // wrapOpaque() more public if it is useful. template ()> struct OpaqueWrappable; struct OpaqueWrappableBase: public Wrappable { kj::StringPtr jsgGetMemoryName() const override final { return "OpaqueWrappable"_kjc; } void jsgGetMemoryInfo(MemoryTracker& tracker) const override final { Wrappable::jsgGetMemoryInfo(tracker); } }; template struct OpaqueWrappable: public OpaqueWrappableBase { // Used to implement wrapOpaque(). OpaqueWrappable(T&& value): value(kj::mv(value)) {} T value; bool movedAway = false; size_t jsgGetMemorySelfSize() const override final { return sizeof(OpaqueWrappable); } }; template struct OpaqueWrappable: public OpaqueWrappable { // When T is GC-visitable, make sure to implement visitation. using OpaqueWrappable::OpaqueWrappable; void jsgVisitForGc(GcVisitor& visitor) override { if (!this->movedAway) { visitor.visit(this->value); } } }; // Create a JavaScript value that wraps `t` in an opaque way. JS code will see this as an empty // object, as if created by `{}`, but C++ code can unwrap the handle with `unwrapOpaque()`. // // If `T` is a type that can be passed to GcVisitor::visit(), then it will be visited whenever // the opaque handle is found to be reachable. // // Generally, the opaque handle should not actually be passed to the application at all. This // is useful in cases where the producer and consumer are both C++ code, but V8 requires that // a handle be used for some reason. For example, this is used to pass C++ values through V8 // Promises. // // Opaque-wrapping of `V8Ref` is explicitly disallowed to avoid waste. Just use the handle // directly in this case. If you really want to wrap a V8Ref opaquely, wrap it in a struct of // your own first. (Don't forget to implement `visitForGc()`.) template v8::Local wrapOpaque(v8::Local context, T&& t) { static_assert(!kj::isReference()); static_assert(!isV8Ref(), "no need to opaque-wrap regular JavaScript values"); static_assert(!isV8Local(), "can't opaque-wrap non-persistent handles"); auto wrapped = kj::refcounted>(kj::mv(t)); return wrapped->attachOpaqueWrapper(context, isGcVisitable()); } // Unwraps a handle created using `wrapOpaque()`. This consumes (moves away) the underlying // value, so can only be called once. Throws if the handle is the wrong type or has already been // consumed previously. template T unwrapOpaque(v8::Isolate* isolate, v8::Local handle) { static_assert(!kj::isReference()); static_assert(!isV8Ref(), "no need to opaque-wrap regular JavaScript values"); static_assert(!isV8Local(), "can't opaque-wrap non-persistent handles"); Wrappable& wrappable = KJ_ASSERT_NONNULL(Wrappable::tryUnwrapOpaque(isolate, handle)); OpaqueWrappable* holder = dynamic_cast*>(&wrappable); KJ_ASSERT(holder != nullptr); KJ_ASSERT(!holder->movedAway); holder->movedAway = true; return kj::mv(holder->value); } // Unwraps a handle created using `wrapOpaque()`, without consuming the value. Throws if the // handle is the wrong type or has already been consumed previously. template T& unwrapOpaqueRef(v8::Isolate* isolate, v8::Local handle) { static_assert(!kj::isReference()); static_assert(!isV8Ref(), "no need to opaque-wrap regular JavaScript values"); static_assert(!isV8Local(), "can't opaque-wrap non-persistent handles"); Wrappable& wrappable = KJ_ASSERT_NONNULL(Wrappable::tryUnwrapOpaque(isolate, handle)); OpaqueWrappable* holder = dynamic_cast*>(&wrappable); KJ_ASSERT(holder != nullptr); KJ_ASSERT(!holder->movedAway); return holder->value; } // Destroys the value contained by an opaque handle, without returning it. This is equivalent // to calling unwrapOpaque() and dropping the result, except that if the handle is the wrong // type, this function silently does nothing rather than throw. template void dropOpaque(v8::Isolate* isolate, v8::Local handle) { static_assert(!kj::isReference()); static_assert(!isV8Ref()); KJ_IF_SOME(wrappable, Wrappable::tryUnwrapOpaque(isolate, handle)) { OpaqueWrappable* holder = dynamic_cast*>(&wrappable); if (holder != nullptr) { holder->movedAway = true; auto drop KJ_UNUSED = kj::mv(holder->value); } } } // ======================================================================================= // Promise implementation // This type (opaque-wrapped) is the type of the "data" for a continuation callback. We have both // the success and error callbacks share the same "data" object so that both underlying C++ // callbacks are proactively destroyed after one of the runs. Otherwise, we'd only destroy the // function that was called, while the other one would have to wait for GC, which may mean // keeping around C++ resources longer than necessary. template struct ThenCatchPair { ThenFunc thenFunc; CatchFunc catchFunc; }; // FunctionCallback implementing a C++ .then() continuation on a JS promise. // // We expect the input is already an opaque-wrapped value, args.Data() is an opaque-wrapped C++ // function to execute, and we want to produce an opaque-wrapped output or Promise. template void promiseContinuation(const v8::FunctionCallbackInfo& args) { liftKj(args, [&]() { auto isolate = args.GetIsolate(); #ifdef KJ_DEBUG // In debug mode only, we verify that the function hasn't captured any KJ heap objects without // a IoOwn. We don't bother with this check in release mode because it's pretty deterministic, // so it's likely to be caught in debug, and we'd like to avoid the extra overhead in releases. DISALLOW_KJ_IO_DESTRUCTORS_SCOPE; #endif auto funcPair = unwrapOpaque(isolate, args.Data()); #ifdef KJ_DEBUG kj::AllowAsyncDestructorsScope allowAsyncDestructors; #endif auto callFunc = [&]() -> Output { auto& js = Lock::from(isolate); if constexpr (isCatch) { // Exception from V8 is not expected to be opaque-wrapped. It's just a Value. return funcPair.catchFunc(js, Value(isolate, args[0])); } else if constexpr (isVoid()) { return funcPair.thenFunc(js); } else if constexpr (isV8Ref()) { return funcPair.thenFunc(js, Input(isolate, args[0])); } else { return funcPair.thenFunc(js, unwrapOpaque(isolate, args[0])); } }; if constexpr (isVoid()) { callFunc(); } else if constexpr (isPromise()) { // Continuation returns Promise. We don't want to opaque-wrap that, we want to return it // raw, so that the V8 Promise machinery will chain it. // We cast the return value to v8::Local so that it doesn't trigger liftKj()'s // special handling of promises, where it tries to catch exceptions and merge them into the // promise. We don't need to do this, because this is being called as a .then() which already // catches exceptions and does the right thing. return v8::Local(callFunc().consumeHandle(Lock::from(isolate))); } else if constexpr (isV8Ref()) { return callFunc().getHandle(isolate); } else { return wrapOpaque(isolate->GetCurrentContext(), callFunc()); } }); } // Promise continuation that propagates the value or exception unmodified, but makes sure to // proactively destroy the ThenCatchPair. template void identityPromiseContinuation(const v8::FunctionCallbackInfo& args) { auto isolate = args.GetIsolate(); dropOpaque(isolate, args.Data()); if constexpr (isCatch) { isolate->ThrowException(args[0]); } else { args.GetReturnValue().Set(args[0]); } } template class PromiseWrapper; template class Promise { public: static_assert(!kj::canConvert(), "jsg::Promise expects T to be an instantiable C++ type, not a JS heap type; use " "jsg::Promise> to represent a promise for a JavaScript heap object."); Promise(v8::Isolate* isolate, v8::Local v8Promise) : v8Promise(V8Ref(isolate, v8Promise)) {} Promise(decltype(nullptr)): v8Promise(kj::none) {} // For use when you're declaring a local variable that will be initialized later. void markAsHandled(Lock& js) { auto promise = getInner(js); promise->MarkAsHandled(); markedAsHandled = true; } // Attach a continuation function and error handler to be called when this promise // is fulfilled. It is important to remember that then(...) can synchronously throw // a JavaScript exception (and jsg::JsExceptionThrown) in certain cases. template PromiseForResult then(Lock& js, Func&& func, ErrorFunc&& errorFunc) { using Output = ReturnType; static_assert(kj::isSameType>(), "functions passed to .then() must return exactly the same type"); using FuncPair = ThenCatchPair; return thenImpl(js, FuncPair{kj::fwd(func), kj::fwd(errorFunc)}, &promiseContinuation, &promiseContinuation); } // Attach a continuation function to be called when this promise is fulfilled. // It is important to remember that then(...) can synchronously throw // a JavaScript exception (and jsg::JsExceptionThrown) in certain cases. template PromiseForResult then(Lock& js, Func&& func) { using Output = ReturnType; // HACK: The error function is never called, so it need not actually be a functor. using FuncPair = ThenCatchPair; return thenImpl(js, FuncPair{kj::fwd(func), false}, &promiseContinuation, &identityPromiseContinuation); } template Promise catch_(Lock& js, ErrorFunc&& errorFunc) { static_assert(kj::isSameType>(), "function passed to .catch_() must return exactly the promise's type"); // HACK: The non-error function is never called, so it need not actually be a functor. using FuncPair = ThenCatchPair; return thenImpl(js, FuncPair{false, kj::fwd(errorFunc)}, &identityPromiseContinuation, &promiseContinuation); } // whenResolved returns a new Promise that resolves when this promise resolves, // stopping the propagation of the resolved value. Unlike then(), calling whenResolved() // does not consume the promise, and whenResolved() can be called multiple times, // with each call creating a new branch off the original promise. Another key difference // with whenResolved() is that the markAsHandled status will propagate to the new Promise // returned by whenResolved(). Promise whenResolved(Lock& js) { auto promise = Promise(js.v8Isolate, getInner(js)); if (markedAsHandled) { promise.markAsHandled(js); } return kj::mv(promise); } v8::Local consumeHandle(Lock& js) { auto result = getInner(js); v8Promise = kj::none; return result; } // If the promise is resolved, return the result, consuming the Promise. If it is pending // or rejected, returns null. This can be used as an optimization or in tests, but you must // never rely on it for correctness. kj::Maybe tryConsumeResolved(Lock& js) { return js.withinHandleScope([&]() -> kj::Maybe { auto handle = KJ_REQUIRE_NONNULL(v8Promise, "jsg::Promise can only be used once").getHandle(js); switch (handle->State()) { case v8::Promise::kPending: case v8::Promise::kRejected: return kj::none; case v8::Promise::kFulfilled: v8Promise = kj::none; return unwrapOpaque(js.v8Isolate, handle->Result()); } }); } class Resolver { public: Resolver(v8::Isolate* isolate, v8::Local v8Resolver) : v8Resolver(isolate, kj::mv(v8Resolver)) {} template ()>> void resolve(Lock& js, kj::NoInfer&& value) { js.withinHandleScope([&] { auto context = js.v8Context(); v8::Local handle; if constexpr (isV8Ref()) { handle = value.getHandle(js); } else { handle = wrapOpaque(context, kj::mv(value)); } check(v8Resolver.getHandle(js)->Resolve(context, handle)); }); } template ()>> void resolve(Lock& js) { js.withinHandleScope( [&] { check(v8Resolver.getHandle(js)->Resolve(js.v8Context(), js.v8Undefined())); }); } void resolve(Lock& js, Promise&& promise) { // Resolve to another Promise. check(v8Resolver.getHandle(js)->Resolve(js.v8Context(), promise.consumeHandle(js))); } void reject(Lock& js, v8::Local exception) { js.withinHandleScope( [&] { check(v8Resolver.getHandle(js)->Reject(js.v8Context(), exception)); }); } void reject(Lock& js, kj::Exception exception, ExceptionToJsOptions options = {}) { reject(js, exceptionToJs(js.v8Isolate, kj::mv(exception), options)); } Resolver addRef(Lock& js) { return {js.v8Isolate, v8Resolver.getHandle(js)}; } void visitForGc(GcVisitor& visitor) { visitor.visit(v8Resolver); } JSG_MEMORY_INFO(Resolver) { tracker.trackField("resolver", v8Resolver); } private: V8Ref v8Resolver; friend class MemoryTracker; }; void visitForGc(GcVisitor& visitor) { visitor.visit(v8Promise); } JSG_MEMORY_INFO(Promise) { KJ_IF_SOME(promise, v8Promise) { tracker.trackField("promise", promise); } } // Ths is for testing/diagnostics purposes only. enum class State { PENDING = v8::Promise::kPending, FULFILLED = v8::Promise::kFulfilled, REJECTED = v8::Promise::kRejected, CONSUMED = 3 // Not a real state; indicates the Promise has been consumed. }; State getState(Lock& js) { KJ_IF_SOME(promise, v8Promise) { return static_cast(promise.getHandle(js)->State()); } else { return State::CONSUMED; } } private: kj::Maybe> v8Promise; bool markedAsHandled = false; v8::Local getInner(Lock& js) { return KJ_REQUIRE_NONNULL(v8Promise, "jsg::Promise can only be used once").getHandle(js); } template ()>()> Promise(Lock& js, kj::NoInfer&& value) { js.withinHandleScope([&] { auto context = js.v8Context(); auto resolver = check(v8::Promise::Resolver::New(context)); v8::Local handle; if constexpr (isV8Ref()) { handle = value.getHandle(js); } else { handle = wrapOpaque(context, kj::mv(value)); }; check(resolver->Resolve(context, handle)); v8Promise.emplace(js.v8Isolate, resolver->GetPromise()); }); } template ()>()> explicit Promise(Lock& js) { js.withinHandleScope([&] { auto context = js.v8Context(); auto resolver = check(v8::Promise::Resolver::New(context)); check(resolver->Resolve(context, js.v8Undefined())); v8Promise.emplace(js.v8Isolate, resolver->GetPromise()); }); } template MaintainPromise thenImpl(Lock& js, FuncPair&& funcPair, v8::FunctionCallback thenCallback, v8::FunctionCallback errCallback) { return js.withinHandleScope([&] { auto context = js.v8Context(); auto funcPairHandle = wrapOpaque(context, kj::mv(funcPair)); auto then = check(v8::Function::New( context, thenCallback, funcPairHandle, 1, v8::ConstructorBehavior::kThrow)); auto errThen = check(v8::Function::New( context, errCallback, funcPairHandle, 1, v8::ConstructorBehavior::kThrow)); return MaintainPromise( js.v8Isolate, check(consumeHandle(js)->Then(context, then, errThen))); }); } friend class Lock; template friend class PromiseWrapper; friend class MemoryTracker; }; template class Promise> { static_assert(sizeof(T*) == 0, "Promise> is invalid; use Promise instead"); }; template class Promise> { static_assert(sizeof(T*) == 0, "jsg::Promise> is illegal; you need a IoOwn!"); }; template struct PromiseResolverPair { Promise promise; Promise::Resolver resolver; JSG_MEMORY_INFO(PromiseResolverPair) { tracker.trackField("promise", promise); tracker.trackField("resolver", resolver); } }; template PromiseResolverPair Lock::newPromiseAndResolver() { return withinHandleScope([&]() -> PromiseResolverPair { auto resolver = check(v8::Promise::Resolver::New(v8Context())); auto promise = resolver->GetPromise(); return {{v8Isolate, promise}, {v8Isolate, resolver}}; }); } template inline Promise Lock::resolvedPromise(T&& value) { return Promise(*this, kj::fwd(value)); } inline Promise Lock::resolvedPromise() { return Promise(*this); } template Promise Lock::rejectedPromise(v8::Local exception) { auto [promise, resolver] = newPromiseAndResolver(); resolver.reject(*this, exception); return kj::mv(promise); } template Promise Lock::rejectedPromise(jsg::Value exception) { return withinHandleScope([&] { return rejectedPromise(exception.getHandle(*this)); }); } template Promise Lock::rejectedPromise(kj::Exception&& exception, ExceptionToJsOptions options) { return withinHandleScope( [&] { return rejectedPromise(exceptionToJs(kj::mv(exception), options)); }); } template PromiseForResult Lock::evalNow(Func&& func) { using Result = RemovePromise>; v8::TryCatch tryCatch(v8Isolate); try { if constexpr (isPromise>()) { return func(); } else { return resolvedPromise(func()); } } catch (jsg::JsExceptionThrown&) { if (tryCatch.HasCaught() && tryCatch.CanContinue()) { return rejectedPromise(tryCatch.Exception()); } else { // Probably TerminateExecution() called. tryCatch.ReThrow(); throw; } } catch (kj::Exception& e) { return rejectedPromise(kj::mv(e)); } catch (std::exception& exception) { return rejectedPromise(makeInternalError(v8Isolate, exception.what())); } catch (...) { return rejectedPromise(makeInternalError( v8Isolate, kj::str("caught unknown exception of type: ", kj::getCaughtExceptionType()))); } } // ----------------------------------------------------------------------------- // Continuation function that converts a promised C++ value into a JavaScript value. template void thenWrap(const v8::FunctionCallbackInfo& args) { if constexpr (isVoid()) { // No wrapping needed. Note that we still attach `thenWrap` to the promise chain only because // we use `args.data` to prevent the object from being GC'ed while the promise is still // executing. args.GetReturnValue().SetUndefined(); } else if constexpr (isV8Ref()) { // Similarly, no unwrapping needed. args.GetReturnValue().Set(args[0]); } else { liftKj(args, [&]() { v8::Isolate* isolate = args.GetIsolate(); auto& wrapper = TypeWrapper::from(isolate); auto context = isolate->GetCurrentContext(); auto& lock = Lock::from(isolate); return wrapper.wrap(lock, context, kj::none, unwrapOpaque(isolate, args[0])); }); } } // Continuation function that converts a promised JavaScript value into a C++ value. template void thenUnwrap(const v8::FunctionCallbackInfo& args) { liftKj(args, [&]() { v8::Isolate* isolate = args.GetIsolate(); auto& wrapper = TypeWrapper::from(isolate); auto context = isolate->GetCurrentContext(); auto& js = Lock::from(isolate); return wrapOpaque(context, wrapper.template unwrap( js, context, args[0], TypeErrorContext::promiseResolution())); }); } // TypeWrapper mixin for Promise. template class PromiseWrapper { public: // The constructor here is a bit of a hack. The config is optional and might not be a JsgConfig // object (or convertible to a JsgConfig) if is provided. However, because of the way TypeWrapper // inherits PromiseWrapper, we always end up passing a config option (which might be // std::nullptr_t). The getConfig allows us to handle any case using reasonable defaults. PromiseWrapper(const auto& config): config(getConfig(config)) {} template static constexpr const char* getName(Promise*) { return "Promise"; } template v8::Local wrap(jsg::Lock& js, v8::Local context, kj::Maybe> creator, Promise&& promise) { // Add a .then() to unwrap the value (i.e. convert C++ value to JavaScript). // // We use `creator` as the `data` value for this continuation so that the creator object // cannot be GC'ed while the callback still exists. This gives us the KJ-style guarantee that // the object whose method returned the promise will not be destroyed while the promise is // still executing. auto markedAsHandled = promise.markedAsHandled; auto then = check(v8::Function::New(context, &thenWrap, creator.orDefault({}), 1, v8::ConstructorBehavior::kThrow)); auto ret = check(promise.consumeHandle(js)->Then(context, then)); // Although we added a .then() to the promise to translate the value to JavaScript, we would // like things to behave as if the C++ code returned this Promise directly to JavaScript. In // particular, if the C++ code marked the Promise handled, then the derived JavaScript promise // ought to be marked as handled as well. if (markedAsHandled) { ret->MarkAsHandled(); } return ret; } template kj::Maybe> tryUnwrap(Lock& js, v8::Local context, v8::Local handle, Promise*, kj::Maybe> parentObject) { if (handle->IsPromise()) { auto promise = handle.As(); if constexpr (!isVoid() && !isV8Ref()) { // Add a .then() to unwrap the promise's resolution (i.e. convert it from JS to C++). // Note that we don't need to handle the rejection case here as there is no wrapping // applied to exception values, so we just let it propagate through. // // TODO(perf): We could in theory check if promise->State() is kFulfilled and, in that // case, pull out promise->Result(), unwrap it, and make a new immediate promise. // Similarly in `wrap()`. Not clear if the added complexity is worth it, though. auto then = check(v8::Function::New( context, &thenUnwrap, {}, 1, v8::ConstructorBehavior::kThrow)); promise = check(promise->Then(context, then)); } return Promise(js.v8Isolate, promise); } else { // Input is a resolved value (not a promise). Try to unwrap it now. // If the input is an object that is not a promise, there's a chance it is a custom // thenable (and object with a then method intended to be used as a promise). If that // is the case, then we can handle the thenable by resolving it to a promise then // unwrapping that promise. // Unfortunately this needs to be gated by a compatibility flag because there are // existing workers that appear to rely on the old behavior -- although it's not clear // if those workers actually work the way they were intended to. if (config.unwrapCustomThenables && isThenable(context, handle)) { auto paf = check(v8::Promise::Resolver::New(context)); check(paf->Resolve(context, handle)); return tryUnwrap( js, context, paf->GetPromise(), static_cast*>(nullptr), parentObject); } if constexpr (isVoid()) { // When expecting Promise, we treat absolutely any non-promise value as being // an immediately-resolved promise. This is consistent with JavaScript where you'd // commonly use `Promise.resolve(param).then(() => {...})` in order to coerce the param // to a promise... normally you wouldn't bother checking that the param specifically // resolved to `undefined`, you'd just throw away whatever it resolved to. // // It's possible to argue that we should actually allow only `undefined` here but // changing it now could break existing users, e.g. html-rewriter.ew-test is broken // because it writes `() => someExpression()` for a callback that's supposed to // optionally return Promise -- it seems like the callback isn't actually intending // to return the result of `someExpression()` but does so by accident since the braces // are missing. This is probably common in user code, too. return js.resolvedPromise(); } else { auto& wrapper = *static_cast(this); KJ_IF_SOME(value, wrapper.tryUnwrap(js, context, handle, (T*)nullptr, parentObject)) { return js.resolvedPromise(kj::mv(value)); } else { // Wrong type. return kj::none; } } } } private: const JsgConfig config; static bool isThenable(v8::Local context, v8::Local handle) { if (handle->IsObject()) { auto obj = handle.As(); return check(obj->Has(context, v8StrIntern(v8::Isolate::GetCurrent(), "then"))); } return false; } }; // ----------------------------------------------------------------------------- // A utility used internally by ServiceWorkerGlobalScope to perform the book keeping // for unhandled promise rejection notifications. The handler maintains a table of // weak references to rejected promises that have not been handled and will handle // emitting events and console warnings as appropriate. class UnhandledRejectionHandler { public: using Handler = void(jsg::Lock& js, v8::PromiseRejectEvent event, jsg::V8Ref promise, jsg::Value value); explicit UnhandledRejectionHandler(kj::Function handler): handler(kj::mv(handler)) {} void report(jsg::Lock& js, v8::PromiseRejectEvent event, jsg::V8Ref promise, jsg::Value value); void setUseMicrotasksCompletedCallback(bool value) { useMicrotasksCompletedCallback = value; } void clear(); JSG_MEMORY_INFO(UnhandledRejectionHandler) { // TODO (soon): Can we reasonably measure the function handler? tracker.trackField("unhandledRejections", unhandledRejections); tracker.trackField("warnedRejections", warnedRejections); } private: // Used as part of the book keeping for unhandled rejections. When an // unhandled rejection occurs, the unhandledRejections Table will be updated. // If the rejection is later handled asynchronously, then the item will be // removed from the table. When the unhandled rejection table is processed // later in the event loop tick, any remaining rejections will generate a // warning to the inspector console (if enabled); struct UnhandledRejection { explicit UnhandledRejection(jsg::Lock& js, jsg::V8Ref promise, jsg::Value value, v8::Local message); ~UnhandledRejection(); UnhandledRejection(UnhandledRejection&& other) = default; UnhandledRejection& operator=(UnhandledRejection&& other) = default; // TODO(cleanup): It would be better to use a jsg::HashableV8Ref or // jsg::Identity here but we need the Globals to always be weak so // that the book keeping doesn't end up being a memory leak. uint hash; // We use v8::Globals directly here because these references are going to // be made weak and could be garbage collected and cleared while the items // are still in the unhandledRejections or warnedRejections tables. v8::Global promise; v8::Global value; v8::Global message; kj::Maybe> asyncContextFrame; inline bool isAlive() { return !promise.IsEmpty() && !value.IsEmpty(); } uint hashCode() const { return hash; } JSG_MEMORY_INFO(UnhandledRejection) { tracker.trackField("promise", promise); tracker.trackField("value", value); visitForMemoryInfo(tracker); } void visitForMemoryInfo(MemoryTracker& tracker) const; }; // A v8::Promise with memoized hash code. struct HashedPromise { v8::Local promise; uint hash; HashedPromise(v8::Local promise) : promise(promise), hash(kj::hashCode(promise->GetIdentityHash())) {} JSG_MEMORY_INFO(HashedPromise) { tracker.trackField("promise", promise); } }; struct UnhandledRejectionCallbacks { inline const UnhandledRejection& keyForRow( const UnhandledRejection& row KJ_LIFETIMEBOUND) const { return row; } inline bool matches(const UnhandledRejection& a, const UnhandledRejection& b) const { return a.promise == b.promise; } inline bool matches(const UnhandledRejection& a, const HashedPromise& b) const { return a.promise == b.promise; } inline uint hashCode(const UnhandledRejection& row) const { return row.hashCode(); } inline uint hashCode(const HashedPromise& key) const { return key.hash; } }; kj::Function handler; bool scheduled = false; // Controlled by the unhandled_rejection_after_microtask_checkpoint compat flag. bool useMicrotasksCompletedCallback = false; using UnhandledRejectionsTable = kj::Table>; UnhandledRejectionsTable unhandledRejections; UnhandledRejectionsTable warnedRejections; void rejectedWithNoHandler(jsg::Lock& js, jsg::V8Ref promise, jsg::Value value); void handledAfterRejection(jsg::Lock& js, jsg::V8Ref promise); void ensureProcessingWarnings(jsg::Lock& js); void processWarnings(jsg::Lock& js); // Must be static: V8 requires a plain C function pointer for this callback. static void onMicrotasksCompleted(v8::Isolate* isolate, void* data); }; } // namespace workerd::jsg