quickjs_runtime/
quickjsrealmadapter.rs

1use crate::facades::QuickjsRuntimeFacadeInner;
2use crate::quickjs_utils::objects::construct_object;
3use crate::quickjs_utils::primitives::{from_bool, from_f64, from_i32, from_string_q};
4use crate::quickjs_utils::typedarrays::{
5    detach_array_buffer_buffer_q, get_array_buffer_buffer_copy_q, get_array_buffer_q,
6    new_uint8_array_copy_q, new_uint8_array_q,
7};
8use crate::quickjs_utils::{arrays, errors, functions, get_global_q, json, new_null_ref, objects};
9use crate::quickjsruntimeadapter::{make_cstring, QuickJsRuntimeAdapter};
10use crate::quickjsvalueadapter::{QuickJsValueAdapter, TAG_EXCEPTION};
11use crate::reflection::eventtarget::dispatch_event;
12use crate::reflection::eventtarget::dispatch_static_event;
13use crate::reflection::{new_instance, new_instance3, Proxy};
14use hirofa_utils::auto_id_map::AutoIdMap;
15
16use crate::jsutils::jsproxies::{JsProxy, JsProxyInstanceId};
17use crate::jsutils::{JsError, JsValueType, Script};
18use crate::quickjs_utils::promises::QuickJsPromiseAdapter;
19use crate::values::{
20    CachedJsArrayRef, CachedJsFunctionRef, CachedJsObjectRef, CachedJsPromiseRef, JsValueFacade,
21    TypedArrayType,
22};
23use libquickjs_sys as q;
24use serde_json::Value;
25use std::cell::RefCell;
26use std::collections::HashMap;
27use std::ffi::CString;
28use std::future::Future;
29use std::os::raw::c_void;
30use std::rc::Rc;
31use std::sync::{Arc, Weak};
32
33use crate::jsutils::promises::new_resolving_promise;
34use crate::jsutils::promises::new_resolving_promise_async;
35use string_cache::DefaultAtom;
36
37type ProxyEventListenerMaps = HashMap<
38    String, /*proxy_class_name*/
39    HashMap<
40        usize, /*proxy_instance_id*/
41        HashMap<
42            String, /*event_id*/
43            HashMap<
44                QuickJsValueAdapter, /*listener_func*/
45                QuickJsValueAdapter, /*options_obj*/
46            >,
47        >,
48    >,
49>;
50
51type ProxyStaticEventListenerMaps = HashMap<
52    String, /*proxy_class_name*/
53    HashMap<
54        String, /*event_id*/
55        HashMap<
56            QuickJsValueAdapter, /*listener_func*/
57            QuickJsValueAdapter, /*options_obj*/
58        >,
59    >,
60>;
61
62pub struct QuickJsRealmAdapter {
63    object_cache: RefCell<AutoIdMap<QuickJsValueAdapter>>,
64    promise_cache: RefCell<AutoIdMap<QuickJsPromiseAdapter>>,
65    pub(crate) proxy_registry: RefCell<HashMap<String, Rc<Proxy>>>, // todo is this Rc needed or can we just borrow the Proxy when needed?
66    pub(crate) proxy_constructor_refs: RefCell<HashMap<String, QuickJsValueAdapter>>,
67    pub(crate) proxy_event_listeners: RefCell<ProxyEventListenerMaps>,
68    pub(crate) proxy_static_event_listeners: RefCell<ProxyStaticEventListenerMaps>,
69    pub id: String,
70    pub context: *mut q::JSContext,
71}
72
73thread_local! {
74    #[allow(clippy::box_collection)]
75    static ID_REGISTRY: RefCell<HashMap<String, Box<String>>> = RefCell::new(HashMap::new());
76}
77
78impl QuickJsRealmAdapter {
79    pub fn print_stats(&self) {
80        println!(
81            "QuickJsRealmAdapter.object_cache.len = {}",
82            self.object_cache.borrow().len()
83        );
84        println!(
85            "QuickJsRealmAdapter.promise_cache.len = {}",
86            self.promise_cache.borrow().len()
87        );
88
89        println!("-- > QuickJsRealmAdapter.proxy instances");
90        for p in &*self.proxy_registry.borrow() {
91            let prc = p.1.clone();
92            let proxy = &*prc;
93            let mappings = &*proxy.proxy_instance_id_mappings.borrow();
94            println!("---- > {} len:{}", p.0, mappings.len());
95            print!("------ ids: ");
96            for i in mappings {
97                print!("{}, ", i.0);
98            }
99            println!("\n---- < {}", p.0);
100        }
101        println!("-- < QuickJsRealmAdapter.proxy instances");
102
103        let _spsel: &ProxyStaticEventListenerMaps = &self.proxy_static_event_listeners.borrow();
104        let psel: &ProxyEventListenerMaps = &self.proxy_event_listeners.borrow();
105
106        println!("> psel");
107        for a in psel {
108            println!("- psel - {}", a.0);
109            let map = a.1;
110            for b in map {
111                println!("- psel - id {}", b.0);
112                let map_b = b.1;
113                for c in map_b {
114                    println!("- psel - id {} - evt {}", b.0, c.0);
115                    let map_c = c.1;
116                    println!(
117                        "- psel - id {} - evt {} - mapC.len={}",
118                        b.0,
119                        c.0,
120                        map_c.len()
121                    );
122                    for eh in map_c {
123                        // handler, options?
124                        println!(
125                            "- psel - id {} - evt {} - handler:{} options:{}",
126                            b.0,
127                            c.0,
128                            eh.0.to_string().expect("could not toString"),
129                            eh.1.to_string().expect("could not toString")
130                        );
131                    }
132                }
133            }
134        }
135        println!("< psel");
136    }
137
138    pub(crate) fn free(&self) {
139        log::trace!("QuickJsContext:free {}", self.id);
140        {
141            let cache_map = &mut *self.object_cache.borrow_mut();
142            log::trace!(
143                "QuickJsContext:free {}, dropping {} cached objects",
144                self.id,
145                cache_map.len()
146            );
147            cache_map.clear();
148        }
149
150        let mut all_listeners = {
151            let proxy_event_listeners: &mut ProxyEventListenerMaps =
152                &mut self.proxy_event_listeners.borrow_mut();
153            std::mem::take(proxy_event_listeners)
154        };
155        // drop outside of borrowmut so finalizers don;t get error when trying to get mut borrow on map
156        all_listeners.clear();
157
158        // hmm these should still exist minus the constrcutor ref on free, so we need to remove the constructor refs, then call free, then call gc and then clear proxies
159        // so here we should just clear the refs..
160        let mut all_constructor_refs = {
161            let proxy_constructor_refs = &mut *self.proxy_constructor_refs.borrow_mut();
162            std::mem::take(proxy_constructor_refs)
163        };
164        all_constructor_refs.clear();
165
166        unsafe { q::JS_FreeContext(self.context) };
167
168        log::trace!("after QuickJsContext:free {}", self.id);
169    }
170    pub(crate) fn new(id: String, q_js_rt: &QuickJsRuntimeAdapter) -> Self {
171        let context = unsafe { q::JS_NewContext(q_js_rt.runtime) };
172
173        let mut bx = Box::new(id.clone());
174
175        let ibp: &mut String = &mut bx;
176        let info_ptr = ibp as *mut _ as *mut c_void;
177
178        ID_REGISTRY.with(|rc| {
179            let registry = &mut *rc.borrow_mut();
180            registry.insert(id.clone(), bx);
181        });
182
183        unsafe { q::JS_SetContextOpaque(context, info_ptr) };
184
185        if context.is_null() {
186            panic!("ContextCreationFailed");
187        }
188
189        Self {
190            id,
191            context,
192            object_cache: RefCell::new(AutoIdMap::new_with_max_size(i32::MAX as usize)),
193            promise_cache: RefCell::new(AutoIdMap::new()),
194            proxy_registry: RefCell::new(Default::default()),
195            proxy_constructor_refs: RefCell::new(Default::default()),
196            proxy_event_listeners: RefCell::new(Default::default()),
197            proxy_static_event_listeners: RefCell::new(Default::default()),
198        }
199    }
200    /// get the id of a QuickJsContext from a JSContext
201    /// # Safety
202    /// when passing a context ptr please be sure that the corresponding QuickJsContext is still active
203    pub unsafe fn get_id(context: *mut q::JSContext) -> &'static str {
204        let info_ptr: *mut c_void = q::JS_GetContextOpaque(context);
205        let info: &mut String = &mut *(info_ptr as *mut String);
206        info
207    }
208    /// invoke a function by namespace and name
209    pub fn invoke_function_by_name(
210        &self,
211        namespace: &[&str],
212        func_name: &str,
213        arguments: &[QuickJsValueAdapter],
214    ) -> Result<QuickJsValueAdapter, JsError> {
215        let namespace_ref = unsafe { objects::get_namespace(self.context, namespace, false) }?;
216        functions::invoke_member_function_q(self, &namespace_ref, func_name, arguments)
217    }
218
219    /// evaluate a script
220    pub fn eval(&self, script: Script) -> Result<QuickJsValueAdapter, JsError> {
221        unsafe { Self::eval_ctx(self.context, script, None) }
222    }
223
224    pub fn eval_this(
225        &self,
226        script: Script,
227        this: QuickJsValueAdapter,
228    ) -> Result<QuickJsValueAdapter, JsError> {
229        unsafe { Self::eval_ctx(self.context, script, Some(this)) }
230    }
231
232    /// # Safety
233    /// when passing a context ptr please be sure that the corresponding QuickJsContext is still active
234    pub unsafe fn eval_ctx(
235        context: *mut q::JSContext,
236        mut script: Script,
237        this_opt: Option<QuickJsValueAdapter>,
238    ) -> Result<QuickJsValueAdapter, JsError> {
239        log::debug!("q_js_rt.eval file {}", script.get_path());
240
241        script = QuickJsRuntimeAdapter::pre_process(script)?;
242
243        let code_str = script.get_runnable_code();
244
245        let filename_c = make_cstring(script.get_path())?;
246        let code_c = make_cstring(code_str)?;
247
248        let value_raw = match this_opt {
249            None => q::JS_Eval(
250                context,
251                code_c.as_ptr(),
252                code_str.len() as _,
253                filename_c.as_ptr(),
254                q::JS_EVAL_TYPE_GLOBAL as i32,
255            ),
256            Some(this) => q::JS_EvalThis(
257                context,
258                this.clone_value_incr_rc(),
259                code_c.as_ptr(),
260                code_str.len() as _,
261                filename_c.as_ptr(),
262                q::JS_EVAL_TYPE_GLOBAL as i32,
263            ),
264        };
265
266        log::trace!("after eval, checking error");
267
268        // check for error
269        let ret = QuickJsValueAdapter::new(
270            context,
271            value_raw,
272            false,
273            true,
274            format!("eval result of {}", script.get_path()).as_str(),
275        );
276        if ret.is_exception() {
277            let ex_opt = Self::get_exception(context);
278            if let Some(ex) = ex_opt {
279                log::debug!("eval_ctx failed: {}", ex);
280                Err(ex)
281            } else {
282                Err(JsError::new_str("eval failed and could not get exception"))
283            }
284        } else {
285            Ok(ret)
286        }
287    }
288
289    /// evaluate a Module
290    pub fn eval_module(&self, script: Script) -> Result<QuickJsValueAdapter, JsError> {
291        unsafe { Self::eval_module_ctx(self.context, script) }
292    }
293
294    /// # Safety
295    /// when passing a context ptr please be sure that the corresponding QuickJsContext is still active
296    pub unsafe fn eval_module_ctx(
297        context: *mut q::JSContext,
298        mut script: Script,
299    ) -> Result<QuickJsValueAdapter, JsError> {
300        log::debug!("q_js_rt.eval_module file {}", script.get_path());
301
302        script = QuickJsRuntimeAdapter::pre_process(script)?;
303
304        let code_str = script.get_runnable_code();
305
306        let filename_c = make_cstring(script.get_path())?;
307        let code_c = make_cstring(code_str)?;
308
309        let value_raw = q::JS_Eval(
310            context,
311            code_c.as_ptr(),
312            code_str.len() as _,
313            filename_c.as_ptr(),
314            q::JS_EVAL_TYPE_MODULE as i32,
315        );
316
317        let ret = QuickJsValueAdapter::new(
318            context,
319            value_raw,
320            false,
321            true,
322            format!("eval_module result of {}", script.get_path()).as_str(),
323        );
324
325        log::trace!("evalled module yielded a {}", ret.borrow_value().tag);
326
327        // check for error
328
329        if ret.is_exception() {
330            let ex_opt = Self::get_exception(context);
331            if let Some(ex) = ex_opt {
332                log::debug!("eval_module_ctx failed: {}", ex);
333                Err(ex)
334            } else {
335                Err(JsError::new_str(
336                    "eval_module failed and could not get exception",
337                ))
338            }
339        } else {
340            Ok(ret)
341        }
342    }
343    /// throw an internal error to quickjs and create a new ex obj
344    pub fn report_ex(&self, err: &str) -> q::JSValue {
345        unsafe { Self::report_ex_ctx(self.context, err) }
346    }
347    /// throw an Error in the runtime and init an Exception JSValue to return
348    /// # Safety
349    /// when passing a context ptr please be sure that the corresponding QuickJsContext is still active
350    pub unsafe fn report_ex_ctx(context: *mut q::JSContext, err: &str) -> q::JSValue {
351        let c_err = CString::new(err);
352        q::JS_ThrowInternalError(context, c_err.as_ref().ok().unwrap().as_ptr());
353        q::JSValue {
354            u: q::JSValueUnion { int32: 0 },
355            tag: TAG_EXCEPTION,
356        }
357    }
358
359    /// Get the last exception from the runtime, and if present, convert it to a JsError.
360    pub fn get_exception_ctx(&self) -> Option<JsError> {
361        unsafe { errors::get_exception(self.context) }
362    }
363
364    /// Get the last exception from the runtime, and if present, convert it to a JsError.
365    /// # Safety
366    /// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
367    pub unsafe fn get_exception(context: *mut q::JSContext) -> Option<JsError> {
368        errors::get_exception(context)
369    }
370
371    pub fn cache_object(&self, obj: QuickJsValueAdapter) -> i32 {
372        let cache_map = &mut *self.object_cache.borrow_mut();
373        let id = cache_map.insert(obj) as i32;
374        log::trace!("cache_object: id={}, thread={}", id, thread_id::get());
375        id
376    }
377
378    pub fn remove_cached_obj_if_present(&self, id: i32) {
379        log::trace!(
380            "remove_cached_obj_if_present: id={}, thread={}",
381            id,
382            thread_id::get()
383        );
384        let cache_map = &mut *self.object_cache.borrow_mut();
385        if cache_map.contains_key(&(id as usize)) {
386            let _ = cache_map.remove(&(id as usize));
387        }
388    }
389
390    pub fn consume_cached_obj(&self, id: i32) -> QuickJsValueAdapter {
391        log::trace!("consume_cached_obj: id={}, thread={}", id, thread_id::get());
392        let cache_map = &mut *self.object_cache.borrow_mut();
393        cache_map.remove(&(id as usize))
394    }
395
396    pub fn with_cached_obj<C, R>(&self, id: i32, consumer: C) -> R
397    where
398        C: FnOnce(QuickJsValueAdapter) -> R,
399    {
400        log::trace!("with_cached_obj: id={}, thread={}", id, thread_id::get());
401        let clone_ref = {
402            let cache_map = &*self.object_cache.borrow();
403            let opt = cache_map.get(&(id as usize));
404            let cached_ref = opt.expect("no such obj in cache");
405            cached_ref.clone()
406        };
407        // prevent running consumer while borrowed
408
409        consumer(clone_ref)
410    }
411    /// # Safety
412    /// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
413    pub unsafe fn with_context<C, R>(context: *mut q::JSContext, consumer: C) -> R
414    where
415        C: FnOnce(&QuickJsRealmAdapter) -> R,
416    {
417        QuickJsRuntimeAdapter::do_with(|q_js_rt| {
418            let id = QuickJsRealmAdapter::get_id(context);
419            let q_ctx = q_js_rt.get_context(id);
420            consumer(q_ctx)
421        })
422    }
423}
424
425impl Drop for QuickJsRealmAdapter {
426    fn drop(&mut self) {
427        log::trace!("before drop QuickJSContext {}", self.id);
428
429        let id = &self.id;
430        {
431            ID_REGISTRY.with(|rc| {
432                let registry = &mut *rc.borrow_mut();
433                registry.remove(id);
434            });
435        }
436        {
437            let proxies = &mut *self.proxy_registry.borrow_mut();
438            proxies.clear();
439        }
440
441        log::trace!("after drop QuickJSContext {}", self.id);
442    }
443}
444
445impl QuickJsRealmAdapter {
446    pub fn get_realm_id(&self) -> &str {
447        self.id.as_str()
448    }
449
450    pub fn get_runtime_facade_inner(&self) -> Weak<QuickjsRuntimeFacadeInner> {
451        QuickJsRuntimeAdapter::do_with(|rt| {
452            Arc::downgrade(&rt.get_rti_ref().expect("Runtime was dropped"))
453        })
454    }
455
456    pub fn get_script_or_module_name(&self) -> Result<String, JsError> {
457        crate::quickjs_utils::get_script_or_module_name_q(self)
458    }
459
460    pub fn install_proxy(
461        &self,
462        proxy: JsProxy,
463        add_global_var: bool,
464    ) -> Result<QuickJsValueAdapter, JsError> {
465        // create qjs proxy from proxy
466
467        proxy.install(self, add_global_var)
468    }
469
470    pub fn instantiate_proxy_with_id(
471        &self,
472        namespace: &[&str],
473        class_name: &str,
474        instance_id: usize,
475    ) -> Result<QuickJsValueAdapter, JsError> {
476        // todo store proxies with slice/name as key?
477        let cn = if namespace.is_empty() {
478            class_name.to_string()
479        } else {
480            format!("{}.{}", namespace.join("."), class_name)
481        };
482
483        let proxy_map = self.proxy_registry.borrow();
484        let proxy = proxy_map.get(cn.as_str()).expect("class not found");
485
486        new_instance3(proxy, instance_id, self)
487    }
488
489    pub fn instantiate_proxy(
490        &self,
491        namespace: &[&str],
492        class_name: &str,
493        arguments: &[QuickJsValueAdapter],
494    ) -> Result<(JsProxyInstanceId, QuickJsValueAdapter), JsError> {
495        // todo store proxies with slice/name as key?
496        let cn = if namespace.is_empty() {
497            class_name.to_string()
498        } else {
499            format!("{}.{}", namespace.join("."), class_name)
500        };
501
502        let proxy_map = self.proxy_registry.borrow();
503        let proxy = proxy_map.get(cn.as_str()).expect("class not found");
504
505        let instance_info = new_instance(cn.as_str(), self)?;
506
507        if let Some(constructor) = &proxy.constructor {
508            // call constructor myself
509            QuickJsRuntimeAdapter::do_with(|rt| constructor(rt, self, instance_info.0, arguments))?
510        }
511
512        Ok(instance_info)
513    }
514
515    pub fn dispatch_proxy_event(
516        &self,
517        namespace: &[&str],
518        class_name: &str,
519        proxy_instance_id: &usize,
520        event_id: &str,
521        event_obj: &QuickJsValueAdapter,
522    ) -> Result<bool, JsError> {
523        // todo store proxies with slice/name as key?
524        let cn = if namespace.is_empty() {
525            class_name.to_string()
526        } else {
527            format!("{}.{}", namespace.join("."), class_name)
528        };
529
530        let proxy_map = self.proxy_registry.borrow();
531        let proxy = proxy_map.get(cn.as_str()).expect("class not found");
532
533        dispatch_event(self, proxy, *proxy_instance_id, event_id, event_obj.clone())
534    }
535
536    pub fn dispatch_static_proxy_event(
537        &self,
538        namespace: &[&str],
539        class_name: &str,
540        event_id: &str,
541        event_obj: &QuickJsValueAdapter,
542    ) -> Result<bool, JsError> {
543        // todo store proxies with slice/name as key?
544        let cn = if namespace.is_empty() {
545            class_name.to_string()
546        } else {
547            format!("{}.{}", namespace.join("."), class_name)
548        };
549
550        let proxy_map = self.proxy_registry.borrow();
551        let proxy = proxy_map.get(cn.as_str()).expect("class not found");
552
553        dispatch_static_event(
554            self,
555            proxy.get_class_name().as_str(),
556            event_id,
557            event_obj.clone(),
558        )
559    }
560
561    pub fn install_function(
562        &self,
563        namespace: &[&str],
564        name: &str,
565        js_function: fn(
566            &QuickJsRuntimeAdapter,
567            &Self,
568            &QuickJsValueAdapter,
569            &[QuickJsValueAdapter],
570        ) -> Result<QuickJsValueAdapter, JsError>,
571        arg_count: u32,
572    ) -> Result<(), JsError> {
573        // todo namespace as slice?
574        let ns = self.get_namespace(namespace)?;
575
576        let func = functions::new_function_q(
577            self,
578            name,
579            move |ctx, this, args| {
580                QuickJsRuntimeAdapter::do_with(|rt| js_function(rt, ctx, this, args))
581            },
582            arg_count,
583        )?;
584        self.set_object_property(&ns, name, &func)?;
585        Ok(())
586    }
587
588    pub fn install_closure<
589        F: Fn(
590                &QuickJsRuntimeAdapter,
591                &Self,
592                &QuickJsValueAdapter,
593                &[QuickJsValueAdapter],
594            ) -> Result<QuickJsValueAdapter, JsError>
595            + 'static,
596    >(
597        &self,
598        namespace: &[&str],
599        name: &str,
600        js_function: F,
601        arg_count: u32,
602    ) -> Result<(), JsError> {
603        // todo namespace as slice?
604        let ns = self.get_namespace(namespace)?;
605
606        let func = functions::new_function_q(
607            self,
608            name,
609            move |ctx, this, args| {
610                QuickJsRuntimeAdapter::do_with(|rt| js_function(rt, ctx, this, args))
611            },
612            arg_count,
613        )?;
614        self.set_object_property(&ns, name, &func)?;
615        Ok(())
616    }
617
618    pub fn get_global(&self) -> Result<QuickJsValueAdapter, JsError> {
619        Ok(get_global_q(self))
620    }
621
622    pub fn get_namespace(&self, namespace: &[&str]) -> Result<QuickJsValueAdapter, JsError> {
623        objects::get_namespace_q(self, namespace, true)
624    }
625
626    pub fn invoke_function_on_object_by_name(
627        &self,
628        this_obj: &QuickJsValueAdapter,
629        method_name: &str,
630        args: &[QuickJsValueAdapter],
631    ) -> Result<QuickJsValueAdapter, JsError> {
632        functions::invoke_member_function_q(self, this_obj, method_name, args)
633    }
634
635    pub fn invoke_function(
636        &self,
637        this_obj: Option<&QuickJsValueAdapter>,
638        function_obj: &QuickJsValueAdapter,
639        args: &[&QuickJsValueAdapter],
640    ) -> Result<QuickJsValueAdapter, JsError> {
641        functions::call_function_q_ref_args(self, function_obj, args, this_obj)
642    }
643
644    pub fn create_function<
645        F: Fn(
646                &Self,
647                &QuickJsValueAdapter,
648                &[QuickJsValueAdapter],
649            ) -> Result<QuickJsValueAdapter, JsError>
650            + 'static,
651    >(
652        &self,
653        name: &str,
654        js_function: F,
655        arg_count: u32,
656    ) -> Result<QuickJsValueAdapter, JsError> {
657        functions::new_function_q(self, name, js_function, arg_count)
658    }
659
660    pub fn create_function_async<R, F>(
661        &self,
662        name: &str,
663        js_function: F,
664        arg_count: u32,
665    ) -> Result<QuickJsValueAdapter, JsError>
666    where
667        Self: Sized + 'static,
668        R: Future<Output = Result<JsValueFacade, JsError>> + Send + 'static,
669        F: Fn(JsValueFacade, Vec<JsValueFacade>) -> R + 'static,
670    {
671        //
672        self.create_function(
673            name,
674            move |realm, this, args| {
675                let this_fac = realm.to_js_value_facade(this)?;
676                let mut args_fac = vec![];
677                for arg in args {
678                    args_fac.push(realm.to_js_value_facade(arg)?);
679                }
680                let fut = js_function(this_fac, args_fac);
681                realm.create_resolving_promise_async(fut, |realm, pres| {
682                    //
683                    realm.from_js_value_facade(pres)
684                })
685            },
686            arg_count,
687        )
688    }
689
690    pub fn create_error(
691        &self,
692        name: &str,
693        message: &str,
694        stack: &str,
695    ) -> Result<QuickJsValueAdapter, JsError> {
696        unsafe { errors::new_error(self.context, name, message, stack) }
697    }
698
699    pub fn delete_object_property(
700        &self,
701        object: &QuickJsValueAdapter,
702        property_name: &str,
703    ) -> Result<(), JsError> {
704        // todo impl a real delete_prop
705        objects::set_property_q(self, object, property_name, &new_null_ref())
706    }
707
708    pub fn set_object_property(
709        &self,
710        object: &QuickJsValueAdapter,
711        property_name: &str,
712        property: &QuickJsValueAdapter,
713    ) -> Result<(), JsError> {
714        objects::set_property_q(self, object, property_name, property)
715    }
716
717    pub fn get_object_property(
718        &self,
719        object: &QuickJsValueAdapter,
720        property_name: &str,
721    ) -> Result<QuickJsValueAdapter, JsError> {
722        objects::get_property_q(self, object, property_name)
723    }
724
725    pub fn create_object(&self) -> Result<QuickJsValueAdapter, JsError> {
726        objects::create_object_q(self)
727    }
728
729    pub fn construct_object(
730        &self,
731        constructor: &QuickJsValueAdapter,
732        args: &[&QuickJsValueAdapter],
733    ) -> Result<QuickJsValueAdapter, JsError> {
734        // todo alter constructor method to accept slice
735        unsafe { construct_object(self.context, constructor, args) }
736    }
737
738    pub fn get_object_properties(
739        &self,
740        object: &QuickJsValueAdapter,
741    ) -> Result<Vec<String>, JsError> {
742        let props = objects::get_own_property_names_q(self, object)?;
743        let mut ret = vec![];
744        for x in 0..props.len() {
745            let prop = props.get_name(x)?;
746            ret.push(prop);
747        }
748        Ok(ret)
749    }
750
751    pub fn traverse_object<F, R>(
752        &self,
753        object: &QuickJsValueAdapter,
754        visitor: F,
755    ) -> Result<Vec<R>, JsError>
756    where
757        F: Fn(&str, &QuickJsValueAdapter) -> Result<R, JsError>,
758    {
759        objects::traverse_properties_q(self, object, visitor)
760    }
761
762    pub fn traverse_object_mut<F>(
763        &self,
764        object: &QuickJsValueAdapter,
765        visitor: F,
766    ) -> Result<(), JsError>
767    where
768        F: FnMut(&str, &QuickJsValueAdapter) -> Result<(), JsError>,
769    {
770        objects::traverse_properties_q_mut(self, object, visitor)
771    }
772
773    pub fn get_array_element(
774        &self,
775        array: &QuickJsValueAdapter,
776        index: u32,
777    ) -> Result<QuickJsValueAdapter, JsError> {
778        arrays::get_element_q(self, array, index)
779    }
780
781    /// push an element into an Array
782    pub fn push_array_element(
783        &self,
784        array: &QuickJsValueAdapter,
785        element: &QuickJsValueAdapter,
786    ) -> Result<u32, JsError> {
787        let push_func = self.get_object_property(array, "push")?;
788        let res = self.invoke_function(Some(array), &push_func, &[element])?;
789        Ok(res.to_i32() as u32)
790    }
791
792    pub fn set_array_element(
793        &self,
794        array: &QuickJsValueAdapter,
795        index: u32,
796        element: &QuickJsValueAdapter,
797    ) -> Result<(), JsError> {
798        arrays::set_element_q(self, array, index, element)
799    }
800
801    pub fn get_array_length(&self, array: &QuickJsValueAdapter) -> Result<u32, JsError> {
802        arrays::get_length_q(self, array)
803    }
804
805    pub fn create_array(&self) -> Result<QuickJsValueAdapter, JsError> {
806        arrays::create_array_q(self)
807    }
808
809    pub fn traverse_array<F, R>(
810        &self,
811        array: &QuickJsValueAdapter,
812        visitor: F,
813    ) -> Result<Vec<R>, JsError>
814    where
815        F: Fn(u32, &QuickJsValueAdapter) -> Result<R, JsError>,
816    {
817        // todo impl real traverse methods
818        let mut ret = vec![];
819        for x in 0..arrays::get_length_q(self, array)? {
820            let val = arrays::get_element_q(self, array, x)?;
821            ret.push(visitor(x, &val)?)
822        }
823        Ok(ret)
824    }
825
826    pub fn traverse_array_mut<F>(
827        &self,
828        array: &QuickJsValueAdapter,
829        mut visitor: F,
830    ) -> Result<(), JsError>
831    where
832        F: FnMut(u32, &QuickJsValueAdapter) -> Result<(), JsError>,
833    {
834        // todo impl real traverse methods
835        for x in 0..arrays::get_length_q(self, array)? {
836            let val = arrays::get_element_q(self, array, x)?;
837            visitor(x, &val)?;
838        }
839        Ok(())
840    }
841
842    pub fn create_null(&self) -> Result<QuickJsValueAdapter, JsError> {
843        Ok(crate::quickjs_utils::new_null_ref())
844    }
845
846    pub fn create_undefined(&self) -> Result<QuickJsValueAdapter, JsError> {
847        Ok(crate::quickjs_utils::new_undefined_ref())
848    }
849
850    pub fn create_i32(&self, val: i32) -> Result<QuickJsValueAdapter, JsError> {
851        Ok(from_i32(val))
852    }
853
854    pub fn create_string(&self, val: &str) -> Result<QuickJsValueAdapter, JsError> {
855        from_string_q(self, val)
856    }
857
858    pub fn create_boolean(&self, val: bool) -> Result<QuickJsValueAdapter, JsError> {
859        Ok(from_bool(val))
860    }
861
862    pub fn create_f64(&self, val: f64) -> Result<QuickJsValueAdapter, JsError> {
863        Ok(from_f64(val))
864    }
865
866    pub fn create_promise(&self) -> Result<QuickJsPromiseAdapter, JsError> {
867        crate::quickjs_utils::promises::new_promise_q(self)
868    }
869
870    pub fn add_promise_reactions(
871        &self,
872        promise: &QuickJsValueAdapter,
873        then: Option<QuickJsValueAdapter>,
874        catch: Option<QuickJsValueAdapter>,
875        finally: Option<QuickJsValueAdapter>,
876    ) -> Result<(), JsError> {
877        crate::quickjs_utils::promises::add_promise_reactions_q(self, promise, then, catch, finally)
878    }
879
880    pub fn cache_promise(&self, promise_ref: QuickJsPromiseAdapter) -> usize {
881        let map = &mut *self.promise_cache.borrow_mut();
882        map.insert(promise_ref)
883    }
884
885    pub fn consume_cached_promise(&self, id: usize) -> Option<QuickJsPromiseAdapter> {
886        let map = &mut *self.promise_cache.borrow_mut();
887        map.remove_opt(&id)
888    }
889
890    pub fn dispose_cached_object(&self, id: i32) {
891        let _ = self.consume_cached_obj(id);
892    }
893
894    pub fn with_cached_object<C, R>(&self, id: i32, consumer: C) -> R
895    where
896        C: FnOnce(&QuickJsValueAdapter) -> R,
897    {
898        self.with_cached_obj(id, |obj| consumer(&obj))
899    }
900
901    pub fn consume_cached_object(&self, id: i32) -> QuickJsValueAdapter {
902        self.consume_cached_obj(id)
903    }
904
905    pub fn is_instance_of(
906        &self,
907        object: &QuickJsValueAdapter,
908        constructor: &QuickJsValueAdapter,
909    ) -> bool {
910        objects::is_instance_of_q(self, object, constructor)
911    }
912
913    pub fn json_stringify(
914        &self,
915        object: &QuickJsValueAdapter,
916        opt_space: Option<&str>,
917    ) -> Result<String, JsError> {
918        let opt_space_jsvr = match opt_space {
919            None => None,
920            Some(s) => Some(self.create_string(s)?),
921        };
922        let res = json::stringify_q(self, object, opt_space_jsvr);
923        match res {
924            Ok(jsvr) => jsvr.to_string(),
925            Err(e) => Err(e),
926        }
927    }
928
929    pub fn json_parse(&self, json_string: &str) -> Result<QuickJsValueAdapter, JsError> {
930        json::parse_q(self, json_string)
931    }
932
933    pub fn create_typed_array_uint8(
934        &self,
935        buffer: Vec<u8>,
936    ) -> Result<QuickJsValueAdapter, JsError> {
937        new_uint8_array_q(self, buffer)
938    }
939
940    pub fn create_typed_array_uint8_copy(
941        &self,
942        buffer: &[u8],
943    ) -> Result<QuickJsValueAdapter, JsError> {
944        new_uint8_array_copy_q(self, buffer)
945    }
946
947    pub fn detach_typed_array_buffer(
948        &self,
949        array: &QuickJsValueAdapter,
950    ) -> Result<Vec<u8>, JsError> {
951        let abuf = get_array_buffer_q(self, array)?;
952        detach_array_buffer_buffer_q(self, &abuf)
953    }
954
955    pub fn copy_typed_array_buffer(&self, array: &QuickJsValueAdapter) -> Result<Vec<u8>, JsError> {
956        let abuf = get_array_buffer_q(self, array)?;
957        get_array_buffer_buffer_copy_q(self, &abuf)
958    }
959
960    pub fn get_proxy_instance_info(
961        &self,
962        obj: &QuickJsValueAdapter,
963    ) -> Result<(String, JsProxyInstanceId), JsError>
964    where
965        Self: Sized,
966    {
967        if let Some((p, i)) =
968            crate::reflection::get_proxy_instance_proxy_and_instance_id_q(self, obj)
969        {
970            Ok((p.get_class_name(), i))
971        } else {
972            Err(JsError::new_str("not a proxy instance"))
973        }
974    }
975
976    pub fn to_js_value_facade(
977        &self,
978        js_value: &QuickJsValueAdapter,
979    ) -> Result<JsValueFacade, JsError>
980    where
981        Self: Sized + 'static,
982    {
983        let res: JsValueFacade = match js_value.get_js_type() {
984            JsValueType::I32 => JsValueFacade::I32 {
985                val: js_value.to_i32(),
986            },
987            JsValueType::F64 => JsValueFacade::F64 {
988                val: js_value.to_f64(),
989            },
990            JsValueType::String => JsValueFacade::String {
991                val: DefaultAtom::from(js_value.to_string()?),
992            },
993            JsValueType::Boolean => JsValueFacade::Boolean {
994                val: js_value.to_bool(),
995            },
996            JsValueType::Object => {
997                if js_value.is_typed_array() {
998                    // todo TypedArray as JsValueType?
999                    // passing a typedarray out of the worker thread is sketchy because you either copy the buffer like we do here, or you detach the buffer effectively destroying the jsvalue
1000                    // you should be better of optimizing this in native methods
1001                    JsValueFacade::TypedArray {
1002                        buffer: self.copy_typed_array_buffer(js_value)?,
1003                        array_type: TypedArrayType::Uint8,
1004                    }
1005                } else {
1006                    JsValueFacade::JsObject {
1007                        cached_object: CachedJsObjectRef::new(self, js_value.clone()),
1008                    }
1009                }
1010            }
1011            JsValueType::Function => JsValueFacade::JsFunction {
1012                cached_function: CachedJsFunctionRef {
1013                    cached_object: CachedJsObjectRef::new(self, js_value.clone()),
1014                },
1015            },
1016            JsValueType::BigInt => {
1017                todo!();
1018            }
1019            JsValueType::Promise => JsValueFacade::JsPromise {
1020                cached_promise: CachedJsPromiseRef {
1021                    cached_object: CachedJsObjectRef::new(self, js_value.clone()),
1022                },
1023            },
1024            JsValueType::Date => {
1025                todo!();
1026            }
1027            JsValueType::Null => JsValueFacade::Null,
1028            JsValueType::Undefined => JsValueFacade::Undefined,
1029
1030            JsValueType::Array => JsValueFacade::JsArray {
1031                cached_array: CachedJsArrayRef {
1032                    cached_object: CachedJsObjectRef::new(self, js_value.clone()),
1033                },
1034            },
1035            JsValueType::Error => {
1036                let name = self.get_object_property(js_value, "name")?.to_string()?;
1037                let message = self.get_object_property(js_value, "message")?.to_string()?;
1038                let stack = self.get_object_property(js_value, "stack")?.to_string()?;
1039
1040                #[cfg(feature = "typescript")]
1041                let stack = crate::typescript::unmap_stack_trace(stack.as_str());
1042
1043                JsValueFacade::JsError {
1044                    val: JsError::new(name, message, stack),
1045                }
1046            }
1047        };
1048        Ok(res)
1049    }
1050
1051    /// convert a JSValueFacade into a JSValueAdapter
1052    /// you need this to move values into the worker thread from a different thread (JSValueAdapter cannot leave the worker thread)
1053    #[allow(clippy::wrong_self_convention)]
1054    pub fn from_js_value_facade(
1055        &self,
1056        value_facade: JsValueFacade,
1057    ) -> Result<QuickJsValueAdapter, JsError>
1058    where
1059        Self: Sized + 'static,
1060    {
1061        match value_facade {
1062            JsValueFacade::I32 { val } => self.create_i32(val),
1063            JsValueFacade::F64 { val } => self.create_f64(val),
1064            JsValueFacade::String { val } => self.create_string(&val),
1065            JsValueFacade::Boolean { val } => self.create_boolean(val),
1066            JsValueFacade::JsObject { cached_object } => {
1067                // todo check realm (else copy? or error?)
1068                self.with_cached_object(cached_object.id, |obj| Ok(obj.clone()))
1069            }
1070            JsValueFacade::JsPromise { cached_promise } => {
1071                // todo check realm (else copy? or error?)
1072                self.with_cached_object(cached_promise.cached_object.id, |obj| Ok(obj.clone()))
1073            }
1074            JsValueFacade::JsArray { cached_array } => {
1075                // todo check realm (else copy? or error?)
1076                self.with_cached_object(cached_array.cached_object.id, |obj| Ok(obj.clone()))
1077            }
1078            JsValueFacade::JsFunction { cached_function } => {
1079                // todo check realm (else copy? or error?)
1080                self.with_cached_object(cached_function.cached_object.id, |obj| Ok(obj.clone()))
1081            }
1082            JsValueFacade::Object { val } => {
1083                let obj = self.create_object()?;
1084                for entry in val {
1085                    let prop = self.from_js_value_facade(entry.1)?;
1086                    self.set_object_property(&obj, entry.0.as_str(), &prop)?;
1087                }
1088                Ok(obj)
1089            }
1090            JsValueFacade::Array { val } => {
1091                let obj = self.create_array()?;
1092                for (x, entry) in val.into_iter().enumerate() {
1093                    let prop = self.from_js_value_facade(entry)?;
1094                    self.set_array_element(&obj, x as u32, &prop)?;
1095                }
1096                Ok(obj)
1097            }
1098            JsValueFacade::Promise { producer } => {
1099                let producer = &mut *producer.lock("from_js_value_facade").unwrap();
1100                if producer.is_some() {
1101                    self.create_resolving_promise_async(producer.take().unwrap(), |realm, jsvf| {
1102                        realm.from_js_value_facade(jsvf)
1103                    })
1104                } else {
1105                    self.create_null()
1106                }
1107            }
1108            JsValueFacade::Function {
1109                name,
1110                arg_count,
1111                func,
1112            } => {
1113                //
1114
1115                self.create_function(
1116                    name.as_str(),
1117                    move |realm, _this, args| {
1118                        let mut esvf_args = vec![];
1119                        for arg in args {
1120                            esvf_args.push(realm.to_js_value_facade(arg)?);
1121                        }
1122                        let esvf_res: Result<JsValueFacade, JsError> = func(esvf_args.as_slice());
1123
1124                        match esvf_res {
1125                            //
1126                            Ok(jsvf) => realm.from_js_value_facade(jsvf),
1127                            Err(err) => Err(err),
1128                        }
1129                    },
1130                    arg_count,
1131                )
1132            }
1133            JsValueFacade::Null => self.create_null(),
1134            JsValueFacade::Undefined => self.create_undefined(),
1135            JsValueFacade::JsError { val } => {
1136                self.create_error(val.get_name(), val.get_message(), val.get_stack())
1137            }
1138            JsValueFacade::ProxyInstance {
1139                instance_id,
1140                namespace,
1141                class_name,
1142            } => self.instantiate_proxy_with_id(namespace, class_name, instance_id),
1143            JsValueFacade::TypedArray { buffer, array_type } => match array_type {
1144                TypedArrayType::Uint8 => self.create_typed_array_uint8(buffer),
1145            },
1146            JsValueFacade::JsonStr { json } => self.json_parse(json.as_str()),
1147            JsValueFacade::SerdeValue { value } => self.serde_value_to_value_adapter(value),
1148        }
1149    }
1150
1151    pub fn value_adapter_to_serde_value(
1152        &self,
1153        value_adapter: &QuickJsValueAdapter,
1154    ) -> Result<serde_json::Value, JsError> {
1155        match value_adapter.get_js_type() {
1156            JsValueType::I32 => Ok(Value::from(value_adapter.to_i32())),
1157            JsValueType::F64 => Ok(Value::from(value_adapter.to_f64())),
1158            JsValueType::String => Ok(Value::from(value_adapter.to_string()?)),
1159            JsValueType::Boolean => Ok(Value::from(value_adapter.to_bool())),
1160            JsValueType::Object => {
1161                let mut map: serde_json::Map<String, serde_json::Value> = serde_json::Map::new();
1162                self.traverse_object_mut(value_adapter, |k, v| {
1163                    map.insert(k.to_string(), self.value_adapter_to_serde_value(v)?);
1164                    Ok(())
1165                })?;
1166                let obj_val = serde_json::Value::Object(map);
1167                Ok(obj_val)
1168            }
1169            JsValueType::Array => {
1170                let mut arr: Vec<serde_json::Value> = vec![];
1171                self.traverse_array_mut(value_adapter, |_i, v| {
1172                    arr.push(self.value_adapter_to_serde_value(v)?);
1173                    Ok(())
1174                })?;
1175                let arr_val = serde_json::Value::Array(arr);
1176                Ok(arr_val)
1177            }
1178            JsValueType::Null => Ok(serde_json::Value::Null),
1179            JsValueType::Undefined => Ok(serde_json::Value::Null),
1180            JsValueType::Function => Ok(serde_json::Value::Null),
1181            JsValueType::BigInt => Ok(serde_json::Value::Null),
1182            JsValueType::Promise => Ok(serde_json::Value::Null),
1183            JsValueType::Date => Ok(serde_json::Value::Null),
1184            JsValueType::Error => Ok(serde_json::Value::Null),
1185        }
1186    }
1187
1188    pub fn serde_value_to_value_adapter(
1189        &self,
1190        value: Value,
1191    ) -> Result<QuickJsValueAdapter, JsError> {
1192        match value {
1193            Value::Null => self.create_null(),
1194            Value::Bool(b) => self.create_boolean(b),
1195            Value::Number(n) => {
1196                if n.is_i64() {
1197                    let i = n.as_i64().unwrap();
1198                    if i <= i32::MAX as i64 {
1199                        self.create_i32(i as i32)
1200                    } else {
1201                        self.create_f64(i as f64)
1202                    }
1203                } else if n.is_u64() {
1204                    let i = n.as_u64().unwrap();
1205                    if i <= i32::MAX as u64 {
1206                        self.create_i32(i as i32)
1207                    } else {
1208                        self.create_f64(i as f64)
1209                    }
1210                } else {
1211                    // f64
1212                    let i = n.as_f64().unwrap();
1213                    self.create_f64(i)
1214                }
1215            }
1216            Value::String(s) => self.create_string(s.as_str()),
1217            Value::Array(a) => {
1218                let arr = self.create_array()?;
1219                for (x, aval) in (0_u32..).zip(a.into_iter()) {
1220                    let entry = self.serde_value_to_value_adapter(aval)?;
1221                    self.set_array_element(&arr, x, &entry)?;
1222                }
1223                Ok(arr)
1224            }
1225            Value::Object(o) => {
1226                let obj = self.create_object()?;
1227                for oval in o {
1228                    let entry = self.serde_value_to_value_adapter(oval.1)?;
1229                    self.set_object_property(&obj, oval.0.as_str(), &entry)?;
1230                }
1231                Ok(obj)
1232            }
1233        }
1234    }
1235    /// create a new Promise with a Future which will run async and then resolve or reject the promise
1236    /// the mapper is used to convert the result of the future into a JSValueAdapter
1237    pub fn create_resolving_promise_async<P, R: Send + 'static, M>(
1238        &self,
1239        producer: P,
1240        mapper: M,
1241    ) -> Result<QuickJsValueAdapter, JsError>
1242    where
1243        P: Future<Output = Result<R, JsError>> + Send + 'static,
1244        M: FnOnce(&QuickJsRealmAdapter, R) -> Result<QuickJsValueAdapter, JsError> + Send + 'static,
1245        Self: Sized + 'static,
1246    {
1247        new_resolving_promise_async(self, producer, mapper)
1248    }
1249    /// create a new Promise with a FnOnce producer which will run async and then resolve or reject the promise
1250    /// the mapper is used to convert the result of the future into a JSValueAdapter
1251    ///
1252    pub fn create_resolving_promise<P, R: Send + 'static, M>(
1253        &self,
1254        producer: P,
1255        mapper: M,
1256    ) -> Result<QuickJsValueAdapter, JsError>
1257    where
1258        P: FnOnce() -> Result<R, JsError> + Send + 'static,
1259        M: FnOnce(&QuickJsRealmAdapter, R) -> Result<QuickJsValueAdapter, JsError> + Send + 'static,
1260        Self: Sized + 'static,
1261    {
1262        new_resolving_promise(self, producer, mapper)
1263    }
1264}
1265
1266#[cfg(test)]
1267pub mod tests {
1268    use crate::builder::QuickJsRuntimeBuilder;
1269    use crate::facades::tests::init_test_rt;
1270    use crate::jsutils::Script;
1271    use crate::quickjs_utils;
1272    use crate::quickjs_utils::primitives::to_i32;
1273    use crate::quickjs_utils::{functions, get_global_q, objects};
1274
1275    #[test]
1276    fn test_eval() {
1277        let rt = init_test_rt();
1278        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1279            let q_ctx = q_js_rt.get_main_realm();
1280            let res = q_ctx.eval(Script::new("test_eval.es", "(1 + 1);"));
1281
1282            match res {
1283                Ok(res) => {
1284                    log::info!("script ran ok: {:?}", res);
1285                    assert!(res.is_i32());
1286                    assert_eq!(to_i32(&res).ok().expect("conversion failed"), 2);
1287                }
1288                Err(e) => {
1289                    log::error!("script failed: {}", e);
1290                    panic!("script failed");
1291                }
1292            }
1293        });
1294    }
1295
1296    #[test]
1297    fn test_multi_ctx() {
1298        let rt = QuickJsRuntimeBuilder::new().build();
1299        rt.create_context("a").ok().expect("could not create ctx a");
1300        rt.create_context("b").ok().expect("could not create ctx b");
1301
1302        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1303            let ctx_a = q_js_rt.get_context("a");
1304            let ctx_b = q_js_rt.get_context("b");
1305            ctx_a
1306                .eval(Script::new("a.es", "this.a = 1"))
1307                .ok()
1308                .expect("script failed");
1309            ctx_b
1310                .eval(Script::new("a.es", "this.b = 1"))
1311                .ok()
1312                .expect("script failed");
1313            let v = ctx_a
1314                .eval(Script::new("a2.es", "this.a;"))
1315                .ok()
1316                .expect("script failed");
1317            assert!(v.is_i32());
1318            let v2 = ctx_b
1319                .eval(Script::new("b2.es", "this.a;"))
1320                .ok()
1321                .expect("script failed");
1322            assert!(v2.is_null_or_undefined());
1323            let v3 = ctx_a
1324                .eval(Script::new("a2.es", "this.b;"))
1325                .ok()
1326                .expect("script failed");
1327            assert!(v3.is_null_or_undefined());
1328            let v4 = ctx_b
1329                .eval(Script::new("b2.es", "this.b;"))
1330                .ok()
1331                .expect("script failed");
1332            assert!(v4.is_i32());
1333        });
1334        let _ = rt.drop_context("b");
1335
1336        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1337            q_js_rt.gc();
1338            let ctx_a = q_js_rt.get_context("a");
1339            let v = ctx_a
1340                .eval(Script::new("a2.es", "this.a;"))
1341                .ok()
1342                .expect("script failed");
1343            assert!(v.is_i32());
1344            q_js_rt.gc();
1345        });
1346
1347        rt.create_context("c")
1348            .ok()
1349            .expect("could not create context c");
1350
1351        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1352            let c_ctx = q_js_rt.get_context("c");
1353            let func = functions::new_function_q(
1354                c_ctx,
1355                "test",
1356                |_q_ctx, _this, _args| Ok(quickjs_utils::new_null_ref()),
1357                1,
1358            )
1359            .ok()
1360            .unwrap();
1361            let global = get_global_q(c_ctx);
1362            objects::set_property_q(c_ctx, &global, "test_func", &func)
1363                .ok()
1364                .expect("could not set prop");
1365            q_js_rt.gc();
1366        });
1367        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1368            q_js_rt.gc();
1369            let ctx_a = q_js_rt.get_context("a");
1370            let v = ctx_a
1371                .eval(Script::new("a2.es", "this.a;"))
1372                .ok()
1373                .expect("script failed");
1374            assert!(v.is_i32());
1375            q_js_rt.gc();
1376        });
1377        let _ = rt.drop_context("c");
1378        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1379            q_js_rt.gc();
1380            let ctx_a = q_js_rt.get_context("a");
1381            let v = ctx_a
1382                .eval(Script::new("a2.es", "this.a;"))
1383                .ok()
1384                .expect("script failed");
1385            assert!(v.is_i32());
1386            q_js_rt.gc();
1387        });
1388    }
1389}