Skip to main content

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            #[cfg(feature = "bellard")]
355            u: q::JSValueUnion { uint64: 0 },
356            #[cfg(feature = "quickjs-ng")]
357            u: q::JSValueUnion { int32: 0 },
358            tag: TAG_EXCEPTION,
359        }
360    }
361
362    /// Get the last exception from the runtime, and if present, convert it to a JsError.
363    pub fn get_exception_ctx(&self) -> Option<JsError> {
364        unsafe { errors::get_exception(self.context) }
365    }
366
367    /// Get the last exception from the runtime, and if present, convert it to a JsError.
368    /// # Safety
369    /// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
370    pub unsafe fn get_exception(context: *mut q::JSContext) -> Option<JsError> {
371        errors::get_exception(context)
372    }
373
374    pub fn cache_object(&self, obj: QuickJsValueAdapter) -> i32 {
375        let cache_map = &mut *self.object_cache.borrow_mut();
376        let id = cache_map.insert(obj) as i32;
377        log::trace!("cache_object: id={}, thread={}", id, thread_id::get());
378        id
379    }
380
381    pub fn remove_cached_obj_if_present(&self, id: i32) {
382        log::trace!(
383            "remove_cached_obj_if_present: id={}, thread={}",
384            id,
385            thread_id::get()
386        );
387        let cache_map = &mut *self.object_cache.borrow_mut();
388        if cache_map.contains_key(&(id as usize)) {
389            let _ = cache_map.remove(&(id as usize));
390        }
391    }
392
393    pub fn consume_cached_obj(&self, id: i32) -> QuickJsValueAdapter {
394        log::trace!("consume_cached_obj: id={}, thread={}", id, thread_id::get());
395        let cache_map = &mut *self.object_cache.borrow_mut();
396        cache_map.remove(&(id as usize))
397    }
398
399    pub fn with_cached_obj<C, R>(&self, id: i32, consumer: C) -> R
400    where
401        C: FnOnce(QuickJsValueAdapter) -> R,
402    {
403        log::trace!("with_cached_obj: id={}, thread={}", id, thread_id::get());
404        let clone_ref = {
405            let cache_map = &*self.object_cache.borrow();
406            let opt = cache_map.get(&(id as usize));
407            let cached_ref = opt.expect("no such obj in cache");
408            cached_ref.clone()
409        };
410        // prevent running consumer while borrowed
411
412        consumer(clone_ref)
413    }
414    /// # Safety
415    /// When passing a context pointer please make sure the corresponding QuickJsContext is still valid
416    pub unsafe fn with_context<C, R>(context: *mut q::JSContext, consumer: C) -> R
417    where
418        C: FnOnce(&QuickJsRealmAdapter) -> R,
419    {
420        QuickJsRuntimeAdapter::do_with(|q_js_rt| {
421            let id = QuickJsRealmAdapter::get_id(context);
422            let q_ctx = q_js_rt.get_context(id);
423            consumer(q_ctx)
424        })
425    }
426}
427
428impl Drop for QuickJsRealmAdapter {
429    fn drop(&mut self) {
430        log::trace!("before drop QuickJSContext {}", self.id);
431
432        let id = &self.id;
433        {
434            ID_REGISTRY.with(|rc| {
435                let registry = &mut *rc.borrow_mut();
436                registry.remove(id);
437            });
438        }
439        {
440            let proxies = &mut *self.proxy_registry.borrow_mut();
441            proxies.clear();
442        }
443
444        log::trace!("after drop QuickJSContext {}", self.id);
445    }
446}
447
448impl QuickJsRealmAdapter {
449    pub fn get_realm_id(&self) -> &str {
450        self.id.as_str()
451    }
452
453    pub fn get_runtime_facade_inner(&self) -> Weak<QuickjsRuntimeFacadeInner> {
454        QuickJsRuntimeAdapter::do_with(|rt| {
455            Arc::downgrade(&rt.get_rti_ref().expect("Runtime was dropped"))
456        })
457    }
458
459    pub fn get_script_or_module_name(&self) -> Result<String, JsError> {
460        crate::quickjs_utils::get_script_or_module_name_q(self)
461    }
462
463    pub fn install_proxy(
464        &self,
465        proxy: JsProxy,
466        add_global_var: bool,
467    ) -> Result<QuickJsValueAdapter, JsError> {
468        // create qjs proxy from proxy
469
470        proxy.install(self, add_global_var)
471    }
472
473    pub fn instantiate_proxy_with_id(
474        &self,
475        namespace: &[&str],
476        class_name: &str,
477        instance_id: usize,
478    ) -> Result<QuickJsValueAdapter, JsError> {
479        // todo store proxies with slice/name as key?
480        let cn = if namespace.is_empty() {
481            class_name.to_string()
482        } else {
483            format!("{}.{}", namespace.join("."), class_name)
484        };
485
486        let proxy_map = self.proxy_registry.borrow();
487        let proxy = proxy_map.get(cn.as_str()).expect("class not found");
488
489        new_instance3(proxy, instance_id, self)
490    }
491
492    pub fn instantiate_proxy(
493        &self,
494        namespace: &[&str],
495        class_name: &str,
496        arguments: &[QuickJsValueAdapter],
497    ) -> Result<(JsProxyInstanceId, QuickJsValueAdapter), JsError> {
498        // todo store proxies with slice/name as key?
499        let cn = if namespace.is_empty() {
500            class_name.to_string()
501        } else {
502            format!("{}.{}", namespace.join("."), class_name)
503        };
504
505        let proxy_map = self.proxy_registry.borrow();
506        let proxy = proxy_map.get(cn.as_str()).expect("class not found");
507
508        let instance_info = new_instance(cn.as_str(), self)?;
509
510        if let Some(constructor) = &proxy.constructor {
511            // call constructor myself
512            QuickJsRuntimeAdapter::do_with(|rt| constructor(rt, self, instance_info.0, arguments))?
513        }
514
515        Ok(instance_info)
516    }
517
518    pub fn dispatch_proxy_event(
519        &self,
520        namespace: &[&str],
521        class_name: &str,
522        proxy_instance_id: &usize,
523        event_id: &str,
524        event_obj: &QuickJsValueAdapter,
525    ) -> Result<bool, JsError> {
526        // todo store proxies with slice/name as key?
527        let cn = if namespace.is_empty() {
528            class_name.to_string()
529        } else {
530            format!("{}.{}", namespace.join("."), class_name)
531        };
532
533        let proxy_map = self.proxy_registry.borrow();
534        let proxy = proxy_map.get(cn.as_str()).expect("class not found");
535
536        dispatch_event(self, proxy, *proxy_instance_id, event_id, event_obj.clone())
537    }
538
539    pub fn dispatch_static_proxy_event(
540        &self,
541        namespace: &[&str],
542        class_name: &str,
543        event_id: &str,
544        event_obj: &QuickJsValueAdapter,
545    ) -> Result<bool, JsError> {
546        // todo store proxies with slice/name as key?
547        let cn = if namespace.is_empty() {
548            class_name.to_string()
549        } else {
550            format!("{}.{}", namespace.join("."), class_name)
551        };
552
553        let proxy_map = self.proxy_registry.borrow();
554        let proxy = proxy_map.get(cn.as_str()).expect("class not found");
555
556        dispatch_static_event(
557            self,
558            proxy.get_class_name().as_str(),
559            event_id,
560            event_obj.clone(),
561        )
562    }
563
564    pub fn install_function(
565        &self,
566        namespace: &[&str],
567        name: &str,
568        js_function: fn(
569            &QuickJsRuntimeAdapter,
570            &Self,
571            &QuickJsValueAdapter,
572            &[QuickJsValueAdapter],
573        ) -> Result<QuickJsValueAdapter, JsError>,
574        arg_count: u32,
575    ) -> Result<(), JsError> {
576        // todo namespace as slice?
577        let ns = self.get_namespace(namespace)?;
578
579        let func = functions::new_function_q(
580            self,
581            name,
582            move |ctx, this, args| {
583                QuickJsRuntimeAdapter::do_with(|rt| js_function(rt, ctx, this, args))
584            },
585            arg_count,
586        )?;
587        self.set_object_property(&ns, name, &func)?;
588        Ok(())
589    }
590
591    pub fn install_closure<
592        F: Fn(
593                &QuickJsRuntimeAdapter,
594                &Self,
595                &QuickJsValueAdapter,
596                &[QuickJsValueAdapter],
597            ) -> Result<QuickJsValueAdapter, JsError>
598            + 'static,
599    >(
600        &self,
601        namespace: &[&str],
602        name: &str,
603        js_function: F,
604        arg_count: u32,
605    ) -> Result<(), JsError> {
606        // todo namespace as slice?
607        let ns = self.get_namespace(namespace)?;
608
609        let func = functions::new_function_q(
610            self,
611            name,
612            move |ctx, this, args| {
613                QuickJsRuntimeAdapter::do_with(|rt| js_function(rt, ctx, this, args))
614            },
615            arg_count,
616        )?;
617        self.set_object_property(&ns, name, &func)?;
618        Ok(())
619    }
620
621    pub fn get_global(&self) -> Result<QuickJsValueAdapter, JsError> {
622        Ok(get_global_q(self))
623    }
624
625    pub fn get_namespace(&self, namespace: &[&str]) -> Result<QuickJsValueAdapter, JsError> {
626        objects::get_namespace_q(self, namespace, true)
627    }
628
629    pub fn invoke_function_on_object_by_name(
630        &self,
631        this_obj: &QuickJsValueAdapter,
632        method_name: &str,
633        args: &[QuickJsValueAdapter],
634    ) -> Result<QuickJsValueAdapter, JsError> {
635        functions::invoke_member_function_q(self, this_obj, method_name, args)
636    }
637
638    pub fn invoke_function(
639        &self,
640        this_obj: Option<&QuickJsValueAdapter>,
641        function_obj: &QuickJsValueAdapter,
642        args: &[&QuickJsValueAdapter],
643    ) -> Result<QuickJsValueAdapter, JsError> {
644        functions::call_function_q_ref_args(self, function_obj, args, this_obj)
645    }
646
647    pub fn create_function<
648        F: Fn(
649                &Self,
650                &QuickJsValueAdapter,
651                &[QuickJsValueAdapter],
652            ) -> Result<QuickJsValueAdapter, JsError>
653            + 'static,
654    >(
655        &self,
656        name: &str,
657        js_function: F,
658        arg_count: u32,
659    ) -> Result<QuickJsValueAdapter, JsError> {
660        functions::new_function_q(self, name, js_function, arg_count)
661    }
662
663    pub fn create_function_async<R, F>(
664        &self,
665        name: &str,
666        js_function: F,
667        arg_count: u32,
668    ) -> Result<QuickJsValueAdapter, JsError>
669    where
670        Self: Sized + 'static,
671        R: Future<Output = Result<JsValueFacade, JsError>> + Send + 'static,
672        F: Fn(JsValueFacade, Vec<JsValueFacade>) -> R + 'static,
673    {
674        //
675        self.create_function(
676            name,
677            move |realm, this, args| {
678                let this_fac = realm.to_js_value_facade(this)?;
679                let mut args_fac = vec![];
680                for arg in args {
681                    args_fac.push(realm.to_js_value_facade(arg)?);
682                }
683                let fut = js_function(this_fac, args_fac);
684                realm.create_resolving_promise_async(fut, |realm, pres| {
685                    //
686                    realm.from_js_value_facade(pres)
687                })
688            },
689            arg_count,
690        )
691    }
692
693    pub fn create_error(
694        &self,
695        name: &str,
696        message: &str,
697        stack: &str,
698    ) -> Result<QuickJsValueAdapter, JsError> {
699        unsafe { errors::new_error(self.context, name, message, stack) }
700    }
701
702    pub fn delete_object_property(
703        &self,
704        object: &QuickJsValueAdapter,
705        property_name: &str,
706    ) -> Result<(), JsError> {
707        // todo impl a real delete_prop
708        objects::set_property_q(self, object, property_name, &new_null_ref())
709    }
710
711    pub fn set_object_property(
712        &self,
713        object: &QuickJsValueAdapter,
714        property_name: &str,
715        property: &QuickJsValueAdapter,
716    ) -> Result<(), JsError> {
717        objects::set_property_q(self, object, property_name, property)
718    }
719
720    pub fn get_object_property(
721        &self,
722        object: &QuickJsValueAdapter,
723        property_name: &str,
724    ) -> Result<QuickJsValueAdapter, JsError> {
725        objects::get_property_q(self, object, property_name)
726    }
727
728    pub fn create_object(&self) -> Result<QuickJsValueAdapter, JsError> {
729        objects::create_object_q(self)
730    }
731
732    pub fn construct_object(
733        &self,
734        constructor: &QuickJsValueAdapter,
735        args: &[&QuickJsValueAdapter],
736    ) -> Result<QuickJsValueAdapter, JsError> {
737        // todo alter constructor method to accept slice
738        unsafe { construct_object(self.context, constructor, args) }
739    }
740
741    pub fn get_object_properties(
742        &self,
743        object: &QuickJsValueAdapter,
744    ) -> Result<Vec<String>, JsError> {
745        let props = objects::get_own_property_names_q(self, object)?;
746        let mut ret = vec![];
747        for x in 0..props.len() {
748            let prop = props.get_name(x)?;
749            ret.push(prop);
750        }
751        Ok(ret)
752    }
753
754    pub fn traverse_object<F, R>(
755        &self,
756        object: &QuickJsValueAdapter,
757        visitor: F,
758    ) -> Result<Vec<R>, JsError>
759    where
760        F: Fn(&str, &QuickJsValueAdapter) -> Result<R, JsError>,
761    {
762        objects::traverse_properties_q(self, object, visitor)
763    }
764
765    pub fn traverse_object_mut<F>(
766        &self,
767        object: &QuickJsValueAdapter,
768        visitor: F,
769    ) -> Result<(), JsError>
770    where
771        F: FnMut(&str, &QuickJsValueAdapter) -> Result<(), JsError>,
772    {
773        objects::traverse_properties_q_mut(self, object, visitor)
774    }
775
776    pub fn get_array_element(
777        &self,
778        array: &QuickJsValueAdapter,
779        index: u32,
780    ) -> Result<QuickJsValueAdapter, JsError> {
781        arrays::get_element_q(self, array, index)
782    }
783
784    /// push an element into an Array
785    pub fn push_array_element(
786        &self,
787        array: &QuickJsValueAdapter,
788        element: &QuickJsValueAdapter,
789    ) -> Result<u32, JsError> {
790        let push_func = self.get_object_property(array, "push")?;
791        let res = self.invoke_function(Some(array), &push_func, &[element])?;
792        Ok(res.to_i32() as u32)
793    }
794
795    pub fn set_array_element(
796        &self,
797        array: &QuickJsValueAdapter,
798        index: u32,
799        element: &QuickJsValueAdapter,
800    ) -> Result<(), JsError> {
801        arrays::set_element_q(self, array, index, element)
802    }
803
804    pub fn get_array_length(&self, array: &QuickJsValueAdapter) -> Result<u32, JsError> {
805        arrays::get_length_q(self, array)
806    }
807
808    pub fn create_array(&self) -> Result<QuickJsValueAdapter, JsError> {
809        arrays::create_array_q(self)
810    }
811
812    pub fn traverse_array<F, R>(
813        &self,
814        array: &QuickJsValueAdapter,
815        visitor: F,
816    ) -> Result<Vec<R>, JsError>
817    where
818        F: Fn(u32, &QuickJsValueAdapter) -> Result<R, JsError>,
819    {
820        // todo impl real traverse methods
821        let mut ret = vec![];
822        for x in 0..arrays::get_length_q(self, array)? {
823            let val = arrays::get_element_q(self, array, x)?;
824            ret.push(visitor(x, &val)?)
825        }
826        Ok(ret)
827    }
828
829    pub fn traverse_array_mut<F>(
830        &self,
831        array: &QuickJsValueAdapter,
832        mut visitor: F,
833    ) -> Result<(), JsError>
834    where
835        F: FnMut(u32, &QuickJsValueAdapter) -> Result<(), JsError>,
836    {
837        // todo impl real traverse methods
838        for x in 0..arrays::get_length_q(self, array)? {
839            let val = arrays::get_element_q(self, array, x)?;
840            visitor(x, &val)?;
841        }
842        Ok(())
843    }
844
845    pub fn create_null(&self) -> Result<QuickJsValueAdapter, JsError> {
846        Ok(crate::quickjs_utils::new_null_ref())
847    }
848
849    pub fn create_undefined(&self) -> Result<QuickJsValueAdapter, JsError> {
850        Ok(crate::quickjs_utils::new_undefined_ref())
851    }
852
853    pub fn create_i32(&self, val: i32) -> Result<QuickJsValueAdapter, JsError> {
854        Ok(from_i32(val))
855    }
856
857    pub fn create_string(&self, val: &str) -> Result<QuickJsValueAdapter, JsError> {
858        from_string_q(self, val)
859    }
860
861    pub fn create_boolean(&self, val: bool) -> Result<QuickJsValueAdapter, JsError> {
862        Ok(from_bool(val))
863    }
864
865    pub fn create_f64(&self, val: f64) -> Result<QuickJsValueAdapter, JsError> {
866        Ok(from_f64(val))
867    }
868
869    pub fn create_promise(&self) -> Result<QuickJsPromiseAdapter, JsError> {
870        crate::quickjs_utils::promises::new_promise_q(self)
871    }
872
873    pub fn add_promise_reactions(
874        &self,
875        promise: &QuickJsValueAdapter,
876        then: Option<QuickJsValueAdapter>,
877        catch: Option<QuickJsValueAdapter>,
878        finally: Option<QuickJsValueAdapter>,
879    ) -> Result<(), JsError> {
880        crate::quickjs_utils::promises::add_promise_reactions_q(self, promise, then, catch, finally)
881    }
882
883    pub fn cache_promise(&self, promise_ref: QuickJsPromiseAdapter) -> usize {
884        let map = &mut *self.promise_cache.borrow_mut();
885        map.insert(promise_ref)
886    }
887
888    pub fn consume_cached_promise(&self, id: usize) -> Option<QuickJsPromiseAdapter> {
889        let map = &mut *self.promise_cache.borrow_mut();
890        map.remove_opt(&id)
891    }
892
893    pub fn dispose_cached_object(&self, id: i32) {
894        let _ = self.consume_cached_obj(id);
895    }
896
897    pub fn with_cached_object<C, R>(&self, id: i32, consumer: C) -> R
898    where
899        C: FnOnce(&QuickJsValueAdapter) -> R,
900    {
901        self.with_cached_obj(id, |obj| consumer(&obj))
902    }
903
904    pub fn consume_cached_object(&self, id: i32) -> QuickJsValueAdapter {
905        self.consume_cached_obj(id)
906    }
907
908    pub fn is_instance_of(
909        &self,
910        object: &QuickJsValueAdapter,
911        constructor: &QuickJsValueAdapter,
912    ) -> bool {
913        objects::is_instance_of_q(self, object, constructor)
914    }
915
916    pub fn json_stringify(
917        &self,
918        object: &QuickJsValueAdapter,
919        opt_space: Option<&str>,
920    ) -> Result<String, JsError> {
921        let opt_space_jsvr = match opt_space {
922            None => None,
923            Some(s) => Some(self.create_string(s)?),
924        };
925        let res = json::stringify_q(self, object, opt_space_jsvr);
926        match res {
927            Ok(jsvr) => jsvr.to_string(),
928            Err(e) => Err(e),
929        }
930    }
931
932    pub fn json_parse(&self, json_string: &str) -> Result<QuickJsValueAdapter, JsError> {
933        json::parse_q(self, json_string)
934    }
935
936    pub fn create_typed_array_uint8(
937        &self,
938        buffer: Vec<u8>,
939    ) -> Result<QuickJsValueAdapter, JsError> {
940        new_uint8_array_q(self, buffer)
941    }
942
943    pub fn create_typed_array_uint8_copy(
944        &self,
945        buffer: &[u8],
946    ) -> Result<QuickJsValueAdapter, JsError> {
947        new_uint8_array_copy_q(self, buffer)
948    }
949
950    pub fn detach_typed_array_buffer(
951        &self,
952        array: &QuickJsValueAdapter,
953    ) -> Result<Vec<u8>, JsError> {
954        let abuf = get_array_buffer_q(self, array)?;
955        detach_array_buffer_buffer_q(self, &abuf)
956    }
957
958    pub fn copy_typed_array_buffer(&self, array: &QuickJsValueAdapter) -> Result<Vec<u8>, JsError> {
959        let abuf = get_array_buffer_q(self, array)?;
960        get_array_buffer_buffer_copy_q(self, &abuf)
961    }
962
963    pub fn get_proxy_instance_info(
964        &self,
965        obj: &QuickJsValueAdapter,
966    ) -> Result<(String, JsProxyInstanceId), JsError>
967    where
968        Self: Sized,
969    {
970        if let Some((p, i)) =
971            crate::reflection::get_proxy_instance_proxy_and_instance_id_q(self, obj)
972        {
973            Ok((p.get_class_name(), i))
974        } else {
975            Err(JsError::new_str("not a proxy instance"))
976        }
977    }
978
979    pub fn to_js_value_facade(
980        &self,
981        js_value: &QuickJsValueAdapter,
982    ) -> Result<JsValueFacade, JsError>
983    where
984        Self: Sized + 'static,
985    {
986        let res: JsValueFacade = match js_value.get_js_type() {
987            JsValueType::I32 => JsValueFacade::I32 {
988                val: js_value.to_i32(),
989            },
990            JsValueType::F64 => JsValueFacade::F64 {
991                val: js_value.to_f64(),
992            },
993            JsValueType::String => JsValueFacade::String {
994                val: DefaultAtom::from(js_value.to_string()?),
995            },
996            JsValueType::Boolean => JsValueFacade::Boolean {
997                val: js_value.to_bool(),
998            },
999            JsValueType::Object => {
1000                if js_value.is_typed_array() {
1001                    // todo TypedArray as JsValueType?
1002                    // 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
1003                    // you should be better of optimizing this in native methods
1004                    JsValueFacade::TypedArray {
1005                        buffer: self.copy_typed_array_buffer(js_value)?,
1006                        array_type: TypedArrayType::Uint8,
1007                    }
1008                } else {
1009                    JsValueFacade::JsObject {
1010                        cached_object: CachedJsObjectRef::new(self, js_value.clone()),
1011                    }
1012                }
1013            }
1014            JsValueType::Function => JsValueFacade::JsFunction {
1015                cached_function: CachedJsFunctionRef {
1016                    cached_object: CachedJsObjectRef::new(self, js_value.clone()),
1017                },
1018            },
1019            JsValueType::BigInt => {
1020                todo!();
1021            }
1022            JsValueType::Promise => JsValueFacade::JsPromise {
1023                cached_promise: CachedJsPromiseRef {
1024                    cached_object: CachedJsObjectRef::new(self, js_value.clone()),
1025                },
1026            },
1027            JsValueType::Date => {
1028                todo!();
1029            }
1030            JsValueType::Null => JsValueFacade::Null,
1031            JsValueType::Undefined => JsValueFacade::Undefined,
1032
1033            JsValueType::Array => JsValueFacade::JsArray {
1034                cached_array: CachedJsArrayRef {
1035                    cached_object: CachedJsObjectRef::new(self, js_value.clone()),
1036                },
1037            },
1038            JsValueType::Error => {
1039                let name = self.get_object_property(js_value, "name")?.to_string()?;
1040                let message = self.get_object_property(js_value, "message")?.to_string()?;
1041                let stack = self.get_object_property(js_value, "stack")?.to_string()?;
1042
1043                #[cfg(feature = "typescript")]
1044                let stack = crate::typescript::unmap_stack_trace(stack.as_str());
1045
1046                JsValueFacade::JsError {
1047                    val: JsError::new(name, message, stack),
1048                }
1049            }
1050        };
1051        Ok(res)
1052    }
1053
1054    /// convert a JSValueFacade into a JSValueAdapter
1055    /// you need this to move values into the worker thread from a different thread (JSValueAdapter cannot leave the worker thread)
1056    #[allow(clippy::wrong_self_convention)]
1057    pub fn from_js_value_facade(
1058        &self,
1059        value_facade: JsValueFacade,
1060    ) -> Result<QuickJsValueAdapter, JsError>
1061    where
1062        Self: Sized + 'static,
1063    {
1064        match value_facade {
1065            JsValueFacade::I32 { val } => self.create_i32(val),
1066            JsValueFacade::F64 { val } => self.create_f64(val),
1067            JsValueFacade::String { val } => self.create_string(&val),
1068            JsValueFacade::Boolean { val } => self.create_boolean(val),
1069            JsValueFacade::JsObject { cached_object } => {
1070                // todo check realm (else copy? or error?)
1071                self.with_cached_object(cached_object.id, |obj| Ok(obj.clone()))
1072            }
1073            JsValueFacade::JsPromise { cached_promise } => {
1074                // todo check realm (else copy? or error?)
1075                self.with_cached_object(cached_promise.cached_object.id, |obj| Ok(obj.clone()))
1076            }
1077            JsValueFacade::JsArray { cached_array } => {
1078                // todo check realm (else copy? or error?)
1079                self.with_cached_object(cached_array.cached_object.id, |obj| Ok(obj.clone()))
1080            }
1081            JsValueFacade::JsFunction { cached_function } => {
1082                // todo check realm (else copy? or error?)
1083                self.with_cached_object(cached_function.cached_object.id, |obj| Ok(obj.clone()))
1084            }
1085            JsValueFacade::Object { val } => {
1086                let obj = self.create_object()?;
1087                for entry in val {
1088                    let prop = self.from_js_value_facade(entry.1)?;
1089                    self.set_object_property(&obj, entry.0.as_str(), &prop)?;
1090                }
1091                Ok(obj)
1092            }
1093            JsValueFacade::Array { val } => {
1094                let obj = self.create_array()?;
1095                for (x, entry) in val.into_iter().enumerate() {
1096                    let prop = self.from_js_value_facade(entry)?;
1097                    self.set_array_element(&obj, x as u32, &prop)?;
1098                }
1099                Ok(obj)
1100            }
1101            JsValueFacade::Promise { producer } => {
1102                let producer = &mut *producer.lock("from_js_value_facade").unwrap();
1103                if producer.is_some() {
1104                    self.create_resolving_promise_async(producer.take().unwrap(), |realm, jsvf| {
1105                        realm.from_js_value_facade(jsvf)
1106                    })
1107                } else {
1108                    self.create_null()
1109                }
1110            }
1111            JsValueFacade::Function {
1112                name,
1113                arg_count,
1114                func,
1115            } => {
1116                //
1117
1118                self.create_function(
1119                    name.as_str(),
1120                    move |realm, _this, args| {
1121                        let mut esvf_args = vec![];
1122                        for arg in args {
1123                            esvf_args.push(realm.to_js_value_facade(arg)?);
1124                        }
1125                        let esvf_res: Result<JsValueFacade, JsError> = func(esvf_args.as_slice());
1126
1127                        match esvf_res {
1128                            //
1129                            Ok(jsvf) => realm.from_js_value_facade(jsvf),
1130                            Err(err) => Err(err),
1131                        }
1132                    },
1133                    arg_count,
1134                )
1135            }
1136            JsValueFacade::Null => self.create_null(),
1137            JsValueFacade::Undefined => self.create_undefined(),
1138            JsValueFacade::JsError { val } => {
1139                self.create_error(val.get_name(), val.get_message(), val.get_stack())
1140            }
1141            JsValueFacade::ProxyInstance {
1142                instance_id,
1143                namespace,
1144                class_name,
1145            } => self.instantiate_proxy_with_id(namespace, class_name, instance_id),
1146            JsValueFacade::TypedArray { buffer, array_type } => match array_type {
1147                TypedArrayType::Uint8 => self.create_typed_array_uint8(buffer),
1148            },
1149            JsValueFacade::JsonStr { json } => self.json_parse(json.as_str()),
1150            JsValueFacade::SerdeValue { value } => self.serde_value_to_value_adapter(value),
1151        }
1152    }
1153
1154    pub fn value_adapter_to_serde_value(
1155        &self,
1156        value_adapter: &QuickJsValueAdapter,
1157    ) -> Result<serde_json::Value, JsError> {
1158        match value_adapter.get_js_type() {
1159            JsValueType::I32 => Ok(Value::from(value_adapter.to_i32())),
1160            JsValueType::F64 => Ok(Value::from(value_adapter.to_f64())),
1161            JsValueType::String => Ok(Value::from(value_adapter.to_string()?)),
1162            JsValueType::Boolean => Ok(Value::from(value_adapter.to_bool())),
1163            JsValueType::Object => {
1164                let mut map: serde_json::Map<String, serde_json::Value> = serde_json::Map::new();
1165                self.traverse_object_mut(value_adapter, |k, v| {
1166                    map.insert(k.to_string(), self.value_adapter_to_serde_value(v)?);
1167                    Ok(())
1168                })?;
1169                let obj_val = serde_json::Value::Object(map);
1170                Ok(obj_val)
1171            }
1172            JsValueType::Array => {
1173                let mut arr: Vec<serde_json::Value> = vec![];
1174                self.traverse_array_mut(value_adapter, |_i, v| {
1175                    arr.push(self.value_adapter_to_serde_value(v)?);
1176                    Ok(())
1177                })?;
1178                let arr_val = serde_json::Value::Array(arr);
1179                Ok(arr_val)
1180            }
1181            JsValueType::Null => Ok(serde_json::Value::Null),
1182            JsValueType::Undefined => Ok(serde_json::Value::Null),
1183            JsValueType::Function => Ok(serde_json::Value::Null),
1184            JsValueType::BigInt => Ok(serde_json::Value::Null),
1185            JsValueType::Promise => Ok(serde_json::Value::Null),
1186            JsValueType::Date => Ok(serde_json::Value::Null),
1187            JsValueType::Error => Ok(serde_json::Value::Null),
1188        }
1189    }
1190
1191    pub fn serde_value_to_value_adapter(
1192        &self,
1193        value: Value,
1194    ) -> Result<QuickJsValueAdapter, JsError> {
1195        match value {
1196            Value::Null => self.create_null(),
1197            Value::Bool(b) => self.create_boolean(b),
1198            Value::Number(n) => {
1199                if n.is_i64() {
1200                    let i = n.as_i64().unwrap();
1201                    if i <= i32::MAX as i64 {
1202                        self.create_i32(i as i32)
1203                    } else {
1204                        self.create_f64(i as f64)
1205                    }
1206                } else if n.is_u64() {
1207                    let i = n.as_u64().unwrap();
1208                    if i <= i32::MAX as u64 {
1209                        self.create_i32(i as i32)
1210                    } else {
1211                        self.create_f64(i as f64)
1212                    }
1213                } else {
1214                    // f64
1215                    let i = n.as_f64().unwrap();
1216                    self.create_f64(i)
1217                }
1218            }
1219            Value::String(s) => self.create_string(s.as_str()),
1220            Value::Array(a) => {
1221                let arr = self.create_array()?;
1222                for (x, aval) in (0_u32..).zip(a) {
1223                    let entry = self.serde_value_to_value_adapter(aval)?;
1224                    self.set_array_element(&arr, x, &entry)?;
1225                }
1226                Ok(arr)
1227            }
1228            Value::Object(o) => {
1229                let obj = self.create_object()?;
1230                for oval in o {
1231                    let entry = self.serde_value_to_value_adapter(oval.1)?;
1232                    self.set_object_property(&obj, oval.0.as_str(), &entry)?;
1233                }
1234                Ok(obj)
1235            }
1236        }
1237    }
1238    /// create a new Promise with a Future which will run async and then resolve or reject the promise
1239    /// the mapper is used to convert the result of the future into a JSValueAdapter
1240    pub fn create_resolving_promise_async<P, R: Send + 'static, M>(
1241        &self,
1242        producer: P,
1243        mapper: M,
1244    ) -> Result<QuickJsValueAdapter, JsError>
1245    where
1246        P: Future<Output = Result<R, JsError>> + Send + 'static,
1247        M: FnOnce(&QuickJsRealmAdapter, R) -> Result<QuickJsValueAdapter, JsError> + Send + 'static,
1248        Self: Sized + 'static,
1249    {
1250        new_resolving_promise_async(self, producer, mapper)
1251    }
1252    /// create a new Promise with a FnOnce producer which will run async and then resolve or reject the promise
1253    /// the mapper is used to convert the result of the future into a JSValueAdapter
1254    ///
1255    pub fn create_resolving_promise<P, R: Send + 'static, M>(
1256        &self,
1257        producer: P,
1258        mapper: M,
1259    ) -> Result<QuickJsValueAdapter, JsError>
1260    where
1261        P: FnOnce() -> Result<R, JsError> + Send + 'static,
1262        M: FnOnce(&QuickJsRealmAdapter, R) -> Result<QuickJsValueAdapter, JsError> + Send + 'static,
1263        Self: Sized + 'static,
1264    {
1265        new_resolving_promise(self, producer, mapper)
1266    }
1267}
1268
1269#[cfg(test)]
1270pub mod tests {
1271    use crate::builder::QuickJsRuntimeBuilder;
1272    use crate::facades::tests::init_test_rt;
1273    use crate::jsutils::Script;
1274    use crate::quickjs_utils;
1275    use crate::quickjs_utils::primitives::to_i32;
1276    use crate::quickjs_utils::{functions, get_global_q, objects};
1277
1278    #[test]
1279    fn test_eval() {
1280        let rt = init_test_rt();
1281        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1282            let q_ctx = q_js_rt.get_main_realm();
1283            let res = q_ctx.eval(Script::new("test_eval.es", "(1 + 1);"));
1284
1285            match res {
1286                Ok(res) => {
1287                    log::info!("script ran ok: {:?}", res);
1288                    assert!(res.is_i32());
1289                    assert_eq!(to_i32(&res).ok().expect("conversion failed"), 2);
1290                }
1291                Err(e) => {
1292                    log::error!("script failed: {}", e);
1293                    panic!("script failed");
1294                }
1295            }
1296        });
1297    }
1298
1299    #[test]
1300    fn test_multi_ctx() {
1301        let rt = QuickJsRuntimeBuilder::new().build();
1302        rt.create_context("a").ok().expect("could not create ctx a");
1303        rt.create_context("b").ok().expect("could not create ctx b");
1304
1305        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1306            let ctx_a = q_js_rt.get_context("a");
1307            let ctx_b = q_js_rt.get_context("b");
1308            ctx_a
1309                .eval(Script::new("a.es", "this.a = 1"))
1310                .ok()
1311                .expect("script failed");
1312            ctx_b
1313                .eval(Script::new("a.es", "this.b = 1"))
1314                .ok()
1315                .expect("script failed");
1316            let v = ctx_a
1317                .eval(Script::new("a2.es", "this.a;"))
1318                .ok()
1319                .expect("script failed");
1320            assert!(v.is_i32());
1321            let v2 = ctx_b
1322                .eval(Script::new("b2.es", "this.a;"))
1323                .ok()
1324                .expect("script failed");
1325            assert!(v2.is_null_or_undefined());
1326            let v3 = ctx_a
1327                .eval(Script::new("a2.es", "this.b;"))
1328                .ok()
1329                .expect("script failed");
1330            assert!(v3.is_null_or_undefined());
1331            let v4 = ctx_b
1332                .eval(Script::new("b2.es", "this.b;"))
1333                .ok()
1334                .expect("script failed");
1335            assert!(v4.is_i32());
1336        });
1337        let _ = rt.drop_context("b");
1338
1339        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1340            q_js_rt.gc();
1341            let ctx_a = q_js_rt.get_context("a");
1342            let v = ctx_a
1343                .eval(Script::new("a2.es", "this.a;"))
1344                .ok()
1345                .expect("script failed");
1346            assert!(v.is_i32());
1347            q_js_rt.gc();
1348        });
1349
1350        rt.create_context("c")
1351            .ok()
1352            .expect("could not create context c");
1353
1354        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1355            let c_ctx = q_js_rt.get_context("c");
1356            let func = functions::new_function_q(
1357                c_ctx,
1358                "test",
1359                |_q_ctx, _this, _args| Ok(quickjs_utils::new_null_ref()),
1360                1,
1361            )
1362            .ok()
1363            .unwrap();
1364            let global = get_global_q(c_ctx);
1365            objects::set_property_q(c_ctx, &global, "test_func", &func)
1366                .ok()
1367                .expect("could not set prop");
1368            q_js_rt.gc();
1369        });
1370        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1371            q_js_rt.gc();
1372            let ctx_a = q_js_rt.get_context("a");
1373            let v = ctx_a
1374                .eval(Script::new("a2.es", "this.a;"))
1375                .ok()
1376                .expect("script failed");
1377            assert!(v.is_i32());
1378            q_js_rt.gc();
1379        });
1380        let _ = rt.drop_context("c");
1381        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1382            q_js_rt.gc();
1383            let ctx_a = q_js_rt.get_context("a");
1384            let v = ctx_a
1385                .eval(Script::new("a2.es", "this.a;"))
1386                .ok()
1387                .expect("script failed");
1388            assert!(v.is_i32());
1389            q_js_rt.gc();
1390        });
1391    }
1392}