@@ -32,17 +32,20 @@ pub(crate) fn diff(
32 &NodeKind::Text(TextNode { text: old_text }),
32 &NodeKind::Text(TextNode { text: old_text }),
34 if new_text != old_text {
34 if new_text != old_text {
35+ change_list.commit_traversal();
35 change_list.set_text(new_text);
36 change_list.set_text(new_text);
39 (&NodeKind::Text(_), &NodeKind::Element(_)) => {
40 (&NodeKind::Text(_), &NodeKind::Element(_)) => {
41+ change_list.commit_traversal();
40 create(cached_set, change_list, registry, new, cached_roots);
42 create(cached_set, change_list, registry, new, cached_roots);
41 registry.remove_subtree(&old);
43 registry.remove_subtree(&old);
42 change_list.replace_with();
44 change_list.replace_with();
45 (&NodeKind::Element(_), &NodeKind::Text(_)) => {
47 (&NodeKind::Element(_), &NodeKind::Text(_)) => {
48+ change_list.commit_traversal();
46 create(cached_set, change_list, registry, new, cached_roots);
49 create(cached_set, change_list, registry, new, cached_roots);
47 // Note: text nodes cannot have event listeners, so we don't need to
50 // Note: text nodes cannot have event listeners, so we don't need to
48 // remove the old node's listeners from our registry her.
51 // remove the old node's listeners from our registry her.
@@ -68,6 +71,7 @@ pub(crate) fn diff(
70 if new_tag_name != old_tag_name || new_namespace != old_namespace {
73 if new_tag_name != old_tag_name || new_namespace != old_namespace {
74+ change_list.commit_traversal();
71 create(cached_set, change_list, registry, new, cached_roots);
75 create(cached_set, change_list, registry, new, cached_roots);
72 registry.remove_subtree(&old);
76 registry.remove_subtree(&old);
73 change_list.replace_with();
77 change_list.replace_with();
@@ -103,6 +107,7 @@ pub(crate) fn diff(
103 // we assume that they are pretty different, and it isn't worth diffing
107 // we assume that they are pretty different, and it isn't worth diffing
104 // the subtrees, so we just create the new cached node afresh.
108 // the subtrees, so we just create the new cached node afresh.
105 (&NodeKind::Cached(ref c), _) => {
109 (&NodeKind::Cached(ref c), _) => {
110+ change_list.commit_traversal();
106 cached_roots.insert(c.id);
111 cached_roots.insert(c.id);
107 let new = cached_set.get(c.id);
112 let new = cached_set.get(c.id);
108 create(cached_set, change_list, registry, new, cached_roots);
113 create(cached_set, change_list, registry, new, cached_roots);
@@ -113,6 +118,7 @@ pub(crate) fn diff(
113 // Old cached node and new non-cached node. Again, assume that they are
118 // Old cached node and new non-cached node. Again, assume that they are
114 // probably pretty different and create the new non-cached node afresh.
119 // probably pretty different and create the new non-cached node afresh.
115 (_, &NodeKind::Cached(_)) => {
120 (_, &NodeKind::Cached(_)) => {
121+ change_list.commit_traversal();
116 create(cached_set, change_list, registry, new, cached_roots);
122 create(cached_set, change_list, registry, new, cached_roots);
117 registry.remove_subtree(&old);
123 registry.remove_subtree(&old);
118 change_list.replace_with();
124 change_list.replace_with();
@@ -133,6 +139,10 @@ fn diff_listeners(
133 old: &[Listener],
139 old: &[Listener],
134 new: &[Listener],
140 new: &[Listener],
142+ if !old.is_empty() || !new.is_empty() {
143+ change_list.commit_traversal();
136 'outer1: for new_l in new {
146 'outer1: for new_l in new {
138 // Safety relies on removing `new_l` from the registry manually at
148 // Safety relies on removing `new_l` from the registry manually at
@@ -175,16 +185,20 @@ fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new:
175 // there are almost always very few attributes.
185 // there are almost always very few attributes.
176 'outer: for new_attr in new {
186 'outer: for new_attr in new {
177 if new_attr.is_volatile() {
187 if new_attr.is_volatile() {
188+ change_list.commit_traversal();
178 change_list.set_attribute(new_attr.name, new_attr.value);
189 change_list.set_attribute(new_attr.name, new_attr.value);
180 for old_attr in old {
191 for old_attr in old {
181 if old_attr.name == new_attr.name {
192 if old_attr.name == new_attr.name {
182 if old_attr.value != new_attr.value {
193 if old_attr.value != new_attr.value {
194+ change_list.commit_traversal();
183 change_list.set_attribute(new_attr.name, new_attr.value);
195 change_list.set_attribute(new_attr.name, new_attr.value);
185 continue 'outer;
197 continue 'outer;
201+ change_list.commit_traversal();
188 change_list.set_attribute(new_attr.name, new_attr.value);
202 change_list.set_attribute(new_attr.name, new_attr.value);
@@ -195,6 +209,8 @@ fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new:
195 continue 'outer2;
209 continue 'outer2;
213+ change_list.commit_traversal();
198 change_list.remove_attribute(old_attr.name);
214 change_list.remove_attribute(old_attr.name);
@@ -217,13 +233,17 @@ fn diff_children(
218 if new.is_empty() {
234 if new.is_empty() {
219 if !old.is_empty() {
235 if !old.is_empty() {
236+ change_list.commit_traversal();
220 remove_all_children(change_list, registry, old);
237 remove_all_children(change_list, registry, old);
225 if old.is_empty() {
242 if old.is_empty() {
243+ if !new.is_empty() {
244+ change_list.commit_traversal();
226− create_and_append_children(cached_set, change_list, registry, new, cached_roots);
245+ create_and_append_children(cached_set, change_list, registry, new, cached_roots);
@@ -384,42 +404,24 @@ fn diff_keyed_prefix(
384 new: &[Node],
404 new: &[Node],
385 cached_roots: &mut FxHashSet<CacheId>,
405 cached_roots: &mut FxHashSet<CacheId>,
386) -> KeyedPrefixResult {
406) -> KeyedPrefixResult {
387− let mut pushed = false;
407+ change_list.go_down();
388 let mut shared_prefix_count = 0;
408 let mut shared_prefix_count = 0;
390− for (old, new) in old.iter().zip(new.iter()) {
410+ for (i, (old, new)) in old.iter().zip(new.iter()).enumerate() {
391 if old.key() != new.key() {
411 if old.key() != new.key() {
395− if pushed {
415+ change_list.go_to_sibling(i);
396− debug_assert!(shared_prefix_count > 0);
397− change_list.pop_push_next_sibling();
399− debug_assert_eq!(shared_prefix_count, 0);
400− change_list.push_first_child();
404 diff(cached_set, change_list, registry, old, new, cached_roots);
416 diff(cached_set, change_list, registry, old, new, cached_roots);
405 shared_prefix_count += 1;
417 shared_prefix_count += 1;
407− // At the end of the loop, the change list stack looks like
409− // [... parent child_we_just_diffed]
410− debug_assert!(pushed);
413 // If that was all of the old children, then create and append the remaining
420 // If that was all of the old children, then create and append the remaining
414 // new children and we're finished.
421 // new children and we're finished.
415 if shared_prefix_count == old.len() {
422 if shared_prefix_count == old.len() {
416− debug_assert!(
423+ change_list.go_up();
418− "we handle the case of empty children before calling this method, so
419− `shared_prefix_count` must be greater than zero, and therefore we
420− must have pushed in the above loop"
422− change_list.pop();
424+ change_list.commit_traversal();
423 create_and_append_children(
425 create_and_append_children(
424 cached_set,
426 cached_set,
425 change_list,
427 change_list,
@@ -433,17 +435,13 @@ fn diff_keyed_prefix(
433 // And if that was all of the new children, then remove all of the remaining
435 // And if that was all of the new children, then remove all of the remaining
434 // old children and we're finished.
436 // old children and we're finished.
435 if shared_prefix_count == new.len() {
437 if shared_prefix_count == new.len() {
436− // Same as above.
438+ change_list.go_to_sibling(shared_prefix_count);
437− debug_assert!(pushed);
439+ change_list.commit_traversal();
438− change_list.pop_push_next_sibling();
439− change_list.remove_self_and_next_siblings();
440+ remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
440 return KeyedPrefixResult::Finished;
441 return KeyedPrefixResult::Finished;
444− debug_assert!(shared_prefix_count > 0);
445− change_list.pop();
444+ change_list.go_up();
447 KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
445 KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
@@ -507,9 +505,11 @@ fn diff_keyed_middle(
507 // afresh.
505 // afresh.
508 if shared_suffix_count == 0 && shared_keys.is_empty() {
506 if shared_suffix_count == 0 && shared_keys.is_empty() {
509 if shared_prefix_count == 0 {
507 if shared_prefix_count == 0 {
508+ change_list.commit_traversal();
510 remove_all_children(change_list, registry, old);
509 remove_all_children(change_list, registry, old);
512− change_list.pop_push_next_sibling();
511+ change_list.go_down_to_child(shared_prefix_count);
512+ change_list.commit_traversal();
513 remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
513 remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
515 create_and_append_children(cached_set, change_list, registry, new, cached_roots);
515 create_and_append_children(cached_set, change_list, registry, new, cached_roots);
@@ -529,6 +529,7 @@ fn diff_keyed_middle(
529 .unwrap_or(old.len());
529 .unwrap_or(old.len());
531 if end - start > 0 {
531 if end - start > 0 {
532+ change_list.commit_traversal();
532 let mut t = change_list.save_children_to_temporaries(
533 let mut t = change_list.save_children_to_temporaries(
533 shared_prefix_count + start,
534 shared_prefix_count + start,
534 shared_prefix_count + end,
535 shared_prefix_count + end,
@@ -553,6 +554,7 @@ fn diff_keyed_middle(
553 for (i, old_child) in old.iter().enumerate().rev() {
554 for (i, old_child) in old.iter().enumerate().rev() {
554 if !shared_keys.contains(&old_child.key()) {
555 if !shared_keys.contains(&old_child.key()) {
555 registry.remove_subtree(old_child);
556 registry.remove_subtree(old_child);
557+ change_list.commit_traversal();
556 change_list.remove_child(i + shared_prefix_count);
558 change_list.remove_child(i + shared_prefix_count);
557 removed_count += 1;
559 removed_count += 1;
@@ -596,7 +598,7 @@ fn diff_keyed_middle(
596 // shared suffix to the change list stack.
598 // shared suffix to the change list stack.
598 // [... parent]
600 // [... parent]
599− change_list.push_child(old_shared_suffix_start - removed_count);
601+ change_list.go_down_to_child(old_shared_suffix_start - removed_count);
600 // [... parent first_child_of_shared_suffix]
602 // [... parent first_child_of_shared_suffix]
602 // There is no shared suffix coming after these middle children.
604 // There is no shared suffix coming after these middle children.
@@ -609,7 +611,7 @@ fn diff_keyed_middle(
609 let old_index = new_index_to_old_index[last_index];
611 let old_index = new_index_to_old_index[last_index];
610 let temp = old_index_to_temp[old_index];
612 let temp = old_index_to_temp[old_index];
611 // [... parent]
613 // [... parent]
612− change_list.push_temporary(temp);
614+ change_list.go_down_to_temp_child(temp);
613 // [... parent last]
615 // [... parent last]
615 cached_set,
617 cached_set,
@@ -622,18 +624,21 @@ fn diff_keyed_middle(
622 if new_index_is_in_lis.contains(&last_index) {
624 if new_index_is_in_lis.contains(&last_index) {
623 // Don't move it, since it is already where it needs to be.
625 // Don't move it, since it is already where it needs to be.
627+ change_list.commit_traversal();
628+ // [... parent last]
625 change_list.append_child();
629 change_list.append_child();
626 // [... parent]
630 // [... parent]
627− change_list.push_temporary(temp);
631+ change_list.go_down_to_temp_child(temp);
628 // [... parent last]
632 // [... parent last]
635+ change_list.commit_traversal();
631 // [... parent]
636 // [... parent]
632 create(cached_set, change_list, registry, last, cached_roots);
637 create(cached_set, change_list, registry, last, cached_roots);
633 // [... parent last]
638 // [... parent last]
634 change_list.append_child();
639 change_list.append_child();
635 // [... parent]
640 // [... parent]
636− change_list.push_last_child();
641+ change_list.go_down_to_reverse_child(0);
637 // [... parent last]
642 // [... parent last]
@@ -642,6 +647,7 @@ fn diff_keyed_middle(
642 let old_index = new_index_to_old_index[new_index];
647 let old_index = new_index_to_old_index[new_index];
643 if old_index == u32::MAX as usize {
648 if old_index == u32::MAX as usize {
644 debug_assert!(!shared_keys.contains(&new_child.key()));
649 debug_assert!(!shared_keys.contains(&new_child.key()));
650+ change_list.commit_traversal();
645 // [... parent successor]
651 // [... parent successor]
646 create(cached_set, change_list, registry, new_child, cached_roots);
652 create(cached_set, change_list, registry, new_child, cached_roots);
647 // [... parent successor new_child]
653 // [... parent successor new_child]
@@ -654,11 +660,10 @@ fn diff_keyed_middle(
655 if new_index_is_in_lis.contains(&new_index) {
661 if new_index_is_in_lis.contains(&new_index) {
656 // [... parent successor]
662 // [... parent successor]
657− change_list.pop();
663+ change_list.go_to_temp_sibling(temp);
659− change_list.push_temporary(temp);
660 // [... parent new_child]
664 // [... parent new_child]
666+ change_list.commit_traversal();
662 // [... parent successor]
667 // [... parent successor]
663 change_list.push_temporary(temp);
668 change_list.push_temporary(temp);
664 // [... parent successor new_child]
669 // [... parent successor new_child]
@@ -678,7 +683,7 @@ fn diff_keyed_middle(
680 // [... parent child]
685 // [... parent child]
681− change_list.pop();
686+ change_list.go_up();
682 // [... parent]
687 // [... parent]
@@ -702,10 +707,11 @@ fn diff_keyed_suffix(
702 debug_assert!(!old.is_empty());
707 debug_assert!(!old.is_empty());
704 // [... parent]
709 // [... parent]
705− change_list.push_child(new_shared_suffix_start);
710+ change_list.go_down();
706 // [... parent new_child]
711 // [... parent new_child]
708− for (old_child, new_child) in old.iter().zip(new.iter()) {
713+ for (i, (old_child, new_child)) in old.iter().zip(new.iter()).enumerate() {
714+ change_list.go_to_sibling(new_shared_suffix_start + i);
710 cached_set,
716 cached_set,
711 change_list,
717 change_list,
@@ -714,14 +720,10 @@ fn diff_keyed_suffix(
714 new_child,
720 new_child,
715 cached_roots,
721 cached_roots,
718− // [... parent this_new_child]
719− change_list.pop_push_next_sibling();
720− // [... parent next_new_child]
723 // [... parent]
725 // [... parent]
724− change_list.pop();
726+ change_list.go_up();
727// Diff children that are not keyed.
729// Diff children that are not keyed.
@@ -744,17 +746,14 @@ fn diff_non_keyed_children(
744 debug_assert!(!new.is_empty());
746 debug_assert!(!new.is_empty());
745 debug_assert!(!old.is_empty());
747 debug_assert!(!old.is_empty());
747− for (i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
750− change_list.push_first_child();
751− // [... parent first_child]
749+ // [... parent]
753− // [... parent prev_sibling]
754− change_list.pop_push_next_sibling();
750+ change_list.go_down();
755− // [... parent next_sibling]
751+ // [... parent child]
753+ for (i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
754+ // [... parent prev_child]
755+ change_list.go_to_sibling(i);
756+ // [... parent this_child]
759 cached_set,
758 cached_set,
760 change_list,
759 change_list,
@@ -765,23 +764,20 @@ fn diff_non_keyed_children(
768− // Note that because `new` and `old` are not empty, the previous loop always
769− // executes at least once, so the change list stack is now:
771− // [... parent child]
773 match old.len().cmp(&new.len()) {
767 match old.len().cmp(&new.len()) {
774 Ordering::Greater => {
768 Ordering::Greater => {
775− // [... parent last_shared_child]
769+ // [... parent prev_child]
776− change_list.pop_push_next_sibling();
770+ change_list.go_to_sibling(new.len());
777 // [... parent first_child_to_remove]
771 // [... parent first_child_to_remove]
772+ change_list.commit_traversal();
778 remove_self_and_next_siblings(change_list, registry, &old[new.len()..]);
773 remove_self_and_next_siblings(change_list, registry, &old[new.len()..]);
779 // [... parent]
774 // [... parent]
781 Ordering::Less => {
776 Ordering::Less => {
782 // [... parent last_child]
777 // [... parent last_child]
783− change_list.pop();
778+ change_list.go_up();
784 // [... parent]
779 // [... parent]
780+ change_list.commit_traversal();
785 create_and_append_children(
781 create_and_append_children(
786 cached_set,
782 cached_set,
787 change_list,
783 change_list,
@@ -792,7 +788,7 @@ fn diff_non_keyed_children(
793 Ordering::Equal => {
789 Ordering::Equal => {
794 // [... parent child]
790 // [... parent child]
795− change_list.pop();
791+ change_list.go_up();
796 // [... parent]
792 // [... parent]
@@ -812,6 +808,7 @@ fn create_and_append_children(
812 new: &[Node],
808 new: &[Node],
813 cached_roots: &mut FxHashSet<CacheId>,
809 cached_roots: &mut FxHashSet<CacheId>,
811+ debug_assert!(change_list.traversal_is_committed());
815 for child in new {
812 for child in new {
816 create(cached_set, change_list, registry, child, cached_roots);
813 create(cached_set, change_list, registry, child, cached_roots);
817 change_list.append_child();
814 change_list.append_child();
@@ -830,6 +827,7 @@ fn remove_all_children(
830 registry: &mut EventsRegistry,
827 registry: &mut EventsRegistry,
831 old: &[Node],
828 old: &[Node],
830+ debug_assert!(change_list.traversal_is_committed());
833 for child in old {
831 for child in old {
834 registry.remove_subtree(child);
832 registry.remove_subtree(child);
@@ -852,6 +850,7 @@ fn remove_self_and_next_siblings(
852 registry: &mut EventsRegistry,
850 registry: &mut EventsRegistry,
853 old: &[Node],
851 old: &[Node],
853+ debug_assert!(change_list.traversal_is_committed());
855 for child in old {
854 for child in old {
856 registry.remove_subtree(child);
855 registry.remove_subtree(child);
@@ -874,6 +873,7 @@ fn create(
874 node: &Node,
873 node: &Node,
875 cached_roots: &mut FxHashSet<CacheId>,
874 cached_roots: &mut FxHashSet<CacheId>,
876+ debug_assert!(change_list.traversal_is_committed());
877 match node.kind {
877 match node.kind {
878 NodeKind::Text(TextNode { text }) => {
878 NodeKind::Text(TextNode { text }) => {
879 change_list.create_text_node(text);
879 change_list.create_text_node(text);