use crate::{Class, ClassID, ContextRef, Error, ValueRef, ValueResult}; use oden_js_sys as sys; use std::ffi::{c_int, CString}; use std::panic::catch_unwind; pub trait Callback: Fn(&ContextRef, &ValueRef, &[&ValueRef]) -> ValueResult {} impl Callback for T where T: Fn(&ContextRef, &ValueRef, &[&ValueRef]) -> ValueResult {} struct CallbackObject { callback: T, } impl CallbackObject { fn new(callback: T, context: &ContextRef) -> ValueResult { let obj = CallbackObject { callback }; obj.into_value(context) } } impl Class for CallbackObject { fn class_id() -> &'static ClassID { static ID: ClassID = ClassID::new("CallbackObject"); &ID } } fn throw_string(context: &ContextRef, message: String) -> sys::JSValue { let ctx = context.ctx; match context.new_string(&message) { Ok(e) => unsafe { // Because context.new_string yields an owned Value, and will // clean it up on the way out, we need to explicitly DupValue a // reference for the `Throw` to own. let err = sys::JS_NewError(ctx); if sys::JS_ValueGetTag(err) == sys::JS_TAG_EXCEPTION { // GIVE UP; this is out of memory anyway things probably went // wrong because of that. return err; } sys::JS_DupValue(ctx, e.val); // SetProperty takes ownership. let prop = CString::new("message").unwrap(); if sys::JS_SetPropertyStr(ctx, err, prop.as_ptr(), e.val) == -1 { // Also an out of memory but we need to free the error object // on our way out. sys::JS_FreeValue(ctx, err); return sys::JS_MakeException(); // JS_EXCEPTION } sys::JS_Throw(ctx, err) }, Err(_) => unsafe { sys::JS_Throw( ctx, sys::JS_NewString(ctx, "Errors within errors: embedded nulls in the description of the error that occurred".as_bytes().as_ptr() as *const i8), ) }, } } fn callback_impl( ctx: *mut sys::JSContext, _this: sys::JSValue, argc: c_int, argv: *mut sys::JSValue, _magic: c_int, data: *mut sys::JSValue, ) -> sys::JSValue where F: Callback, { let context: ContextRef = ContextRef::from_raw(ctx); let this: ValueRef = ValueRef::from_raw(_this); let mut actual_args = Vec::new(); unsafe { for arg in std::slice::from_raw_parts(argv, argc.try_into().unwrap()) { actual_args.push(ValueRef::from_raw(*arg)); } } let mut args = Vec::new(); for aa in &actual_args { args.push(aa); } // Grab the callback that we stashed in the value. let data_ref = unsafe { ValueRef::from_raw(*data) }; let result = match CallbackObject::::try_from_value(&data_ref) { Ok(closure) => (closure.callback)(&context, &this, &args), Err(e) => Err(e), }; match result { Ok(v) => { // `result` is owned; make sure to dup the value on the way out. Ideally // we would just have a `take` or something on `Value` but I don't quite // know how to implement it correctly. unsafe { let ret = &v.val; sys::JS_DupValue(ctx, *ret); *ret } } Err(Error::Exception(e)) => unsafe { // If we returned `Error::Exception` then we're propagating an // exception through the JS stack, just flip it. let exc = &e.val; sys::JS_DupValue(ctx, *exc); sys::JS_Throw(ctx, *exc) }, Err(err) => throw_string(&context, err.to_string()), } } unsafe extern "C" fn trampoline( ctx: *mut sys::JSContext, _this: sys::JSValue, argc: c_int, argv: *mut sys::JSValue, _magic: c_int, data: *mut sys::JSValue, ) -> sys::JSValue where F: Callback, { match catch_unwind(|| callback_impl::(ctx, _this, argc, argv, _magic, data)) { Ok(r) => r, Err(e) => { let message = if let Some(e) = e.downcast_ref::<&'static str>() { format!("{e}") } else { format!("Unknown error") }; let context: ContextRef = ContextRef::from_raw(ctx); throw_string(&context, message) } } } /// Get an unsafe pointer to the trampoline for a closure. We need a generic /// function here to intuit the type of the closure, otherwise otherwise /// there's nothing we can put in the `<>` for `trampoline`. This also /// ensures that the rust compiler actually generates the trampoline. fn get_trampoline(_closure: &F) -> sys::JSCFunctionData where F: Callback, { Some(trampoline::) } /// Construct a new value that wraps a closure. pub(crate) fn new_fn(ctx: &ContextRef, func: F) -> sys::JSValue where F: Callback, { let closure = func; let callback = get_trampoline(&closure); let value = CallbackObject::new(closure, ctx).expect("Unable to create"); unsafe { // Just some silly pointer magic; they want an array but we only have // one value so. let mut val = value.val; sys::JS_NewCFunctionData(ctx.ctx, callback, 0, 0, 1, &mut val) } }