diff --git a/oden-js/src/lib.rs b/oden-js/src/lib.rs index e4171675..cf11d155 100644 --- a/oden-js/src/lib.rs +++ b/oden-js/src/lib.rs @@ -16,7 +16,7 @@ pub use atom::{Atom, AtomRef}; pub use class::{Class, ClassID}; pub use context::{Context, ContextRef, EvalFlags}; pub use conversion::*; -pub use promise::Promise; +pub use promise::{DefaultRejectedPromiseTracker, Promise, RejectedPromiseTracker}; pub use runtime::Runtime; pub use value::{Value, ValueRef, ValueType}; diff --git a/oden-js/src/promise.rs b/oden-js/src/promise.rs index 8ee23b26..03cabee8 100644 --- a/oden-js/src/promise.rs +++ b/oden-js/src/promise.rs @@ -1,4 +1,4 @@ -use crate::{ContextRef, ValueResult}; +use crate::{ContextRef, ValueRef, ValueResult}; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::mpsc::Sender; @@ -72,3 +72,53 @@ impl Drop for Promise { assert!(self.complete); } } + +pub trait RejectedPromiseTracker { + fn on_rejected_promise( + &self, + ctx: &ContextRef, + promise: &ValueRef, + reason: &ValueRef, + is_handled: bool, + ); +} + +impl RejectedPromiseTracker for T +where + T: Fn(&ContextRef, &ValueRef, &ValueRef, bool) -> (), +{ + fn on_rejected_promise( + &self, + ctx: &ContextRef, + promise: &ValueRef, + reason: &ValueRef, + is_handled: bool, + ) { + self(ctx, promise, reason, is_handled); + } +} + +pub struct DefaultRejectedPromiseTracker {} + +impl DefaultRejectedPromiseTracker { + pub fn new() -> Self { + DefaultRejectedPromiseTracker {} + } +} + +impl RejectedPromiseTracker for DefaultRejectedPromiseTracker { + fn on_rejected_promise( + &self, + ctx: &ContextRef, + _promise: &ValueRef, + reason: &ValueRef, + is_handled: bool, + ) { + if !is_handled { + let reason_str = reason.to_string(ctx).expect( + "Unhandled rejected promise: reason unknown: unable to convert reason to string", + ); + panic!("Unhandled rejected promise: {reason_str}"); + } + } +} diff --git a/oden-js/src/runtime.rs b/oden-js/src/runtime.rs index e349d955..5c1fb4ab 100644 --- a/oden-js/src/runtime.rs +++ b/oden-js/src/runtime.rs @@ -1,7 +1,8 @@ use crate::{ module::loader::{load_module, DefaultModuleLoader, ModuleLoader}, promise::{PromiseEvent, PromiseHandle}, - ContextRef, Promise, Result, Value, + ContextRef, DefaultRejectedPromiseTracker, Promise, RejectedPromiseTracker, Result, Value, + ValueRef, }; use oden_js_sys as sys; use std::cell::{RefCell, RefMut}; @@ -45,20 +46,19 @@ struct PrivateState { promise_send: Sender<(PromiseHandle, PromiseEvent)>, promise_recv: Receiver<(PromiseHandle, PromiseEvent)>, promise_table: HashMap, // ! + rejection_tracker: Arc>, } impl PrivateState { - pub fn new(loader: T) -> Box> - where - T: ModuleLoader + 'static, - { + pub fn new() -> Box> { let (send, recv) = channel(); Box::new(RefCell::new(PrivateState { refs: 1, - loader: Arc::new(Box::new(loader)), + loader: Arc::new(Box::new(DefaultModuleLoader::new())), promise_send: send, promise_recv: recv, promise_table: HashMap::new(), + rejection_tracker: Arc::new(Box::new(DefaultRejectedPromiseTracker::new())), })) } @@ -92,6 +92,28 @@ impl PrivateState { let context = ContextRef::from_raw(ctx); load_module(&context, path, &loader) } + + unsafe extern "C" fn promise_rejection_tracker( + ctx: *mut sys::JSContext, + promise: sys::JSValue, + reason: sys::JSValue, + is_handled: i32, + opaque: *mut std::os::raw::c_void, + ) { + let ctx = ContextRef::from_raw(ctx); + let promise = ValueRef::from_raw(promise); + let reason = ValueRef::from_raw(reason); + let is_handled = is_handled != 0; + let handler = unsafe { + let ptr = opaque as *const RefCell; + ptr.as_ref() + .expect("We already know this runtime is one of ours!") + .borrow() + .rejection_tracker + .clone() + }; + handler.on_rejected_promise(&ctx, &promise, &reason, is_handled); + } } #[derive(Debug)] @@ -101,16 +123,17 @@ pub struct Runtime { impl Runtime { pub fn new() -> Runtime { - Self::with_loader(DefaultModuleLoader::new()) - } - - pub fn with_loader(loader: TLoader) -> Runtime { - let state = PrivateState::new(loader); + let state = PrivateState::new(); let rt = unsafe { let rt = sys::JS_NewRuntime(); let state = Box::into_raw(state) as *mut _; sys::JS_SetRuntimeOpaque(rt, state); sys::JS_SetModuleLoaderFunc(rt, None, Some(PrivateState::module_loader), state); + sys::JS_SetHostPromiseRejectionTracker( + rt, + Some(PrivateState::promise_rejection_tracker), + state, + ); rt }; Runtime { rt } @@ -141,6 +164,26 @@ impl Runtime { } } + /// Set the handler for loading modules. By default this is an instance + /// of `DefaultModuleLoader`. + pub fn set_module_loader(&mut self, loader: T) + where + T: ModuleLoader + 'static, + { + let mut state = unsafe { PrivateState::from_rt_mut(self.rt) }; + state.loader = Arc::new(Box::new(loader)); + } + + /// Set a tracker to be notified whenever a promise is rejected. By + /// default, this is an instance of `DefaultRejectedPromiseHandler`. + pub fn set_rejected_promise_tracker(&mut self, tracker: T) + where + T: RejectedPromiseTracker + 'static, + { + let mut state = unsafe { PrivateState::from_rt_mut(self.rt) }; + state.rejection_tracker = Arc::new(Box::new(tracker)); + } + pub fn run_gc(&mut self) { unsafe { sys::JS_RunGC(self.rt); diff --git a/src/script.rs b/src/script.rs index 4d38a705..5d9b0c2e 100644 --- a/src/script.rs +++ b/src/script.rs @@ -56,7 +56,8 @@ pub struct ScriptContext { impl ScriptContext { pub fn new() -> Self { - let runtime = Runtime::with_loader(Loader::new()); + let mut runtime = Runtime::new(); + runtime.set_module_loader(Loader::new()); let mut context = Context::new(runtime); context.add_intrinsic_bigfloat();