mirror of
https://github.com/napi-rs/napi-rs.git
synced 2025-12-08 19:56:07 +00:00
feat(napi): EscapableHandleScope API (#2652)
This commit is contained in:
parent
92b094e487
commit
7182db3a81
12
.github/workflows/test-release.yaml
vendored
12
.github/workflows/test-release.yaml
vendored
@ -602,6 +602,18 @@ jobs:
|
||||
settings:
|
||||
- features: 'napi1,napi2,napi3,napi4,napi5,napi6,napi7,napi8,napi9,experimental,async,chrono_date,latin1,full'
|
||||
package: 'napi'
|
||||
- features: 'napi3'
|
||||
package: 'napi'
|
||||
- features: 'napi3,compat-mode'
|
||||
package: 'napi'
|
||||
- features: 'napi9'
|
||||
package: 'napi'
|
||||
- features: 'napi3,serde-json'
|
||||
package: 'napi'
|
||||
- features: 'napi9,serde-json'
|
||||
package: 'napi'
|
||||
- features: 'async,compat-mode'
|
||||
package: 'napi'
|
||||
- features: 'compat-mode,strict,type-def,noop,full,default'
|
||||
package: 'napi-derive'
|
||||
- features: 'noop'
|
||||
|
||||
@ -2,6 +2,7 @@ use std::{marker::PhantomData, ptr};
|
||||
|
||||
use crate::{bindgen_prelude::*, check_status, Value};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Array<'env> {
|
||||
pub(crate) env: sys::napi_env,
|
||||
pub(crate) inner: sys::napi_value,
|
||||
|
||||
@ -12,6 +12,7 @@ use crate::{
|
||||
check_status, sys, Env, JsValue, Property, PropertyAttributes, Value, ValueType,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct This<'env, T = Object<'env>> {
|
||||
pub object: T,
|
||||
_phantom: &'env PhantomData<()>,
|
||||
@ -46,12 +47,6 @@ impl<'env, T: JsValue<'env>> JsValue<'env> for This<'_, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, T: JsValue<'env>> JsValue<'env> for &This<'_, T> {
|
||||
fn value(&self) -> Value {
|
||||
self.object.value()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromNapiValue> FromNapiValue for This<'_, T> {
|
||||
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
|
||||
Ok(Self {
|
||||
@ -146,7 +141,7 @@ impl<'env, T: 'env> ClassInstance<'env, T> {
|
||||
U: FromNapiValue + JsValue<'this>,
|
||||
{
|
||||
let property = Property::new(name)?
|
||||
.with_value(&self)
|
||||
.with_value(self)
|
||||
.with_property_attributes(attributes);
|
||||
|
||||
check_status!(
|
||||
@ -171,16 +166,6 @@ impl<'env, T: 'env> ClassInstance<'env, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, T: 'env> JsValue<'env> for &ClassInstance<'env, T> {
|
||||
fn value(&self) -> Value {
|
||||
Value {
|
||||
env: self.env,
|
||||
value: self.value,
|
||||
value_type: ValueType::Object,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, T: 'env> TypeName for ClassInstance<'env, T>
|
||||
where
|
||||
&'env T: TypeName,
|
||||
|
||||
@ -105,6 +105,7 @@ impl_tuple_conversion!(
|
||||
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z
|
||||
);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
/// A JavaScript function.
|
||||
/// It can only live in the scope of a function call.
|
||||
/// If you want to use it outside the scope of a function call, you can turn it into a reference.
|
||||
|
||||
@ -18,7 +18,7 @@ pub struct PromiseRaw<'env, T> {
|
||||
_phantom: &'env PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'env, T> JsValue<'env> for PromiseRaw<'env, T> {
|
||||
impl<'env, T: FromNapiValue> JsValue<'env> for PromiseRaw<'env, T> {
|
||||
fn value(&self) -> Value {
|
||||
Value {
|
||||
env: self.env,
|
||||
@ -28,7 +28,7 @@ impl<'env, T> JsValue<'env> for PromiseRaw<'env, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env, T> JsObjectValue<'env> for PromiseRaw<'env, T> {}
|
||||
impl<'env, T: FromNapiValue> JsObjectValue<'env> for PromiseRaw<'env, T> {}
|
||||
|
||||
impl<T: FromNapiValue> TypeName for PromiseRaw<'_, T> {
|
||||
fn type_name() -> &'static str {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use std::ptr;
|
||||
|
||||
use crate::{check_status, sys, Env, JsValue, Result};
|
||||
use crate::{bindgen_runtime::FromNapiValue, check_status, sys, Env, JsValue, Result};
|
||||
|
||||
pub struct HandleScope {
|
||||
pub(crate) scope: sys::napi_handle_scope,
|
||||
@ -16,7 +16,29 @@ impl HandleScope {
|
||||
Ok(Self { scope })
|
||||
}
|
||||
|
||||
pub fn run<A, T>(self, arg: A, f: impl FnOnce(A) -> Result<T>) -> Result<T>
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is unsafe because it will invalidate the JsValue created within the HandleScope.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```no_run
|
||||
/// #[napi]
|
||||
/// pub fn shorter_scope(env: &Env, arr: Array) -> Result<Vec<u32>> {
|
||||
/// let len = arr.len();
|
||||
/// let mut result = Vec::with_capacity(len as usize);
|
||||
/// for i in 0..len {
|
||||
/// let scope = HandleScope::create(env)?;
|
||||
/// let value: Unknown = arr.get_element(i)?;
|
||||
/// ^^^ this will be invalidated after the scope is closed
|
||||
/// let len = unsafe { scope.close(value, |v| match v.get_type()? {
|
||||
/// ValueType::String => Ok(v.utf8_len()? as u32),
|
||||
/// _ => Ok(0),
|
||||
/// })? };
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub unsafe fn close<A, T>(self, arg: A, f: impl FnOnce(A) -> Result<T>) -> Result<T>
|
||||
where
|
||||
A: JsValuesTuple,
|
||||
{
|
||||
@ -30,6 +52,57 @@ impl HandleScope {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EscapableHandleScope<'env> {
|
||||
pub(crate) scope: sys::napi_escapable_handle_scope,
|
||||
pub(crate) env: sys::napi_env,
|
||||
pub(crate) phantom: std::marker::PhantomData<&'env ()>,
|
||||
}
|
||||
|
||||
impl<'env, 'scope: 'env> EscapableHandleScope<'scope> {
|
||||
pub fn with<
|
||||
T,
|
||||
Args: JsValuesTuple,
|
||||
F: 'env + FnOnce(EscapableHandleScope<'env>, Args) -> Result<T>,
|
||||
>(
|
||||
env: &'env Env,
|
||||
args: Args,
|
||||
scope_fn: F,
|
||||
) -> Result<T> {
|
||||
let mut scope = ptr::null_mut();
|
||||
check_status!(
|
||||
unsafe { sys::napi_open_escapable_handle_scope(env.0, &mut scope) },
|
||||
"Failed to open handle scope"
|
||||
)?;
|
||||
let scope: EscapableHandleScope<'env> = Self {
|
||||
scope,
|
||||
env: env.0,
|
||||
phantom: std::marker::PhantomData,
|
||||
};
|
||||
scope_fn(scope, args)
|
||||
}
|
||||
|
||||
pub fn escape<V: JsValue<'env> + FromNapiValue>(&self, value: V) -> Result<V> {
|
||||
let mut result = ptr::null_mut();
|
||||
check_status!(
|
||||
unsafe { sys::napi_escape_handle(self.env, self.scope, value.raw(), &mut result) },
|
||||
"Failed to escape handle"
|
||||
)?;
|
||||
unsafe { V::from_napi_value(self.env, result) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EscapableHandleScope<'_> {
|
||||
fn drop(&mut self) {
|
||||
let status = unsafe { sys::napi_close_escapable_handle_scope(self.env, self.scope) };
|
||||
if status != sys::Status::napi_ok {
|
||||
panic!(
|
||||
"Failed to close handle scope: {}",
|
||||
crate::Status::from(status)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait JsValuesTuple {
|
||||
fn env(&self) -> sys::napi_env;
|
||||
}
|
||||
|
||||
@ -26,9 +26,9 @@ use crate::bindgen_runtime::FinalizeContext;
|
||||
use crate::bindgen_runtime::FunctionCallContext;
|
||||
#[cfg(all(feature = "tokio_rt", feature = "napi4"))]
|
||||
use crate::bindgen_runtime::PromiseRaw;
|
||||
#[cfg(feature = "napi4")]
|
||||
use crate::bindgen_runtime::ToNapiValue;
|
||||
use crate::bindgen_runtime::{FromNapiValue, Function, JsValuesTupleIntoVec, Object, Unknown};
|
||||
use crate::bindgen_runtime::{
|
||||
FromNapiValue, Function, JsValuesTupleIntoVec, Object, ToNapiValue, Unknown,
|
||||
};
|
||||
#[cfg(feature = "napi3")]
|
||||
use crate::cleanup_env::{CleanupEnvHook, CleanupEnvHookData};
|
||||
#[cfg(feature = "serde-json")]
|
||||
|
||||
@ -3,7 +3,7 @@ use std::ops::{Deref, DerefMut};
|
||||
use std::ptr;
|
||||
|
||||
use crate::{
|
||||
bindgen_runtime::{TypeName, ValidateNapiValue},
|
||||
bindgen_runtime::{FromNapiValue, TypeName, ValidateNapiValue},
|
||||
check_status, sys, Env, Error, JsValue, Ref, Result, Status, Unknown, Value, ValueType,
|
||||
};
|
||||
|
||||
@ -34,6 +34,16 @@ impl ValidateNapiValue for JsBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromNapiValue for JsBuffer {
|
||||
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
|
||||
Ok(JsBuffer(Value {
|
||||
env,
|
||||
value: napi_val,
|
||||
value_type: ValueType::Object,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl JsValue<'_> for JsBuffer {
|
||||
fn value(&self) -> Value {
|
||||
self.0
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::ptr;
|
||||
|
||||
use super::{check_status, JsObjectValue};
|
||||
use crate::{
|
||||
bindgen_runtime::{FromNapiValue, TypeName, ValidateNapiValue},
|
||||
sys, Error, JsValue, Result, Status, Value, ValueType,
|
||||
bindgen_runtime::{FromNapiValue, JsObjectValue, TypeName, ValidateNapiValue},
|
||||
check_status, sys, Error, JsValue, Result, Status, Value, ValueType,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
||||
@ -6,6 +6,19 @@ pub struct JsGlobal<'env>(
|
||||
pub(crate) std::marker::PhantomData<&'env ()>,
|
||||
);
|
||||
|
||||
impl FromNapiValue for JsGlobal<'_> {
|
||||
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
|
||||
Ok(JsGlobal(
|
||||
Value {
|
||||
env,
|
||||
value: napi_val,
|
||||
value_type: ValueType::Object,
|
||||
},
|
||||
std::marker::PhantomData,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env> JsValue<'env> for JsGlobal<'env> {
|
||||
fn value(&self) -> Value {
|
||||
self.0
|
||||
|
||||
@ -12,11 +12,11 @@ use crate::bindgen_runtime::finalize_closures;
|
||||
#[cfg(feature = "compat-mode")]
|
||||
use crate::{
|
||||
bindgen_runtime::{FromNapiValue, ValidateNapiValue},
|
||||
type_of, Callback, Error, Status,
|
||||
check_status, type_of, Callback, Error, Status,
|
||||
};
|
||||
use crate::{
|
||||
bindgen_runtime::{JsObjectValue, ToNapiValue},
|
||||
check_status, sys, Result, ValueType,
|
||||
sys, Result, ValueType,
|
||||
};
|
||||
|
||||
#[cfg(feature = "compat-mode")]
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use std::fmt::{self, Display};
|
||||
use std::ptr;
|
||||
|
||||
use crate::bindgen_runtime::EscapableHandleScope;
|
||||
use crate::{
|
||||
bindgen_runtime::{FromNapiValue, Object, Unknown},
|
||||
{check_status, sys, JsNumber, JsString, Result, ValueType},
|
||||
@ -19,7 +20,7 @@ impl Display for Value {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait JsValue<'env>: Sized {
|
||||
pub trait JsValue<'env>: Sized + FromNapiValue {
|
||||
fn value(&self) -> Value;
|
||||
|
||||
fn raw(&self) -> sys::napi_value {
|
||||
@ -157,4 +158,20 @@ pub trait JsValue<'env>: Sized {
|
||||
})?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn escape<'scope, E: JsValue<'scope> + FromNapiValue>(
|
||||
&self,
|
||||
escapable_handle_scope: EscapableHandleScope<'scope>,
|
||||
) -> Result<E> {
|
||||
let mut result = ptr::null_mut();
|
||||
unsafe {
|
||||
sys::napi_escape_handle(
|
||||
escapable_handle_scope.env,
|
||||
escapable_handle_scope.scope,
|
||||
self.raw(),
|
||||
&mut result,
|
||||
)
|
||||
};
|
||||
unsafe { <E as FromNapiValue>::from_napi_value(self.value().env, result) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -824,6 +824,8 @@ Generated by [AVA](https://avajs.dev).
|
||||
value: number␊
|
||||
}␊
|
||||
␊
|
||||
export declare function shorterEscapableScope(createString: () => string | null): string␊
|
||||
␊
|
||||
export declare function shorterScope(arr: unknown[]): Array<number>␊
|
||||
␊
|
||||
export declare function shutdownRuntime(): void␊
|
||||
|
||||
Binary file not shown.
@ -233,6 +233,7 @@ import {
|
||||
shutdownRuntime,
|
||||
callAsyncWithUnknownReturnValue,
|
||||
shorterScope,
|
||||
shorterEscapableScope,
|
||||
} from '../index.cjs'
|
||||
// import other stuff in `#[napi(module_exports)]`
|
||||
import nativeAddon from '../index.cjs'
|
||||
@ -1666,3 +1667,19 @@ test('shorter scope', (t) => {
|
||||
const result = shorterScope(['hello', { foo: 'bar' }, 'world', true])
|
||||
t.deepEqual(result, [5, 1, 5, 0])
|
||||
})
|
||||
|
||||
test('escapable handle scope', (t) => {
|
||||
function makeIterFunction() {
|
||||
let i = 0
|
||||
return () => {
|
||||
if (i >= 10_000) {
|
||||
return null
|
||||
}
|
||||
i++
|
||||
return Math.random().toString().repeat(100)
|
||||
}
|
||||
}
|
||||
t.notThrows(() => {
|
||||
shorterEscapableScope(makeIterFunction())
|
||||
})
|
||||
})
|
||||
|
||||
@ -292,6 +292,7 @@ export const roundtripStr = __napiModule.exports.roundtripStr
|
||||
export const runScript = __napiModule.exports.runScript
|
||||
export const setNullByteProperty = __napiModule.exports.setNullByteProperty
|
||||
export const setSymbolInObj = __napiModule.exports.setSymbolInObj
|
||||
export const shorterEscapableScope = __napiModule.exports.shorterEscapableScope
|
||||
export const shorterScope = __napiModule.exports.shorterScope
|
||||
export const shutdownRuntime = __napiModule.exports.shutdownRuntime
|
||||
export const spawnThreadInThread = __napiModule.exports.spawnThreadInThread
|
||||
|
||||
@ -316,6 +316,7 @@ module.exports.roundtripStr = __napiModule.exports.roundtripStr
|
||||
module.exports.runScript = __napiModule.exports.runScript
|
||||
module.exports.setNullByteProperty = __napiModule.exports.setNullByteProperty
|
||||
module.exports.setSymbolInObj = __napiModule.exports.setSymbolInObj
|
||||
module.exports.shorterEscapableScope = __napiModule.exports.shorterEscapableScope
|
||||
module.exports.shorterScope = __napiModule.exports.shorterScope
|
||||
module.exports.shutdownRuntime = __napiModule.exports.shutdownRuntime
|
||||
module.exports.spawnThreadInThread = __napiModule.exports.spawnThreadInThread
|
||||
|
||||
@ -606,6 +606,7 @@ module.exports.roundtripStr = nativeBinding.roundtripStr
|
||||
module.exports.runScript = nativeBinding.runScript
|
||||
module.exports.setNullByteProperty = nativeBinding.setNullByteProperty
|
||||
module.exports.setSymbolInObj = nativeBinding.setSymbolInObj
|
||||
module.exports.shorterEscapableScope = nativeBinding.shorterEscapableScope
|
||||
module.exports.shorterScope = nativeBinding.shorterScope
|
||||
module.exports.shutdownRuntime = nativeBinding.shutdownRuntime
|
||||
module.exports.spawnThreadInThread = nativeBinding.spawnThreadInThread
|
||||
|
||||
@ -786,6 +786,8 @@ export interface Shared {
|
||||
value: number
|
||||
}
|
||||
|
||||
export declare function shorterEscapableScope(createString: () => string | null): string
|
||||
|
||||
export declare function shorterScope(arr: unknown[]): Array<number>
|
||||
|
||||
export declare function shutdownRuntime(): void
|
||||
|
||||
@ -7,15 +7,56 @@ pub fn shorter_scope(env: &Env, arr: Array) -> Result<Vec<u32>> {
|
||||
for i in 0..len {
|
||||
let scope = HandleScope::create(env)?;
|
||||
let value: Unknown = arr.get_element(i)?;
|
||||
let len = scope.run(value, |v| match v.get_type()? {
|
||||
ValueType::String => {
|
||||
let string = unsafe { v.cast::<JsString>() }?;
|
||||
Ok(string.utf8_len()? as u32)
|
||||
}
|
||||
ValueType::Object => Ok(1),
|
||||
_ => Ok(0),
|
||||
})?;
|
||||
let len = unsafe {
|
||||
scope.close(value, |v| match v.get_type()? {
|
||||
ValueType::String => {
|
||||
let string = v.cast::<JsString>()?;
|
||||
Ok(string.utf8_len()? as u32)
|
||||
}
|
||||
ValueType::Object => Ok(1),
|
||||
_ => Ok(0),
|
||||
})?
|
||||
};
|
||||
result.push(len);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn shorter_escapable_scope<'env>(
|
||||
env: &'env Env,
|
||||
create_string: Function<(), Option<JsString>>,
|
||||
) -> Result<JsString<'env>> {
|
||||
let mut longest_string = env.create_string("")?;
|
||||
let mut prev_len = 0;
|
||||
loop {
|
||||
if let Some(maybe_longest) = EscapableHandleScope::with(
|
||||
env,
|
||||
(create_string, longest_string),
|
||||
move |scope, (create_string, prev)| {
|
||||
let elem = create_string.call(())?;
|
||||
if let Some(string) = elem {
|
||||
let len = string.utf8_len()?;
|
||||
if len > prev.utf8_len()? {
|
||||
return Ok(Some(Either::A(string.escape::<JsString>(scope)?)));
|
||||
}
|
||||
} else {
|
||||
return Ok(Some(Either::B(())));
|
||||
}
|
||||
Ok(None)
|
||||
},
|
||||
)? {
|
||||
match maybe_longest {
|
||||
Either::A(longest) => {
|
||||
if longest.utf8_len()? == prev_len {
|
||||
break;
|
||||
}
|
||||
prev_len = longest.utf8_len()?;
|
||||
longest_string = longest;
|
||||
}
|
||||
Either::B(_) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(longest_string)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user