Bladeren bron

Merge pull request #1393 from ealmloff/fix-event-bubbling

Fix event multi node event bubbling
Jonathan Kelley 1 jaar geleden
bovenliggende
commit
a6dd8316d1

+ 63 - 59
packages/core/src/arena.rs

@@ -13,62 +13,50 @@ use crate::{
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
 pub struct ElementId(pub usize);
 
-pub(crate) struct ElementRef {
+/// An Element that can be bubbled to's unique identifier.
+///
+/// `BubbleId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
+/// unmounted, then the `BubbleId` will be reused for a new component.
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
+pub struct VNodeId(pub usize);
+
+#[derive(Debug, Clone, Copy)]
+pub struct ElementRef {
     // the pathway of the real element inside the template
-    pub path: ElementPath,
+    pub(crate) path: ElementPath,
 
     // The actual template
-    pub template: Option<NonNull<VNode<'static>>>,
+    pub(crate) template: VNodeId,
 
     // The scope the element belongs to
-    pub scope: ScopeId,
+    pub(crate) scope: ScopeId,
 }
 
 #[derive(Clone, Copy, Debug)]
-pub enum ElementPath {
-    Deep(&'static [u8]),
-    Root(usize),
-}
-
-impl ElementRef {
-    pub(crate) fn none() -> Self {
-        Self {
-            template: None,
-            path: ElementPath::Root(0),
-            scope: ScopeId::ROOT,
-        }
-    }
+pub struct ElementPath {
+    pub(crate) path: &'static [u8],
 }
 
 impl VirtualDom {
-    pub(crate) fn next_element(&mut self, template: &VNode, path: &'static [u8]) -> ElementId {
-        self.next_reference(template, ElementPath::Deep(path))
-    }
-
-    pub(crate) fn next_root(&mut self, template: &VNode, path: usize) -> ElementId {
-        self.next_reference(template, ElementPath::Root(path))
+    pub(crate) fn next_element(&mut self) -> ElementId {
+        ElementId(self.elements.insert(None))
     }
 
-    pub(crate) fn next_null(&mut self) -> ElementId {
-        let entry = self.elements.vacant_entry();
-        let id = entry.key();
-
-        entry.insert(ElementRef::none());
-        ElementId(id)
-    }
-
-    fn next_reference(&mut self, template: &VNode, path: ElementPath) -> ElementId {
-        let entry = self.elements.vacant_entry();
-        let id = entry.key();
-        let scope = self.runtime.current_scope_id().unwrap_or(ScopeId::ROOT);
+    pub(crate) fn next_vnode_ref(&mut self, vnode: &VNode) -> VNodeId {
+        let new_id = VNodeId(self.element_refs.insert(Some(unsafe {
+            std::mem::transmute::<NonNull<VNode>, _>(vnode.into())
+        })));
+
+        // Set this id to be dropped when the scope is rerun
+        if let Some(scope) = self.runtime.current_scope_id() {
+            self.scopes[scope.0]
+                .element_refs_to_drop
+                .borrow_mut()
+                .push(new_id);
+        }
 
-        entry.insert(ElementRef {
-            // We know this is non-null because it comes from a reference
-            template: Some(unsafe { NonNull::new_unchecked(template as *const _ as *mut _) }),
-            path,
-            scope,
-        });
-        ElementId(id)
+        new_id
     }
 
     pub(crate) fn reclaim(&mut self, el: ElementId) {
@@ -76,7 +64,7 @@ impl VirtualDom {
             .unwrap_or_else(|| panic!("cannot reclaim {:?}", el));
     }
 
-    pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option<ElementRef> {
+    pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option<()> {
         if el.0 == 0 {
             panic!(
                 "Cannot reclaim the root element - {:#?}",
@@ -84,12 +72,12 @@ impl VirtualDom {
             );
         }
 
-        self.elements.try_remove(el.0)
+        self.elements.try_remove(el.0).map(|_| ())
     }
 
-    pub(crate) fn update_template(&mut self, el: ElementId, node: &VNode) {
-        let node: *const VNode = node as *const _;
-        self.elements[el.0].template = unsafe { std::mem::transmute(node) };
+    pub(crate) fn set_template(&mut self, id: VNodeId, vnode: &VNode) {
+        self.element_refs[id.0] =
+            Some(unsafe { std::mem::transmute::<NonNull<VNode>, _>(vnode.into()) });
     }
 
     // Drop a scope and all its children
@@ -101,6 +89,15 @@ impl VirtualDom {
             id,
         });
 
+        // Remove all VNode ids from the scope
+        for id in self.scopes[id.0]
+            .element_refs_to_drop
+            .borrow_mut()
+            .drain(..)
+        {
+            self.element_refs.try_remove(id.0);
+        }
+
         self.ensure_drop_safety(id);
 
         if recursive {
@@ -145,14 +142,25 @@ impl VirtualDom {
     }
 
     /// Descend through the tree, removing any borrowed props and listeners
-    pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) {
+    pub(crate) fn ensure_drop_safety(&mut self, scope_id: ScopeId) {
         let scope = &self.scopes[scope_id.0];
 
+        {
+            // Drop all element refs that could be invalidated when the component was rerun
+            let mut element_refs = self.scopes[scope_id.0].element_refs_to_drop.borrow_mut();
+            let element_refs_slab = &mut self.element_refs;
+            for element_ref in element_refs.drain(..) {
+                if let Some(element_ref) = element_refs_slab.get_mut(element_ref.0) {
+                    *element_ref = None;
+                }
+            }
+        }
+
         // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
         // run the hooks (which hold an &mut Reference)
         // recursively call ensure_drop_safety on all children
-        let mut props = scope.borrowed_props.borrow_mut();
-        props.drain(..).for_each(|comp| {
+        let props = { scope.borrowed_props.borrow_mut().clone() };
+        for comp in props {
             let comp = unsafe { &*comp };
             match comp.scope.get() {
                 Some(child) if child != scope_id => self.ensure_drop_safety(child),
@@ -161,7 +169,9 @@ impl VirtualDom {
             if let Ok(mut props) = comp.props.try_borrow_mut() {
                 *props = None;
             }
-        });
+        }
+        let scope = &self.scopes[scope_id.0];
+        scope.borrowed_props.borrow_mut().clear();
 
         // Now that all the references are gone, we can safely drop our own references in our listeners.
         let mut listeners = scope.attributes_to_drop_before_render.borrow_mut();
@@ -176,18 +186,12 @@ impl VirtualDom {
 
 impl ElementPath {
     pub(crate) fn is_decendant(&self, small: &&[u8]) -> bool {
-        match *self {
-            ElementPath::Deep(big) => small.len() <= big.len() && *small == &big[..small.len()],
-            ElementPath::Root(r) => small.len() == 1 && small[0] == r as u8,
-        }
+        small.len() <= self.path.len() && *small == &self.path[..small.len()]
     }
 }
 
 impl PartialEq<&[u8]> for ElementPath {
     fn eq(&self, other: &&[u8]) -> bool {
-        match *self {
-            ElementPath::Deep(deep) => deep.eq(*other),
-            ElementPath::Root(r) => other.len() == 1 && other[0] == r as u8,
-        }
+        self.path.eq(*other)
     }
 }

+ 78 - 44
packages/core/src/create.rs

@@ -1,5 +1,7 @@
 use crate::any_props::AnyProps;
-use crate::innerlude::{BorrowedAttributeValue, VComponent, VPlaceholder, VText};
+use crate::innerlude::{
+    BorrowedAttributeValue, ElementPath, ElementRef, VComponent, VPlaceholder, VText,
+};
 use crate::mutations::Mutation;
 use crate::mutations::Mutation::*;
 use crate::nodes::VNode;
@@ -94,6 +96,9 @@ impl<'b> VirtualDom {
             nodes_mut.resize(len, ElementId::default());
         };
 
+        // Set this node id
+        node.stable_id.set(Some(self.next_vnode_ref(node)));
+
         // The best renderers will have templates prehydrated and registered
         // Just in case, let's create the template using instructions anyways
         self.register_template(node.template.get());
@@ -181,15 +186,30 @@ impl<'b> VirtualDom {
         use DynamicNode::*;
         match &template.dynamic_nodes[idx] {
             node @ Component { .. } | node @ Fragment(_) => {
-                self.create_dynamic_node(template, node, idx)
+                let template_ref = ElementRef {
+                    path: ElementPath {
+                        path: template.template.get().node_paths[idx],
+                    },
+                    template: template.stable_id().unwrap(),
+                    scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
+                };
+                self.create_dynamic_node(template_ref, node)
             }
-            Placeholder(VPlaceholder { id }) => {
-                let id = self.set_slot(template, id, idx);
+            Placeholder(VPlaceholder { id, parent }) => {
+                let template_ref = ElementRef {
+                    path: ElementPath {
+                        path: template.template.get().node_paths[idx],
+                    },
+                    template: template.stable_id().unwrap(),
+                    scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
+                };
+                parent.set(Some(template_ref));
+                let id = self.set_slot(id);
                 self.mutations.push(CreatePlaceholder { id });
                 1
             }
             Text(VText { id, value }) => {
-                let id = self.set_slot(template, id, idx);
+                let id = self.set_slot(id);
                 self.create_static_text(value, id);
                 1
             }
@@ -265,7 +285,14 @@ impl<'b> VirtualDom {
             .map(|sorted_index| dynamic_nodes[sorted_index].0);
 
         for idx in reversed_iter {
-            let m = self.create_dynamic_node(template, &template.dynamic_nodes[idx], idx);
+            let boundary_ref = ElementRef {
+                path: ElementPath {
+                    path: template.template.get().node_paths[idx],
+                },
+                template: template.stable_id().unwrap(),
+                scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
+            };
+            let m = self.create_dynamic_node(boundary_ref, &template.dynamic_nodes[idx]);
             if m > 0 {
                 // The path is one shorter because the top node is the root
                 let path = &template.template.get().node_paths[idx][1..];
@@ -279,15 +306,15 @@ impl<'b> VirtualDom {
         attrs: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,
         root_idx: u8,
         root: ElementId,
-        node: &VNode,
+        node: &'b VNode<'b>,
     ) {
         while let Some((mut attr_id, path)) =
             attrs.next_if(|(_, p)| p.first().copied() == Some(root_idx))
         {
-            let id = self.assign_static_node_as_dynamic(path, root, node, attr_id);
+            let id = self.assign_static_node_as_dynamic(path, root);
 
             loop {
-                self.write_attribute(&node.dynamic_attrs[attr_id], id);
+                self.write_attribute(node, attr_id, &node.dynamic_attrs[attr_id], id);
 
                 // Only push the dynamic attributes forward if they match the current path (same element)
                 match attrs.next_if(|(_, p)| *p == path) {
@@ -298,7 +325,13 @@ impl<'b> VirtualDom {
         }
     }
 
-    fn write_attribute(&mut self, attribute: &'b crate::Attribute<'b>, id: ElementId) {
+    fn write_attribute(
+        &mut self,
+        template: &'b VNode<'b>,
+        idx: usize,
+        attribute: &'b crate::Attribute<'b>,
+        id: ElementId,
+    ) {
         // Make sure we set the attribute's associated id
         attribute.mounted_element.set(id);
 
@@ -307,6 +340,13 @@ impl<'b> VirtualDom {
 
         match &attribute.value {
             AttributeValue::Listener(_) => {
+                let path = &template.template.get().attr_paths[idx];
+                let element_ref = ElementRef {
+                    path: ElementPath { path },
+                    template: template.stable_id().unwrap(),
+                    scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
+                };
+                self.elements[id.0] = Some(element_ref);
                 self.mutations.push(NewEventListener {
                     // all listeners start with "on"
                     name: &unbounded_name[2..],
@@ -330,7 +370,7 @@ impl<'b> VirtualDom {
 
     fn load_template_root(&mut self, template: &VNode, root_idx: usize) -> ElementId {
         // Get an ID for this root since it's a real root
-        let this_id = self.next_root(template, root_idx);
+        let this_id = self.next_element();
         template.root_ids.borrow_mut()[root_idx] = this_id;
 
         self.mutations.push(LoadTemplate {
@@ -353,8 +393,6 @@ impl<'b> VirtualDom {
         &mut self,
         path: &'static [u8],
         this_id: ElementId,
-        template: &VNode,
-        attr_id: usize,
     ) -> ElementId {
         if path.len() == 1 {
             return this_id;
@@ -362,7 +400,7 @@ impl<'b> VirtualDom {
 
         // if attribute is on a root node, then we've already created the element
         // Else, it's deep in the template and we should create a new id for it
-        let id = self.next_element(template, template.template.get().attr_paths[attr_id]);
+        let id = self.next_element();
 
         self.mutations.push(Mutation::AssignId {
             path: &path[1..],
@@ -440,27 +478,21 @@ impl<'b> VirtualDom {
 
     pub(crate) fn create_dynamic_node(
         &mut self,
-        template: &'b VNode<'b>,
+        parent: ElementRef,
         node: &'b DynamicNode<'b>,
-        idx: usize,
     ) -> usize {
         use DynamicNode::*;
         match node {
-            Text(text) => self.create_dynamic_text(template, text, idx),
-            Placeholder(place) => self.create_placeholder(place, template, idx),
-            Component(component) => self.create_component_node(template, component),
-            Fragment(frag) => frag.iter().map(|child| self.create(child)).sum(),
+            Text(text) => self.create_dynamic_text(parent, text),
+            Placeholder(place) => self.create_placeholder(place, parent),
+            Component(component) => self.create_component_node(Some(parent), component),
+            Fragment(frag) => self.create_children(*frag, Some(parent)),
         }
     }
 
-    fn create_dynamic_text(
-        &mut self,
-        template: &'b VNode<'b>,
-        text: &'b VText<'b>,
-        idx: usize,
-    ) -> usize {
+    fn create_dynamic_text(&mut self, parent: ElementRef, text: &'b VText<'b>) -> usize {
         // Allocate a dynamic element reference for this text node
-        let new_id = self.next_element(template, template.template.get().node_paths[idx]);
+        let new_id = self.next_element();
 
         // Make sure the text node is assigned to the correct element
         text.id.set(Some(new_id));
@@ -471,7 +503,7 @@ impl<'b> VirtualDom {
         // Add the mutation to the list
         self.mutations.push(HydrateText {
             id: new_id,
-            path: &template.template.get().node_paths[idx][1..],
+            path: &parent.path.path[1..],
             value,
         });
 
@@ -482,18 +514,20 @@ impl<'b> VirtualDom {
     pub(crate) fn create_placeholder(
         &mut self,
         placeholder: &VPlaceholder,
-        template: &'b VNode<'b>,
-        idx: usize,
+        parent: ElementRef,
     ) -> usize {
         // Allocate a dynamic element reference for this text node
-        let id = self.next_element(template, template.template.get().node_paths[idx]);
+        let id = self.next_element();
 
         // Make sure the text node is assigned to the correct element
         placeholder.id.set(Some(id));
 
+        // Assign the placeholder's parent
+        placeholder.parent.set(Some(parent));
+
         // Assign the ID to the existing node in the template
         self.mutations.push(AssignId {
-            path: &template.template.get().node_paths[idx][1..],
+            path: &parent.path.path[1..],
             id,
         });
 
@@ -503,7 +537,7 @@ impl<'b> VirtualDom {
 
     pub(super) fn create_component_node(
         &mut self,
-        template: &'b VNode<'b>,
+        parent: Option<ElementRef>,
         component: &'b VComponent<'b>,
     ) -> usize {
         use RenderReturn::*;
@@ -515,8 +549,11 @@ impl<'b> VirtualDom {
 
         match unsafe { self.run_scope(scope).extend_lifetime_ref() } {
             // Create the component's root element
-            Ready(t) => self.create_scope(scope, t),
-            Aborted(t) => self.mount_aborted(template, t),
+            Ready(t) => {
+                self.assign_boundary_ref(parent, t);
+                self.create_scope(scope, t)
+            }
+            Aborted(t) => self.mount_aborted(t, parent),
         }
     }
 
@@ -532,20 +569,17 @@ impl<'b> VirtualDom {
             .unwrap_or_else(|| component.scope.get().unwrap())
     }
 
-    fn mount_aborted(&mut self, parent: &'b VNode<'b>, placeholder: &VPlaceholder) -> usize {
-        let id = self.next_element(parent, &[]);
+    fn mount_aborted(&mut self, placeholder: &VPlaceholder, parent: Option<ElementRef>) -> usize {
+        let id = self.next_element();
         self.mutations.push(Mutation::CreatePlaceholder { id });
         placeholder.id.set(Some(id));
+        placeholder.parent.set(parent);
+
         1
     }
 
-    fn set_slot(
-        &mut self,
-        template: &'b VNode<'b>,
-        slot: &'b Cell<Option<ElementId>>,
-        id: usize,
-    ) -> ElementId {
-        let id = self.next_element(template, template.template.get().node_paths[id]);
+    fn set_slot(&mut self, slot: &'b Cell<Option<ElementId>>) -> ElementId {
+        let id = self.next_element();
         slot.set(Some(id));
         id
     }

+ 149 - 65
packages/core/src/diff.rs

@@ -1,7 +1,10 @@
 use crate::{
     any_props::AnyProps,
     arena::ElementId,
-    innerlude::{BorrowedAttributeValue, DirtyScope, VComponent, VPlaceholder, VText},
+    innerlude::{
+        BorrowedAttributeValue, DirtyScope, ElementPath, ElementRef, VComponent, VPlaceholder,
+        VText,
+    },
     mutations::Mutation,
     nodes::RenderReturn,
     nodes::{DynamicNode, VNode},
@@ -39,19 +42,27 @@ impl<'b> VirtualDom {
                 (Ready(l), Aborted(p)) => self.diff_ok_to_err(l, p),
 
                 // Just move over the placeholder
-                (Aborted(l), Aborted(r)) => r.id.set(l.id.get()),
+                (Aborted(l), Aborted(r)) => {
+                    r.id.set(l.id.get());
+                    r.parent.set(l.parent.get())
+                }
 
                 // Placeholder becomes something
                 // We should also clear the error now
-                (Aborted(l), Ready(r)) => self.replace_placeholder(l, [r]),
+                (Aborted(l), Ready(r)) => self.replace_placeholder(
+                    l,
+                    [r],
+                    l.parent.get().expect("root node should not be none"),
+                ),
             };
         }
         self.runtime.scope_stack.borrow_mut().pop();
     }
 
     fn diff_ok_to_err(&mut self, l: &'b VNode<'b>, p: &'b VPlaceholder) {
-        let id = self.next_null();
+        let id = self.next_element();
         p.id.set(Some(id));
+        p.parent.set(l.parent.get());
         self.mutations.push(Mutation::CreatePlaceholder { id });
 
         let pre_edits = self.mutations.edits.len();
@@ -81,12 +92,24 @@ impl<'b> VirtualDom {
                 if let Some(&template) = map.get(&byte_index) {
                     right_template.template.set(template);
                     if template != left_template.template.get() {
-                        return self.replace(left_template, [right_template]);
+                        let parent = left_template.parent.take();
+                        return self.replace(left_template, [right_template], parent);
                     }
                 }
             }
         }
 
+        // Copy over the parent
+        {
+            right_template.parent.set(left_template.parent.get());
+        }
+
+        // Update the bubble id pointer
+        right_template.stable_id.set(left_template.stable_id.get());
+        if let Some(bubble_id) = right_template.stable_id.get() {
+            self.set_template(bubble_id, right_template);
+        }
+
         // If the templates are the same, we don't need to do anything, nor do we want to
         if templates_are_the_same(left_template, right_template) {
             return;
@@ -105,12 +128,8 @@ impl<'b> VirtualDom {
             .zip(right_template.dynamic_attrs.iter())
             .for_each(|(left_attr, right_attr)| {
                 // Move over the ID from the old to the new
-                right_attr
-                    .mounted_element
-                    .set(left_attr.mounted_element.get());
-
-                // We want to make sure anything that gets pulled is valid
-                self.update_template(left_attr.mounted_element.get(), right_template);
+                let mounted_element = left_attr.mounted_element.get();
+                right_attr.mounted_element.set(mounted_element);
 
                 // If the attributes are different (or volatile), we need to update them
                 if left_attr.value != right_attr.value || left_attr.volatile {
@@ -123,8 +142,16 @@ impl<'b> VirtualDom {
             .dynamic_nodes
             .iter()
             .zip(right_template.dynamic_nodes.iter())
-            .for_each(|(left_node, right_node)| {
-                self.diff_dynamic_node(left_node, right_node, right_template);
+            .enumerate()
+            .for_each(|(dyn_node_idx, (left_node, right_node))| {
+                let current_ref = ElementRef {
+                    template: right_template.stable_id().unwrap(),
+                    path: ElementPath {
+                        path: left_template.template.get().node_paths[dyn_node_idx],
+                    },
+                    scope: self.runtime.scope_stack.borrow().last().copied().unwrap(),
+                };
+                self.diff_dynamic_node(left_node, right_node, current_ref);
             });
 
         // Make sure the roots get transferred over while we're here
@@ -135,30 +162,24 @@ impl<'b> VirtualDom {
                 right.push(element);
             }
         }
-
-        let root_ids = right_template.root_ids.borrow();
-
-        // Update the node refs
-        for i in 0..root_ids.len() {
-            if let Some(root_id) = root_ids.get(i) {
-                self.update_template(*root_id, right_template);
-            }
-        }
     }
 
     fn diff_dynamic_node(
         &mut self,
         left_node: &'b DynamicNode<'b>,
         right_node: &'b DynamicNode<'b>,
-        node: &'b VNode<'b>,
+        parent: ElementRef,
     ) {
         match (left_node, right_node) {
-            (Text(left), Text(right)) => self.diff_vtext(left, right, node),
-            (Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right),
-            (Placeholder(left), Placeholder(right)) => right.id.set(left.id.get()),
-            (Component(left), Component(right)) => self.diff_vcomponent(left, right, node),
-            (Placeholder(left), Fragment(right)) => self.replace_placeholder(left, *right),
-            (Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right),
+            (Text(left), Text(right)) => self.diff_vtext(left, right),
+            (Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right, parent),
+            (Placeholder(left), Placeholder(right)) => {
+                right.id.set(left.id.get());
+                right.parent.set(left.parent.get());
+            },
+            (Component(left), Component(right)) => self.diff_vcomponent(left, right, Some(parent)),
+            (Placeholder(left), Fragment(right)) => self.replace_placeholder(left, *right, parent),
+            (Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right, parent),
             _ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."),
         };
     }
@@ -179,7 +200,7 @@ impl<'b> VirtualDom {
         &mut self,
         left: &'b VComponent<'b>,
         right: &'b VComponent<'b>,
-        right_template: &'b VNode<'b>,
+        parent: Option<ElementRef>,
     ) {
         if std::ptr::eq(left, right) {
             return;
@@ -187,7 +208,7 @@ impl<'b> VirtualDom {
 
         // Replace components that have different render fns
         if left.render_fn != right.render_fn {
-            return self.replace_vcomponent(right_template, right, left);
+            return self.replace_vcomponent(right, left, parent);
         }
 
         // Make sure the new vcomponent has the right scopeid associated to it
@@ -228,11 +249,11 @@ impl<'b> VirtualDom {
 
     fn replace_vcomponent(
         &mut self,
-        right_template: &'b VNode<'b>,
         right: &'b VComponent<'b>,
         left: &'b VComponent<'b>,
+        parent: Option<ElementRef>,
     ) {
-        let m = self.create_component_node(right_template, right);
+        let m = self.create_component_node(parent, right);
 
         let pre_edits = self.mutations.edits.len();
 
@@ -287,11 +308,12 @@ impl<'b> VirtualDom {
     /// }
     /// ```
     fn light_diff_templates(&mut self, left: &'b VNode<'b>, right: &'b VNode<'b>) {
+        let parent = left.parent.take();
         match matching_components(left, right) {
-            None => self.replace(left, [right]),
+            None => self.replace(left, [right], parent),
             Some(components) => components
                 .into_iter()
-                .for_each(|(l, r)| self.diff_vcomponent(l, r, right)),
+                .for_each(|(l, r)| self.diff_vcomponent(l, r, parent)),
         }
     }
 
@@ -299,11 +321,8 @@ impl<'b> VirtualDom {
     ///
     /// This just moves the ID of the old node over to the new node, and then sets the text of the new node if it's
     /// different.
-    fn diff_vtext(&mut self, left: &'b VText<'b>, right: &'b VText<'b>, node: &'b VNode<'b>) {
-        let id = left
-            .id
-            .get()
-            .unwrap_or_else(|| self.next_element(node, &[0]));
+    fn diff_vtext(&mut self, left: &'b VText<'b>, right: &'b VText<'b>) {
+        let id = left.id.get().unwrap_or_else(|| self.next_element());
 
         right.id.set(Some(id));
         if left.value != right.value {
@@ -312,7 +331,12 @@ impl<'b> VirtualDom {
         }
     }
 
-    fn diff_non_empty_fragment(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+    fn diff_non_empty_fragment(
+        &mut self,
+        old: &'b [VNode<'b>],
+        new: &'b [VNode<'b>],
+        parent: ElementRef,
+    ) {
         let new_is_keyed = new[0].key.is_some();
         let old_is_keyed = old[0].key.is_some();
         debug_assert!(
@@ -325,9 +349,9 @@ impl<'b> VirtualDom {
         );
 
         if new_is_keyed && old_is_keyed {
-            self.diff_keyed_children(old, new);
+            self.diff_keyed_children(old, new, parent);
         } else {
-            self.diff_non_keyed_children(old, new);
+            self.diff_non_keyed_children(old, new, parent);
         }
     }
 
@@ -339,7 +363,12 @@ impl<'b> VirtualDom {
     //     [... parent]
     //
     // the change list stack is in the same state when this function returns.
-    fn diff_non_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+    fn diff_non_keyed_children(
+        &mut self,
+        old: &'b [VNode<'b>],
+        new: &'b [VNode<'b>],
+        parent: ElementRef,
+    ) {
         use std::cmp::Ordering;
 
         // Handled these cases in `diff_children` before calling this function.
@@ -348,7 +377,9 @@ impl<'b> VirtualDom {
 
         match old.len().cmp(&new.len()) {
             Ordering::Greater => self.remove_nodes(&old[new.len()..]),
-            Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()),
+            Ordering::Less => {
+                self.create_and_insert_after(&new[old.len()..], old.last().unwrap(), parent)
+            }
             Ordering::Equal => {}
         }
 
@@ -373,7 +404,12 @@ impl<'b> VirtualDom {
     // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
     //
     // The stack is empty upon entry.
-    fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+    fn diff_keyed_children(
+        &mut self,
+        old: &'b [VNode<'b>],
+        new: &'b [VNode<'b>],
+        parent: ElementRef,
+    ) {
         if cfg!(debug_assertions) {
             let mut keys = rustc_hash::FxHashSet::default();
             let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
@@ -401,7 +437,7 @@ impl<'b> VirtualDom {
         //
         // `shared_prefix_count` is the count of how many nodes at the start of
         // `new` and `old` share the same keys.
-        let (left_offset, right_offset) = match self.diff_keyed_ends(old, new) {
+        let (left_offset, right_offset) = match self.diff_keyed_ends(old, new, parent) {
             Some(count) => count,
             None => return,
         };
@@ -427,18 +463,18 @@ impl<'b> VirtualDom {
             if left_offset == 0 {
                 // insert at the beginning of the old list
                 let foothold = &old[old.len() - right_offset];
-                self.create_and_insert_before(new_middle, foothold);
+                self.create_and_insert_before(new_middle, foothold, parent);
             } else if right_offset == 0 {
                 // insert at the end  the old list
                 let foothold = old.last().unwrap();
-                self.create_and_insert_after(new_middle, foothold);
+                self.create_and_insert_after(new_middle, foothold, parent);
             } else {
                 // inserting in the middle
                 let foothold = &old[left_offset - 1];
-                self.create_and_insert_after(new_middle, foothold);
+                self.create_and_insert_after(new_middle, foothold, parent);
             }
         } else {
-            self.diff_keyed_middle(old_middle, new_middle);
+            self.diff_keyed_middle(old_middle, new_middle, parent);
         }
     }
 
@@ -451,6 +487,7 @@ impl<'b> VirtualDom {
         &mut self,
         old: &'b [VNode<'b>],
         new: &'b [VNode<'b>],
+        parent: ElementRef,
     ) -> Option<(usize, usize)> {
         let mut left_offset = 0;
 
@@ -466,7 +503,7 @@ impl<'b> VirtualDom {
         // If that was all of the old children, then create and append the remaining
         // new children and we're finished.
         if left_offset == old.len() {
-            self.create_and_insert_after(&new[left_offset..], old.last().unwrap());
+            self.create_and_insert_after(&new[left_offset..], old.last().unwrap(), parent);
             return None;
         }
 
@@ -505,7 +542,12 @@ impl<'b> VirtualDom {
     //
     // Upon exit from this function, it will be restored to that same self.
     #[allow(clippy::too_many_lines)]
-    fn diff_keyed_middle(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+    fn diff_keyed_middle(
+        &mut self,
+        old: &'b [VNode<'b>],
+        new: &'b [VNode<'b>],
+        parent: ElementRef,
+    ) {
         /*
         1. Map the old keys into a numerical ordering based on indices.
         2. Create a map of old key to its index
@@ -562,7 +604,7 @@ impl<'b> VirtualDom {
         if shared_keys.is_empty() {
             if old.first().is_some() {
                 self.remove_nodes(&old[1..]);
-                self.replace(&old[0], new);
+                self.replace(&old[0], new, Some(parent));
             } else {
                 // I think this is wrong - why are we appending?
                 // only valid of the if there are no trailing elements
@@ -739,20 +781,38 @@ impl<'b> VirtualDom {
             .sum()
     }
 
-    fn create_children(&mut self, nodes: impl IntoIterator<Item = &'b VNode<'b>>) -> usize {
+    pub(crate) fn create_children(
+        &mut self,
+        nodes: impl IntoIterator<Item = &'b VNode<'b>>,
+        parent: Option<ElementRef>,
+    ) -> usize {
         nodes
             .into_iter()
-            .fold(0, |acc, child| acc + self.create(child))
+            .map(|child| {
+                self.assign_boundary_ref(parent, child);
+                self.create(child)
+            })
+            .sum()
     }
 
-    fn create_and_insert_before(&mut self, new: &'b [VNode<'b>], before: &'b VNode<'b>) {
-        let m = self.create_children(new);
+    fn create_and_insert_before(
+        &mut self,
+        new: &'b [VNode<'b>],
+        before: &'b VNode<'b>,
+        parent: ElementRef,
+    ) {
+        let m = self.create_children(new, Some(parent));
         let id = self.find_first_element(before);
         self.mutations.push(Mutation::InsertBefore { id, m })
     }
 
-    fn create_and_insert_after(&mut self, new: &'b [VNode<'b>], after: &'b VNode<'b>) {
-        let m = self.create_children(new);
+    fn create_and_insert_after(
+        &mut self,
+        new: &'b [VNode<'b>],
+        after: &'b VNode<'b>,
+        parent: ElementRef,
+    ) {
+        let m = self.create_children(new, Some(parent));
         let id = self.find_last_element(after);
         self.mutations.push(Mutation::InsertAfter { id, m })
     }
@@ -762,15 +822,21 @@ impl<'b> VirtualDom {
         &mut self,
         l: &'b VPlaceholder,
         r: impl IntoIterator<Item = &'b VNode<'b>>,
+        parent: ElementRef,
     ) {
-        let m = self.create_children(r);
+        let m = self.create_children(r, Some(parent));
         let id = l.id.get().unwrap();
         self.mutations.push(Mutation::ReplaceWith { id, m });
         self.reclaim(id);
     }
 
-    fn replace(&mut self, left: &'b VNode<'b>, right: impl IntoIterator<Item = &'b VNode<'b>>) {
-        let m = self.create_children(right);
+    fn replace(
+        &mut self,
+        left: &'b VNode<'b>,
+        right: impl IntoIterator<Item = &'b VNode<'b>>,
+        parent: Option<ElementRef>,
+    ) {
+        let m = self.create_children(right, parent);
 
         let pre_edits = self.mutations.edits.len();
 
@@ -789,11 +855,12 @@ impl<'b> VirtualDom {
         };
     }
 
-    fn node_to_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b VPlaceholder) {
+    fn node_to_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b VPlaceholder, parent: ElementRef) {
         // Create the placeholder first, ensuring we get a dedicated ID for the placeholder
-        let placeholder = self.next_element(&l[0], &[]);
+        let placeholder = self.next_element();
 
         r.id.set(Some(placeholder));
+        r.parent.set(Some(parent));
 
         self.mutations
             .push(Mutation::CreatePlaceholder { id: placeholder });
@@ -831,6 +898,16 @@ impl<'b> VirtualDom {
         // Clean up the roots, assuming we need to generate mutations for these
         // This is done last in order to preserve Node ID reclaim order (reclaim in reverse order of claim)
         self.reclaim_roots(node, gen_muts);
+
+        // Clean up the vnode id
+        self.reclaim_vnode_id(node);
+    }
+
+    fn reclaim_vnode_id(&mut self, node: &'b VNode<'b>) {
+        // Clean up the vnode id
+        if let Some(id) = node.stable_id() {
+            self.element_refs.remove(id.0);
+        }
     }
 
     fn reclaim_roots(&mut self, node: &VNode, gen_muts: bool) {
@@ -989,6 +1066,13 @@ impl<'b> VirtualDom {
             }
         }
     }
+
+    pub(crate) fn assign_boundary_ref(&mut self, parent: Option<ElementRef>, child: &'b VNode<'b>) {
+        if let Some(parent) = parent {
+            // assign the parent of the child
+            child.parent.set(Some(parent));
+        }
+    }
 }
 
 /// Are the templates the same?

+ 2 - 1
packages/core/src/fragment.rs

@@ -30,7 +30,8 @@ pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
     let children = cx.props.0.as_ref()?;
     Some(VNode {
         key: children.key,
-        parent: children.parent,
+        parent: children.parent.clone(),
+        stable_id: children.stable_id.clone(),
         template: children.template.clone(),
         root_ids: children.root_ids.clone(),
         dynamic_nodes: children.dynamic_nodes,

+ 36 - 4
packages/core/src/nodes.rs

@@ -1,3 +1,4 @@
+use crate::innerlude::{ElementRef, VNodeId};
 use crate::{
     any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState,
 };
@@ -47,7 +48,10 @@ pub struct VNode<'a> {
     pub key: Option<&'a str>,
 
     /// When rendered, this template will be linked to its parent manually
-    pub parent: Option<ElementId>,
+    pub(crate) parent: Cell<Option<ElementRef>>,
+
+    /// The bubble id assigned to the child that we need to update and drop when diffing happens
+    pub(crate) stable_id: Cell<Option<VNodeId>>,
 
     /// The static nodes and static descriptor of the template
     pub template: Cell<Template<'static>>,
@@ -68,7 +72,8 @@ impl<'a> VNode<'a> {
     pub fn empty(cx: &'a ScopeState) -> Element<'a> {
         Some(VNode {
             key: None,
-            parent: None,
+            parent: Default::default(),
+            stable_id: Default::default(),
             root_ids: RefCell::new(bumpalo::collections::Vec::new_in(cx.bump())),
             dynamic_nodes: &[],
             dynamic_attrs: &[],
@@ -81,6 +86,30 @@ impl<'a> VNode<'a> {
         })
     }
 
+    /// Create a new VNode
+    pub fn new(
+        key: Option<&'a str>,
+        template: Template<'static>,
+        root_ids: bumpalo::collections::Vec<'a, ElementId>,
+        dynamic_nodes: &'a [DynamicNode<'a>],
+        dynamic_attrs: &'a [Attribute<'a>],
+    ) -> Self {
+        Self {
+            key,
+            parent: Cell::new(None),
+            stable_id: Cell::new(None),
+            template: Cell::new(template),
+            root_ids: RefCell::new(root_ids),
+            dynamic_nodes,
+            dynamic_attrs,
+        }
+    }
+
+    /// Get the stable id of this node used for bubbling events
+    pub(crate) fn stable_id(&self) -> Option<VNodeId> {
+        self.stable_id.get()
+    }
+
     /// Load a dynamic root at the given index
     ///
     /// Returns [`None`] if the root is actually a static node (Element/Text)
@@ -319,7 +348,7 @@ pub struct VComponent<'a> {
 
     /// The function pointer of the component, known at compile time
     ///
-    /// It is possible that components get folded at comppile time, so these shouldn't be really used as a key
+    /// It is possible that components get folded at compile time, so these shouldn't be really used as a key
     pub(crate) render_fn: *const (),
 
     pub(crate) props: RefCell<Option<Box<dyn AnyProps<'a> + 'a>>>,
@@ -372,6 +401,8 @@ impl<'a> VText<'a> {
 pub struct VPlaceholder {
     /// The ID of this node in the real DOM
     pub(crate) id: Cell<Option<ElementId>>,
+    /// The parent of this node
+    pub(crate) parent: Cell<Option<ElementRef>>,
 }
 
 impl VPlaceholder {
@@ -722,7 +753,8 @@ impl<'b> IntoDynNode<'b> for Arguments<'_> {
 impl<'a> IntoDynNode<'a> for &'a VNode<'a> {
     fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
         DynamicNode::Fragment(_cx.bump().alloc([VNode {
-            parent: self.parent,
+            parent: self.parent.clone(),
+            stable_id: self.stable_id.clone(),
             template: self.template.clone(),
             root_ids: self.root_ids.clone(),
             key: self.key,

+ 1 - 0
packages/core/src/scope_arena.rs

@@ -36,6 +36,7 @@ impl VirtualDom {
 
             borrowed_props: Default::default(),
             attributes_to_drop_before_render: Default::default(),
+            element_refs_to_drop: Default::default(),
         }));
 
         let context =

+ 3 - 2
packages/core/src/scopes.rs

@@ -3,7 +3,7 @@ use crate::{
     any_props::VProps,
     bump_frame::BumpFrame,
     innerlude::ErrorBoundary,
-    innerlude::{DynamicNode, EventHandler, VComponent, VText},
+    innerlude::{DynamicNode, EventHandler, VComponent, VNodeId, VText},
     lazynodes::LazyNodes,
     nodes::{IntoAttributeValue, IntoDynNode, RenderReturn},
     runtime::Runtime,
@@ -94,6 +94,7 @@ pub struct ScopeState {
     pub(crate) hook_idx: Cell<usize>,
 
     pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
+    pub(crate) element_refs_to_drop: RefCell<Vec<VNodeId>>,
     pub(crate) attributes_to_drop_before_render: RefCell<Vec<*const Attribute<'static>>>,
 
     pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
@@ -466,7 +467,7 @@ impl<'src> ScopeState {
             render_fn: component as *const (),
             static_props: P::IS_STATIC,
             props: RefCell::new(Some(extended)),
-            scope: Cell::new(None),
+            scope: Default::default(),
         })
     }
 

+ 84 - 76
packages/core/src/virtual_dom.rs

@@ -4,19 +4,19 @@
 
 use crate::{
     any_props::VProps,
-    arena::{ElementId, ElementRef},
-    innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg},
+    arena::ElementId,
+    innerlude::{DirtyScope, ElementRef, ErrorBoundary, Mutations, Scheduler, SchedulerMsg},
     mutations::Mutation,
     nodes::RenderReturn,
     nodes::{Template, TemplateId},
     runtime::{Runtime, RuntimeGuard},
     scopes::{ScopeId, ScopeState},
-    AttributeValue, Element, Event, Scope,
+    AttributeValue, Element, Event, Scope, VNode,
 };
 use futures_util::{pin_mut, StreamExt};
 use rustc_hash::{FxHashMap, FxHashSet};
 use slab::Slab;
-use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
+use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, ptr::NonNull, rc::Rc};
 
 /// A virtual node system that progresses user events and diffs UI trees.
 ///
@@ -186,7 +186,10 @@ pub struct VirtualDom {
     pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
 
     // Every element is actually a dual reference - one to the template and the other to the dynamic node in that template
-    pub(crate) elements: Slab<ElementRef>,
+    pub(crate) element_refs: Slab<Option<NonNull<VNode<'static>>>>,
+
+    // The element ids that are used in the renderer
+    pub(crate) elements: Slab<Option<ElementRef>>,
 
     pub(crate) mutations: Mutations<'static>,
 
@@ -263,6 +266,7 @@ impl VirtualDom {
             dirty_scopes: Default::default(),
             templates: Default::default(),
             elements: Default::default(),
+            element_refs: Default::default(),
             mutations: Mutations::default(),
             suspended_scopes: Default::default(),
         };
@@ -276,7 +280,7 @@ impl VirtualDom {
         root.provide_context(Rc::new(ErrorBoundary::new(ScopeId::ROOT)));
 
         // the root element is always given element ID 0 since it's the container for the entire tree
-        dom.elements.insert(ElementRef::none());
+        dom.elements.insert(None);
 
         dom
     }
@@ -314,9 +318,9 @@ impl VirtualDom {
         }
     }
 
-    /// Call a listener inside the VirtualDom with data from outside the VirtualDom.
+    /// Call a listener inside the VirtualDom with data from outside the VirtualDom. **The ElementId passed in must be the id of an dynamic element, not a static node or a text node.**
     ///
-    /// This method will identify the appropriate element. The data must match up with the listener delcared. Note that
+    /// This method will identify the appropriate element. The data must match up with the listener declared. Note that
     /// this method does not give any indication as to the success of the listener call. If the listener is not found,
     /// nothing will happen.
     ///
@@ -353,7 +357,15 @@ impl VirtualDom {
         | | |       <-- no, broke early
         |           <-- no, broke early
         */
-        let mut parent_path = self.elements.get(element.0);
+        let parent_path = match self.elements.get(element.0) {
+            Some(Some(el)) => el,
+            _ => return,
+        };
+        let mut parent_node = self
+            .element_refs
+            .get(parent_path.template.0)
+            .cloned()
+            .map(|el| (*parent_path, el));
         let mut listeners = vec![];
 
         // We will clone this later. The data itself is wrapped in RC to be used in callbacks if required
@@ -365,82 +377,81 @@ impl VirtualDom {
         // If the event bubbles, we traverse through the tree until we find the target element.
         if bubbles {
             // Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
-            while let Some(el_ref) = parent_path {
+            while let Some((path, el_ref)) = parent_node {
                 // safety: we maintain references of all vnodes in the element slab
-                if let Some(template) = el_ref.template {
-                    let template = unsafe { template.as_ref() };
-                    let node_template = template.template.get();
-                    let target_path = el_ref.path;
-
-                    for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
-                        let this_path = node_template.attr_paths[idx];
-
-                        // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
-                        if attr.name.trim_start_matches("on") == name
-                            && target_path.is_decendant(&this_path)
-                        {
-                            listeners.push(&attr.value);
-
-                            // Break if this is the exact target element.
-                            // This means we won't call two listeners with the same name on the same element. This should be
-                            // documented, or be rejected from the rsx! macro outright
-                            if target_path == this_path {
-                                break;
-                            }
+                let template = unsafe { el_ref.unwrap().as_ref() };
+                let node_template = template.template.get();
+                let target_path = path.path;
+
+                for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
+                    let this_path = node_template.attr_paths[idx];
+
+                    // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
+                    if attr.name.trim_start_matches("on") == name
+                        && target_path.is_decendant(&this_path)
+                    {
+                        listeners.push(&attr.value);
+
+                        // Break if this is the exact target element.
+                        // This means we won't call two listeners with the same name on the same element. This should be
+                        // documented, or be rejected from the rsx! macro outright
+                        if target_path == this_path {
+                            break;
                         }
                     }
+                }
 
-                    // Now that we've accumulated all the parent attributes for the target element, call them in reverse order
-                    // We check the bubble state between each call to see if the event has been stopped from bubbling
-                    for listener in listeners.drain(..).rev() {
-                        if let AttributeValue::Listener(listener) = listener {
-                            let origin = el_ref.scope;
-                            self.runtime.scope_stack.borrow_mut().push(origin);
-                            self.runtime.rendering.set(false);
-                            if let Some(cb) = listener.borrow_mut().as_deref_mut() {
-                                cb(uievent.clone());
-                            }
-                            self.runtime.scope_stack.borrow_mut().pop();
-                            self.runtime.rendering.set(true);
+                // Now that we've accumulated all the parent attributes for the target element, call them in reverse order
+                // We check the bubble state between each call to see if the event has been stopped from bubbling
+                for listener in listeners.drain(..).rev() {
+                    if let AttributeValue::Listener(listener) = listener {
+                        let origin = path.scope;
+                        self.runtime.scope_stack.borrow_mut().push(origin);
+                        self.runtime.rendering.set(false);
+                        if let Some(cb) = listener.borrow_mut().as_deref_mut() {
+                            cb(uievent.clone());
+                        }
+                        self.runtime.scope_stack.borrow_mut().pop();
+                        self.runtime.rendering.set(true);
 
-                            if !uievent.propagates.get() {
-                                return;
-                            }
+                        if !uievent.propagates.get() {
+                            return;
                         }
                     }
-
-                    parent_path = template.parent.and_then(|id| self.elements.get(id.0));
-                } else {
-                    break;
                 }
+
+                parent_node = template.parent.get().and_then(|element_ref| {
+                    self.element_refs
+                        .get(element_ref.template.0)
+                        .cloned()
+                        .map(|el| (element_ref, el))
+                });
             }
         } else {
             // Otherwise, we just call the listener on the target element
-            if let Some(el_ref) = parent_path {
+            if let Some((path, el_ref)) = parent_node {
                 // safety: we maintain references of all vnodes in the element slab
-                if let Some(template) = el_ref.template {
-                    let template = unsafe { template.as_ref() };
-                    let node_template = template.template.get();
-                    let target_path = el_ref.path;
-
-                    for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
-                        let this_path = node_template.attr_paths[idx];
-
-                        // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
-                        // Only call the listener if this is the exact target element.
-                        if attr.name.trim_start_matches("on") == name && target_path == this_path {
-                            if let AttributeValue::Listener(listener) = &attr.value {
-                                let origin = el_ref.scope;
-                                self.runtime.scope_stack.borrow_mut().push(origin);
-                                self.runtime.rendering.set(false);
-                                if let Some(cb) = listener.borrow_mut().as_deref_mut() {
-                                    cb(uievent.clone());
-                                }
-                                self.runtime.scope_stack.borrow_mut().pop();
-                                self.runtime.rendering.set(true);
-
-                                break;
+                let template = unsafe { el_ref.unwrap().as_ref() };
+                let node_template = template.template.get();
+                let target_path = path.path;
+
+                for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
+                    let this_path = node_template.attr_paths[idx];
+
+                    // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
+                    // Only call the listener if this is the exact target element.
+                    if attr.name.trim_start_matches("on") == name && target_path == this_path {
+                        if let AttributeValue::Listener(listener) = &attr.value {
+                            let origin = path.scope;
+                            self.runtime.scope_stack.borrow_mut().push(origin);
+                            self.runtime.rendering.set(false);
+                            if let Some(cb) = listener.borrow_mut().as_deref_mut() {
+                                cb(uievent.clone());
                             }
+                            self.runtime.scope_stack.borrow_mut().pop();
+                            self.runtime.rendering.set(true);
+
+                            break;
                         }
                     }
                 }
@@ -563,7 +574,7 @@ impl VirtualDom {
             // If an error occurs, we should try to render the default error component and context where the error occured
             RenderReturn::Aborted(placeholder) => {
                 tracing::debug!("Ran into suspended or aborted scope during rebuild");
-                let id = self.next_null();
+                let id = self.next_element();
                 placeholder.id.set(Some(id));
                 self.mutations.push(Mutation::CreatePlaceholder { id });
             }
@@ -595,15 +606,12 @@ impl VirtualDom {
     /// The mutations will be thrown out, so it's best to use this method for things like SSR that have async content
     pub async fn wait_for_suspense(&mut self) {
         loop {
-            // println!("waiting for suspense {:?}", self.suspended_scopes);
             if self.suspended_scopes.is_empty() {
                 return;
             }
 
-            // println!("waiting for suspense");
             self.wait_for_work().await;
 
-            // println!("Rendered immediately");
             _ = self.render_immediate();
         }
     }

+ 69 - 0
packages/core/tests/event_propagation.rs

@@ -0,0 +1,69 @@
+use dioxus::prelude::*;
+use dioxus_core::ElementId;
+use std::{rc::Rc, sync::Mutex};
+
+static CLICKS: Mutex<usize> = Mutex::new(0);
+
+#[test]
+fn events_propagate() {
+    let mut dom = VirtualDom::new(app);
+    _ = dom.rebuild();
+
+    // Top-level click is registered
+    dom.handle_event("click", Rc::new(MouseData::default()), ElementId(1), true);
+    assert_eq!(*CLICKS.lock().unwrap(), 1);
+
+    // break reference....
+    for _ in 0..5 {
+        dom.mark_dirty(ScopeId(0));
+        _ = dom.render_immediate();
+    }
+
+    // Lower click is registered
+    dom.handle_event("click", Rc::new(MouseData::default()), ElementId(2), true);
+    assert_eq!(*CLICKS.lock().unwrap(), 3);
+
+    // break reference....
+    for _ in 0..5 {
+        dom.mark_dirty(ScopeId(0));
+        _ = dom.render_immediate();
+    }
+
+    // Stop propagation occurs
+    dom.handle_event("click", Rc::new(MouseData::default()), ElementId(2), true);
+    assert_eq!(*CLICKS.lock().unwrap(), 3);
+}
+
+fn app(cx: Scope) -> Element {
+    render! {
+        div {
+            onclick: move |_| {
+                println!("top clicked");
+                *CLICKS.lock().unwrap() += 1;
+            },
+
+            vec![
+                render! {
+                    problematic_child {}
+                }
+            ].into_iter()
+        }
+    }
+}
+
+fn problematic_child(cx: Scope) -> Element {
+    render! {
+        button {
+            onclick: move |evt| {
+                println!("bottom clicked");
+                let mut clicks = CLICKS.lock().unwrap();
+
+                if *clicks == 3 {
+                    evt.stop_propagation();
+                } else {
+                    *clicks += 1;
+                }
+            }
+        }
+    }
+}

+ 29 - 30
packages/core/tests/fuzzing.rs

@@ -2,7 +2,7 @@
 
 use dioxus::prelude::Props;
 use dioxus_core::*;
-use std::{cell::Cell, collections::HashSet};
+use std::{cfg, collections::HashSet};
 
 fn random_ns() -> Option<&'static str> {
     let namespace = rand::random::<u8>() % 2;
@@ -170,22 +170,23 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode {
     let range = if depth > 5 { 1 } else { 4 };
     match rand::random::<u8>() % range {
         0 => DynamicNode::Placeholder(Default::default()),
-        1 => cx.make_node((0..(rand::random::<u8>() % 5)).map(|_| VNode {
-            key: None,
-            parent: Default::default(),
-            template: Cell::new(Template {
-                name: concat!(file!(), ":", line!(), ":", column!(), ":0"),
-                roots: &[TemplateNode::Dynamic { id: 0 }],
-                node_paths: &[&[0]],
-                attr_paths: &[],
-            }),
-            root_ids: bumpalo::collections::Vec::new_in(cx.bump()).into(),
-            dynamic_nodes: cx.bump().alloc([cx.component(
-                create_random_element,
-                DepthProps { depth, root: false },
-                "create_random_element",
-            )]),
-            dynamic_attrs: &[],
+        1 => cx.make_node((0..(rand::random::<u8>() % 5)).map(|_| {
+            VNode::new(
+                None,
+                Template {
+                    name: concat!(file!(), ":", line!(), ":", column!(), ":0"),
+                    roots: &[TemplateNode::Dynamic { id: 0 }],
+                    node_paths: &[&[0]],
+                    attr_paths: &[],
+                },
+                bumpalo::collections::Vec::new_in(cx.bump()),
+                cx.bump().alloc([cx.component(
+                    create_random_element,
+                    DepthProps { depth, root: false },
+                    "create_random_element",
+                )]),
+                &[],
+            )
         })),
         2 => cx.component(
             create_random_element,
@@ -271,13 +272,11 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
                 )
                 .into_boxed_str(),
             ));
-            // println!("{template:#?}");
-            let node = VNode {
-                key: None,
-                parent: None,
-                template: Cell::new(template),
-                root_ids: bumpalo::collections::Vec::new_in(cx.bump()).into(),
-                dynamic_nodes: {
+            let node = VNode::new(
+                None,
+                template,
+                bumpalo::collections::Vec::new_in(cx.bump()),
+                {
                     let dynamic_nodes: Vec<_> = dynamic_node_types
                         .iter()
                         .map(|ty| match ty {
@@ -291,12 +290,12 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
                         .collect();
                     cx.bump().alloc(dynamic_nodes)
                 },
-                dynamic_attrs: cx.bump().alloc(
+                cx.bump().alloc(
                     (0..template.attr_paths.len())
                         .map(|_| create_random_dynamic_attr(cx))
                         .collect::<Vec<_>>(),
                 ),
-            };
+            );
             Some(node)
         }
         _ => None,
@@ -306,10 +305,10 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
 }
 
 // test for panics when creating random nodes and templates
-#[cfg(not(miri))]
 #[test]
 fn create() {
-    for _ in 0..1000 {
+    let repeat_count = if cfg!(miri) { 100 } else { 1000 };
+    for _ in 0..repeat_count {
         let mut vdom =
             VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0, root: true });
         let _ = vdom.rebuild();
@@ -318,10 +317,10 @@ fn create() {
 
 // test for panics when diffing random nodes
 // This test will change the template every render which is not very realistic, but it helps stress the system
-#[cfg(not(miri))]
 #[test]
 fn diff() {
-    for _ in 0..100000 {
+    let repeat_count = if cfg!(miri) { 100 } else { 1000 };
+    for _ in 0..repeat_count {
         let mut vdom =
             VirtualDom::new_with_props(create_random_element, DepthProps { depth: 0, root: true });
         let _ = vdom.rebuild();

+ 0 - 1
packages/fullstack/src/hooks/server_cached.rs

@@ -15,7 +15,6 @@ use serde::{de::DeserializeOwned, Serialize};
 ///    let state1 = use_state(cx, || server_cached(|| {
 ///       1234
 ///    }));
-///
 ///    todo!()
 /// }
 /// ```

+ 24 - 26
packages/native-core/tests/fuzzing.rs

@@ -3,7 +3,6 @@ use dioxus_core::*;
 use dioxus_native_core::prelude::*;
 use dioxus_native_core_macro::partial_derive_state;
 use shipyard::Component;
-use std::cell::Cell;
 
 fn random_ns() -> Option<&'static str> {
     let namespace = rand::random::<u8>() % 2;
@@ -178,22 +177,23 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode {
     let range = if depth > 3 { 1 } else { 3 };
     match rand::random::<u8>() % range {
         0 => DynamicNode::Placeholder(Default::default()),
-        1 => cx.make_node((0..(rand::random::<u8>() % 5)).map(|_| VNode {
-            key: None,
-            parent: Default::default(),
-            template: Cell::new(Template {
-                name: concat!(file!(), ":", line!(), ":", column!(), ":0"),
-                roots: &[TemplateNode::Dynamic { id: 0 }],
-                node_paths: &[&[0]],
-                attr_paths: &[],
-            }),
-            root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()).into(),
-            dynamic_nodes: cx.bump().alloc([cx.component(
-                create_random_element,
-                DepthProps { depth, root: false },
-                "create_random_element",
-            )]),
-            dynamic_attrs: &[],
+        1 => cx.make_node((0..(rand::random::<u8>() % 5)).map(|_| {
+            VNode::new(
+                None,
+                Template {
+                    name: concat!(file!(), ":", line!(), ":", column!(), ":0"),
+                    roots: &[TemplateNode::Dynamic { id: 0 }],
+                    node_paths: &[&[0]],
+                    attr_paths: &[],
+                },
+                dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()),
+                cx.bump().alloc([cx.component(
+                    create_random_element,
+                    DepthProps { depth, root: false },
+                    "create_random_element",
+                )]),
+                &[],
+            )
         })),
         2 => cx.component(
             create_random_element,
@@ -253,13 +253,11 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
                 .into_boxed_str(),
             ));
             println!("{template:#?}");
-            let node = VNode {
-                key: None,
-                parent: None,
-                template: Cell::new(template),
-                root_ids: dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump())
-                    .into(),
-                dynamic_nodes: {
+            let node = VNode::new(
+                None,
+                template,
+                dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()),
+                {
                     let dynamic_nodes: Vec<_> = dynamic_node_types
                         .iter()
                         .map(|ty| match ty {
@@ -273,12 +271,12 @@ fn create_random_element(cx: Scope<DepthProps>) -> Element {
                         .collect();
                     cx.bump().alloc(dynamic_nodes)
                 },
-                dynamic_attrs: cx.bump().alloc(
+                cx.bump().alloc(
                     (0..template.attr_paths.len())
                         .map(|_| create_random_dynamic_attr(cx))
                         .collect::<Vec<_>>(),
                 ),
-            };
+            );
             Some(node)
         }
         _ => None,

+ 7 - 8
packages/rsx/src/lib.rs

@@ -252,14 +252,13 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
                 node_paths: &[ #(#node_paths),* ],
                 attr_paths: &[ #(#attr_paths),* ],
             };
-            ::dioxus::core::VNode {
-                parent: None,
-                key: #key_tokens,
-                template: std::cell::Cell::new(TEMPLATE),
-                root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(#root_count, __cx.bump()).into(),
-                dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
-                dynamic_attrs: __cx.bump().alloc([ #( #dyn_attr_printer ),* ]),
-            }
+            ::dioxus::core::VNode::new(
+                #key_tokens,
+                TEMPLATE,
+                dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(#root_count, __cx.bump()),
+                __cx.bump().alloc([ #( #node_printer ),* ]),
+                __cx.bump().alloc([ #( #dyn_attr_printer ),* ]),
+            )
         });
     }
 }