use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::HashSet;
use std::hash::{Hash, Hasher};
use std::rc::{Rc, Weak};
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
use wasm_bindgen::{intern, JsCast, UnwrapThrowExt};
use web_sys::{
AddEventListenerOptions, Element, Event, EventTarget as HtmlEventTarget, ShadowRoot,
};
use super::{test_log, Registry};
use crate::virtual_dom::{Listener, ListenerKind};
pub trait EventGrating {
fn subtree_id(&self) -> Option<TreeId>;
fn set_subtree_id(&self, tree_id: TreeId);
fn cache_key(&self) -> Option<u32>;
fn set_cache_key(&self, key: u32);
}
#[wasm_bindgen]
extern "C" {
type EventTargetable;
#[wasm_bindgen(method, getter = __yew_subtree_id, structural)]
fn subtree_id(this: &EventTargetable) -> Option<TreeId>;
#[wasm_bindgen(method, setter = __yew_subtree_id, structural)]
fn set_subtree_id(this: &EventTargetable, id: TreeId);
#[wasm_bindgen(method, getter = __yew_subtree_cache_key, structural)]
fn cache_key(this: &EventTargetable) -> Option<u32>;
#[wasm_bindgen(method, setter = __yew_subtree_cache_key, structural)]
fn set_cache_key(this: &EventTargetable, key: u32);
}
macro_rules! impl_event_grating {
($($t:ty);* $(;)?) => {
$(
impl EventGrating for $t {
fn subtree_id(&self) -> Option<TreeId> {
self.unchecked_ref::<EventTargetable>().subtree_id()
}
fn set_subtree_id(&self, tree_id: TreeId) {
self.unchecked_ref::<EventTargetable>()
.set_subtree_id(tree_id);
}
fn cache_key(&self) -> Option<u32> {
self.unchecked_ref::<EventTargetable>().cache_key()
}
fn set_cache_key(&self, key: u32) {
self.unchecked_ref::<EventTargetable>().set_cache_key(key)
}
}
)*
}
}
impl_event_grating!(
HtmlEventTarget;
Event; );
type TreeId = u32;
static NONE_TREE_ID: TreeId = 0;
static NEXT_ROOT_ID: AtomicU32 = AtomicU32::new(1);
fn next_root_id() -> TreeId {
NEXT_ROOT_ID.fetch_add(1, Ordering::SeqCst)
}
#[derive(Debug, Clone)]
pub struct BSubtree(Rc<SubtreeData>);
#[derive(Debug)]
struct ParentingInformation {
parent_root: Rc<SubtreeData>,
mount_element: Element,
}
#[derive(Clone, Hash, Eq, PartialEq, Debug)]
pub struct EventDescriptor {
kind: ListenerKind,
passive: bool,
}
impl From<&dyn Listener> for EventDescriptor {
fn from(l: &dyn Listener) -> Self {
Self {
kind: l.kind(),
passive: l.passive(),
}
}
}
type EventClosure = Closure<dyn Fn(&Event)>;
#[derive(Debug)]
#[must_use = "event listener will never be called after being dropped"]
struct EventListener {
target: HtmlEventTarget,
event_type: Cow<'static, str>,
callback: Option<EventClosure>,
}
impl Drop for EventListener {
#[inline]
fn drop(&mut self) {
if let Some(ref callback) = self.callback {
self.target
.remove_event_listener_with_callback_and_bool(
&self.event_type,
callback.as_ref().unchecked_ref(),
true, )
.unwrap_throw();
}
}
}
impl EventListener {
fn new(
target: &HtmlEventTarget,
desc: &EventDescriptor,
callback: impl 'static + Fn(&Event),
) -> Self {
let event_type = desc.kind.type_name();
let callback = Closure::wrap(Box::new(callback) as Box<dyn Fn(&Event)>);
let options = AddEventListenerOptions::new();
options.set_capture(true);
options.set_passive(desc.passive);
target
.add_event_listener_with_callback_and_add_event_listener_options(
intern(&event_type),
callback.as_ref().unchecked_ref(),
&options,
)
.unwrap_throw();
EventListener {
target: target.clone(),
event_type,
callback: Some(callback),
}
}
#[cfg(not(test))]
fn forget(mut self) {
if let Some(callback) = self.callback.take() {
callback.forget();
}
}
}
#[derive(Debug)]
struct HostHandlers {
host: HtmlEventTarget,
#[cfg(test)]
registered: Vec<(ListenerKind, EventListener)>,
}
impl HostHandlers {
fn new(host: HtmlEventTarget) -> Self {
Self {
host,
#[cfg(test)]
registered: Vec::default(),
}
}
fn add_listener(&mut self, desc: &EventDescriptor, callback: impl 'static + Fn(&Event)) {
let cl = EventListener::new(&self.host, desc, callback);
#[cfg(not(test))]
cl.forget();
#[cfg(test)]
self.registered.push((desc.kind.clone(), cl));
}
}
#[derive(Debug)]
struct SubtreeData {
app_data: Rc<RefCell<AppData>>,
parent: Option<ParentingInformation>,
subtree_id: TreeId,
host: HtmlEventTarget,
event_registry: RefCell<Registry>,
global: RefCell<HostHandlers>,
}
#[derive(Debug)]
struct WeakSubtree {
subtree_id: TreeId,
weak_ref: Weak<SubtreeData>,
}
impl Hash for WeakSubtree {
fn hash<H: Hasher>(&self, state: &mut H) {
self.subtree_id.hash(state)
}
}
impl PartialEq for WeakSubtree {
fn eq(&self, other: &Self) -> bool {
self.subtree_id == other.subtree_id
}
}
impl Eq for WeakSubtree {}
#[derive(Debug, Default)]
struct AppData {
subtrees: HashSet<WeakSubtree>,
listening: HashSet<EventDescriptor>,
}
impl AppData {
fn add_subtree(&mut self, subtree: &Rc<SubtreeData>) {
for event in self.listening.iter() {
subtree.add_listener(event);
}
self.subtrees.insert(WeakSubtree {
subtree_id: subtree.subtree_id,
weak_ref: Rc::downgrade(subtree),
});
}
fn ensure_handled(&mut self, desc: &EventDescriptor) {
if !self.listening.insert(desc.clone()) {
return;
}
self.subtrees.retain(|subtree| {
if let Some(subtree) = subtree.weak_ref.upgrade() {
subtree.add_listener(desc);
true
} else {
false
}
})
}
}
static BUBBLE_EVENTS: AtomicBool = AtomicBool::new(true);
#[cfg(feature = "csr")]
pub fn set_event_bubbling(bubble: bool) {
BUBBLE_EVENTS.store(bubble, Ordering::Relaxed);
}
struct BrandingSearchResult {
branding: TreeId,
closest_branded_ancestor: Element,
}
fn shadow_aware_parent(el: &Element) -> Option<Element> {
match el.parent_element() {
s @ Some(_) => s,
None => el.parent_node()?.dyn_ref::<ShadowRoot>().map(|h| h.host()),
}
}
fn find_closest_branded_element(mut el: Element, do_bubble: bool) -> Option<BrandingSearchResult> {
if !do_bubble {
let branding = el.subtree_id()?;
Some(BrandingSearchResult {
branding,
closest_branded_ancestor: el,
})
} else {
let responsible_tree_id = loop {
if let Some(tree_id) = el.subtree_id() {
break tree_id;
}
el = shadow_aware_parent(&el)?;
};
Some(BrandingSearchResult {
branding: responsible_tree_id,
closest_branded_ancestor: el,
})
}
}
fn start_bubbling_from(
subtree: &SubtreeData,
root_or_listener: Element,
should_bubble: bool,
) -> impl '_ + Iterator<Item = (&'_ SubtreeData, Element)> {
let start = subtree.bubble_to_inner_element(root_or_listener, should_bubble);
std::iter::successors(start, move |(subtree, element)| {
if !should_bubble {
return None;
}
let parent = shadow_aware_parent(element)?;
subtree.bubble_to_inner_element(parent, true)
})
}
impl SubtreeData {
fn new_ref(host_element: &HtmlEventTarget, parent: Option<ParentingInformation>) -> Rc<Self> {
let tree_root_id = next_root_id();
let event_registry = Registry::new();
let host_handlers = HostHandlers::new(host_element.clone());
let app_data = match parent {
Some(ref parent) => parent.parent_root.app_data.clone(),
None => Rc::default(),
};
let subtree = Rc::new(SubtreeData {
parent,
app_data,
subtree_id: tree_root_id,
host: host_element.clone(),
event_registry: RefCell::new(event_registry),
global: RefCell::new(host_handlers),
});
subtree.app_data.borrow_mut().add_subtree(&subtree);
subtree
}
fn event_registry(&self) -> &RefCell<Registry> {
&self.event_registry
}
fn host_handlers(&self) -> &RefCell<HostHandlers> {
&self.global
}
fn bubble_to_inner_element(
&self,
parent_el: Element,
should_bubble: bool,
) -> Option<(&Self, Element)> {
let mut next_subtree = self;
let mut next_el = parent_el;
if !should_bubble && next_subtree.host.eq(&next_el) {
return None;
}
while next_subtree.host.eq(&next_el) {
let parent = next_subtree.parent.as_ref()?;
next_subtree = &parent.parent_root;
next_el = parent.mount_element.clone();
}
Some((next_subtree, next_el))
}
fn start_bubbling_if_responsible<'s>(
&'s self,
event: &'s Event,
) -> Option<impl 's + Iterator<Item = (&'s SubtreeData, Element)>> {
let event_path = event.composed_path();
let derived_cached_key = event_path.length();
let cached_branding = if matches!(event.cache_key(), Some(cache_key) if cache_key == derived_cached_key)
{
event.subtree_id()
} else {
None
};
if matches!(cached_branding, Some(responsible_tree_id) if responsible_tree_id != self.subtree_id)
{
return None;
}
let target = event_path.get(0).dyn_into::<Element>().ok()?;
let should_bubble = BUBBLE_EVENTS.load(Ordering::Relaxed) && event.bubbles();
let (responsible_tree_id, bubbling_start) = if let Some(branding) = cached_branding {
(branding, target.clone())
} else if let Some(branding) = find_closest_branded_element(target.clone(), should_bubble) {
let BrandingSearchResult {
branding,
closest_branded_ancestor,
} = branding;
event.set_subtree_id(branding);
event.set_cache_key(derived_cached_key);
(branding, closest_branded_ancestor)
} else {
event.set_subtree_id(NONE_TREE_ID);
event.set_cache_key(derived_cached_key);
return None;
};
if self.subtree_id != responsible_tree_id {
return None;
}
if self.host.eq(&target) {
return None;
}
Some(start_bubbling_from(self, bubbling_start, should_bubble))
}
fn handle(&self, desc: EventDescriptor, event: Event) {
let run_handler = |root: &Self, el: &Element| {
let handler = Registry::get_handler(root.event_registry(), el, &desc);
if let Some(handler) = handler {
handler(&event)
}
};
if let Some(bubbling_it) = self.start_bubbling_if_responsible(&event) {
test_log!("Running handler on subtree {}", self.subtree_id);
for (subtree, el) in bubbling_it {
if event.cancel_bubble() {
break;
}
run_handler(subtree, &el);
}
}
}
fn add_listener(self: &Rc<Self>, desc: &EventDescriptor) {
let this = self.clone();
let listener = {
let desc = desc.clone();
move |e: &Event| {
this.handle(desc.clone(), e.clone());
}
};
self.host_handlers()
.borrow_mut()
.add_listener(desc, listener);
}
}
impl BSubtree {
fn do_create_root(
host_element: &HtmlEventTarget,
parent: Option<ParentingInformation>,
) -> Self {
let shared_inner = SubtreeData::new_ref(host_element, parent);
let root = BSubtree(shared_inner);
root.brand_element(host_element);
root
}
pub fn create_root(host_element: &HtmlEventTarget) -> Self {
Self::do_create_root(host_element, None)
}
pub fn create_subroot(&self, mount_point: Element, host_element: &HtmlEventTarget) -> Self {
let parent_information = ParentingInformation {
parent_root: self.0.clone(),
mount_element: mount_point,
};
Self::do_create_root(host_element, Some(parent_information))
}
pub fn ensure_handled(&self, desc: &EventDescriptor) {
self.0.app_data.borrow_mut().ensure_handled(desc);
}
#[inline]
pub fn with_listener_registry<R>(&self, f: impl FnOnce(&mut Registry) -> R) -> R {
f(&mut self.0.event_registry().borrow_mut())
}
pub fn brand_element(&self, el: &dyn EventGrating) {
el.set_subtree_id(self.0.subtree_id);
}
}