@@ -32,17 +32,20 @@ pub(crate) fn diff(
3232 &NodeKind::Text(TextNode { text: old_text }),
3333 ) => {
3434 if new_text != old_text {
35+ change_list.commit_traversal();
3536 change_list.set_text(new_text);
3637 }
3738 }
3839
3940 (&NodeKind::Text(_), &NodeKind::Element(_)) => {
41+ change_list.commit_traversal();
4042 create(cached_set, change_list, registry, new, cached_roots);
4143 registry.remove_subtree(&old);
4244 change_list.replace_with();
4345 }
4446
4547 (&NodeKind::Element(_), &NodeKind::Text(_)) => {
48+ change_list.commit_traversal();
4649 create(cached_set, change_list, registry, new, cached_roots);
4750 // Note: text nodes cannot have event listeners, so we don't need to
4851 // remove the old node's listeners from our registry her.
@@ -68,6 +71,7 @@ pub(crate) fn diff(
6871 }),
6972 ) => {
7073 if new_tag_name != old_tag_name || new_namespace != old_namespace {
74+ change_list.commit_traversal();
7175 create(cached_set, change_list, registry, new, cached_roots);
7276 registry.remove_subtree(&old);
7377 change_list.replace_with();
@@ -103,6 +107,7 @@ pub(crate) fn diff(
103107 // we assume that they are pretty different, and it isn't worth diffing
104108 // the subtrees, so we just create the new cached node afresh.
105109 (&NodeKind::Cached(ref c), _) => {
110+ change_list.commit_traversal();
106111 cached_roots.insert(c.id);
107112 let new = cached_set.get(c.id);
108113 create(cached_set, change_list, registry, new, cached_roots);
@@ -113,6 +118,7 @@ pub(crate) fn diff(
113118 // Old cached node and new non-cached node. Again, assume that they are
114119 // probably pretty different and create the new non-cached node afresh.
115120 (_, &NodeKind::Cached(_)) => {
121+ change_list.commit_traversal();
116122 create(cached_set, change_list, registry, new, cached_roots);
117123 registry.remove_subtree(&old);
118124 change_list.replace_with();
@@ -133,6 +139,10 @@ fn diff_listeners(
133139 old: &[Listener],
134140 new: &[Listener],
135141) {
142+ if !old.is_empty() || !new.is_empty() {
143+ change_list.commit_traversal();
144+ }
145+
136146 'outer1: for new_l in new {
137147 unsafe {
138148 // 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:
175185 // there are almost always very few attributes.
176186 'outer: for new_attr in new {
177187 if new_attr.is_volatile() {
188+ change_list.commit_traversal();
178189 change_list.set_attribute(new_attr.name, new_attr.value);
179190 } else {
180191 for old_attr in old {
181192 if old_attr.name == new_attr.name {
182193 if old_attr.value != new_attr.value {
194+ change_list.commit_traversal();
183195 change_list.set_attribute(new_attr.name, new_attr.value);
184196 }
185197 continue 'outer;
186198 }
187199 }
200+
201+ change_list.commit_traversal();
188202 change_list.set_attribute(new_attr.name, new_attr.value);
189203 }
190204 }
@@ -195,6 +209,8 @@ fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new:
195209 continue 'outer2;
196210 }
197211 }
212+
213+ change_list.commit_traversal();
198214 change_list.remove_attribute(old_attr.name);
199215 }
200216}
@@ -217,13 +233,17 @@ fn diff_children(
217233) {
218234 if new.is_empty() {
219235 if !old.is_empty() {
236+ change_list.commit_traversal();
220237 remove_all_children(change_list, registry, old);
221238 }
222239 return;
223240 }
224241
225242 if old.is_empty() {
226− create_and_append_children(cached_set, change_list, registry, new, cached_roots);
243+ if !new.is_empty() {
244+ change_list.commit_traversal();
245+ create_and_append_children(cached_set, change_list, registry, new, cached_roots);
246+ }
227247 return;
228248 }
229249
@@ -384,42 +404,24 @@ fn diff_keyed_prefix(
384404 new: &[Node],
385405 cached_roots: &mut FxHashSet<CacheId>,
386406) -> KeyedPrefixResult {
387− let mut pushed = false;
407+ change_list.go_down();
388408 let mut shared_prefix_count = 0;
389409
390− for (old, new) in old.iter().zip(new.iter()) {
410+ for (i, (old, new)) in old.iter().zip(new.iter()).enumerate() {
391411 if old.key() != new.key() {
392412 break;
393413 }
394414
395− if pushed {
396− debug_assert!(shared_prefix_count > 0);
397− change_list.pop_push_next_sibling();
398− } else {
399− debug_assert_eq!(shared_prefix_count, 0);
400− change_list.push_first_child();
401− pushed = true;
402− }
403−
415+ change_list.go_to_sibling(i);
404416 diff(cached_set, change_list, registry, old, new, cached_roots);
405417 shared_prefix_count += 1;
406−
407− // At the end of the loop, the change list stack looks like
408− //
409− // [... parent child_we_just_diffed]
410− debug_assert!(pushed);
411418 }
412419
413420 // If that was all of the old children, then create and append the remaining
414421 // new children and we're finished.
415422 if shared_prefix_count == old.len() {
416− debug_assert!(
417− pushed,
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"
421− );
422− change_list.pop();
423+ change_list.go_up();
424+ change_list.commit_traversal();
423425 create_and_append_children(
424426 cached_set,
425427 change_list,
@@ -433,17 +435,13 @@ fn diff_keyed_prefix(
433435 // And if that was all of the new children, then remove all of the remaining
434436 // old children and we're finished.
435437 if shared_prefix_count == new.len() {
436− // Same as above.
437− debug_assert!(pushed);
438− change_list.pop_push_next_sibling();
439− change_list.remove_self_and_next_siblings();
438+ change_list.go_to_sibling(shared_prefix_count);
439+ change_list.commit_traversal();
440+ remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
440441 return KeyedPrefixResult::Finished;
441442 }
442443
443− if pushed {
444− debug_assert!(shared_prefix_count > 0);
445− change_list.pop();
446− }
444+ change_list.go_up();
447445 KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
448446}
449447
@@ -507,9 +505,11 @@ fn diff_keyed_middle(
507505 // afresh.
508506 if shared_suffix_count == 0 && shared_keys.is_empty() {
509507 if shared_prefix_count == 0 {
508+ change_list.commit_traversal();
510509 remove_all_children(change_list, registry, old);
511510 } else {
512− change_list.pop_push_next_sibling();
511+ change_list.go_down_to_child(shared_prefix_count);
512+ change_list.commit_traversal();
513513 remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
514514 }
515515 create_and_append_children(cached_set, change_list, registry, new, cached_roots);
@@ -529,6 +529,7 @@ fn diff_keyed_middle(
529529 .unwrap_or(old.len());
530530
531531 if end - start > 0 {
532+ change_list.commit_traversal();
532533 let mut t = change_list.save_children_to_temporaries(
533534 shared_prefix_count + start,
534535 shared_prefix_count + end,
@@ -553,6 +554,7 @@ fn diff_keyed_middle(
553554 for (i, old_child) in old.iter().enumerate().rev() {
554555 if !shared_keys.contains(&old_child.key()) {
555556 registry.remove_subtree(old_child);
557+ change_list.commit_traversal();
556558 change_list.remove_child(i + shared_prefix_count);
557559 removed_count += 1;
558560 }
@@ -596,7 +598,7 @@ fn diff_keyed_middle(
596598 // shared suffix to the change list stack.
597599 //
598600 // [... 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);
600602 // [... parent first_child_of_shared_suffix]
601603 } else {
602604 // There is no shared suffix coming after these middle children.
@@ -609,7 +611,7 @@ fn diff_keyed_middle(
609611 let old_index = new_index_to_old_index[last_index];
610612 let temp = old_index_to_temp[old_index];
611613 // [... parent]
612− change_list.push_temporary(temp);
614+ change_list.go_down_to_temp_child(temp);
613615 // [... parent last]
614616 diff(
615617 cached_set,
@@ -622,18 +624,21 @@ fn diff_keyed_middle(
622624 if new_index_is_in_lis.contains(&last_index) {
623625 // Don't move it, since it is already where it needs to be.
624626 } else {
627+ change_list.commit_traversal();
628+ // [... parent last]
625629 change_list.append_child();
626630 // [... parent]
627− change_list.push_temporary(temp);
631+ change_list.go_down_to_temp_child(temp);
628632 // [... parent last]
629633 }
630634 } else {
635+ change_list.commit_traversal();
631636 // [... parent]
632637 create(cached_set, change_list, registry, last, cached_roots);
633638 // [... parent last]
634639 change_list.append_child();
635640 // [... parent]
636− change_list.push_last_child();
641+ change_list.go_down_to_reverse_child(0);
637642 // [... parent last]
638643 }
639644 }
@@ -642,6 +647,7 @@ fn diff_keyed_middle(
642647 let old_index = new_index_to_old_index[new_index];
643648 if old_index == u32::MAX as usize {
644649 debug_assert!(!shared_keys.contains(&new_child.key()));
650+ change_list.commit_traversal();
645651 // [... parent successor]
646652 create(cached_set, change_list, registry, new_child, cached_roots);
647653 // [... parent successor new_child]
@@ -654,11 +660,10 @@ fn diff_keyed_middle(
654660
655661 if new_index_is_in_lis.contains(&new_index) {
656662 // [... parent successor]
657− change_list.pop();
658− // [... parent]
659− change_list.push_temporary(temp);
663+ change_list.go_to_temp_sibling(temp);
660664 // [... parent new_child]
661665 } else {
666+ change_list.commit_traversal();
662667 // [... parent successor]
663668 change_list.push_temporary(temp);
664669 // [... parent successor new_child]
@@ -678,7 +683,7 @@ fn diff_keyed_middle(
678683 }
679684
680685 // [... parent child]
681− change_list.pop();
686+ change_list.go_up();
682687 // [... parent]
683688}
684689
@@ -702,10 +707,11 @@ fn diff_keyed_suffix(
702707 debug_assert!(!old.is_empty());
703708
704709 // [... parent]
705− change_list.push_child(new_shared_suffix_start);
710+ change_list.go_down();
706711 // [... parent new_child]
707712
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);
709715 diff(
710716 cached_set,
711717 change_list,
@@ -714,14 +720,10 @@ fn diff_keyed_suffix(
714720 new_child,
715721 cached_roots,
716722 );
717−
718− // [... parent this_new_child]
719− change_list.pop_push_next_sibling();
720− // [... parent next_new_child]
721723 }
722724
723725 // [... parent]
724− change_list.pop();
726+ change_list.go_up();
725727}
726728
727729// Diff children that are not keyed.
@@ -744,17 +746,14 @@ fn diff_non_keyed_children(
744746 debug_assert!(!new.is_empty());
745747 debug_assert!(!old.is_empty());
746748
747− for (i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
748− if i == 0 {
749− // [... parent]
750− change_list.push_first_child();
751− // [... parent first_child]
752− } else {
753− // [... parent prev_sibling]
754− change_list.pop_push_next_sibling();
755− // [... parent next_sibling]
756− }
749+ // [... parent]
750+ change_list.go_down();
751+ // [... parent child]
757752
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]
758757 diff(
759758 cached_set,
760759 change_list,
@@ -765,23 +764,20 @@ fn diff_non_keyed_children(
765764 );
766765 }
767766
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:
770− //
771− // [... parent child]
772−
773767 match old.len().cmp(&new.len()) {
774768 Ordering::Greater => {
775− // [... parent last_shared_child]
776− change_list.pop_push_next_sibling();
769+ // [... parent prev_child]
770+ change_list.go_to_sibling(new.len());
777771 // [... parent first_child_to_remove]
772+ change_list.commit_traversal();
778773 remove_self_and_next_siblings(change_list, registry, &old[new.len()..]);
779774 // [... parent]
780775 }
781776 Ordering::Less => {
782777 // [... parent last_child]
783− change_list.pop();
778+ change_list.go_up();
784779 // [... parent]
780+ change_list.commit_traversal();
785781 create_and_append_children(
786782 cached_set,
787783 change_list,
@@ -792,7 +788,7 @@ fn diff_non_keyed_children(
792788 }
793789 Ordering::Equal => {
794790 // [... parent child]
795− change_list.pop();
791+ change_list.go_up();
796792 // [... parent]
797793 }
798794 }
@@ -812,6 +808,7 @@ fn create_and_append_children(
812808 new: &[Node],
813809 cached_roots: &mut FxHashSet<CacheId>,
814810) {
811+ debug_assert!(change_list.traversal_is_committed());
815812 for child in new {
816813 create(cached_set, change_list, registry, child, cached_roots);
817814 change_list.append_child();
@@ -830,6 +827,7 @@ fn remove_all_children(
830827 registry: &mut EventsRegistry,
831828 old: &[Node],
832829) {
830+ debug_assert!(change_list.traversal_is_committed());
833831 for child in old {
834832 registry.remove_subtree(child);
835833 }
@@ -852,6 +850,7 @@ fn remove_self_and_next_siblings(
852850 registry: &mut EventsRegistry,
853851 old: &[Node],
854852) {
853+ debug_assert!(change_list.traversal_is_committed());
855854 for child in old {
856855 registry.remove_subtree(child);
857856 }
@@ -874,6 +873,7 @@ fn create(
874873 node: &Node,
875874 cached_roots: &mut FxHashSet<CacheId>,
876875) {
876+ debug_assert!(change_list.traversal_is_committed());
877877 match node.kind {
878878 NodeKind::Text(TextNode { text }) => {
879879 change_list.create_text_node(text);