1use crate::jsutils::JsError;
4use crate::jsutils::Script;
5use crate::quickjs_utils::errors::error_to_js_error;
6use crate::quickjs_utils::{atoms, errors, objects, parse_args, primitives};
7use crate::quickjsrealmadapter::QuickJsRealmAdapter;
8use crate::quickjsruntimeadapter::{make_cstring, QuickJsRuntimeAdapter};
9use crate::quickjsvalueadapter::QuickJsValueAdapter;
10use hirofa_utils::auto_id_map::AutoIdMap;
11use libquickjs_sys as q;
12use log::trace;
13use std::cell::RefCell;
14use std::collections::{HashMap, HashSet};
15use std::os::raw::{c_char, c_int, c_void};
16use std::rc::Rc;
17
18pub unsafe fn parse_function(
46 context: *mut q::JSContext,
47 async_fn: bool,
48 name: &str,
49 body: &str,
50 arg_names: Vec<&str>,
51) -> Result<QuickJsValueAdapter, JsError> {
52 let as_pfx = if async_fn { "async " } else { "" };
56 let args_str = arg_names.join(", ");
57 let src = format!("({as_pfx}function {name}({args_str}) {{\n{body}\n}});");
58
59 let file_name = format!("compile_func_{name}.es");
60
61 let ret = QuickJsRealmAdapter::eval_ctx(context, Script::new(&file_name, &src), None)?;
62
63 debug_assert!(is_function(context, &ret));
64
65 Ok(ret)
66}
67
68pub fn call_function_q_ref_args(
70 q_ctx: &QuickJsRealmAdapter,
71 function_ref: &QuickJsValueAdapter,
72 arguments: &[&QuickJsValueAdapter],
73 this_ref_opt: Option<&QuickJsValueAdapter>,
74) -> Result<QuickJsValueAdapter, JsError> {
75 unsafe { call_function_ref_args(q_ctx.context, function_ref, arguments, this_ref_opt) }
76}
77
78pub fn call_function_q(
80 q_ctx: &QuickJsRealmAdapter,
81 function_ref: &QuickJsValueAdapter,
82 arguments: &[QuickJsValueAdapter],
83 this_ref_opt: Option<&QuickJsValueAdapter>,
84) -> Result<QuickJsValueAdapter, JsError> {
85 let r: Vec<&QuickJsValueAdapter> = arguments.iter().collect();
86 unsafe { call_function_ref_args(q_ctx.context, function_ref, &r, this_ref_opt) }
87}
88
89pub unsafe fn call_function(
93 context: *mut q::JSContext,
94 function_ref: &QuickJsValueAdapter,
95 arguments: &[QuickJsValueAdapter],
96 this_ref_opt: Option<&QuickJsValueAdapter>,
97) -> Result<QuickJsValueAdapter, JsError> {
98 let r: Vec<&QuickJsValueAdapter> = arguments.iter().collect();
99 call_function_ref_args(context, function_ref, &r, this_ref_opt)
100}
101
102pub unsafe fn call_function_ref_args(
106 context: *mut q::JSContext,
107 function_ref: &QuickJsValueAdapter,
108 arguments: &[&QuickJsValueAdapter],
109 this_ref_opt: Option<&QuickJsValueAdapter>,
110) -> Result<QuickJsValueAdapter, JsError> {
111 log::trace!("functions::call_function()");
112
113 debug_assert!(is_function(context, function_ref));
114
115 let arg_count = arguments.len() as i32;
116
117 let mut qargs = arguments
118 .iter()
119 .map(|a| *a.borrow_value())
120 .collect::<Vec<_>>();
121
122 let this_val = if let Some(this_ref) = this_ref_opt {
123 *this_ref.borrow_value()
124 } else {
125 crate::quickjs_utils::new_null()
126 };
127
128 let res = q::JS_Call(
129 context,
130 *function_ref.borrow_value(),
131 this_val,
132 arg_count,
133 qargs.as_mut_ptr(),
134 );
135
136 let res_ref = QuickJsValueAdapter::new(context, res, false, true, "call_function result");
137
138 if res_ref.is_exception() {
139 if let Some(ex) = QuickJsRealmAdapter::get_exception(context) {
140 Err(ex)
141 } else {
142 Err(JsError::new_str(
143 "function invocation failed but could not get ex",
144 ))
145 }
146 } else {
147 Ok(res_ref)
148 }
149}
150
151pub fn invoke_member_function_q(
162 q_ctx: &QuickJsRealmAdapter,
163 obj_ref: &QuickJsValueAdapter,
164 function_name: &str,
165 arguments: &[QuickJsValueAdapter],
166) -> Result<QuickJsValueAdapter, JsError> {
167 unsafe { invoke_member_function(q_ctx.context, obj_ref, function_name, arguments) }
168}
169
170#[allow(dead_code)]
171pub unsafe fn invoke_member_function(
174 context: *mut q::JSContext,
175 obj_ref: &QuickJsValueAdapter,
176 function_name: &str,
177 arguments: &[QuickJsValueAdapter],
178) -> Result<QuickJsValueAdapter, JsError> {
179 let arg_count = arguments.len() as i32;
183
184 let atom_ref = atoms::from_string(context, function_name)?;
185 atom_ref.increment_ref_ct();
186
187 let mut qargs = arguments
188 .iter()
189 .map(|a| *a.borrow_value())
190 .collect::<Vec<_>>();
191
192 let res_val = q::JS_Invoke(
193 context,
194 *obj_ref.borrow_value(),
195 atom_ref.get_atom(),
196 arg_count,
197 qargs.as_mut_ptr(),
198 );
199
200 let res_ref = QuickJsValueAdapter::new(
201 context,
202 res_val,
203 false,
204 true,
205 format!("functions::invoke_member_function res: {function_name}").as_str(),
206 );
207
208 if res_ref.is_exception() {
209 if let Some(ex) = QuickJsRealmAdapter::get_exception(context) {
210 Err(ex)
211 } else {
212 Err(JsError::new_str(
213 "invoke_member_function failed but could not get ex",
214 ))
215 }
216 } else {
217 Ok(res_ref)
218 }
219}
220
221pub fn call_to_string_q(
223 q_ctx: &QuickJsRealmAdapter,
224 obj_ref: &QuickJsValueAdapter,
225) -> Result<String, JsError> {
226 unsafe { call_to_string(q_ctx.context, obj_ref) }
227}
228
229pub unsafe fn call_to_string(
233 context: *mut q::JSContext,
234 obj_ref: &QuickJsValueAdapter,
235) -> Result<String, JsError> {
236 if obj_ref.is_string() {
237 primitives::to_string(context, obj_ref)
238 } else if obj_ref.is_null() {
239 Ok("null".to_string())
240 } else if obj_ref.is_undefined() {
241 Ok("undefined".to_string())
242 } else if obj_ref.is_i32() {
243 let i = primitives::to_i32(obj_ref).expect("could not get i32");
244 Ok(i.to_string())
245 } else if obj_ref.is_f64() {
246 let i = primitives::to_f64(obj_ref).expect("could not get f64");
247 Ok(i.to_string())
248 } else if obj_ref.is_bool() {
249 let i = primitives::to_bool(obj_ref).expect("could not get bool");
250 Ok(i.to_string())
251 } else if errors::is_error(context, obj_ref) {
252 let pretty_err = error_to_js_error(context, obj_ref);
253 Ok(format!("{pretty_err}"))
254 } else {
255 log::trace!("calling JS_ToString on a {}", obj_ref.borrow_value().tag);
256
257 let res = q::JS_ToString(context, *obj_ref.borrow_value());
260 let res_ref = QuickJsValueAdapter::new(context, res, false, true, "call_to_string result");
261
262 log::trace!("called JS_ToString got a {}", res_ref.borrow_value().tag);
263
264 if !res_ref.is_string() {
265 return Err(JsError::new_str("Could not convert value to string"));
266 }
267 primitives::to_string(context, &res_ref)
268 }
269}
270
271pub fn is_function_q(q_ctx: &QuickJsRealmAdapter, obj_ref: &QuickJsValueAdapter) -> bool {
273 unsafe { is_function(q_ctx.context, obj_ref) }
274}
275
276#[allow(dead_code)]
277pub unsafe fn is_function(context: *mut q::JSContext, obj_ref: &QuickJsValueAdapter) -> bool {
281 if obj_ref.is_object() {
282 #[cfg(feature = "bellard")]
283 {
284 let res = q::JS_IsFunction(context, *obj_ref.borrow_value());
285 res != 0
286 }
287 #[cfg(feature = "quickjs-ng")]
288 q::JS_IsFunction(context, *obj_ref.borrow_value())
289 } else {
290 false
291 }
292}
293
294pub fn is_constructor_q(q_ctx: &QuickJsRealmAdapter, obj_ref: &QuickJsValueAdapter) -> bool {
296 unsafe { is_constructor(q_ctx.context, obj_ref) }
297}
298
299pub unsafe fn is_constructor(context: *mut q::JSContext, obj_ref: &QuickJsValueAdapter) -> bool {
303 if obj_ref.is_object() {
304 #[cfg(feature = "bellard")]
305 {
306 let res = q::JS_IsConstructor(context, *obj_ref.borrow_value());
307 res != 0
308 }
309 #[cfg(feature = "quickjs-ng")]
310 q::JS_IsConstructor(context, *obj_ref.borrow_value())
311 } else {
312 false
313 }
314}
315
316pub fn call_constructor_q(
318 q_ctx: &QuickJsRealmAdapter,
319 constructor_ref: &QuickJsValueAdapter,
320 arguments: &[QuickJsValueAdapter],
321) -> Result<QuickJsValueAdapter, JsError> {
322 unsafe { call_constructor(q_ctx.context, constructor_ref, arguments) }
323}
324
325pub unsafe fn call_constructor(
329 context: *mut q::JSContext,
330 constructor_ref: &QuickJsValueAdapter,
331 arguments: &[QuickJsValueAdapter],
332) -> Result<QuickJsValueAdapter, JsError> {
333 let arg_count = arguments.len() as i32;
343
344 let mut qargs = arguments
345 .iter()
346 .map(|arg| *arg.borrow_value())
347 .collect::<Vec<_>>();
348
349 let ret_val = q::JS_CallConstructor(
350 context,
351 *constructor_ref.borrow_value(),
352 arg_count,
353 qargs.as_mut_ptr(),
354 );
355 let res_ref = QuickJsValueAdapter::new(
356 context,
357 ret_val,
358 false,
359 true,
360 "functions::call_constructor result",
361 );
362
363 if res_ref.is_exception() {
364 if let Some(ex) = QuickJsRealmAdapter::get_exception(context) {
365 Err(ex)
366 } else {
367 Err(JsError::new_str(
368 "call_constructor failed but could not get ex",
369 ))
370 }
371 } else {
372 Ok(res_ref)
373 }
374}
375
376pub fn new_native_function_q(
378 q_ctx: &QuickJsRealmAdapter,
379 name: &str,
380 func: q::JSCFunction,
381 arg_count: i32,
382 is_constructor: bool,
383) -> Result<QuickJsValueAdapter, JsError> {
384 unsafe { new_native_function(q_ctx.context, name, func, arg_count, is_constructor) }
385}
386
387pub unsafe fn new_native_function(
391 context: *mut q::JSContext,
392 name: &str,
393 func: q::JSCFunction,
394 arg_count: i32,
395 is_constructor: bool,
396) -> Result<QuickJsValueAdapter, JsError> {
397 log::trace!("functions::new_native_function / 0 : {}", name);
398
399 let cname = make_cstring(name)?;
400 let magic: i32 = 1;
401
402 log::trace!("functions::new_native_function / 1");
403
404 let cproto = if is_constructor {
405 q::JSCFunctionEnum_JS_CFUNC_constructor
406 } else {
407 q::JSCFunctionEnum_JS_CFUNC_generic
408 };
409
410 log::trace!("functions::new_native_function / 2");
411
412 let func_val = q::JS_NewCFunction2(
413 context,
414 func,
415 cname.as_ptr(),
416 arg_count as c_int,
417 cproto,
418 magic as c_int,
419 );
420
421 log::trace!("functions::new_native_function / 3");
422
423 let func_ref = QuickJsValueAdapter::new(
424 context,
425 func_val,
426 false,
427 true,
428 format!("functions::new_native_function {name}").as_str(),
429 );
430
431 log::trace!("functions::new_native_function / 4");
432
433 if !func_ref.is_object() {
434 Err(JsError::new_str("Could not create new_native_function"))
435 } else {
436 Ok(func_ref)
437 }
438}
439
440pub fn new_native_function_data_q(
442 q_ctx: &QuickJsRealmAdapter,
443 func: q::JSCFunctionData,
444 name: &str,
445 arg_count: i32,
446 data: QuickJsValueAdapter,
447) -> Result<QuickJsValueAdapter, JsError> {
448 unsafe { new_native_function_data(q_ctx.context, func, name, arg_count, data) }
449}
450
451pub unsafe fn new_native_function_data(
455 context: *mut q::JSContext,
456 func: q::JSCFunctionData,
457 name: &str,
458 arg_count: i32,
459 mut data: QuickJsValueAdapter,
460) -> Result<QuickJsValueAdapter, JsError> {
461 let magic = 1;
462 let data_len = 1;
463
464 let func_val = q::JS_NewCFunctionData(
465 context,
466 func,
467 magic,
468 arg_count as c_int,
469 data_len,
470 data.borrow_value_mut(),
471 );
472 let func_ref = QuickJsValueAdapter::new(
473 context,
474 func_val,
475 false,
476 true,
477 format!("functions::new_native_function_data {name}").as_str(),
478 );
479
480 if !func_ref.is_object() {
481 Err(JsError::new_str("Could not create new_native_function"))
482 } else {
483 let name_ref = primitives::from_string(context, name)?;
484 objects::set_property2(context, &func_ref, "name", &name_ref, 0)?;
485 Ok(func_ref)
486 }
487}
488
489static CNAME: &str = "CallbackClass\0";
490
491type Callback = dyn Fn(
492 *mut q::JSContext,
493 &QuickJsValueAdapter,
494 &[QuickJsValueAdapter],
495 ) -> Result<QuickJsValueAdapter, JsError>
496 + 'static;
497
498thread_local! {
499 static INSTANCE_ID_MAPPINGS: RefCell<HashMap<usize, Box<(usize, String)>>> = RefCell::new(HashMap::new());
500
501 #[cfg(feature = "quickjs-ng")]
502 static CALLBACK_EXOTIC: RefCell<q::JSClassExoticMethods> = RefCell::new(q::JSClassExoticMethods {
503 get_own_property: None,
504 get_own_property_names: None,
505 delete_property: None,
506 define_own_property: None,
507 has_property: None,
508 get_property: None,
509 set_property: None,
510
511 });
512 #[cfg(feature = "bellard")]
513 static CALLBACK_EXOTIC: RefCell<q::JSClassExoticMethods> = RefCell::new(q::JSClassExoticMethods {
514 get_own_property: None,
515 get_own_property_names: None,
516 delete_property: None,
517 define_own_property: None,
518 has_property: None,
519 get_property: None,
520 set_property: None,
521 get_prototype: None,
522 is_extensible: None,
523 prevent_extensions: None,
524 set_prototype: None
525 });
526
527
528 static CALLBACK_CLASS_DEF: RefCell<q::JSClassDef> = {
529 CALLBACK_EXOTIC.with(|e_rc|{
530 let exotic = &mut *e_rc.borrow_mut();
531 RefCell::new(q::JSClassDef {
532 class_name: CNAME.as_ptr() as *const c_char,
533 finalizer: Some(callback_finalizer),
534 gc_mark: None,
535 call: None,
536 exotic,
537 })
538 })
539 };
540
541 static CALLBACK_CLASS_ID: RefCell<u32> = {
542
543 let class_id: u32 =
544 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
545 q_js_rt.new_class_id()
546 });
547
548
549 log::trace!("got class id {}", class_id);
550
551 CALLBACK_CLASS_DEF.with(|cd_rc| {
552 let class_def = &*cd_rc.borrow();
553 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
554 let res = unsafe { q::JS_NewClass(q_js_rt.runtime, class_id, class_def) };
555 log::trace!("callback: new class res {}", res);
556 });
558 });
559
560 RefCell::new(class_id)
561 };
562
563 pub static CALLBACK_REGISTRY: RefCell<AutoIdMap<(String, Rc<Callback>)>> = {
564 RefCell::new(AutoIdMap::new_with_max_size(i32::MAX as usize))
565 };
566
567 pub static CALLBACK_IDS: RefCell<HashSet<Box<i32>>> = RefCell::new(HashSet::new());
568}
569
570pub(crate) fn init_statics() {
571 CALLBACK_CLASS_ID.with(|_rc| {
572 });
574}
575
576pub fn new_function_q<F>(
598 q_ctx: &QuickJsRealmAdapter,
599 name: &str,
600 func: F,
601 arg_count: u32,
602) -> Result<QuickJsValueAdapter, JsError>
603where
604 F: Fn(
605 &QuickJsRealmAdapter,
606 &QuickJsValueAdapter,
607 &[QuickJsValueAdapter],
608 ) -> Result<QuickJsValueAdapter, JsError>
609 + 'static,
610{
611 let func_raw =
612 move |ctx: *mut q::JSContext, this: &QuickJsValueAdapter, args: &[QuickJsValueAdapter]| {
613 log::trace!("new_function_q outer");
614 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
615 log::trace!("new_function_q inner");
616 func(unsafe { q_js_rt.get_quickjs_context(ctx) }, this, args)
617 })
618 };
619
620 unsafe { new_function(q_ctx.context, name, func_raw, arg_count) }
621}
622
623pub unsafe fn new_function<F>(
627 context: *mut q::JSContext,
628 name: &str,
629 func: F,
630 arg_count: u32,
631) -> Result<QuickJsValueAdapter, JsError>
632where
633 F: Fn(
634 *mut q::JSContext,
635 &QuickJsValueAdapter,
636 &[QuickJsValueAdapter],
637 ) -> Result<QuickJsValueAdapter, JsError>
638 + 'static,
639{
640 let callback_id = CALLBACK_REGISTRY.with(|registry_rc| {
647 let registry = &mut *registry_rc.borrow_mut();
648 registry.insert((name.to_string(), Rc::new(func)))
649 });
650 log::trace!("new_function callback_id = {}", callback_id);
651
652 let data = primitives::from_i32(callback_id as i32);
653 let func_ref = new_native_function_data(
654 context,
655 Some(callback_function),
656 name,
657 arg_count as i32,
658 data,
659 )?;
660
661 let callback_class_id = CALLBACK_CLASS_ID.with(|rc| *rc.borrow());
662
663 let class_val: q::JSValue = q::JS_NewObjectClass(context, callback_class_id as i32);
664
665 let class_val_ref = QuickJsValueAdapter::new(
666 context,
667 class_val,
668 false,
669 true,
670 "functions::new_function class_val",
671 );
672
673 if class_val_ref.is_exception() {
674 return if let Some(e) = QuickJsRealmAdapter::get_exception(context) {
675 Err(e)
676 } else {
677 Err(JsError::new_str("could not create callback class"))
678 };
679 }
680
681 CALLBACK_IDS.with(|rc| {
682 let ids = &mut *rc.borrow_mut();
683 let mut bx = Box::new(callback_id as i32);
684
685 let ibp: &mut i32 = &mut bx;
686 let info_ptr = ibp as *mut _ as *mut c_void;
687
688 q::JS_SetOpaque(*class_val_ref.borrow_value(), info_ptr);
689
690 ids.insert(bx);
691 });
692
693 objects::set_property2(context, &func_ref, "_cb_fin_marker_", &class_val_ref, 0)
694 .expect("could not set cb marker");
695
696 Ok(func_ref)
697}
698
699#[cfg(test)]
700pub mod tests {
701 use crate::facades::tests::init_test_rt;
702 use crate::quickjs_utils::functions::{
703 call_function_q, call_to_string_q, invoke_member_function_q, new_function_q,
704 };
705 use crate::quickjs_utils::{functions, objects, primitives};
706
707 use crate::jsutils::{JsError, Script};
708 use std::time::Duration;
709
710 #[test]
711 pub fn test_invoke() {
712 let rt = init_test_rt();
713 rt.exe_rt_task_in_event_loop(|q_js_rt| {
714 let q_ctx = q_js_rt.get_main_realm();
715 let obj_ref = q_ctx
716 .eval(Script::new(
717 "test_to_invoke.es",
718 "({func: function(a, b) {return a*b}});",
719 ))
720 .expect("test_to_invoke.es failed");
721
722 let res = invoke_member_function_q(
723 q_ctx,
724 &obj_ref,
725 "func",
726 &[primitives::from_i32(12), primitives::from_i32(14)],
727 )
728 .expect("func failed");
729
730 q_js_rt.gc();
731 log::info!("invoke_res = {}", res.get_tag());
732
733 assert!(res.is_i32());
734 assert_eq!(primitives::to_i32(&res).expect("wtf?"), (12 * 14));
735 });
736 rt.gc_sync();
737 }
738
739 #[test]
740 pub fn test_ret_refcount() {
741 let rt = init_test_rt();
742 let io = rt.exe_rt_task_in_event_loop(|q_js_rt| {
743 let q_ctx = q_js_rt.get_main_realm();
744 let func_ref = q_ctx
745 .eval(Script::new(
746 "test_ret_refcount.es",
747 "this.test = {q: {}}; let global = this; (function(a, b){global.test.a = a; return {a: 1};});",
748 ))
749 .expect("aa");
750 #[cfg(feature = "bellard")]
751 assert_eq!(func_ref.get_ref_count(), 1);
752
753 let a = objects::create_object_q(q_ctx).ok().unwrap();
754 let b = objects::create_object_q(q_ctx).ok().unwrap();
755
756 #[cfg(feature = "bellard")]
757 assert_eq!(1, a.get_ref_count());
758 #[cfg(feature = "bellard")]
759 assert_eq!(1, b.get_ref_count());
760
761 let i_res = call_function_q(q_ctx, &func_ref, &[a.clone(), b.clone()], None)
762 .expect("a");
763
764 assert!(i_res.is_object());
765 #[cfg(feature = "bellard")]
766 assert_eq!(i_res.get_ref_count(), 1);
767 #[cfg(feature = "bellard")]
768 assert_eq!(2, a.get_ref_count());
769 #[cfg(feature = "bellard")]
770 assert_eq!(1, b.get_ref_count());
771
772 let q_ref = q_ctx.eval(Script::new("test_ret_refcount2.es", "test.q;")).expect("get q failed");
773 #[cfg(feature = "bellard")]
774 assert_eq!(2, q_ref.get_ref_count());
775 let _ = call_function_q(q_ctx, &func_ref, &[primitives::from_i32(123), q_ref], None)
776 .expect("b");
777 let q_ref = q_ctx.eval(Script::new("test_ret_refcount2.es", "test.q;")).expect("get q failed");
778 #[cfg(feature = "bellard")]
779 assert_eq!(2, q_ref.get_ref_count());
780 let _ = call_function_q(q_ctx, &func_ref, &[q_ref, primitives::from_i32(123)], None)
781 .expect("b");
782 let _q_ref = q_ctx.eval(Script::new("test_ret_refcount2.es", "test.q;")).expect("get q failed");
783 #[cfg(feature = "bellard")]
784 assert_eq!(3, _q_ref.get_ref_count());
785
786 q_ctx.eval(Script::new("cleanup.es", "this.test = null;")).ok().unwrap();
788
789 true
790 });
791 assert!(io);
792 rt.gc_sync();
793 }
794
795 #[test]
796 pub fn test_to_string() {
797 let rt = init_test_rt();
798 let io = rt.exe_rt_task_in_event_loop(|q_js_rt| {
799 let q_ctx = q_js_rt.get_main_realm();
800 let i = primitives::from_i32(480);
801 let i_s = call_to_string_q(q_ctx, &i)
802 .ok()
803 .expect("to_string failed on i");
804 assert_eq!(i_s.as_str(), "480");
805
806 let b = primitives::from_bool(true);
807 let b_s = call_to_string_q(q_ctx, &b)
808 .ok()
809 .expect("to_string failed on b");
810 assert_eq!(b_s.as_str(), "true");
811
812 true
813 });
814 assert!(io);
815 rt.gc_sync();
816 }
817
818 #[test]
819 pub fn test_call() {
820 let rt = init_test_rt();
821 let io = rt.exe_rt_task_in_event_loop(|q_js_rt| {
822 let q_ctx = q_js_rt.get_main_realm();
823 let func_ref = q_ctx
824 .eval(Script::new(
825 "test_call.es",
826 "(function(a, b){return ((a || 7)*(b || 7));});",
827 ))
828 .ok()
829 .expect("could not get func obj");
830
831 let res = call_function_q(
832 q_ctx,
833 &func_ref,
834 &[primitives::from_i32(8), primitives::from_i32(6)],
835 None,
836 );
837 if res.is_err() {
838 panic!("test_call failed: {}", res.err().unwrap());
839 }
840 let res_val = res.ok().unwrap();
841
842 q_js_rt.gc();
843
844 assert!(res_val.is_i32());
845
846 assert_eq!(primitives::to_i32(&res_val).ok().unwrap(), 6 * 8);
847
848 true
849 });
850 assert!(io)
851 }
852
853 #[test]
854 fn test_callback() {
855 let rt = init_test_rt();
856
857 rt.eval_sync(None, Script::new("test_callback1.es", "let test_callback_563 = function(cb){console.log('before invoke cb');let result = cb(1, true, 'foobar');console.log('after invoke cb. got:' + result);};")).ok().expect("script failed");
858
859 rt.exe_rt_task_in_event_loop(|q_js_rt| {
860 let q_ctx = q_js_rt.get_main_realm();
861 let mut cb_ref = new_function_q(
862 q_ctx,
863 "cb",
864 |_q_ctx, _this_ref, _args| {
865 log::trace!("native callback invoked");
866 Ok(primitives::from_i32(983))
867 },
868 3,
869 )
870 .ok()
871 .expect("could not create function");
872
873 #[cfg(feature = "bellard")]
874 assert_eq!(1, cb_ref.get_ref_count());
875
876 cb_ref.label("cb_ref at test_callback");
877
878 let func_ref = q_ctx
879 .eval(Script::new("", "(test_callback_563);"))
880 .ok()
881 .expect("could not get function");
882
883 #[cfg(feature = "bellard")]
884 assert_eq!(2, func_ref.get_ref_count());
885
886 let res = call_function_q(q_ctx, &func_ref, &[cb_ref], None);
887 if res.is_err() {
888 let err = res.err().unwrap();
889 log::error!("could not invoke test_callback_563: {}", err);
890 panic!("could not invoke test_callback_563: {}", err);
891 }
892 res.expect("could not invoke test_callback_563");
893 });
894 log::trace!("done with cb");
895 rt.gc_sync();
896 std::thread::sleep(Duration::from_secs(1));
897 }
898
899 #[test]
900 fn test_callback_arg_ref_ct() {
901 let rt = init_test_rt();
902
903 rt.exe_rt_task_in_event_loop(|q_js_rt| {
904
905 let q_ctx = q_js_rt.get_main_realm();
906
907 let func_ref = q_ctx.eval(Script::new(
908 "test_callback845.es",
909 "let test_callback_845 = function(cb){let obj = {}; cb(obj);cb(obj);cb(obj);}; test_callback_845;",
910 ))
911 .expect("script failed");
912
913 let cb_ref = new_function_q(
914 q_ctx,
915 "cb",
916 |_q_ctx, _this_ref, _args| {
917 log::trace!("native callback invoked");
918 #[cfg(feature = "bellard")]
919 assert_eq!(_args[0].get_ref_count(), 3);
920
921 Ok(primitives::from_i32(983))
922 },
923 3,
924 )
925 .expect("could not create function");
926 log::debug!("calling js func test_callback_845");
927 let res = functions::call_function_q(q_ctx, &func_ref, &[cb_ref], None);
928 if res.is_err() {
929 let e = format!("test_callback_845 failed: {}", res.err().unwrap());
930 log::error!("{}", e);
931 panic!("{}", e);
932 }
933 });
934 log::trace!("done with cb");
935 std::thread::sleep(Duration::from_secs(1));
936 rt.exe_rt_task_in_event_loop(|q_js_rt| {
937 q_js_rt.gc();
938 });
939 std::thread::sleep(Duration::from_secs(1));
940 }
941
942 #[test]
943 fn test_ex() {
944 let rt = init_test_rt();
945
946 let err = rt.exe_rt_task_in_event_loop(|q_js_rt| {
947 let q_ctx = q_js_rt.get_main_realm();
948
949 q_ctx
950 .install_function(
951 &["test_927"],
952 "testMe",
953 |_rt, _q_ctx, _this_ref, _args| {
954 log::trace!("native callback invoked");
955 Err(JsError::new_str("poof"))
956 },
957 0,
958 )
959 .expect("could not install func");
960
961 let err = q_ctx
962 .eval(Script::new(
963 "test_927.es",
964 "console.log('foo');test_927.testMe();",
965 ))
966 .expect_err("did not get err");
967
968 format!("{err}")
969 });
970
971 assert!(err.contains("[testMe]"));
972 assert!(err.contains("test_927.es"));
973 }
974}
975
976unsafe extern "C" fn callback_finalizer(_rt: *mut q::JSRuntime, val: q::JSValue) {
977 trace!("callback_finalizer called");
978
979 let callback_class_id = CALLBACK_CLASS_ID.with(|rc| *rc.borrow());
980 let info_ptr: *mut c_void = q::JS_GetOpaque(val, callback_class_id);
981 let callback_id: i32 = *(info_ptr as *mut i32);
982
983 trace!("callback_finalizer called, id={}", callback_id);
984
985 let _ = CALLBACK_IDS.try_with(|rc| {
986 let ids = &mut *rc.borrow_mut();
987 ids.remove(&callback_id);
988 });
989 let _ = CALLBACK_REGISTRY.try_with(|rc| {
990 let registry = &mut *rc.borrow_mut();
991
992 let rid = callback_id as usize;
993 trace!("callback_finalizer remove id={}", rid);
994 let _ = registry.remove(&rid);
995 });
996}
997
998unsafe extern "C" fn callback_function(
999 ctx: *mut q::JSContext,
1000 this_val: q::JSValue,
1001 argc: ::std::os::raw::c_int,
1002 argv: *mut q::JSValue,
1003 _magic: ::std::os::raw::c_int,
1004 func_data: *mut q::JSValue,
1005) -> q::JSValue {
1006 trace!("callback_function called");
1007
1008 let data_ref =
1011 QuickJsValueAdapter::new(ctx, *func_data, true, true, "callback_function func_data");
1012 let callback_id = primitives::to_i32(&data_ref).expect("failed to get callback_id");
1013
1014 trace!("callback_function id = {}", callback_id);
1015
1016 let cb_opt = CALLBACK_REGISTRY.with(|registry_rc| {
1017 let registry = &*registry_rc.borrow();
1018 registry.get(&(callback_id as usize)).cloned()
1020 });
1021 if let Some((name, callback)) = cb_opt {
1022 let args_vec = parse_args(ctx, argc, argv);
1023
1024 let this_ref =
1025 QuickJsValueAdapter::new(ctx, this_val, true, true, "callback_function this_val");
1026
1027 let callback_res: Result<QuickJsValueAdapter, JsError> =
1028 callback(ctx, &this_ref, args_vec.as_slice());
1029
1030 match callback_res {
1031 Ok(res) => res.clone_value_incr_rc(),
1032 Err(e) => {
1033 let nat_stack = format!(" at native_function [{}]\n{}", name, e.get_stack());
1034 let err = errors::new_error(ctx, e.get_name(), e.get_message(), nat_stack.as_str())
1035 .expect("could not create err");
1036 errors::throw(ctx, err)
1037 }
1038 }
1039 } else {
1040 panic!("callback not found");
1041 }
1042}
1043
1044#[cfg(test)]
1045pub mod tests2 {
1046 use crate::builder::QuickJsRuntimeBuilder;
1047 use crate::quickjs_utils::functions::{new_function_q, CALLBACK_IDS, CALLBACK_REGISTRY};
1048 use crate::quickjs_utils::new_null_ref;
1049
1050 #[test]
1051 fn test_function() {
1052 let rt = QuickJsRuntimeBuilder::new().build();
1053 rt.exe_rt_task_in_event_loop(|q_js_rt| {
1054 let q_ctx = q_js_rt.get_main_realm();
1055 let func = new_function_q(
1056 q_ctx,
1057 "test_func",
1058 |_q_ctx, _this_arg, _args| Ok(new_null_ref()),
1059 0,
1060 )
1061 .ok()
1062 .unwrap();
1063 let ct1 = CALLBACK_REGISTRY.with(|rc| rc.borrow().len());
1064 let ct2 = CALLBACK_IDS.with(|rc| rc.borrow().len());
1065 assert_eq!(1, ct1);
1066 assert_eq!(1, ct2);
1067 drop(func);
1068
1069 let ct1 = CALLBACK_REGISTRY.with(|rc| rc.borrow().len());
1070 let ct2 = CALLBACK_IDS.with(|rc| rc.borrow().len());
1071 assert_eq!(0, ct1);
1072 assert_eq!(0, ct2);
1073 });
1074 }
1075}