diff --git a/deps/v8/include/v8.h b/deps/v8/include/v8.h index 7999f358ab5e5d..a3158c999ff204 100644 --- a/deps/v8/include/v8.h +++ b/deps/v8/include/v8.h @@ -10524,6 +10524,17 @@ class V8_EXPORT Context { */ void SetContinuationPreservedEmbedderData(Local context); + /** + * Set or clear hooks to be invoked for promise lifecycle operations. + * The individual hooks can be either undefined (to disable the + * hooks) or some Function (to get notified). Each function will receive + * the observed promise as the first argument. If a chaining operation + * is used on a promise, the init will additionally receive the parent + * promise as the second argument. + */ + void SetPromiseHooks(Local init_hook, Local before_hook, + Local after_hook, Local resolve_hook); + /** * Stack-allocated class which sets the execution context for all * operations executed within a local scope. diff --git a/deps/v8/src/api/api.cc b/deps/v8/src/api/api.cc index c27db3e7b876e0..ecf3f4d3109960 100644 --- a/deps/v8/src/api/api.cc +++ b/deps/v8/src/api/api.cc @@ -6196,6 +6196,38 @@ void Context::SetContinuationPreservedEmbedderData(Local data) { *i::Handle::cast(Utils::OpenHandle(*data))); } +void v8::Context::SetPromiseHooks(Local init_hook, + Local before_hook, + Local after_hook, + Local resolve_hook) { + i::Handle context = Utils::OpenHandle(this); + i::Isolate* isolate = context->GetIsolate(); + isolate->UpdatePromiseHookProtector(); + + i::Handle init = Utils::OpenHandle(*init_hook); + i::Handle before = Utils::OpenHandle(*before_hook); + i::Handle after = Utils::OpenHandle(*after_hook); + i::Handle resolve = Utils::OpenHandle(*resolve_hook); + + Utils::ApiCheck(init->IsJSFunction() || init->IsUndefined(), + "v8::Context::SetPromiseHooks", + "Init Promise hook must be a Function or undefined"); + Utils::ApiCheck(before->IsJSFunction() || before->IsUndefined(), + "v8::Context::SetPromiseHooks", + "Before Promise hook must be a Function or undefined"); + Utils::ApiCheck(after->IsJSFunction() || after->IsUndefined(), + "v8::Context::SetPromiseHooks", + "After Promise hook must be a Function or undefined"); + Utils::ApiCheck(resolve->IsJSFunction() || resolve->IsUndefined(), + "v8::Context::SetPromiseHooks", + "Resolve Promise hook must be a Function or undefined"); + + context->native_context().set_promise_hook_init_function(*init); + context->native_context().set_promise_hook_before_function(*before); + context->native_context().set_promise_hook_after_function(*after); + context->native_context().set_promise_hook_resolve_function(*resolve); +} + MaybeLocal metrics::Recorder::GetContext( Isolate* isolate, metrics::Recorder::ContextId id) { i::Isolate* i_isolate = reinterpret_cast(isolate); diff --git a/deps/v8/src/builtins/builtins-async-function-gen.cc b/deps/v8/src/builtins/builtins-async-function-gen.cc index e84442295cfc90..83ead9e840f14c 100644 --- a/deps/v8/src/builtins/builtins-async-function-gen.cc +++ b/deps/v8/src/builtins/builtins-async-function-gen.cc @@ -157,6 +157,8 @@ TF_BUILTIN(AsyncFunctionEnter, AsyncFunctionBuiltinsAssembler) { StoreObjectFieldNoWriteBarrier( async_function_object, JSAsyncFunctionObject::kPromiseOffset, promise); + RunContextPromiseHookInit(context, promise, UndefinedConstant()); + // Fire promise hooks if enabled and push the Promise under construction // in an async function on the catch prediction stack to handle exceptions // thrown before the first await. diff --git a/deps/v8/src/builtins/builtins-async-gen.cc b/deps/v8/src/builtins/builtins-async-gen.cc index 383289fd0f3ea4..b0f977c6f99e22 100644 --- a/deps/v8/src/builtins/builtins-async-gen.cc +++ b/deps/v8/src/builtins/builtins-async-gen.cc @@ -99,17 +99,30 @@ TNode AsyncBuiltinsAssembler::AwaitOld( TVARIABLE(HeapObject, var_throwaway, UndefinedConstant()); + RunContextPromiseHookInit(context, promise, outer_promise); + // Deal with PromiseHooks and debug support in the runtime. This // also allocates the throwaway promise, which is only needed in // case of PromiseHooks or debugging. - Label if_debugging(this, Label::kDeferred), do_resolve_promise(this); + Label if_debugging(this, Label::kDeferred), + if_promise_hook(this, Label::kDeferred), + not_debugging(this), + do_resolve_promise(this); Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(), - &if_debugging, &do_resolve_promise); + &if_debugging, ¬_debugging); BIND(&if_debugging); var_throwaway = CAST(CallRuntime(Runtime::kAwaitPromisesInitOld, context, value, promise, outer_promise, on_reject, is_predicted_as_caught)); Goto(&do_resolve_promise); + BIND(¬_debugging); + + const TNode promise_hook_obj = LoadContextElement( + native_context, Context::PROMISE_HOOK_INIT_FUNCTION_INDEX); + Branch(IsUndefined(promise_hook_obj), &do_resolve_promise, &if_promise_hook); + BIND(&if_promise_hook); + var_throwaway = NewJSPromise(context, promise); + Goto(&do_resolve_promise); BIND(&do_resolve_promise); // Perform ! Call(promiseCapability.[[Resolve]], undefined, « promise »). @@ -173,14 +186,26 @@ TNode AsyncBuiltinsAssembler::AwaitOptimized( // Deal with PromiseHooks and debug support in the runtime. This // also allocates the throwaway promise, which is only needed in // case of PromiseHooks or debugging. - Label if_debugging(this, Label::kDeferred), do_perform_promise_then(this); + Label if_debugging(this, Label::kDeferred), + if_promise_hook(this, Label::kDeferred), + not_debugging(this), + do_perform_promise_then(this); Branch(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(), - &if_debugging, &do_perform_promise_then); + &if_debugging, ¬_debugging); BIND(&if_debugging); var_throwaway = CAST(CallRuntime(Runtime::kAwaitPromisesInit, context, promise, promise, outer_promise, on_reject, is_predicted_as_caught)); Goto(&do_perform_promise_then); + BIND(¬_debugging); + + const TNode promise_hook_obj = LoadContextElement( + native_context, Context::PROMISE_HOOK_INIT_FUNCTION_INDEX); + Branch(IsUndefined(promise_hook_obj), &do_perform_promise_then, + &if_promise_hook); + BIND(&if_promise_hook); + var_throwaway = NewJSPromise(context, promise); + Goto(&do_perform_promise_then); BIND(&do_perform_promise_then); return CallBuiltin(Builtins::kPerformPromiseThen, native_context, promise, diff --git a/deps/v8/src/builtins/builtins-microtask-queue-gen.cc b/deps/v8/src/builtins/builtins-microtask-queue-gen.cc index 1da6f54c82057b..a2f3fe834e7099 100644 --- a/deps/v8/src/builtins/builtins-microtask-queue-gen.cc +++ b/deps/v8/src/builtins/builtins-microtask-queue-gen.cc @@ -198,6 +198,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( const TNode thenable = LoadObjectField( microtask, PromiseResolveThenableJobTask::kThenableOffset); + RunContextPromiseHookBefore(microtask_context, promise_to_resolve); RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context, CAST(promise_to_resolve)); @@ -207,6 +208,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( promise_to_resolve, thenable, then); } + RunContextPromiseHookAfter(microtask_context, promise_to_resolve); RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context, CAST(promise_to_resolve)); @@ -242,6 +244,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( BIND(&preserved_data_done); // Run the promise before/debug hook if enabled. + RunContextPromiseHookBefore(microtask_context, promise_or_capability); RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context, promise_or_capability); @@ -252,6 +255,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( } // Run the promise after/debug hook if enabled. + RunContextPromiseHookAfter(microtask_context, promise_or_capability); RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context, promise_or_capability); @@ -295,6 +299,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( BIND(&preserved_data_done); // Run the promise before/debug hook if enabled. + RunContextPromiseHookBefore(microtask_context, promise_or_capability); RunPromiseHook(Runtime::kPromiseHookBefore, microtask_context, promise_or_capability); @@ -305,6 +310,7 @@ void MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask( } // Run the promise after/debug hook if enabled. + RunContextPromiseHookAfter(microtask_context, promise_or_capability); RunPromiseHook(Runtime::kPromiseHookAfter, microtask_context, promise_or_capability); diff --git a/deps/v8/src/builtins/cast.tq b/deps/v8/src/builtins/cast.tq index 1562b7b4ddc0a2..60d1cf78ca2ba1 100644 --- a/deps/v8/src/builtins/cast.tq +++ b/deps/v8/src/builtins/cast.tq @@ -385,6 +385,12 @@ Cast(o: HeapObject): Undefined|Callable return HeapObjectToCallable(o) otherwise CastError; } +Cast(o: HeapObject): Undefined|JSFunction + labels CastError { + if (o == Undefined) return Undefined; + return Cast(o) otherwise CastError; +} + macro Cast(o: Symbol): T labels CastError; Cast(s: Symbol): PublicSymbol labels CastError { if (s.flags.is_private) goto CastError; diff --git a/deps/v8/src/builtins/promise-abstract-operations.tq b/deps/v8/src/builtins/promise-abstract-operations.tq index b7a1b571e6418b..79b9f063535e0b 100644 --- a/deps/v8/src/builtins/promise-abstract-operations.tq +++ b/deps/v8/src/builtins/promise-abstract-operations.tq @@ -196,6 +196,8 @@ FulfillPromise(implicit context: Context)( // Assert: The value of promise.[[PromiseState]] is "pending". assert(promise.Status() == PromiseState::kPending); + RunContextPromiseHookResolve(promise); + // 2. Let reactions be promise.[[PromiseFulfillReactions]]. const reactions = UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result); @@ -233,6 +235,8 @@ RejectPromise(implicit context: Context)( return runtime::RejectPromise(promise, reason, debugEvent); } + RunContextPromiseHookResolve(promise); + // 2. Let reactions be promise.[[PromiseRejectReactions]]. const reactions = UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result); diff --git a/deps/v8/src/builtins/promise-constructor.tq b/deps/v8/src/builtins/promise-constructor.tq index 32de19f3b29cdd..b96bce76a455bd 100644 --- a/deps/v8/src/builtins/promise-constructor.tq +++ b/deps/v8/src/builtins/promise-constructor.tq @@ -73,6 +73,8 @@ PromiseConstructor( result = UnsafeCast( FastNewObject(context, promiseFun, UnsafeCast(newTarget))); PromiseInit(result); + RunContextPromiseHookInit(result, Undefined); + if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) { runtime::PromiseHookInit(result, Undefined); } diff --git a/deps/v8/src/builtins/promise-misc.tq b/deps/v8/src/builtins/promise-misc.tq index 67e5e38687d76c..ee6e45e21f234e 100644 --- a/deps/v8/src/builtins/promise-misc.tq +++ b/deps/v8/src/builtins/promise-misc.tq @@ -90,6 +90,59 @@ macro NewPromiseRejectReactionJobTask(implicit context: Context)( }; } +@export +transitioning macro RunContextPromiseHookInit(implicit context: Context)( + promise: JSPromise, parent: Object) { + const maybeHook = *NativeContextSlot( + ContextSlot::PROMISE_HOOK_INIT_FUNCTION_INDEX); + if (IsUndefined(maybeHook)) return; + + const hook = Cast(maybeHook) otherwise unreachable; + const parentObject = Is(parent) ? Cast(parent) + otherwise unreachable: Undefined; + + try { + Call(context, hook, Undefined, promise, parentObject); + } catch (_err) { + } +} + +@export +transitioning macro RunContextPromiseHookResolve(implicit context: Context)( + maybePromise: Object) { + RunContextPromiseHook( + ContextSlot::PROMISE_HOOK_RESOLVE_FUNCTION_INDEX, maybePromise); +} + +@export +transitioning macro RunContextPromiseHookBefore(implicit context: Context)( + maybePromise: Object) { + RunContextPromiseHook( + ContextSlot::PROMISE_HOOK_BEFORE_FUNCTION_INDEX, maybePromise); +} + +@export +transitioning macro RunContextPromiseHookAfter(implicit context: Context)( + maybePromise: Object) { + RunContextPromiseHook( + ContextSlot::PROMISE_HOOK_AFTER_FUNCTION_INDEX, maybePromise); +} + +transitioning macro RunContextPromiseHook(implicit context: Context)( + slot: Slot, maybePromise: Object) { + const maybeHook = *NativeContextSlot(slot); + if (IsUndefined(maybeHook)) return; + if (!Is(maybePromise)) return; + + const hook = Cast(maybeHook) otherwise unreachable; + const promise = Cast(maybePromise) otherwise unreachable; + + try { + Call(context, hook, Undefined, promise); + } catch (_err) { + } +} + // These allocate and initialize a promise with pending state and // undefined fields. // @@ -100,6 +153,7 @@ transitioning macro NewJSPromise(implicit context: Context)(parent: Object): JSPromise { const instance = InnerNewJSPromise(); PromiseInit(instance); + RunContextPromiseHookInit(instance, parent); if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) { runtime::PromiseHookInit(instance, parent); } @@ -124,7 +178,7 @@ transitioning macro NewJSPromise(implicit context: Context)( instance.reactions_or_result = result; instance.SetStatus(status); promise_internal::ZeroOutEmbedderOffsets(instance); - + RunContextPromiseHookInit(instance, Undefined); if (IsPromiseHookEnabledOrHasAsyncEventDelegate()) { runtime::PromiseHookInit(instance, Undefined); } diff --git a/deps/v8/src/d8/d8.cc b/deps/v8/src/d8/d8.cc index dc17a696976b36..bc2597130771fc 100644 --- a/deps/v8/src/d8/d8.cc +++ b/deps/v8/src/d8/d8.cc @@ -1572,6 +1572,16 @@ void Shell::AsyncHooksTriggerAsyncId( PerIsolateData::Get(isolate)->GetAsyncHooks()->GetTriggerAsyncId())); } +void Shell::SetPromiseHooks(const v8::FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + HandleScope handle_scope(isolate); + + context->SetPromiseHooks(args[0], args[1], args[2], args[3]); + + args.GetReturnValue().Set(v8::Undefined(isolate)); +} + void WriteToFile(FILE* file, const v8::FunctionCallbackInfo& args) { for (int i = 0; i < args.Length(); i++) { HandleScope handle_scope(args.GetIsolate()); @@ -2265,6 +2275,14 @@ Local Shell::CreateD8Template(Isolate* isolate) { d8_template->Set(isolate, "log", log_template); } + { + Local promise_template = ObjectTemplate::New(isolate); + promise_template->Set( + isolate, "setHooks", + FunctionTemplate::New(isolate, SetPromiseHooks, Local(), + Local(), 4)); + d8_template->Set(isolate, "promise", promise_template); + } return d8_template; } diff --git a/deps/v8/src/d8/d8.h b/deps/v8/src/d8/d8.h index 11ec47d81500b7..529014097d2d44 100644 --- a/deps/v8/src/d8/d8.h +++ b/deps/v8/src/d8/d8.h @@ -462,6 +462,8 @@ class Shell : public i::AllStatic { static void AsyncHooksTriggerAsyncId( const v8::FunctionCallbackInfo& args); + static void SetPromiseHooks(const v8::FunctionCallbackInfo& args); + static void Print(const v8::FunctionCallbackInfo& args); static void PrintErr(const v8::FunctionCallbackInfo& args); static void Write(const v8::FunctionCallbackInfo& args); diff --git a/deps/v8/src/execution/isolate.cc b/deps/v8/src/execution/isolate.cc index c1c3bd1b24a651..c6d8cc796bc29d 100644 --- a/deps/v8/src/execution/isolate.cc +++ b/deps/v8/src/execution/isolate.cc @@ -4074,15 +4074,20 @@ void Isolate::FireCallCompletedCallback(MicrotaskQueue* microtask_queue) { } } +void Isolate::UpdatePromiseHookProtector() { + if (Protectors::IsPromiseHookIntact(this)) { + HandleScope scope(this); + Protectors::InvalidatePromiseHook(this); + } +} + void Isolate::PromiseHookStateUpdated() { bool promise_hook_or_async_event_delegate = promise_hook_ || async_event_delegate_; bool promise_hook_or_debug_is_active_or_async_event_delegate = promise_hook_or_async_event_delegate || debug()->is_active(); - if (promise_hook_or_debug_is_active_or_async_event_delegate && - Protectors::IsPromiseHookIntact(this)) { - HandleScope scope(this); - Protectors::InvalidatePromiseHook(this); + if (promise_hook_or_debug_is_active_or_async_event_delegate) { + UpdatePromiseHookProtector(); } promise_hook_or_async_event_delegate_ = promise_hook_or_async_event_delegate; promise_hook_or_debug_is_active_or_async_event_delegate_ = @@ -4272,6 +4277,13 @@ void Isolate::SetPromiseHook(PromiseHook hook) { PromiseHookStateUpdated(); } +void Isolate::RunAllPromiseHooks(PromiseHookType type, + Handle promise, + Handle parent) { + native_context()->RunPromiseHook(type, promise, parent); + RunPromiseHook(type, promise, parent); +} + void Isolate::RunPromiseHook(PromiseHookType type, Handle promise, Handle parent) { RunPromiseHookForAsyncEventDelegate(type, promise); diff --git a/deps/v8/src/execution/isolate.h b/deps/v8/src/execution/isolate.h index 43b7e27dd42979..a0e37b3255c278 100644 --- a/deps/v8/src/execution/isolate.h +++ b/deps/v8/src/execution/isolate.h @@ -1366,6 +1366,9 @@ class V8_EXPORT_PRIVATE Isolate final : private HiddenFactory { void SetPromiseHook(PromiseHook hook); void RunPromiseHook(PromiseHookType type, Handle promise, Handle parent); + void RunAllPromiseHooks(PromiseHookType type, Handle promise, + Handle parent); + void UpdatePromiseHookProtector(); void PromiseHookStateUpdated(); void AddDetachedContext(Handle context); diff --git a/deps/v8/src/heap/factory.cc b/deps/v8/src/heap/factory.cc index 7e66123681078f..65031ab688a0bf 100644 --- a/deps/v8/src/heap/factory.cc +++ b/deps/v8/src/heap/factory.cc @@ -3598,7 +3598,8 @@ Handle Factory::NewJSPromiseWithoutHook() { Handle Factory::NewJSPromise() { Handle promise = NewJSPromiseWithoutHook(); - isolate()->RunPromiseHook(PromiseHookType::kInit, promise, undefined_value()); + isolate()->RunAllPromiseHooks(PromiseHookType::kInit, promise, + undefined_value()); return promise; } diff --git a/deps/v8/src/objects/contexts.cc b/deps/v8/src/objects/contexts.cc index acbee9d5108cd0..64029df27045ee 100644 --- a/deps/v8/src/objects/contexts.cc +++ b/deps/v8/src/objects/contexts.cc @@ -511,5 +511,52 @@ STATIC_ASSERT(NativeContext::kSize == (Context::SizeFor(NativeContext::NATIVE_CONTEXT_SLOTS) + kSystemPointerSize)); +void NativeContext::RunPromiseHook(PromiseHookType type, + Handle promise, + Handle parent) { + bool isInit = false; + int contextSlot; + + switch (type) { + case PromiseHookType::kInit: + contextSlot = PROMISE_HOOK_INIT_FUNCTION_INDEX; + isInit = true; + break; + case PromiseHookType::kResolve: + contextSlot = PROMISE_HOOK_RESOLVE_FUNCTION_INDEX; + break; + case PromiseHookType::kBefore: + contextSlot = PROMISE_HOOK_BEFORE_FUNCTION_INDEX; + break; + case PromiseHookType::kAfter: + contextSlot = PROMISE_HOOK_AFTER_FUNCTION_INDEX; + break; + default: + UNREACHABLE(); + } + + Isolate* isolate = promise->GetIsolate(); + Handle hook(isolate->native_context()->get(contextSlot), isolate); + if (hook->IsUndefined()) return; + + int argc = isInit ? 2 : 1; + ScopedVector> argv(argc); + argv[0] = Handle::cast(promise); + if (isInit) { + argv[1] = parent; + } + + bool success = false; + Handle result; + Handle receiver = isolate->global_proxy(); + success = Execution::Call(isolate, hook, receiver, argc, argv.begin()) + .ToHandle(&result); + + // Discard hook exceptions for now. Need to figure out a better solution. + if (!success) { + isolate->clear_pending_exception(); + } +} + } // namespace internal } // namespace v8 diff --git a/deps/v8/src/objects/contexts.h b/deps/v8/src/objects/contexts.h index e63ed580f41f74..1956eacc1a32b1 100644 --- a/deps/v8/src/objects/contexts.h +++ b/deps/v8/src/objects/contexts.h @@ -198,6 +198,11 @@ enum ContextLookupFlags { V(NUMBER_FUNCTION_INDEX, JSFunction, number_function) \ V(OBJECT_FUNCTION_INDEX, JSFunction, object_function) \ V(OBJECT_FUNCTION_PROTOTYPE_MAP_INDEX, Map, object_function_prototype_map) \ + V(PROMISE_HOOK_INIT_FUNCTION_INDEX, Object, promise_hook_init_function) \ + V(PROMISE_HOOK_BEFORE_FUNCTION_INDEX, Object, promise_hook_before_function) \ + V(PROMISE_HOOK_AFTER_FUNCTION_INDEX, Object, promise_hook_after_function) \ + V(PROMISE_HOOK_RESOLVE_FUNCTION_INDEX, Object, \ + promise_hook_resolve_function) \ V(PROXY_CALLABLE_MAP_INDEX, Map, proxy_callable_map) \ V(PROXY_CONSTRUCTOR_MAP_INDEX, Map, proxy_constructor_map) \ V(PROXY_FUNCTION_INDEX, JSFunction, proxy_function) \ @@ -710,6 +715,9 @@ class NativeContext : public Context { void IncrementErrorsThrown(); int GetErrorsThrown(); + void RunPromiseHook(PromiseHookType type, Handle promise, + Handle parent); + private: STATIC_ASSERT(OffsetOfElementAt(EMBEDDER_DATA_INDEX) == Internals::kNativeContextEmbedderDataOffset); diff --git a/deps/v8/src/objects/contexts.tq b/deps/v8/src/objects/contexts.tq index a246b2177b8830..60c266019d08ff 100644 --- a/deps/v8/src/objects/contexts.tq +++ b/deps/v8/src/objects/contexts.tq @@ -119,6 +119,12 @@ extern enum ContextSlot extends intptr constexpr 'Context::Field' { PROMISE_PROTOTYPE_INDEX: Slot, STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX: Slot, + PROMISE_HOOK_INIT_FUNCTION_INDEX: Slot, + PROMISE_HOOK_BEFORE_FUNCTION_INDEX: Slot, + PROMISE_HOOK_AFTER_FUNCTION_INDEX: Slot, + PROMISE_HOOK_RESOLVE_FUNCTION_INDEX: + Slot, + CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX: Slot, BOUND_FUNCTION_WITH_CONSTRUCTOR_MAP_INDEX: Slot, diff --git a/deps/v8/src/objects/objects.cc b/deps/v8/src/objects/objects.cc index bb33b5d097afcc..2d80358491ae69 100644 --- a/deps/v8/src/objects/objects.cc +++ b/deps/v8/src/objects/objects.cc @@ -5340,8 +5340,8 @@ Handle JSPromise::Reject(Handle promise, if (isolate->debug()->is_active()) MoveMessageToPromise(isolate, promise); if (debug_event) isolate->debug()->OnPromiseReject(promise, reason); - isolate->RunPromiseHook(PromiseHookType::kResolve, promise, - isolate->factory()->undefined_value()); + isolate->RunAllPromiseHooks(PromiseHookType::kResolve, promise, + isolate->factory()->undefined_value()); // 1. Assert: The value of promise.[[PromiseState]] is "pending". CHECK_EQ(Promise::kPending, promise->status()); @@ -5376,8 +5376,8 @@ MaybeHandle JSPromise::Resolve(Handle promise, DCHECK( !reinterpret_cast(isolate)->GetCurrentContext().IsEmpty()); - isolate->RunPromiseHook(PromiseHookType::kResolve, promise, - isolate->factory()->undefined_value()); + isolate->RunAllPromiseHooks(PromiseHookType::kResolve, promise, + isolate->factory()->undefined_value()); // 7. If SameValue(resolution, promise) is true, then if (promise.is_identical_to(resolution)) { diff --git a/deps/v8/src/runtime/runtime-promise.cc b/deps/v8/src/runtime/runtime-promise.cc index dcc2c69013e0a7..e180d8471b6052 100644 --- a/deps/v8/src/runtime/runtime-promise.cc +++ b/deps/v8/src/runtime/runtime-promise.cc @@ -29,8 +29,8 @@ RUNTIME_FUNCTION(Runtime_PromiseRejectEventFromStack) { // undefined, which we interpret as being a caught exception event. rejected_promise = isolate->GetPromiseOnStackOnThrow(); } - isolate->RunPromiseHook(PromiseHookType::kResolve, promise, - isolate->factory()->undefined_value()); + isolate->RunAllPromiseHooks(PromiseHookType::kResolve, promise, + isolate->factory()->undefined_value()); isolate->debug()->OnPromiseReject(rejected_promise, value); // Report only if we don't actually have a handler. @@ -142,7 +142,7 @@ Handle AwaitPromisesInitCommon(Isolate* isolate, // hook for the throwaway promise (passing the {promise} as its // parent). Handle throwaway = isolate->factory()->NewJSPromiseWithoutHook(); - isolate->RunPromiseHook(PromiseHookType::kInit, throwaway, promise); + isolate->RunAllPromiseHooks(PromiseHookType::kInit, throwaway, promise); // On inspector side we capture async stack trace and store it by // outer_promise->async_task_id when async function is suspended first time. @@ -204,7 +204,7 @@ RUNTIME_FUNCTION(Runtime_AwaitPromisesInitOld) { // Fire the init hook for the wrapper promise (that we created for the // {value} previously). - isolate->RunPromiseHook(PromiseHookType::kInit, promise, outer_promise); + isolate->RunAllPromiseHooks(PromiseHookType::kInit, promise, outer_promise); return *AwaitPromisesInitCommon(isolate, value, promise, outer_promise, reject_handler, is_predicted_as_caught); } diff --git a/deps/v8/test/cctest/test-api.cc b/deps/v8/test/cctest/test-api.cc index cd291aa524aa88..b8ec700d0e2ff4 100644 --- a/deps/v8/test/cctest/test-api.cc +++ b/deps/v8/test/cctest/test-api.cc @@ -16501,6 +16501,158 @@ TEST(PromiseHook) { isolate->SetPromiseHook(nullptr); } +Local Get(const char* name) { + auto context = CcTest::isolate()->GetCurrentContext(); + return CcTest::global()->Get(context, v8_str(name)).ToLocalChecked(); +} + +int64_t GetInteger(const char* name) { + auto context = CcTest::isolate()->GetCurrentContext(); + return Get(name)->IntegerValue(context).ToChecked(); +} + +TEST(ContextPromiseHooks) { + LocalContext env; + v8::Isolate* isolate = env->GetIsolate(); + v8::HandleScope scope(isolate); + + // v8::Local global = CcTest::global(); + v8::Local context = CcTest::isolate()->GetCurrentContext(); + + CompileRun( + "parent = undefined;\n" + "init = undefined;\n" + "resolve = undefined;\n" + "before = undefined;\n" + "after = undefined;\n" + "initCount = 0;\n" + "beforeCount = 0;\n" + "afterCount = 0;\n" + "resolveCount = 0;\n" + "\n" + "function reset() {\n" + " parent = undefined;\n" + " init = undefined;\n" + " resolve = undefined;\n" + " before = undefined;\n" + " after = undefined;\n" + " initCount = 0;\n" + " beforeCount = 0;\n" + " afterCount = 0;\n" + " resolveCount = 0;\n" + "}\n" + "\n" + "function initHook(promise, _parent) {\n" + " init = promise;\n" + " parent = _parent;\n" + " initCount++;\n" + "}\n" + "\n" + "function resolveHook(promise) {\n" + " resolve = promise;\n" + " resolveCount++;\n" + "}\n" + "\n" + "function beforeHook(promise) {\n" + " before = promise;\n" + " beforeCount++;\n" + "}\n" + "\n" + "function afterHook(promise) {\n" + " after = promise;\n" + " afterCount++;\n" + "}\n"); + + auto reset = Get("reset").As(); + context->SetPromiseHooks(Get("initHook"), Get("beforeHook"), Get("afterHook"), + Get("resolveHook")); + + auto undefined = v8::Undefined(isolate); + + for (int i = 0; i < 2; i++) { + CompileRun("var done, p1 = new Promise(r => done = r);\n"); + + CHECK_EQ(v8::Promise::kPending, GetPromise("p1")->State()); + CHECK_EQ(1, GetInteger("initCount")); + CHECK_EQ(0, GetInteger("beforeCount")); + CHECK_EQ(0, GetInteger("afterCount")); + CHECK_EQ(0, GetInteger("resolveCount")); + CHECK(Get("init")->Equals(env.local(), Get("p1")).FromJust()); + CHECK(Get("before")->Equals(env.local(), undefined).FromJust()); + CHECK(Get("resolve")->Equals(env.local(), undefined).FromJust()); + CHECK(Get("after")->Equals(env.local(), undefined).FromJust()); + CHECK(Get("parent")->Equals(env.local(), undefined).FromJust()); + + reset->Call(env.local(), undefined), 0, 0).ToLocalChecked(); + + CompileRun("var p2 = p1.then(() => {});\n"); + + CHECK_EQ(v8::Promise::kPending, GetPromise("p1")->State()); + CHECK_EQ(v8::Promise::kPending, GetPromise("p2")->State()); + CHECK_EQ(1, GetInteger("initCount")); + CHECK_EQ(0, GetInteger("beforeCount")); + CHECK_EQ(0, GetInteger("afterCount")); + CHECK_EQ(0, GetInteger("resolveCount")); + CHECK(Get("init")->Equals(env.local(), Get("p2")).FromJust()); + CHECK(Get("before")->Equals(env.local(), undefined).FromJust()); + CHECK(Get("resolve")->Equals(env.local(), undefined).FromJust()); + CHECK(Get("after")->Equals(env.local(), undefined).FromJust()); + CHECK(Get("parent")->Equals(env.local(), Get("p1")).FromJust()); + + reset->Call(env.local(), undefined, 0, 0).ToLocalChecked(); + + CompileRun("done();\n"); + + CHECK_EQ(v8::Promise::kFulfilled, GetPromise("p1")->State()); + CHECK_EQ(v8::Promise::kFulfilled, GetPromise("p2")->State()); + CHECK_EQ(0, GetInteger("initCount")); + CHECK_EQ(1, GetInteger("beforeCount")); + CHECK_EQ(1, GetInteger("afterCount")); + CHECK_EQ(2, GetInteger("resolveCount")); + CHECK(Get("init")->Equals(env.local(), undefined).FromJust()); + CHECK(Get("before")->Equals(env.local(), Get("p2")).FromJust()); + CHECK(Get("resolve")->Equals(env.local(), Get("p2")).FromJust()); + CHECK(Get("after")->Equals(env.local(), Get("p2")).FromJust()); + CHECK(Get("parent")->Equals(env.local(), undefined).FromJust()); + + reset->Call(env.local(), undefined, 0, 0).ToLocalChecked(); + + CompileRun("var done, p3 = new Promise((_, r) => done = r); done();\n"); + + CHECK_EQ(v8::Promise::kRejected, GetPromise("p3")->State()); + CHECK_EQ(1, GetInteger("initCount")); + CHECK_EQ(0, GetInteger("beforeCount")); + CHECK_EQ(0, GetInteger("afterCount")); + CHECK_EQ(1, GetInteger("resolveCount")); + CHECK(Get("init")->Equals(env.local(), Get("p3")).FromJust()); + CHECK(Get("before")->Equals(env.local(), undefined).FromJust()); + CHECK(Get("resolve")->Equals(env.local(), Get("p3")).FromJust()); + CHECK(Get("after")->Equals(env.local(), undefined).FromJust()); + CHECK(Get("parent")->Equals(env.local(), undefined).FromJust()); + + reset->Call(env.local(), undefined, 0, 0).ToLocalChecked(); + + CompileRun("var p4 = p3.catch(() => {});\n"); + + CHECK_EQ(v8::Promise::kFulfilled, GetPromise("p4")->State()); + CHECK_EQ(1, GetInteger("initCount")); + CHECK_EQ(1, GetInteger("beforeCount")); + CHECK_EQ(1, GetInteger("afterCount")); + CHECK_EQ(1, GetInteger("resolveCount")); + CHECK(Get("init")->Equals(env.local(), Get("p4")).FromJust()); + CHECK(Get("before")->Equals(env.local(), Get("p4")).FromJust()); + CHECK(Get("resolve")->Equals(env.local(), Get("p4")).FromJust()); + CHECK(Get("after")->Equals(env.local(), Get("p4")).FromJust()); + CHECK(Get("parent")->Equals(env.local(), Get("p3")).FromJust()); + + reset->Call(env.local(), undefined, 0, 0).ToLocalChecked(); + + // Next run will have original PromiseHooks attached too + isolate->SetPromiseHook([](v8::PromiseHookType type, + v8::Local promise, + v8::Local parentPromise) {}); + } +} TEST(EvalWithSourceURLInMessageScriptResourceNameOrSourceURL) { LocalContext context;