1use crate::dom::Dom;
39use crate::node_id::NodeId;
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48#[non_exhaustive]
49pub enum InteractionKind {
50 Hover,
51 Focus,
52 FocusVisible,
56 Active,
59}
60
61#[derive(Debug, Clone)]
63#[non_exhaustive]
64pub enum Mutation {
65 AttributeChanged {
70 id: NodeId,
71 name: String,
72 old: Option<String>,
73 new: Option<String>,
74 },
75 ClassChanged {
80 id: NodeId,
81 added: Vec<String>,
82 removed: Vec<String>,
83 },
84 ChildListChanged {
88 parent: NodeId,
89 added: Vec<NodeId>,
90 removed: Vec<NodeId>,
91 },
92 CharacterDataChanged {
94 id: NodeId,
95 old: String,
96 new: String,
97 },
98 InteractionChanged {
103 prev: Option<NodeId>,
104 next: Option<NodeId>,
105 kind: InteractionKind,
106 },
107 SelectionChanged {
112 prev: Option<crate::Selection>,
113 next: Option<crate::Selection>,
114 },
115 PreDetach {
134 detached_root: NodeId,
136 focused: Option<NodeId>,
138 hovered: Option<NodeId>,
140 },
141}
142
143pub trait MutationObserver<Ext>: 'static {
151 fn observe(&mut self, dom: &mut Dom<Ext>, record: &Mutation);
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
157pub struct ObserverId(pub(crate) u32);
158
159type ObserverSlot<Ext> = (ObserverId, Option<Box<dyn MutationObserver<Ext>>>);
163
164pub(crate) struct ObserverStore<Ext> {
165 next_id: u32,
166 entries: Vec<ObserverSlot<Ext>>,
167}
168
169impl<Ext> Default for ObserverStore<Ext> {
170 fn default() -> Self {
171 Self {
172 next_id: 0,
173 entries: Vec::new(),
174 }
175 }
176}
177
178impl<Ext> std::fmt::Debug for ObserverStore<Ext> {
179 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180 f.debug_struct("ObserverStore")
181 .field("count", &self.entries.len())
182 .field("next_id", &self.next_id)
183 .finish()
184 }
185}
186
187impl<Ext> ObserverStore<Ext> {
188 pub(crate) fn is_empty(&self) -> bool {
189 self.entries.is_empty()
190 }
191}
192
193impl<Ext: 'static> Dom<Ext> {
196 pub fn add_mutation_observer(
201 &mut self,
202 observer: Box<dyn MutationObserver<Ext>>,
203 ) -> ObserverId {
204 let id = ObserverId(self.observers.next_id);
205 self.observers.next_id += 1;
206 self.observers.entries.push((id, Some(observer)));
207 id
208 }
209
210 pub fn remove_mutation_observer(&mut self, id: ObserverId) -> bool {
215 let before = self.observers.entries.len();
216 self.observers.entries.retain(|(oid, _)| *oid != id);
217 self.observers.entries.len() < before
218 }
219
220 pub fn observer_count(&self) -> usize {
222 self.observers.entries.len()
223 }
224
225 pub(crate) fn fire_mutation(&mut self, record: Mutation) {
232 if self.is_observing {
233 panic!(
234 "rdom-core: mutation attempted inside MutationObserver callback: {:?}. \
235 Observers must not mutate the tree during `observe()`. \
236 Schedule the mutation for after dispatch returns.",
237 record
238 );
239 }
240 if self.observers.is_empty() {
241 return;
242 }
243
244 let ids: Vec<ObserverId> = self.observers.entries.iter().map(|(id, _)| *id).collect();
251 self.is_observing = true;
252 for id in ids {
253 let Some(pos) = self
254 .observers
255 .entries
256 .iter()
257 .position(|(oid, _)| *oid == id)
258 else {
259 continue; };
261 let Some(mut obs) = self.observers.entries[pos].1.take() else {
262 continue;
263 };
264 let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
269 obs.observe(self, &record);
270 }));
271 if let Some(pos) = self
272 .observers
273 .entries
274 .iter()
275 .position(|(oid, _)| *oid == id)
276 {
277 self.observers.entries[pos].1 = Some(obs);
278 }
279 if let Err(payload) = outcome {
282 self.is_observing = false;
283 std::panic::resume_unwind(payload);
284 }
285 }
286 self.is_observing = false;
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use crate::Dom;
294 use std::cell::RefCell;
295 use std::rc::Rc;
296
297 struct Collector {
299 records: Rc<RefCell<Vec<Mutation>>>,
300 }
301 impl MutationObserver<()> for Collector {
302 fn observe(&mut self, _dom: &mut Dom<()>, record: &Mutation) {
303 self.records.borrow_mut().push(record.clone());
304 }
305 }
306
307 fn install_collector(dom: &mut Dom<()>) -> (ObserverId, Rc<RefCell<Vec<Mutation>>>) {
308 let records = Rc::new(RefCell::new(Vec::new()));
309 let obs = Box::new(Collector {
310 records: records.clone(),
311 });
312 let id = dom.add_mutation_observer(obs);
313 (id, records)
314 }
315
316 #[test]
317 fn add_and_remove_observer() {
318 let mut dom: Dom = Dom::new();
319 let (id, _) = install_collector(&mut dom);
320 assert_eq!(dom.observer_count(), 1);
321 assert!(dom.remove_mutation_observer(id));
322 assert_eq!(dom.observer_count(), 0);
323 assert!(!dom.remove_mutation_observer(id));
325 }
326
327 #[test]
332 fn panicking_observer_leaves_dom_usable_and_observers_installed() {
333 struct Bomb;
334 impl MutationObserver<()> for Bomb {
335 fn observe(&mut self, _dom: &mut Dom<()>, _record: &Mutation) {
336 panic!("observer bomb");
337 }
338 }
339 let mut dom: Dom = Dom::new();
340 let root = dom.root();
341 let (_, before) = install_collector(&mut dom);
342 let bomb_id = dom.add_mutation_observer(Box::new(Bomb));
343 let (_, after) = install_collector(&mut dom);
344 assert_eq!(dom.observer_count(), 3);
345
346 let el = dom.create_element("div");
347 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
348 dom.append_child(root, el).unwrap();
349 }));
350 assert!(result.is_err(), "the bomb must actually fire");
351
352 assert_eq!(dom.observer_count(), 3);
354 assert!(dom.remove_mutation_observer(bomb_id));
355 let n_before = before.borrow().len();
356 let n_after = after.borrow().len();
357 dom.set_attribute(el, "id", "x").unwrap();
358 assert_eq!(before.borrow().len(), n_before + 1);
359 assert_eq!(after.borrow().len(), n_after + 1);
360 }
361
362 #[test]
366 fn observer_can_remove_itself_during_callback() {
367 struct SelfRemover {
368 me: Rc<std::cell::Cell<Option<ObserverId>>>,
369 seen: Rc<std::cell::Cell<u32>>,
370 removed: Rc<std::cell::Cell<Option<bool>>>,
371 }
372 impl MutationObserver<()> for SelfRemover {
373 fn observe(&mut self, dom: &mut Dom<()>, _record: &Mutation) {
374 self.seen.set(self.seen.get() + 1);
375 let id = self.me.get().expect("id stored before first mutation");
376 self.removed.set(Some(dom.remove_mutation_observer(id)));
377 }
378 }
379 let mut dom: Dom = Dom::new();
380 let (_, other) = install_collector(&mut dom);
381 let me = Rc::new(std::cell::Cell::new(None));
382 let seen = Rc::new(std::cell::Cell::new(0));
383 let removed = Rc::new(std::cell::Cell::new(None));
384 let id = dom.add_mutation_observer(Box::new(SelfRemover {
385 me: me.clone(),
386 seen: seen.clone(),
387 removed: removed.clone(),
388 }));
389 me.set(Some(id));
390 assert_eq!(dom.observer_count(), 2);
391
392 let el = dom.create_element("div");
393 dom.set_attribute(el, "id", "a").unwrap();
394 assert_eq!(seen.get(), 1);
395 assert_eq!(
396 removed.get(),
397 Some(true),
398 "removal inside observe() succeeds"
399 );
400 assert_eq!(dom.observer_count(), 1);
401
402 let n = other.borrow().len();
403 dom.set_attribute(el, "id", "b").unwrap();
404 assert_eq!(seen.get(), 1, "the removed observer gets nothing more");
405 assert_eq!(
406 other.borrow().len(),
407 n + 1,
408 "the other observer still fires"
409 );
410 }
411
412 #[test]
415 fn observer_added_during_callback_sees_only_later_records() {
416 struct Installer {
417 installed: Rc<std::cell::Cell<bool>>,
418 records: Rc<RefCell<Vec<Mutation>>>,
419 }
420 impl MutationObserver<()> for Installer {
421 fn observe(&mut self, dom: &mut Dom<()>, _record: &Mutation) {
422 if !self.installed.replace(true) {
423 dom.add_mutation_observer(Box::new(Collector {
424 records: self.records.clone(),
425 }));
426 }
427 }
428 }
429 let mut dom: Dom = Dom::new();
430 let late = Rc::new(RefCell::new(Vec::new()));
431 dom.add_mutation_observer(Box::new(Installer {
432 installed: Rc::new(std::cell::Cell::new(false)),
433 records: late.clone(),
434 }));
435 let el = dom.create_element("div");
436 dom.set_attribute(el, "id", "first").unwrap();
437 assert_eq!(dom.observer_count(), 2);
438 assert!(late.borrow().is_empty(), "not the record that installed it");
439 dom.set_attribute(el, "id", "second").unwrap();
440 assert_eq!(late.borrow().len(), 1);
441 }
442
443 #[test]
446 fn observer_can_remove_a_later_observer_before_it_fires() {
447 struct Remover {
448 victim: Rc<std::cell::Cell<Option<ObserverId>>>,
449 }
450 impl MutationObserver<()> for Remover {
451 fn observe(&mut self, dom: &mut Dom<()>, _record: &Mutation) {
452 if let Some(v) = self.victim.take() {
453 assert!(dom.remove_mutation_observer(v));
454 }
455 }
456 }
457 let mut dom: Dom = Dom::new();
458 let victim = Rc::new(std::cell::Cell::new(None));
459 dom.add_mutation_observer(Box::new(Remover {
460 victim: victim.clone(),
461 }));
462 let (victim_id, victim_records) = install_collector(&mut dom);
463 victim.set(Some(victim_id));
464
465 let el = dom.create_element("div");
466 dom.set_attribute(el, "id", "x").unwrap();
467 assert!(
468 victim_records.borrow().is_empty(),
469 "removed before its turn"
470 );
471 assert_eq!(dom.observer_count(), 1);
472 }
473
474 #[test]
475 fn no_observers_means_no_fires() {
476 let mut dom: Dom = Dom::new();
480 let el = dom.create_element("div");
481 let _ = dom.set_attribute(el, "id", "x");
482 assert_eq!(dom.observer_count(), 0);
484 }
485
486 #[test]
487 fn attribute_changed_fires_with_old_new() {
488 let mut dom: Dom = Dom::new();
489 let (_, records) = install_collector(&mut dom);
490 let el = dom.create_element("div");
491 dom.set_attribute(el, "role", "banner").unwrap();
492 dom.set_attribute(el, "role", "navigation").unwrap();
493 dom.remove_attribute(el, "role").unwrap();
494
495 let recs = records.borrow();
496 let matches: Vec<_> = recs
499 .iter()
500 .filter_map(|r| match r {
501 Mutation::AttributeChanged { old, new, .. } => Some((old.clone(), new.clone())),
502 _ => None,
503 })
504 .collect();
505 assert_eq!(matches.len(), 3);
506 assert_eq!(matches[0], (None, Some("banner".into())));
507 assert_eq!(
508 matches[1],
509 (Some("banner".into()), Some("navigation".into()))
510 );
511 assert_eq!(matches[2], (Some("navigation".into()), None));
512 }
513
514 #[test]
515 fn class_changed_fires_add_remove_toggle_replace() {
516 let mut dom: Dom = Dom::new();
517 let (_, records) = install_collector(&mut dom);
518 let el = dom.create_element("div");
519 dom.add_class(el, "active").unwrap();
520 dom.remove_class(el, "active").unwrap();
521 dom.toggle_class(el, "on").unwrap(); dom.toggle_class(el, "on").unwrap(); dom.add_class(el, "old").unwrap();
524 dom.replace_class(el, "old", "new").unwrap();
525
526 let cls_recs: Vec<_> = records
527 .borrow()
528 .iter()
529 .filter_map(|r| match r {
530 Mutation::ClassChanged { added, removed, .. } => {
531 Some((added.clone(), removed.clone()))
532 }
533 _ => None,
534 })
535 .collect();
536 assert_eq!(cls_recs.len(), 6);
539 assert_eq!(cls_recs[0], (vec!["active".to_string()], vec![]));
540 assert_eq!(cls_recs[1], (vec![], vec!["active".to_string()]));
541 assert_eq!(cls_recs[2], (vec!["on".to_string()], vec![]));
542 assert_eq!(cls_recs[3], (vec![], vec!["on".to_string()]));
543 assert_eq!(cls_recs[4], (vec!["old".to_string()], vec![]));
544 assert_eq!(
545 cls_recs[5],
546 (vec!["new".to_string()], vec!["old".to_string()])
547 );
548 }
549
550 #[test]
551 fn child_list_changed_on_append() {
552 let mut dom: Dom = Dom::new();
553 let (_, records) = install_collector(&mut dom);
554 let parent = dom.create_element("div");
555 let child = dom.create_element("span");
556 dom.append_child(parent, child).unwrap();
557
558 let tree_recs: Vec<_> = records
559 .borrow()
560 .iter()
561 .filter_map(|r| match r {
562 Mutation::ChildListChanged {
563 parent,
564 added,
565 removed,
566 } => Some((*parent, added.clone(), removed.clone())),
567 _ => None,
568 })
569 .collect();
570 assert_eq!(tree_recs.len(), 1);
571 assert_eq!(tree_recs[0].0, parent);
572 assert_eq!(tree_recs[0].1, vec![child]);
573 assert_eq!(tree_recs[0].2, Vec::<NodeId>::new());
574 }
575
576 #[test]
577 fn child_list_changed_on_remove() {
578 let mut dom: Dom = Dom::new();
579 let parent = dom.create_element("div");
580 let child = dom.create_element("span");
581 dom.append_child(parent, child).unwrap();
582 let (_, records) = install_collector(&mut dom);
584 dom.remove_child(parent, child).unwrap();
585
586 let rec = records
587 .borrow()
588 .iter()
589 .find(|r| matches!(r, Mutation::ChildListChanged { .. }))
590 .cloned()
591 .unwrap();
592 match rec {
593 Mutation::ChildListChanged { added, removed, .. } => {
594 assert!(added.is_empty());
595 assert_eq!(removed, vec![child]);
596 }
597 _ => unreachable!(),
598 }
599 }
600
601 #[test]
605 fn interaction_changed_fires_on_set_focus_visible() {
606 let mut dom: Dom = Dom::new();
607 let el = dom.create_element("div");
608 dom.set_focused(Some(el));
609 let (_, records) = install_collector(&mut dom);
610 dom.set_focus_visible(false);
611 dom.set_focus_visible(false);
612 dom.set_focus_visible(true);
613
614 let interactions: Vec<_> = records
615 .borrow()
616 .iter()
617 .filter_map(|r| match r {
618 Mutation::InteractionChanged { prev, next, kind } => Some((*prev, *next, *kind)),
619 _ => None,
620 })
621 .collect();
622 let rec = (Some(el), Some(el), InteractionKind::FocusVisible);
623 assert_eq!(interactions, vec![rec, rec]);
624 }
625
626 #[test]
627 fn interaction_changed_fires_on_set_hovered() {
628 let mut dom: Dom = Dom::new();
629 let (_, records) = install_collector(&mut dom);
630 let el = dom.create_element("div");
631 dom.set_hovered(Some(el));
632 dom.set_hovered(None);
633
634 let interactions: Vec<_> = records
635 .borrow()
636 .iter()
637 .filter_map(|r| match r {
638 Mutation::InteractionChanged { prev, next, kind } => Some((*prev, *next, *kind)),
639 _ => None,
640 })
641 .collect();
642 assert_eq!(interactions.len(), 2);
643 assert_eq!(interactions[0], (None, Some(el), InteractionKind::Hover));
644 assert_eq!(interactions[1], (Some(el), None, InteractionKind::Hover));
645 }
646
647 #[test]
648 fn interaction_changed_fires_on_set_focused() {
649 let mut dom: Dom = Dom::new();
650 let (_, records) = install_collector(&mut dom);
651 let a = dom.create_element("a");
652 let b = dom.create_element("b");
653 dom.set_focused(Some(a));
654 dom.set_focused(Some(b));
655
656 let interactions: Vec<_> = records
657 .borrow()
658 .iter()
659 .filter_map(|r| match r {
660 Mutation::InteractionChanged {
661 kind: InteractionKind::Focus,
662 prev,
663 next,
664 } => Some((*prev, *next)),
665 _ => None,
666 })
667 .collect();
668 assert_eq!(interactions.len(), 2);
669 assert_eq!(interactions[0], (None, Some(a)));
670 assert_eq!(interactions[1], (Some(a), Some(b)));
671 }
672
673 #[test]
674 fn set_hovered_to_same_does_not_fire() {
675 let mut dom: Dom = Dom::new();
677 let el = dom.create_element("div");
678 dom.set_hovered(Some(el));
679 let (_, records) = install_collector(&mut dom);
680 dom.set_hovered(Some(el));
681 assert!(
682 records
683 .borrow()
684 .iter()
685 .all(|r| !matches!(r, Mutation::InteractionChanged { .. }))
686 );
687 }
688
689 #[test]
690 fn re_entrant_mutation_panics() {
691 struct EvilObserver {
695 target: NodeId,
696 }
697 impl MutationObserver<()> for EvilObserver {
698 fn observe(&mut self, dom: &mut Dom<()>, _record: &Mutation) {
699 let _ = dom.set_attribute(self.target, "evil", "1");
701 }
702 }
703 let mut dom: Dom = Dom::new();
704 let el = dom.create_element("div");
705 dom.add_mutation_observer(Box::new(EvilObserver { target: el }));
706 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
707 let _ = dom.set_attribute(el, "x", "1");
709 }));
710 assert!(result.is_err(), "expected panic on re-entrant mutation");
711 }
712
713 #[test]
714 fn multiple_observers_all_fire() {
715 let mut dom: Dom = Dom::new();
716 let (_, r1) = install_collector(&mut dom);
717 let (_, r2) = install_collector(&mut dom);
718 let el = dom.create_element("div");
719 dom.set_attribute(el, "id", "x").unwrap();
720 assert!(!r1.borrow().is_empty());
721 assert!(!r2.borrow().is_empty());
722 }
723
724 #[test]
725 fn unregistered_observer_does_not_fire() {
726 let mut dom: Dom = Dom::new();
727 let (id, records) = install_collector(&mut dom);
728 dom.remove_mutation_observer(id);
729 let el = dom.create_element("div");
730 dom.set_attribute(el, "id", "x").unwrap();
731 assert!(records.borrow().is_empty());
732 }
733
734 #[test]
735 fn toggle_attribute_fires_twice() {
736 let mut dom: Dom = Dom::new();
737 let (_, records) = install_collector(&mut dom);
738 let el = dom.create_element("input");
739 dom.toggle_attribute(el, "disabled").unwrap(); dom.toggle_attribute(el, "disabled").unwrap(); let attr_count = records
743 .borrow()
744 .iter()
745 .filter(|r| matches!(r, Mutation::AttributeChanged { .. }))
746 .count();
747 assert_eq!(attr_count, 2);
748 }
749
750 #[test]
751 fn interaction_changed_fires_on_set_active() {
752 let mut dom: Dom = Dom::new();
753 let (_, records) = install_collector(&mut dom);
754 let el = dom.create_element("div");
755 dom.set_active(Some(el));
756 dom.set_active(Some(el));
757 dom.set_active(None);
758
759 let interactions: Vec<_> = records
760 .borrow()
761 .iter()
762 .filter_map(|r| match r {
763 Mutation::InteractionChanged { prev, next, kind } => Some((*prev, *next, *kind)),
764 _ => None,
765 })
766 .collect();
767 assert_eq!(
768 interactions,
769 vec![
770 (None, Some(el), InteractionKind::Active),
771 (Some(el), None, InteractionKind::Active),
772 ]
773 );
774 }
775
776 #[test]
781 fn detach_clears_interaction_state_while_the_ancestor_chain_is_intact() {
782 type Seen = Rc<RefCell<Vec<(InteractionKind, Option<NodeId>)>>>;
783 struct ParentAtRecord {
784 seen: Seen,
785 }
786 impl MutationObserver<()> for ParentAtRecord {
787 fn observe(&mut self, dom: &mut Dom<()>, record: &Mutation) {
788 if let Mutation::InteractionChanged {
789 prev: Some(p),
790 next: None,
791 kind,
792 } = record
793 {
794 let parent = dom.get_node(*p).and_then(|n| n.parent);
795 self.seen.borrow_mut().push((*kind, parent));
796 }
797 }
798 }
799 let mut dom: Dom = Dom::new();
800 let root = dom.root();
801 let li = dom.create_element("li");
802 let span = dom.create_element("span");
803 dom.append_child(root, li).unwrap();
804 dom.append_child(li, span).unwrap();
805 dom.set_hovered(Some(span));
806 dom.set_focused(Some(span));
807 dom.set_active(Some(span));
808 let seen: Seen = Rc::new(RefCell::new(Vec::new()));
809 dom.add_mutation_observer(Box::new(ParentAtRecord { seen: seen.clone() }));
810 dom.remove_child(li, span).unwrap();
811 assert_eq!(
812 (dom.hovered(), dom.focused(), dom.active()),
813 (None, None, None)
814 );
815 assert_eq!(
816 *seen.borrow(),
817 vec![
818 (InteractionKind::Focus, Some(li)),
819 (InteractionKind::Hover, Some(li)),
820 (InteractionKind::Active, Some(li)),
821 ]
822 );
823 }
824}