feat(napi): support JsGlobal in Env

This commit is contained in:
LongYinan 2021-11-15 18:56:06 +08:00
parent 1ef7de4dd6
commit 3386bb9867
No known key found for this signature in database
GPG key ID: C3666B7FC82ADAD7
15 changed files with 148 additions and 100 deletions

View file

@ -61,7 +61,7 @@ jobs:
run: cargo fmt -- --check run: cargo fmt -- --check
- name: Clippy - name: Clippy
run: cargo clippy run: cargo clippy --all-features
- name: Clear the cargo caches - name: Clear the cargo caches
run: | run: |

View file

@ -242,7 +242,7 @@ yarn test
| T: Fn(...) -> Result<T> | Function | 1 | v8.0.0 | | T: Fn(...) -> Result<T> | Function | 1 | v8.0.0 |
| Async/Future | Promise<T> | 4 | v10.6.0 | async | | Async/Future | Promise<T> | 4 | v10.6.0 | async |
| AsyncTask | Promise<T> | 1 | v8.5.0 | | AsyncTask | Promise<T> | 1 | v8.5.0 |
| (NOT YET) | global | 1 | v8.0.0 | | JsGlobal | global | 1 | v8.0.0 |
| JsSymbol | Symbol | 1 | v8.0.0 | | JsSymbol | Symbol | 1 | v8.0.0 |
| (NOT YET) | ArrayBuffer/TypedArray | 1 | v8.0.0 | | (NOT YET) | ArrayBuffer/TypedArray | 1 | v8.0.0 |
| JsFunction | threadsafe function | 4 | v10.6.0 | napi4 | | JsFunction | threadsafe function | 4 | v10.6.0 | napi4 |

View file

@ -75,6 +75,7 @@ static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
("external", "object"), ("external", "object"),
("AbortSignal", "AbortSignal"), ("AbortSignal", "AbortSignal"),
("JsFunction", "(...args: any[]) => any"), ("JsFunction", "(...args: any[]) => any"),
("JsGlobal", "typeof global"),
]); ]);
map map

View file

@ -1,22 +1,57 @@
use crate::{sys, Result}; use std::cell::RefCell;
use std::ptr;
use super::{Array, Object}; use crate::{check_status, sys, JsGlobal, JsNull, JsUndefined, NapiValue, Result};
#[repr(transparent)] use super::Array;
pub struct Env(sys::napi_env);
impl From<sys::napi_env> for Env { pub use crate::Env;
fn from(raw_env: sys::napi_env) -> Env {
Env(raw_env) thread_local! {
} static JS_UNDEFINED: RefCell<Option<JsUndefined>> = RefCell::default();
static JS_NULL: RefCell<Option<JsNull>> = RefCell::default();
} }
impl Env { impl Env {
pub fn create_object(&self) -> Result<Object> {
Object::new(self.0)
}
pub fn create_array(&self, len: u32) -> Result<Array> { pub fn create_array(&self, len: u32) -> Result<Array> {
Array::new(self.0, len) Array::new(self.0, len)
} }
/// Get [JsUndefined](./struct.JsUndefined.html) value
pub fn get_undefined(&self) -> Result<JsUndefined> {
if let Some(js_undefined) = JS_UNDEFINED.with(|x| *x.borrow()) {
return Ok(js_undefined);
}
let mut raw_value = ptr::null_mut();
check_status!(unsafe { sys::napi_get_undefined(self.0, &mut raw_value) })?;
let js_undefined = unsafe { JsUndefined::from_raw_unchecked(self.0, raw_value) };
JS_UNDEFINED.with(|x| x.borrow_mut().replace(js_undefined));
Ok(js_undefined)
}
pub fn get_null(&self) -> Result<JsNull> {
if let Some(js_null) = JS_NULL.with(|cell| *cell.borrow()) {
return Ok(js_null);
}
let mut raw_value = ptr::null_mut();
check_status!(unsafe { sys::napi_get_null(self.0, &mut raw_value) })?;
let js_null = unsafe { JsNull::from_raw_unchecked(self.0, raw_value) };
JS_NULL.with(|js_null_cell| {
js_null_cell.borrow_mut().replace(js_null);
});
Ok(js_null)
}
pub fn get_global(&self) -> Result<JsGlobal> {
let mut global = std::ptr::null_mut();
crate::check_status!(
unsafe { sys::napi_get_global(self.0, &mut global) },
"Get global object from Env failed"
)?;
Ok(JsGlobal(crate::Value {
value: global,
env: self.0,
value_type: crate::ValueType::Object,
}))
}
} }

View file

@ -1,10 +1,7 @@
use crate::{bindgen_prelude::*, check_status, sys, type_of, ValueType}; use crate::{bindgen_prelude::*, check_status, sys, type_of, JsObject, ValueType};
use std::{ffi::CString, ptr}; use std::{ffi::CString, ptr};
pub struct Object { pub type Object = JsObject;
pub(crate) env: sys::napi_env,
pub(crate) inner: sys::napi_value,
}
impl Object { impl Object {
pub(crate) fn new(env: sys::napi_env) -> Result<Self> { pub(crate) fn new(env: sys::napi_env) -> Result<Self> {
@ -16,7 +13,11 @@ impl Object {
)?; )?;
} }
Ok(Object { env, inner: ptr }) Ok(Self(crate::Value {
env,
value: ptr,
value_type: ValueType::Object,
}))
} }
pub fn get<K: AsRef<str>, V: FromNapiValue>(&self, field: K) -> Result<Option<V>> { pub fn get<K: AsRef<str>, V: FromNapiValue>(&self, field: K) -> Result<Option<V>> {
@ -26,17 +27,17 @@ impl Object {
let mut ret = ptr::null_mut(); let mut ret = ptr::null_mut();
check_status!( check_status!(
sys::napi_get_named_property(self.env, self.inner, c_field.as_ptr(), &mut ret), sys::napi_get_named_property(self.0.env, self.0.value, c_field.as_ptr(), &mut ret),
"Failed to get property with field `{}`", "Failed to get property with field `{}`",
c_field.to_string_lossy(), c_field.to_string_lossy(),
)?; )?;
let ty = type_of!(self.env, ret)?; let ty = type_of!(self.0.env, ret)?;
Ok(if ty == ValueType::Undefined { Ok(if ty == ValueType::Undefined {
None None
} else { } else {
Some(V::from_napi_value(self.env, ret)?) Some(V::from_napi_value(self.0.env, ret)?)
}) })
} }
} }
@ -45,10 +46,10 @@ impl Object {
let c_field = CString::new(field.as_ref())?; let c_field = CString::new(field.as_ref())?;
unsafe { unsafe {
let napi_val = V::to_napi_value(self.env, val)?; let napi_val = V::to_napi_value(self.0.env, val)?;
check_status!( check_status!(
sys::napi_set_named_property(self.env, self.inner, c_field.as_ptr(), napi_val), sys::napi_set_named_property(self.0.env, self.0.value, c_field.as_ptr(), napi_val),
"Failed to set property with field `{}`", "Failed to set property with field `{}`",
c_field.to_string_lossy(), c_field.to_string_lossy(),
)?; )?;
@ -61,12 +62,12 @@ impl Object {
let mut names = ptr::null_mut(); let mut names = ptr::null_mut();
unsafe { unsafe {
check_status!( check_status!(
sys::napi_get_property_names(obj.env, obj.inner, &mut names), sys::napi_get_property_names(obj.0.env, obj.0.value, &mut names),
"Failed to get property names of given object" "Failed to get property names of given object"
)?; )?;
} }
let names = unsafe { Array::from_napi_value(obj.env, names)? }; let names = unsafe { Array::from_napi_value(obj.0.env, names)? };
let mut ret = vec![]; let mut ret = vec![];
for i in 0..names.len() { for i in 0..names.len() {
@ -86,25 +87,3 @@ impl TypeName for Object {
ValueType::Object ValueType::Object
} }
} }
impl ToNapiValue for Object {
unsafe fn to_napi_value(_env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
Ok(val.inner)
}
}
impl FromNapiValue for Object {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
let value_type = type_of!(env, napi_val)?;
match value_type {
ValueType::Object => Ok(Self {
inner: napi_val,
env,
}),
_ => Err(Error::new(
Status::InvalidArg,
"Given napi value is not an object".to_owned(),
)),
}
}
}

View file

@ -1,6 +1,8 @@
use serde_json::{Map, Value}; use serde_json::{Map, Value};
use crate::{bindgen_runtime::Null, check_status, sys, type_of, Error, Result, Status, ValueType}; use crate::{
bindgen_runtime::Null, check_status, sys, type_of, Error, JsObject, Result, Status, ValueType,
};
use super::{FromNapiValue, Object, ToNapiValue}; use super::{FromNapiValue, Object, ToNapiValue};
@ -78,10 +80,11 @@ impl ToNapiValue for Map<String, Value> {
impl FromNapiValue for Map<String, Value> { impl FromNapiValue for Map<String, Value> {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> { unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
let obj = Object { let obj = JsObject(crate::Value {
env, env,
inner: napi_val, value: napi_val,
}; value_type: ValueType::Object,
});
let mut map = Map::new(); let mut map = Map::new();
for key in Object::keys(&obj)?.into_iter() { for key in Object::keys(&obj)?.into_iter() {

View file

@ -43,25 +43,18 @@ pub type Callback = extern "C" fn(sys::napi_env, sys::napi_callback_info) -> sys
/// Notification of this event is delivered through the callbacks given to `Env::add_env_cleanup_hook` and `Env::set_instance_data`. /// Notification of this event is delivered through the callbacks given to `Env::add_env_cleanup_hook` and `Env::set_instance_data`.
pub struct Env(pub(crate) sys::napi_env); pub struct Env(pub(crate) sys::napi_env);
impl From<sys::napi_env> for Env {
fn from(env: sys::napi_env) -> Self {
Env(env)
}
}
impl Env { impl Env {
#[allow(clippy::missing_safety_doc)] #[allow(clippy::missing_safety_doc)]
pub unsafe fn from_raw(env: sys::napi_env) -> Self { pub unsafe fn from_raw(env: sys::napi_env) -> Self {
Env(env) Env(env)
} }
/// Get [JsUndefined](./struct.JsUndefined.html) value
pub fn get_undefined(&self) -> Result<JsUndefined> {
let mut raw_value = ptr::null_mut();
check_status!(unsafe { sys::napi_get_undefined(self.0, &mut raw_value) })?;
Ok(unsafe { JsUndefined::from_raw_unchecked(self.0, raw_value) })
}
pub fn get_null(&self) -> Result<JsNull> {
let mut raw_value = ptr::null_mut();
check_status!(unsafe { sys::napi_get_null(self.0, &mut raw_value) })?;
Ok(unsafe { JsNull::from_raw_unchecked(self.0, raw_value) })
}
pub fn get_boolean(&self, value: bool) -> Result<JsBoolean> { pub fn get_boolean(&self, value: bool) -> Result<JsBoolean> {
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
check_status!(unsafe { sys::napi_get_boolean(self.0, value, &mut raw_value) })?; check_status!(unsafe { sys::napi_get_boolean(self.0, value, &mut raw_value) })?;
@ -231,7 +224,7 @@ impl Env {
Ok(unsafe { JsObject::from_raw_unchecked(self.0, raw_value) }) Ok(unsafe { JsObject::from_raw_unchecked(self.0, raw_value) })
} }
pub fn create_array(&self) -> Result<JsObject> { pub fn create_empty_array(&self) -> Result<JsObject> {
let mut raw_value = ptr::null_mut(); let mut raw_value = ptr::null_mut();
check_status!(unsafe { sys::napi_create_array(self.0, &mut raw_value) })?; check_status!(unsafe { sys::napi_create_array(self.0, &mut raw_value) })?;
Ok(unsafe { JsObject::from_raw_unchecked(self.0, raw_value) }) Ok(unsafe { JsObject::from_raw_unchecked(self.0, raw_value) })
@ -949,12 +942,6 @@ impl Env {
result result
} }
pub fn get_global(&self) -> Result<JsGlobal> {
let mut raw_global = ptr::null_mut();
check_status!(unsafe { sys::napi_get_global(self.0, &mut raw_global) })?;
Ok(unsafe { JsGlobal::from_raw_unchecked(self.0, raw_global) })
}
pub fn get_napi_version(&self) -> Result<u32> { pub fn get_napi_version(&self) -> Result<u32> {
let global = self.get_global()?; let global = self.get_global()?;
let process: JsObject = global.get_named_property("process")?; let process: JsObject = global.get_named_property("process")?;

View file

@ -9,7 +9,7 @@ pub struct JsTimeout(pub(crate) Value);
impl JsGlobal { impl JsGlobal {
pub fn set_interval(&self, handler: JsFunction, interval: f64) -> Result<JsTimeout> { pub fn set_interval(&self, handler: JsFunction, interval: f64) -> Result<JsTimeout> {
let func: JsFunction = self.get_named_property("setInterval")?; let func: JsFunction = self.get_named_property_unchecked("setInterval")?;
func func
.call( .call(
None, None,
@ -24,14 +24,14 @@ impl JsGlobal {
} }
pub fn clear_interval(&self, timer: JsTimeout) -> Result<JsUndefined> { pub fn clear_interval(&self, timer: JsTimeout) -> Result<JsUndefined> {
let func: JsFunction = self.get_named_property("clearInterval")?; let func: JsFunction = self.get_named_property_unchecked("clearInterval")?;
func func
.call(None, &[timer.into_unknown()]) .call(None, &[timer.into_unknown()])
.and_then(|ret| ret.try_into()) .and_then(|ret| ret.try_into())
} }
pub fn set_timeout(&self, handler: JsFunction, interval: f64) -> Result<JsTimeout> { pub fn set_timeout(&self, handler: JsFunction, interval: f64) -> Result<JsTimeout> {
let func: JsFunction = self.get_named_property("setTimeout")?; let func: JsFunction = self.get_named_property_unchecked("setTimeout")?;
func func
.call( .call(
None, None,
@ -46,7 +46,7 @@ impl JsGlobal {
} }
pub fn clear_timeout(&self, timer: JsTimeout) -> Result<JsUndefined> { pub fn clear_timeout(&self, timer: JsTimeout) -> Result<JsUndefined> {
let func: JsFunction = self.get_named_property("clearTimeout")?; let func: JsFunction = self.get_named_property_unchecked("clearTimeout")?;
func func
.call(None, &[timer.into_unknown()]) .call(None, &[timer.into_unknown()])
.and_then(|ret| ret.try_into()) .and_then(|ret| ret.try_into())

View file

@ -8,7 +8,6 @@ use std::ptr;
#[cfg(feature = "napi5")] #[cfg(feature = "napi5")]
use super::check_status; use super::check_status;
use super::Value; use super::Value;
use crate::bindgen_runtime::TypeName;
#[cfg(feature = "napi5")] #[cfg(feature = "napi5")]
use crate::sys; use crate::sys;
#[cfg(feature = "napi5")] #[cfg(feature = "napi5")]
@ -17,20 +16,9 @@ use crate::Env;
use crate::Error; use crate::Error;
#[cfg(feature = "napi5")] #[cfg(feature = "napi5")]
use crate::Result; use crate::Result;
use crate::ValueType;
pub struct JsObject(pub(crate) Value); pub struct JsObject(pub(crate) Value);
impl TypeName for JsObject {
fn type_name() -> &'static str {
"Object"
}
fn value_type() -> crate::ValueType {
ValueType::Object
}
}
#[cfg(feature = "napi5")] #[cfg(feature = "napi5")]
pub struct FinalizeContext<T: 'static, Hint: 'static> { pub struct FinalizeContext<T: 'static, Hint: 'static> {
pub env: Env, pub env: Env,

View file

@ -7,7 +7,7 @@ use napi::{
#[contextless_function] #[contextless_function]
fn test_create_array(env: Env) -> ContextlessResult<JsObject> { fn test_create_array(env: Env) -> ContextlessResult<JsObject> {
env.create_array().map(Some) env.create_empty_array().map(Some)
} }
#[js_function(1)] #[js_function(1)]

View file

@ -34,6 +34,9 @@ Generated by [AVA](https://avajs.dev).
export function fibonacci(n: number): number␊ export function fibonacci(n: number): number␊
export function listObjKeys(obj: object): Array<string> export function listObjKeys(obj: object): Array<string>
export function createObj(): object␊ export function createObj(): object␊
export function getGlobal(): typeof global␊
export function getUndefined(): JsUndefined␊
export function getNull(): JsNull␊
export function asyncPlus100(p: Promise<number>): Promise<number> export function asyncPlus100(p: Promise<number>): Promise<number>
interface PackageJson {␊ interface PackageJson {␊
name: string␊ name: string␊

View file

@ -41,6 +41,9 @@ import {
callThreadsafeFunction, callThreadsafeFunction,
threadsafeFunctionThrowError, threadsafeFunctionThrowError,
asyncPlus100, asyncPlus100,
getGlobal,
getUndefined,
getNull,
} from '../' } from '../'
test('number', (t) => { test('number', (t) => {
@ -137,6 +140,22 @@ test('object', (t) => {
t.deepEqual(createObj(), { test: 1 }) t.deepEqual(createObj(), { test: 1 })
}) })
test('global', (t) => {
t.is(getGlobal(), global)
})
test('get undefined', (t) => {
for (const _ of Array.from({ length: 100 })) {
t.is(getUndefined(), undefined)
}
})
test('get null', (t) => {
for (const _ of Array.from({ length: 100 })) {
t.is(getNull(), null)
}
})
test('Option', (t) => { test('Option', (t) => {
t.is(mapOption(null), null) t.is(mapOption(null), null)
t.is(mapOption(3), 4) t.is(mapOption(3), 4)

View file

@ -7,7 +7,9 @@ export function bigintAdd(a: BigInt, b: BigInt): BigInt
export function createBigInt(): BigInt export function createBigInt(): BigInt
export function createBigIntI64(): BigInt export function createBigIntI64(): BigInt
export function getCwd(callback: (arg0: string) => void): void export function getCwd(callback: (arg0: string) => void): void
export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void export function readFile(
callback: (arg0: Error | undefined, arg1: string | null) => void,
): void
export function eitherStringOrNumber(input: string | number): number export function eitherStringOrNumber(input: string | number): number
export function returnEither(input: number): string | number export function returnEither(input: number): string | number
export function either3(input: string | number | boolean): number export function either3(input: string | number | boolean): number
@ -15,8 +17,21 @@ interface Obj {
v: string | number v: string | number
} }
export function either4(input: string | number | boolean | Obj): number export function either4(input: string | number | boolean | Obj): number
export enum Kind { Dog = 0, Cat = 1, Duck = 2 } export enum Kind {
export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 } Dog = 0,
Cat = 1,
Duck = 2,
}
export enum CustomNumEnum {
One = 1,
Two = 2,
Three = 3,
Four = 4,
Six = 6,
Eight = 8,
Nine = 9,
Ten = 10,
}
export function enumToI32(e: CustomNumEnum): number export function enumToI32(e: CustomNumEnum): number
export function throwError(): void export function throwError(): void
export function mapOption(val: number | null): number | null export function mapOption(val: number | null): number | null
@ -24,6 +39,9 @@ export function add(a: number, b: number): number
export function fibonacci(n: number): number export function fibonacci(n: number): number
export function listObjKeys(obj: object): Array<string> export function listObjKeys(obj: object): Array<string>
export function createObj(): object export function createObj(): object
export function getGlobal(): typeof global
export function getUndefined(): JsUndefined
export function getNull(): JsNull
export function asyncPlus100(p: Promise<number>): Promise<number> export function asyncPlus100(p: Promise<number>): Promise<number>
interface PackageJson { interface PackageJson {
name: string name: string
@ -38,7 +56,11 @@ export function concatStr(s: string): string
export function concatUtf16(s: string): string export function concatUtf16(s: string): string
export function concatLatin1(s: string): string export function concatLatin1(s: string): string
export function withoutAbortController(a: number, b: number): Promise<number> export function withoutAbortController(a: number, b: number): Promise<number>
export function withAbortController(a: number, b: number, signal: AbortSignal): Promise<number> export function withAbortController(
a: number,
b: number,
signal: AbortSignal,
): Promise<number>
export function callThreadsafeFunction(callback: (...args: any[]) => any): void export function callThreadsafeFunction(callback: (...args: any[]) => any): void
export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void export function threadsafeFunctionThrowError(cb: (...args: any[]) => any): void
export function getBuffer(): Buffer export function getBuffer(): Buffer
@ -52,14 +74,10 @@ export class Animal {
static getDogKind(): Kind static getDogKind(): Kind
} }
export class Blake2BHasher { export class Blake2BHasher {
static withKey(key: Blake2bKey): Blake2BHasher static withKey(key: Blake2bKey): Blake2BHasher
} }
export class Blake2BKey { export class Blake2BKey {}
}
export class Context { export class Context {
constructor() constructor()
static withData(data: string): Context static withData(data: string): Context
method(): string method(): string

View file

@ -1,4 +1,4 @@
use napi::bindgen_prelude::*; use napi::{bindgen_prelude::*, JsGlobal, JsNull, JsUndefined};
#[napi] #[napi]
fn list_obj_keys(obj: Object) -> Vec<String> { fn list_obj_keys(obj: Object) -> Vec<String> {
@ -12,3 +12,18 @@ fn create_obj(env: Env) -> Object {
obj obj
} }
#[napi]
fn get_global(env: Env) -> Result<JsGlobal> {
env.get_global()
}
#[napi]
fn get_undefined(env: Env) -> Result<JsUndefined> {
env.get_undefined()
}
#[napi]
fn get_null(env: Env) -> Result<JsNull> {
env.get_null()
}