Add a way to convert _stateful_ (Rust) closures into JsFunction
s.
This commit is contained in:
parent
3f40b9270e
commit
4aba159958
5 changed files with 163 additions and 15 deletions
|
@ -114,7 +114,7 @@ pub fn js_function(attr: TokenStream, input: TokenStream) -> TokenStream {
|
|||
}
|
||||
|
||||
let mut env = unsafe { Env::from_raw(raw_env) };
|
||||
let ctx = CallContext::new(&mut env, cb_info, raw_this, &raw_args, #arg_len_span, argc);
|
||||
let ctx = CallContext::new(&mut env, cb_info, raw_this, &raw_args, argc);
|
||||
#execute_js_function
|
||||
}
|
||||
};
|
||||
|
|
|
@ -9,19 +9,33 @@ pub struct CallContext<'env> {
|
|||
raw_this: sys::napi_value,
|
||||
callback_info: sys::napi_callback_info,
|
||||
args: &'env [sys::napi_value],
|
||||
arg_len: usize,
|
||||
/// arguments.length
|
||||
pub length: usize,
|
||||
}
|
||||
|
||||
impl<'env> CallContext<'env> {
|
||||
/// The number of N-api obtained values. In practice this is the numeric
|
||||
/// parameter provided to the `#[js_function(arg_len)]` macro.
|
||||
///
|
||||
/// As a comparison, the (arguments) `.length` represents the actual number
|
||||
/// of arguments given at a specific function call.
|
||||
///
|
||||
/// If `.length < .arg_len`, then the elements in the `length .. arg_len`
|
||||
/// range are just `JsUndefined`s.
|
||||
///
|
||||
/// If `.length > .arg_len`, then truncation has happened and some args have
|
||||
/// been lost.
|
||||
#[inline]
|
||||
fn arg_len(&self) -> usize {
|
||||
self.args.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new(
|
||||
env: &'env mut Env,
|
||||
callback_info: sys::napi_callback_info,
|
||||
raw_this: sys::napi_value,
|
||||
args: &'env [sys::napi_value],
|
||||
arg_len: usize,
|
||||
length: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
|
@ -29,14 +43,13 @@ impl<'env> CallContext<'env> {
|
|||
callback_info,
|
||||
raw_this,
|
||||
args,
|
||||
arg_len,
|
||||
length,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get<ArgType: NapiValue>(&self, index: usize) -> Result<ArgType> {
|
||||
if index + 1 > self.arg_len {
|
||||
if index >= self.arg_len() {
|
||||
Err(Error {
|
||||
status: Status::GenericFailure,
|
||||
reason: "Arguments index out of range".to_owned(),
|
||||
|
@ -48,7 +61,7 @@ impl<'env> CallContext<'env> {
|
|||
|
||||
#[inline]
|
||||
pub fn try_get<ArgType: NapiValue>(&self, index: usize) -> Result<Either<ArgType, JsUndefined>> {
|
||||
if index + 1 > self.arg_len {
|
||||
if index >= self.arg_len() {
|
||||
Err(Error {
|
||||
status: Status::GenericFailure,
|
||||
reason: "Arguments index out of range".to_owned(),
|
||||
|
@ -60,6 +73,16 @@ impl<'env> CallContext<'env> {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_all(&self) -> Vec<crate::JsUnknown> {
|
||||
/* (0 .. self.arg_len()).map(|i| self.get(i).unwrap()).collect() */
|
||||
self
|
||||
.args
|
||||
.iter()
|
||||
.map(|&raw| unsafe { crate::JsUnknown::from_raw_unchecked(self.env.0, raw) })
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_new_target<V>(&self) -> Result<V>
|
||||
where
|
||||
|
|
127
napi/src/env.rs
127
napi/src/env.rs
|
@ -509,6 +509,133 @@ impl Env {
|
|||
Ok(unsafe { JsFunction::from_raw_unchecked(self.0, raw_result) })
|
||||
}
|
||||
|
||||
#[cfg(feature = "napi5")]
|
||||
pub fn create_function_from_closure<R, F>(&self, name: &str, callback: F) -> Result<JsFunction>
|
||||
where
|
||||
F: 'static + Send + Sync + Fn(crate::CallContext<'_>) -> Result<R>,
|
||||
R: NapiValue,
|
||||
{
|
||||
use crate::CallContext;
|
||||
let boxed_callback = Box::new(callback);
|
||||
let closure_data_ptr: *mut F = Box::into_raw(boxed_callback);
|
||||
|
||||
let mut raw_result = ptr::null_mut();
|
||||
let len = name.len();
|
||||
let name = CString::new(name)?;
|
||||
check_status!(unsafe {
|
||||
sys::napi_create_function(
|
||||
self.0,
|
||||
name.as_ptr(),
|
||||
len,
|
||||
Some({
|
||||
unsafe extern "C" fn trampoline<R: NapiValue, F: Fn(CallContext<'_>) -> Result<R>>(
|
||||
raw_env: sys::napi_env,
|
||||
cb_info: sys::napi_callback_info,
|
||||
) -> sys::napi_value {
|
||||
use ::std::panic::{self, AssertUnwindSafe};
|
||||
panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
let (raw_this, ref raw_args, closure_data_ptr) = {
|
||||
let argc = {
|
||||
let mut argc = 0;
|
||||
let status = sys::napi_get_cb_info(
|
||||
raw_env,
|
||||
cb_info,
|
||||
&mut argc,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
debug_assert!(
|
||||
Status::from(status) == Status::Ok,
|
||||
"napi_get_cb_info failed"
|
||||
);
|
||||
argc
|
||||
};
|
||||
let mut raw_args = vec![ptr::null_mut(); argc];
|
||||
let mut raw_this = ptr::null_mut();
|
||||
let mut closure_data_ptr = ptr::null_mut();
|
||||
|
||||
let status = sys::napi_get_cb_info(
|
||||
raw_env,
|
||||
cb_info,
|
||||
&mut { argc },
|
||||
raw_args.as_mut_ptr(),
|
||||
&mut raw_this,
|
||||
&mut closure_data_ptr,
|
||||
);
|
||||
debug_assert!(
|
||||
Status::from(status) == Status::Ok,
|
||||
"napi_get_cb_info failed"
|
||||
);
|
||||
(raw_this, raw_args, closure_data_ptr)
|
||||
};
|
||||
|
||||
let closure: &F = closure_data_ptr
|
||||
.cast::<F>()
|
||||
.as_ref()
|
||||
.expect("`napi_get_cb_info` should have yielded non-`NULL` assoc data");
|
||||
let ref mut env = Env::from_raw(raw_env);
|
||||
let ctx = CallContext::new(env, cb_info, raw_this, raw_args, raw_args.len());
|
||||
closure(ctx).map(|ret: R| ret.raw())
|
||||
}))
|
||||
.map_err(|e| {
|
||||
Error::from_reason(format!(
|
||||
"panic from Rust code: {}",
|
||||
if let Some(s) = e.downcast_ref::<String>() {
|
||||
s
|
||||
} else if let Some(s) = e.downcast_ref::<&str>() {
|
||||
s
|
||||
} else {
|
||||
"<no error message>"
|
||||
},
|
||||
))
|
||||
})
|
||||
.and_then(|v| v)
|
||||
.unwrap_or_else(|e| {
|
||||
JsError::from(e).throw_into(raw_env);
|
||||
ptr::null_mut()
|
||||
})
|
||||
}
|
||||
|
||||
trampoline::<R, F>
|
||||
}),
|
||||
closure_data_ptr.cast(), // We let it borrow the data here
|
||||
&mut raw_result,
|
||||
)
|
||||
})?;
|
||||
|
||||
// Note: based on N-API docs, at this point, we have created an effective
|
||||
// `&'static dyn Fn…` in Rust parlance, in that thanks to `Box::into_raw()`
|
||||
// we are sure the context won't be freed, and thus the callback may use
|
||||
// it to call the actual method thanks to the trampoline…
|
||||
// But we thus have a data leak: there is nothing yet reponsible for
|
||||
// running the `drop(Box::from_raw(…))` cleanup code.
|
||||
//
|
||||
// To solve that, according to the docs, we need to attach a finalizer:
|
||||
check_status!(unsafe {
|
||||
sys::napi_add_finalizer(
|
||||
self.0,
|
||||
raw_result,
|
||||
closure_data_ptr.cast(),
|
||||
Some({
|
||||
unsafe extern "C" fn finalize_box_trampoline<F>(
|
||||
_raw_env: sys::napi_env,
|
||||
closure_data_ptr: *mut c_void,
|
||||
_finalize_hint: *mut c_void,
|
||||
) {
|
||||
drop(Box::<F>::from_raw(closure_data_ptr.cast()))
|
||||
}
|
||||
|
||||
finalize_box_trampoline::<F>
|
||||
}),
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(unsafe { JsFunction::from_raw_unchecked(self.0, raw_result) })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// This API retrieves a napi_extended_error_info structure with information about the last error that occurred.
|
||||
///
|
||||
|
|
|
@ -13,7 +13,9 @@ use crate::sys;
|
|||
#[cfg(feature = "napi5")]
|
||||
use crate::Env;
|
||||
#[cfg(feature = "napi6")]
|
||||
use crate::{Error, Result};
|
||||
use crate::Error;
|
||||
#[cfg(feature = "napi5")]
|
||||
use crate::Result;
|
||||
|
||||
pub struct JsObject(pub(crate) Value);
|
||||
|
||||
|
@ -52,7 +54,7 @@ impl JsObject {
|
|||
),
|
||||
),
|
||||
Box::leak(Box::new(finalize_hint)) as *mut _ as *mut c_void,
|
||||
&mut maybe_ref,
|
||||
&mut maybe_ref, // Note: this does not point to the boxed one…
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -73,6 +75,7 @@ unsafe extern "C" fn finalize_callback<T, Hint, F>(
|
|||
let env = Env::from_raw(raw_env);
|
||||
callback(FinalizeContext { value, hint, env });
|
||||
if !raw_ref.is_null() {
|
||||
// … ⬆️ this branch is thus unreachable.
|
||||
let status = sys::napi_delete_reference(raw_env, raw_ref);
|
||||
debug_assert!(
|
||||
status == sys::Status::napi_ok,
|
||||
|
|
|
@ -18,14 +18,9 @@ impl<T> Ref<T> {
|
|||
#[inline]
|
||||
pub(crate) fn new(js_value: Value, ref_count: u32, inner: T) -> Result<Ref<T>> {
|
||||
let mut raw_ref = ptr::null_mut();
|
||||
let initial_ref_count = 1;
|
||||
assert_ne!(ref_count, 0, "Initial `ref_count` must be > 0");
|
||||
check_status!(unsafe {
|
||||
sys::napi_create_reference(
|
||||
js_value.env,
|
||||
js_value.value,
|
||||
initial_ref_count,
|
||||
&mut raw_ref,
|
||||
)
|
||||
sys::napi_create_reference(js_value.env, js_value.value, ref_count, &mut raw_ref)
|
||||
})?;
|
||||
Ok(Ref {
|
||||
raw_ref,
|
||||
|
|
Loading…
Reference in a new issue