diff --git a/napi/src/env.rs b/napi/src/env.rs index 4b601205..d73cab24 100644 --- a/napi/src/env.rs +++ b/napi/src/env.rs @@ -834,6 +834,68 @@ impl Env { Ok(unsafe { JsDate::from_raw_unchecked(self.0, js_value) }) } + #[cfg(feature = "napi6")] + #[inline] + /// This API associates data with the currently running Agent. data can later be retrieved using `Env::get_instance_data()`. + /// Any existing data associated with the currently running Agent which was set by means of a previous call to `Env::set_instance_data()` will be overwritten. + /// If a `finalize_cb` was provided by the previous call, it will not be called. + pub fn set_instance_data(&self, native: T, hint: Hint, finalize_cb: F) -> Result<()> + where + T: 'static, + Hint: 'static, + F: FnOnce(FinalizeContext), + { + check_status!(unsafe { + sys::napi_set_instance_data( + self.0, + Box::leak(Box::new((TaggedObject::new(native), finalize_cb))) as *mut (TaggedObject, F) + as *mut c_void, + Some( + set_instance_finalize_callback:: + as unsafe extern "C" fn( + env: sys::napi_env, + finalize_data: *mut c_void, + finalize_hint: *mut c_void, + ), + ), + Box::leak(Box::new(hint)) as *mut Hint as *mut c_void, + ) + }) + } + + #[cfg(feature = "napi6")] + #[inline] + /// This API retrieves data that was previously associated with the currently running Agent via `Env::set_instance_data()`. + /// If no data is set, the call will succeed and data will be set to NULL. + pub fn get_instance_data(&self) -> Result> + where + T: 'static, + { + let mut unknown_tagged_object: *mut c_void = ptr::null_mut(); + unsafe { + check_status!(sys::napi_get_instance_data( + self.0, + &mut unknown_tagged_object + ))?; + let type_id = unknown_tagged_object as *const TypeId; + if unknown_tagged_object.is_null() { + return Ok(None); + } + if *type_id == TypeId::of::() { + let tagged_object = unknown_tagged_object as *mut TaggedObject; + (*tagged_object).object.as_mut().map(Some).ok_or(Error { + status: Status::InvalidArg, + reason: "Invalid argument, nothing attach to js_object".to_owned(), + }) + } else { + Err(Error { + status: Status::InvalidArg, + reason: "Invalid argument, T on unrwap is not the type of wrapped object".to_owned(), + }) + } + } + } + /// # Serialize `Rust Struct` into `JavaScript Value` /// ``` /// #[derive(Serialize, Debug, Deserialize)] @@ -933,6 +995,26 @@ unsafe extern "C" fn raw_finalize( Box::from_raw(tagged_object); } +#[cfg(feature = "napi6")] +unsafe extern "C" fn set_instance_finalize_callback( + raw_env: sys::napi_env, + finalize_data: *mut c_void, + finalize_hint: *mut c_void, +) where + T: 'static, + Hint: 'static, + F: FnOnce(FinalizeContext), +{ + let (value, callback) = *Box::from_raw(finalize_data as *mut (TaggedObject, F)); + let hint = *Box::from_raw(finalize_hint as *mut Hint); + let env = Env::from_raw(raw_env); + callback(FinalizeContext { + value: value.object.unwrap(), + hint, + env, + }); +} + #[cfg(feature = "napi3")] unsafe extern "C" fn cleanup_env(hook_data: *mut c_void) { let cleanup_env_hook = Box::from_raw(hook_data as *mut CleanupEnvHookData); diff --git a/test_module/__test__/napi6/instance-data.spec.ts b/test_module/__test__/napi6/instance-data.spec.ts new file mode 100644 index 00000000..1c4f28ab --- /dev/null +++ b/test_module/__test__/napi6/instance-data.spec.ts @@ -0,0 +1,24 @@ +import test from 'ava' + +import { napiVersion } from '../napi-version' + +const bindings = require('../../index.node') + +test('should set and get instance data', (t) => { + if (napiVersion >= 6) { + t.is(bindings.getInstanceData(), undefined) + bindings.setInstanceData() + t.is(bindings.getInstanceData(), 1024) + } else { + t.is(bindings.getInstanceData, undefined) + t.is(bindings.setInstanceData, undefined) + } +}) + +test('should throw if get instance data type mismatched', (t) => { + if (napiVersion >= 6) { + t.throws(bindings.getWrongTypeInstanceData) + } else { + t.is(bindings.getWrongTypeInstanceData, undefined) + } +}) diff --git a/test_module/src/napi6/instance.rs b/test_module/src/napi6/instance.rs new file mode 100644 index 00000000..41003645 --- /dev/null +++ b/test_module/src/napi6/instance.rs @@ -0,0 +1,29 @@ +use napi::*; + +struct NativeObject { + count: i64, +} + +#[contextless_function] +pub fn set_instance_data(env: Env) -> ContextlessResult { + env.set_instance_data(NativeObject { count: 1024 }, 0, |_ctx| {})?; + env.get_undefined().map(Some) +} + +#[contextless_function] +pub fn get_instance_data(env: Env) -> ContextlessResult { + if let Some(obj) = env.get_instance_data::()? { + env.create_int64(obj.count).map(Some) + } else { + Ok(None) + } +} + +#[contextless_function] +pub fn get_wrong_type_instance_data(env: Env) -> ContextlessResult { + if let Some(count) = env.get_instance_data::()? { + env.create_int64(*count as i64).map(Some) + } else { + Ok(None) + } +} diff --git a/test_module/src/napi6/mod.rs b/test_module/src/napi6/mod.rs index 61a8e246..de30e224 100644 --- a/test_module/src/napi6/mod.rs +++ b/test_module/src/napi6/mod.rs @@ -1,8 +1,10 @@ use napi::{JsObject, Result}; mod bigint; +mod instance; use bigint::*; +use instance::*; pub fn register_js(exports: &mut JsObject) -> Result<()> { exports.create_named_method("testCreateBigintFromI64", test_create_bigint_from_i64)?; @@ -13,5 +15,9 @@ pub fn register_js(exports: &mut JsObject) -> Result<()> { exports.create_named_method("testGetBigintI64", test_get_bigint_i64)?; exports.create_named_method("testGetBigintU64", test_get_bigint_u64)?; exports.create_named_method("testGetBigintWords", test_get_bigint_words)?; + + exports.create_named_method("setInstanceData", set_instance_data)?; + exports.create_named_method("getInstanceData", get_instance_data)?; + exports.create_named_method("getWrongTypeInstanceData", get_wrong_type_instance_data)?; Ok(()) }