1use std::borrow::Cow;
4use std::cell::RefCell;
5use std::collections::HashSet;
6use std::hash::{Hash, Hasher};
7use std::rc::{Rc, Weak};
8use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
9
10use wasm_bindgen::prelude::{wasm_bindgen, Closure};
11use wasm_bindgen::{intern, JsCast, UnwrapThrowExt};
12use web_sys::{
13 AddEventListenerOptions, Element, Event, EventTarget as HtmlEventTarget, ShadowRoot,
14};
15
16use super::{test_log, Registry};
17use crate::virtual_dom::{Listener, ListenerKind};
18
19pub trait EventGrating {
22 fn subtree_id(&self) -> Option<TreeId>;
23 fn set_subtree_id(&self, tree_id: TreeId);
24 fn cache_key(&self) -> Option<u32>;
27 fn set_cache_key(&self, key: u32);
28}
29
30#[wasm_bindgen]
31extern "C" {
32 type EventTargetable;
34 #[wasm_bindgen(method, getter = __yew_subtree_id, structural)]
35 fn subtree_id(this: &EventTargetable) -> Option<TreeId>;
36 #[wasm_bindgen(method, setter = __yew_subtree_id, structural)]
37 fn set_subtree_id(this: &EventTargetable, id: TreeId);
38 #[wasm_bindgen(method, getter = __yew_subtree_cache_key, structural)]
39 fn cache_key(this: &EventTargetable) -> Option<u32>;
40 #[wasm_bindgen(method, setter = __yew_subtree_cache_key, structural)]
41 fn set_cache_key(this: &EventTargetable, key: u32);
42}
43
44macro_rules! impl_event_grating {
45 ($($t:ty);* $(;)?) => {
46 $(
47 impl EventGrating for $t {
48 fn subtree_id(&self) -> Option<TreeId> {
49 self.unchecked_ref::<EventTargetable>().subtree_id()
50 }
51 fn set_subtree_id(&self, tree_id: TreeId) {
52 self.unchecked_ref::<EventTargetable>()
53 .set_subtree_id(tree_id);
54 }
55 fn cache_key(&self) -> Option<u32> {
56 self.unchecked_ref::<EventTargetable>().cache_key()
57 }
58 fn set_cache_key(&self, key: u32) {
59 self.unchecked_ref::<EventTargetable>().set_cache_key(key)
60 }
61 }
62 )*
63 }
64}
65
66impl_event_grating!(
67 HtmlEventTarget;
68 Event; );
70
71type TreeId = u32;
75
76static NONE_TREE_ID: TreeId = 0;
78static NEXT_ROOT_ID: AtomicU32 = AtomicU32::new(1);
79
80fn next_root_id() -> TreeId {
81 NEXT_ROOT_ID.fetch_add(1, Ordering::SeqCst)
82}
83
84#[derive(Debug, Clone)]
90pub struct BSubtree(Rc<SubtreeData>);
91
92#[derive(Debug)]
96struct ParentingInformation {
97 parent_root: Rc<SubtreeData>,
98 mount_element: Element,
101}
102
103#[derive(Clone, Hash, Eq, PartialEq, Debug)]
104pub struct EventDescriptor {
105 kind: ListenerKind,
106 passive: bool,
107}
108
109impl From<&dyn Listener> for EventDescriptor {
110 fn from(l: &dyn Listener) -> Self {
111 Self {
112 kind: l.kind(),
113 passive: l.passive(),
114 }
115 }
116}
117
118type EventClosure = Closure<dyn Fn(&Event)>;
122#[derive(Debug)]
123#[must_use = "event listener will never be called after being dropped"]
124struct EventListener {
125 target: HtmlEventTarget,
126 event_type: Cow<'static, str>,
127 callback: Option<EventClosure>,
128}
129
130impl Drop for EventListener {
131 #[inline]
132 fn drop(&mut self) {
133 if let Some(ref callback) = self.callback {
134 self.target
135 .remove_event_listener_with_callback_and_bool(
136 &self.event_type,
137 callback.as_ref().unchecked_ref(),
138 true, )
140 .unwrap_throw();
141 }
142 }
143}
144
145impl EventListener {
146 fn new(
147 target: &HtmlEventTarget,
148 desc: &EventDescriptor,
149 callback: impl 'static + Fn(&Event),
150 ) -> Self {
151 let event_type = desc.kind.type_name();
152
153 let callback = Closure::wrap(Box::new(callback) as Box<dyn Fn(&Event)>);
154 let options = AddEventListenerOptions::new();
156 options.set_capture(true);
157 options.set_passive(desc.passive);
158
159 target
160 .add_event_listener_with_callback_and_add_event_listener_options(
161 intern(&event_type),
162 callback.as_ref().unchecked_ref(),
163 &options,
164 )
165 .unwrap_throw();
166
167 EventListener {
168 target: target.clone(),
169 event_type,
170 callback: Some(callback),
171 }
172 }
173
174 #[cfg(not(test))]
175 fn forget(mut self) {
176 if let Some(callback) = self.callback.take() {
177 callback.forget();
179 }
180 }
181}
182
183#[derive(Debug)]
186struct HostHandlers {
187 host: HtmlEventTarget,
189
190 #[cfg(test)]
193 registered: Vec<(ListenerKind, EventListener)>,
194}
195
196impl HostHandlers {
197 fn new(host: HtmlEventTarget) -> Self {
198 Self {
199 host,
200 #[cfg(test)]
201 registered: Vec::default(),
202 }
203 }
204
205 fn add_listener(&mut self, desc: &EventDescriptor, callback: impl 'static + Fn(&Event)) {
206 let cl = EventListener::new(&self.host, desc, callback);
207
208 #[cfg(not(test))]
210 cl.forget();
211 #[cfg(test)]
212 self.registered.push((desc.kind.clone(), cl));
213 }
214}
215
216#[derive(Debug)]
218struct SubtreeData {
219 app_data: Rc<RefCell<AppData>>,
221 parent: Option<ParentingInformation>,
223
224 subtree_id: TreeId,
225 host: HtmlEventTarget,
226 event_registry: RefCell<Registry>,
227 global: RefCell<HostHandlers>,
228}
229
230#[derive(Debug)]
231struct WeakSubtree {
232 subtree_id: TreeId,
233 weak_ref: Weak<SubtreeData>,
234}
235
236impl Hash for WeakSubtree {
237 fn hash<H: Hasher>(&self, state: &mut H) {
238 self.subtree_id.hash(state)
239 }
240}
241
242impl PartialEq for WeakSubtree {
243 fn eq(&self, other: &Self) -> bool {
244 self.subtree_id == other.subtree_id
245 }
246}
247impl Eq for WeakSubtree {}
248
249#[derive(Debug, Default)]
251struct AppData {
252 subtrees: HashSet<WeakSubtree>,
253 listening: HashSet<EventDescriptor>,
254}
255
256impl AppData {
257 fn add_subtree(&mut self, subtree: &Rc<SubtreeData>) {
258 for event in self.listening.iter() {
259 subtree.add_listener(event);
260 }
261 self.subtrees.insert(WeakSubtree {
262 subtree_id: subtree.subtree_id,
263 weak_ref: Rc::downgrade(subtree),
264 });
265 }
266
267 fn ensure_handled(&mut self, desc: &EventDescriptor) {
268 if !self.listening.insert(desc.clone()) {
269 return;
270 }
271 self.subtrees.retain(|subtree| {
272 if let Some(subtree) = subtree.weak_ref.upgrade() {
273 subtree.add_listener(desc);
274 true
275 } else {
276 false
277 }
278 })
279 }
280}
281
282static BUBBLE_EVENTS: AtomicBool = AtomicBool::new(true);
284
285#[cfg(feature = "csr")]
292pub fn set_event_bubbling(bubble: bool) {
293 BUBBLE_EVENTS.store(bubble, Ordering::Relaxed);
294}
295
296struct BrandingSearchResult {
297 branding: TreeId,
298 closest_branded_ancestor: Element,
299}
300
301fn shadow_aware_parent(el: &Element) -> Option<Element> {
302 match el.parent_element() {
303 s @ Some(_) => s,
304 None => el.parent_node()?.dyn_ref::<ShadowRoot>().map(|h| h.host()),
305 }
306}
307
308fn find_closest_branded_element(mut el: Element, do_bubble: bool) -> Option<BrandingSearchResult> {
312 if !do_bubble {
313 let branding = el.subtree_id()?;
314 Some(BrandingSearchResult {
315 branding,
316 closest_branded_ancestor: el,
317 })
318 } else {
319 let responsible_tree_id = loop {
320 if let Some(tree_id) = el.subtree_id() {
321 break tree_id;
322 }
323 el = shadow_aware_parent(&el)?;
324 };
325 Some(BrandingSearchResult {
326 branding: responsible_tree_id,
327 closest_branded_ancestor: el,
328 })
329 }
330}
331
332fn start_bubbling_from(
335 subtree: &SubtreeData,
336 root_or_listener: Element,
337 should_bubble: bool,
338) -> impl '_ + Iterator<Item = (&'_ SubtreeData, Element)> {
339 let start = subtree.bubble_to_inner_element(root_or_listener, should_bubble);
340
341 std::iter::successors(start, move |(subtree, element)| {
342 if !should_bubble {
343 return None;
344 }
345 let parent = shadow_aware_parent(element)?;
346 subtree.bubble_to_inner_element(parent, true)
347 })
348}
349
350impl SubtreeData {
351 fn new_ref(host_element: &HtmlEventTarget, parent: Option<ParentingInformation>) -> Rc<Self> {
352 let tree_root_id = next_root_id();
353 let event_registry = Registry::new();
354 let host_handlers = HostHandlers::new(host_element.clone());
355 let app_data = match parent {
356 Some(ref parent) => parent.parent_root.app_data.clone(),
357 None => Rc::default(),
358 };
359 let subtree = Rc::new(SubtreeData {
360 parent,
361 app_data,
362
363 subtree_id: tree_root_id,
364 host: host_element.clone(),
365 event_registry: RefCell::new(event_registry),
366 global: RefCell::new(host_handlers),
367 });
368 subtree.app_data.borrow_mut().add_subtree(&subtree);
369 subtree
370 }
371
372 fn event_registry(&self) -> &RefCell<Registry> {
373 &self.event_registry
374 }
375
376 fn host_handlers(&self) -> &RefCell<HostHandlers> {
377 &self.global
378 }
379
380 fn bubble_to_inner_element(
382 &self,
383 parent_el: Element,
384 should_bubble: bool,
385 ) -> Option<(&Self, Element)> {
386 let mut next_subtree = self;
387 let mut next_el = parent_el;
388 if !should_bubble && next_subtree.host.eq(&next_el) {
389 return None;
390 }
391 while next_subtree.host.eq(&next_el) {
392 let parent = next_subtree.parent.as_ref()?;
394 next_subtree = &parent.parent_root;
395 next_el = parent.mount_element.clone();
396 }
397 Some((next_subtree, next_el))
398 }
399
400 fn start_bubbling_if_responsible<'s>(
401 &'s self,
402 event: &'s Event,
403 ) -> Option<impl 's + Iterator<Item = (&'s SubtreeData, Element)>> {
404 let event_path = event.composed_path();
413 let derived_cached_key = event_path.length();
414 let cached_branding = if matches!(event.cache_key(), Some(cache_key) if cache_key == derived_cached_key)
415 {
416 event.subtree_id()
417 } else {
418 None
419 };
420 if matches!(cached_branding, Some(responsible_tree_id) if responsible_tree_id != self.subtree_id)
421 {
422 return None;
425 }
426 let target = event_path.get(0).dyn_into::<Element>().ok()?;
429 let should_bubble = BUBBLE_EVENTS.load(Ordering::Relaxed) && event.bubbles();
430 let (responsible_tree_id, bubbling_start) = if let Some(branding) = cached_branding {
432 (branding, target.clone())
433 } else if let Some(branding) = find_closest_branded_element(target.clone(), should_bubble) {
434 let BrandingSearchResult {
435 branding,
436 closest_branded_ancestor,
437 } = branding;
438 event.set_subtree_id(branding);
439 event.set_cache_key(derived_cached_key);
440 (branding, closest_branded_ancestor)
441 } else {
442 event.set_subtree_id(NONE_TREE_ID);
445 event.set_cache_key(derived_cached_key);
446 return None;
447 };
448 if self.subtree_id != responsible_tree_id {
449 return None;
450 }
451 if self.host.eq(&target) {
452 return None;
454 }
455 Some(start_bubbling_from(self, bubbling_start, should_bubble))
456 }
475
476 fn handle(&self, desc: EventDescriptor, event: Event) {
478 let run_handler = |root: &Self, el: &Element| {
479 let handler = Registry::get_handler(root.event_registry(), el, &desc);
480 if let Some(handler) = handler {
481 handler(&event)
482 }
483 };
484 if let Some(bubbling_it) = self.start_bubbling_if_responsible(&event) {
485 test_log!("Running handler on subtree {}", self.subtree_id);
486 for (subtree, el) in bubbling_it {
487 if event.cancel_bubble() {
488 break;
489 }
490 run_handler(subtree, &el);
491 }
492 }
493 }
494
495 fn add_listener(self: &Rc<Self>, desc: &EventDescriptor) {
496 let this = self.clone();
497 let listener = {
498 let desc = desc.clone();
499 move |e: &Event| {
500 this.handle(desc.clone(), e.clone());
501 }
502 };
503 self.host_handlers()
504 .borrow_mut()
505 .add_listener(desc, listener);
506 }
507}
508
509impl BSubtree {
510 fn do_create_root(
511 host_element: &HtmlEventTarget,
512 parent: Option<ParentingInformation>,
513 ) -> Self {
514 let shared_inner = SubtreeData::new_ref(host_element, parent);
515 let root = BSubtree(shared_inner);
516 root.brand_element(host_element);
517 root
518 }
519
520 pub fn create_root(host_element: &HtmlEventTarget) -> Self {
522 Self::do_create_root(host_element, None)
523 }
524
525 pub fn create_subroot(&self, mount_point: Element, host_element: &HtmlEventTarget) -> Self {
528 let parent_information = ParentingInformation {
529 parent_root: self.0.clone(),
530 mount_element: mount_point,
531 };
532 Self::do_create_root(host_element, Some(parent_information))
533 }
534
535 pub fn ensure_handled(&self, desc: &EventDescriptor) {
537 self.0.app_data.borrow_mut().ensure_handled(desc);
538 }
539
540 #[inline]
542 pub fn with_listener_registry<R>(&self, f: impl FnOnce(&mut Registry) -> R) -> R {
543 f(&mut self.0.event_registry().borrow_mut())
544 }
545
546 pub fn brand_element(&self, el: &dyn EventGrating) {
547 el.set_subtree_id(self.0.subtree_id);
548 }
549}