This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew/dom_bundle/
subtree_root.rs

1//! Per-subtree state of apps
2
3use 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
19/// DOM-Types that capture (bubbling) events. This generally includes event targets,
20/// but also subtree roots.
21pub trait EventGrating {
22    fn subtree_id(&self) -> Option<TreeId>;
23    fn set_subtree_id(&self, tree_id: TreeId);
24    // When caching, we key on the length of the `composed_path`. Important to check
25    // considering event retargeting!
26    fn cache_key(&self) -> Option<u32>;
27    fn set_cache_key(&self, key: u32);
28}
29
30#[wasm_bindgen]
31extern "C" {
32    // Duck-typing, not a real class on js-side. On rust-side, use impls of EventGrating below
33    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; // We cache the found subtree id on the event. This should speed up repeated searches
69);
70
71/// The TreeId is the additional payload attached to each listening element
72/// It identifies the host responsible for the target. Events not matching
73/// are ignored during handling
74type TreeId = u32;
75
76/// Special id for caching the fact that some event should not be handled
77static 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/// Data kept per controlled subtree. [Portal] and [AppHandle] serve as
85/// hosts. Two controlled subtrees should never overlap.
86///
87/// [Portal]: super::bportal::BPortal
88/// [AppHandle]: super::app_handle::AppHandle
89#[derive(Debug, Clone)]
90pub struct BSubtree(Rc<SubtreeData>);
91
92/// The parent is the logical location where a subtree is mounted
93/// Used to bubble events through portals, which are physically somewhere else in the DOM tree
94/// but should bubble to logical ancestors in the virtual DOM tree
95#[derive(Debug)]
96struct ParentingInformation {
97    parent_root: Rc<SubtreeData>,
98    // Logical parent of the subtree. Might be the host element of another subtree,
99    // if mounted as a direct child, or a controlled element.
100    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
118// FIXME: this is a reproduction of gloo's EventListener to work around #2989
119// change back to gloo's implementation once it has been decided how to fix this upstream
120// The important part is that we use `Fn` instead of `FnMut` below!
121type 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, // Always capture
139                )
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        // defaults: { once: false }
155        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            // Should always match, but no need to introduce a panic path here
178            callback.forget();
179        }
180    }
181}
182
183/// Ensures event handler registration.
184// Separate struct to DRY, while avoiding partial struct mutability.
185#[derive(Debug)]
186struct HostHandlers {
187    /// The host element where events are registered
188    host: HtmlEventTarget,
189
190    /// Keep track of all listeners to drop them on registry drop.
191    /// The registry is never dropped in production.
192    #[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        // Never drop the closure as this event handler is static
209        #[cfg(not(test))]
210        cl.forget();
211        #[cfg(test)]
212        self.registered.push((desc.kind.clone(), cl));
213    }
214}
215
216/// Per subtree data
217#[derive(Debug)]
218struct SubtreeData {
219    /// Data shared between all trees in an app
220    app_data: Rc<RefCell<AppData>>,
221    /// Parent subtree
222    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/// Per tree data, shared between all subtrees in the hierarchy
250#[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
282/// Bubble events during delegation
283static BUBBLE_EVENTS: AtomicBool = AtomicBool::new(true);
284
285/// Set, if events should bubble up the DOM tree, calling any matching callbacks.
286///
287/// Bubbling is enabled by default. Disabling bubbling can lead to substantial improvements in event
288/// handling performance.
289///
290/// This function should be called before any component is mounted.
291#[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
308/// Deduce the subtree an element is part of. This already partially starts the bubbling
309/// process, as long as no listeners are encountered.
310/// Subtree roots are always branded with their own subtree id.
311fn 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
332/// Iterate over all potentially listening elements in bubbling order.
333/// If bubbling is turned off, yields at most a single element.
334fn 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    // Bubble a potential parent until it reaches an internal element
381    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            // we've reached the host, delegate to a parent if one exists
393            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        // Note: the event is not necessarily indentically the same object for all installed
405        // handlers hence this cache can be unreliable. Hence the cached repsonsible_tree_id
406        // might be missing. On the other hand, due to event retargeting at shadow roots,
407        // the cache might be wrong! Keep in mind that we handle events in the capture
408        // phase, so top-down. When descending and retargeting into closed shadow-dom, the
409        // event might have been handled 'prematurely'. TODO: figure out how to prevent this
410        // and establish correct event handling for closed shadow root. Note: Other
411        // frameworks also get this wrong and dispatch such events multiple times.
412        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            // some other handler has determined (via this function, but other `self`) a subtree
423            // that is responsible for handling this event, and it's not this subtree.
424            return None;
425        }
426        // We're tasked with finding the subtree that is reponsible with handling the event, and/or
427        // run the handling if that's `self`.
428        let target = event_path.get(0).dyn_into::<Element>().ok()?;
429        let should_bubble = BUBBLE_EVENTS.load(Ordering::Relaxed) && event.bubbles();
430        // We say that the most deeply nested subtree is "responsible" for handling the event.
431        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            // Possible only? if bubbling is disabled
443            // No tree should handle this event
444            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            // One more special case: don't handle events that get fired directly on a subtree host
453            return None;
454        }
455        Some(start_bubbling_from(self, bubbling_start, should_bubble))
456        // # More details: When nesting occurs
457        //
458        // Event listeners are installed only on the subtree roots. Still, those roots can
459        // nest. This could lead to events getting handled multiple times. We want event handling to
460        // start at the most deeply nested subtree.
461        //
462        // A nested subtree portals into an element that is controlled by the user and rendered
463        // with VNode::VRef. We get the following dom nesting:
464        //
465        // AppRoot > .. > UserControlledVRef > .. > NestedTree(PortalExit) > ..
466        // --------------                          ----------------------------
467        // The underlined parts of the hierarchy are controlled by Yew.
468        //
469        // from the following virtual_dom
470        // <AppRoot>
471        //   {VNode::VRef(<div><div id="portal_target" /></div>)}
472        //   {create_portal(<NestedTree />, #portal_target)}
473        // </AppRoot>
474    }
475
476    /// Handle a global event firing
477    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    /// Create a bundle root at the specified host element
521    pub fn create_root(host_element: &HtmlEventTarget) -> Self {
522        Self::do_create_root(host_element, None)
523    }
524
525    /// Create a bundle root at the specified host element, that is logically
526    /// mounted under the specified element in this tree.
527    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    /// Ensure the event described is handled on all subtrees
536    pub fn ensure_handled(&self, desc: &EventDescriptor) {
537        self.0.app_data.borrow_mut().ensure_handled(desc);
538    }
539
540    /// Run f with access to global Registry
541    #[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}