use crate::compat::Vec;
use crate::core::ObjectId;
use crate::event::Event;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DispatchPhase {
Capture,
Target,
Bubble,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Propagation {
#[default]
Continue,
Consumed,
}
thread_local! {
static CONSUMED: core::cell::Cell<bool> = const { core::cell::Cell::new(false) };
}
pub fn consume_event() -> bool {
CONSUMED.try_with(|cell| cell.replace(true)).unwrap_or(false)
}
pub fn is_consumed() -> bool {
CONSUMED.try_with(|cell| cell.get()).unwrap_or(false)
}
pub fn take_consumed() -> bool {
CONSUMED.try_with(|cell| cell.replace(false)).unwrap_or(false)
}
pub fn reset_consumed() {
let _ = CONSUMED.try_with(|cell| cell.set(false));
}
pub fn route_event(
target: ObjectId,
ancestors: &[ObjectId],
event: &Event,
handler: &mut impl FnMut(ObjectId, &Event, DispatchPhase),
) -> Propagation {
reset_consumed();
for &ancestor in ancestors.iter() {
handler(ancestor, event, DispatchPhase::Capture);
if take_consumed() {
return Propagation::Consumed;
}
}
handler(target, event, DispatchPhase::Target);
if take_consumed() {
return Propagation::Consumed;
}
for &ancestor in ancestors.iter().rev() {
handler(ancestor, event, DispatchPhase::Bubble);
if take_consumed() {
return Propagation::Consumed;
}
}
Propagation::Continue
}
pub fn ancestor_chain(
target: ObjectId,
parent_of: &dyn Fn(ObjectId) -> Option<ObjectId>,
) -> Vec<ObjectId> {
let mut chain = Vec::new();
fill_ancestor_chain(target, parent_of, &mut chain);
chain
}
fn fill_ancestor_chain(
target: ObjectId,
parent_of: &dyn Fn(ObjectId) -> Option<ObjectId>,
chain: &mut Vec<ObjectId>,
) {
chain.clear();
let mut current = parent_of(target);
for _ in 0..1024 {
let Some(parent) = current else {
break;
};
chain.push(parent);
current = parent_of(parent);
}
chain.reverse();
}
thread_local! {
static ANCESTOR_BUFFER: core::cell::RefCell<Vec<ObjectId>> =
const { core::cell::RefCell::new(Vec::new()) };
}
struct AncestorBuffer(Vec<ObjectId>);
impl Drop for AncestorBuffer {
fn drop(&mut self) {
self.0.clear();
let _ = ANCESTOR_BUFFER.try_with(|slot| {
*slot.borrow_mut() = core::mem::take(&mut self.0);
});
}
}
pub(crate) fn with_ancestor_chain<R>(
target: ObjectId,
parent_of: &dyn Fn(ObjectId) -> Option<ObjectId>,
f: impl FnOnce(&[ObjectId]) -> R,
) -> R {
let mut buffer =
AncestorBuffer(ANCESTOR_BUFFER.with(|slot| core::mem::take(&mut *slot.borrow_mut())));
fill_ancestor_chain(target, parent_of, &mut buffer.0);
f(&buffer.0)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compat::vec;
#[test]
fn an_unconsumed_event_travels_the_whole_route() {
use std::cell::RefCell;
use std::rc::Rc;
let seen = Rc::new(RefCell::new(Vec::new()));
let log = Rc::clone(&seen);
let propagation = route_event(3, &[1, 2], &Event::Paint, &mut |node, _, phase| {
log.borrow_mut().push((node, phase));
});
assert_eq!(propagation, Propagation::Continue);
let seen = seen.borrow();
assert_eq!(
*seen,
vec![
(1, DispatchPhase::Capture),
(2, DispatchPhase::Capture),
(3, DispatchPhase::Target),
(2, DispatchPhase::Bubble),
(1, DispatchPhase::Bubble),
]
);
}
#[test]
fn a_consuming_target_stops_the_bubble_pass() {
use std::cell::RefCell;
use std::rc::Rc;
let seen = Rc::new(RefCell::new(Vec::new()));
let log = Rc::clone(&seen);
let propagation = route_event(3, &[1, 2], &Event::Paint, &mut |node, _, phase| {
log.borrow_mut().push((node, phase));
if phase == DispatchPhase::Target {
consume_event();
}
});
assert_eq!(propagation, Propagation::Consumed);
assert!(
!seen.borrow().iter().any(|(_, phase)| *phase == DispatchPhase::Bubble),
"nothing above the target may see a consumed event"
);
}
#[test]
fn a_capture_handler_can_pre_empt_the_target() {
use std::cell::RefCell;
use std::rc::Rc;
let seen = Rc::new(RefCell::new(Vec::new()));
let log = Rc::clone(&seen);
let propagation = route_event(3, &[1, 2], &Event::Paint, &mut |node, _, phase| {
log.borrow_mut().push((node, phase));
if node == 1 && phase == DispatchPhase::Capture {
consume_event();
}
});
assert_eq!(propagation, Propagation::Consumed);
assert_eq!(*seen.borrow(), vec![(1, DispatchPhase::Capture)]);
}
#[test]
fn a_stale_consumed_flag_does_not_leak_between_routes() {
consume_event();
let propagation = route_event(1, &[], &Event::Paint, &mut |_, _, _| {});
assert_eq!(propagation, Propagation::Continue);
}
#[test]
fn cached_ancestor_storage_survives_nested_dispatch_and_unwind() {
let parent = |id| if id > 1 { Some(id - 1) } else { None };
let address = with_ancestor_chain(4, &parent, |outer| {
assert_eq!(outer, &[1, 2, 3]);
with_ancestor_chain(3, &parent, |inner| assert_eq!(inner, &[1, 2]));
assert_eq!(outer, &[1, 2, 3]);
outer.as_ptr() as usize
});
with_ancestor_chain(4, &parent, |chain| assert_eq!(chain.as_ptr() as usize, address));
let outcome = std::panic::catch_unwind(|| {
with_ancestor_chain(4, &parent, |_| panic!("handler failure"));
});
assert!(outcome.is_err());
with_ancestor_chain(4, &parent, |chain| {
assert_eq!(chain, &[1, 2, 3]);
assert_eq!(chain.as_ptr() as usize, address);
});
with_ancestor_chain(5, &|_| Some(5), |chain| assert_eq!(chain.len(), 1024));
}
#[test]
fn the_ancestor_chain_is_root_first() {
let parent = |id: ObjectId| match id {
3 => Some(2),
2 => Some(1),
_ => None,
};
assert_eq!(ancestor_chain(3, &parent), vec![1, 2]);
let cyclic = |_id: ObjectId| Some(5);
assert_eq!(ancestor_chain(5, &cyclic).len(), 1024, "a cycle must terminate");
}
}