mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
Fix event bubbling inside templates after a hot template reload (#2484)
This commit is contained in:
parent
b6cdcb4e7f
commit
5494e38cf8
4 changed files with 101 additions and 68 deletions
|
@ -80,11 +80,27 @@ impl VirtualDom {
|
|||
}
|
||||
|
||||
impl ElementPath {
|
||||
pub(crate) fn is_decendant(&self, small: &&[u8]) -> bool {
|
||||
small.len() <= self.path.len() && *small == &self.path[..small.len()]
|
||||
pub(crate) fn is_decendant(&self, small: &[u8]) -> bool {
|
||||
small.len() <= self.path.len() && small == &self.path[..small.len()]
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_decendant() {
|
||||
let event_path = ElementPath {
|
||||
path: &[1, 2, 3, 4, 5],
|
||||
};
|
||||
|
||||
assert!(event_path.is_decendant(&[1, 2, 3, 4, 5]));
|
||||
assert!(event_path.is_decendant(&[1, 2, 3, 4]));
|
||||
assert!(event_path.is_decendant(&[1, 2, 3]));
|
||||
assert!(event_path.is_decendant(&[1, 2]));
|
||||
assert!(event_path.is_decendant(&[1]));
|
||||
|
||||
assert!(!event_path.is_decendant(&[1, 2, 3, 4, 5, 6]));
|
||||
assert!(!event_path.is_decendant(&[2, 3, 4]));
|
||||
}
|
||||
|
||||
impl PartialEq<&[u8]> for ElementPath {
|
||||
fn eq(&self, other: &&[u8]) -> bool {
|
||||
self.path.eq(*other)
|
||||
|
|
|
@ -561,8 +561,12 @@ impl VNode {
|
|||
|
||||
// If this is a debug build, we need to check that the paths are in the correct order because hot reloading can cause scrambled states
|
||||
#[cfg(debug_assertions)]
|
||||
let (attrs_sorted, nodes_sorted) =
|
||||
{ (sort_bfs(template.attr_paths), sort_bfs(template.node_paths)) };
|
||||
let (attrs_sorted, nodes_sorted) = {
|
||||
(
|
||||
crate::nodes::sort_bfo(template.attr_paths),
|
||||
crate::nodes::sort_bfo(template.node_paths),
|
||||
)
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
let (mut attrs, mut nodes) = {
|
||||
(
|
||||
|
@ -959,54 +963,3 @@ fn matching_components<'a>(
|
|||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
fn sort_bfs(paths: &[&'static [u8]]) -> Vec<(usize, &'static [u8])> {
|
||||
let mut with_indecies = paths.iter().copied().enumerate().collect::<Vec<_>>();
|
||||
with_indecies.sort_unstable_by(|(_, a), (_, b)| {
|
||||
let mut a = a.iter();
|
||||
let mut b = b.iter();
|
||||
loop {
|
||||
match (a.next(), b.next()) {
|
||||
(Some(a), Some(b)) => {
|
||||
if a != b {
|
||||
return a.cmp(b);
|
||||
}
|
||||
}
|
||||
// The shorter path goes first
|
||||
(None, Some(_)) => return std::cmp::Ordering::Less,
|
||||
(Some(_), None) => return std::cmp::Ordering::Greater,
|
||||
(None, None) => return std::cmp::Ordering::Equal,
|
||||
}
|
||||
}
|
||||
});
|
||||
with_indecies
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(debug_assertions)]
|
||||
fn sorting() {
|
||||
let r: [(usize, &[u8]); 5] = [
|
||||
(0, &[0, 1]),
|
||||
(1, &[0, 2]),
|
||||
(2, &[1, 0]),
|
||||
(3, &[1, 0, 1]),
|
||||
(4, &[1, 2]),
|
||||
];
|
||||
assert_eq!(
|
||||
sort_bfs(&[&[0, 1,], &[0, 2,], &[1, 0,], &[1, 0, 1,], &[1, 2,],]),
|
||||
r
|
||||
);
|
||||
let r: [(usize, &[u8]); 6] = [
|
||||
(0, &[0]),
|
||||
(1, &[0, 1]),
|
||||
(2, &[0, 1, 2]),
|
||||
(3, &[1]),
|
||||
(4, &[1, 2]),
|
||||
(5, &[2]),
|
||||
];
|
||||
assert_eq!(
|
||||
sort_bfs(&[&[0], &[0, 1], &[0, 1, 2], &[1], &[1, 2], &[2],]),
|
||||
r
|
||||
);
|
||||
}
|
||||
|
|
|
@ -424,6 +424,22 @@ impl Template {
|
|||
.iter()
|
||||
.all(|root| matches!(root, Dynamic { .. } | DynamicText { .. }))
|
||||
}
|
||||
|
||||
/// Iterate over the attribute paths in order along with the original indexes for each path
|
||||
pub(crate) fn breadth_first_attribute_paths(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (usize, &'static [u8])> {
|
||||
// In release mode, hot reloading is disabled and everything is in breadth first order already
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
self.attr_paths.iter().copied().enumerate()
|
||||
}
|
||||
// If we are in debug mode, hot reloading may have messed up the order of the paths. We need to sort them
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
sort_bfo(self.attr_paths).into_iter()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A statically known node in a layout.
|
||||
|
@ -1012,3 +1028,54 @@ pub trait HasAttributes {
|
|||
volatile: bool,
|
||||
) -> Self;
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) fn sort_bfo(paths: &[&'static [u8]]) -> Vec<(usize, &'static [u8])> {
|
||||
let mut with_indecies = paths.iter().copied().enumerate().collect::<Vec<_>>();
|
||||
with_indecies.sort_unstable_by(|(_, a), (_, b)| {
|
||||
let mut a = a.iter();
|
||||
let mut b = b.iter();
|
||||
loop {
|
||||
match (a.next(), b.next()) {
|
||||
(Some(a), Some(b)) => {
|
||||
if a != b {
|
||||
return a.cmp(b);
|
||||
}
|
||||
}
|
||||
// The shorter path goes first
|
||||
(None, Some(_)) => return std::cmp::Ordering::Less,
|
||||
(Some(_), None) => return std::cmp::Ordering::Greater,
|
||||
(None, None) => return std::cmp::Ordering::Equal,
|
||||
}
|
||||
}
|
||||
});
|
||||
with_indecies
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(debug_assertions)]
|
||||
fn sorting() {
|
||||
let r: [(usize, &[u8]); 5] = [
|
||||
(0, &[0, 1]),
|
||||
(1, &[0, 2]),
|
||||
(2, &[1, 0]),
|
||||
(3, &[1, 0, 1]),
|
||||
(4, &[1, 2]),
|
||||
];
|
||||
assert_eq!(
|
||||
sort_bfo(&[&[0, 1,], &[0, 2,], &[1, 0,], &[1, 0, 1,], &[1, 2,],]),
|
||||
r
|
||||
);
|
||||
let r: [(usize, &[u8]); 6] = [
|
||||
(0, &[0]),
|
||||
(1, &[0, 1]),
|
||||
(2, &[0, 1, 2]),
|
||||
(3, &[1]),
|
||||
(4, &[1, 2]),
|
||||
(5, &[2]),
|
||||
];
|
||||
assert_eq!(
|
||||
sort_bfo(&[&[0], &[0, 1], &[0, 1, 2], &[1], &[1, 2], &[2],]),
|
||||
r
|
||||
);
|
||||
}
|
||||
|
|
|
@ -429,7 +429,7 @@ impl VirtualDom {
|
|||
|
||||
if let Some(Some(parent_path)) = self.elements.get(element.0).copied() {
|
||||
if bubbles {
|
||||
self.handle_bubbling_event(Some(parent_path), name, Event::new(data, bubbles));
|
||||
self.handle_bubbling_event(parent_path, name, Event::new(data, bubbles));
|
||||
} else {
|
||||
self.handle_non_bubbling_event(parent_path, name, Event::new(data, bubbles));
|
||||
}
|
||||
|
@ -799,14 +799,10 @@ impl VirtualDom {
|
|||
level = "trace",
|
||||
name = "VirtualDom::handle_bubbling_event"
|
||||
)]
|
||||
fn handle_bubbling_event(
|
||||
&mut self,
|
||||
mut parent: Option<ElementRef>,
|
||||
name: &str,
|
||||
uievent: Event<dyn Any>,
|
||||
) {
|
||||
fn handle_bubbling_event(&mut self, parent: ElementRef, name: &str, uievent: Event<dyn Any>) {
|
||||
// If the event bubbles, we traverse through the tree until we find the target element.
|
||||
// Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
|
||||
let mut parent = Some(parent);
|
||||
while let Some(path) = parent {
|
||||
let mut listeners = vec![];
|
||||
|
||||
|
@ -815,13 +811,13 @@ impl VirtualDom {
|
|||
let target_path = path.path;
|
||||
|
||||
// Accumulate listeners into the listener list bottom to top
|
||||
for (idx, attrs) in el_ref.dynamic_attrs.iter().enumerate() {
|
||||
let this_path = node_template.attr_paths[idx];
|
||||
for (idx, this_path) in node_template.breadth_first_attribute_paths() {
|
||||
let attrs = &*el_ref.dynamic_attrs[idx];
|
||||
|
||||
for attr in attrs.iter() {
|
||||
// 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)
|
||||
&& target_path.is_decendant(this_path)
|
||||
{
|
||||
listeners.push(&attr.value);
|
||||
|
||||
|
@ -842,6 +838,7 @@ impl VirtualDom {
|
|||
"Calling {} listeners",
|
||||
listeners.len()
|
||||
);
|
||||
tracing::info!("Listeners: {:?}", listeners);
|
||||
for listener in listeners.into_iter().rev() {
|
||||
if let AttributeValue::Listener(listener) = listener {
|
||||
self.runtime.rendering.set(false);
|
||||
|
@ -870,10 +867,10 @@ impl VirtualDom {
|
|||
let node_template = el_ref.template.get();
|
||||
let target_path = node.path;
|
||||
|
||||
for (idx, attr) in el_ref.dynamic_attrs.iter().enumerate() {
|
||||
let this_path = node_template.attr_paths[idx];
|
||||
for (idx, this_path) in node_template.breadth_first_attribute_paths() {
|
||||
let attrs = &*el_ref.dynamic_attrs[idx];
|
||||
|
||||
for attr in attr.iter() {
|
||||
for attr in attrs.iter() {
|
||||
// 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 {
|
||||
|
|
Loading…
Reference in a new issue