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);
    }
}