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_str(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) {
1068                trace!("found method for {}", prop_name);
1069
1070                let function_data_ref =
1071                    from_string(context, prop_name).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,
1077                    1,
1078                    function_data_ref,
1079                )
1080                .expect("could not create func");
1081
1082                objects::set_property(context, &receiver_ref, prop_name, &func_ref)
1083                    .expect("set_property 9656738 failed");
1084
1085                func_ref.clone_value_incr_rc()
1086            } else if let Some(native_static_method) = proxy.static_native_methods.get(prop_name) {
1087                trace!("found static native method for {}", prop_name);
1088
1089                let func_ref = functions::new_native_function(
1090                    context,
1091                    prop_name,
1092                    *native_static_method,
1093                    1,
1094                    false,
1095                )
1096                .expect("could not create func");
1097
1098                objects::set_property(context, &receiver_ref, prop_name, &func_ref)
1099                    .expect("set_property 36099 failed");
1100
1101                func_ref.clone_value_incr_rc()
1102            } else if let Some(getter_setter) = proxy.static_getters_setters.get(prop_name) {
1103                // call the getter
1104                let getter = &getter_setter.0;
1105                let res: Result<QuickJsValueAdapter, JsError> = getter(q_js_rt, q_ctx);
1106                match res {
1107                    Ok(g_val) => g_val.clone_value_incr_rc(),
1108                    Err(e) => {
1109                        let es = format!("proxy_static_get_prop failed: {e}");
1110                        q_ctx.report_ex(es.as_str())
1111                    }
1112                }
1113            } else if let Some(catch_all_getter_setter) = &proxy.static_catch_all {
1114                // call the getter
1115                let getter = &catch_all_getter_setter.0;
1116                let res: Result<QuickJsValueAdapter, JsError> = getter(q_js_rt, q_ctx, prop_name);
1117                match res {
1118                    Ok(g_val) => g_val.clone_value_incr_rc(),
1119                    Err(e) => {
1120                        let es = format!("proxy_static_get_prop failed: {e}");
1121                        q_ctx.report_ex(es.as_str())
1122                    }
1123                }
1124            } else {
1125                quickjs_utils::new_undefined()
1126            }
1127        } else {
1128            q_ctx.report_ex("proxy class not found")
1129        }
1130    })
1131}
1132
1133#[allow(dead_code)]
1134unsafe extern "C" fn proxy_instance_get_prop(
1135    context: *mut q::JSContext,
1136    obj: q::JSValue,
1137    atom: q::JSAtom,
1138    receiver: q::JSValue,
1139) -> q::JSValue {
1140    trace!("proxy_instance_get_prop");
1141
1142    let _obj_ref = QuickJsValueAdapter::new(
1143        context,
1144        obj,
1145        false,
1146        false,
1147        "reflection::proxy_instance_get_prop obj",
1148    );
1149    let receiver_ref = QuickJsValueAdapter::new(
1150        context,
1151        receiver,
1152        false,
1153        false,
1154        "reflection::proxy_instance_get_prop receiver",
1155    );
1156
1157    QuickJsRuntimeAdapter::do_with(|q_js_rt| {
1158        let q_ctx = q_js_rt.get_quickjs_context(context);
1159
1160        let prop_name = atoms::to_str(context, &atom).expect("could not get name");
1161        trace!("proxy_instance_get_prop: {}", prop_name);
1162
1163        let info = get_proxy_instance_info(&obj);
1164
1165        trace!("obj_ref.classname = {}", info.class_name);
1166
1167        // see if we have a matching method
1168
1169        let registry = &*q_ctx.proxy_registry.borrow();
1170        let proxy = registry.get(&info.class_name).unwrap();
1171        if proxy.methods.contains_key(prop_name) {
1172            trace!("found method for {}", prop_name);
1173
1174            let function_data_ref =
1175                from_string(context, prop_name).expect("could not create function_data_ref");
1176
1177            let func_ref = functions::new_native_function_data(
1178                context,
1179                Some(proxy_instance_method),
1180                prop_name,
1181                1,
1182                function_data_ref,
1183            )
1184            .expect("could not create func");
1185
1186            objects::set_property(context, &receiver_ref, prop_name, &func_ref)
1187                .expect("set_property 96385 failed"); // todo report ex
1188
1189            func_ref.clone_value_incr_rc()
1190        } else if let Some(native_method) = proxy.native_methods.get(prop_name) {
1191            trace!("found native method for {}", prop_name);
1192
1193            let func_ref =
1194                functions::new_native_function(context, prop_name, *native_method, 1, false)
1195                    .expect("could not create func"); // tyodo report ex
1196
1197            objects::set_property(context, &receiver_ref, prop_name, &func_ref)
1198                .expect("set_property 49671 failed"); // todo report ex
1199
1200            func_ref.clone_value_incr_rc()
1201        } else if let Some(getter_setter) = proxy.getters_setters.get(prop_name) {
1202            // call the getter
1203            let getter = &getter_setter.0;
1204            let res: Result<QuickJsValueAdapter, JsError> = getter(q_js_rt, q_ctx, &info.id);
1205            match res {
1206                Ok(g_val) => g_val.clone_value_incr_rc(),
1207                Err(e) => {
1208                    let msg = format!("proxy_instance_get failed: {}", e.get_message());
1209                    let nat_stack = format!(
1210                        "    at Proxy instance getter [{}]\n{}",
1211                        prop_name,
1212                        e.get_stack()
1213                    );
1214                    let err =
1215                        errors::new_error(context, e.get_name(), msg.as_str(), nat_stack.as_str())
1216                            .expect("create error failed");
1217                    errors::throw(context, err)
1218                }
1219            }
1220        } else if let Some(catch_all_getter_setter) = &proxy.catch_all {
1221            // call the getter
1222            let getter = &catch_all_getter_setter.0;
1223            let res: Result<QuickJsValueAdapter, JsError> =
1224                getter(q_js_rt, q_ctx, &info.id, prop_name);
1225            match res {
1226                Ok(g_val) => g_val.clone_value_incr_rc(),
1227                Err(e) => {
1228                    let msg = format!("proxy_instance_catch_all_get failed: {}", e.get_message());
1229                    let nat_stack = format!(
1230                        "    at Proxy instance getter [{}]\n{}",
1231                        prop_name,
1232                        e.get_stack()
1233                    );
1234                    let err =
1235                        errors::new_error(context, e.get_name(), msg.as_str(), nat_stack.as_str())
1236                            .expect("create error failed");
1237                    errors::throw(context, err)
1238                }
1239            }
1240        } else {
1241            // return null if nothing was returned
1242            quickjs_utils::new_undefined()
1243        }
1244    })
1245
1246    // get constructor name
1247    // get proxy
1248    // get method or getter or setter
1249    // return native func (cache those?)
1250}
1251#[allow(dead_code)]
1252unsafe extern "C" fn proxy_instance_has_prop(
1253    _context: *mut q::JSContext,
1254    _obj: q::JSValue,
1255    _atom: q::JSAtom,
1256) -> ::std::os::raw::c_int {
1257    todo!()
1258}
1259#[allow(dead_code)]
1260unsafe extern "C" fn proxy_static_has_prop(
1261    _context: *mut q::JSContext,
1262    _obj: q::JSValue,
1263    _atom: q::JSAtom,
1264) -> ::std::os::raw::c_int {
1265    todo!()
1266}
1267
1268unsafe extern "C" fn proxy_instance_method(
1269    context: *mut q::JSContext,
1270    this_val: q::JSValue,
1271    argc: ::std::os::raw::c_int,
1272    argv: *mut q::JSValue,
1273    _magic: ::std::os::raw::c_int,
1274    func_data: *mut q::JSValue,
1275) -> q::JSValue {
1276    trace!("proxy_instance_method");
1277    QuickJsRuntimeAdapter::do_with(|q_js_rt| {
1278        let q_ctx = q_js_rt.get_quickjs_context(context);
1279
1280        let proxy_instance_info: &ProxyInstanceInfo = get_proxy_instance_info(&this_val);
1281
1282        let args_vec = parse_args(context, argc, argv);
1283
1284        let func_name_ref = QuickJsValueAdapter::new(
1285            context,
1286            *func_data,
1287            false,
1288            false,
1289            "reflection::proxy_instance_method func_data",
1290        );
1291        let func_name = primitives::to_string(context, &func_name_ref)
1292            .expect("could not to_string func_name_ref");
1293
1294        trace!("proxy_instance_method: {}", func_name);
1295
1296        let registry = &*q_ctx.proxy_registry.borrow();
1297        let proxy = registry
1298            .get(proxy_instance_info.class_name.as_str())
1299            .unwrap();
1300        if let Some(method) = proxy.methods.get(func_name.as_str()) {
1301            // todo report ex
1302            let m_res: Result<QuickJsValueAdapter, JsError> =
1303                method(q_js_rt, q_ctx, &proxy_instance_info.id, &args_vec);
1304
1305            match m_res {
1306                Ok(m_res_ref) => m_res_ref.clone_value_incr_rc(),
1307                Err(e) => {
1308                    let msg = format!("proxy_instance_method failed: {}", e.get_message());
1309                    let nat_stack = format!(
1310                        "    at Proxy instance method [{}]\n{}",
1311                        func_name,
1312                        e.get_stack()
1313                    );
1314                    let err =
1315                        errors::new_error(context, e.get_name(), msg.as_str(), nat_stack.as_str())
1316                            .expect("create error failed");
1317                    errors::throw(context, err)
1318                }
1319            }
1320        } else {
1321            // return null if nothing was returned
1322            quickjs_utils::new_undefined()
1323        }
1324    })
1325}
1326
1327#[allow(dead_code)]
1328unsafe extern "C" fn proxy_static_method(
1329    context: *mut q::JSContext,
1330    this_val: q::JSValue,
1331    argc: ::std::os::raw::c_int,
1332    argv: *mut q::JSValue,
1333    _magic: ::std::os::raw::c_int,
1334    func_data: *mut q::JSValue,
1335) -> q::JSValue {
1336    trace!("proxy_static_method");
1337    QuickJsRuntimeAdapter::do_with(|q_js_rt| {
1338        let q_ctx = q_js_rt.get_quickjs_context(context);
1339        let this_ref = QuickJsValueAdapter::new(
1340            context,
1341            this_val,
1342            false,
1343            false,
1344            "reflection::proxy_static_method this_val",
1345        );
1346
1347        let proxy_name_ref = objects::get_property(context, &this_ref, "name")
1348            .ok()
1349            .unwrap();
1350        let proxy_name =
1351            primitives::to_string(context, &proxy_name_ref).expect("could not to_string classname");
1352
1353        let args_vec = parse_args(context, argc, argv);
1354
1355        let func_name_ref = QuickJsValueAdapter::new(
1356            context,
1357            *func_data,
1358            false,
1359            false,
1360            "reflection::proxy_static_method func_data",
1361        );
1362        let func_name = primitives::to_string(context, &func_name_ref)
1363            .expect("could not to_string func_name_ref");
1364
1365        trace!("proxy_static_method: {}", func_name);
1366
1367        let registry = &*q_ctx.proxy_registry.borrow();
1368        let proxy = registry.get(proxy_name.as_str()).unwrap();
1369        if let Some(method) = proxy.static_methods.get(func_name.as_str()) {
1370            let m_res: Result<QuickJsValueAdapter, JsError> = method(q_js_rt, q_ctx, &args_vec);
1371            match m_res {
1372                Ok(m_res_ref) => m_res_ref.clone_value_incr_rc(),
1373                Err(e) => {
1374                    let msg = format!("proxy_static_method failed: {}", e.get_message());
1375                    let nat_stack = format!(
1376                        "    at Proxy static method [{}]\n{}",
1377                        func_name,
1378                        e.get_stack()
1379                    );
1380                    let err =
1381                        errors::new_error(context, e.get_name(), msg.as_str(), nat_stack.as_str())
1382                            .expect("create error failed");
1383                    errors::throw(context, err)
1384                }
1385            }
1386        } else {
1387            // return null if nothing was returned
1388            quickjs_utils::new_undefined()
1389        }
1390    })
1391}
1392
1393unsafe extern "C" fn proxy_static_set_prop(
1394    context: *mut q::JSContext,
1395    _obj: q::JSValue,
1396    atom: q::JSAtom,
1397    value: q::JSValue,
1398    receiver: q::JSValue,
1399    _flags: ::std::os::raw::c_int,
1400) -> ::std::os::raw::c_int {
1401    trace!("proxy_static_set_prop");
1402
1403    let value_ref = QuickJsValueAdapter::new(
1404        context,
1405        value,
1406        false,
1407        false,
1408        "reflection::proxy_static_set_prop value",
1409    );
1410    let receiver_ref = QuickJsValueAdapter::new(
1411        context,
1412        receiver,
1413        false,
1414        false,
1415        "reflection::proxy_static_set_prop value",
1416    );
1417
1418    QuickJsRuntimeAdapter::do_with(|rt| {
1419        let realm = rt.get_quickjs_context(context);
1420
1421        let prop_name = atoms::to_str(context, &atom).expect("could not get name");
1422        trace!("proxy_static_set_prop: {}", prop_name);
1423
1424        // see if we have a matching gettersetter
1425
1426        let proxy_name_ref = objects::get_property(context, &receiver_ref, "name")
1427            .ok()
1428            .unwrap();
1429        let proxy_name = primitives::to_string(context, &proxy_name_ref)
1430            .ok()
1431            .unwrap();
1432        trace!("proxy_static_set_prop: {}", proxy_name);
1433
1434        let registry = &*realm.proxy_registry.borrow();
1435        if let Some(proxy) = registry.get(proxy_name.as_str()) {
1436            if let Some(getter_setter) = proxy.static_getters_setters.get(prop_name) {
1437                // call the setter
1438                let setter = &getter_setter.1;
1439                let res: Result<(), JsError> = setter(rt, realm, value_ref);
1440                match res {
1441                    Ok(_) => 0,
1442                    Err(e) => {
1443                        // fail, todo do i need ex?
1444                        let err = format!("proxy_static_set_prop failed: {e}");
1445                        log::error!("{}", err);
1446                        let _ = realm.report_ex(err.as_str());
1447                        -1
1448                    }
1449                }
1450            } else if let Some(catch_all_getter_setter) = &proxy.static_catch_all {
1451                // call the setter
1452                let setter = &catch_all_getter_setter.1;
1453                let res: Result<(), JsError> = setter(rt, realm, prop_name, value_ref);
1454                match res {
1455                    Ok(_) => 0,
1456                    Err(e) => {
1457                        // fail, todo do i need ex?
1458                        let err = format!("proxy_static_set_prop failed: {e}");
1459                        log::error!("{}", err);
1460                        let _ = realm.report_ex(err.as_str());
1461                        -1
1462                    }
1463                }
1464            } else {
1465                let receiver_ref = QuickJsValueAdapter::new(
1466                    context,
1467                    receiver,
1468                    false,
1469                    false,
1470                    "reflection::proxy_static_set_prop receiver",
1471                );
1472
1473                match realm.set_object_property(&receiver_ref, prop_name, &value_ref) {
1474                    Ok(()) => 0,
1475                    Err(e) => {
1476                        let err = format!("proxy_static_set_prop failed, {}", e);
1477                        log::error!("{}", err);
1478                        let _ = realm.report_ex(err.as_str());
1479                        -1
1480                    }
1481                }
1482                /*
1483                let err = format!("proxy_static_set_prop failed, no handler found for proxy_static_set_prop: {}", prop_name);
1484                log::error!("{}", err);
1485                let _ = q_ctx.report_ex(err.as_str());
1486                -1
1487
1488                 */
1489            }
1490        } else {
1491            let err = "proxy_static_set_prop failed, no proxy found";
1492            log::error!("{}", err);
1493            let _ = realm.report_ex(err);
1494            -1
1495        }
1496    })
1497}
1498
1499unsafe extern "C" fn proxy_instance_set_prop(
1500    context: *mut q::JSContext,
1501    obj: q::JSValue,
1502    atom: q::JSAtom,
1503    value: q::JSValue,
1504    receiver: q::JSValue,
1505    _flags: ::std::os::raw::c_int,
1506) -> ::std::os::raw::c_int {
1507    trace!("proxy_instance_set_prop");
1508
1509    let value_ref = QuickJsValueAdapter::new(
1510        context,
1511        value,
1512        false,
1513        false,
1514        "reflection::proxy_instance_set_prop value",
1515    );
1516
1517    QuickJsRuntimeAdapter::do_with(|rt| {
1518        let realm = rt.get_quickjs_context(context);
1519
1520        let prop_name = atoms::to_str(context, &atom).expect("could not get name");
1521        trace!("proxy_instance_set_prop: {}", prop_name);
1522
1523        let info = get_proxy_instance_info(&obj);
1524
1525        trace!("obj_ref.classname = {}", info.class_name);
1526
1527        // see if we have a matching gettersetter
1528
1529        let registry = &*realm.proxy_registry.borrow();
1530        let proxy = registry.get(&info.class_name).unwrap();
1531
1532        if let Some(getter_setter) = proxy.getters_setters.get(prop_name) {
1533            // call the setter
1534            let setter = &getter_setter.1;
1535            let res: Result<(), JsError> = setter(rt, realm, &info.id, value_ref);
1536            match res {
1537                Ok(_) => 0,
1538                Err(e) => {
1539                    // fail, todo do i need ex?
1540                    let err = format!("proxy_instance_set_prop failed: {e}");
1541                    log::error!("{}", err);
1542                    let _ = realm.report_ex(err.as_str());
1543                    -1
1544                }
1545            }
1546        } else if let Some(catch_all_getter_setter) = &proxy.catch_all {
1547            // call the setter
1548            let setter = &catch_all_getter_setter.1;
1549            let res: Result<(), JsError> = setter(rt, realm, &info.id, prop_name, value_ref);
1550            match res {
1551                Ok(_) => 0,
1552                Err(e) => {
1553                    // fail, todo do i need ex?
1554                    let err = format!("proxy_instance_set_prop failed: {e}");
1555                    log::error!("{}", err);
1556                    let _ = realm.report_ex(err.as_str());
1557                    -1
1558                }
1559            }
1560        } else {
1561            // if not handler just add to receiver
1562
1563            let receiver_ref = QuickJsValueAdapter::new(
1564                context,
1565                receiver,
1566                false,
1567                false,
1568                "reflection::proxy_instance_set_prop receiver",
1569            );
1570
1571            match realm.set_object_property(&receiver_ref, prop_name, &value_ref) {
1572                Ok(()) => 0,
1573                Err(e) => {
1574                    let err = format!("proxy_instance_set_prop failed, {}", e);
1575                    log::error!("{}", err);
1576                    let _ = realm.report_ex(err.as_str());
1577                    -1
1578                }
1579            }
1580            /*
1581            let err = format!(
1582                "proxy_instance_set_prop failed, no handler found for proxy_instance_set_prop: {}",
1583                prop_name
1584            );
1585            log::error!("{}", err);
1586            let _ = realm.report_ex(err.as_str());
1587            -1
1588
1589             */
1590        }
1591    })
1592}
1593
1594#[cfg(test)]
1595pub mod tests {
1596    use crate::facades::tests::init_test_rt;
1597    use crate::jsutils::JsError;
1598    use crate::jsutils::Script;
1599    use crate::quickjs_utils::objects::create_object_q;
1600    use crate::quickjs_utils::{functions, primitives};
1601    use crate::reflection::{
1602        get_proxy_instance_proxy_and_instance_id_q, is_proxy_instance_q, Proxy,
1603        PROXY_INSTANCE_CLASS_ID,
1604    };
1605    use libquickjs_sys as q;
1606    use log::trace;
1607    use std::cell::RefCell;
1608    use std::collections::HashMap;
1609    use std::panic;
1610    use std::time::Duration;
1611
1612    thread_local! {
1613        static TEST_INSTANCES: RefCell<HashMap<usize, String>> = RefCell::new(HashMap::new())
1614    }
1615
1616    #[test]
1617    pub fn test_proxy1() {
1618        log::info!("> test_proxy");
1619
1620        let rt = init_test_rt();
1621        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1622            q_js_rt.gc();
1623            let q_ctx = q_js_rt.get_main_realm();
1624            let _ = Proxy::new()
1625                .constructor(|_q_js_rt, _q_ctx, _id, _args| Ok(()))
1626                .name("Test")
1627                .install(q_ctx, true);
1628            q_ctx
1629                .eval(Script::new("test.es", "let t = new Test();"))
1630                .expect("script failed");
1631        });
1632    }
1633
1634    #[test]
1635    pub fn test_proxy_ex() {
1636        log::info!("> test_proxy");
1637
1638        let rt = init_test_rt();
1639        let err = rt.exe_rt_task_in_event_loop(|q_js_rt| {
1640            q_js_rt.gc();
1641            let q_ctx = q_js_rt.get_main_realm();
1642            let _ = Proxy::new()
1643                .constructor(|_q_js_rt, _q_ctx, _id, _args| Ok(()))
1644                .method("run", |_rt, _realm, _instance_id, _args| {
1645                    Err(JsError::new_str("cant run"))
1646                })
1647                .name("Test")
1648                .install(q_ctx, true);
1649            let err = q_ctx
1650                .eval(Script::new("test.es", "let t = new Test(); \nt.run();"))
1651                .expect_err("script failed");
1652
1653            format!("{err}")
1654        });
1655
1656        assert!(err.contains("test.es:2"));
1657        assert!(err.contains("at Proxy instance method [run]"));
1658        assert!(err.contains("cant run"));
1659    }
1660
1661    #[test]
1662    pub fn test_proxy_instanceof() {
1663        log::info!("> test_proxy_instanceof");
1664
1665        let rt = init_test_rt();
1666        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1667            q_js_rt.gc();
1668            let q_ctx = q_js_rt.get_main_realm();
1669            let _ = Proxy::new()
1670                .constructor(|_rt, _q_ctx, _id, _args| Ok(()))
1671                .namespace(&["com", "company"])
1672                .name("Test")
1673                .install(q_ctx, true);
1674            let res = q_ctx
1675                .eval(Script::new("test_tostring.es", "new com.company.Test()"))
1676                .expect("script failed");
1677            assert!(is_proxy_instance_q(q_ctx, &res));
1678            let info = get_proxy_instance_proxy_and_instance_id_q(q_ctx, &res)
1679                .expect("could not get info");
1680            let id = info.1;
1681            let p = info.0;
1682            println!("id={id}");
1683            assert_eq!(p.get_class_name().as_str(), "com.company.Test");
1684
1685            let some_obj = create_object_q(q_ctx).expect("could not create obj");
1686            assert!(some_obj.is_object());
1687
1688            let class_id = PROXY_INSTANCE_CLASS_ID.with(|rc| *rc.borrow());
1689            let proxy_class_proto: q::JSValue =
1690                unsafe { q::JS_GetClassProto(q_ctx.context, class_id) };
1691            //println!("proxy_class_proto = {}", proxy_class_proto);
1692            let res = unsafe {
1693                q::JS_IsInstanceOf(q_ctx.context, *some_obj.borrow_value(), proxy_class_proto) != 0
1694            };
1695            println!("res = {res}");
1696            let res2 = is_proxy_instance_q(q_ctx, &some_obj);
1697            println!("res2 = {res2}");
1698            assert!(!res2);
1699        });
1700    }
1701
1702    #[test]
1703    pub fn test_rest_props() {
1704        log::info!("> test_rest_props");
1705
1706        let rt = init_test_rt();
1707        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1708            q_js_rt.gc();
1709            let realm = q_js_rt.get_main_realm();
1710            let _ = Proxy::new()
1711                .constructor(|_rt, _q_ctx, _id, _args| Ok(()))
1712                .namespace(&["com", "company"])
1713                .name("Test")
1714                .install(realm, true);
1715            match realm.eval(Script::new(
1716                "test_tostring.js",
1717                r#"
1718                    let t = new com.company.Test();
1719                    t.foo = "bar";
1720                    com.company.Test.sfoo = "sbar"
1721                    t.foo + "_" + com.company.Test.sfoo;
1722                    "#,
1723            )) {
1724                Ok(res) => {
1725                    let s = res.to_str().expect("could not to_str");
1726                    assert_eq!(s, "bar_sbar");
1727                }
1728                Err(e) => {
1729                    panic!("e: {}", e);
1730                }
1731            }
1732        });
1733    }
1734
1735    #[test]
1736    pub fn test_instance_of() {
1737        log::info!("> test_instance_of");
1738
1739        let rt = init_test_rt();
1740        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1741            q_js_rt.gc();
1742            let q_ctx = q_js_rt.get_main_realm();
1743            let _ = Proxy::new()
1744                .constructor(|_rt, _q_ctx, _id, _args| Ok(()))
1745                .namespace(&["com", "company"])
1746                .name("Test")
1747                .install(q_ctx, true);
1748            match q_ctx.eval(Script::new(
1749                "test_tostring.js",
1750                r#"
1751                    let t = new com.company.Test();
1752                    t instanceof com.company.Test
1753                    "#,
1754            )) {
1755                Ok(res) => {
1756                    let bln = res.to_bool();
1757                    assert!(bln);
1758                }
1759                Err(e) => {
1760                    panic!("e: {}", e);
1761                }
1762            }
1763        });
1764    }
1765
1766    #[test]
1767    pub fn test_to_string() {
1768        log::info!("> test_proxy");
1769
1770        let rt = init_test_rt();
1771        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1772            q_js_rt.gc();
1773            let q_ctx = q_js_rt.get_main_realm();
1774            let _ = Proxy::new()
1775                .constructor(|_rt, _q_ctx, _id, _args| Ok(()))
1776                .namespace(&["com", "company"])
1777                .name("Test")
1778                .install(q_ctx, true);
1779            let res = q_ctx
1780                .eval(Script::new(
1781                    "test_tostring.es",
1782                    "com.company.Test + '-' + new com.company.Test()",
1783                ))
1784                .expect("script failed");
1785            let str = primitives::to_string_q(q_ctx, &res).expect("could not tostring");
1786            assert!(str.starts_with("Proxy::com.company.Test-Proxy::instance("));
1787            assert!(str.ends_with(")::com.company.Test"));
1788        });
1789    }
1790
1791    #[test]
1792    pub fn test_proxy() {
1793        log::info!("> test_proxy");
1794
1795        let rt = init_test_rt();
1796        rt.exe_rt_task_in_event_loop(|q_js_rt| {
1797            let q_ctx = q_js_rt.get_main_realm();
1798            let res = Proxy::new()
1799                .name("TestClass1")
1800                .constructor(|_rt, _context, id, _args| {
1801                    TEST_INSTANCES.with(|rc| {
1802                        let map = &mut *rc.borrow_mut();
1803                        map.insert(id, "hi".to_string())
1804                    });
1805                    Ok(())
1806                })
1807                .method("doIt", |_rt, _context, _obj_id, _args| {
1808                    Ok(primitives::from_i32(531))
1809                })
1810                .method("doIt2", |_rt, _context, _obj_id, _args| {
1811                    Err(JsError::new_str("aaargh"))
1812                })
1813                .getter_setter(
1814                    "gVar",
1815                    |_rt, _context, _id| Ok(primitives::from_i32(147)),
1816                    |_rt, _context, _id, _val| Ok(()),
1817                )
1818                .static_method("sDoIt", |_rt, _context, _args| {
1819                    Ok(primitives::from_i32(9876))
1820                })
1821                .static_method("sDoIt2", |_rt, _context, _args| {
1822                    Ok(primitives::from_i32(140))
1823                })
1824                .static_getter_setter(
1825                    "someThing",
1826                    |_rt, _context| {
1827                        trace!("static getter called, returning 754");
1828                        Ok(primitives::from_i32(754))
1829                    },
1830                    |_rt, q_ctx, val| {
1831                        trace!(
1832                            "static setter called, set to {}",
1833                            functions::call_to_string_q(q_ctx, &val)?
1834                        );
1835                        Ok(())
1836                    },
1837                )
1838                .finalizer(|_rt, _context, id| {
1839                    TEST_INSTANCES.with(|rc| {
1840                        let map = &mut *rc.borrow_mut();
1841                        let _ = map.remove(&id);
1842                    });
1843                    log::trace!("ran finalizer: {}", id);
1844                })
1845                .install(q_ctx, true);
1846
1847            match res {
1848                Ok(_) => {}
1849                Err(e) => panic!("could not install proxy: {}", e),
1850            }
1851        });
1852
1853        let i2_res = rt.eval_sync(None, Script::new(
1854            "test_proxy.es",
1855            "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;"
1856            ,
1857        ));
1858        log::debug!("test_proxy.es done, ok = {}", i2_res.is_ok());
1859        match i2_res {
1860            Ok(i2) => {
1861                assert!(i2.is_i32());
1862                assert_eq!(i2.get_i32(), 531);
1863            }
1864            Err(e) => {
1865                log::error!("test_proxy.es failed with: {}", e);
1866                panic!("test_proxy.es failed");
1867            }
1868        }
1869
1870        let i = rt.eval_sync(None, Script::new(
1871            "test_proxy2.es",
1872            "let tc1 = new TestClass1(1, true, 'abc'); let r = tc1.doIt(1, true, 'abc'); r = tc1.doIt(1, true, 'abc'); tc1 = null; r;"
1873        ))
1874            .ok()
1875            .expect("script failed");
1876
1877        assert!(i.is_i32());
1878        assert_eq!(i.get_i32(), 531);
1879
1880        let i3_res = rt.eval_sync(None, Script::new("test_proxy.es", "TestClass1.sDoIt();"));
1881
1882        if i3_res.is_err() {
1883            panic!("script failed: {}", i3_res.err().unwrap());
1884        }
1885        let i3 = i3_res.ok().unwrap();
1886
1887        assert!(i3.is_i32());
1888        assert_eq!(i3.get_i32(), 9876);
1889
1890        let i4 = rt
1891            .eval_sync(
1892                None,
1893                Script::new(
1894                    "test_proxy.es",
1895                    "TestClass1.someThing = 1; TestClass1.someThing;",
1896                ),
1897            )
1898            .expect("script failed");
1899
1900        assert!(i4.is_i32());
1901        assert_eq!(i4.get_i32(), 754);
1902
1903        let i5 = rt
1904            .eval_sync(
1905                None,
1906                Script::new(
1907                    "test_proxy.es",
1908                    "let tc5 = new TestClass1(); let r5 = tc5.gVar; tc5 = null; r5;",
1909                ),
1910            )
1911            .expect("script failed");
1912
1913        assert!(i5.is_i32());
1914        assert_eq!(i5.get_i32(), 147);
1915
1916        let i6_res = rt.eval_sync(
1917            None,
1918            Script::new(
1919                "test_proxy.es",
1920                "let tc6 = new TestClass1(); let r6 = tc6.doIt2(); tc6 = null; r6;",
1921            ),
1922        );
1923        assert!(i6_res.is_err());
1924        let e = i6_res.err().unwrap();
1925        let e_msg = e.get_message();
1926        assert_eq!(e_msg, "proxy_instance_method failed: aaargh");
1927
1928        assert!(e.get_stack().contains("[doIt2]"));
1929
1930        rt.gc_sync();
1931
1932        std::thread::sleep(Duration::from_secs(1));
1933
1934        log::info!("< test_proxy");
1935    }
1936
1937    #[test]
1938    pub fn test_constructor() {
1939        // todo init logger
1940
1941        let rt = init_test_rt();
1942        rt.loop_realm_sync(None, |_rt, realm| {
1943            Proxy::new()
1944                .name("TestClass")
1945                .namespace(&["com", "company"])
1946                .constructor(|_rt, _realm, _id, _args| Ok(()))
1947                .finalizer(|_rt, _realm, _id| {
1948                    //
1949                })
1950                .install(realm, true)
1951                .expect("poof");
1952
1953            let constr_str = realm
1954                .eval(Script::new(
1955                    "test_constr.js",
1956                    r#"
1957                let instance = new com.company.TestClass();
1958                const ret = "" + instance.constructor.name;
1959                instance = null;
1960                ret
1961            "#,
1962                ))
1963                .expect("script failed");
1964
1965            println!(
1966                "cons str = {}",
1967                constr_str.to_string().expect("not a string")
1968            );
1969        });
1970    }
1971}