@@ -4,15 +4,26 @@ use crate::{
44 events::EventsRegistry,
55 node::{Attribute, ElementNode, Listener, Node, NodeKind, TextNode},
66};
7−use fxhash::FxHashSet;
8−use std::cmp;
7+use fxhash::{FxHashMap, FxHashSet};
8+use std::cmp::Ordering;
9+use std::u32;
10+use wasm_bindgen::UnwrapThrowExt;
911
12+// Diff the `old` node with the `new` node. Emits instructions to modify a
13+// physical DOM node that reflects `old` into something that reflects `new`.
14+//
15+// Upon entry to this function, the physical DOM node must be on the top of the
16+// change list stack:
17+//
18+// [... node]
19+//
20+// The change list stack is in the same state when this function exits.
1021pub(crate) fn diff(
1122 cached_set: &CachedSet,
1223 change_list: &mut ChangeListBuilder,
1324 registry: &mut EventsRegistry,
14− old: Node,
15− new: Node,
25+ old: &Node,
26+ new: &Node,
1627 cached_roots: &mut FxHashSet<CacheId>,
1728) {
1829 match (&new.kind, &old.kind) {
@@ -20,22 +31,18 @@ pub(crate) fn diff(
2031 &NodeKind::Text(TextNode { text: new_text }),
2132 &NodeKind::Text(TextNode { text: old_text }),
2233 ) => {
23− debug!(" both are text nodes");
2434 if new_text != old_text {
25− debug!(" text needs updating");
2635 change_list.set_text(new_text);
2736 }
2837 }
2938
3039 (&NodeKind::Text(_), &NodeKind::Element(_)) => {
31− debug!(" replacing a text node with an element");
3240 create(cached_set, change_list, registry, new, cached_roots);
3341 registry.remove_subtree(&old);
3442 change_list.replace_with();
3543 }
3644
3745 (&NodeKind::Element(_), &NodeKind::Text(_)) => {
38− debug!(" replacing an element with a text node");
3946 create(cached_set, change_list, registry, new, cached_roots);
4047 // Note: text nodes cannot have event listeners, so we don't need to
4148 // remove the old node's listeners from our registry her.
@@ -44,6 +51,7 @@ pub(crate) fn diff(
4451
4552 (
4653 &NodeKind::Element(ElementNode {
54+ key: _,
4755 tag_name: new_tag_name,
4856 listeners: new_listeners,
4957 attributes: new_attributes,
@@ -51,6 +59,7 @@ pub(crate) fn diff(
5159 namespace: new_namespace,
5260 }),
5361 &NodeKind::Element(ElementNode {
62+ key: _,
5463 tag_name: old_tag_name,
5564 listeners: old_listeners,
5665 attributes: old_attributes,
@@ -58,9 +67,7 @@ pub(crate) fn diff(
5867 namespace: old_namespace,
5968 }),
6069 ) => {
61− debug!(" updating an element");
6270 if new_tag_name != old_tag_name || new_namespace != old_namespace {
63− debug!(" different tag names or namespaces; creating new element and replacing old element");
6471 create(cached_set, change_list, registry, new, cached_roots);
6572 registry.remove_subtree(&old);
6673 change_list.replace_with();
@@ -113,14 +120,19 @@ pub(crate) fn diff(
113120 }
114121}
115122
123+// Diff event listeners between `old` and `new`.
124+//
125+// The listeners' node must be on top of the change list stack:
126+//
127+// [... node]
128+//
129+// The change list stack is left unchanged.
116130fn diff_listeners(
117131 change_list: &mut ChangeListBuilder,
118132 registry: &mut EventsRegistry,
119133 old: &[Listener],
120134 new: &[Listener],
121135) {
122− debug!(" updating event listeners");
123−
124136 'outer1: for new_l in new {
125137 unsafe {
126138 // Safety relies on removing `new_l` from the registry manually at
@@ -151,9 +163,14 @@ fn diff_listeners(
151163 }
152164}
153165
166+// Diff a node's attributes.
167+//
168+// The attributes' node must be on top of the change list stack:
169+//
170+// [... node]
171+//
172+// The change list stack is left unchanged.
154173fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new: &[Attribute]) {
155− debug!(" updating attributes");
156−
157174 // Do O(n^2) passes to add/update and remove attributes, since
158175 // there are almost always very few attributes.
159176 'outer: for new_attr in new {
@@ -182,6 +199,14 @@ fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new:
182199 }
183200}
184201
202+// Diff the given set of old and new children.
203+//
204+// The parent must be on top of the change list stack when this function is
205+// entered:
206+//
207+// [... parent]
208+//
209+// the change list stack is in the same state when this function returns.
185210fn diff_children(
186211 cached_set: &CachedSet,
187212 change_list: &mut ChangeListBuilder,
@@ -190,83 +215,671 @@ fn diff_children(
190215 new: &[Node],
191216 cached_roots: &mut FxHashSet<CacheId>,
192217) {
193− debug!(" updating children shared by old and new");
218+ if new.is_empty() {
219+ if !old.is_empty() {
220+ remove_all_children(change_list, registry, old);
221+ }
222+ return;
223+ }
224+
225+ if old.is_empty() {
226+ create_and_append_children(cached_set, change_list, registry, new, cached_roots);
227+ return;
228+ }
229+
230+ let new_is_keyed = new[0].key().is_some();
231+ let old_is_keyed = old[0].key().is_some();
232+
233+ debug_assert!(
234+ new.iter().all(|n| n.key().is_some() == new_is_keyed),
235+ "all siblings must be keyed or all siblings must be non-keyed"
236+ );
237+ debug_assert!(
238+ old.iter().all(|o| o.key().is_some() == old_is_keyed),
239+ "all siblings must be keyed or all siblings must be non-keyed"
240+ );
241+
242+ if new_is_keyed && old_is_keyed {
243+ let t = change_list.next_temporary();
244+ diff_keyed_children(cached_set, change_list, registry, old, new, cached_roots);
245+ change_list.set_next_temporary(t);
246+ } else {
247+ diff_non_keyed_children(cached_set, change_list, registry, old, new, cached_roots);
248+ }
249+}
250+
251+// Diffing "keyed" children.
252+//
253+// With keyed children, we care about whether we delete, move, or create nodes
254+// versus mutate existing nodes in place. Presumably there is some sort of CSS
255+// transition animation that makes the virtual DOM diffing algorithm
256+// observable. By specifying keys for nodes, we know which virtual DOM nodes
257+// must reuse (or not reuse) the same physical DOM nodes.
258+//
259+// This is loosely based on Inferno's keyed patching implementation. However, we
260+// have to modify the algorithm since we are compiling the diff down into change
261+// list instructions that will be executed later, rather than applying the
262+// changes to the DOM directly as we compare virtual DOMs.
263+//
264+// https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
265+//
266+// When entering this function, the parent must be on top of the change list
267+// stack:
268+//
269+// [... parent]
270+//
271+// Upon exiting, the change list stack is in the same state.
272+fn diff_keyed_children(
273+ cached_set: &CachedSet,
274+ change_list: &mut ChangeListBuilder,
275+ registry: &mut EventsRegistry,
276+ old: &[Node],
277+ new: &[Node],
278+ cached_roots: &mut FxHashSet<CacheId>,
279+) {
280+ if cfg!(debug_assertions) {
281+ let mut keys = FxHashSet::default();
282+ let mut assert_unique_keys = |children: &[Node]| {
283+ keys.clear();
284+ for child in children {
285+ let key = child.key();
286+ debug_assert!(
287+ key.is_some(),
288+ "if any sibling is keyed, all siblings must be keyed"
289+ );
290+ keys.insert(key);
291+ }
292+ debug_assert_eq!(
293+ children.len(),
294+ keys.len(),
295+ "keyed siblings must each have a unique key"
296+ );
297+ };
298+ assert_unique_keys(old);
299+ assert_unique_keys(new);
300+ }
194301
195− let num_children_to_diff = cmp::min(new.len(), old.len());
196− let mut new_children = new.iter();
197− let mut old_children = old.iter();
302+ // First up, we diff all the nodes with the same key at the beginning of the
303+ // children.
304+ //
305+ // `shared_prefix_count` is the count of how many nodes at the start of
306+ // `new` and `old` share the same keys.
307+ let shared_prefix_count =
308+ match diff_keyed_prefix(cached_set, change_list, registry, old, new, cached_roots) {
309+ KeyedPrefixResult::Finished => return,
310+ KeyedPrefixResult::MoreWorkToDo(count) => count,
311+ };
312+
313+ // Next, we find out how many of the nodes at the end of the children have
314+ // the same key. We do _not_ diff them yet, since we want to emit the change
315+ // list instructions such that they can be applied in a single pass over the
316+ // DOM. Instead, we just save this information for later.
317+ //
318+ // `shared_suffix_count` is the count of how many nodes at the end of `new`
319+ // and `old` share the same keys.
320+ let shared_suffix_count = old[shared_prefix_count..]
321+ .iter()
322+ .rev()
323+ .zip(new[shared_prefix_count..].iter().rev())
324+ .take_while(|&(old, new)| old.key() == new.key())
325+ .count();
326+
327+ let old_shared_suffix_start = old.len() - shared_suffix_count;
328+ let new_shared_suffix_start = new.len() - shared_suffix_count;
329+
330+ // Ok, we now hopefully have a smaller range of children in the middle
331+ // within which to re-order nodes with the same keys, remove old nodes with
332+ // now-unused keys, and create new nodes with fresh keys.
333+ diff_keyed_middle(
334+ cached_set,
335+ change_list,
336+ registry,
337+ &old[shared_prefix_count..old_shared_suffix_start],
338+ &new[shared_prefix_count..new_shared_suffix_start],
339+ cached_roots,
340+ shared_prefix_count,
341+ shared_suffix_count,
342+ old_shared_suffix_start,
343+ );
344+
345+ // Finally, diff the nodes at the end of `old` and `new` that share keys.
346+ let old_suffix = &old[old_shared_suffix_start..];
347+ let new_suffix = &new[new_shared_suffix_start..];
348+ debug_assert_eq!(old_suffix.len(), new_suffix.len());
349+ if !old_suffix.is_empty() {
350+ diff_keyed_suffix(
351+ cached_set,
352+ change_list,
353+ registry,
354+ old_suffix,
355+ new_suffix,
356+ cached_roots,
357+ new_shared_suffix_start,
358+ );
359+ }
360+}
361+
362+enum KeyedPrefixResult {
363+ // Fast path: we finished diffing all the children just by looking at the
364+ // prefix of shared keys!
365+ Finished,
366+ // There is more diffing work to do. Here is a count of how many children at
367+ // the beginning of `new` and `old` we already processed.
368+ MoreWorkToDo(usize),
369+}
370+
371+// Diff the prefix of children in `new` and `old` that share the same keys in
372+// the same order.
373+//
374+// Upon entry of this function, the change list stack must be:
375+//
376+// [... parent]
377+//
378+// Upon exit, the change list stack is the same.
379+fn diff_keyed_prefix(
380+ cached_set: &CachedSet,
381+ change_list: &mut ChangeListBuilder,
382+ registry: &mut EventsRegistry,
383+ old: &[Node],
384+ new: &[Node],
385+ cached_roots: &mut FxHashSet<CacheId>,
386+) -> KeyedPrefixResult {
198387 let mut pushed = false;
388+ let mut shared_prefix_count = 0;
199389
200− for (i, (new_child, old_child)) in new_children
201− .by_ref()
202− .zip(old_children.by_ref())
203− .take(num_children_to_diff)
204− .enumerate()
205− {
206− if i == 0 {
390+ for (old, new) in old.iter().zip(new.iter()) {
391+ if old.key() != new.key() {
392+ break;
393+ }
394+
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);
207400 change_list.push_first_child();
208401 pushed = true;
209− } else {
210− debug_assert!(pushed);
211− change_list.pop_push_next_sibling();
212402 }
213403
214− diff(
404+ diff(cached_set, change_list, registry, old, new, cached_roots);
405+ 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);
411+ }
412+
413+ // If that was all of the old children, then create and append the remaining
414+ // new children and we're finished.
415+ 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+ create_and_append_children(
215424 cached_set,
216425 change_list,
217426 registry,
218− old_child.clone(),
219− new_child.clone(),
427+ &new[shared_prefix_count..],
220428 cached_roots,
221429 );
430+ return KeyedPrefixResult::Finished;
222431 }
223432
224− if old_children.next().is_some() {
225− debug!(" removing extra old children");
226− debug_assert!(new_children.next().is_none());
227− if pushed {
433+ // And if that was all of the new children, then remove all of the remaining
434+ // old children and we're finished.
435+ 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();
440+ return KeyedPrefixResult::Finished;
441+ }
442+
443+ if pushed {
444+ debug_assert!(shared_prefix_count > 0);
445+ change_list.pop();
446+ }
447+ KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
448+}
449+
450+// The most-general, expensive code path for keyed children diffing.
451+//
452+// We find the longest subsequence within `old` of children that are relatively
453+// ordered the same way in `new` (via finding a longest-increasing-subsequence
454+// of the old child's index within `new`). The children that are elements of
455+// this subsequence will remain in place, minimizing the number of DOM moves we
456+// will have to do.
457+//
458+// Upon entry to this function, the change list stack must be:
459+//
460+// [... parent]
461+//
462+// Upon exit from this function, it will be restored to that same state.
463+fn diff_keyed_middle(
464+ cached_set: &CachedSet,
465+ change_list: &mut ChangeListBuilder,
466+ registry: &mut EventsRegistry,
467+ old: &[Node],
468+ mut new: &[Node],
469+ cached_roots: &mut FxHashSet<CacheId>,
470+ shared_prefix_count: usize,
471+ shared_suffix_count: usize,
472+ old_shared_suffix_start: usize,
473+) {
474+ // Should have already diffed the shared-key prefixes and suffixes.
475+ debug_assert_ne!(new.first().map(|n| n.key()), old.first().map(|o| o.key()));
476+ debug_assert_ne!(new.last().map(|n| n.key()), old.last().map(|o| o.key()));
477+
478+ // The algorithm below relies upon using `u32::MAX` as a sentinel
479+ // value, so if we have that many new nodes, it won't work. This
480+ // check is a bit academic (hence only enabled in debug), since
481+ // wasm32 doesn't have enough address space to hold that many nodes
482+ // in memory.
483+ debug_assert!(new.len() < u32::MAX as usize);
484+
485+ // Map from each `old` node's key to its index within `old`.
486+ let mut old_key_to_old_index = FxHashMap::default();
487+ old_key_to_old_index.reserve(old.len());
488+ old_key_to_old_index.extend(old.iter().enumerate().map(|(i, o)| (o.key(), i)));
489+
490+ // The set of shared keys between `new` and `old`.
491+ let mut shared_keys = FxHashSet::default();
492+ // Map from each index in `new` to the index of the node in `old` that
493+ // has the same key.
494+ let mut new_index_to_old_index = Vec::with_capacity(new.len());
495+ new_index_to_old_index.extend(new.iter().map(|n| {
496+ let key = n.key();
497+ if let Some(&i) = old_key_to_old_index.get(&key) {
498+ shared_keys.insert(key);
499+ i
500+ } else {
501+ u32::MAX as usize
502+ }
503+ }));
504+
505+ // If none of the old keys are reused by the new children, then we
506+ // remove all the remaining old children and create the new children
507+ // afresh.
508+ if shared_suffix_count == 0 && shared_keys.is_empty() {
509+ if shared_prefix_count == 0 {
510+ remove_all_children(change_list, registry, old);
511+ } else {
228512 change_list.pop_push_next_sibling();
513+ remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
514+ }
515+ create_and_append_children(cached_set, change_list, registry, new, cached_roots);
516+ return;
517+ }
518+
519+ // Save each of the old children whose keys are reused in the new
520+ // children.
521+ let mut old_index_to_temp = vec![u32::MAX; old.len()];
522+ let mut start = 0;
523+ loop {
524+ let end = (start..old.len())
525+ .find(|&i| {
526+ let key = old[i].key();
527+ !shared_keys.contains(&key)
528+ })
529+ .unwrap_or(old.len());
530+
531+ if end - start > 0 {
532+ let mut t = change_list.save_children_to_temporaries(
533+ shared_prefix_count + start,
534+ shared_prefix_count + end,
535+ );
536+ for i in start..end {
537+ old_index_to_temp[i] = t;
538+ t += 1;
539+ }
540+ }
541+
542+ debug_assert!(end <= old.len());
543+ if end == old.len() {
544+ break;
229545 } else {
230− change_list.push_first_child();
546+ start = end + 1;
231547 }
232− change_list.remove_self_and_next_siblings();
233− pushed = false;
548+ }
549+
550+ // Remove any old children whose keys were not reused in the new
551+ // children. Remove from the end first so that we don't mess up indices.
552+ let mut removed_count = 0;
553+ for (i, old_child) in old.iter().enumerate().rev() {
554+ if !shared_keys.contains(&old_child.key()) {
555+ registry.remove_subtree(old_child);
556+ change_list.remove_child(i + shared_prefix_count);
557+ removed_count += 1;
558+ }
559+ }
560+
561+ // If there aren't any more new children, then we are done!
562+ if new.is_empty() {
563+ return;
564+ }
565+
566+ // The longest increasing subsequence within `new_index_to_old_index`. This
567+ // is the longest sequence on DOM nodes in `old` that are relatively ordered
568+ // correctly within `new`. We will leave these nodes in place in the DOM,
569+ // and only move nodes that are not part of the LIS. This results in the
570+ // maximum number of DOM nodes left in place, AKA the minimum number of DOM
571+ // nodes moved.
572+ let mut new_index_is_in_lis = FxHashSet::default();
573+ new_index_is_in_lis.reserve(new_index_to_old_index.len());
574+ let mut predecessors = vec![0; new_index_to_old_index.len()];
575+ let mut starts = vec![0; new_index_to_old_index.len()];
576+ longest_increasing_subsequence::lis_with(
577+ &new_index_to_old_index,
578+ &mut new_index_is_in_lis,
579+ |a, b| a < b,
580+ &mut predecessors,
581+ &mut starts,
582+ );
583+
584+ // Now we will iterate from the end of the new children back to the
585+ // beginning, diffing old children we are reusing and if they aren't in the
586+ // LIS moving them to their new destination, or creating new children. Note
587+ // that iterating in reverse order lets us use `Node.prototype.insertBefore`
588+ // to move/insert children.
589+ //
590+ // But first, we ensure that we have a child on the change list stack that
591+ // we can `insertBefore`. We handle this once before looping over `new`
592+ // children, so that we don't have to keep checking on every loop iteration.
593+ if shared_suffix_count > 0 {
594+ // There is a shared suffix after these middle children. We will be
595+ // inserting before that shared suffix, so add the first child of that
596+ // shared suffix to the change list stack.
597+ //
598+ // [... parent]
599+ change_list.push_child(old_shared_suffix_start - removed_count);
600+ // [... parent first_child_of_shared_suffix]
234601 } else {
235− debug!(" creating new children");
236− for (i, new_child) in new_children.enumerate() {
237− if i == 0 && pushed {
602+ // There is no shared suffix coming after these middle children.
603+ // Therefore we have to process the last child in `new` and move it to
604+ // the end of the parent's children if it isn't already there.
605+ let last_index = new.len() - 1;
606+ let last = new.last().unwrap_throw();
607+ new = &new[..new.len() - 1];
608+ if shared_keys.contains(&last.key()) {
609+ let old_index = new_index_to_old_index[last_index];
610+ let temp = old_index_to_temp[old_index];
611+ // [... parent]
612+ change_list.push_temporary(temp);
613+ // [... parent last]
614+ diff(
615+ cached_set,
616+ change_list,
617+ registry,
618+ &old[old_index],
619+ last,
620+ cached_roots,
621+ );
622+ if new_index_is_in_lis.contains(&last_index) {
623+ // Don't move it, since it is already where it needs to be.
624+ } else {
625+ change_list.append_child();
626+ // [... parent]
627+ change_list.push_temporary(temp);
628+ // [... parent last]
629+ }
630+ } else {
631+ // [... parent]
632+ create(cached_set, change_list, registry, last, cached_roots);
633+ // [... parent last]
634+ change_list.append_child();
635+ // [... parent]
636+ change_list.push_last_child();
637+ // [... parent last]
638+ }
639+ }
640+
641+ for (new_index, new_child) in new.iter().enumerate().rev() {
642+ let old_index = new_index_to_old_index[new_index];
643+ if old_index == u32::MAX as usize {
644+ debug_assert!(!shared_keys.contains(&new_child.key()));
645+ // [... parent successor]
646+ create(cached_set, change_list, registry, new_child, cached_roots);
647+ // [... parent successor new_child]
648+ change_list.insert_before();
649+ // [... parent new_child]
650+ } else {
651+ debug_assert!(shared_keys.contains(&new_child.key()));
652+ let temp = old_index_to_temp[old_index];
653+ debug_assert_ne!(temp, u32::MAX);
654+
655+ if new_index_is_in_lis.contains(&new_index) {
656+ // [... parent successor]
238657 change_list.pop();
239− pushed = false;
658+ // [... parent]
659+ change_list.push_temporary(temp);
660+ // [... parent new_child]
661+ } else {
662+ // [... parent successor]
663+ change_list.push_temporary(temp);
664+ // [... parent successor new_child]
665+ change_list.insert_before();
666+ // [... parent new_child]
240667 }
241− create(
668+
669+ diff(
242670 cached_set,
243671 change_list,
244672 registry,
245− new_child.clone(),
673+ &old[old_index],
674+ new_child,
246675 cached_roots,
247676 );
248− change_list.append_child();
249677 }
250678 }
251679
252− debug!(" done updating children");
253− if pushed {
254− change_list.pop();
680+ // [... parent child]
681+ change_list.pop();
682+ // [... parent]
683+}
684+
685+// Diff the suffix of keyed children that share the same keys in the same order.
686+//
687+// The parent must be on the change list stack when we enter this function:
688+//
689+// [... parent]
690+//
691+// When this function exits, the change list stack remains the same.
692+fn diff_keyed_suffix(
693+ cached_set: &CachedSet,
694+ change_list: &mut ChangeListBuilder,
695+ registry: &mut EventsRegistry,
696+ old: &[Node],
697+ new: &[Node],
698+ cached_roots: &mut FxHashSet<CacheId>,
699+ new_shared_suffix_start: usize,
700+) {
701+ debug_assert_eq!(old.len(), new.len());
702+ debug_assert!(!old.is_empty());
703+
704+ // [... parent]
705+ change_list.push_child(new_shared_suffix_start);
706+ // [... parent new_child]
707+
708+ for (old_child, new_child) in old.iter().zip(new.iter()) {
709+ diff(
710+ cached_set,
711+ change_list,
712+ registry,
713+ old_child,
714+ new_child,
715+ cached_roots,
716+ );
717+
718+ // [... parent this_new_child]
719+ change_list.pop_push_next_sibling();
720+ // [... parent next_new_child]
721+ }
722+
723+ // [... parent]
724+ change_list.pop();
725+}
726+
727+// Diff children that are not keyed.
728+//
729+// The parent must be on the top of the change list stack when entering this
730+// function:
731+//
732+// [... parent]
733+//
734+// the change list stack is in the same state when this function returns.
735+fn diff_non_keyed_children(
736+ cached_set: &CachedSet,
737+ change_list: &mut ChangeListBuilder,
738+ registry: &mut EventsRegistry,
739+ old: &[Node],
740+ new: &[Node],
741+ cached_roots: &mut FxHashSet<CacheId>,
742+) {
743+ // Handled these cases in `diff_children` before calling this function.
744+ debug_assert!(!new.is_empty());
745+ debug_assert!(!old.is_empty());
746+
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+ }
757+
758+ diff(
759+ cached_set,
760+ change_list,
761+ registry,
762+ old_child,
763+ new_child,
764+ cached_roots,
765+ );
766+ }
767+
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+
773+ match old.len().cmp(&new.len()) {
774+ Ordering::Greater => {
775+ // [... parent last_shared_child]
776+ change_list.pop_push_next_sibling();
777+ // [... parent first_child_to_remove]
778+ remove_self_and_next_siblings(change_list, registry, &old[new.len()..]);
779+ // [... parent]
780+ }
781+ Ordering::Less => {
782+ // [... parent last_child]
783+ change_list.pop();
784+ // [... parent]
785+ create_and_append_children(
786+ cached_set,
787+ change_list,
788+ registry,
789+ &new[old.len()..],
790+ cached_roots,
791+ );
792+ }
793+ Ordering::Equal => {
794+ // [... parent child]
795+ change_list.pop();
796+ // [... parent]
797+ }
255798 }
256799}
257800
801+// Create the given children and append them to the parent node.
802+//
803+// The parent node must currently be on top of the change list stack:
804+//
805+// [... parent]
806+//
807+// When this function returns, the change list stack is in the same state.
808+fn create_and_append_children(
809+ cached_set: &CachedSet,
810+ change_list: &mut ChangeListBuilder,
811+ registry: &mut EventsRegistry,
812+ new: &[Node],
813+ cached_roots: &mut FxHashSet<CacheId>,
814+) {
815+ for child in new {
816+ create(cached_set, change_list, registry, child, cached_roots);
817+ change_list.append_child();
818+ }
819+}
820+
821+// Remove all of a node's children.
822+//
823+// The change list stack must have this shape upon entry to this function:
824+//
825+// [... parent]
826+//
827+// When this function returns, the change list stack is in the same state.
828+fn remove_all_children(
829+ change_list: &mut ChangeListBuilder,
830+ registry: &mut EventsRegistry,
831+ old: &[Node],
832+) {
833+ for child in old {
834+ registry.remove_subtree(child);
835+ }
836+ // Fast way to remove all children: set the node's textContent to an empty
837+ // string.
838+ change_list.set_text("");
839+}
840+
841+// Remove the current child and all of its following siblings.
842+//
843+// The change list stack must have this shape upon entry to this function:
844+//
845+// [... parent child]
846+//
847+// After the function returns, the child is no longer on the change list stack:
848+//
849+// [... parent]
850+fn remove_self_and_next_siblings(
851+ change_list: &mut ChangeListBuilder,
852+ registry: &mut EventsRegistry,
853+ old: &[Node],
854+) {
855+ for child in old {
856+ registry.remove_subtree(child);
857+ }
858+ change_list.remove_self_and_next_siblings();
859+}
860+
861+// Emit instructions to create the given virtual node.
862+//
863+// The change list stack may have any shape upon entering this function:
864+//
865+// [...]
866+//
867+// When this function returns, the new node is on top of the change list stack:
868+//
869+// [... node]
258870fn create(
259871 cached_set: &CachedSet,
260872 change_list: &mut ChangeListBuilder,
261873 registry: &mut EventsRegistry,
262− node: Node,
874+ node: &Node,
263875 cached_roots: &mut FxHashSet<CacheId>,
264876) {
265877 match node.kind {
266878 NodeKind::Text(TextNode { text }) => {
267879 change_list.create_text_node(text);
268880 }
269− NodeKind::Element(ElementNode {
881+ NodeKind::Element(&ElementNode {
882+ key: _,
270883 tag_name,
271884 listeners,
272885 attributes,
@@ -288,17 +901,11 @@ fn create(
288901 change_list.set_attribute(&attr.name, &attr.value);
289902 }
290903 for child in children {
291− create(
292− cached_set,
293− change_list,
294− registry,
295− child.clone(),
296− cached_roots,
297− );
904+ create(cached_set, change_list, registry, child, cached_roots);
298905 change_list.append_child();
299906 }
300907 }
301− NodeKind::Cached(c) => {
908+ NodeKind::Cached(ref c) => {
302909 cached_roots.insert(c.id);
303910 let node = cached_set.get(c.id);
304911 create(cached_set, change_list, registry, node, cached_roots)