use crate::dom::WebsysDom; use dioxus_core::{ AttributeValue, DynamicNode, ElementId, ScopeState, TemplateNode, VNode, VirtualDom, }; use dioxus_html::event_bubbles; use wasm_bindgen::JsCast; use web_sys::{Comment, Node}; #[derive(Debug, Copy, Clone)] pub enum RehydrationError { NodeTypeMismatch, NodeNotFound, VNodeNotInitialized, } use RehydrationError::*; fn set_node(hydrated: &mut Vec, id: ElementId, node: Node) { let idx = id.0; if idx >= hydrated.len() { hydrated.resize(idx + 1, false); } if !hydrated[idx] { dioxus_interpreter_js::set_node(idx as u32, node); hydrated[idx] = true; } } impl WebsysDom { // we're streaming in patches, but the nodes already exist // so we're just going to write the correct IDs to the node and load them in pub fn rehydrate(&mut self, dom: &VirtualDom) -> Result<(), RehydrationError> { let mut root = self .root .clone() .dyn_into::() .map_err(|_| NodeTypeMismatch)? .first_child() .ok_or(NodeNotFound); let root_scope = dom.base_scope(); let mut hydrated = vec![true]; let mut last_node_was_static_text = false; // Recursively rehydrate the dom from the VirtualDom self.rehydrate_scope( root_scope, &mut root, &mut hydrated, dom, &mut last_node_was_static_text, )?; self.interpreter.flush(); Ok(()) } fn rehydrate_scope( &mut self, scope: &ScopeState, current_child: &mut Result, hydrated: &mut Vec, dom: &VirtualDom, last_node_was_static_text: &mut bool, ) -> Result<(), RehydrationError> { let vnode = match scope.root_node() { dioxus_core::RenderReturn::Ready(ready) => ready, _ => return Err(VNodeNotInitialized), }; self.rehydrate_vnode( current_child, hydrated, dom, vnode, last_node_was_static_text, ) } fn rehydrate_vnode( &mut self, current_child: &mut Result, hydrated: &mut Vec, dom: &VirtualDom, vnode: &VNode, last_node_was_static_text: &mut bool, ) -> Result<(), RehydrationError> { for (i, root) in vnode.template.get().roots.iter().enumerate() { // make sure we set the root node ids even if the node is not dynamic set_node( hydrated, *vnode.root_ids.borrow().get(i).ok_or(VNodeNotInitialized)?, current_child.clone()?, ); self.rehydrate_template_node( current_child, hydrated, dom, vnode, root, last_node_was_static_text, )?; } Ok(()) } fn rehydrate_template_node( &mut self, current_child: &mut Result, hydrated: &mut Vec, dom: &VirtualDom, vnode: &VNode, node: &TemplateNode, last_node_was_static_text: &mut bool, ) -> Result<(), RehydrationError> { tracing::trace!("rehydrate template node: {:?}", node); if let Ok(current_child) = current_child { if tracing::event_enabled!(tracing::Level::TRACE) { web_sys::console::log_1(¤t_child.clone().into()); } } match node { TemplateNode::Element { children, attrs, .. } => { let mut mounted_id = None; for attr in *attrs { if let dioxus_core::TemplateAttribute::Dynamic { id } = attr { let attribute = &vnode.dynamic_attrs[*id]; let value = &attribute.value; let id = attribute.mounted_element(); mounted_id = Some(id); let name = attribute.name; if let AttributeValue::Listener(_) = value { let event_name = &name[2..]; self.interpreter.new_event_listener( event_name, id.0 as u32, event_bubbles(event_name) as u8, ); } } } if let Some(id) = mounted_id { set_node(hydrated, id, current_child.clone()?); } if !children.is_empty() { let mut children_current_child = current_child .as_mut() .map_err(|e| *e)? .first_child() .ok_or(NodeNotFound)? .dyn_into::() .map_err(|_| NodeTypeMismatch); for child in *children { self.rehydrate_template_node( &mut children_current_child, hydrated, dom, vnode, child, last_node_was_static_text, )?; } } *current_child = current_child .as_mut() .map_err(|e| *e)? .next_sibling() .ok_or(NodeNotFound); *last_node_was_static_text = false; } TemplateNode::Text { .. } => { // if the last node was static text, it got merged with this one if !*last_node_was_static_text { *current_child = current_child .as_mut() .map_err(|e| *e)? .next_sibling() .ok_or(NodeNotFound); } *last_node_was_static_text = true; } TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => { self.rehydrate_dynamic_node( current_child, hydrated, dom, &vnode.dynamic_nodes[*id], last_node_was_static_text, )?; } } Ok(()) } fn rehydrate_dynamic_node( &mut self, current_child: &mut Result, hydrated: &mut Vec, dom: &VirtualDom, dynamic: &DynamicNode, last_node_was_static_text: &mut bool, ) -> Result<(), RehydrationError> { tracing::trace!("rehydrate dynamic node: {:?}", dynamic); if let Ok(current_child) = current_child { if tracing::event_enabled!(tracing::Level::TRACE) { web_sys::console::log_1(¤t_child.clone().into()); } } match dynamic { dioxus_core::DynamicNode::Text(text) => { let id = text.mounted_element(); // skip comment separator before node if cfg!(debug_assertions) { assert!(current_child .as_mut() .map_err(|e| *e)? .has_type::()); } *current_child = current_child .as_mut() .map_err(|e| *e)? .next_sibling() .ok_or(NodeNotFound); set_node( hydrated, id.ok_or(VNodeNotInitialized)?, current_child.clone()?, ); *current_child = current_child .as_mut() .map_err(|e| *e)? .next_sibling() .ok_or(NodeNotFound); // skip comment separator after node if cfg!(debug_assertions) { assert!(current_child .as_mut() .map_err(|e| *e)? .has_type::()); } *current_child = current_child .as_mut() .map_err(|e| *e)? .next_sibling() .ok_or(NodeNotFound); *last_node_was_static_text = false; } dioxus_core::DynamicNode::Placeholder(placeholder) => { set_node( hydrated, placeholder.mounted_element().ok_or(VNodeNotInitialized)?, current_child.clone()?, ); *current_child = current_child .as_mut() .map_err(|e| *e)? .next_sibling() .ok_or(NodeNotFound); *last_node_was_static_text = false; } dioxus_core::DynamicNode::Component(comp) => { let scope = comp.mounted_scope().ok_or(VNodeNotInitialized)?; self.rehydrate_scope( dom.get_scope(scope).unwrap(), current_child, hydrated, dom, last_node_was_static_text, )?; } dioxus_core::DynamicNode::Fragment(fragment) => { for vnode in *fragment { self.rehydrate_vnode( current_child, hydrated, dom, vnode, last_node_was_static_text, )?; } } } Ok(()) } }