From 4e582d0374509834a4b262957dca99da7813aff8 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 12 Apr 2023 11:35:57 -0500 Subject: [PATCH] custom element test passing --- packages/native-core-macro/src/lib.rs | 10 ++-- packages/native-core/examples/custom_attr.rs | 2 +- packages/native-core/examples/font_size.rs | 2 +- packages/native-core/examples/simple.rs | 2 +- .../native-core/examples/simple_dioxus.rs | 2 +- packages/native-core/src/passes.rs | 35 +++++++---- packages/native-core/src/real_dom.rs | 52 +++++++++++------ packages/native-core/src/tree.rs | 47 ++++++++++++--- .../tests/called_minimally_on_build.rs | 2 +- packages/native-core/tests/custom_element.rs | 58 ++++++++++++++----- 10 files changed, 155 insertions(+), 57 deletions(-) diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index e75a1485d..ff0e4629d 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -224,14 +224,14 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { let get_parent_view = { if parent_dependencies.is_empty() { quote! { - let raw_parent = tree.parent_id(id).map(|_| ()); + let raw_parent = tree.parent_id_advanced(id, Self::TRAVERSE_SHADOW_DOM).map(|_| ()); } } else { let temps = (0..parent_dependencies.len()) .map(|i| format_ident!("__temp{}", i)) .collect::>(); quote! { - let raw_parent = tree.parent_id(id).and_then(|parent_id| { + let raw_parent = tree.parent_id_advanced(id, Self::TRAVERSE_SHADOW_DOM).and_then(|parent_id| { let raw_parent: Option<(#(*const #parent_dependencies,)*)> = (#(&#parent_view,)*).get(parent_id).ok().map(|c| { let (#(#temps,)*) = c; (#(#temps as *const _,)*) @@ -261,14 +261,14 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { let get_child_view = { if child_dependencies.is_empty() { quote! { - let raw_children: Vec<_> = tree.children_ids(id).into_iter().map(|_| ()).collect(); + let raw_children: Vec<_> = tree.children_ids_advanced(id, Self::TRAVERSE_SHADOW_DOM).into_iter().map(|_| ()).collect(); } } else { let temps = (0..child_dependencies.len()) .map(|i| format_ident!("__temp{}", i)) .collect::>(); quote! { - let raw_children: Vec<_> = tree.children_ids(id).into_iter().filter_map(|id| { + let raw_children: Vec<_> = tree.children_ids_advanced(id, Self::TRAVERSE_SHADOW_DOM).into_iter().filter_map(|id| { let raw_children: Option<(#(*const #child_dependencies,)*)> = (#(&#child_view,)*).get(id).ok().map(|c| { let (#(#temps,)*) = c; (#(#temps as *const _,)*) @@ -336,7 +336,7 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { let (#(#split_views,)*) = data; let tree = run_view.tree.clone(); let node_types = run_view.node_type.clone(); - dioxus_native_core::prelude::run_pass(type_id, dependants.clone(), pass_direction, run_view, Self::TRAVERSE_SHADOW_DOM, |id, context| { + dioxus_native_core::prelude::run_pass(type_id, dependants.clone(), pass_direction, run_view, |id, context| { let node_data: &NodeType<_> = node_types.get(id).unwrap_or_else(|err| panic!("Failed to get node type {:?}", err)); // get all of the states from the tree view // Safety: No node has itself as a parent or child. diff --git a/packages/native-core/examples/custom_attr.rs b/packages/native-core/examples/custom_attr.rs index 5a1fcfb8c..ee78a69f4 100644 --- a/packages/native-core/examples/custom_attr.rs +++ b/packages/native-core/examples/custom_attr.rs @@ -215,7 +215,7 @@ fn main() -> Result<(), Box> { let _to_rerender = rdom.update_state(ctx); // render... - rdom.traverse_depth_first(|node| { + rdom.traverse_depth_first(true, |node| { let indent = " ".repeat(node.height() as usize); let color = *node.get::().unwrap(); let size = *node.get::().unwrap(); diff --git a/packages/native-core/examples/font_size.rs b/packages/native-core/examples/font_size.rs index 937db0c17..4ff6b876d 100644 --- a/packages/native-core/examples/font_size.rs +++ b/packages/native-core/examples/font_size.rs @@ -160,7 +160,7 @@ fn main() -> Result<(), Box> { let _to_rerender = rdom.update_state(ctx); // render... - rdom.traverse_depth_first(|node| { + rdom.traverse_depth_first(true, |node| { let indent = " ".repeat(node.height() as usize); let font_size = *node.get::().unwrap(); let size = *node.get::().unwrap(); diff --git a/packages/native-core/examples/simple.rs b/packages/native-core/examples/simple.rs index 469147c35..b44f05e63 100644 --- a/packages/native-core/examples/simple.rs +++ b/packages/native-core/examples/simple.rs @@ -206,7 +206,7 @@ fn main() -> Result<(), Box> { let _to_rerender = rdom.update_state(ctx); // render... - rdom.traverse_depth_first(|node| { + rdom.traverse_depth_first(true, |node| { let indent = " ".repeat(node.height() as usize); let color = *node.get::().unwrap(); let size = *node.get::().unwrap(); diff --git a/packages/native-core/examples/simple_dioxus.rs b/packages/native-core/examples/simple_dioxus.rs index fa1b202dd..c5aebd5d3 100644 --- a/packages/native-core/examples/simple_dioxus.rs +++ b/packages/native-core/examples/simple_dioxus.rs @@ -234,7 +234,7 @@ fn main() -> Result<(), Box> { let _to_rerender = rdom.update_state(ctx); // render... - rdom.traverse_depth_first(|node| { + rdom.traverse_depth_first(true, |node| { let indent = " ".repeat(node.height() as usize); let color = *node.get::().unwrap(); let size = *node.get::().unwrap(); diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index 383cdadd2..a76dbc61c 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -153,6 +153,7 @@ pub trait State: Any + Send + Sync { dependants: Default::default(), mask: node_mask, pass_direction: pass_direction::(), + enter_shadow_dom: Self::TRAVERSE_SHADOW_DOM, workload: Self::workload_system, phantom: PhantomData, } @@ -193,7 +194,6 @@ pub fn run_pass( dependants: Arc, pass_direction: PassDirection, view: RunPassView, - enter_shadow_dom: bool, mut update_node: impl FnMut(NodeId, &SendAnyMap) -> bool, ) { let RunPassView { @@ -232,28 +232,42 @@ pub fn run_pass( } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct Dependant { + pub(crate) type_id: TypeId, + pub(crate) enter_shadow_dom: bool, +} + /// The states that depend on this state #[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct Dependants { /// The states in the parent direction that should be invalidated when this state is invalidated - pub parent: Vec, + pub(crate) parent: Vec, /// The states in the child direction that should be invalidated when this state is invalidated - pub child: Vec, + pub(crate) child: Vec, /// The states in the node direction that should be invalidated when this state is invalidated - pub node: Vec, + pub(crate) node: Vec, } impl Dependants { fn mark_dirty(&self, dirty: &DirtyNodeStates, id: NodeId, tree: &impl TreeRef, height: u16) { - for dependant in &self.child { - for id in tree.children_ids(id) { - dirty.insert(*dependant, id, height + 1); + for &Dependant { + type_id, + enter_shadow_dom, + } in &self.child + { + for id in tree.children_ids_advanced(id, enter_shadow_dom) { + dirty.insert(type_id, id, height + 1); } } - for dependant in &self.parent { - if let Some(id) = tree.parent_id(id) { - dirty.insert(*dependant, id, height - 1); + for &Dependant { + type_id, + enter_shadow_dom, + } in &self.parent + { + if let Some(id) = tree.parent_id_advanced(id, enter_shadow_dom) { + dirty.insert(type_id, id, height - 1); } } @@ -273,6 +287,7 @@ pub struct TypeErasedState { pub(crate) mask: NodeMask, pub(crate) workload: fn(TypeId, Arc, PassDirection) -> WorkloadSystem, pub(crate) pass_direction: PassDirection, + pub(crate) enter_shadow_dom: bool, phantom: PhantomData, } diff --git a/packages/native-core/src/real_dom.rs b/packages/native-core/src/real_dom.rs index 8f5967789..e885963d1 100644 --- a/packages/native-core/src/real_dom.rs +++ b/packages/native-core/src/real_dom.rs @@ -16,7 +16,7 @@ use crate::node::{ }; use crate::node_ref::{NodeMask, NodeMaskBuilder}; use crate::node_watcher::{AttributeWatcher, NodeWatcher}; -use crate::passes::{DirtyNodeStates, PassDirection, TypeErasedState}; +use crate::passes::{Dependant, DirtyNodeStates, PassDirection, TypeErasedState}; use crate::prelude::AttributeMaskBuilder; use crate::tree::{TreeMut, TreeMutView, TreeRef, TreeRefView}; use crate::NodeId; @@ -128,19 +128,25 @@ impl RealDom { let (current, before) = before.split_last_mut().unwrap(); for state in before.iter_mut().chain(after.iter_mut()) { let dependants = Arc::get_mut(&mut state.dependants).unwrap(); + + let current_dependant = Dependant { + type_id: current.this_type_id, + enter_shadow_dom: current.enter_shadow_dom, + }; + // If this node depends on the other state as a parent, then the other state should update its children of the current type when it is invalidated if current .parent_dependancies_ids .contains(&state.this_type_id) - && !dependants.child.contains(¤t.this_type_id) + && !dependants.child.contains(¤t_dependant) { - dependants.child.push(current.this_type_id); + dependants.child.push(current_dependant); } // If this node depends on the other state as a child, then the other state should update its parent of the current type when it is invalidated if current.child_dependancies_ids.contains(&state.this_type_id) - && !dependants.parent.contains(¤t.this_type_id) + && !dependants.parent.contains(¤t_dependant) { - dependants.parent.push(current.this_type_id); + dependants.parent.push(current_dependant); } // If this node depends on the other state as a sibling, then the other state should update its siblings of the current type when it is invalidated if current.node_dependancies_ids.contains(&state.this_type_id) @@ -151,15 +157,19 @@ impl RealDom { } // If the current state depends on itself, then it should update itself when it is invalidated let dependants = Arc::get_mut(&mut current.dependants).unwrap(); + let current_dependant = Dependant { + type_id: current.this_type_id, + enter_shadow_dom: current.enter_shadow_dom, + }; match current.pass_direction { PassDirection::ChildToParent => { - if !dependants.parent.contains(¤t.this_type_id) { - dependants.parent.push(current.this_type_id); + if !dependants.parent.contains(¤t_dependant) { + dependants.parent.push(current_dependant); } } PassDirection::ParentToChild => { - if !dependants.child.contains(¤t.this_type_id) { - dependants.child.push(current.this_type_id); + if !dependants.child.contains(¤t_dependant) { + dependants.child.push(current_dependant); } } _ => {} @@ -371,27 +381,27 @@ impl RealDom { } /// Traverses the dom in a depth first manner, calling the provided function on each node. - pub fn traverse_depth_first(&self, mut f: impl FnMut(NodeRef)) { + pub fn traverse_depth_first(&self, enter_shadow_doms: bool, mut f: impl FnMut(NodeRef)) { let mut stack = vec![self.root_id()]; let tree = self.tree_ref(); while let Some(id) = stack.pop() { if let Some(node) = self.get(id) { f(node); - let children = tree.children_ids(id); + let children = tree.children_ids_advanced(id, enter_shadow_doms); stack.extend(children.iter().copied().rev()); } } } /// Traverses the dom in a breadth first manner, calling the provided function on each node. - pub fn traverse_breadth_first(&self, mut f: impl FnMut(NodeRef)) { + pub fn traverse_breadth_first(&self, enter_shadow_doms: bool, mut f: impl FnMut(NodeRef)) { let mut queue = VecDeque::new(); queue.push_back(self.root_id()); let tree = self.tree_ref(); while let Some(id) = queue.pop_front() { if let Some(node) = self.get(id) { f(node); - let children = tree.children_ids(id); + let children = tree.children_ids_advanced(id, enter_shadow_doms); for id in children { queue.push_back(id); } @@ -400,11 +410,15 @@ impl RealDom { } /// Traverses the dom in a depth first manner mutably, calling the provided function on each node. - pub fn traverse_depth_first_mut(&mut self, mut f: impl FnMut(NodeMut)) { + pub fn traverse_depth_first_mut( + &mut self, + enter_shadow_doms: bool, + mut f: impl FnMut(NodeMut), + ) { let mut stack = vec![self.root_id()]; while let Some(id) = stack.pop() { let tree = self.tree_ref(); - let mut children = tree.children_ids(id); + let mut children = tree.children_ids_advanced(id, enter_shadow_doms); drop(tree); children.reverse(); if let Some(node) = self.get_mut(id) { @@ -416,12 +430,16 @@ impl RealDom { } /// Traverses the dom in a breadth first manner mutably, calling the provided function on each node. - pub fn traverse_breadth_first_mut(&mut self, mut f: impl FnMut(NodeMut)) { + pub fn traverse_breadth_first_mut( + &mut self, + enter_shadow_doms: bool, + mut f: impl FnMut(NodeMut), + ) { let mut queue = VecDeque::new(); queue.push_back(self.root_id()); while let Some(id) = queue.pop_front() { let tree = self.tree_ref(); - let children = tree.children_ids(id); + let children = tree.children_ids_advanced(id, enter_shadow_doms); drop(tree); if let Some(node) = self.get_mut(id) { f(node); diff --git a/packages/native-core/src/tree.rs b/packages/native-core/src/tree.rs index 1f46216b2..1f26dc6a7 100644 --- a/packages/native-core/src/tree.rs +++ b/packages/native-core/src/tree.rs @@ -11,8 +11,6 @@ pub struct ShadowTree { pub shadow_roots: Vec, /// The node that children of the super tree should be inserted under. pub slot: Option, - /// The node in the super tree that the shadow_tree is attached to. - pub super_tree_root: NodeId, } /// A node in a tree. @@ -35,8 +33,43 @@ pub type TreeMutView<'a> = (EntitiesViewMut<'a>, ViewMut<'a, Node>); /// A immutable view of a tree. pub trait TreeRef { + /// Get the id of the parent of the current node, if enter_shadow_dom is true and the current node is a shadow root, the node the shadow root is attached to will be returned + #[inline] + fn parent_id_advanced(&self, id: NodeId, enter_shadow_dom: bool) -> Option { + // If this node is the root of a shadow_tree, return the node the shadow_tree is attached + let light_tree_root = self.light_tree_root(id); + match (light_tree_root, enter_shadow_dom) { + (Some(id), true) => Some(id), + _ => { + let parent_id = self.parent_id(id); + if enter_shadow_dom { + // If this node is attached via a slot, return the slot as the parent instead of the light tree parent + parent_id.map(|id| { + self.shadow_tree(id) + .and_then(|tree| tree.slot) + .unwrap_or(id) + }) + } else { + parent_id + } + } + } + } /// The parent id of the node. fn parent_id(&self, id: NodeId) -> Option; + /// Get the ids of the children of the current node, if enter_shadow_dom is true and the current node is a shadow slot, the ids of the nodes under the node the shadow slot is attached to will be returned + #[inline] + fn children_ids_advanced(&self, id: NodeId, enter_shadow_dom: bool) -> Vec { + let shadow_tree = self.shadow_tree(id); + let slot_of_light_tree = self.slot_for_light_tree(id); + match (shadow_tree, slot_of_light_tree, enter_shadow_dom) { + // If this node is a shadow root, return the shadow roots + (Some(tree), _, true) => tree.shadow_roots.clone(), + // If this node is a slot, return the children of the node the slot is attached to + (None, Some(id), true) => self.children_ids(id), + _ => self.children_ids(id), + } + } /// The children ids of the node. fn children_ids(&self, id: NodeId) -> Vec; /// The shadow tree tree under the node. @@ -229,7 +262,6 @@ impl<'a> TreeMut for TreeMutView<'a> { let light_root_height; { let shadow_tree = ShadowTree { - super_tree_root: id, shadow_roots: shadow_roots.clone(), slot, }; @@ -251,6 +283,7 @@ impl<'a> TreeMut for TreeMutView<'a> { // Now that we have created the shadow_tree, we need to update the height of the shadow_tree roots for root in shadow_roots { + (&mut self.1).get(root).unwrap().light_tree_root = Some(id); set_height(self, root, light_root_height + 1); } } @@ -319,7 +352,7 @@ fn set_height(tree: &mut TreeMutView<'_>, node: NodeId, height: u16) { if let Some(shadow_tree) = shadow_tree { // Set the height of the shadow_tree roots for &shadow_root in &shadow_tree.shadow_roots { - set_height(tree, shadow_root, height); + set_height(tree, shadow_root, height + 1); } } else { // Otherwise, we just set the height of the children to be one more than the height of the parent @@ -362,17 +395,17 @@ impl<'a> TreeRef for TreeMutView<'a> { fn shadow_tree(&self, id: NodeId) -> Option<&ShadowTree> { let node_data = &self.1; - node_data.get(id).unwrap().child_subtree.as_ref() + node_data.get(id).ok()?.child_subtree.as_ref() } fn slot_for_light_tree(&self, id: NodeId) -> Option { let node_data = &self.1; - node_data.get(id).unwrap().slot_for_light_tree + node_data.get(id).ok()?.slot_for_light_tree } fn light_tree_root(&self, id: NodeId) -> Option { let node_data = &self.1; - node_data.get(id).unwrap().light_tree_root + node_data.get(id).ok()?.light_tree_root } } diff --git a/packages/native-core/tests/called_minimally_on_build.rs b/packages/native-core/tests/called_minimally_on_build.rs index 394cf464a..f4e8cf6e1 100644 --- a/packages/native-core/tests/called_minimally_on_build.rs +++ b/packages/native-core/tests/called_minimally_on_build.rs @@ -146,7 +146,7 @@ macro_rules! test_state{ dioxus_state.apply_mutations(&mut dom, mutations); dom.update_state(SendAnyMap::new()); - dom.traverse_depth_first(|n| { + dom.traverse_depth_first(false, |n| { $( assert_eq!(n.get::<$state>().unwrap().0, 1); )* diff --git a/packages/native-core/tests/custom_element.rs b/packages/native-core/tests/custom_element.rs index 3aa1b7462..b7c6e7c97 100644 --- a/packages/native-core/tests/custom_element.rs +++ b/packages/native-core/tests/custom_element.rs @@ -23,14 +23,26 @@ impl State for ColorState { fn update<'a>( &mut self, - _: NodeView, + view: NodeView, _: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, _: Vec<::ElementBorrowed<'a>>, _: &SendAnyMap, ) -> bool { - if let Some((parent,)) = parent { - self.color = parent.color; + if let Some(size) = view + .attributes() + .into_iter() + .flatten() + .find(|attr| attr.attribute.name == "color") + { + self.color = size + .value + .as_float() + .or_else(|| size.value.as_int().map(|i| i as f64)) + .or_else(|| size.value.as_text().and_then(|i| i.parse().ok())) + .unwrap_or(0.0) as usize; + } else if let Some((parent,)) = parent { + *self = *parent; } true } @@ -74,11 +86,6 @@ impl State for LayoutState { _: Vec<::ElementBorrowed<'a>>, _: &SendAnyMap, ) -> bool { - println!( - "Updating layout state @{:?} {:?}", - parent.as_ref().map(|(p,)| p.size), - view.node_id() - ); if let Some(size) = view .attributes() .into_iter() @@ -93,7 +100,7 @@ impl State for LayoutState { .unwrap_or(0.0) as usize; } else if let Some((parent,)) = parent { if parent.size > 0 { - self.size -= 1; + self.size = parent.size - 1; } } true @@ -130,7 +137,7 @@ mod dioxus_elements { $(#[$attr])* pub struct $name; - #[allow(non_upper_case_globals)] + #[allow(non_upper_case_globals, unused)] impl $name { pub const TAG_NAME: &'static str = stringify!($name); pub const NAME_SPACE: Option<&'static str> = None; @@ -152,10 +159,14 @@ mod dioxus_elements { builder_constructors! { customelementslot { size: attr, + color: attr, }; customelementnoslot { + size: attr, + color: attr, }; testing132 { + color: attr, }; } } @@ -253,6 +264,7 @@ fn custom_elements_work() { cx.render(rsx! { customelementslot { size: "{count}", + color: "1", customelementslot { testing132 {} } @@ -277,7 +289,7 @@ fn custom_elements_work() { let ctx = SendAnyMap::new(); rdom.update_state(ctx); - for _ in 0..10 { + for i in 0..10usize { dom.wait_for_work().await; let mutations = dom.render_immediate(); @@ -287,13 +299,33 @@ fn custom_elements_work() { rdom.update_state(ctx); // render... - rdom.traverse_depth_first(|node| { + rdom.traverse_depth_first(true, |node| { let node_type = &*node.node_type(); - let indent = " ".repeat(node.height() as usize); + let height = node.height() as usize; + let indent = " ".repeat(height); let color = *node.get::().unwrap(); let size = *node.get::().unwrap(); let id = node.id(); println!("{indent}{id:?} {color:?} {size:?} {node_type:?}"); + match node_type { + NodeType::Element(el) => { + match el.tag.as_str() { + // the color should bubble up from customelementslot + "testing132" | "customelementslot" => { + assert_eq!(color.color, 1); + } + // the color of the light dom should not effect the color of the shadow dom, so the color of divs in the shadow dom should be 0 + "div" => { + assert_eq!(color.color, 0); + } + _ => {} + } + if el.tag != "Root" { + assert_eq!(size.size, (i + 2).saturating_sub(height)); + } + } + _ => {} + } }); } });