feat(napi): new handle scope API (#2650)

This commit is contained in:
LongYinan 2025-05-18 23:16:40 +08:00 committed by GitHub
parent 18816c710c
commit 92b094e487
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 238 additions and 0 deletions

View File

@ -25,6 +25,7 @@ mod object;
#[cfg(all(feature = "tokio_rt", feature = "napi4"))]
mod promise;
mod promise_raw;
mod scope;
#[cfg(feature = "serde-json")]
mod serde;
mod set;
@ -52,6 +53,7 @@ pub use object::*;
#[cfg(all(feature = "tokio_rt", feature = "napi4"))]
pub use promise::*;
pub use promise_raw::*;
pub use scope::*;
#[cfg(feature = "web_stream")]
pub use stream::*;
pub use string::*;

View File

@ -0,0 +1,201 @@
use std::ptr;
use crate::{check_status, sys, Env, JsValue, Result};
pub struct HandleScope {
pub(crate) scope: sys::napi_handle_scope,
}
impl HandleScope {
pub fn create(env: &Env) -> Result<Self> {
let mut scope = ptr::null_mut();
check_status!(
unsafe { sys::napi_open_handle_scope(env.0, &mut scope) },
"Failed to open handle scope"
)?;
Ok(Self { scope })
}
pub fn run<A, T>(self, arg: A, f: impl FnOnce(A) -> Result<T>) -> Result<T>
where
A: JsValuesTuple,
{
let env = arg.env();
let ret = f(arg);
check_status!(
unsafe { sys::napi_close_handle_scope(env, self.scope) },
"Failed to close handle scope"
)?;
ret
}
}
pub trait JsValuesTuple {
fn env(&self) -> sys::napi_env;
}
impl<'env, T: JsValue<'env>> JsValuesTuple for T {
fn env(&self) -> sys::napi_env {
self.value().env
}
}
impl<'env, T1: JsValue<'env>, T2: JsValue<'env>> JsValuesTuple for (T1, T2) {
fn env(&self) -> sys::napi_env {
self.0.value().env
}
}
impl<'env, T1: JsValue<'env>, T2: JsValue<'env>, T3: JsValue<'env>> JsValuesTuple for (T1, T2, T3) {
fn env(&self) -> sys::napi_env {
self.0.value().env
}
}
impl<'env, T1: JsValue<'env>, T2: JsValue<'env>, T3: JsValue<'env>, T4: JsValue<'env>> JsValuesTuple
for (T1, T2, T3, T4)
{
fn env(&self) -> sys::napi_env {
self.0.value().env
}
}
impl<
'env,
T1: JsValue<'env>,
T2: JsValue<'env>,
T3: JsValue<'env>,
T4: JsValue<'env>,
T5: JsValue<'env>,
> JsValuesTuple for (T1, T2, T3, T4, T5)
{
fn env(&self) -> sys::napi_env {
self.0.value().env
}
}
impl<
'env,
T1: JsValue<'env>,
T2: JsValue<'env>,
T3: JsValue<'env>,
T4: JsValue<'env>,
T5: JsValue<'env>,
T6: JsValue<'env>,
> JsValuesTuple for (T1, T2, T3, T4, T5, T6)
{
fn env(&self) -> sys::napi_env {
self.0.value().env
}
}
impl<
'env,
T1: JsValue<'env>,
T2: JsValue<'env>,
T3: JsValue<'env>,
T4: JsValue<'env>,
T5: JsValue<'env>,
T6: JsValue<'env>,
T7: JsValue<'env>,
> JsValuesTuple for (T1, T2, T3, T4, T5, T6, T7)
{
fn env(&self) -> sys::napi_env {
self.0.value().env
}
}
impl<
'env,
T1: JsValue<'env>,
T2: JsValue<'env>,
T3: JsValue<'env>,
T4: JsValue<'env>,
T5: JsValue<'env>,
T6: JsValue<'env>,
T7: JsValue<'env>,
T8: JsValue<'env>,
> JsValuesTuple for (T1, T2, T3, T4, T5, T6, T7, T8)
{
fn env(&self) -> sys::napi_env {
self.0.value().env
}
}
impl<
'env,
T1: JsValue<'env>,
T2: JsValue<'env>,
T3: JsValue<'env>,
T4: JsValue<'env>,
T5: JsValue<'env>,
T6: JsValue<'env>,
T7: JsValue<'env>,
T8: JsValue<'env>,
T9: JsValue<'env>,
> JsValuesTuple for (T1, T2, T3, T4, T5, T6, T7, T8, T9)
{
fn env(&self) -> sys::napi_env {
self.0.value().env
}
}
impl<
'env,
T1: JsValue<'env>,
T2: JsValue<'env>,
T3: JsValue<'env>,
T4: JsValue<'env>,
T5: JsValue<'env>,
T6: JsValue<'env>,
T7: JsValue<'env>,
T8: JsValue<'env>,
T9: JsValue<'env>,
T10: JsValue<'env>,
> JsValuesTuple for (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)
{
fn env(&self) -> sys::napi_env {
self.0.value().env
}
}
impl<
'env,
T1: JsValue<'env>,
T2: JsValue<'env>,
T3: JsValue<'env>,
T4: JsValue<'env>,
T5: JsValue<'env>,
T6: JsValue<'env>,
T7: JsValue<'env>,
T8: JsValue<'env>,
T9: JsValue<'env>,
T10: JsValue<'env>,
T11: JsValue<'env>,
> JsValuesTuple for (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11)
{
fn env(&self) -> sys::napi_env {
self.0.value().env
}
}
impl<
'env,
T1: JsValue<'env>,
T2: JsValue<'env>,
T3: JsValue<'env>,
T4: JsValue<'env>,
T5: JsValue<'env>,
T6: JsValue<'env>,
T7: JsValue<'env>,
T8: JsValue<'env>,
T9: JsValue<'env>,
T10: JsValue<'env>,
T11: JsValue<'env>,
T12: JsValue<'env>,
> JsValuesTuple for (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12)
{
fn env(&self) -> sys::napi_env {
self.0.value().env
}
}

View File

@ -824,6 +824,8 @@ Generated by [AVA](https://avajs.dev).
value: number␊
}␊
export declare function shorterScope(arr: unknown[]): Array<number>
export declare function shutdownRuntime(): void␊
export declare function spawnThreadInThread(tsfn: ((err: Error | null, arg: number) => number)): void␊

View File

@ -232,6 +232,7 @@ import {
extendsJavascriptError,
shutdownRuntime,
callAsyncWithUnknownReturnValue,
shorterScope,
} from '../index.cjs'
// import other stuff in `#[napi(module_exports)]`
import nativeAddon from '../index.cjs'
@ -1660,3 +1661,8 @@ test('extends javascript error', (t) => {
test('module exports', (t) => {
t.is(nativeAddon.NAPI_RS_SYMBOL, Symbol.for('NAPI_RS_SYMBOL'))
})
test('shorter scope', (t) => {
const result = shorterScope(['hello', { foo: 'bar' }, 'world', true])
t.deepEqual(result, [5, 1, 5, 0])
})

View File

@ -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 shorterScope = __napiModule.exports.shorterScope
export const shutdownRuntime = __napiModule.exports.shutdownRuntime
export const spawnThreadInThread = __napiModule.exports.spawnThreadInThread
export const Status = __napiModule.exports.Status

View File

@ -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.shorterScope = __napiModule.exports.shorterScope
module.exports.shutdownRuntime = __napiModule.exports.shutdownRuntime
module.exports.spawnThreadInThread = __napiModule.exports.spawnThreadInThread
module.exports.Status = __napiModule.exports.Status

View File

@ -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.shorterScope = nativeBinding.shorterScope
module.exports.shutdownRuntime = nativeBinding.shutdownRuntime
module.exports.spawnThreadInThread = nativeBinding.spawnThreadInThread
module.exports.Status = nativeBinding.Status

View File

@ -786,6 +786,8 @@ export interface Shared {
value: number
}
export declare function shorterScope(arr: unknown[]): Array<number>
export declare function shutdownRuntime(): void
export declare function spawnThreadInThread(tsfn: ((err: Error | null, arg: number) => number)): void

View File

@ -80,6 +80,7 @@ mod number;
mod object;
mod promise;
mod reference;
mod scope;
mod serde;
mod set;
mod shared;

View File

@ -0,0 +1,21 @@
use napi::{bindgen_prelude::*, JsString};
#[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)?;
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),
})?;
result.push(len);
}
Ok(result)
}