diff --git a/src/binding.cc b/src/binding.cc index cb173a43..0755510e 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -188,6 +188,10 @@ void v8__Isolate__RequestInterrupt(v8::Isolate* isolate, isolate->RequestInterrupt(callback, data); } +void v8__Isolate__SetPromiseHook(v8::Isolate* isolate, v8::PromiseHook hook) { + isolate->SetPromiseHook(hook); +} + void v8__Isolate__SetPromiseRejectCallback(v8::Isolate* isolate, v8::PromiseRejectCallback callback) { isolate->SetPromiseRejectCallback(callback); diff --git a/src/isolate.rs b/src/isolate.rs index 34a93241..eb457448 100644 --- a/src/isolate.rs +++ b/src/isolate.rs @@ -42,8 +42,34 @@ pub enum MicrotasksPolicy { Auto = 2, } +/// PromiseHook with type Init is called when a new promise is +/// created. When a new promise is created as part of the chain in the +/// case of Promise.then or in the intermediate promises created by +/// Promise.{race, all}/AsyncFunctionAwait, we pass the parent promise +/// otherwise we pass undefined. +/// +/// PromiseHook with type Resolve is called at the beginning of +/// resolve or reject function defined by CreateResolvingFunctions. +/// +/// PromiseHook with type Before is called at the beginning of the +/// PromiseReactionJob. +/// +/// PromiseHook with type After is called right at the end of the +/// PromiseReactionJob. +#[derive(Debug)] +#[repr(C)] +pub enum PromiseHookType { + Init, + Resolve, + Before, + After, +} + pub type MessageCallback = extern "C" fn(Local, Local); +pub type PromiseHook = + extern "C" fn(PromiseHookType, Local, Local); + pub type PromiseRejectCallback = extern "C" fn(PromiseRejectMessage); /// HostInitializeImportMetaObjectCallback is called the first time import.meta @@ -127,6 +153,7 @@ extern "C" { callback: NearHeapLimitCallback, heap_limit: usize, ); + fn v8__Isolate__SetPromiseHook(isolate: *mut Isolate, hook: PromiseHook); fn v8__Isolate__SetPromiseRejectCallback( isolate: *mut Isolate, callback: PromiseRejectCallback, @@ -398,6 +425,12 @@ impl Isolate { unsafe { v8__Isolate__AddMessageListener(self, callback) } } + /// Set the PromiseHook callback for various promise lifecycle + /// events. + pub fn set_promise_hook(&mut self, hook: PromiseHook) { + unsafe { v8__Isolate__SetPromiseHook(self, hook) } + } + /// Set callback to notify about promise reject with no handler, or /// revocation of such a previous notification once the handler is added. pub fn set_promise_reject_callback( diff --git a/src/lib.rs b/src/lib.rs index facd0f60..a6d94f61 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,6 +98,8 @@ pub use isolate::MessageCallback; pub use isolate::MicrotasksPolicy; pub use isolate::NearHeapLimitCallback; pub use isolate::OwnedIsolate; +pub use isolate::PromiseHook; +pub use isolate::PromiseHookType; pub use isolate::PromiseRejectCallback; pub use isolate_create_params::CreateParams; pub use module::*; diff --git a/tests/test_api.rs b/tests/test_api.rs index 547e03e3..2f895ad9 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -1726,6 +1726,57 @@ fn promise_reject_callback_no_value() { } } +#[test] +fn promise_hook() { + extern "C" fn hook( + type_: v8::PromiseHookType, + promise: v8::Local, + _parent: v8::Local, + ) { + let scope = &mut unsafe { v8::CallbackScope::new(promise) }; + let context = promise.creation_context(scope); + let scope = &mut v8::ContextScope::new(scope, context); + let global = context.global(scope); + let name = v8::String::new(scope, "hook").unwrap(); + let func = global.get(scope, name.into()).unwrap(); + let func = v8::Local::::try_from(func).unwrap(); + let args = &[v8::Integer::new(scope, type_ as i32).into(), promise.into()]; + func.call(scope, global.into(), args).unwrap(); + } + let _setup_guard = setup(); + let isolate = &mut v8::Isolate::new(Default::default()); + isolate.set_promise_hook(hook); + { + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + let source = r#" + var promises = new Set(); + function hook(type, promise) { + if (type === /* Init */ 0) promises.add(promise); + if (type === /* Resolve */ 1) promises.delete(promise); + } + function expect(expected, actual = promises.size) { + if (actual !== expected) throw `expected ${expected}, actual ${actual}`; + } + expect(0); + new Promise(resolve => { + expect(1); + resolve(); + expect(0); + }); + expect(0); + new Promise(() => {}); + expect(1); + promises.values().next().value + "#; + let promise = eval(scope, source).unwrap(); + let promise = v8::Local::::try_from(promise).unwrap(); + assert!(!promise.has_handler()); + assert_eq!(promise.state(), v8::PromiseState::Pending); + } +} + fn mock_script_origin<'s>( scope: &mut v8::HandleScope<'s>, resource_name_: &str,