1use super::{diff_lis, Attribute, Element, Node, Patch, TreePath};
3use super::{Tag, KEY, REPLACE, SKIP, SKIP_CRITERIA};
4use crate::dom::skip_diff::SkipAttrs;
5use crate::dom::SkipPath;
6use crate::vdom::AttributeValue;
7use crate::vdom::Leaf;
8use std::{cmp, mem};
9
10#[cfg(feature = "use-skipdiff")]
11static USE_SKIP_DIFF: bool = true;
12
13#[cfg(not(feature = "use-skipdiff"))]
14static USE_SKIP_DIFF: bool = false;
15
16pub fn diff<'a, MSG>(old_node: &'a Node<MSG>, new_node: &'a Node<MSG>) -> Vec<Patch<'a, MSG>> {
54 diff_recursive(
55 old_node,
56 new_node,
57 &SkipPath {
58 path: TreePath::root(),
59 skip_diff: None,
60 },
61 )
62}
63
64fn is_any_keyed<MSG>(nodes: &[Node<MSG>]) -> bool {
65 nodes.iter().any(|child| is_keyed_node(child))
66}
67
68fn is_keyed_node<MSG>(node: &Node<MSG>) -> bool {
70 if let Some(attributes) = node.attributes() {
71 attributes.iter().any(|att| att.name == *KEY)
72 } else {
73 false
74 }
75}
76
77fn should_replace<'a, MSG>(old_node: &'a Node<MSG>, new_node: &'a Node<MSG>) -> bool {
78 if mem::discriminant(old_node) != mem::discriminant(new_node) {
80 return true;
81 }
82 let replace = |_old_node: &'a Node<MSG>, new_node: &'a Node<MSG>| {
83 let explicit_replace_attr = new_node
84 .first_value(REPLACE)
85 .and_then(|v| v.as_bool())
86 .unwrap_or(false);
87
88 explicit_replace_attr
89 };
90 if replace(old_node, new_node) {
92 return true;
93 }
94
95 if let (Some(old_key), Some(new_key)) =
97 (old_node.attribute_value(KEY), new_node.attribute_value(KEY))
98 {
99 if old_key != new_key {
100 return true;
101 }
102 }
103 if let (Node::Element(old_element), Node::Element(new_element)) = (old_node, new_node) {
105 if old_element.tag != new_element.tag {
107 return true;
108 }
109 }
110 false
111}
112
113pub fn diff_recursive<'a, MSG>(
115 old_node: &'a Node<MSG>,
116 new_node: &'a Node<MSG>,
117 path: &SkipPath,
118) -> Vec<Patch<'a, MSG>> {
119 if let Some(skip_diff) = path.skip_diff.as_ref() {
120 if USE_SKIP_DIFF && skip_diff.shall_skip_node() {
121 return vec![];
122 }
123 }
124
125 let skip = |old_node: &'a Node<MSG>, new_node: &'a Node<MSG>| {
126 let new_skip_criteria = new_node.attribute_value(SKIP_CRITERIA);
127 let old_skip_criteria = old_node.attribute_value(SKIP_CRITERIA);
128 match (new_skip_criteria, old_skip_criteria) {
130 (Some(new), Some(old)) => new == old,
131 _ => new_node
132 .first_value(SKIP)
133 .and_then(|v| v.as_bool())
134 .unwrap_or(false),
135 }
136 };
137 if skip(old_node, new_node) {
139 return vec![];
140 }
141
142 if should_replace(old_node, new_node) {
144 return vec![Patch::replace_node(
145 old_node.tag(),
146 path.path.clone(),
147 vec![new_node],
148 )];
149 }
150
151 let mut patches = vec![];
152
153 match (old_node, new_node) {
157 (Node::Leaf(old_leaf), Node::Leaf(new_leaf)) => {
158 match (old_leaf, new_leaf) {
159 (Leaf::Text(_), Leaf::Text(_))
160 | (Leaf::Symbol(_), Leaf::Symbol(_))
161 | (Leaf::Comment(_), Leaf::Comment(_))
162 | (Leaf::DocType(_), Leaf::DocType(_)) => {
163 if old_leaf != new_leaf {
164 let patch = Patch::replace_node(None, path.path.clone(), vec![new_node]);
165 patches.push(patch);
166 }
167 }
168 (Leaf::Fragment(old_nodes), Leaf::Fragment(new_nodes)) => {
169 let patch = diff_nodes(None, old_nodes, new_nodes, &path.backtrack());
172 patches.extend(patch);
173 }
174 (Leaf::NodeList(_old_elements), Leaf::NodeList(_new_elements)) => {
175 panic!("Node list must have already unrolled when creating an element");
176 }
177 (Leaf::StatelessComponent(old_comp), Leaf::StatelessComponent(new_comp)) => {
178 let new_path = SkipPath {
179 path: path.path.clone(),
180 skip_diff: old_comp.view.skip_diff(),
181 };
182
183 let old_real_view = old_comp.view.unwrap_template_ref();
184 let new_real_view = new_comp.view.unwrap_template_ref();
185
186 assert!(
187 !old_real_view.is_template(),
188 "old comp view should not be a template"
189 );
190 assert!(
191 !new_real_view.is_template(),
192 "new comp view should not be a template"
193 );
194 let patch = diff_recursive(old_real_view, new_real_view, &new_path);
195 patches.extend(patch);
196 }
197 (Leaf::StatefulComponent(old_comp), Leaf::StatefulComponent(new_comp)) => {
198 let attr_patches = create_attribute_patches(
199 &"component",
200 &old_comp.attrs,
201 &new_comp.attrs,
202 path,
203 );
204 if !attr_patches.is_empty() {
205 log::info!("stateful component attr_patches: {attr_patches:#?}");
206 }
207 patches.extend(attr_patches);
208 let patch = diff_nodes(None, &old_comp.children, &new_comp.children, path);
209 if !patch.is_empty() {
210 log::info!("stateful component patch: {patch:#?}");
211 }
212 patches.extend(patch);
213 }
214 (Leaf::TemplatedView(_old_view), _) => {
215 unreachable!("templated view should not be diffed..")
216 }
217 (_, Leaf::TemplatedView(_new_view)) => {
218 unreachable!("templated view should not be diffed..")
219 }
220 _ => {
221 let patch = Patch::replace_node(None, path.path.clone(), vec![new_node]);
222 patches.push(patch);
223 }
224 }
225 }
226 (Node::Element(old_element), Node::Element(new_element)) => {
228 let skip_attributes = if let Some(skip_diff) = path.skip_diff.as_ref() {
229 USE_SKIP_DIFF && skip_diff.shall_skip_attributes()
230 } else {
231 false
232 };
233
234 if !skip_attributes {
235 let attr_patches = create_attribute_patches(
236 old_element.tag(),
237 old_element.attributes(),
238 new_element.attributes(),
239 path,
240 );
241 patches.extend(attr_patches);
242 }
243
244 let more_patches = diff_nodes(
245 Some(old_element.tag()),
246 old_element.children(),
247 new_element.children(),
248 path,
249 );
250
251 patches.extend(more_patches);
252 }
253 _ => {
254 unreachable!("Unequal variant discriminants should already have been handled");
255 }
256 };
257
258 patches
259}
260
261fn diff_nodes<'a, MSG>(
262 old_tag: Option<&'a Tag>,
263 old_children: &'a [Node<MSG>],
264 new_children: &'a [Node<MSG>],
265 path: &SkipPath,
266) -> Vec<Patch<'a, MSG>> {
267 let diff_as_keyed = is_any_keyed(old_children) || is_any_keyed(new_children);
268
269 if diff_as_keyed {
270 let keyed_patches = diff_lis::diff_keyed_nodes(old_tag, old_children, new_children, path);
271 keyed_patches
272 } else {
273 let non_keyed_patches = diff_non_keyed_nodes(old_tag, old_children, new_children, path);
274 non_keyed_patches
275 }
276}
277
278fn diff_non_keyed_nodes<'a, MSG>(
289 old_element_tag: Option<&'a Tag>,
290 old_children: &'a [Node<MSG>],
291 new_children: &'a [Node<MSG>],
292 path: &SkipPath,
293) -> Vec<Patch<'a, MSG>> {
294 let mut patches = vec![];
295 let old_child_count = old_children.len();
296 let new_child_count = new_children.len();
297
298 if old_child_count > 0 && new_child_count == 0 {
300 return vec![Patch::clear_children(old_element_tag, path.path.clone())];
301 }
302
303 let min_count = cmp::min(old_child_count, new_child_count);
304 for index in 0..min_count {
305 let child_path = path.traverse(index);
307
308 let old_child = &old_children.get(index).expect("No old_node child node");
309 let new_child = &new_children.get(index).expect("No new child node");
310
311 let more_patches = diff_recursive(old_child, new_child, &child_path);
312 patches.extend(more_patches);
313 }
314
315 if new_child_count > old_child_count {
318 patches.push(Patch::append_children(
319 old_element_tag,
320 path.path.clone(),
321 new_children.iter().skip(old_child_count).collect(),
322 ));
323 }
324
325 if new_child_count < old_child_count {
326 let remove_node_patches = old_children
327 .iter()
328 .skip(new_child_count)
329 .enumerate()
330 .map(|(i, old_child)| {
331 Patch::remove_node(old_child.tag(), path.traverse(new_child_count + i).path)
332 })
333 .collect::<Vec<_>>();
334
335 patches.extend(remove_node_patches);
336 }
337
338 patches
339}
340
341#[allow(clippy::type_complexity)]
346fn create_attribute_patches<'a, MSG>(
347 old_tag: &'a Tag,
348 old_attributes: &'a [Attribute<MSG>],
349 new_attributes: &'a [Attribute<MSG>],
350 path: &SkipPath,
351) -> Vec<Patch<'a, MSG>> {
352 let skip_indices = if let Some(skip_diff) = &path.skip_diff {
353 if let SkipAttrs::Indices(skip_indices) = &skip_diff.skip_attrs {
354 skip_indices.clone()
355 } else {
356 vec![]
357 }
358 } else {
359 vec![]
360 };
361
362 let has_skip_indices = !skip_indices.is_empty();
363
364 let mut patches = vec![];
365
366 if old_attributes.is_empty() && new_attributes.is_empty() {
368 return vec![];
369 }
370
371 let mut add_attributes: Vec<&Attribute<MSG>> = vec![];
372 let mut remove_attributes: Vec<&Attribute<MSG>> = vec![];
373
374 let new_attributes_grouped = Element::group_indexed_attributes_per_name(new_attributes);
375 let old_attributes_grouped = Element::group_indexed_attributes_per_name(old_attributes);
376
377 for (new_attr_name, new_attrs) in new_attributes_grouped.iter() {
381 let old_indexed_attr_values = old_attributes_grouped.get(new_attr_name).map(|attrs| {
382 attrs
383 .iter()
384 .map(|(i, attr)| (*i, &attr.value))
385 .collect::<Vec<_>>()
386 });
387
388 let new_indexed_attr_values = new_attributes_grouped.get(new_attr_name).map(|attrs| {
389 attrs
390 .iter()
391 .map(|(i, attr)| (*i, &attr.value))
392 .collect::<Vec<_>>()
393 });
394
395 if let Some(old_indexed_attr_values) = old_indexed_attr_values {
396 let new_indexed_attr_values =
397 new_indexed_attr_values.expect("must have new attr values");
398 let (_new_indices, new_attr_values): (Vec<usize>, Vec<&Vec<AttributeValue<MSG>>>) =
399 new_indexed_attr_values.into_iter().unzip();
400 let (old_indices, old_attr_values): (Vec<usize>, Vec<&Vec<AttributeValue<MSG>>>) =
401 old_indexed_attr_values.into_iter().unzip();
402 if USE_SKIP_DIFF && has_skip_indices && is_subset_of(&old_indices, &skip_indices) {
403 } else if old_attr_values != new_attr_values {
405 for (_i, new_att) in new_attrs {
406 add_attributes.push(new_att);
407 }
408 }
409 } else {
410 for (_i, new_att) in new_attrs {
412 add_attributes.push(new_att);
413 }
414 }
415 }
416
417 for (old_attr_name, old_indexed_attrs) in old_attributes_grouped.into_iter() {
420 let (old_indices, old_attrs): (Vec<usize>, Vec<&Attribute<MSG>>) =
421 old_indexed_attrs.into_iter().unzip();
422 if USE_SKIP_DIFF && has_skip_indices && is_subset_of(&old_indices, &skip_indices) {
423 } else if !new_attributes_grouped.contains_key(old_attr_name) {
425 remove_attributes.extend(old_attrs.clone());
426 }
427 }
428
429 if !add_attributes.is_empty() {
430 patches.push(Patch::add_attributes(
431 old_tag,
432 path.path.clone(),
433 add_attributes,
434 ));
435 }
436 if !remove_attributes.is_empty() {
437 patches.push(Patch::remove_attributes(
438 old_tag,
439 path.path.clone(),
440 remove_attributes,
441 ));
442 }
443 patches
444}
445
446fn is_subset_of<T: PartialEq>(subset: &[T], big_set: &[T]) -> bool {
449 subset.iter().all(|set| big_set.contains(set))
450}