1use std::collections::HashMap;
2#[cfg(not(feature = "noop"))]
3use std::collections::HashSet;
4use std::ffi::CStr;
5use std::ptr;
6#[cfg(all(feature = "napi4", not(target_family = "wasm")))]
7use std::sync::atomic::AtomicPtr;
8#[cfg(all(
9 not(any(target_os = "macos", target_family = "wasm")),
10 feature = "napi4",
11 feature = "tokio_rt"
12))]
13use std::sync::atomic::AtomicUsize;
14#[cfg(not(feature = "noop"))]
15use std::sync::atomic::{AtomicBool, Ordering};
16use std::sync::RwLock;
17use std::thread::ThreadId;
18
19use once_cell::sync::Lazy;
20
21use crate::{check_status, sys, Env, JsFunction, Property, Result, Value, ValueType};
22#[cfg(not(feature = "noop"))]
23use crate::{check_status_or_throw, JsError};
24
25pub type ExportRegisterCallback = unsafe fn(sys::napi_env) -> Result<sys::napi_value>;
26pub type ModuleExportsCallback =
27 unsafe fn(env: sys::napi_env, exports: sys::napi_value) -> Result<()>;
28
29#[repr(transparent)]
30pub(crate) struct PersistedPerInstanceHashMap<K, V>(RwLock<HashMap<K, V>>);
31
32impl<K, V> PersistedPerInstanceHashMap<K, V> {
33 #[cfg(not(feature = "noop"))]
34 pub(crate) fn from_hashmap(hashmap: HashMap<K, V>) -> Self {
35 Self(RwLock::new(hashmap))
36 }
37
38 #[allow(clippy::mut_from_ref)]
39 pub(crate) fn borrow_mut<F, R>(&self, f: F) -> R
40 where
41 F: FnOnce(&mut HashMap<K, V>) -> R,
42 {
43 let mut write_lock: RwLockWriteGuard<'_, HashMap<…, …>> = self.0.write().unwrap();
44 f(&mut *write_lock)
45 }
46}
47
48impl<K, V> Default for PersistedPerInstanceHashMap<K, V> {
49 fn default() -> Self {
50 Self(RwLock::new(HashMap::default()))
51 }
52}
53
54type ModuleRegisterCallback =
55 RwLock<Vec<(Option<&'static str>, (&'static str, ExportRegisterCallback))>>;
56
57type ModuleClassProperty = PersistedPerInstanceHashMap<
58 &'static str,
59 HashMap<Option<&'static str>, (&'static str, Vec<Property>)>,
60>;
61
62unsafe impl<K, V> Send for PersistedPerInstanceHashMap<K, V> {}
63unsafe impl<K, V> Sync for PersistedPerInstanceHashMap<K, V> {}
64
65type FnRegisterMap =
66 PersistedPerInstanceHashMap<ExportRegisterCallback, (sys::napi_callback, &'static str)>;
67type RegisteredClassesMap = PersistedPerInstanceHashMap<ThreadId, RegisteredClasses>;
68
69static MODULE_REGISTER_CALLBACK: Lazy<ModuleRegisterCallback> = Lazy::new(Default::default);
70static MODULE_CLASS_PROPERTIES: Lazy<ModuleClassProperty> = Lazy::new(Default::default);
71#[cfg(not(feature = "noop"))]
72static IS_FIRST_MODULE: AtomicBool = AtomicBool::new(true);
73#[cfg(not(feature = "noop"))]
74static FIRST_MODULE_REGISTERED: AtomicBool = AtomicBool::new(false);
75static REGISTERED_CLASSES: Lazy<RegisteredClassesMap> = Lazy::new(Default::default);
76static FN_REGISTER_MAP: Lazy<FnRegisterMap> = Lazy::new(Default::default);
77#[cfg(all(feature = "napi4", not(feature = "noop"), not(target_family = "wasm")))]
78pub(crate) static CUSTOM_GC_TSFN: AtomicPtr<sys::napi_threadsafe_function__> =
79 AtomicPtr::new(ptr::null_mut());
80#[cfg(all(feature = "napi4", not(feature = "noop"), not(target_family = "wasm")))]
81pub(crate) static CUSTOM_GC_TSFN_DESTROYED: AtomicBool = AtomicBool::new(false);
82#[cfg(all(feature = "napi4", not(feature = "noop"), not(target_family = "wasm")))]
83// Store thread id of the thread that created the CustomGC ThreadsafeFunction.
84pub(crate) static THREADS_CAN_ACCESS_ENV: once_cell::sync::Lazy<
85 PersistedPerInstanceHashMap<ThreadId, bool>,
86> = once_cell::sync::Lazy::new(Default::default);
87
88type RegisteredClasses =
89 PersistedPerInstanceHashMap</* export name */ String, /* constructor */ sys::napi_ref>;
90
91#[cfg(all(feature = "compat-mode", not(feature = "noop")))]
92// compatibility for #[module_exports]
93static MODULE_EXPORTS: Lazy<RwLock<Vec<ModuleExportsCallback>>> = Lazy::new(Default::default);
94
95#[cfg(not(feature = "noop"))]
96#[inline]
97fn wait_first_thread_registered() {
98 while !FIRST_MODULE_REGISTERED.load(order:Ordering::SeqCst) {
99 std::hint::spin_loop();
100 }
101}
102
103#[doc(hidden)]
104pub fn get_class_constructor(js_name: &'static str) -> Option<sys::napi_ref> {
105 let current_id: ThreadId = std::thread::current().id();
106 REGISTERED_CLASSES.borrow_mut(|map: &mut HashMap>| {
107 mapOption<&PersistedPerInstanceHashMap<…, …>>
108 .get(&current_id)
109 .map(|m: &PersistedPerInstanceHashMap<…, …>| m.borrow_mut(|map: &mut HashMap| map.get(js_name).copied()))
110 })?
111}
112
113#[doc(hidden)]
114#[cfg(all(feature = "compat-mode", not(feature = "noop")))]
115// compatibility for #[module_exports]
116pub fn register_module_exports(callback: ModuleExportsCallback) {
117 MODULE_EXPORTS
118 .write()
119 .expect("Register module exports failed")
120 .push(callback);
121}
122
123#[doc(hidden)]
124pub fn register_module_export(
125 js_mod: Option<&'static str>,
126 name: &'static str,
127 cb: ExportRegisterCallback,
128) {
129 MODULE_REGISTER_CALLBACKRwLockWriteGuard<'_, Vec<…>>
130 .write()
131 .expect(msg:"Register module export failed")
132 .push((js_mod, (name, cb)));
133}
134
135#[doc(hidden)]
136pub fn register_js_function(
137 name: &'static str,
138 cb: ExportRegisterCallback,
139 c_fn: sys::napi_callback,
140) {
141 FN_REGISTER_MAP.borrow_mut(|inner: &mut HashMap …, …>| {
142 inner.insert(k:cb, (c_fn, name));
143 });
144}
145
146#[doc(hidden)]
147pub fn register_class(
148 rust_name: &'static str,
149 js_mod: Option<&'static str>,
150 js_name: &'static str,
151 props: Vec<Property>,
152) {
153 MODULE_CLASS_PROPERTIES.borrow_mut(|inner: &mut HashMap<&str, HashMap<…, …>>| {
154 let val: &mut HashMap, …> = inner.entry(key:rust_name).or_default();
155 let val: &mut (&str, Vec) = val.entry(key:js_mod).or_default();
156 val.0 = js_name;
157 val.1.extend(iter:props);
158 });
159}
160
161#[inline]
162/// Get `JsFunction` from defined Rust `fn`
163/// ```rust
164/// #[napi]
165/// fn some_fn() -> u32 {
166/// 1
167/// }
168///
169/// #[napi]
170/// fn return_some_fn() -> Result<JsFunction> {
171/// get_js_function(some_fn_js_function)
172/// }
173/// ```
174///
175/// ```js
176/// returnSomeFn()(); // 1
177/// ```
178///
179pub fn get_js_function(env: &Env, raw_fn: ExportRegisterCallback) -> Result<JsFunction> {
180 FN_REGISTER_MAP.borrow_mut(|inner| {
181 inner
182 .get(&raw_fn)
183 .and_then(|(cb, name)| {
184 let mut function = ptr::null_mut();
185 let name_len = name.len() - 1;
186 let fn_name = unsafe { CStr::from_bytes_with_nul_unchecked(name.as_bytes()) };
187 check_status!(unsafe {
188 sys::napi_create_function(
189 env.0,
190 fn_name.as_ptr(),
191 name_len,
192 *cb,
193 ptr::null_mut(),
194 &mut function,
195 )
196 })
197 .ok()?;
198 Some(JsFunction(Value {
199 env: env.0,
200 value: function,
201 value_type: ValueType::Function,
202 }))
203 })
204 .ok_or_else(|| {
205 crate::Error::new(
206 crate::Status::InvalidArg,
207 "JavaScript function does not exist".to_owned(),
208 )
209 })
210 })
211}
212
213/// Get `C Callback` from defined Rust `fn`
214/// ```rust
215/// #[napi]
216/// fn some_fn() -> u32 {
217/// 1
218/// }
219///
220/// #[napi]
221/// fn create_obj(env: Env) -> Result<JsObject> {
222/// let mut obj = env.create_object()?;
223/// obj.define_property(&[Property::new("getter")?.with_getter(get_c_callback(some_fn_js_function)?)])?;
224/// Ok(obj)
225/// }
226/// ```
227///
228/// ```js
229/// console.log(createObj().getter) // 1
230/// ```
231///
232pub fn get_c_callback(raw_fn: ExportRegisterCallback) -> Result<crate::Callback> {
233 FN_REGISTER_MAP.borrow_mut(|inner: &mut HashMap …, …>| {
234 inner
235 .get(&raw_fn)
236 .and_then(|(cb, _name)| *cb)
237 .ok_or_else(|| {
238 crate::Error::new(
239 crate::Status::InvalidArg,
240 reason:"JavaScript function does not exist".to_owned(),
241 )
242 })
243 })
244}
245
246#[cfg(all(windows, not(feature = "noop")))]
247#[ctor::ctor]
248fn load_host() {
249 unsafe {
250 sys::setup();
251 }
252}
253
254#[cfg(all(target_family = "wasm", not(feature = "noop")))]
255#[no_mangle]
256unsafe extern "C" fn napi_register_wasm_v1(
257 env: sys::napi_env,
258 exports: sys::napi_value,
259) -> sys::napi_value {
260 unsafe { napi_register_module_v1(env, exports) }
261}
262
263#[cfg(not(feature = "noop"))]
264#[no_mangle]
265/// Register the n-api module exports.
266///
267/// # Safety
268/// This method is meant to be called by Node.js while importing the n-api module.
269/// Only call this method if the current module is **not** imported by a node-like runtime.
270///
271/// Arguments `env` and `exports` must **not** be null.
272pub unsafe extern "C" fn napi_register_module_v1(
273 env: sys::napi_env,
274 exports: sys::napi_value,
275) -> sys::napi_value {
276 if IS_FIRST_MODULE.load(Ordering::SeqCst) {
277 IS_FIRST_MODULE.store(false, Ordering::SeqCst);
278 } else {
279 wait_first_thread_registered();
280 }
281 let mut exports_objects: HashSet<String> = HashSet::default();
282
283 {
284 let mut register_callback = MODULE_REGISTER_CALLBACK
285 .write()
286 .expect("Write MODULE_REGISTER_CALLBACK in napi_register_module_v1 failed");
287 register_callback
288 .iter_mut()
289 .fold(
290 HashMap::<Option<&'static str>, Vec<(&'static str, ExportRegisterCallback)>>::new(),
291 |mut acc, (js_mod, item)| {
292 if let Some(k) = acc.get_mut(js_mod) {
293 k.push(*item);
294 } else {
295 acc.insert(*js_mod, vec![*item]);
296 }
297 acc
298 },
299 )
300 .iter()
301 .for_each(|(js_mod, items)| {
302 let mut exports_js_mod = ptr::null_mut();
303 if let Some(js_mod_str) = js_mod {
304 let mod_name_c_str =
305 unsafe { CStr::from_bytes_with_nul_unchecked(js_mod_str.as_bytes()) };
306 if exports_objects.contains(*js_mod_str) {
307 check_status_or_throw!(
308 env,
309 unsafe {
310 sys::napi_get_named_property(
311 env,
312 exports,
313 mod_name_c_str.as_ptr(),
314 &mut exports_js_mod,
315 )
316 },
317 "Get mod {} from exports failed",
318 js_mod_str,
319 );
320 } else {
321 check_status_or_throw!(
322 env,
323 unsafe { sys::napi_create_object(env, &mut exports_js_mod) },
324 "Create export JavaScript Object [{}] failed",
325 js_mod_str
326 );
327 check_status_or_throw!(
328 env,
329 unsafe {
330 sys::napi_set_named_property(env, exports, mod_name_c_str.as_ptr(), exports_js_mod)
331 },
332 "Set exports Object [{}] into exports object failed",
333 js_mod_str
334 );
335 exports_objects.insert(js_mod_str.to_string());
336 }
337 }
338 for (name, callback) in items {
339 unsafe {
340 let js_name = CStr::from_bytes_with_nul_unchecked(name.as_bytes());
341 if let Err(e) = callback(env).and_then(|v| {
342 let exported_object = if exports_js_mod.is_null() {
343 exports
344 } else {
345 exports_js_mod
346 };
347 check_status!(
348 sys::napi_set_named_property(env, exported_object, js_name.as_ptr(), v),
349 "Failed to register export `{}`",
350 name,
351 )
352 }) {
353 JsError::from(e).throw_into(env)
354 }
355 }
356 }
357 });
358 }
359
360 let mut registered_classes = HashMap::new();
361
362 MODULE_CLASS_PROPERTIES.borrow_mut(|inner| {
363 inner.iter().for_each(|(rust_name, js_mods)| {
364 for (js_mod, (js_name, props)) in js_mods {
365 let mut exports_js_mod = ptr::null_mut();
366 unsafe {
367 if let Some(js_mod_str) = js_mod {
368 let mod_name_c_str = CStr::from_bytes_with_nul_unchecked(js_mod_str.as_bytes());
369 if exports_objects.contains(*js_mod_str) {
370 check_status_or_throw!(
371 env,
372 sys::napi_get_named_property(
373 env,
374 exports,
375 mod_name_c_str.as_ptr(),
376 &mut exports_js_mod,
377 ),
378 "Get mod {} from exports failed",
379 js_mod_str,
380 );
381 } else {
382 check_status_or_throw!(
383 env,
384 sys::napi_create_object(env, &mut exports_js_mod),
385 "Create export JavaScript Object [{}] failed",
386 js_mod_str
387 );
388 check_status_or_throw!(
389 env,
390 sys::napi_set_named_property(env, exports, mod_name_c_str.as_ptr(), exports_js_mod),
391 "Set exports Object [{}] into exports object failed",
392 js_mod_str
393 );
394 exports_objects.insert(js_mod_str.to_string());
395 }
396 }
397 let (ctor, props): (Vec<_>, Vec<_>) = props.iter().partition(|prop| prop.is_ctor);
398
399 let ctor = ctor
400 .first()
401 .map(|c| c.raw().method.unwrap())
402 .unwrap_or(noop);
403 let raw_props: Vec<_> = props.iter().map(|prop| prop.raw()).collect();
404
405 let js_class_name = CStr::from_bytes_with_nul_unchecked(js_name.as_bytes());
406 let mut class_ptr = ptr::null_mut();
407
408 check_status_or_throw!(
409 env,
410 sys::napi_define_class(
411 env,
412 js_class_name.as_ptr(),
413 js_name.len() - 1,
414 Some(ctor),
415 ptr::null_mut(),
416 raw_props.len(),
417 raw_props.as_ptr(),
418 &mut class_ptr,
419 ),
420 "Failed to register class `{}` generate by struct `{}`",
421 &js_name,
422 &rust_name
423 );
424
425 let mut ctor_ref = ptr::null_mut();
426 sys::napi_create_reference(env, class_ptr, 1, &mut ctor_ref);
427
428 registered_classes.insert(js_name.to_string(), ctor_ref);
429
430 check_status_or_throw!(
431 env,
432 sys::napi_set_named_property(
433 env,
434 if exports_js_mod.is_null() {
435 exports
436 } else {
437 exports_js_mod
438 },
439 js_class_name.as_ptr(),
440 class_ptr
441 ),
442 "Failed to register class `{}` generate by struct `{}`",
443 &js_name,
444 &rust_name
445 );
446 }
447 }
448 });
449
450 REGISTERED_CLASSES.borrow_mut(|map| {
451 map.insert(
452 std::thread::current().id(),
453 PersistedPerInstanceHashMap::from_hashmap(registered_classes),
454 )
455 });
456 });
457
458 #[cfg(feature = "compat-mode")]
459 {
460 let module_exports = MODULE_EXPORTS.read().expect("Read MODULE_EXPORTS failed");
461 module_exports.iter().for_each(|callback| unsafe {
462 if let Err(e) = callback(env, exports) {
463 JsError::from(e).throw_into(env);
464 }
465 })
466 }
467
468 #[cfg(all(
469 not(any(target_os = "macos", target_family = "wasm")),
470 feature = "napi4",
471 feature = "tokio_rt"
472 ))]
473 {
474 crate::tokio_runtime::ensure_runtime();
475
476 static init_counter: AtomicUsize = AtomicUsize::new(0);
477 let cleanup_hook_payload =
478 init_counter.fetch_add(1, Ordering::Relaxed) as *mut std::ffi::c_void;
479
480 unsafe {
481 sys::napi_add_env_cleanup_hook(
482 env,
483 Some(crate::tokio_runtime::drop_runtime),
484 cleanup_hook_payload,
485 )
486 };
487 }
488 #[cfg(all(feature = "napi4", not(target_family = "wasm")))]
489 create_custom_gc(env);
490 FIRST_MODULE_REGISTERED.store(true, Ordering::SeqCst);
491 exports
492}
493
494#[cfg(not(feature = "noop"))]
495pub(crate) unsafe extern "C" fn noop(
496 env: sys::napi_env,
497 _info: sys::napi_callback_info,
498) -> sys::napi_value {
499 if !crate::bindgen_runtime::___CALL_FROM_FACTORY.with(|s: &AtomicBool| s.load(order:Ordering::Relaxed)) {
500 unsafe {
501 sys::napi_throw_error(
502 env,
503 code:ptr::null_mut(),
504 msg:CStr::from_bytes_with_nul_unchecked(bytes:b"Class contains no `constructor`, can not new it!\0")
505 .as_ptr(),
506 );
507 }
508 }
509 ptr::null_mut()
510}
511
512#[cfg(all(feature = "napi4", not(target_family = "wasm"), not(feature = "noop")))]
513fn create_custom_gc(env: sys::napi_env) {
514 use std::os::raw::c_char;
515
516 if !FIRST_MODULE_REGISTERED.load(Ordering::SeqCst) {
517 let mut custom_gc_fn = ptr::null_mut();
518 check_status_or_throw!(
519 env,
520 unsafe {
521 sys::napi_create_function(
522 env,
523 "custom_gc".as_ptr().cast(),
524 9,
525 Some(empty),
526 ptr::null_mut(),
527 &mut custom_gc_fn,
528 )
529 },
530 "Create Custom GC Function in napi_register_module_v1 failed"
531 );
532 let mut async_resource_name = ptr::null_mut();
533 check_status_or_throw!(
534 env,
535 unsafe {
536 sys::napi_create_string_utf8(
537 env,
538 "CustomGC".as_ptr() as *const c_char,
539 8,
540 &mut async_resource_name,
541 )
542 },
543 "Create async resource string in napi_register_module_v1"
544 );
545 let mut custom_gc_tsfn = ptr::null_mut();
546 check_status_or_throw!(
547 env,
548 unsafe {
549 sys::napi_create_threadsafe_function(
550 env,
551 custom_gc_fn,
552 ptr::null_mut(),
553 async_resource_name,
554 0,
555 1,
556 ptr::null_mut(),
557 Some(custom_gc_finalize),
558 ptr::null_mut(),
559 Some(custom_gc),
560 &mut custom_gc_tsfn,
561 )
562 },
563 "Create Custom GC ThreadsafeFunction in napi_register_module_v1 failed"
564 );
565 check_status_or_throw!(
566 env,
567 unsafe { sys::napi_unref_threadsafe_function(env, custom_gc_tsfn) },
568 "Unref Custom GC ThreadsafeFunction in napi_register_module_v1 failed"
569 );
570 CUSTOM_GC_TSFN.store(custom_gc_tsfn, Ordering::Relaxed);
571 }
572
573 let current_thread_id = std::thread::current().id();
574 THREADS_CAN_ACCESS_ENV.borrow_mut(|m| m.insert(current_thread_id, true));
575 check_status_or_throw!(
576 env,
577 unsafe {
578 sys::napi_add_env_cleanup_hook(
579 env,
580 Some(remove_thread_id),
581 Box::into_raw(Box::new(current_thread_id)).cast(),
582 )
583 },
584 "Failed to add remove thread id cleanup hook"
585 );
586}
587
588#[cfg(all(feature = "napi4", not(target_family = "wasm"), not(feature = "noop")))]
589unsafe extern "C" fn remove_thread_id(id: *mut std::ffi::c_void) {
590 let thread_id: Box = unsafe { Box::from_raw(id.cast::<ThreadId>()) };
591 THREADS_CAN_ACCESS_ENV.borrow_mut(|m: &mut HashMap| m.insert(*thread_id, v:false));
592}
593
594#[cfg(all(feature = "napi4", not(target_family = "wasm"), not(feature = "noop")))]
595#[allow(unused)]
596unsafe extern "C" fn empty(env: sys::napi_env, info: sys::napi_callback_info) -> sys::napi_value {
597 ptr::null_mut()
598}
599
600#[cfg(all(feature = "napi4", not(target_family = "wasm"), not(feature = "noop")))]
601#[allow(unused_variables)]
602unsafe extern "C" fn custom_gc_finalize(
603 env: sys::napi_env,
604 finalize_data: *mut std::ffi::c_void,
605 finalize_hint: *mut std::ffi::c_void,
606) {
607 CUSTOM_GC_TSFN_DESTROYED.store(val:true, order:Ordering::SeqCst);
608}
609
610#[cfg(all(feature = "napi4", not(target_family = "wasm"), not(feature = "noop")))]
611// recycle the ArrayBuffer/Buffer Reference if the ArrayBuffer/Buffer is not dropped on the main thread
612extern "C" fn custom_gc(
613 env: sys::napi_env,
614 _js_callback: sys::napi_value,
615 _context: *mut std::ffi::c_void,
616 data: *mut std::ffi::c_void,
617) {
618 // current thread was destroyed
619 if THREADS_CAN_ACCESS_ENV.borrow_mut(|m: &mut HashMap| m.get(&std::thread::current().id()) == Some(&false)) {
620 return;
621 }
622 let mut ref_count: u32 = 0;
623 check_status_or_throw!(
624 env,
625 unsafe { sys::napi_reference_unref(env, data.cast(), &mut ref_count) },
626 "Failed to unref Buffer reference in Custom GC"
627 );
628 debug_assert!(
629 ref_count == 0,
630 "Buffer reference count in Custom GC is not 0"
631 );
632 check_status_or_throw!(
633 env,
634 unsafe { sys::napi_delete_reference(env, data.cast()) },
635 "Failed to delete Buffer reference in Custom GC"
636 );
637}
638