quickjs_runtime/reflection/
mod.rs

1//! utils for implementing proxy classes which can be used to use rust structs from JS (define method/getters/setters/etc)
2
3use crate::jsutils::JsError;
4use crate::quickjs_utils;
5use crate::quickjs_utils::functions::new_native_function_q;
6use crate::quickjs_utils::objects::{get_property, set_property2_q};
7use crate::quickjs_utils::primitives::from_string;
8use crate::quickjs_utils::{atoms, errors, functions, objects, parse_args, primitives};
9use crate::quickjsrealmadapter::QuickJsRealmAdapter;
10use crate::quickjsruntimeadapter::QuickJsRuntimeAdapter;
11use crate::quickjsvalueadapter::QuickJsValueAdapter;
12use libquickjs_sys as q;
13use log::trace;
14use rand::{thread_rng, Rng};
15use std::cell::RefCell;
16use std::collections::HashMap;
17use std::os::raw::{c_char, c_void};
18use std::rc::Rc;
19
20pub type JsProxyInstanceId = usize;
21
22pub mod eventtarget;
23
24pub type ProxyConstructor = dyn Fn(
25        &QuickJsRuntimeAdapter,
26        &QuickJsRealmAdapter,
27        usize,
28        &[QuickJsValueAdapter],
29    ) -> Result<(), JsError>
30    + 'static;
31pub type ProxyFinalizer = dyn Fn(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter, usize) + 'static;
32pub type ProxyMethod = dyn Fn(
33        &QuickJsRuntimeAdapter,
34        &QuickJsRealmAdapter,
35        &usize,
36        &[QuickJsValueAdapter],
37    ) -> Result<QuickJsValueAdapter, JsError>
38    + 'static;
39pub type ProxyNativeMethod = q::JSCFunction;
40pub type ProxyStaticMethod = dyn Fn(
41        &QuickJsRuntimeAdapter,
42        &QuickJsRealmAdapter,
43        &[QuickJsValueAdapter],
44    ) -> Result<QuickJsValueAdapter, JsError>
45    + 'static;
46pub type ProxyStaticNativeMethod = q::JSCFunction;
47pub type ProxyStaticGetter = dyn Fn(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> Result<QuickJsValueAdapter, JsError>
48    + 'static;
49pub type ProxyStaticSetter = dyn Fn(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter, QuickJsValueAdapter) -> Result<(), JsError>
50    + 'static;
51pub type ProxyStaticCatchAllGetter = dyn Fn(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter, &str) -> Result<QuickJsValueAdapter, JsError>
52    + 'static;
53pub type ProxyStaticCatchAllSetter = dyn Fn(
54        &QuickJsRuntimeAdapter,
55        &QuickJsRealmAdapter,
56        &str,
57        QuickJsValueAdapter,
58    ) -> Result<(), JsError>
59    + 'static;
60pub type ProxyGetter = dyn Fn(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter, &usize) -> Result<QuickJsValueAdapter, JsError>
61    + 'static;
62pub type ProxyCatchAllGetter = dyn Fn(
63        &QuickJsRuntimeAdapter,
64        &QuickJsRealmAdapter,
65        &usize,
66        &str,
67    ) -> Result<QuickJsValueAdapter, JsError>
68    + 'static;
69pub type ProxySetter = dyn Fn(
70        &QuickJsRuntimeAdapter,
71        &QuickJsRealmAdapter,
72        &usize,
73        QuickJsValueAdapter,
74    ) -> Result<(), JsError>
75    + 'static;
76pub type ProxyCatchAllSetter = dyn Fn(
77        &QuickJsRuntimeAdapter,
78        &QuickJsRealmAdapter,
79        &usize,
80        &str,
81        QuickJsValueAdapter,
82    ) -> Result<(), JsError>
83    + 'static;
84
85static CNAME: &str = "ProxyInstanceClass\0";
86static SCNAME: &str = "ProxyStaticClass\0";
87
88thread_local! {
89
90    #[cfg(feature = "quickjs-ng")]
91    static PROXY_STATIC_EXOTIC: RefCell<q::JSClassExoticMethods> = RefCell::new(q::JSClassExoticMethods {
92        get_own_property: None,
93        get_own_property_names: None,
94        delete_property: None,
95        define_own_property: None,
96        has_property: Some(proxy_static_has_prop),
97        get_property: Some(proxy_static_get_prop),
98        set_property: Some(proxy_static_set_prop),
99    });
100    #[cfg(feature = "bellard")]
101    static PROXY_STATIC_EXOTIC: RefCell<q::JSClassExoticMethods> = RefCell::new(q::JSClassExoticMethods {
102        get_own_property: None,
103        get_own_property_names: None,
104        delete_property: None,
105        define_own_property: None,
106        has_property: Some(proxy_static_has_prop),
107        get_property: Some(proxy_static_get_prop),
108        set_property: Some(proxy_static_set_prop),
109        get_prototype: None,
110        is_extensible: None,
111        prevent_extensions: None,
112        set_prototype: None
113    });
114
115    #[cfg(feature = "quickjs-ng")]
116    static PROXY_INSTANCE_EXOTIC: RefCell<q::JSClassExoticMethods> = RefCell::new(q::JSClassExoticMethods {
117        get_own_property: None,
118        get_own_property_names: None,
119        delete_property: None,
120        define_own_property: None,
121        has_property: Some(proxy_instance_has_prop),
122        get_property: Some(proxy_instance_get_prop),
123        set_property: Some(proxy_instance_set_prop),
124    });
125
126    #[cfg(feature = "bellard")]
127    static PROXY_INSTANCE_EXOTIC: RefCell<q::JSClassExoticMethods> = RefCell::new(q::JSClassExoticMethods {
128        get_own_property: None,
129        get_own_property_names: None,
130        delete_property: None,
131        define_own_property: None,
132        has_property: Some(proxy_instance_has_prop),
133        get_property: Some(proxy_instance_get_prop),
134        set_property: Some(proxy_instance_set_prop),
135        get_prototype: None,
136        is_extensible: None,
137        prevent_extensions: None,
138        set_prototype: None
139    });
140
141    static PROXY_STATIC_CLASS_DEF: RefCell<q::JSClassDef> = {
142        PROXY_STATIC_EXOTIC.with(|e_rc|{
143            let exotic = &mut *e_rc.borrow_mut();
144            RefCell::new(q::JSClassDef {
145                class_name: SCNAME.as_ptr() as *const c_char,
146                finalizer: None,
147                gc_mark: None,
148                call: None,
149                exotic,
150            })
151        })
152    };
153
154    static PROXY_INSTANCE_CLASS_DEF: RefCell<q::JSClassDef> = {
155        PROXY_INSTANCE_EXOTIC.with(|e_rc|{
156            let exotic = &mut *e_rc.borrow_mut();
157            RefCell::new(q::JSClassDef {
158                class_name: CNAME.as_ptr() as *const c_char,
159                finalizer: Some(finalizer),
160                gc_mark: None,
161                call: None,
162                exotic,
163            })
164        })
165    };
166    pub static PROXY_STATIC_CLASS_ID: RefCell<u32> = {
167
168        let class_id: u32 =
169            QuickJsRuntimeAdapter::do_with(|q_js_rt| {
170                q_js_rt.new_class_id()
171            });
172
173        log::trace!("got static class id {}", class_id);
174
175        PROXY_STATIC_CLASS_DEF.with(|cd_rc| {
176            let class_def = &*cd_rc.borrow();
177            QuickJsRuntimeAdapter::do_with(|q_js_rt| {
178                let res = unsafe { q::JS_NewClass(q_js_rt.runtime, class_id, class_def) };
179                log::trace!("new static class res {}", res);
180                // todo res should be 0 for ok
181            });
182        });
183
184        RefCell::new(class_id)
185    };
186    pub static PROXY_INSTANCE_CLASS_ID: RefCell<u32> = {
187
188        let class_id: u32 =
189            QuickJsRuntimeAdapter::do_with(|q_js_rt| {
190                q_js_rt.new_class_id()
191            });
192        log::trace!("got class id {}", class_id);
193
194        PROXY_INSTANCE_CLASS_DEF.with(|cd_rc| {
195            let class_def = &*cd_rc.borrow();
196            QuickJsRuntimeAdapter::do_with(|q_js_rt| {
197                let res = unsafe { q::JS_NewClass(q_js_rt.runtime, class_id, class_def) };
198
199                log::trace!("new class res {}", res);
200                // todo res should be 0 for ok
201            });
202        });
203
204        RefCell::new(class_id)
205    };
206}
207
208const MAX_INSTANCE_NUM: usize = u32::MAX as usize;
209
210pub(crate) fn init_statics() {
211    PROXY_INSTANCE_CLASS_ID.with(|_rc| {
212        //
213    });
214}
215
216fn next_id(proxy: &Proxy) -> usize {
217    let mappings = &*proxy.proxy_instance_id_mappings.borrow();
218    if mappings.len() == MAX_INSTANCE_NUM {
219        panic!("too many instances"); // todo report ex
220    }
221    let mut rng = thread_rng();
222    let mut r: usize = rng.r#gen();
223    while mappings.contains_key(&r) {
224        r += 1;
225    }
226    r
227}
228
229/// The Proxy struct can be used to create a class in JavaScript who's methods can be implemented in rust
230/// # Example
231/// ```rust
232/// use quickjs_runtime::builder::QuickJsRuntimeBuilder;
233/// use quickjs_runtime::reflection::Proxy;
234/// use quickjs_runtime::quickjsrealmadapter::QuickJsRealmAdapter;
235/// use quickjs_runtime::quickjsvalueadapter::QuickJsValueAdapter;
236/// use std::cell::RefCell;
237/// use std::collections::HashMap;
238/// use quickjs_runtime::quickjs_utils::primitives;
239/// use quickjs_runtime::jsutils::Script;
240///
241/// struct MyFunkyStruct{
242///     name: String
243/// }
244///
245/// impl Drop for MyFunkyStruct {fn drop(&mut self) {
246///         println!("Funky drop: {}", self.name.as_str());
247///     }
248/// }
249///
250/// thread_local! {
251///    static INSTANCES: RefCell<HashMap<usize, MyFunkyStruct>> = RefCell::new(HashMap::new());
252/// }
253///
254/// //create a new EsRuntime
255/// let rt = QuickJsRuntimeBuilder::new().build();
256///
257/// // install our proxy class as com.hirofa.FunkyClass
258/// rt.exe_rt_task_in_event_loop(|q_js_rt| {
259///    let q_ctx = q_js_rt.get_main_realm();
260///    Proxy::new()
261///    .namespace(&["com", "hirofa"])
262///    .name("FunkyClass")
263///    // the constructor is called when a script does new com.hirofa.FunkyClass, the reflection utils
264///    // generate an instance_id which may be used to identify the instance
265///    .constructor(|rt, q_ctx: &QuickJsRealmAdapter, instance_id: usize, args: &[QuickJsValueAdapter]| {
266///        // we'll assume our script always constructs the Proxy with a single name argument
267///        let name = primitives::to_string_q(q_ctx, &args[0]).ok().expect("bad constructor! bad!");
268///        // create a new instance of our struct and store it in a map
269///        let instance = MyFunkyStruct{name};
270///        // store our struct in a thread_local map
271///        INSTANCES.with(move |rc| {
272///            let map = &mut *rc.borrow_mut();
273///            map.insert(instance_id, instance);
274///        });
275///        // return Ok, or Err if the constructor failed (e.g. wrong args were passed)
276///        Ok(())
277///     })
278///    // next we create a simple getName method, this will return a String
279///    .method("getName", |rt, q_ctx, instance_id, args| {
280///        INSTANCES.with(move |rc| {
281///            let map = & *rc.borrow();
282///            let instance = map.get(instance_id).unwrap();
283///            primitives::from_string_q(q_ctx, instance.name.as_str())
284///        })
285///    })
286///    // and lastly (but very important) implement a finalizer so our rust struct may be dropped
287///    .finalizer(|rt, q_ctx, instance_id| {
288///        INSTANCES.with(move |rc| {
289///            let map = &mut *rc.borrow_mut();
290///            map.remove(&instance_id);
291///        });
292///     })
293///     // install the Proxy in the context
294///    .install(q_ctx, true).expect("proxy install failed");      
295/// });
296///
297/// match rt.eval_sync(None, Script::new("test_proxy.es",
298///     "{let inst = new com.hirofa.FunkyClass('FooBar'); let name = inst.getName(); inst = null; name;}"
299/// )) {
300///     Ok(name_esvf) => {
301///         // assert correct getName result
302///         assert_eq!(name_esvf.get_str(), "FooBar");
303///         let i_ct = INSTANCES.with(|rc| rc.borrow().len());
304///         // assert instance was finalized
305///         assert_eq!(i_ct, 0);
306///     }
307///     Err(e) => {
308///         panic!("script failed: {}", e);
309///     }
310/// }
311/// rt.gc_sync();
312///
313/// ```
314pub struct Proxy {
315    name: Option<String>,
316    namespace: Option<Vec<String>>,
317    pub(crate) constructor: Option<Box<ProxyConstructor>>,
318    finalizers: Vec<Box<ProxyFinalizer>>,
319    methods: HashMap<String, Box<ProxyMethod>>,
320    native_methods: HashMap<String, ProxyNativeMethod>,
321    static_methods: HashMap<String, Box<ProxyStaticMethod>>,
322    static_native_methods: HashMap<String, ProxyStaticNativeMethod>,
323    static_getters_setters: HashMap<String, (Box<ProxyStaticGetter>, Box<ProxyStaticSetter>)>,
324    getters_setters: HashMap<String, (Box<ProxyGetter>, Box<ProxySetter>)>,
325    catch_all: Option<(Box<ProxyCatchAllGetter>, Box<ProxyCatchAllSetter>)>,
326    static_catch_all: Option<(
327        Box<ProxyStaticCatchAllGetter>,
328        Box<ProxyStaticCatchAllSetter>,
329    )>,
330    is_event_target: bool,
331    is_static_event_target: bool,
332    pub(crate) proxy_instance_id_mappings: RefCell<HashMap<usize, Box<ProxyInstanceInfo>>>,
333}
334
335impl Default for crate::reflection::Proxy {
336    fn default() -> Self {
337        Self::new()
338    }
339}
340
341/// get a proxy by class_name (namespace.ClassName)
342pub fn get_proxy(q_ctx: &QuickJsRealmAdapter, class_name: &str) -> Option<Rc<Proxy>> {
343    let registry = &*q_ctx.proxy_registry.borrow();
344    registry.get(class_name).cloned()
345}
346
347impl Proxy {
348    #[allow(dead_code)]
349    pub fn new() -> Self {
350        Proxy {
351            name: None,
352            namespace: None,
353            constructor: None,
354            finalizers: Default::default(),
355            methods: Default::default(),
356            native_methods: Default::default(),
357            static_methods: Default::default(),
358            static_native_methods: Default::default(),
359            static_getters_setters: Default::default(),
360            getters_setters: Default::default(),
361            catch_all: None,
362            static_catch_all: None,
363            is_event_target: false,
364            is_static_event_target: false,
365            proxy_instance_id_mappings: RefCell::new(Default::default()),
366        }
367    }
368
369    /// set the name of the proxy class
370    /// this will indicate how to construct the class from script
371    pub fn name(mut self, name: &str) -> Self {
372        self.name = Some(name.to_string());
373        self
374    }
375    /// set the namespace of the proxy class
376    /// # Example
377    /// ```
378    /// use quickjs_runtime::reflection::Proxy;
379    /// Proxy::new().namespace(&["com", "hirofa"]).name("SomeClass");
380    /// ```
381    /// means from script you can access the class by
382    /// ```javascript
383    /// let instance = new com.hirofa.SomeClass();
384    /// ```
385    pub fn namespace(mut self, namespace: &[&str]) -> Self {
386        if namespace.is_empty() {
387            self.namespace = None;
388        } else {
389            self.namespace = Some(namespace.iter().map(|s| s.to_string()).collect());
390        }
391        self
392    }
393    /// get the canonical classname of a Proxy
394    /// # example
395    /// ```
396    /// use quickjs_runtime::reflection::Proxy;
397    /// Proxy::new().namespace(&["com", "hirofa"]).name("SomeClass");
398    /// ```
399    /// will result in a class_name of "com.hirofa.SomeClass"
400    pub fn get_class_name(&self) -> String {
401        let cn = if let Some(n) = self.name.as_ref() {
402            n.as_str()
403        } else {
404            "__nameless_class__"
405        };
406        if self.namespace.is_some() {
407            format!("{}.{}", self.namespace.as_ref().unwrap().join("."), cn)
408        } else {
409            cn.to_string()
410        }
411    }
412    /// add a constructor for the Proxy class
413    /// this will enable a script to create a new instance of a Proxy class
414    /// if omitted the Proxy class will not be constructable from script
415    pub fn constructor<C>(mut self, constructor: C) -> Self
416    where
417        C: Fn(
418                &QuickJsRuntimeAdapter,
419                &QuickJsRealmAdapter,
420                usize,
421                &[QuickJsValueAdapter],
422            ) -> Result<(), JsError>
423            + 'static,
424    {
425        self.constructor = Some(Box::new(constructor));
426        self
427    }
428    /// add a finalizer for the Proxy class
429    /// this will be called when an instance of the Proxy class is dropped or garbage collected
430    pub fn finalizer<C>(mut self, finalizer: C) -> Self
431    where
432        C: Fn(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter, usize) + 'static,
433    {
434        self.finalizers.push(Box::new(finalizer));
435        self
436    }
437    /// add a method to the Proxy class, this method will be available as a member of instances of the Proxy class
438    pub fn method<M>(mut self, name: &str, method: M) -> Self
439    where
440        M: Fn(
441                &QuickJsRuntimeAdapter,
442                &QuickJsRealmAdapter,
443                &usize,
444                &[QuickJsValueAdapter],
445            ) -> Result<QuickJsValueAdapter, JsError>
446            + 'static,
447    {
448        self.methods.insert(name.to_string(), Box::new(method));
449        self
450    }
451    /// add a method to the Proxy class, this method will be available as a member of instances of the Proxy class
452    pub fn native_method(mut self, name: &str, method: ProxyNativeMethod) -> Self {
453        self.native_methods.insert(name.to_string(), method);
454        self
455    }
456    /// add a static method to the Proxy class, this method will be available as a member of the Proxy class itself
457    pub fn static_method<M>(mut self, name: &str, method: M) -> Self
458    where
459        M: Fn(
460                &QuickJsRuntimeAdapter,
461                &QuickJsRealmAdapter,
462                &[QuickJsValueAdapter],
463            ) -> Result<QuickJsValueAdapter, JsError>
464            + 'static,
465    {
466        self.static_methods
467            .insert(name.to_string(), Box::new(method));
468        self
469    }
470    /// add a static method to the Proxy class, this method will be available as a member of the Proxy class itself
471    pub fn static_native_method(mut self, name: &str, method: ProxyStaticNativeMethod) -> Self {
472        self.static_native_methods.insert(name.to_string(), method);
473        self
474    }
475
476    /// add a static getter and setter to the Proxy class
477    pub fn static_getter_setter<G, S>(mut self, name: &str, getter: G, setter: S) -> Self
478    where
479        G: Fn(&QuickJsRuntimeAdapter, &QuickJsRealmAdapter) -> Result<QuickJsValueAdapter, JsError>
480            + 'static,
481        S: Fn(
482                &QuickJsRuntimeAdapter,
483                &QuickJsRealmAdapter,
484                QuickJsValueAdapter,
485            ) -> Result<(), JsError>
486            + 'static,
487    {
488        self.static_getters_setters
489            .insert(name.to_string(), (Box::new(getter), Box::new(setter)));
490        self
491    }
492    /// add a static getter and setter to the Proxy class
493    pub fn static_catch_all_getter_setter<G, S>(mut self, getter: G, setter: S) -> Self
494    where
495        G: Fn(
496                &QuickJsRuntimeAdapter,
497                &QuickJsRealmAdapter,
498                &str,
499            ) -> Result<QuickJsValueAdapter, JsError>
500            + 'static,
501        S: Fn(
502                &QuickJsRuntimeAdapter,
503                &QuickJsRealmAdapter,
504                &str,
505                QuickJsValueAdapter,
506            ) -> Result<(), JsError>
507            + 'static,
508    {
509        self.static_catch_all = Some((Box::new(getter), Box::new(setter)));
510        self
511    }
512    /// add a getter and setter to the Proxy class, these will be available as a member of an instance of this Proxy class
513    pub fn getter_setter<G, S>(mut self, name: &str, getter: G, setter: S) -> Self
514    where
515        G: Fn(
516                &QuickJsRuntimeAdapter,
517                &QuickJsRealmAdapter,
518                &usize,
519            ) -> Result<QuickJsValueAdapter, JsError>
520            + 'static,
521        S: Fn(
522                &QuickJsRuntimeAdapter,
523                &QuickJsRealmAdapter,
524                &usize,
525                QuickJsValueAdapter,
526            ) -> Result<(), JsError>
527            + 'static,
528    {
529        self.getters_setters
530            .insert(name.to_string(), (Box::new(getter), Box::new(setter)));
531        self
532    }
533    /// add a getter and setter to the Proxy class, these will be available as a member of an instance of this Proxy class
534    pub fn getter<G>(self, name: &str, getter: G) -> Self
535    where
536        G: Fn(
537                &QuickJsRuntimeAdapter,
538                &QuickJsRealmAdapter,
539                &usize,
540            ) -> Result<QuickJsValueAdapter, JsError>
541            + 'static,
542    {
543        self.getter_setter(name, getter, |_rt, _realm, _id, _val| Ok(()))
544    }
545    /// add a catchall getter and setter to the Proxy class, these will be used for properties which are not specifically defined as getter, setter or method in this Proxy
546    pub fn catch_all_getter_setter<G, S>(mut self, getter: G, setter: S) -> Self
547    where
548        G: Fn(
549                &QuickJsRuntimeAdapter,
550                &QuickJsRealmAdapter,
551                &usize,
552                &str,
553            ) -> Result<QuickJsValueAdapter, JsError>
554            + 'static,
555        S: Fn(
556                &QuickJsRuntimeAdapter,
557                &QuickJsRealmAdapter,
558                &usize,
559                &str,
560                QuickJsValueAdapter,
561            ) -> Result<(), JsError>
562            + 'static,
563    {
564        self.catch_all = Some((Box::new(getter), Box::new(setter)));
565
566        self
567    }
568    /// indicate the Proxy class should implement the EventTarget interface, this will result in the addEventListener, removeEventListener and dispatchEvent methods to be available on instances of the Proxy class
569    pub fn event_target(mut self) -> Self {
570        self.is_event_target = true;
571        self
572    }
573    /// indicate the Proxy class should implement the EventTarget interface, this will result in the addEventListener, removeEventListener and dispatchEvent methods to be available
574    pub fn static_event_target(mut self) -> Self {
575        self.is_static_event_target = true;
576        self
577    }
578    /// install the Proxy class in a QuickJsContext, this is always needed as a final step to actually make the Proxy class work
579    pub fn install(
580        mut self,
581        q_ctx: &QuickJsRealmAdapter,
582        add_variable_to_global: bool,
583    ) -> Result<QuickJsValueAdapter, JsError> {
584        if self.name.is_none() {
585            return Err(JsError::new_str("Proxy needs a name"));
586        }
587
588        let prim_cn = self.get_class_name();
589        let prim_cn2 = prim_cn.clone();
590
591        // todo turn these into native methods
592        self = self.method("Symbol.toPrimitive", move |_rt, q_ctx, id, _args| {
593            let prim = primitives::from_string_q(
594                q_ctx,
595                format!("Proxy::instance({id})::{prim_cn}").as_str(),
596            )?;
597            Ok(prim)
598        });
599        let prim_cn = self.get_class_name();
600        self = self.static_method("Symbol.hasInstance", move |_rt, realm, args| {
601            if args.len() == 1 {
602                let instance = &args[0];
603                if instance.is_proxy_instance() {
604                    let info = realm.get_proxy_instance_info(instance)?;
605                    if info.0.eq(prim_cn2.as_str()) {
606                        return realm.create_boolean(true);
607                    }
608                }
609            }
610            realm.create_boolean(false)
611        });
612        self = self.static_method("Symbol.toPrimitive", move |_rt, q_ctx, _args| {
613            let prim = primitives::from_string_q(q_ctx, format!("Proxy::{prim_cn}").as_str())?;
614            Ok(prim)
615        });
616
617        let ret = self.install_class_prop(q_ctx, add_variable_to_global)?;
618        eventtarget::impl_event_target(self).install_move_to_registry(q_ctx);
619
620        Ok(ret)
621    }
622
623    fn install_move_to_registry(self, q_ctx: &QuickJsRealmAdapter) {
624        let proxy = self;
625        let reg_map = &mut *q_ctx.proxy_registry.borrow_mut();
626        reg_map.insert(proxy.get_class_name(), Rc::new(proxy));
627    }
628    fn install_class_prop(
629        &mut self,
630        q_ctx: &QuickJsRealmAdapter,
631        add_variable_to_global: bool,
632    ) -> Result<QuickJsValueAdapter, JsError> {
633        // this creates a constructor function, adds it to the global scope and then makes an instance of the static_proxy_class its prototype so we can add static_getters_setters and static_methods
634
635        log::trace!("reflection::Proxy::install_class_prop / 1");
636
637        let static_class_id = PROXY_STATIC_CLASS_ID.with(|rc| *rc.borrow());
638
639        log::trace!("reflection::Proxy::install_class_prop / 2");
640
641        let constructor_ref = new_native_function_q(
642            q_ctx,
643            self.name.as_ref().unwrap().as_str(),
644            Some(constructor),
645            1,
646            true,
647        )?;
648
649        log::trace!("reflection::Proxy::install_class_prop / 3");
650
651        let class_val: q::JSValue =
652            unsafe { q::JS_NewObjectClass(q_ctx.context, static_class_id as i32) };
653
654        log::trace!("reflection::Proxy::install_class_prop / 4");
655
656        let class_val_ref = QuickJsValueAdapter::new(
657            q_ctx.context,
658            class_val,
659            false,
660            true,
661            "reflection::Proxy::install_class_prop class_val",
662        );
663
664        #[cfg(feature = "bellard")]
665        assert_eq!(1, class_val_ref.get_ref_count());
666
667        log::trace!("reflection::Proxy::install_class_prop / 5");
668
669        if class_val_ref.is_exception() {
670            return if let Some(e) = unsafe { QuickJsRealmAdapter::get_exception(q_ctx.context) } {
671                Err(e)
672            } else {
673                Err(JsError::new_string(format!(
674                    "could not create class:{}",
675                    self.get_class_name()
676                )))
677            };
678        }
679
680        log::trace!("reflection::Proxy::install_class_prop / 6");
681
682        unsafe {
683            let res = q::JS_SetPrototype(
684                q_ctx.context,
685                *constructor_ref.borrow_value(),
686                *class_val_ref.borrow_value(),
687            );
688            if res < 0 {
689                return if let Some(err) = QuickJsRealmAdapter::get_exception(q_ctx.context) {
690                    Err(err)
691                } else {
692                    Err(JsError::new_str("could not set class proto"))
693                };
694            }
695        }
696
697        #[cfg(feature = "bellard")]
698        assert_eq!(2, class_val_ref.get_ref_count());
699
700        log::trace!("reflection::Proxy::install_class_prop / 7");
701
702        objects::set_property2_q(
703            q_ctx,
704            &constructor_ref,
705            "name",
706            &primitives::from_string_q(q_ctx, &self.get_class_name())?,
707            0,
708        )?;
709
710        // todo impl namespace here
711        if add_variable_to_global {
712            log::trace!("reflection::Proxy::install_class_prop / 8");
713            let ns = if let Some(namespace) = &self.namespace {
714                let ns_str = namespace.iter().map(|s| s.as_str()).collect::<Vec<&str>>();
715                objects::get_namespace_q(q_ctx, ns_str.as_slice(), true)?
716            } else {
717                quickjs_utils::get_global_q(q_ctx)
718            };
719
720            log::trace!("reflection::Proxy::install_class_prop / 9");
721
722            objects::set_property2_q(
723                q_ctx,
724                &ns,
725                self.name.as_ref().unwrap().as_str(),
726                &constructor_ref,
727                0,
728            )?;
729        }
730        log::trace!("reflection::Proxy::install_class_prop / 10");
731
732        let proxy_constructor_refs = &mut *q_ctx.proxy_constructor_refs.borrow_mut();
733        proxy_constructor_refs.insert(self.get_class_name(), constructor_ref.clone());
734
735        log::trace!("install_class_prop done");
736
737        Ok(constructor_ref)
738    }
739}
740
741pub fn get_proxy_instance_proxy_and_instance_id_q(
742    q_ctx: &QuickJsRealmAdapter,
743    obj: &QuickJsValueAdapter,
744) -> Option<(Rc<Proxy>, usize)> {
745    if !is_proxy_instance_q(q_ctx, obj) {
746        None
747    } else {
748        let info = get_proxy_instance_info(obj.borrow_value());
749        let cn = info.class_name.as_str();
750        let registry = &*q_ctx.proxy_registry.borrow();
751        registry.get(cn).cloned().map(|proxy| (proxy, info.id))
752    }
753}
754
755pub fn get_proxy_instance_id_q(
756    q_ctx: &QuickJsRealmAdapter,
757    obj: &QuickJsValueAdapter,
758) -> Option<usize> {
759    if !is_proxy_instance_q(q_ctx, obj) {
760        None
761    } else {
762        let info = get_proxy_instance_info(obj.borrow_value());
763        Some(info.id)
764    }
765}
766
767/// Get the instance id of a proxy instance
768/// # Safety
769/// please make sure context is still valid
770pub unsafe fn get_proxy_instance_id(
771    ctx: *mut libquickjs_sys::JSContext,
772    obj: &QuickJsValueAdapter,
773) -> Option<usize> {
774    if !is_proxy_instance(ctx, obj) {
775        None
776    } else {
777        let info = get_proxy_instance_info(obj.borrow_value());
778        Some(info.id)
779    }
780}
781
782pub fn is_proxy_instance_q(q_ctx: &QuickJsRealmAdapter, obj: &QuickJsValueAdapter) -> bool {
783    unsafe { is_proxy_instance(q_ctx.context, obj) }
784}
785
786/// check if an object is an instance of a Proxy class
787/// # Safety
788/// please make sure context is still valid
789pub unsafe fn is_proxy_instance(ctx: *mut q::JSContext, obj: &QuickJsValueAdapter) -> bool {
790    if !obj.is_object() {
791        false
792    } else {
793        // workaround for instanceof not yet working
794        let prop_res = get_property(ctx, obj, "__proxy__");
795        if let Ok(prop) = prop_res {
796            if prop.is_bool() && prop.to_bool() {
797                return true;
798            }
799        }
800
801        let class_id = PROXY_INSTANCE_CLASS_ID.with(|rc| *rc.borrow());
802        let proxy_class_proto: q::JSValue = q::JS_GetClassProto(ctx, class_id);
803        //let proto_ref: JSValueRef = JSValueRef::new(ctx, proxy_class_proto, false, false, "proxy_class_proto");
804
805        let proxy_class_proto_obj = q::JS_GetPrototype(ctx, proxy_class_proto);
806        let res = q::JS_IsInstanceOf(ctx, *obj.borrow_value(), proxy_class_proto_obj);
807
808        if res == -1 {
809            // log err
810            if let Some(ex) = QuickJsRealmAdapter::get_exception(ctx) {
811                log::error!("is_proxy_instance failed: {}", ex);
812            } else {
813                log::error!("is_proxy_instance failed");
814            }
815        }
816
817        res > 0
818    }
819}
820
821pub fn new_instance2(
822    proxy: &Proxy,
823    q_ctx: &QuickJsRealmAdapter,
824) -> Result<(usize, QuickJsValueAdapter), JsError> {
825    let instance_id = next_id(proxy);
826    Ok((instance_id, new_instance3(proxy, instance_id, q_ctx)?))
827}
828
829pub(crate) fn new_instance3(
830    proxy: &Proxy,
831    instance_id: usize,
832    q_ctx: &QuickJsRealmAdapter,
833) -> Result<QuickJsValueAdapter, JsError> {
834    let ctx = q_ctx.context;
835    let class_id = PROXY_INSTANCE_CLASS_ID.with(|rc| *rc.borrow());
836
837    let class_val: q::JSValue = unsafe { q::JS_NewObjectClass(ctx, class_id as i32) };
838
839    let class_name = proxy.get_class_name();
840
841    trace!("creating new instance {} of {}", instance_id, class_name);
842
843    let class_val_ref = QuickJsValueAdapter::new(
844        q_ctx.context,
845        class_val,
846        false,
847        true,
848        format!("reflection::Proxy; cn={class_name}").as_str(),
849    );
850
851    if class_val_ref.is_exception() {
852        return if let Some(e) = q_ctx.get_exception_ctx() {
853            Err(JsError::new_string(format!(
854                "could not create class:{class_name} due to: {e}"
855            )))
856        } else {
857            Err(JsError::new_string(format!(
858                "could not create class:{class_name}"
859            )))
860        };
861    }
862
863    let mappings = &mut *proxy.proxy_instance_id_mappings.borrow_mut();
864    assert!(!mappings.contains_key(&instance_id));
865
866    let mut bx = Box::new(ProxyInstanceInfo {
867        id: instance_id,
868        class_name: proxy.get_class_name(),
869        context_id: q_ctx.id.clone(),
870    });
871
872    let ibp: &mut ProxyInstanceInfo = &mut bx;
873    let info_ptr = ibp as *mut _ as *mut c_void;
874
875    mappings.insert(instance_id, bx);
876    unsafe { q::JS_SetOpaque(*class_val_ref.borrow_value(), info_ptr) };
877
878    // todo this is a workaround.. i need to set a prototype for classes using JS_setClassProto per context on init..
879    set_property2_q(
880        q_ctx,
881        &class_val_ref,
882        "__proxy__",
883        &primitives::from_bool(true),
884        0,
885    )?;
886
887    let proxy_constructor_refs = &*q_ctx.proxy_constructor_refs.borrow();
888
889    let constructor = proxy_constructor_refs
890        .get(&class_name)
891        .expect("proxy was not installed properly");
892
893    set_property2_q(q_ctx, &class_val_ref, "constructor", constructor, 0)?;
894
895    Ok(class_val_ref)
896}
897
898pub fn new_instance(
899    class_name: &str,
900    q_ctx: &QuickJsRealmAdapter,
901) -> Result<(usize, QuickJsValueAdapter), JsError> {
902    // todo
903
904    let registry = &*q_ctx.proxy_registry.borrow();
905
906    if let Some(proxy) = registry.get(class_name) {
907        // construct
908
909        new_instance2(proxy, q_ctx)
910    } else {
911        Err(JsError::new_str("no such proxy"))
912    }
913}
914
915#[allow(dead_code)]
916unsafe extern "C" fn constructor(
917    context: *mut q::JSContext,
918    this_val: q::JSValue,
919    argc: ::std::os::raw::c_int,
920    argv: *mut q::JSValue,
921) -> q::JSValue {
922    log::trace!("constructor called, this_tag={}", this_val.tag);
923
924    // this is the function we created earlier (the constructor)
925    // so classname = this.name;
926    let this_ref = QuickJsValueAdapter::new(
927        context,
928        this_val,
929        false,
930        false,
931        "reflection::constructor this_val",
932    );
933    QuickJsRuntimeAdapter::do_with(|q_js_rt| {
934        let name_ref = objects::get_property(context, &this_ref, "name").expect("name get failed");
935        let class_name =
936            functions::call_to_string(context, &name_ref).expect("name.toString failed");
937
938        let q_ctx = q_js_rt.get_quickjs_context(context);
939
940        let registry = &*q_ctx.proxy_registry.borrow();
941        if let Some(proxy) = registry.get(&class_name) {
942            if let Some(constructor) = &proxy.constructor {
943                // construct
944
945                let args_vec = parse_args(context, argc, argv);
946                let instance_id = next_id(proxy);
947                let constructor_res = constructor(q_js_rt, q_ctx, instance_id, &args_vec);
948
949                match constructor_res {
950                    Ok(()) => {
951                        let instance_ref_res = new_instance3(proxy, instance_id, q_ctx);
952
953                        match instance_ref_res {
954                            Ok(instance_ref) => instance_ref.clone_value_incr_rc(),
955
956                            Err(e) => q_ctx.report_ex(
957                                format!(
958                                    "could not create proxy instance for {class_name} due to {e}"
959                                )
960                                .as_str(),
961                            ),
962                        }
963                    }
964                    Err(es_err) => q_ctx.report_ex(
965                        format!("constructor for {class_name} failed with {es_err}").as_str(),
966                    ),
967                }
968            } else {
969                q_ctx.report_ex("not a constructor")
970            }
971        } else {
972            q_ctx.report_ex("no such proxy")
973        }
974    })
975}
976
977pub(crate) struct ProxyInstanceInfo {
978    id: usize,
979    class_name: String, // todo, store all proxies in an autoidmap with a usize as key and store proxy_class_id here instead of string
980    context_id: String, // todo store all context ids in an autoidmap with a usize as key and store context_id here instead of string
981}
982
983fn get_proxy_instance_info(val: &q::JSValue) -> &ProxyInstanceInfo {
984    let class_id = PROXY_INSTANCE_CLASS_ID.with(|rc| *rc.borrow());
985    let info_ptr: *mut c_void = unsafe { q::JS_GetOpaque(*val, class_id) };
986    let info: &mut ProxyInstanceInfo = unsafe { &mut *(info_ptr as *mut ProxyInstanceInfo) };
987    info
988}
989
990#[allow(dead_code)]
991unsafe extern "C" fn finalizer(_rt: *mut q::JSRuntime, val: q::JSValue) {
992    log::trace!("finalizer called");
993
994    let info: &ProxyInstanceInfo = get_proxy_instance_info(&val);
995    trace!(
996        "finalize id:{} class:{} context:{}",
997        info.id,
998        info.class_name,
999        info.context_id
1000    );
1001
1002    QuickJsRuntimeAdapter::do_with(|q_js_rt| {
1003        let q_ctx = q_js_rt.get_context(&info.context_id);
1004        log::trace!("finalizer called, got q_ctx");
1005        let registry = &*q_ctx.proxy_registry.borrow();
1006        let proxy = registry.get(&info.class_name).unwrap();
1007
1008        for finalizer in &proxy.finalizers {
1009            log::trace!("calling Proxy's finalizer");
1010            finalizer(q_js_rt, q_ctx, info.id);
1011            log::trace!("after calling Proxy's finalizer");
1012        }
1013
1014        {
1015            log::trace!("reflection::finalizer: remove from INSTANCE_ID_MAPPINGS");
1016            let id_map = &mut *proxy.proxy_instance_id_mappings.borrow_mut();
1017            let _ = id_map.remove(&info.id).expect("no such id to finalize");
1018            log::trace!("reflection::finalizer: remove from INSTANCE_ID_MAPPINGS -> done");
1019        }
1020        log::trace!("reflection::finalizer: 2");
1021
1022        log::trace!("reflection::finalizer: 3, exit");
1023    });
1024}
1025
1026#[allow(dead_code)]
1027unsafe extern "C" fn proxy_static_get_prop(
1028    context: *mut q::JSContext,
1029    obj: q::JSValue,
1030    atom: q::JSAtom,
1031    receiver: q::JSValue,
1032) -> q::JSValue {
1033    // static proxy class, not an instance
1034    trace!("proxy_static_get_prop");
1035
1036    let _obj_ref = QuickJsValueAdapter::new(
1037        context,
1038        obj,
1039        false,
1040        false,
1041        "reflection::proxy_static_get_prop obj",
1042    );
1043    let receiver_ref = QuickJsValueAdapter::new(
1044        context,
1045        receiver,
1046        false,
1047        false,
1048        "reflection::proxy_static_get_prop receiver",
1049    );
1050
1051    QuickJsRuntimeAdapter::do_with(|q_js_rt| {
1052        let q_ctx = q_js_rt.get_quickjs_context(context);
1053
1054        let proxy_name_ref = objects::get_property(context, &receiver_ref, "name")
1055            .ok()
1056            .unwrap();
1057        let proxy_name = primitives::to_string(context, &proxy_name_ref)
1058            .ok()
1059            .unwrap();
1060        trace!("proxy_static_get_prop: {}", proxy_name);
1061
1062        let prop_name = atoms::to_string2(context, &atom).expect("could not get name");
1063        trace!("proxy_static_get_prop: prop: {}", prop_name);
1064
1065        let registry = &*q_ctx.proxy_registry.borrow();
1066        if let Some(proxy) = registry.get(proxy_name.as_str()) {
1067            if proxy.static_methods.contains_key(prop_name.as_str()) {
1068                trace!("found method for {}", prop_name);
1069
1070                let function_data_ref = from_string(context, prop_name.as_str())
1071                    .expect("could not create function_data_ref");
1072
1073                let func_ref = functions::new_native_function_data(
1074                    context,
1075                    Some(proxy_static_method),
1076                    prop_name.as_str(),
1077                    1,
1078                    function_data_ref,
1079                )
1080                .expect("could not create func");
1081
1082                objects::set_property(context, &receiver_ref, prop_name.as_str(), &func_ref)
1083                    .expect("set_property 9656738 failed");
1084
1085                func_ref.clone_value_incr_rc()
1086            } else if let Some(native_static_method) =
1087                proxy.static_native_methods.get(prop_name.as_str())
1088            {
1089                trace!("found static native method for {}", prop_name);
1090
1091                let func_ref = functions::new_native_function(
1092                    context,
1093                    prop_name.as_str(),
1094                    *native_static_method,
1095                    1,
1096                    false,
1097                )
1098                .expect("could not create func");
1099
1100                objects::set_property(context, &receiver_ref, prop_name.as_str(), &func_ref)
1101                    .expect("set_property 36099 failed");
1102
1103                func_ref.clone_value_incr_rc()
1104            } else if let Some(getter_setter) = proxy.static_getters_setters.get(prop_name.as_str())
1105            {
1106                // call the getter
1107                let getter = &getter_setter.0;
1108                let res: Result<QuickJsValueAdapter, JsError> = getter(q_js_rt, q_ctx);
1109                match res {
1110                    Ok(g_val) => g_val.clone_value_incr_rc(),
1111                    Err(e) => {
1112                        let es = format!("proxy_static_get_prop failed: {e}");
1113                        q_ctx.report_ex(es.as_str())
1114                    }
1115                }
1116            } else if let Some(catch_all_getter_setter) = &proxy.static_catch_all {
1117                // call the getter
1118                let getter = &catch_all_getter_setter.0;
1119                let res: Result<QuickJsValueAdapter, JsError> =
1120                    getter(q_js_rt, q_ctx, prop_name.as_str());
1121                match res {
1122                    Ok(g_val) => g_val.clone_value_incr_rc(),
1123                    Err(e) => {
1124                        let es = format!("proxy_static_get_prop failed: {e}");
1125                        q_ctx.report_ex(es.as_str())
1126                    }
1127                }
1128            } else {
1129                quickjs_utils::new_undefined()
1130            }
1131        } else {
1132            q_ctx.report_ex("proxy class not found")
1133        }
1134    })
1135}
1136
1137#[allow(dead_code)]
1138unsafe extern "C" fn proxy_instance_get_prop(
1139    context: *mut q::JSContext,
1140    obj: q::JSValue,
1141    atom: q::JSAtom,
1142    receiver: q::JSValue,
1143) -> q::JSValue {
1144    trace!("proxy_instance_get_prop");
1145
1146    let _obj_ref = QuickJsValueAdapter::new(
1147        context,
1148        obj,
1149        false,
1150        false,
1151        "reflection::proxy_instance_get_prop obj",
1152    );
1153    let receiver_ref = QuickJsValueAdapter::new(
1154        context,
1155        receiver,
1156        false,
1157        false,
1158        "reflection::proxy_instance_get_prop receiver",
1159    );
1160
1161    QuickJsRuntimeAdapter::do_with(|q_js_rt| {
1162        let q_ctx = q_js_rt.get_quickjs_context(context);
1163
1164        let prop_name = atoms::to_string2(context, &atom).expect("could not get name");
1165        trace!("proxy_instance_get_prop: {}", prop_name);
1166
1167        let info = get_proxy_instance_info(&obj);
1168
1169        trace!("obj_ref.classname = {}", info.class_name);
1170
1171        // see if we have a matching method
1172
1173        let registry = &*q_ctx.proxy_registry.borrow();
1174        let proxy = registry.get(&info.class_name).unwrap();
1175        if proxy.methods.contains_key(prop_name.as_str()) {
1176            trace!("found method for {}", prop_name);
1177
1178            let function_data_ref = from_string(context, prop_name.as_str())
1179                .expect("could not create function_data_ref");
1180
1181            let func_ref = functions::new_native_function_data(
1182                context,
1183                Some(proxy_instance_method),
1184                prop_name.as_str(),
1185                1,
1186                function_data_ref,
1187            )
1188            .expect("could not create func");
1189
1190            objects::set_property(context, &receiver_ref, prop_name.as_str(), &func_ref)
1191                .expect("set_property 96385 failed"); // todo report ex
1192
1193            func_ref.clone_value_incr_rc()
1194        } else if let Some(native_method) = proxy.native_methods.get(prop_name.as_str()) {
1195            trace!("found native method for {}", prop_name);
1196
1197            let func_ref = functions::new_native_function(
1198                context,
1199                prop_name.as_str(),
1200                *native_method,
1201                1,
1202                false,
1203            )
1204            .expect("could not create func"); // tyodo report ex
1205
1206            objects::set_property(context, &receiver_ref, prop_name.as_str(), &func_ref)
1207                .expect("set_property 49671 failed"); // todo report ex
1208
1209            func_ref.clone_value_incr_rc()
1210        } else if let Some(getter_setter) = proxy.getters_setters.get(prop_name.as_str()) {
1211            // call the getter
1212            let getter = &getter_setter.0;
1213            let res: Result<QuickJsValueAdapter, JsError> = getter(q_js_rt, q_ctx, &info.id);
1214            match res {
1215                Ok(g_val) => g_val.clone_value_incr_rc(),
1216                Err(e) => {
1217                    let msg = format!("proxy_instance_get failed: {}", e.get_message());
1218                    let nat_stack = format!(
1219                        "    at Proxy instance getter [{}]\n{}",
1220                        prop_name,
1221                        e.get_stack()
1222                    );
1223                    let err =
1224                        errors::new_error(context, e.get_name(), msg.as_str(), nat_stack.as_str())
1225                            .expect("create error failed");
1226                    errors::throw(context, err)
1227                }
1228            }
1229        } else if let Some(catch_all_getter_setter) = &proxy.catch_all {
1230            // call the getter
1231            let getter = &catch_all_getter_setter.0;
1232            let res: Result<QuickJsValueAdapter, JsError> =
1233                getter(q_js_rt, q_ctx, &info.id, prop_name.as_str());
1234            match res {
1235                Ok(g_val) => g_val.clone_value_incr_rc(),
1236                Err(e) => {
1237                    let msg = format!("proxy_instance_catch_all_get failed: {}", e.get_message());
1238                    let nat_stack = format!(
1239                        "    at Proxy instance getter [{}]\n{}",
1240                        prop_name,
1241                        e.get_stack()
1242                    );
1243                    let err =
1244                        errors::new_error(context, e.get_name(), msg.as_str(), nat_stack.as_str())
1245                            .expect("create error failed");
1246                    errors::throw(context, err)
1247                }
1248            }
1249        } else {
1250            // return null if nothing was returned
1251            quickjs_utils::new_undefined()
1252        }
1253    })
1254
1255    // get constructor name
1256    // get proxy
1257    // get method or getter or setter
1258    // return native func (cache those?)
1259}
1260#[allow(dead_code)]
1261unsafe extern "C" fn proxy_instance_has_prop(
1262    _context: *mut q::JSContext,
1263    _obj: q::JSValue,
1264    _atom: q::JSAtom,
1265) -> ::std::os::raw::c_int {
1266    todo!()
1267}
1268#[allow(dead_code)]
1269unsafe extern "C" fn proxy_static_has_prop(
1270    _context: *mut q::JSContext,
1271    _obj: q::JSValue,
1272    _atom: q::JSAtom,
1273) -> ::std::os::raw::c_int {
1274    todo!()
1275}
1276
1277unsafe extern "C" fn proxy_instance_method(
1278    context: *mut q::JSContext,
1279    this_val: q::JSValue,
1280    argc: ::std::os::raw::c_int,
1281    argv: *mut q::JSValue,
1282    _magic: ::std::os::raw::c_int,
1283    func_data: *mut q::JSValue,
1284) -> q::JSValue {
1285    trace!("proxy_instance_method");
1286    QuickJsRuntimeAdapter::do_with(|q_js_rt| {
1287        let q_ctx = q_js_rt.get_quickjs_context(context);
1288
1289        let proxy_instance_info: &ProxyInstanceInfo = get_proxy_instance_info(&this_val);
1290
1291        let args_vec = parse_args(context, argc, argv);
1292
1293        let func_name_ref = QuickJsValueAdapter::new(
1294            context,
1295            *func_data,
1296            false,
1297            false,
1298            "reflection::proxy_instance_method func_data",
1299        );
1300        let func_name = primitives::to_string(context, &func_name_ref)
1301            .expect("could not to_string func_name_ref");
1302
1303        trace!("proxy_instance_method: {}", func_name);
1304
1305        let registry = &*q_ctx.proxy_registry.borrow();
1306        let proxy = registry
1307            .get(proxy_instance_info.class_name.as_str())
1308            .unwrap();
1309        if let Some(method) = proxy.methods.get(func_name.as_str()) {
1310            // todo report ex
1311            let m_res: Result<QuickJsValueAdapter, JsError> =
1312                method(q_js_rt, q_ctx, &proxy_instance_info.id, &args_vec);
1313
1314            match m_res {
1315                Ok(m_res_ref) => m_res_ref.clone_value_incr_rc(),
1316                Err(e) => {
1317                    let msg = format!("proxy_instance_method failed: {}", e.get_message());
1318                    let nat_stack = format!(
1319                        "    at Proxy instance method [{}]\n{}",
1320                        func_name,
1321                        e.get_stack()
1322                    );
1323                    let err =
1324                        errors::new_error(context, e.get_name(), msg.as_str(), nat_stack.as_str())
1325                            .expect("create error failed");
1326                    errors::throw(context, err)
1327                }
1328            }
1329        } else {
1330            // return null if nothing was returned
1331            quickjs_utils::new_undefined()
1332        }
1333    })
1334}
1335
1336#[allow(dead_code)]
1337unsafe extern "C" fn proxy_static_method(
1338    context: *mut q::JSContext,
1339    this_val: q::JSValue,
1340    argc: ::std::os::raw::c_int,
1341    argv: *mut q::JSValue,
1342    _magic: ::std::os::raw::c_int,
1343    func_data: *mut q::JSValue,
1344) -> q::JSValue {
1345    trace!("proxy_static_method");
1346    QuickJsRuntimeAdapter::do_with(|q_js_rt| {
1347        let q_ctx = q_js_rt.get_quickjs_context(context);
1348        let this_ref = QuickJsValueAdapter::new(
1349            context,
1350            this_val,
1351            false,
1352            false,
1353            "reflection::proxy_static_method this_val",
1354        );
1355
1356        let proxy_name_ref = objects::get_property(context, &this_ref, "name")
1357            .ok()
1358            .unwrap();
1359        let proxy_name =
1360            primitives::to_string(context, &proxy_name_ref).expect("could not to_string classname");
1361
1362        let args_vec = parse_args(context, argc, argv);
1363
1364        let func_name_ref = QuickJsValueAdapter::new(
1365            context,
1366            *func_data,
1367            false,
1368            false,
1369            "reflection::proxy_static_method func_data",
1370        );
1371        let func_name = primitives::to_string(context, &func_name_ref)
1372            .expect("could not to_string func_name_ref");
1373
1374        trace!("proxy_static_method: {}", func_name);
1375
1376        let registry = &*q_ctx.proxy_registry.borrow();
1377        let proxy = registry.get(proxy_name.as_str()).unwrap();
1378        if let Some(method) = proxy.static_methods.get(func_name.as_str()) {
1379            let m_res: Result<QuickJsValueAdapter, JsError> = method(q_js_rt, q_ctx, &args_vec);
1380            match m_res {
1381                Ok(m_res_ref) => m_res_ref.clone_value_incr_rc(),
1382                Err(e) => {
1383                    let msg = format!("proxy_static_method failed: {}", e.get_message());
1384                    let nat_stack = format!(
1385                        "    at Proxy static method [{}]\n{}",
1386                        func_name,
1387                        e.get_stack()
1388                    );
1389                    let err =
1390                        errors::new_error(context, e.get_name(), msg.as_str(), nat_stack.as_str())
1391                            .expect("create error failed");
1392                    errors::throw(context, err)
1393                }
1394            }
1395        } else {
1396            // return null if nothing was returned
1397            quickjs_utils::new_undefined()
1398        }
1399    })
1400}
1401
1402unsafe extern "C" fn proxy_static_set_prop(
1403    context: *mut q::JSContext,
1404    _obj: q::JSValue,
1405    atom: q::JSAtom,
1406    value: q::JSValue,
1407    receiver: q::JSValue,
1408    _flags: ::std::os::raw::c_int,
1409) -> ::std::os::raw::c_int {
1410    trace!("proxy_static_set_prop");
1411
1412    let value_ref = QuickJsValueAdapter::new(
1413        context,
1414        value,
1415        false,
1416        false,
1417        "reflection::proxy_static_set_prop value",
1418    );
1419    let receiver_ref = QuickJsValueAdapter::new(
1420        context,
1421        receiver,
1422        false,
1423        false,
1424        "reflection::proxy_static_set_prop value",
1425    );
1426
1427    QuickJsRuntimeAdapter::do_with(|rt| {
1428        let realm = rt.get_quickjs_context(context);
1429
1430        let prop_name = atoms::to_string2(context, &atom).expect("could not get name");
1431        trace!("proxy_static_set_prop: {}", prop_name);
1432
1433        // see if we have a matching gettersetter
1434
1435        let proxy_name_ref = objects::get_property(context, &receiver_ref, "name")
1436            .ok()
1437            .unwrap();
1438        let proxy_name = primitives::to_string(context, &proxy_name_ref)
1439            .ok()
1440            .unwrap();
1441        trace!("proxy_static_set_prop: {}", proxy_name);
1442
1443        let registry = &*realm.proxy_registry.borrow();
1444        if let Some(proxy) = registry.get(proxy_name.as_str()) {
1445            if let Some(getter_setter) = proxy.static_getters_setters.get(prop_name.as_str()) {
1446                // call the setter
1447                let setter = &getter_setter.1;
1448                let res: Result<(), JsError> = setter(rt, realm, value_ref);
1449                match res {
1450                    Ok(_) => 0,
1451                    Err(e) => {
1452                        // fail, todo do i need ex?
1453                        let err = format!("proxy_static_set_prop failed: {e}");
1454                        log::error!("{}", err);
1455                        let _ = realm.report_ex(err.as_str());
1456                        -1
1457                    }
1458                }
1459            } else if let Some(catch_all_getter_setter) = &proxy.static_catch_all {
1460                // call the setter
1461                let setter = &catch_all_getter_setter.1;
1462                let res: Result<(), JsError> = setter(rt, realm, prop_name.as_str(), value_ref);
1463                match res {
1464                    Ok(_) => 0,
1465                    Err(e) => {
1466                        // fail, todo do i need ex?
1467                        let err = format!("proxy_static_set_prop failed: {e}");
1468                        log::error!("{}", err);
1469                        let _ = realm.report_ex(err.as_str());
1470                        -1
1471                    }
1472                }
1473            } else {
1474                let receiver_ref = QuickJsValueAdapter::new(
1475                    context,
1476                    receiver,
1477                    false,
1478                    false,
1479                    "reflection::proxy_static_set_prop receiver",
1480                );
1481
1482                match realm.set_object_property(&receiver_ref, prop_name.as_str(), &value_ref) {
1483                    Ok(()) => 0,
1484                    Err(e) => {
1485                        let err = format!("proxy_static_set_prop failed, {}", e);
1486                        log::error!("{}", err);
1487                        let _ = realm.report_ex(err.as_str());
1488                        -1
1489                    }
1490                }
1491                /*
1492                let err = format!("proxy_static_set_prop failed, no handler found for proxy_static_set_prop: {}", prop_name);
1493                log::error!("{}", err);
1494                let _ = q_ctx.report_ex(err.as_str());
1495                -1
1496
1497                 */
1498            }
1499        } else {
1500            let err = "proxy_static_set_prop failed, no proxy found";
1501            log::error!("{}", err);
1502            let _ = realm.report_ex(err);
1503            -1
1504        }
1505    })
1506}
1507
1508unsafe extern "C" fn proxy_instance_set_prop(
1509    context: *mut q::JSContext,
1510    obj: q::JSValue,
1511    atom: q::JSAtom,
1512    value: q::JSValue,
1513    receiver: q::JSValue,
1514    _flags: ::std::os::raw::c_int,
1515) -> ::std::os::raw::c_int {
1516    trace!("proxy_instance_set_prop");
1517
1518    let value_ref = QuickJsValueAdapter::new(
1519        context,
1520        value,
1521        false,
1522        false,
1523        "reflection::proxy_instance_set_prop value",
1524    );
1525
1526    QuickJsRuntimeAdapter::do_with(|rt| {
1527        let realm = rt.get_quickjs_context(context);
1528
1529        let prop_name = atoms::to_string2(context, &atom).expect("could not get name");
1530        trace!("proxy_instance_set_prop: {}", prop_name);
1531
1532        let info = get_proxy_instance_info(&obj);
1533
1534        trace!("obj_ref.classname = {}", info.class_name);
1535
1536        // see if we have a matching gettersetter
1537
1538        let registry = &*realm.proxy_registry.borrow();
1539        let proxy = registry.get(&info.class_name).unwrap();
1540
1541        if let Some(getter_setter) = proxy.getters_setters.get(prop_name.as_str()) {
1542            // call the setter
1543            let setter = &getter_setter.1;
1544            let res: Result<(), JsError> = setter(rt, realm, &info.id, value_ref);
1545            match res {
1546                Ok(_) => 0,
1547                Err(e) => {
1548                    // fail, todo do i need ex?
1549                    let err = format!("proxy_instance_set_prop failed: {e}");
1550                    log::error!("{}", err);
1551                    let _ = realm.report_ex(err.as_str());
1552                    -1
1553                }
1554            }
1555        } else if let Some(catch_all_getter_setter) = &proxy.catch_all {
1556            // call the setter
1557            let setter = &catch_all_getter_setter.1;
1558            let res: Result<(), JsError> =
1559                setter(rt, realm, &info.id, prop_name.as_str(), value_ref);
1560            match res {
1561                Ok(_) => 0,
1562                Err(e) => {
1563                    // fail, todo do i need ex?
1564                    let err = format!("proxy_instance_set_prop failed: {e}");
1565                    log::error!("{}", err);
1566                    let _ = realm.report_ex(err.as_str());
1567                    -1
1568                }
1569            }
1570        } else {
1571            // if not handler just add to receiver
1572
1573            let receiver_ref = QuickJsValueAdapter::new(
1574                context,
1575                receiver,
1576                false,
1577                false,
1578                "reflection::proxy_instance_set_prop receiver",
1579            );
1580
1581            match realm.set_object_property(&receiver_ref, prop_name.as_str(), &value_ref) {
1582                Ok(()) => 0,
1583                Err(e) => {
1584                    let err = format!("proxy_instance_set_prop failed, {}", e);
1585                    log::error!("{}", err);
1586                    let _ = realm.report_ex(err.as_str());
1587                    -1
1588                }
1589            }
1590            /*
1591            let err = format!(
1592                "proxy_instance_set_prop failed, no handler found for proxy_instance_set_prop: {}",
1593                prop_name
1594            );
1595            log::error!("{}", err);
1596            let _ = realm.report_ex(err.as_str());
1597            -1
1598
1599             */
1600        }
1601    })
1602}
1603
1604#[cfg(test)]
1605pub mod tests {
1606    use crate::facades::tests::init_test_rt;
1607    use crate::jsutils::JsError;
1608    use crate::jsutils::Script;
1609    use crate::quickjs_utils::objects::create_object_q;
1610    use crate::quickjs_utils::{functions, primitives};
1611    use crate::reflection::{
1612        get_proxy_instance_proxy_and_instance_id_q, is_proxy_instance_q, Proxy,
1613        PROXY_INSTANCE_CLASS_ID,
1614    };
1615    use libquickjs_sys as q;
1616    use log::trace;
1617    use std::cell::RefCell;
1618    use std::collections::HashMap;
1619    use std::panic;
1620    use std::time::Duration;
1621
1622    thread_local! {
1623        static TEST_INSTANCES: RefCell<HashMap<usize, String>> = RefCell::new(HashMap::new())
1624    }
1625
1626    #[test]
1627    pub fn test_proxy1() {
1628        log::info!("> test_proxy");
1629
1630        let rt = init_test_rt();
1631        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1632            q_js_rt.gc();
1633            let q_ctx = q_js_rt.get_main_realm();
1634            let _ = Proxy::new()
1635                .constructor(|_q_js_rt, _q_ctx, _id, _args| Ok(()))
1636                .name("Test")
1637                .install(q_ctx, true);
1638            q_ctx
1639                .eval(Script::new("test.es", "let t = new Test();"))
1640                .expect("script failed");
1641        });
1642    }
1643
1644    #[test]
1645    pub fn test_proxy_ex() {
1646        log::info!("> test_proxy");
1647
1648        let rt = init_test_rt();
1649        let err = rt.exe_rt_task_in_event_loop(|q_js_rt| {
1650            q_js_rt.gc();
1651            let q_ctx = q_js_rt.get_main_realm();
1652            let _ = Proxy::new()
1653                .constructor(|_q_js_rt, _q_ctx, _id, _args| Ok(()))
1654                .method("run", |_rt, _realm, _instance_id, _args| {
1655                    Err(JsError::new_str("cant run"))
1656                })
1657                .name("Test")
1658                .install(q_ctx, true);
1659            let err = q_ctx
1660                .eval(Script::new("test.es", "let t = new Test(); \nt.run();"))
1661                .expect_err("script failed");
1662
1663            format!("{err}")
1664        });
1665
1666        assert!(err.contains("test.es:2"));
1667        assert!(err.contains("at Proxy instance method [run]"));
1668        assert!(err.contains("cant run"));
1669    }
1670
1671    #[test]
1672    pub fn test_proxy_instanceof() {
1673        log::info!("> test_proxy_instanceof");
1674
1675        let rt = init_test_rt();
1676        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1677            q_js_rt.gc();
1678            let q_ctx = q_js_rt.get_main_realm();
1679            let _ = Proxy::new()
1680                .constructor(|_rt, _q_ctx, _id, _args| Ok(()))
1681                .namespace(&["com", "company"])
1682                .name("Test")
1683                .install(q_ctx, true);
1684            let res = q_ctx
1685                .eval(Script::new("test_tostring.es", "new com.company.Test()"))
1686                .expect("script failed");
1687            assert!(is_proxy_instance_q(q_ctx, &res));
1688            let info = get_proxy_instance_proxy_and_instance_id_q(q_ctx, &res)
1689                .expect("could not get info");
1690            let id = info.1;
1691            let p = info.0;
1692            println!("id={id}");
1693            assert_eq!(p.get_class_name().as_str(), "com.company.Test");
1694
1695            let some_obj = create_object_q(q_ctx).expect("could not create obj");
1696            assert!(some_obj.is_object());
1697
1698            let class_id = PROXY_INSTANCE_CLASS_ID.with(|rc| *rc.borrow());
1699            let proxy_class_proto: q::JSValue =
1700                unsafe { q::JS_GetClassProto(q_ctx.context, class_id) };
1701            //println!("proxy_class_proto = {}", proxy_class_proto);
1702            let res = unsafe {
1703                q::JS_IsInstanceOf(q_ctx.context, *some_obj.borrow_value(), proxy_class_proto) != 0
1704            };
1705            println!("res = {res}");
1706            let res2 = is_proxy_instance_q(q_ctx, &some_obj);
1707            println!("res2 = {res2}");
1708            assert!(!res2);
1709        });
1710    }
1711
1712    #[test]
1713    pub fn test_rest_props() {
1714        log::info!("> test_rest_props");
1715
1716        let rt = init_test_rt();
1717        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1718            q_js_rt.gc();
1719            let realm = q_js_rt.get_main_realm();
1720            let _ = Proxy::new()
1721                .constructor(|_rt, _q_ctx, _id, _args| Ok(()))
1722                .namespace(&["com", "company"])
1723                .name("Test")
1724                .install(realm, true);
1725            match realm.eval(Script::new(
1726                "test_tostring.js",
1727                r#"
1728                    let t = new com.company.Test();
1729                    t.foo = "bar";
1730                    com.company.Test.sfoo = "sbar"
1731                    t.foo + "_" + com.company.Test.sfoo;
1732                    "#,
1733            )) {
1734                Ok(res) => {
1735                    let s = res.to_string().expect("could not to_str");
1736                    assert_eq!(s, "bar_sbar");
1737                }
1738                Err(e) => {
1739                    panic!("e: {}", e);
1740                }
1741            }
1742        });
1743    }
1744
1745    #[test]
1746    pub fn test_instance_of() {
1747        log::info!("> test_instance_of");
1748
1749        let rt = init_test_rt();
1750        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1751            q_js_rt.gc();
1752            let q_ctx = q_js_rt.get_main_realm();
1753            let _ = Proxy::new()
1754                .constructor(|_rt, _q_ctx, _id, _args| Ok(()))
1755                .namespace(&["com", "company"])
1756                .name("Test")
1757                .install(q_ctx, true);
1758            match q_ctx.eval(Script::new(
1759                "test_tostring.js",
1760                r#"
1761                    let t = new com.company.Test();
1762                    t instanceof com.company.Test
1763                    "#,
1764            )) {
1765                Ok(res) => {
1766                    let bln = res.to_bool();
1767                    assert!(bln);
1768                }
1769                Err(e) => {
1770                    panic!("e: {}", e);
1771                }
1772            }
1773        });
1774    }
1775
1776    #[test]
1777    pub fn test_to_string() {
1778        log::info!("> test_proxy");
1779
1780        let rt = init_test_rt();
1781        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1782            q_js_rt.gc();
1783            let q_ctx = q_js_rt.get_main_realm();
1784            let _ = Proxy::new()
1785                .constructor(|_rt, _q_ctx, _id, _args| Ok(()))
1786                .namespace(&["com", "company"])
1787                .name("Test")
1788                .install(q_ctx, true);
1789            let res = q_ctx
1790                .eval(Script::new(
1791                    "test_tostring.es",
1792                    "com.company.Test + '-' + new com.company.Test()",
1793                ))
1794                .expect("script failed");
1795            let str = primitives::to_string_q(q_ctx, &res).expect("could not tostring");
1796            assert!(str.starts_with("Proxy::com.company.Test-Proxy::instance("));
1797            assert!(str.ends_with(")::com.company.Test"));
1798        });
1799    }
1800
1801    #[test]
1802    pub fn test_proxy() {
1803        log::info!("> test_proxy");
1804
1805        let rt = init_test_rt();
1806        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1807            let q_ctx = q_js_rt.get_main_realm();
1808            let res = Proxy::new()
1809                .name("TestClass1")
1810                .constructor(|_rt, _context, id, _args| {
1811                    TEST_INSTANCES.with(|rc| {
1812                        let map = &mut *rc.borrow_mut();
1813                        map.insert(id, "hi".to_string())
1814                    });
1815                    Ok(())
1816                })
1817                .method("doIt", |_rt, _context, _obj_id, _args| {
1818                    Ok(primitives::from_i32(531))
1819                })
1820                .method("doIt2", |_rt, _context, _obj_id, _args| {
1821                    Err(JsError::new_str("aaargh"))
1822                })
1823                .getter_setter(
1824                    "gVar",
1825                    |_rt, _context, _id| Ok(primitives::from_i32(147)),
1826                    |_rt, _context, _id, _val| Ok(()),
1827                )
1828                .static_method("sDoIt", |_rt, _context, _args| {
1829                    Ok(primitives::from_i32(9876))
1830                })
1831                .static_method("sDoIt2", |_rt, _context, _args| {
1832                    Ok(primitives::from_i32(140))
1833                })
1834                .static_getter_setter(
1835                    "someThing",
1836                    |_rt, _context| {
1837                        trace!("static getter called, returning 754");
1838                        Ok(primitives::from_i32(754))
1839                    },
1840                    |_rt, q_ctx, val| {
1841                        trace!(
1842                            "static setter called, set to {}",
1843                            functions::call_to_string_q(q_ctx, &val)?
1844                        );
1845                        Ok(())
1846                    },
1847                )
1848                .finalizer(|_rt, _context, id| {
1849                    TEST_INSTANCES.with(|rc| {
1850                        let map = &mut *rc.borrow_mut();
1851                        let _ = map.remove(&id);
1852                    });
1853                    log::trace!("ran finalizer: {}", id);
1854                })
1855                .install(q_ctx, true);
1856
1857            match res {
1858                Ok(_) => {}
1859                Err(e) => panic!("could not install proxy: {}", e),
1860            }
1861        });
1862
1863        let i2_res = rt.eval_sync(None, Script::new(
1864            "test_proxy.es",
1865            "let tc2 = new TestClass1(1, true, 'abc'); let r2 = tc2.doIt(1, true, 'abc'); console.log('< setting tc2 to null'); tc2 = null; console.log('> setting tc2 to null'); r2;"
1866            ,
1867        ));
1868        log::debug!("test_proxy.es done, ok = {}", i2_res.is_ok());
1869        match i2_res {
1870            Ok(i2) => {
1871                assert!(i2.is_i32());
1872                assert_eq!(i2.get_i32(), 531);
1873            }
1874            Err(e) => {
1875                log::error!("test_proxy.es failed with: {}", e);
1876                panic!("test_proxy.es failed");
1877            }
1878        }
1879
1880        let i = rt.eval_sync(None, Script::new(
1881            "test_proxy2.es",
1882            "let tc1 = new TestClass1(1, true, 'abc'); let r = tc1.doIt(1, true, 'abc'); r = tc1.doIt(1, true, 'abc'); tc1 = null; r;"
1883        ))
1884            .ok()
1885            .expect("script failed");
1886
1887        assert!(i.is_i32());
1888        assert_eq!(i.get_i32(), 531);
1889
1890        let i3_res = rt.eval_sync(None, Script::new("test_proxy.es", "TestClass1.sDoIt();"));
1891
1892        if i3_res.is_err() {
1893            panic!("script failed: {}", i3_res.err().unwrap());
1894        }
1895        let i3 = i3_res.ok().unwrap();
1896
1897        assert!(i3.is_i32());
1898        assert_eq!(i3.get_i32(), 9876);
1899
1900        let i4 = rt
1901            .eval_sync(
1902                None,
1903                Script::new(
1904                    "test_proxy.es",
1905                    "TestClass1.someThing = 1; TestClass1.someThing;",
1906                ),
1907            )
1908            .expect("script failed");
1909
1910        assert!(i4.is_i32());
1911        assert_eq!(i4.get_i32(), 754);
1912
1913        let i5 = rt
1914            .eval_sync(
1915                None,
1916                Script::new(
1917                    "test_proxy.es",
1918                    "let tc5 = new TestClass1(); let r5 = tc5.gVar; tc5 = null; r5;",
1919                ),
1920            )
1921            .expect("script failed");
1922
1923        assert!(i5.is_i32());
1924        assert_eq!(i5.get_i32(), 147);
1925
1926        let i6_res = rt.eval_sync(
1927            None,
1928            Script::new(
1929                "test_proxy.es",
1930                "let tc6 = new TestClass1(); let r6 = tc6.doIt2(); tc6 = null; r6;",
1931            ),
1932        );
1933        assert!(i6_res.is_err());
1934        let e = i6_res.err().unwrap();
1935        let e_msg = e.get_message();
1936        assert_eq!(e_msg, "proxy_instance_method failed: aaargh");
1937
1938        assert!(e.get_stack().contains("[doIt2]"));
1939
1940        rt.gc_sync();
1941
1942        std::thread::sleep(Duration::from_secs(1));
1943
1944        log::info!("< test_proxy");
1945    }
1946
1947    #[test]
1948    pub fn test_constructor() {
1949        // todo init logger
1950
1951        let rt = init_test_rt();
1952        rt.loop_realm_sync(None, |_rt, realm| {
1953            Proxy::new()
1954                .name("TestClass")
1955                .namespace(&["com", "company"])
1956                .constructor(|_rt, _realm, _id, _args| Ok(()))
1957                .finalizer(|_rt, _realm, _id| {
1958                    //
1959                })
1960                .install(realm, true)
1961                .expect("poof");
1962
1963            let constr_str = realm
1964                .eval(Script::new(
1965                    "test_constr.js",
1966                    r#"
1967                let instance = new com.company.TestClass();
1968                const ret = "" + instance.constructor.name;
1969                instance = null;
1970                ret
1971            "#,
1972                ))
1973                .expect("script failed");
1974
1975            println!(
1976                "cons str = {}",
1977                constr_str.to_string().expect("not a string")
1978            );
1979        });
1980    }
1981}