1use crate::jsutils::JsError;
5use crate::quickjs_utils;
6use crate::quickjs_utils::objects::{create_object_q, set_property_q};
7use crate::quickjs_utils::primitives::from_bool;
8use crate::quickjs_utils::{functions, objects, parse_args, primitives};
9use crate::quickjsrealmadapter::QuickJsRealmAdapter;
10use crate::quickjsvalueadapter::QuickJsValueAdapter;
11use crate::reflection::{get_proxy, get_proxy_instance_info, Proxy};
12use libquickjs_sys as q;
13use std::collections::HashMap;
14
15fn with_proxy_instances_map<C, R>(
16 q_ctx: &QuickJsRealmAdapter,
17 proxy_class_name: &str,
18 consumer: C,
19) -> R
20where
21 C: FnOnce(
22 &HashMap<usize, HashMap<String, HashMap<QuickJsValueAdapter, QuickJsValueAdapter>>>,
23 ) -> R,
24{
25 let listeners = &*q_ctx.proxy_event_listeners.borrow();
26 if listeners.contains_key(proxy_class_name) {
27 let proxy_instance_map = listeners.get(proxy_class_name).unwrap();
28 consumer(proxy_instance_map)
29 } else {
30 consumer(&HashMap::new())
31 }
32}
33
34fn with_proxy_instances_map_mut<C, R>(
35 q_ctx: &QuickJsRealmAdapter,
36 proxy_class_name: &str,
37 consumer: C,
38) -> R
39where
40 C: FnOnce(
41 &mut HashMap<usize, HashMap<String, HashMap<QuickJsValueAdapter, QuickJsValueAdapter>>>,
42 ) -> R,
43{
44 let listeners = &mut *q_ctx.proxy_event_listeners.borrow_mut();
45 if !listeners.contains_key(proxy_class_name) {
46 listeners.insert(proxy_class_name.to_string(), HashMap::new());
47 }
48 let proxy_instance_map = listeners.get_mut(proxy_class_name).unwrap();
49
50 consumer(proxy_instance_map)
51}
52
53fn with_listener_map_mut<C, R>(
54 q_ctx: &QuickJsRealmAdapter,
55 proxy_class_name: &str,
56 instance_id: usize,
57 event_id: &str,
58 consumer: C,
59) -> R
60where
61 C: FnOnce(&mut HashMap<QuickJsValueAdapter, QuickJsValueAdapter>) -> R,
62{
63 with_proxy_instances_map_mut(q_ctx, proxy_class_name, |proxy_instance_map| {
64 let event_id_map = proxy_instance_map.entry(instance_id).or_default();
65
66 if !event_id_map.contains_key(event_id) {
67 event_id_map.insert(event_id.to_string(), HashMap::new());
68 }
69
70 let listener_map = event_id_map.get_mut(event_id).unwrap();
71
72 consumer(listener_map)
73 })
74}
75
76fn with_listener_map<C, R>(
77 q_ctx: &QuickJsRealmAdapter,
78 proxy_class_name: &str,
79 instance_id: usize,
80 event_id: &str,
81 consumer: C,
82) -> R
83where
84 C: FnOnce(&HashMap<QuickJsValueAdapter, QuickJsValueAdapter>) -> R,
85{
86 with_proxy_instances_map(q_ctx, proxy_class_name, |proxy_instance_map| {
87 if let Some(event_id_map) = proxy_instance_map.get(&instance_id) {
88 if let Some(listener_map) = event_id_map.get(event_id) {
89 consumer(listener_map)
90 } else {
91 consumer(&HashMap::new())
92 }
93 } else {
94 consumer(&HashMap::new())
95 }
96 })
97}
98
99fn with_static_listener_map<C, R>(
100 q_ctx: &QuickJsRealmAdapter,
101 proxy_class_name: &str,
102 event_id: &str,
103 consumer: C,
104) -> R
105where
106 C: FnOnce(&mut HashMap<QuickJsValueAdapter, QuickJsValueAdapter>) -> R,
107{
108 let static_listeners = &mut *q_ctx.proxy_static_event_listeners.borrow_mut();
109 if !static_listeners.contains_key(proxy_class_name) {
110 static_listeners.insert(proxy_class_name.to_string(), HashMap::new());
111 }
112 let proxy_static_map = static_listeners.get_mut(proxy_class_name).unwrap();
113 if !proxy_static_map.contains_key(event_id) {
114 proxy_static_map.insert(event_id.to_string(), HashMap::new());
115 }
116 let event_map = proxy_static_map.get_mut(event_id).unwrap();
117 consumer(event_map)
118}
119
120pub fn add_event_listener(
121 q_ctx: &QuickJsRealmAdapter,
122 proxy_class_name: &str,
123 event_id: &str,
124 instance_id: usize,
125 listener_func: QuickJsValueAdapter,
126 options_obj: QuickJsValueAdapter,
127) {
128 log::trace!(
129 "eventtarget::add_listener_to_map p:{} e:{} i:{}",
130 proxy_class_name,
131 event_id,
132 instance_id
133 );
134 with_listener_map_mut(q_ctx, proxy_class_name, instance_id, event_id, |map| {
135 let _ = map.insert(listener_func, options_obj);
136 })
137}
138
139pub fn add_static_event_listener(
140 q_ctx: &QuickJsRealmAdapter,
141 proxy_class_name: &str,
142 event_id: &str,
143 listener_func: QuickJsValueAdapter,
144 options_obj: QuickJsValueAdapter,
145) {
146 log::trace!(
147 "eventtarget::add_static_listener_to_map p:{} e:{}",
148 proxy_class_name,
149 event_id
150 );
151 with_static_listener_map(q_ctx, proxy_class_name, event_id, |map| {
152 let _ = map.insert(listener_func, options_obj);
153 })
154}
155
156pub fn remove_event_listener(
157 q_ctx: &QuickJsRealmAdapter,
158 proxy_class_name: &str,
159 event_id: &str,
160 instance_id: usize,
161 listener_func: &QuickJsValueAdapter,
162) {
163 log::trace!(
164 "eventtarget::remove_listener_from_map p:{} e:{} i:{}",
165 proxy_class_name,
166 event_id,
167 instance_id
168 );
169 with_listener_map_mut(q_ctx, proxy_class_name, instance_id, event_id, |map| {
170 let _ = map.remove(listener_func);
171 })
172}
173
174pub fn remove_static_event_listener(
175 q_ctx: &QuickJsRealmAdapter,
176 proxy_class_name: &str,
177 event_id: &str,
178 listener_func: &QuickJsValueAdapter,
179) {
180 log::trace!(
181 "eventtarget::remove_static_listener_from_map p:{} e:{}",
182 proxy_class_name,
183 event_id
184 );
185 with_static_listener_map(q_ctx, proxy_class_name, event_id, |map| {
186 let _ = map.remove(listener_func);
187 })
188}
189
190fn remove_map(q_ctx: &QuickJsRealmAdapter, proxy_class_name: &str, instance_id: usize) {
191 log::trace!(
192 "eventtarget::remove_map p:{} i:{}",
193 proxy_class_name,
194 instance_id
195 );
196
197 with_proxy_instances_map_mut(q_ctx, proxy_class_name, |map| {
198 let _ = map.remove(&instance_id);
199 });
200}
201
202pub fn dispatch_event(
205 q_ctx: &QuickJsRealmAdapter,
206 proxy: &Proxy,
207 instance_id: usize,
208 event_id: &str,
209 event: QuickJsValueAdapter,
210) -> Result<bool, JsError> {
211 let proxy_class_name = proxy.get_class_name();
212
213 with_listener_map(
214 q_ctx,
215 proxy_class_name.as_str(),
216 instance_id,
217 event_id,
218 |listeners| -> Result<(), JsError> {
219 let func_args = [event];
220 for entry in listeners {
221 let listener = entry.0;
222 let _res = functions::call_function_q(q_ctx, listener, &func_args, None)?;
223
224 }
227 Ok(())
228 },
229 )?;
230
231 Ok(true)
232}
233
234pub fn dispatch_static_event(
237 q_ctx: &QuickJsRealmAdapter,
238 proxy_class_name: &str,
239 event_id: &str,
240 event: QuickJsValueAdapter,
241) -> Result<bool, JsError> {
242 with_static_listener_map(
243 q_ctx,
244 proxy_class_name,
245 event_id,
246 |listeners| -> Result<(), JsError> {
247 let func_args = [event];
248 for entry in listeners {
249 let listener = entry.0;
250 let _res = functions::call_function_q(q_ctx, listener, &func_args, None)?;
251
252 }
255 Ok(())
256 },
257 )?;
258
259 Ok(true)
260}
261
262pub fn _set_event_bubble_target() {
263 unimplemented!()
264}
265
266fn events_instance_finalizer(q_ctx: &QuickJsRealmAdapter, proxy_class_name: &str, id: usize) {
267 remove_map(q_ctx, proxy_class_name, id);
269}
270
271pub(crate) fn impl_event_target(proxy: Proxy) -> Proxy {
272 let proxy_class_name = proxy.get_class_name();
276
277 let mut proxy = proxy;
278 if proxy.is_event_target {
279 proxy = proxy
280 .native_method("addEventListener", Some(ext_add_event_listener))
281 .native_method("removeEventListener", Some(ext_remove_event_listener))
282 .native_method("dispatchEvent", Some(ext_dispatch_event))
283 .finalizer(move |_rt, q_ctx, id| {
284 let n = proxy_class_name.as_str();
285 events_instance_finalizer(q_ctx, n, id);
286 });
287 }
288 if proxy.is_static_event_target {
289 proxy = proxy
291 .static_native_method("addEventListener", Some(ext_add_static_event_listener))
292 .static_native_method(
293 "removeEventListener",
294 Some(ext_remove_static_event_listener),
295 )
296 .static_native_method("dispatchEvent", Some(ext_dispatch_static_event));
297 }
298
299 proxy
300}
301
302unsafe extern "C" fn ext_add_event_listener(
303 ctx: *mut q::JSContext,
304 this_val: q::JSValue,
305 argc: ::std::os::raw::c_int,
306 argv: *mut q::JSValue,
307) -> q::JSValue {
308 let res = QuickJsRealmAdapter::with_context(ctx, |q_ctx| {
315 let args = parse_args(ctx, argc, argv);
316
317 let this_ref =
318 QuickJsValueAdapter::new(ctx, this_val, true, true, "add_event_listener_this");
319
320 let proxy_info = get_proxy_instance_info(this_ref.borrow_value());
321
322 if args.len() < 2 || !args[0].is_string() || !functions::is_function_q(q_ctx, &args[1]) {
323 Err(JsError::new_str("addEventListener requires at least 2 arguments (eventId: String and Listener: Function"))
324 } else {
325 let event_id = primitives::to_string_q(q_ctx, &args[0])?;
326 let listener_func = args[1].clone();
327
328 let options_obj = if args.len() == 3 && args[2].is_object() {
330 args[2].clone()
331 } else {
332 create_object_q(q_ctx)?
333 };
334 if args.len() == 3 && args[2].is_bool() {
336 set_property_q(q_ctx, &options_obj, "capture", &args[2])?;
337 }
338
339 add_event_listener(
340 q_ctx,
341 proxy_info.class_name.as_str(),
342 event_id.as_str(),
343 proxy_info.id,
344 listener_func,
345 options_obj,
346 );
347
348 Ok(())
349 }
350 });
351 match res {
352 Ok(_) => quickjs_utils::new_null(),
353 Err(e) => QuickJsRealmAdapter::report_ex_ctx(ctx, format!("{e}").as_str()),
354 }
355}
356
357unsafe extern "C" fn ext_remove_event_listener(
358 ctx: *mut q::JSContext,
359 this_val: q::JSValue,
360 argc: ::std::os::raw::c_int,
361 argv: *mut q::JSValue,
362) -> q::JSValue {
363 let res = QuickJsRealmAdapter::with_context(ctx, |q_ctx| {
364 let args = parse_args(ctx, argc, argv);
365
366 let this_ref =
367 QuickJsValueAdapter::new(ctx, this_val, true, true, "remove_event_listener_this");
368
369 let proxy_info = get_proxy_instance_info(this_ref.borrow_value());
370
371 if args.len() != 2 || !args[0].is_string() || !functions::is_function_q(q_ctx, &args[1]) {
372 Err(JsError::new_str("removeEventListener requires at least 2 arguments (eventId: String and Listener: Function"))
373 } else {
374 let event_id = primitives::to_string_q(q_ctx, &args[0])?;
375 let listener_func = args[1].clone();
376
377 remove_event_listener(
378 q_ctx,
379 proxy_info.class_name.as_str(),
380 event_id.as_str(),
381 proxy_info.id,
382 &listener_func,
383 );
384
385 Ok(())
386 }
387 });
388 match res {
389 Ok(_) => quickjs_utils::new_null(),
390 Err(e) => QuickJsRealmAdapter::report_ex_ctx(ctx, format!("{e}").as_str()),
391 }
392}
393
394unsafe extern "C" fn ext_dispatch_event(
395 ctx: *mut q::JSContext,
396 this_val: q::JSValue,
397 argc: ::std::os::raw::c_int,
398 argv: *mut q::JSValue,
399) -> q::JSValue {
400 let res = QuickJsRealmAdapter::with_context(ctx, |q_ctx| {
401 let args = parse_args(ctx, argc, argv);
402
403 let this_ref =
404 QuickJsValueAdapter::new(ctx, this_val, true, true, "remove_event_listener_this");
405
406 let proxy_info = get_proxy_instance_info(this_ref.borrow_value());
407
408 if args.len() != 2 || !args[0].is_string() {
409 Err(JsError::new_str(
410 "dispatchEvent requires at least 2 arguments (eventId: String and eventObj: Object)",
411 ))
412 } else {
413 let event_id = primitives::to_string_q(q_ctx, &args[0])?;
414 let evt_obj = args[1].clone();
415
416 let proxy = get_proxy(q_ctx, proxy_info.class_name.as_str()).unwrap();
417
418 let res = dispatch_event(q_ctx, &proxy, proxy_info.id, event_id.as_str(), evt_obj)?;
419
420 Ok(res)
421 }
422 });
423 match res {
424 Ok(res) => {
425 let b_ref = from_bool(res);
426 b_ref.clone_value_incr_rc()
427 }
428 Err(e) => QuickJsRealmAdapter::report_ex_ctx(ctx, format!("{e}").as_str()),
429 }
430}
431
432unsafe fn get_static_proxy_class_name(
433 q_ctx: &QuickJsRealmAdapter,
434 obj: &QuickJsValueAdapter,
435) -> String {
436 let proxy_name_ref = objects::get_property(q_ctx.context, obj, "name")
437 .ok()
438 .unwrap();
439 primitives::to_string(q_ctx.context, &proxy_name_ref)
440 .ok()
441 .unwrap()
442}
443
444unsafe extern "C" fn ext_add_static_event_listener(
445 ctx: *mut q::JSContext,
446 this_val: q::JSValue,
447 argc: ::std::os::raw::c_int,
448 argv: *mut q::JSValue,
449) -> q::JSValue {
450 let res = QuickJsRealmAdapter::with_context(ctx, |q_ctx| {
451 let args = parse_args(ctx, argc, argv);
452
453 let this_ref =
454 QuickJsValueAdapter::new(ctx, this_val, true, true, "add_event_listener_this");
455
456 let proxy_name = get_static_proxy_class_name(q_ctx, &this_ref);
457
458 if args.len() < 2 || !args[0].is_string() || !functions::is_function_q(q_ctx, &args[1]) {
459 Err(JsError::new_str("addEventListener requires at least 2 arguments (eventId: String and Listener: Function"))
460 } else {
461 let event_id = primitives::to_string_q(q_ctx, &args[0])?;
462 let listener_func = args[1].clone();
463
464 let options_obj = if args.len() == 3 && args[2].is_object() {
466 args[2].clone()
467 } else {
468 create_object_q(q_ctx)?
469 };
470 if args.len() == 3 && args[2].is_bool() {
472 set_property_q(q_ctx, &options_obj, "capture", &args[2])?;
473 }
474
475 add_static_event_listener(
476 q_ctx,
477 proxy_name.as_str(),
478 event_id.as_str(),
479 listener_func,
480 options_obj,
481 );
482
483 Ok(())
484 }
485 });
486 match res {
487 Ok(_) => quickjs_utils::new_null(),
488 Err(e) => QuickJsRealmAdapter::report_ex_ctx(ctx, format!("{e}").as_str()),
489 }
490}
491
492unsafe extern "C" fn ext_remove_static_event_listener(
493 ctx: *mut q::JSContext,
494 this_val: q::JSValue,
495 argc: ::std::os::raw::c_int,
496 argv: *mut q::JSValue,
497) -> q::JSValue {
498 let res = QuickJsRealmAdapter::with_context(ctx, |q_ctx| {
499 let args = parse_args(ctx, argc, argv);
500
501 let this_ref =
502 QuickJsValueAdapter::new(ctx, this_val, true, true, "remove_event_listener_this");
503
504 let proxy_name = get_static_proxy_class_name(q_ctx, &this_ref);
505
506 if args.len() != 2 || !args[0].is_string() || !functions::is_function_q(q_ctx, &args[1]) {
507 Err(JsError::new_str("removeEventListener requires at least 2 arguments (eventId: String and Listener: Function"))
508 } else {
509 let event_id = primitives::to_string_q(q_ctx, &args[0])?;
510 let listener_func = args[1].clone();
511
512 remove_static_event_listener(
513 q_ctx,
514 proxy_name.as_str(),
515 event_id.as_str(),
516 &listener_func,
517 );
518
519 Ok(())
520 }
521 });
522 match res {
523 Ok(_) => quickjs_utils::new_null(),
524 Err(e) => QuickJsRealmAdapter::report_ex_ctx(ctx, format!("{e}").as_str()),
525 }
526}
527
528unsafe extern "C" fn ext_dispatch_static_event(
529 ctx: *mut q::JSContext,
530 this_val: q::JSValue,
531 argc: ::std::os::raw::c_int,
532 argv: *mut q::JSValue,
533) -> q::JSValue {
534 let res = QuickJsRealmAdapter::with_context(ctx, |q_ctx| {
535 let args = parse_args(ctx, argc, argv);
536
537 let this_ref =
538 QuickJsValueAdapter::new(ctx, this_val, true, true, "remove_event_listener_this");
539
540 let proxy_name = get_static_proxy_class_name(q_ctx, &this_ref);
541
542 if args.len() != 2 || !args[0].is_string() {
543 Err(JsError::new_str(
544 "dispatchEvent requires at least 2 arguments (eventId: String and eventObj: Object)",
545 ))
546 } else {
547 let event_id = primitives::to_string_q(q_ctx, &args[0])?;
548 let evt_obj = args[1].clone();
549
550 let res =
551 dispatch_static_event(q_ctx, proxy_name.as_str(), event_id.as_str(), evt_obj)?;
552
553 Ok(res)
554 }
555 });
556 match res {
557 Ok(res) => {
558 let b_ref = from_bool(res);
559 b_ref.clone_value_incr_rc()
560 }
561 Err(e) => QuickJsRealmAdapter::report_ex_ctx(ctx, format!("{e}").as_str()),
562 }
563}
564
565#[cfg(test)]
566pub mod tests {
567 use crate::facades::tests::init_test_rt;
568 use crate::jsutils::Script;
569 use crate::quickjs_utils::get_global_q;
570 use crate::quickjs_utils::objects::{create_object_q, get_property_q};
571 use crate::quickjs_utils::primitives::to_i32;
572 use crate::reflection::eventtarget::dispatch_event;
573 use crate::reflection::{get_proxy, Proxy};
574 use std::sync::{Arc, Mutex};
575
576 #[test]
577 fn test_proxy_eh() {
578 let instance_ids: Arc<Mutex<Vec<usize>>> = Arc::new(Mutex::new(vec![]));
579
580 let instance_ids2 = instance_ids.clone();
581
582 let rt = init_test_rt();
583 let ct = rt.exe_rt_task_in_event_loop(move |q_js_rt| {
584 let q_ctx = q_js_rt.get_main_realm();
585 Proxy::new()
586 .namespace(&[])
587 .constructor(move |_rt, _q, id, _args| {
588 log::debug!("construct id={}", id);
589 let vec = &mut *instance_ids2.lock().unwrap();
590 vec.push(id);
591 Ok(())
592 })
593 .finalizer(|_rt, _q_ctx, id| {
594 log::debug!("finalize id={}", id);
595 })
596 .name("MyThing")
597 .event_target()
598 .install(q_ctx, true)
599 .expect("proxy failed");
600
601 match q_ctx.eval(Script::new(
602 "test_proxy_eh.es",
603 "\
604 this.called = false;\
605 let test_proxy_eh_instance = new MyThing();\
606 this.ct = 0;\
607 let listener1 = (evt) => {this.ct++;};\
608 let listener2 = (evt) => {this.ct++;};\
609 test_proxy_eh_instance.addEventListener('someEvent', listener1);\
610 test_proxy_eh_instance.addEventListener('someEvent', listener2);\
611 test_proxy_eh_instance.removeEventListener('someEvent', listener2);\
612 ",
613 )) {
614 Ok(_) => {}
615 Err(e) => {
616 log::error!("script failed: {}", e);
617 panic!("script failed: {}", e);
618 }
619 };
620 let global = get_global_q(q_ctx);
621
622 let proxy = get_proxy(q_ctx, "MyThing").unwrap();
623 let vec = &mut *instance_ids.lock().unwrap();
624 let id = vec[0];
625 let evt = create_object_q(q_ctx).ok().unwrap();
626 let _ = dispatch_event(q_ctx, &proxy, id, "someEvent", evt).expect("dispatch failed");
627
628 let ct_ref = get_property_q(q_ctx, &global, "ct").ok().unwrap();
629
630 to_i32(&ct_ref).ok().unwrap()
631 });
632 log::info!("ok was {}", ct);
633 assert_eq!(ct, 1);
634 }
635
636 #[test]
637 fn test_proxy_eh_rcs() {
638 let rt = init_test_rt();
639 rt.exe_rt_task_in_event_loop(|q_js_rt| {
640 let q_ctx = q_js_rt.get_main_realm();
641 Proxy::new()
642 .namespace(&[])
643 .constructor(move |_rt, _q, id, _args| {
644 log::debug!("construct id={}", id);
645 Ok(())
646 })
647 .finalizer(|_rt, _q_ctx, id| {
648 log::debug!("finalize id={}", id);
649 })
650 .name("MyThing")
651 .event_target()
652 .install(q_ctx, true)
653 .expect("proxy failed");
654
655 q_ctx
656 .eval(Script::new("e.es", "let target = new MyThing();"))
657 .expect("constr failed");
658 let _target_ref = q_ctx
659 .eval(Script::new("t.es", "(target);"))
660 .expect("could not get target");
661
662 #[cfg(feature = "bellard")]
663 assert_eq!(_target_ref.get_ref_count(), 2); q_ctx
666 .eval(Script::new(
667 "r.es",
668 "target.addEventListener('someEvent', (evt) => {console.log('got event');});",
669 ))
670 .expect("addlistnrfailed");
671 #[cfg(feature = "bellard")]
672 assert_eq!(_target_ref.get_ref_count(), 2); });
674 }
675}