1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
use crate::event::event_name::EventName;
use crate::event::EventHandler;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use wasm_bindgen::JsValue;
/// Private type used to attach identifiers to DOM elements so that we can look up their event
/// callbacks.
#[doc(hidden)]
pub const EVENTS_ID_PROP: &'static str = "__events_id__";
type Events = Rc<RefCell<HashMap<u32, HashMap<EventName, ManagedEvent>>>>;
// Really only needs to be boxed.. but using an Rc let's us implement the
// removes_old_non_delegated_event_listeners test.
// A future optimization could be using a feature flag to determine whether to Rc or Box this.
// i.e. #[cfg(feature = "__test-utils")]
pub(crate) type EventWrapper = Rc<dyn AsRef<JsValue>>;
/// Node's in a VirtualNode tree are indexed depth first, where the first node is index 0, it's
/// first child node is index 1, and the first child's first child is index 2.
///
/// When we create a DOM node, we store all of it's closures and all of it's children's closures
/// in this map.
///
/// We also set a `.__nodeIdx` property on nodes that have one or more events.
///
/// Percy will sometimes use event delegation, and other times attach events directly to DOM
/// elements, depending on the kind of event.
///
/// The `.__nodeIdx` property is used to power event delegation, so that the main event handler can
/// look up the callback.
///
/// ## Cloning
///
/// EventsByNodeIdx can be cheaply cloned and passed around.
/// Clones share the same inner data.
#[derive(Clone)]
pub struct EventsByNodeIdx {
events: Events,
// Never changes after creation.
events_id_props_prefix: f64,
}
/// An event that to be managed by the PercyDom.
pub enum ManagedEvent {
/// Every kind of delegated event, such as onclick, has a single event listener attached to
/// the PercyDom's mount.
/// When that listener is called, it looks up the proper ManagedEvent::Delegated event to call.
Delegated(EventHandler),
/// For non delegated events, an event listener is attached to the DOM element using
/// .add_event_listener();
/// That event listener is an `EventWrapper`, which in turn will find and call the
/// `EventAttribFn`.
/// This setup allows us to replace the `EventAttribFn` after every render without needing
/// to re-attach event listeners.
NonDelegated(EventHandler, EventWrapper),
}
impl ManagedEvent {
fn is_delegated(&self) -> bool {
matches!(self, ManagedEvent::Delegated(_))
}
}
impl EventsByNodeIdx {
/// Create a new EventsByNodeIdx.
pub fn new() -> Self {
EventsByNodeIdx {
events: Rc::new(RefCell::new(Default::default())),
events_id_props_prefix: js_sys::Math::random(),
}
}
/// Unique for every PercyDom so that if multiple instances of PercyDom are nested their
/// event delegation handlers don't collide.
pub fn events_id_props_prefix(&self) -> f64 {
self.events_id_props_prefix
}
/// Insert a newly tracked event.
///
/// # Panics
///
/// Panics if the event_name is delegated and the event is not, or vice versa.
pub fn insert_managed_event(&self, node_idx: u32, event_name: EventName, event: ManagedEvent) {
assert_eq!(event_name.is_delegated(), event.is_delegated());
self.events
.borrow_mut()
.entry(node_idx)
.or_default()
.insert(event_name, event);
}
/// Insert a newly tracked event.
///
/// # Panics
///
/// Panics if there isn't an event attrib fn to overwrite.
pub fn overwrite_event_attrib_fn(
&self,
node_idx: u32,
event_name: &EventName,
event: EventHandler,
) {
let mut events = self.events.borrow_mut();
let func = match events
.entry(node_idx)
.or_default()
.get_mut(event_name)
.unwrap()
{
ManagedEvent::Delegated(func) => func,
ManagedEvent::NonDelegated(func, _) => func,
};
*func = event;
}
/// Remove all of the events from one node ID and add them to another node ID.
pub fn move_events(&mut self, old_node_id: &u32, new_node_id: u32) {
let mut events = self.events.borrow_mut();
if let Some(old_events) = events.remove(old_node_id) {
events.insert(new_node_id, old_events);
}
}
/// Remove a managed event.
pub fn remove_managed_event(&mut self, node_id: &u32, event_name: &EventName) -> ManagedEvent {
self.events
.borrow_mut()
.get_mut(node_id)
.unwrap()
.remove(event_name)
.unwrap()
}
/// Get the event handler for a node.
pub fn get_event_handler(&self, node_id: &u32, event_name: &EventName) -> Option<EventHandler> {
self.events.borrow().get(node_id)?.get(event_name).map(|e| {
let event = match e {
ManagedEvent::Delegated(e) => e,
ManagedEvent::NonDelegated(e, _) => e,
};
event.clone()
})
}
// Used by a percy-dom test.
// TODO: Put this behind #[cfg(feature = "__test-utils")]
#[doc(hidden)]
pub fn __get_event_wrapper_clone(&self, node_id: &u32, event_name: &EventName) -> EventWrapper {
self.events
.borrow()
.get(node_id)
.unwrap()
.get(event_name)
.map(|e| match e {
ManagedEvent::NonDelegated(_, wrapper) => wrapper.clone(),
_ => panic!(),
})
.unwrap()
}
/// Remove an event handler.
pub fn remove_event_handler(
&self,
node_id: &u32,
event_name: &EventName,
) -> Option<ManagedEvent> {
self.events
.borrow_mut()
.get_mut(node_id)?
.remove(event_name)
}
/// Remove all event handlers for a node.
pub fn remove_node(&self, node_id: &u32) {
self.events.borrow_mut().remove(node_id);
}
}