apply_patches.rs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. use crate::dom_updater::ActiveClosures;
  2. use crate::patch::Patch;
  3. use std::cmp::min;
  4. use std::collections::HashMap;
  5. use std::collections::HashSet;
  6. use wasm_bindgen::JsCast;
  7. use wasm_bindgen::JsValue;
  8. use web_sys::{Element, Node, Text};
  9. /// Apply all of the patches to our old root node in order to create the new root node
  10. /// that we desire.
  11. /// This is usually used after diffing two virtual nodes.
  12. pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<ActiveClosures, JsValue> {
  13. let root_node: Node = root_node.into();
  14. let mut cur_node_idx = 0;
  15. let mut nodes_to_find = HashSet::new();
  16. for patch in patches {
  17. nodes_to_find.insert(patch.node_idx());
  18. }
  19. let mut element_nodes_to_patch = HashMap::new();
  20. let mut text_nodes_to_patch = HashMap::new();
  21. // Closures that were added to the DOM during this patch operation.
  22. let mut active_closures = HashMap::new();
  23. find_nodes(
  24. root_node,
  25. &mut cur_node_idx,
  26. &mut nodes_to_find,
  27. &mut element_nodes_to_patch,
  28. &mut text_nodes_to_patch,
  29. );
  30. for patch in patches {
  31. let patch_node_idx = patch.node_idx();
  32. if let Some(element) = element_nodes_to_patch.get(&patch_node_idx) {
  33. let new_closures = apply_element_patch(&element, &patch)?;
  34. active_closures.extend(new_closures);
  35. continue;
  36. }
  37. if let Some(text_node) = text_nodes_to_patch.get(&patch_node_idx) {
  38. apply_text_patch(&text_node, &patch)?;
  39. continue;
  40. }
  41. unreachable!("Getting here means we didn't find the element or next node that we were supposed to patch.")
  42. }
  43. Ok(active_closures)
  44. }
  45. fn find_nodes(
  46. root_node: Node,
  47. cur_node_idx: &mut usize,
  48. nodes_to_find: &mut HashSet<usize>,
  49. element_nodes_to_patch: &mut HashMap<usize, Element>,
  50. text_nodes_to_patch: &mut HashMap<usize, Text>,
  51. ) {
  52. if nodes_to_find.len() == 0 {
  53. return;
  54. }
  55. // We use child_nodes() instead of children() because children() ignores text nodes
  56. let children = root_node.child_nodes();
  57. let child_node_count = children.length();
  58. // If the root node matches, mark it for patching
  59. if nodes_to_find.get(&cur_node_idx).is_some() {
  60. match root_node.node_type() {
  61. Node::ELEMENT_NODE => {
  62. element_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
  63. }
  64. Node::TEXT_NODE => {
  65. text_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
  66. }
  67. other => unimplemented!("Unsupported root node type: {}", other),
  68. }
  69. nodes_to_find.remove(&cur_node_idx);
  70. }
  71. *cur_node_idx += 1;
  72. for i in 0..child_node_count {
  73. let node = children.item(i).unwrap();
  74. match node.node_type() {
  75. Node::ELEMENT_NODE => {
  76. find_nodes(
  77. node,
  78. cur_node_idx,
  79. nodes_to_find,
  80. element_nodes_to_patch,
  81. text_nodes_to_patch,
  82. );
  83. }
  84. Node::TEXT_NODE => {
  85. if nodes_to_find.get(&cur_node_idx).is_some() {
  86. text_nodes_to_patch.insert(*cur_node_idx, node.unchecked_into());
  87. }
  88. *cur_node_idx += 1;
  89. }
  90. Node::COMMENT_NODE => {
  91. // At this time we do not support user entered comment nodes, so if we see a comment
  92. // then it was a delimiter created by virtual-dom-rs in order to ensure that two
  93. // neighboring text nodes did not get merged into one by the browser. So we skip
  94. // over this virtual-dom-rs generated comment node.
  95. }
  96. _other => {
  97. // Ignoring unsupported child node type
  98. // TODO: What do we do with this situation? Log a warning?
  99. }
  100. }
  101. }
  102. }
  103. fn apply_element_patch(node: &Element, patch: &Patch) -> Result<ActiveClosures, JsValue> {
  104. let active_closures = HashMap::new();
  105. match patch {
  106. Patch::AddAttributes(_node_idx, attributes) => {
  107. for (attrib_name, attrib_val) in attributes.iter() {
  108. node.set_attribute(attrib_name, attrib_val)?;
  109. }
  110. Ok(active_closures)
  111. }
  112. Patch::RemoveAttributes(_node_idx, attributes) => {
  113. for attrib_name in attributes.iter() {
  114. node.remove_attribute(attrib_name)?;
  115. }
  116. Ok(active_closures)
  117. }
  118. Patch::Replace(_node_idx, new_node) => {
  119. let created_node = new_node.create_dom_node();
  120. node.replace_with_with_node_1(&created_node.node)?;
  121. Ok(created_node.closures)
  122. }
  123. Patch::TruncateChildren(_node_idx, num_children_remaining) => {
  124. let children = node.child_nodes();
  125. let mut child_count = children.length();
  126. // We skip over any separators that we placed between two text nodes
  127. // -> `<!--ptns-->`
  128. // and trim all children that come after our new desired `num_children_remaining`
  129. let mut non_separator_children_found = 0;
  130. for index in 0 as u32..child_count {
  131. let child = children
  132. .get(min(index, child_count - 1))
  133. .expect("Potential child to truncate");
  134. // If this is a comment node then we know that it is a `<!--ptns-->`
  135. // text node separator that was created in virtual_node/mod.rs.
  136. if child.node_type() == Node::COMMENT_NODE {
  137. continue;
  138. }
  139. non_separator_children_found += 1;
  140. if non_separator_children_found <= *num_children_remaining as u32 {
  141. continue;
  142. }
  143. node.remove_child(&child).expect("Truncated children");
  144. child_count -= 1;
  145. }
  146. Ok(active_closures)
  147. }
  148. Patch::AppendChildren(_node_idx, new_nodes) => {
  149. let parent = &node;
  150. let mut active_closures = HashMap::new();
  151. for new_node in new_nodes {
  152. let created_node = new_node.create_dom_node();
  153. parent.append_child(&created_node.node)?;
  154. active_closures.extend(created_node.closures);
  155. }
  156. Ok(active_closures)
  157. }
  158. Patch::ChangeText(_node_idx, _new_node) => {
  159. unreachable!("Elements should not receive ChangeText patches.")
  160. }
  161. }
  162. }
  163. fn apply_text_patch(node: &Text, patch: &Patch) -> Result<(), JsValue> {
  164. match patch {
  165. Patch::ChangeText(_node_idx, new_node) => {
  166. node.set_node_value(Some(&new_node.text));
  167. }
  168. Patch::Replace(_node_idx, new_node) => {
  169. node.replace_with_with_node_1(&new_node.create_dom_node().node)?;
  170. }
  171. other => unreachable!(
  172. "Text nodes should only receive ChangeText or Replace patches, not {:?}.",
  173. other,
  174. ),
  175. };
  176. Ok(())
  177. }