1use std::collections::HashMap;
28
29use crate::abort::AbortSignal;
30use crate::dom::Dom;
31use crate::error::{DomError, Result};
32use crate::event::{Event, EventPhase};
33use crate::node_id::NodeId;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub struct ListenerId {
39 pub node: NodeId,
40 pub seq: u32,
41}
42
43pub(crate) struct Listener<Ext: 'static> {
49 seq: u32,
50 event_type: String,
51 capture: bool,
52 once: bool,
53 signal: Option<AbortSignal>,
56 handler: Option<EventHandler<Ext>>,
57}
58
59pub(crate) type EventHandler<Ext> = Box<dyn FnMut(&mut EventCtx<'_, Ext>) + 'static>;
61
62pub struct EventCtx<'a, Ext: 'static> {
64 pub event: &'a mut Event,
65 pub dom: &'a mut Dom<Ext>,
66}
67
68impl<Ext: 'static> EventCtx<'_, Ext> {
69 pub fn request_redraw(&mut self) {
82 self.event.redraw_requested = true;
83 }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum ActivationPhase {
89 Pre,
92 Post { canceled: bool },
97}
98
99pub type ActivationHook<Ext> =
104 Box<dyn FnMut(&mut Dom<Ext>, NodeId, &Event, ActivationPhase) + 'static>;
105
106struct DispatchFlag<'e>(&'e mut Event);
111
112impl<'e> DispatchFlag<'e> {
113 fn set(event: &'e mut Event) -> Self {
114 event.dispatching = true;
115 Self(event)
116 }
117}
118
119impl Drop for DispatchFlag<'_> {
120 fn drop(&mut self) {
121 self.0.dispatching = false;
122 }
123}
124
125impl std::ops::Deref for DispatchFlag<'_> {
126 type Target = Event;
127 fn deref(&self) -> &Event {
128 self.0
129 }
130}
131
132impl std::ops::DerefMut for DispatchFlag<'_> {
133 fn deref_mut(&mut self) -> &mut Event {
134 self.0
135 }
136}
137
138pub(crate) struct ActivationSlot<Ext: 'static>(pub(crate) Option<ActivationHook<Ext>>);
140
141impl<Ext: 'static> std::fmt::Debug for ActivationSlot<Ext> {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 f.write_str(if self.0.is_some() {
144 "ActivationSlot(Some(hook))"
145 } else {
146 "ActivationSlot(None)"
147 })
148 }
149}
150#[derive(Debug, Clone, Default)]
155#[non_exhaustive]
156pub struct ListenerOptions {
157 pub capture: bool,
159 pub once: bool,
161 pub signal: Option<AbortSignal>,
168}
169
170impl ListenerOptions {
171 pub fn capture() -> Self {
172 Self {
173 capture: true,
174 ..Default::default()
175 }
176 }
177 pub fn once() -> Self {
178 Self {
179 once: true,
180 ..Default::default()
181 }
182 }
183 pub fn with_capture(mut self, capture: bool) -> Self {
185 self.capture = capture;
186 self
187 }
188 pub fn with_once(mut self, once: bool) -> Self {
190 self.once = once;
191 self
192 }
193 pub fn with_signal(mut self, signal: AbortSignal) -> Self {
196 self.signal = Some(signal);
197 self
198 }
199}
200
201#[derive(Default)]
204pub(crate) struct ListenerStore<Ext: 'static> {
205 by_node: HashMap<NodeId, Vec<Listener<Ext>>>,
206 next_seq: u32,
208}
209
210impl<Ext: 'static> ListenerStore<Ext> {
211 fn next(&mut self) -> u32 {
212 let n = self.next_seq;
213 self.next_seq = self.next_seq.wrapping_add(1);
214 n
215 }
216}
217
218impl<Ext: 'static> std::fmt::Debug for ListenerStore<Ext> {
219 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220 f.debug_struct("ListenerStore")
221 .field("nodes_with_listeners", &self.by_node.len())
222 .field("next_seq", &self.next_seq)
223 .finish()
224 }
225}
226
227impl<Ext> Dom<Ext> {
232 pub fn add_event_listener(
235 &mut self,
236 node: NodeId,
237 event_type: impl Into<String>,
238 options: ListenerOptions,
239 handler: impl FnMut(&mut EventCtx<'_, Ext>) + 'static,
240 ) -> Result<ListenerId> {
241 self.node_or_err(node)?;
242 let seq = self.listeners.next();
243 let listener = Listener {
244 seq,
245 event_type: event_type.into(),
246 capture: options.capture,
247 once: options.once,
248 signal: options.signal,
249 handler: Some(Box::new(handler)),
250 };
251 self.listeners
256 .by_node
257 .entry(node)
258 .or_default()
259 .push(listener);
260 Ok(ListenerId { node, seq })
261 }
262
263 pub fn remove_event_listener(&mut self, handle: ListenerId) -> bool {
267 let Some(vec) = self.listeners.by_node.get_mut(&handle.node) else {
268 return false;
269 };
270 let before = vec.len();
271 vec.retain(|l| l.seq != handle.seq);
272 let removed = vec.len() < before;
273 if vec.is_empty() {
274 self.listeners.by_node.remove(&handle.node);
275 }
276 removed
277 }
278
279 pub fn listener_count(&self, node: NodeId) -> usize {
281 self.listeners.by_node.get(&node).map_or(0, Vec::len)
282 }
283
284 pub fn set_activation_hook(&mut self, hook: Option<ActivationHook<Ext>>) {
289 self.activation_hook = ActivationSlot(hook);
290 }
291
292 fn run_activation_hook(&mut self, target: NodeId, event: &Event, phase: ActivationPhase) {
297 let Some(mut hook) = self.activation_hook.0.take() else {
298 return;
299 };
300 hook(self, target, event, phase);
301 if self.activation_hook.0.is_none() {
302 self.activation_hook.0 = Some(hook);
303 }
304 }
305
306 pub fn dispatch_event(&mut self, target: NodeId, event: &mut Event) -> Result<()> {
325 self.node_or_err(target)?;
326 if event.dispatching {
327 return Err(DomError::InvalidState("event is already being dispatched"));
331 }
332 self.dispatch_passes(target, &mut DispatchFlag::set(event))?;
336
337 let canceled = event.default_prevented();
341 self.run_activation_hook(target, event, ActivationPhase::Post { canceled });
342 Ok(())
343 }
344
345 fn dispatch_passes(&mut self, target: NodeId, event: &mut Event) -> Result<()> {
349 event.target = Some(target);
350
351 self.run_activation_hook(target, event, ActivationPhase::Pre);
354
355 let path = self.ancestor_path(target);
358 if path.is_empty() {
359 return Err(DomError::InvalidNode(target));
360 }
361
362 event.phase = EventPhase::Capturing;
373 for &node in path.iter().take(path.len() - 1) {
374 if event.propagation_stopped {
375 break;
376 }
377 event.current_target = Some(node);
378 self.fire_at(node, event, PhaseFilter::Capture);
379 }
380 if !event.propagation_stopped {
381 event.phase = EventPhase::AtTarget;
382 event.current_target = Some(target);
383 self.fire_at(target, event, PhaseFilter::Capture);
384 }
385
386 if !event.propagation_stopped {
388 event.phase = EventPhase::AtTarget;
389 event.current_target = Some(target);
390 self.fire_at(target, event, PhaseFilter::Bubble);
391 }
392 if event.bubbles && !event.propagation_stopped {
393 event.phase = EventPhase::Bubbling;
394 for &node in path.iter().rev().skip(1) {
395 if event.propagation_stopped {
396 break;
397 }
398 event.current_target = Some(node);
399 self.fire_at(node, event, PhaseFilter::Bubble);
400 }
401 }
402
403 event.phase = EventPhase::None;
407 event.current_target = None;
408 event.propagation_stopped = false;
409 event.immediate_propagation_stopped = false;
410 Ok(())
411 }
412
413 fn fire_at(&mut self, node: NodeId, event: &mut Event, filter: PhaseFilter) {
421 if let Some(list) = self.listeners.by_node.get_mut(&node) {
426 list.retain(|l| !l.signal.as_ref().is_some_and(|s| s.is_aborted()));
427 if list.is_empty() {
428 self.listeners.by_node.remove(&node);
429 }
430 }
431
432 let to_fire: Vec<(u32, bool)> = match self.listeners.by_node.get(&node) {
434 Some(list) => list
435 .iter()
436 .filter(|l| l.event_type == event.event_type && filter.matches(l.capture))
437 .map(|l| (l.seq, l.once))
438 .collect(),
439 None => return,
440 };
441
442 for (seq, once) in to_fire {
445 if event.immediate_propagation_stopped {
446 break;
447 }
448 let Some(list) = self.listeners.by_node.get_mut(&node) else {
450 return;
451 };
452 let Some(i) = list.iter().position(|l| l.seq == seq) else {
453 continue; };
455 if list[i].signal.as_ref().is_some_and(|s| s.is_aborted()) {
459 list.remove(i);
460 continue;
461 }
462 let Some(mut handler) = list[i].handler.take() else {
464 continue;
466 };
467
468 let outcome = {
473 let mut ctx = EventCtx { event, dom: self };
474 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| handler(&mut ctx)))
475 };
476
477 if let Some(list) = self.listeners.by_node.get_mut(&node) {
479 if let Some(i) = list.iter().position(|l| l.seq == seq) {
480 if once {
481 list.remove(i);
482 } else {
483 list[i].handler = Some(handler);
484 }
485 }
486 if list.is_empty() {
487 self.listeners.by_node.remove(&node);
488 }
489 }
490 if let Err(payload) = outcome {
495 std::panic::resume_unwind(payload);
496 }
497 }
498 }
499
500 pub(crate) fn drop_listeners(&mut self, node: NodeId) {
503 self.listeners.by_node.remove(&node);
504 }
505}
506
507#[derive(Debug, Clone, Copy)]
509enum PhaseFilter {
510 Capture,
511 Bubble,
512}
513
514impl PhaseFilter {
515 fn matches(self, listener_capture: bool) -> bool {
516 match self {
517 PhaseFilter::Capture => listener_capture,
518 PhaseFilter::Bubble => !listener_capture,
519 }
520 }
521}
522
523#[cfg(test)]
524mod tests {
525 use super::*;
526 use crate::event::Event;
527 use std::cell::Cell;
528 use std::rc::Rc;
529
530 fn build_chain() -> (Dom, NodeId, NodeId, NodeId, NodeId) {
532 let mut dom: Dom = Dom::new();
533 let root = dom.root();
534 let a = dom.create_element("a");
535 let b = dom.create_element("b");
536 let c = dom.create_element("c");
537 dom.append_child(a, b).unwrap();
538 dom.append_child(b, c).unwrap();
539 dom.append_child(root, a).unwrap();
540 (dom, a, b, c, root)
541 }
542
543 #[test]
547 fn panicking_listener_is_restored_after_the_panic() {
548 let mut dom: Dom = Dom::new();
549 let el = dom.create_element("div");
550 let fired = Rc::new(Cell::new(0));
551 let f2 = fired.clone();
552 dom.add_event_listener(el, "click", ListenerOptions::default(), move |_| {
553 f2.set(f2.get() + 1);
554 if f2.get() == 1 {
555 panic!("listener bomb");
556 }
557 })
558 .unwrap();
559
560 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
561 let mut e = Event::new("click");
562 dom.dispatch_event(el, &mut e).unwrap();
563 }));
564 assert!(result.is_err());
565 assert_eq!(fired.get(), 1);
566 assert_eq!(dom.listener_count(el), 1);
567
568 let mut e = Event::new("click");
570 dom.dispatch_event(el, &mut e).unwrap();
571 assert_eq!(fired.get(), 2);
572 }
573
574 #[test]
579 fn an_early_invalid_node_return_clears_the_dispatch_flag() {
580 let (mut dom, a, _, c, _) = build_chain();
581 dom.set_activation_hook(Some(Box::new(|dom, target, _, phase| {
582 if phase == ActivationPhase::Pre && dom.contains(target) {
583 let parent = dom.node(target).parent_node().unwrap().id();
584 dom.remove_child_dropping(parent, target).unwrap();
585 }
586 })));
587 let mut e = Event::new("click");
588 assert!(matches!(
589 dom.dispatch_event(c, &mut e),
590 Err(DomError::InvalidNode(_))
591 ));
592 dom.set_activation_hook(None);
593 assert_eq!(dom.dispatch_event(a, &mut e), Ok(()), "retry succeeds");
594 }
595
596 #[test]
600 fn a_listener_panic_clears_the_dispatch_flag() {
601 let mut dom: Dom = Dom::new();
602 let el = dom.create_element("div");
603 dom.add_event_listener(el, "boom", ListenerOptions::default(), |_| {
604 panic!("listener bomb");
605 })
606 .unwrap();
607 let mut e = Event::new("click");
608 let mut boom = Event::new("boom");
609 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
610 let _ = dom.dispatch_event(el, &mut boom);
611 }));
612 assert!(result.is_err());
613 boom.event_type = "click".into();
614 assert_eq!(dom.dispatch_event(el, &mut boom), Ok(()));
615 assert_eq!(dom.dispatch_event(el, &mut e), Ok(()));
616 }
617
618 fn cap() -> ListenerOptions {
619 ListenerOptions {
620 capture: true,
621 ..ListenerOptions::default()
622 }
623 }
624
625 fn log_listener(
626 log: &Rc<std::cell::RefCell<Vec<&'static str>>>,
627 tag: &'static str,
628 ) -> impl FnMut(&mut EventCtx<'_, ()>) + 'static {
629 let log = log.clone();
630 move |_| log.borrow_mut().push(tag)
631 }
632
633 #[test]
637 fn at_target_capture_listeners_fire_before_bubble_listeners() {
638 let (mut dom, _, _, c, _) = build_chain();
639 let log = Rc::new(std::cell::RefCell::new(Vec::new()));
640 dom.add_event_listener(
643 c,
644 "click",
645 ListenerOptions::default(),
646 log_listener(&log, "b1"),
647 )
648 .unwrap();
649 dom.add_event_listener(c, "click", cap(), log_listener(&log, "c1"))
650 .unwrap();
651 dom.add_event_listener(
652 c,
653 "click",
654 ListenerOptions::default(),
655 log_listener(&log, "b2"),
656 )
657 .unwrap();
658 dom.add_event_listener(c, "click", cap(), log_listener(&log, "c2"))
659 .unwrap();
660
661 let mut e = Event::new("click");
662 dom.dispatch_event(c, &mut e).unwrap();
663 assert_eq!(*log.borrow(), vec!["c1", "c2", "b1", "b2"]);
664 }
665
666 #[test]
669 fn stop_propagation_in_target_capture_listener_suppresses_target_bubble_listeners() {
670 let (mut dom, _, _, c, _) = build_chain();
671 let log = Rc::new(std::cell::RefCell::new(Vec::new()));
672 dom.add_event_listener(
673 c,
674 "click",
675 ListenerOptions::default(),
676 log_listener(&log, "bubble"),
677 )
678 .unwrap();
679 {
680 let log = log.clone();
681 dom.add_event_listener(c, "click", cap(), move |ctx| {
682 log.borrow_mut().push("capture");
683 ctx.event.stop_propagation();
684 })
685 .unwrap();
686 }
687 let mut e = Event::new("click");
688 dom.dispatch_event(c, &mut e).unwrap();
689 assert_eq!(*log.borrow(), vec!["capture"]);
690 }
691
692 #[test]
694 fn at_target_phase_is_reported_for_both_capture_and_bubble_listeners() {
695 let (mut dom, _, _, c, _) = build_chain();
696 let phases = Rc::new(std::cell::RefCell::new(Vec::new()));
697 for opts in [cap(), ListenerOptions::default()] {
698 let phases = phases.clone();
699 dom.add_event_listener(c, "click", opts, move |ctx| {
700 phases.borrow_mut().push(ctx.event.phase);
701 })
702 .unwrap();
703 }
704 let mut e = Event::new("click");
705 dom.dispatch_event(c, &mut e).unwrap();
706 assert_eq!(
707 *phases.borrow(),
708 vec![EventPhase::AtTarget, EventPhase::AtTarget]
709 );
710 }
711
712 #[test]
716 fn propagation_flags_reset_after_dispatch_so_event_can_be_redispatched() {
717 let (mut dom, _, _, c, _) = build_chain();
718 let fired = Rc::new(Cell::new(0));
719 {
720 let fired = fired.clone();
721 dom.add_event_listener(c, "click", ListenerOptions::default(), move |ctx| {
722 fired.set(fired.get() + 1);
723 ctx.event.stop_immediate_propagation();
724 ctx.event.prevent_default();
725 })
726 .unwrap();
727 }
728 let mut e = Event::new("click");
729 dom.dispatch_event(c, &mut e).unwrap();
730 assert!(!e.is_propagation_stopped());
731 assert!(!e.is_immediate_propagation_stopped());
732 assert!(e.default_prevented(), "canceled flag persists");
733
734 dom.dispatch_event(c, &mut e).unwrap();
735 assert_eq!(fired.get(), 2, "second dispatch reaches the listener again");
736 }
737
738 #[test]
743 fn redispatching_an_in_flight_event_is_an_error_and_keeps_outer_flags() {
744 let (mut dom, a, _, c, _) = build_chain();
745 let inner_result = Rc::new(std::cell::RefCell::new(None));
746 let target_fired = Rc::new(Cell::new(false));
747 {
748 let inner_result = inner_result.clone();
749 dom.add_event_listener(a, "click", cap(), move |ctx| {
750 ctx.event.stop_propagation();
751 let r = ctx.dom.dispatch_event(a, ctx.event);
752 *inner_result.borrow_mut() = Some(r);
753 })
754 .unwrap();
755 }
756 {
757 let target_fired = target_fired.clone();
758 dom.add_event_listener(c, "click", ListenerOptions::default(), move |_| {
759 target_fired.set(true);
760 })
761 .unwrap();
762 }
763 let mut e = Event::new("click");
764 dom.dispatch_event(c, &mut e).unwrap();
765 assert!(matches!(
766 inner_result.borrow().as_ref(),
767 Some(Err(DomError::InvalidState(_)))
768 ));
769 assert!(
770 !target_fired.get(),
771 "outer stop_propagation survived the inner attempt"
772 );
773 dom.dispatch_event(c, &mut e).unwrap();
775 }
776
777 #[test]
778 fn stale_id_is_rejected_by_dispatch_and_add_event_listener() {
779 let mut dom: Dom = Dom::new();
780 let root = dom.root();
781 let el = dom.create_element("div");
782 dom.append_child(root, el).unwrap();
783 dom.remove_child_dropping(root, el).unwrap();
784 let _reuses_slot = dom.create_element("span");
785 let mut e = Event::new("click");
786 assert!(matches!(
787 dom.dispatch_event(el, &mut e).unwrap_err(),
788 DomError::InvalidNode(_)
789 ));
790 assert!(matches!(
791 dom.add_event_listener(el, "click", ListenerOptions::default(), |_| {})
792 .unwrap_err(),
793 DomError::InvalidNode(_)
794 ));
795 }
796
797 #[test]
802 fn activation_hook_runs_pre_before_listeners_and_post_after_even_when_propagation_stops() {
803 let (mut dom, _, _, c, _) = build_chain();
804 let log = Rc::new(std::cell::RefCell::new(Vec::<String>::new()));
805 {
806 let log = log.clone();
807 dom.set_activation_hook(Some(Box::new(move |_dom, target, event, phase| {
808 log.borrow_mut()
809 .push(format!("{:?} {} {}", phase, event.event_type, target == c));
810 })));
811 }
812 {
813 let log = log.clone();
814 dom.add_event_listener(c, "click", ListenerOptions::default(), move |ctx| {
815 log.borrow_mut().push("listener".to_string());
816 ctx.event.stop_propagation();
817 ctx.event.prevent_default();
818 })
819 .unwrap();
820 }
821 let mut e = Event::new("click");
822 e.cancelable = true;
823 dom.dispatch_event(c, &mut e).unwrap();
824 assert_eq!(
825 *log.borrow(),
826 vec![
827 "Pre click true".to_string(),
828 "listener".to_string(),
829 "Post { canceled: true } click true".to_string(),
830 ]
831 );
832 dom.set_activation_hook(None);
834 dom.dispatch_event(c, &mut Event::new("click")).unwrap();
835 assert_eq!(log.borrow().len(), 4, "only the listener ran");
836 }
837
838 #[test]
839 fn add_and_remove_listener() {
840 let mut dom: Dom = Dom::new();
841 let el = dom.create_element("div");
842 let fired = Rc::new(Cell::new(0));
843 let f2 = fired.clone();
844 let id = dom
845 .add_event_listener(el, "click", ListenerOptions::default(), move |_| {
846 f2.set(f2.get() + 1);
847 })
848 .unwrap();
849 assert_eq!(dom.listener_count(el), 1);
850
851 let mut e = Event::new("click");
852 dom.dispatch_event(el, &mut e).unwrap();
853 assert_eq!(fired.get(), 1);
854
855 assert!(dom.remove_event_listener(id));
856 assert_eq!(dom.listener_count(el), 0);
857
858 let mut e2 = Event::new("click");
859 dom.dispatch_event(el, &mut e2).unwrap();
860 assert_eq!(fired.get(), 1); }
862
863 #[test]
864 fn capture_target_bubble_ordering() {
865 let (mut dom, a, b, c, _) = build_chain();
866 let order = Rc::new(std::cell::RefCell::new(Vec::<&'static str>::new()));
867
868 let o = order.clone();
869 dom.add_event_listener(a, "click", ListenerOptions::capture(), move |_| {
870 o.borrow_mut().push("a-capture");
871 })
872 .unwrap();
873 let o = order.clone();
874 dom.add_event_listener(b, "click", ListenerOptions::capture(), move |_| {
875 o.borrow_mut().push("b-capture");
876 })
877 .unwrap();
878 let o = order.clone();
879 dom.add_event_listener(c, "click", ListenerOptions::default(), move |_| {
880 o.borrow_mut().push("c-target");
881 })
882 .unwrap();
883 let o = order.clone();
884 dom.add_event_listener(b, "click", ListenerOptions::default(), move |_| {
885 o.borrow_mut().push("b-bubble");
886 })
887 .unwrap();
888 let o = order.clone();
889 dom.add_event_listener(a, "click", ListenerOptions::default(), move |_| {
890 o.borrow_mut().push("a-bubble");
891 })
892 .unwrap();
893
894 let mut e = Event::new("click");
895 dom.dispatch_event(c, &mut e).unwrap();
896
897 assert_eq!(
898 *order.borrow(),
899 vec!["a-capture", "b-capture", "c-target", "b-bubble", "a-bubble"]
900 );
901 }
902
903 #[test]
904 fn stop_propagation_cuts_bubble() {
905 let (mut dom, a, b, c, _) = build_chain();
906 let a_fired = Rc::new(Cell::new(false));
907 let af = a_fired.clone();
908 dom.add_event_listener(a, "click", ListenerOptions::default(), move |_| {
909 af.set(true);
910 })
911 .unwrap();
912 dom.add_event_listener(b, "click", ListenerOptions::default(), move |ctx| {
913 ctx.event.stop_propagation();
914 })
915 .unwrap();
916
917 let mut e = Event::new("click");
918 dom.dispatch_event(c, &mut e).unwrap();
919 assert!(!a_fired.get());
920 }
921
922 #[test]
923 fn stop_immediate_cuts_sibling_listener_on_same_node() {
924 let mut dom: Dom = Dom::new();
925 let el = dom.create_element("div");
926 let hit_first = Rc::new(Cell::new(false));
927 let hit_second = Rc::new(Cell::new(false));
928 let h1 = hit_first.clone();
929 let h2 = hit_second.clone();
930 dom.add_event_listener(el, "x", ListenerOptions::default(), move |ctx| {
931 h1.set(true);
932 ctx.event.stop_immediate_propagation();
933 })
934 .unwrap();
935 dom.add_event_listener(el, "x", ListenerOptions::default(), move |_| {
936 h2.set(true);
937 })
938 .unwrap();
939
940 let mut e = Event::new("x");
941 dom.dispatch_event(el, &mut e).unwrap();
942 assert!(hit_first.get());
943 assert!(!hit_second.get());
944 }
945
946 #[test]
947 fn non_bubbling_skips_bubble_phase() {
948 let (mut dom, a, _, c, _) = build_chain();
949 let a_fired = Rc::new(Cell::new(false));
950 let af = a_fired.clone();
951 dom.add_event_listener(a, "x", ListenerOptions::default(), move |_| {
952 af.set(true);
953 })
954 .unwrap();
955
956 let mut e = Event::new("x").with_bubbles(false);
957 dom.dispatch_event(c, &mut e).unwrap();
958 assert!(!a_fired.get());
959 }
960
961 #[test]
962 fn prevent_default_sets_flag() {
963 let mut dom: Dom = Dom::new();
964 let el = dom.create_element("div");
965 dom.add_event_listener(el, "click", ListenerOptions::default(), move |ctx| {
966 ctx.event.prevent_default();
967 })
968 .unwrap();
969 let mut e = Event::new("click");
970 dom.dispatch_event(el, &mut e).unwrap();
971 assert!(e.default_prevented());
972 }
973
974 #[test]
975 fn once_removes_after_firing() {
976 let mut dom: Dom = Dom::new();
977 let el = dom.create_element("div");
978 let fired = Rc::new(Cell::new(0));
979 let f = fired.clone();
980 dom.add_event_listener(el, "x", ListenerOptions::once(), move |_| {
981 f.set(f.get() + 1);
982 })
983 .unwrap();
984
985 let mut e1 = Event::new("x");
986 dom.dispatch_event(el, &mut e1).unwrap();
987 let mut e2 = Event::new("x");
988 dom.dispatch_event(el, &mut e2).unwrap();
989 assert_eq!(fired.get(), 1);
990 assert_eq!(dom.listener_count(el), 0);
991 }
992
993 #[test]
994 fn listener_type_is_filtered() {
995 let mut dom: Dom = Dom::new();
996 let el = dom.create_element("div");
997 let click = Rc::new(Cell::new(0));
998 let input = Rc::new(Cell::new(0));
999 let cc = click.clone();
1000 let ii = input.clone();
1001 dom.add_event_listener(el, "click", ListenerOptions::default(), move |_| {
1002 cc.set(cc.get() + 1);
1003 })
1004 .unwrap();
1005 dom.add_event_listener(el, "input", ListenerOptions::default(), move |_| {
1006 ii.set(ii.get() + 1);
1007 })
1008 .unwrap();
1009 let mut e = Event::new("click");
1010 dom.dispatch_event(el, &mut e).unwrap();
1011 assert_eq!(click.get(), 1);
1012 assert_eq!(input.get(), 0);
1013 }
1014
1015 #[test]
1016 fn handler_may_read_dom() {
1017 let mut dom: Dom = Dom::new();
1018 let root = dom.root();
1019 let a = dom.create_element("a");
1020 dom.set_attribute(a, "id", "target").unwrap();
1021 dom.append_child(root, a).unwrap();
1022 let seen = Rc::new(Cell::new(false));
1023 let s = seen.clone();
1024 dom.add_event_listener(a, "click", ListenerOptions::default(), move |ctx| {
1025 if ctx.dom.get_element_by_id("target").is_some() {
1026 s.set(true);
1027 }
1028 })
1029 .unwrap();
1030
1031 let mut e = Event::new("click");
1032 dom.dispatch_event(a, &mut e).unwrap();
1033 assert!(seen.get());
1034 }
1035
1036 #[test]
1037 fn handler_may_mutate_dom() {
1038 let mut dom: Dom = Dom::new();
1039 let el = dom.create_element("div");
1040 dom.add_event_listener(el, "click", ListenerOptions::default(), move |ctx| {
1041 let added = ctx.dom.create_element("added");
1042 ctx.dom.append_child(el, added).unwrap();
1043 })
1044 .unwrap();
1045 let mut e = Event::new("click");
1046 dom.dispatch_event(el, &mut e).unwrap();
1047 assert_eq!(dom.node(el).child_element_count(), 1);
1048 }
1049
1050 #[test]
1051 fn dispatch_on_orphan_node_still_fires_target() {
1052 let mut dom: Dom = Dom::new();
1053 let el = dom.create_element("div");
1054 let fired = Rc::new(Cell::new(false));
1055 let f = fired.clone();
1056 dom.add_event_listener(el, "click", ListenerOptions::default(), move |_| {
1057 f.set(true);
1058 })
1059 .unwrap();
1060 let mut e = Event::new("click");
1061 dom.dispatch_event(el, &mut e).unwrap();
1062 assert!(fired.get());
1063 }
1064
1065 #[test]
1066 fn current_target_reflects_node_during_dispatch() {
1067 let (mut dom, a, _b, c, _) = build_chain();
1068 let ct_at_a = Rc::new(Cell::new(None));
1069 let p = ct_at_a.clone();
1070 dom.add_event_listener(a, "click", ListenerOptions::default(), move |ctx| {
1071 p.set(ctx.event.current_target);
1072 })
1073 .unwrap();
1074 let mut e = Event::new("click");
1075 dom.dispatch_event(c, &mut e).unwrap();
1076 assert_eq!(ct_at_a.get(), Some(a));
1077 assert_eq!(e.current_target, None);
1079 }
1080
1081 #[test]
1082 fn removing_node_unregisters_listeners_via_drop_subtree() {
1083 let mut dom: Dom = Dom::new();
1084 let el = dom.create_element("div");
1085 let _ = dom
1086 .add_event_listener(el, "click", ListenerOptions::default(), |_| {})
1087 .unwrap();
1088 let root = dom.root();
1089 dom.append_child(root, el).unwrap();
1090 dom.drop_subtree(el).unwrap();
1091 assert_eq!(dom.listener_count(el), 0);
1092 }
1093
1094 #[test]
1097 fn abort_signal_removes_listener_on_next_dispatch() {
1098 use crate::AbortController;
1099
1100 let mut dom: Dom = Dom::new();
1101 let el = dom.create_element("div");
1102 let fired = Rc::new(Cell::new(0));
1103 let f = fired.clone();
1104
1105 let ctrl = AbortController::new();
1106 let sig = ctrl.signal();
1107 dom.add_event_listener(
1108 el,
1109 "click",
1110 ListenerOptions::default().with_signal(sig),
1111 move |_| {
1112 f.set(f.get() + 1);
1113 },
1114 )
1115 .unwrap();
1116
1117 dom.dispatch_event(el, &mut Event::new("click")).unwrap();
1119 assert_eq!(fired.get(), 1);
1120
1121 ctrl.abort();
1123 dom.dispatch_event(el, &mut Event::new("click")).unwrap();
1124 assert_eq!(fired.get(), 1);
1125 assert_eq!(dom.listener_count(el), 0);
1126 }
1127
1128 #[test]
1129 fn abort_signal_removes_multiple_listeners_at_once() {
1130 use crate::AbortController;
1131
1132 let mut dom: Dom = Dom::new();
1133 let el = dom.create_element("div");
1134 let ctrl = AbortController::new();
1135 let sig = ctrl.signal();
1136
1137 for _ in 0..5 {
1138 dom.add_event_listener(
1139 el,
1140 "click",
1141 ListenerOptions::default().with_signal(sig.clone()),
1142 |_| {},
1143 )
1144 .unwrap();
1145 }
1146 assert_eq!(dom.listener_count(el), 5);
1147
1148 ctrl.abort();
1149 dom.dispatch_event(el, &mut Event::new("click")).unwrap();
1150 assert_eq!(dom.listener_count(el), 0);
1151 }
1152
1153 #[test]
1154 fn abort_signal_independent_of_other_listeners() {
1155 use crate::AbortController;
1156
1157 let mut dom: Dom = Dom::new();
1158 let el = dom.create_element("div");
1159 let ctrl = AbortController::new();
1160 let sig = ctrl.signal();
1161
1162 let fired_aborted = Rc::new(Cell::new(0));
1163 let fa = fired_aborted.clone();
1164 dom.add_event_listener(
1165 el,
1166 "click",
1167 ListenerOptions::default().with_signal(sig),
1168 move |_| {
1169 fa.set(fa.get() + 1);
1170 },
1171 )
1172 .unwrap();
1173
1174 let fired_normal = Rc::new(Cell::new(0));
1175 let fn_ = fired_normal.clone();
1176 dom.add_event_listener(el, "click", ListenerOptions::default(), move |_| {
1177 fn_.set(fn_.get() + 1);
1178 })
1179 .unwrap();
1180
1181 ctrl.abort();
1182 dom.dispatch_event(el, &mut Event::new("click")).unwrap();
1183 assert_eq!(fired_aborted.get(), 0, "aborted listener must not fire");
1184 assert_eq!(fired_normal.get(), 1, "unrelated listener still fires");
1185 }
1186
1187 #[test]
1188 fn adding_listener_with_already_aborted_signal_never_fires() {
1189 use crate::AbortController;
1190
1191 let mut dom: Dom = Dom::new();
1192 let el = dom.create_element("div");
1193 let ctrl = AbortController::new();
1194 ctrl.abort();
1195
1196 let fired = Rc::new(Cell::new(0));
1197 let f = fired.clone();
1198 dom.add_event_listener(
1199 el,
1200 "click",
1201 ListenerOptions::default().with_signal(ctrl.signal()),
1202 move |_| {
1203 f.set(f.get() + 1);
1204 },
1205 )
1206 .unwrap();
1207
1208 dom.dispatch_event(el, &mut Event::new("click")).unwrap();
1209 assert_eq!(fired.get(), 0);
1210 assert_eq!(dom.listener_count(el), 0);
1211 }
1212
1213 #[test]
1214 fn handler_aborting_mid_dispatch_skips_later_listeners_on_same_node() {
1215 use crate::AbortController;
1216
1217 let mut dom: Dom = Dom::new();
1218 let el = dom.create_element("div");
1219 let ctrl = AbortController::new();
1220
1221 let order = Rc::new(std::cell::RefCell::new(Vec::<&'static str>::new()));
1222
1223 let order_a = order.clone();
1226 let ctrl_a = ctrl.clone();
1227 dom.add_event_listener(el, "click", ListenerOptions::default(), move |_| {
1228 order_a.borrow_mut().push("a");
1229 ctrl_a.abort();
1230 })
1231 .unwrap();
1232
1233 let order_b = order.clone();
1236 dom.add_event_listener(
1237 el,
1238 "click",
1239 ListenerOptions::default().with_signal(ctrl.signal()),
1240 move |_| {
1241 order_b.borrow_mut().push("b");
1242 },
1243 )
1244 .unwrap();
1245
1246 let order_c = order.clone();
1248 dom.add_event_listener(el, "click", ListenerOptions::default(), move |_| {
1249 order_c.borrow_mut().push("c");
1250 })
1251 .unwrap();
1252
1253 dom.dispatch_event(el, &mut Event::new("click")).unwrap();
1254 assert_eq!(*order.borrow(), vec!["a", "c"]);
1255 }
1256
1257 #[test]
1258 fn signal_clone_governs_same_listener() {
1259 use crate::AbortController;
1260
1261 let mut dom: Dom = Dom::new();
1262 let el = dom.create_element("div");
1263 let ctrl = AbortController::new();
1264 let sig1 = ctrl.signal();
1265 let sig2 = sig1.clone();
1266
1267 let fired = Rc::new(Cell::new(0));
1268 let f = fired.clone();
1269 dom.add_event_listener(
1270 el,
1271 "click",
1272 ListenerOptions::default().with_signal(sig2),
1273 move |_| {
1274 f.set(f.get() + 1);
1275 },
1276 )
1277 .unwrap();
1278
1279 assert!(!sig1.is_aborted());
1281 dom.dispatch_event(el, &mut Event::new("click")).unwrap();
1282 assert_eq!(fired.get(), 1);
1283
1284 ctrl.abort();
1286 assert!(sig1.is_aborted());
1287 dom.dispatch_event(el, &mut Event::new("click")).unwrap();
1288 assert_eq!(fired.get(), 1);
1289 }
1290}