quickjs_runtime/quickjs_utils/
functions.rs

1//! utils to create and invoke functions
2
3use 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
18/// parse a function body and its arg_names into a JSValueRef which is a Function
19/// # Example
20/// ```dontrun
21/// use quickjs_runtime::esruntimebuilder::EsRuntimeBuilder;
22/// use quickjs_runtime::quickjs_utils::functions::{parse_function, call_function};
23/// use quickjs_runtime::quickjs_utils::primitives;
24/// use quickjs_runtime::JsError::JsError;
25/// use quickjs_runtime::valueref::JSValueRef;
26/// let rt = EsRuntimeBuilder::new().build();
27/// rt.add_to_event_queue_sync(|q_js_rt| {
28///     let q_ctx = q_js_rt.get_main_context();
29///     let func_res = parse_function(q_ctx.context, false, "my_func", "console.log('running my_func'); return(a * b);", vec!["a", "b"]);
30///     let func = match func_res {
31///         Ok(func) => func,
32///         Err(e) => {
33///             panic!("could not get func: {}", e);
34///         }
35///     };
36///     let a = primitives::from_i32(7);
37///     let b = primitives::from_i32(9);
38///     let res = call_function(q_ctx.context, &func, vec![a, b], None).ok().unwrap();
39///     let res_i32 = primitives::to_i32(&res).ok().unwrap();
40///     assert_eq!(res_i32, 63);
41/// });
42/// ```
43/// # Safety
44/// when passing a context ptr please be sure that the corresponding QuickJsContext is still active
45pub 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    // todo validate argNames
53    // todo validate body
54
55    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
68/// call a function
69pub 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
78/// call a function
79pub 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
89/// call a function
90/// # Safety
91/// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
92pub 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
102/// call a function
103/// # Safety
104/// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
105pub 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
151/*
152pub fn JS_Invoke(
153        ctx: *mut JSContext,
154        this_val: JSValue,
155        atom: JSAtom,
156        argc: ::std::os::raw::c_int,
157        argv: *mut JSValue,
158    ) -> JSValue;
159 */
160
161pub 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)]
171/// # Safety
172/// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
173pub 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 member = get_property(context, obj_ref, function_name)?;
180    //call_function(context, &member, arguments, Some(obj_ref))
181
182    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
221/// call an objects to_String method or convert a value to string
222pub 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
229/// call an objects to_String method or convert a value to string
230/// # Safety
231/// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
232pub 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        // todo better for Errors (plus stack)
258
259        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
271/// see if an Object is an instance of Function
272pub 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)]
277/// see if an Object is an instance of Function
278/// # Safety
279/// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
280pub 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
294/// see if an Object is an instance of Function and is a constructor (can be instantiated with new keyword)
295pub fn is_constructor_q(q_ctx: &QuickJsRealmAdapter, obj_ref: &QuickJsValueAdapter) -> bool {
296    unsafe { is_constructor(q_ctx.context, obj_ref) }
297}
298
299/// see if an Object is an instance of Function and is a constructor (can be instantiated with new keyword)
300/// # Safety
301/// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
302pub 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
316/// call a constructor (instantiate an Object)
317pub 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
325/// call a constructor (instantiate an Object)
326/// # Safety
327/// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
328pub unsafe fn call_constructor(
329    context: *mut q::JSContext,
330    constructor_ref: &QuickJsValueAdapter,
331    arguments: &[QuickJsValueAdapter],
332) -> Result<QuickJsValueAdapter, JsError> {
333    //extern "C" {
334    //     pub fn JS_CallConstructor(
335    //         ctx: *mut JSContext,
336    //         func_obj: JSValue,
337    //         argc: ::std::os::raw::c_int,
338    //         argv: *mut JSValue,
339    //     ) -> JSValue;
340    // }
341
342    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
376/// create a new Function object which calls a native method
377pub 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
387/// create a new Function object which calls a native method
388/// # Safety
389/// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
390pub 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
440/// create a new Function object which calls a native method (with data)
441pub 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
451/// create a new Function object which calls a native method (with data)
452/// # Safety
453/// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
454pub 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                // todo res should be 0 for ok
557            });
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        //
573    });
574}
575
576/// create a new Function which is backed by a closure
577/// # Example
578/// ```rust
579/// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
580/// use quickjs_runtime::jsutils::Script;
581/// use quickjs_runtime::quickjs_utils::functions::new_function_q;
582/// use quickjs_runtime::quickjs_utils::primitives::from_i32;
583/// use quickjs_runtime::quickjs_utils::get_global_q;
584/// use quickjs_runtime::quickjs_utils::objects::set_property_q;
585/// let rt = QuickJsRuntimeBuilder::new().build();
586/// rt.exe_rt_task_in_event_loop(|q_js_rt| {
587///     let q_ctx = q_js_rt.get_main_realm();
588///     // create a function which always returns 1253
589///     let func_obj = new_function_q(q_ctx, "myFunc7654", |_q_ctx, _this, _args|{Ok(from_i32(1253))}, 0).ok().unwrap();
590///     // store as a global member so script can call it
591///     let global = get_global_q(q_ctx);
592///     set_property_q(q_ctx, &global, "myFunc7654", &func_obj).expect("set prop failed
593/// ");
594/// });
595/// rt.eval_sync(None, Script::new("new_function_q.es", "let a = myFunc7654(); if (a !== 1253) {throw Error('a was not 1253')}")).ok().expect("script failed");
596/// ```
597pub 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
623/// create a new Function which is backed by a closure
624/// # Safety
625/// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
626pub 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    // put func in map, retrieve on call.. delete on destroy
641    // create a new class_def for callbacks, with a finalize
642    // use setproto to bind class to function
643    // use autoidmap to store callbacks and generate ID's
644    // create function with newCFunctionData and put id in data
645
646    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            // cleanup
787            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    // todo run multiple times and check refcount not growing for data, this and args
1009
1010    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        // yes we clone here, yes that bites, but callbacks can be called recursively causing the CALLBACK_REGISTRY to be already borrowed
1019        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}