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