135 lines
3.9 KiB
Rust
135 lines
3.9 KiB
Rust
use crate::{throw_string, Class, ClassID, ContextRef, Error, ValueRef, ValueResult};
|
|
use oden_js_sys as sys;
|
|
use std::ffi::c_int;
|
|
use std::panic::catch_unwind;
|
|
|
|
pub trait Callback: 'static + Fn(&ContextRef, &ValueRef, &[&ValueRef]) -> ValueResult {}
|
|
|
|
impl<T> Callback for T where T: 'static + Fn(&ContextRef, &ValueRef, &[&ValueRef]) -> ValueResult {}
|
|
|
|
struct CallbackObject<T: Callback> {
|
|
callback: T,
|
|
}
|
|
|
|
impl<T: Callback> CallbackObject<T> {
|
|
fn new(callback: T, context: &ContextRef) -> ValueResult {
|
|
let obj = CallbackObject { callback };
|
|
obj.into_value(context)
|
|
}
|
|
}
|
|
|
|
impl<T: Callback> Class for CallbackObject<T> {
|
|
fn class_id() -> &'static ClassID {
|
|
static ID: ClassID = ClassID::new("CallbackObject");
|
|
&ID
|
|
}
|
|
}
|
|
|
|
fn callback_impl<F>(
|
|
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::<F>::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<F>(
|
|
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::<F>(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<F>(_closure: &F) -> sys::JSCFunctionData
|
|
where
|
|
F: Callback,
|
|
{
|
|
Some(trampoline::<F>)
|
|
}
|
|
|
|
/// Construct a new value that wraps a closure.
|
|
pub(crate) fn new_fn<F>(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)
|
|
}
|
|
}
|