use crate::{ module::loader::{DefaultModuleLoader, ModuleLoader, ModuleSource}, promise::{PromiseEvent, PromiseHandle}, throw_error, Atom, AtomRef, ContextRef, DefaultRejectedPromiseTracker, Promise, RejectedPromiseTracker, Result, Value, ValueRef, }; use oden_js_sys as sys; use std::cell::{RefCell, RefMut}; use std::collections::HashMap; use std::ffi::CStr; use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::Arc; // NOTE: This is DEEPLY unsafe, but the runtime lifetime dominates the // lifetime of all of this stuff, so we really *can* hold on to them. struct PromiseEntry { context: *mut sys::JSContext, resolve: sys::JSValue, reject: sys::JSValue, } impl PromiseEntry { // NOTE: Takes ownership of resolve and reject. fn new(context: *mut sys::JSContext, resolve: sys::JSValue, reject: sys::JSValue) -> Self { PromiseEntry { context: unsafe { sys::JS_DupContext(context) }, resolve, reject, } } } impl Drop for PromiseEntry { fn drop(&mut self) { unsafe { sys::JS_FreeValue(self.context, self.resolve); sys::JS_FreeValue(self.context, self.reject); sys::JS_FreeContext(self.context); } } } struct PrivateState { refs: u64, loader: Arc>>, promise_send: Sender<(PromiseHandle, PromiseEvent)>, promise_recv: Receiver<(PromiseHandle, PromiseEvent)>, promise_table: HashMap, // ! rejection_tracker: Arc>, } impl PrivateState { pub fn new() -> Box> { let (send, recv) = channel(); Box::new(RefCell::new(PrivateState { refs: 1, loader: Arc::new(RefCell::new(Box::new(DefaultModuleLoader::new()))), promise_send: send, promise_recv: recv, promise_table: HashMap::new(), rejection_tracker: Arc::new(Box::new(DefaultRejectedPromiseTracker::new())), })) } pub unsafe fn from_rt_mut(rt: *mut sys::JSRuntime) -> RefMut<'static, PrivateState> { unsafe { let ptr = sys::JS_GetRuntimeOpaque(rt) as *const RefCell; ptr.as_ref() .expect("We already know this runtime is one of ours!") .borrow_mut() } } unsafe extern "C" fn module_loader( ctx: *mut sys::JSContext, path: *const i8, opaque: *mut std::os::raw::c_void, ) -> *mut sys::JSModuleDef { let path = match CStr::from_ptr(path).to_str() { Ok(s) => s, Err(_) => return std::ptr::null_mut(), }; let loader = unsafe { let ptr = opaque as *const RefCell; ptr.as_ref() .expect("We already know this runtime is one of ours!") .borrow() .loader .clone() }; let context = ContextRef::from_raw(ctx); let result = { let mut loader = loader.borrow_mut(); loader.load(&context, path) }; match result { Ok(ModuleSource::Native(native)) => native.module, Ok(ModuleSource::JavaScript(js)) => match context.eval_module(&js, path) { Ok(v) => v.module, Err(e) => { throw_error(&context, e); std::ptr::null_mut() } }, Err(e) => { throw_error(&context, e); std::ptr::null_mut() } } } unsafe extern "C" fn source_mapper( ctx: *mut sys::JSContext, opaque: *mut std::os::raw::c_void, file: u32, line: i32, pfile: *mut u32, pline: *mut i32, ) { let ctx = ContextRef::from_raw(ctx); let file = AtomRef::from_raw(file, &ctx); let loader = unsafe { let ptr = opaque as *const RefCell; ptr.as_ref() .expect("We already know this runtime is one of ours!") .borrow() .loader .clone() }; let result = { let loader = loader.borrow(); loader.map_source(&ctx, &file, line) }; match result { Ok((file, line)) => { *pfile = Atom::consume(file); *pline = line; } Err(e) => { throw_error(&ctx, e); *pfile = file.atom; *pline = line; } } } 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)] pub struct Runtime { pub(crate) rt: *mut sys::JSRuntime, } impl Runtime { pub fn new() -> Runtime { 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 } } pub(crate) fn from_raw(rt: *mut sys::JSRuntime) -> Self { let mut state = unsafe { PrivateState::from_rt_mut(rt) }; state.refs += 1; Runtime { rt } } pub fn set_memory_limit(&mut self, limit: usize) { unsafe { sys::JS_SetMemoryLimit(self.rt, limit); } } pub fn set_gc_threshold(&mut self, threshold: usize) { unsafe { sys::JS_SetGCThreshold(self.rt, threshold); } } /// Pass in 0 to disable the maximum size check. pub fn set_max_stack_size(&mut self, max_stack: usize) { unsafe { sys::JS_SetMaxStackSize(self.rt, max_stack); } } /// 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) }; if loader.support_source_map() { unsafe { let opaque = sys::JS_GetRuntimeOpaque(self.rt); sys::JS_SetSourceMapFunc(self.rt, Some(PrivateState::source_mapper), opaque); } } else { unsafe { sys::JS_SetSourceMapFunc(self.rt, None, std::ptr::null_mut()); } } state.loader = Arc::new(RefCell::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); } } /// Construct a new promise. pub fn new_promise(&self, context: &ContextRef) -> Result<(Value, Promise)> { unsafe { let mut state = PrivateState::from_rt_mut(self.rt); let mut resolving_funcs: [sys::JSValue; 2] = [sys::JS_MakeUndefined(), sys::JS_MakeUndefined()]; let val = sys::JS_NewPromiseCapability( context.ctx, &mut resolving_funcs as *mut sys::JSValue, ); if sys::JS_ValueGetTag(val) == sys::JS_TAG_EXCEPTION { return Err(context.exception_error()); } let handle = PromiseHandle::new(); state.promise_table.insert( handle, PromiseEntry::new(context.ctx, resolving_funcs[0], resolving_funcs[1]), ); let promise = Promise::new(handle, state.promise_send.clone()); drop(state); // Need to drop state so that the value constructor can addref. Ok((Value::from_raw(val, context), promise)) } } /// Process all pending async jobs. This includes all promise resolutions. fn process_promise_completions(&self) { let mut promises = vec![]; // First, gather all the completed promises into a temporary vector // so that we only need to borrow our mutable state for a short // period of time. let mut state = unsafe { PrivateState::from_rt_mut(self.rt) }; while let Ok((handle, evt)) = state.promise_recv.try_recv() { if let Some(entry) = state.promise_table.remove(&handle) { promises.push((entry, evt)); } } drop(state); // Don't need our internal state anymore. // Nowe we can complete all the promises. for (entry, evt) in promises { let ctx = ContextRef::from_raw(entry.context); let (callback, value) = match evt { PromiseEvent::Resolved(v) => (entry.resolve, v), PromiseEvent::Rejected(v) => (entry.reject, v), }; // Convert the result into a JS value, which we can only // really do while we are on this thread. let value = value(&ctx).expect("Should be able to convert promise result to value"); // Call the particular callback and make sure it doesn't throw. ctx.check_exception(unsafe { let mut args = [value.val]; sys::JS_Call( entry.context, callback, sys::JS_MakeUndefined(), 1, args.as_mut_ptr(), ) }) .expect("Exception thrown by promise callback"); } } /// Process all pending async jobs. This includes all promise resolutions. pub fn process_all_jobs(&self) -> Result<()> { self.process_promise_completions(); loop { let mut ctx1: *mut sys::JSContext = std::ptr::null_mut(); let err = unsafe { sys::JS_ExecutePendingJob(self.rt, &mut ctx1) }; if err == 0 { break; } else if err < 0 { return Err(ContextRef::from_raw(ctx1).exception_error()); } } Ok(()) } } impl Clone for Runtime { fn clone(&self) -> Self { Runtime::from_raw(self.rt) } } impl Drop for Runtime { fn drop(&mut self) { let should_free = { let mut state = unsafe { let ptr = sys::JS_GetRuntimeOpaque(self.rt) as *const RefCell; ptr.as_ref() .expect("We already know this runtime is one of ours!") .borrow_mut() }; state.refs -= 1; state.refs == 0 }; if should_free { unsafe { let opaque = sys::JS_GetRuntimeOpaque(self.rt); sys::JS_RunGC(self.rt); sys::JS_FreeRuntime(self.rt); if !opaque.is_null() { // Just let the system drop it here. let _ = Box::from_raw(opaque as *mut RefCell); } } } } }