@@ -0,0 +1,1125 @@
2+ cached_set::{CacheId, CachedSet},
3+ change_list::ChangeListBuilder,
4+ events::EventsRegistry,
5+ node::{Attribute, ElementNode, Listener, Node, NodeKind, TextNode},
7+use fxhash::{FxHashMap, FxHashSet};
8+use std::cmp::Ordering;
10+use wasm_bindgen::UnwrapThrowExt;
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`.
15+// Upon entry to this function, the physical DOM node must be on the top of the
20+// The change list stack is in the same state when this function exits.
22+ cached_set: &CachedSet,
23+ change_list: &mut ChangeListBuilder,
24+ registry: &mut EventsRegistry,
27+ cached_roots: &mut FxHashSet<CacheId>,
29+ match (&new.kind, &old.kind) {
31+ &NodeKind::Text(TextNode { text: new_text }),
32+ &NodeKind::Text(TextNode { text: old_text }),
34+ if new_text != old_text {
35+ change_list.commit_traversal();
36+ change_list.set_text(new_text);
40+ (&NodeKind::Text(_), &NodeKind::Element(_)) => {
41+ change_list.commit_traversal();
42+ create(cached_set, change_list, registry, new, cached_roots);
43+ registry.remove_subtree(&old);
44+ change_list.replace_with();
47+ (&NodeKind::Element(_), &NodeKind::Text(_)) => {
48+ change_list.commit_traversal();
49+ create(cached_set, change_list, registry, new, cached_roots);
50+ // Note: text nodes cannot have event listeners, so we don't need to
51+ // remove the old node's listeners from our registry her.
52+ change_list.replace_with();
56+ &NodeKind::Element(ElementNode {
58+ tag_name: new_tag_name,
59+ listeners: new_listeners,
60+ attributes: new_attributes,
61+ children: new_children,
62+ namespace: new_namespace,
64+ &NodeKind::Element(ElementNode {
66+ tag_name: old_tag_name,
67+ listeners: old_listeners,
68+ attributes: old_attributes,
69+ children: old_children,
70+ namespace: old_namespace,
73+ if new_tag_name != old_tag_name || new_namespace != old_namespace {
74+ change_list.commit_traversal();
75+ create(cached_set, change_list, registry, new, cached_roots);
76+ registry.remove_subtree(&old);
77+ change_list.replace_with();
80+ diff_listeners(change_list, registry, old_listeners, new_listeners);
81+ diff_attributes(change_list, old_attributes, new_attributes, new_namespace.is_some());
92+ // Both the new and old nodes are cached.
93+ (&NodeKind::Cached(ref new), &NodeKind::Cached(ref old)) => {
94+ cached_roots.insert(new.id);
96+ if new.id == old.id {
97+ // This is the same cached node, so nothing has changed!
101+ let (new, new_template) = cached_set.get(new.id);
102+ let (old, old_template) = cached_set.get(old.id);
103+ if new_template == old_template {
104+ // If they are both using the same template, then just diff the
106+ diff(cached_set, change_list, registry, old, new, cached_roots);
108+ // Otherwise, they are probably different enough that
109+ // re-constructing the subtree from scratch should be faster.
110+ // This doubly holds true if we have a new template.
111+ change_list.commit_traversal();
124+ // New cached node when the old node was not cached. In this scenario,
125+ // we assume that they are pretty different, and it isn't worth diffing
126+ // the subtrees, so we just create the new cached node afresh.
127+ (&NodeKind::Cached(ref c), _) => {
128+ change_list.commit_traversal();
129+ cached_roots.insert(c.id);
130+ let (new, new_template) = cached_set.get(c.id);
142+ // Old cached node and new non-cached node. Again, assume that they are
143+ // probably pretty different and create the new non-cached node afresh.
144+ (_, &NodeKind::Cached(_)) => {
145+ change_list.commit_traversal();
146+ create(cached_set, change_list, registry, new, cached_roots);
147+ registry.remove_subtree(&old);
148+ change_list.replace_with();
153+// Diff event listeners between `old` and `new`.
155+// The listeners' node must be on top of the change list stack:
159+// The change list stack is left unchanged.
161+ change_list: &mut ChangeListBuilder,
162+ registry: &mut EventsRegistry,
166+ if !old.is_empty() || !new.is_empty() {
167+ change_list.commit_traversal();
170+ 'outer1: for new_l in new {
172+ // Safety relies on removing `new_l` from the registry manually at
173+ // the end of its lifetime. This happens below in the `'outer2`
174+ // loop, and elsewhere in diffing when removing old dom trees.
175+ registry.add(new_l);
179+ if new_l.event == old_l.event {
180+ change_list.update_event_listener(new_l);
185+ change_list.new_event_listener(new_l);
188+ 'outer2: for old_l in old {
189+ registry.remove(old_l);
192+ if new_l.event == old_l.event {
196+ change_list.remove_event_listener(old_l.event);
200+// Diff a node's attributes.
202+// The attributes' node must be on top of the change list stack:
206+// The change list stack is left unchanged.
207+fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new: &[Attribute], is_namespaced: bool) {
208+ // Do O(n^2) passes to add/update and remove attributes, since
209+ // there are almost always very few attributes.
210+ 'outer: for new_attr in new {
211+ if new_attr.is_volatile() {
212+ change_list.commit_traversal();
213+ change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
215+ for old_attr in old {
216+ if old_attr.name == new_attr.name {
217+ if old_attr.value != new_attr.value {
218+ change_list.commit_traversal();
219+ change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
225+ change_list.commit_traversal();
226+ change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
230+ 'outer2: for old_attr in old {
231+ for new_attr in new {
232+ if old_attr.name == new_attr.name {
237+ change_list.commit_traversal();
238+ change_list.remove_attribute(old_attr.name);
242+// Diff the given set of old and new children.
244+// The parent must be on top of the change list stack when this function is
249+// the change list stack is in the same state when this function returns.
251+ cached_set: &CachedSet,
252+ change_list: &mut ChangeListBuilder,
253+ registry: &mut EventsRegistry,
256+ cached_roots: &mut FxHashSet<CacheId>,
259+ if !old.is_empty() {
260+ change_list.commit_traversal();
261+ remove_all_children(change_list, registry, old);
267+ match (old.first(), &new[0]) {
270+ kind: NodeKind::Text(TextNode { text: old_text }),
273+ kind: NodeKind::Text(TextNode { text: new_text }),
275+ ) if old_text == new_text => {
276+ // Don't take this fast path...
281+ kind: NodeKind::Text(TextNode { text }),
284+ change_list.commit_traversal();
285+ change_list.set_text(text);
287+ registry.remove_subtree(o);
296+ if !new.is_empty() {
297+ change_list.commit_traversal();
298+ create_and_append_children(cached_set, change_list, registry, new, cached_roots);
303+ let new_is_keyed = new[0].key().is_some();
304+ let old_is_keyed = old[0].key().is_some();
307+ new.iter().all(|n| n.key().is_some() == new_is_keyed),
308+ "all siblings must be keyed or all siblings must be non-keyed"
311+ old.iter().all(|o| o.key().is_some() == old_is_keyed),
312+ "all siblings must be keyed or all siblings must be non-keyed"
315+ if new_is_keyed && old_is_keyed {
316+ let t = change_list.next_temporary();
317+ diff_keyed_children(cached_set, change_list, registry, old, new, cached_roots);
318+ change_list.set_next_temporary(t);
320+ diff_non_keyed_children(cached_set, change_list, registry, old, new, cached_roots);
324+// Diffing "keyed" children.
326+// With keyed children, we care about whether we delete, move, or create nodes
327+// versus mutate existing nodes in place. Presumably there is some sort of CSS
328+// transition animation that makes the virtual DOM diffing algorithm
329+// observable. By specifying keys for nodes, we know which virtual DOM nodes
330+// must reuse (or not reuse) the same physical DOM nodes.
332+// This is loosely based on Inferno's keyed patching implementation. However, we
333+// have to modify the algorithm since we are compiling the diff down into change
334+// list instructions that will be executed later, rather than applying the
335+// changes to the DOM directly as we compare virtual DOMs.
337+// https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
339+// When entering this function, the parent must be on top of the change list
344+// Upon exiting, the change list stack is in the same state.
345+fn diff_keyed_children(
346+ cached_set: &CachedSet,
347+ change_list: &mut ChangeListBuilder,
348+ registry: &mut EventsRegistry,
351+ cached_roots: &mut FxHashSet<CacheId>,
353+ if cfg!(debug_assertions) {
354+ let mut keys = FxHashSet::default();
355+ let mut assert_unique_keys = |children: &[Node]| {
357+ for child in children {
358+ let key = child.key();
361+ "if any sibling is keyed, all siblings must be keyed"
368+ "keyed siblings must each have a unique key"
371+ assert_unique_keys(old);
372+ assert_unique_keys(new);
375+ // First up, we diff all the nodes with the same key at the beginning of the
378+ // `shared_prefix_count` is the count of how many nodes at the start of
379+ // `new` and `old` share the same keys.
380+ let shared_prefix_count =
381+ match diff_keyed_prefix(cached_set, change_list, registry, old, new, cached_roots) {
382+ KeyedPrefixResult::Finished => return,
383+ KeyedPrefixResult::MoreWorkToDo(count) => count,
386+ // Next, we find out how many of the nodes at the end of the children have
387+ // the same key. We do _not_ diff them yet, since we want to emit the change
388+ // list instructions such that they can be applied in a single pass over the
389+ // DOM. Instead, we just save this information for later.
391+ // `shared_suffix_count` is the count of how many nodes at the end of `new`
392+ // and `old` share the same keys.
393+ let shared_suffix_count = old[shared_prefix_count..]
396+ .zip(new[shared_prefix_count..].iter().rev())
397+ .take_while(|&(old, new)| old.key() == new.key())
400+ let old_shared_suffix_start = old.len() - shared_suffix_count;
401+ let new_shared_suffix_start = new.len() - shared_suffix_count;
403+ // Ok, we now hopefully have a smaller range of children in the middle
404+ // within which to re-order nodes with the same keys, remove old nodes with
405+ // now-unused keys, and create new nodes with fresh keys.
410+ &old[shared_prefix_count..old_shared_suffix_start],
411+ &new[shared_prefix_count..new_shared_suffix_start],
413+ shared_prefix_count,
414+ shared_suffix_count,
415+ old_shared_suffix_start,
418+ // Finally, diff the nodes at the end of `old` and `new` that share keys.
419+ let old_suffix = &old[old_shared_suffix_start..];
420+ let new_suffix = &new[new_shared_suffix_start..];
421+ debug_assert_eq!(old_suffix.len(), new_suffix.len());
422+ if !old_suffix.is_empty() {
430+ new_shared_suffix_start,
435+enum KeyedPrefixResult {
436+ // Fast path: we finished diffing all the children just by looking at the
437+ // prefix of shared keys!
439+ // There is more diffing work to do. Here is a count of how many children at
440+ // the beginning of `new` and `old` we already processed.
441+ MoreWorkToDo(usize),
444+// Diff the prefix of children in `new` and `old` that share the same keys in
447+// Upon entry of this function, the change list stack must be:
451+// Upon exit, the change list stack is the same.
452+fn diff_keyed_prefix(
453+ cached_set: &CachedSet,
454+ change_list: &mut ChangeListBuilder,
455+ registry: &mut EventsRegistry,
458+ cached_roots: &mut FxHashSet<CacheId>,
459+) -> KeyedPrefixResult {
460+ change_list.go_down();
461+ let mut shared_prefix_count = 0;
463+ for (i, (old, new)) in old.iter().zip(new.iter()).enumerate() {
464+ if old.key() != new.key() {
468+ change_list.go_to_sibling(i);
469+ diff(cached_set, change_list, registry, old, new, cached_roots);
470+ shared_prefix_count += 1;
473+ // If that was all of the old children, then create and append the remaining
474+ // new children and we're finished.
475+ if shared_prefix_count == old.len() {
476+ change_list.go_up();
477+ change_list.commit_traversal();
478+ create_and_append_children(
482+ &new[shared_prefix_count..],
485+ return KeyedPrefixResult::Finished;
488+ // And if that was all of the new children, then remove all of the remaining
489+ // old children and we're finished.
490+ if shared_prefix_count == new.len() {
491+ change_list.go_to_sibling(shared_prefix_count);
492+ change_list.commit_traversal();
493+ remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
494+ return KeyedPrefixResult::Finished;
497+ change_list.go_up();
498+ KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
501+// The most-general, expensive code path for keyed children diffing.
503+// We find the longest subsequence within `old` of children that are relatively
504+// ordered the same way in `new` (via finding a longest-increasing-subsequence
505+// of the old child's index within `new`). The children that are elements of
506+// this subsequence will remain in place, minimizing the number of DOM moves we
509+// Upon entry to this function, the change list stack must be:
513+// Upon exit from this function, it will be restored to that same state.
514+fn diff_keyed_middle(
515+ cached_set: &CachedSet,
516+ change_list: &mut ChangeListBuilder,
517+ registry: &mut EventsRegistry,
520+ cached_roots: &mut FxHashSet<CacheId>,
521+ shared_prefix_count: usize,
522+ shared_suffix_count: usize,
523+ old_shared_suffix_start: usize,
525+ // Should have already diffed the shared-key prefixes and suffixes.
526+ debug_assert_ne!(new.first().map(|n| n.key()), old.first().map(|o| o.key()));
527+ debug_assert_ne!(new.last().map(|n| n.key()), old.last().map(|o| o.key()));
529+ // The algorithm below relies upon using `u32::MAX` as a sentinel
530+ // value, so if we have that many new nodes, it won't work. This
531+ // check is a bit academic (hence only enabled in debug), since
532+ // wasm32 doesn't have enough address space to hold that many nodes
534+ debug_assert!(new.len() < u32::MAX as usize);
536+ // Map from each `old` node's key to its index within `old`.
537+ let mut old_key_to_old_index = FxHashMap::default();
538+ old_key_to_old_index.reserve(old.len());
539+ old_key_to_old_index.extend(old.iter().enumerate().map(|(i, o)| (o.key(), i)));
541+ // The set of shared keys between `new` and `old`.
542+ let mut shared_keys = FxHashSet::default();
543+ // Map from each index in `new` to the index of the node in `old` that
544+ // has the same key.
545+ let mut new_index_to_old_index = Vec::with_capacity(new.len());
546+ new_index_to_old_index.extend(new.iter().map(|n| {
548+ if let Some(&i) = old_key_to_old_index.get(&key) {
549+ shared_keys.insert(key);
556+ // If none of the old keys are reused by the new children, then we
557+ // remove all the remaining old children and create the new children
559+ if shared_suffix_count == 0 && shared_keys.is_empty() {
560+ if shared_prefix_count == 0 {
561+ change_list.commit_traversal();
562+ remove_all_children(change_list, registry, old);
564+ change_list.go_down_to_child(shared_prefix_count);
565+ change_list.commit_traversal();
566+ remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
568+ create_and_append_children(cached_set, change_list, registry, new, cached_roots);
572+ // Save each of the old children whose keys are reused in the new
574+ let mut old_index_to_temp = vec![u32::MAX; old.len()];
577+ let end = (start..old.len())
579+ let key = old[i].key();
580+ !shared_keys.contains(&key)
582+ .unwrap_or(old.len());
584+ if end - start > 0 {
585+ change_list.commit_traversal();
586+ let mut t = change_list.save_children_to_temporaries(
587+ shared_prefix_count + start,
588+ shared_prefix_count + end,
590+ for i in start..end {
591+ old_index_to_temp[i] = t;
596+ debug_assert!(end <= old.len());
597+ if end == old.len() {
604+ // Remove any old children whose keys were not reused in the new
605+ // children. Remove from the end first so that we don't mess up indices.
606+ let mut removed_count = 0;
607+ for (i, old_child) in old.iter().enumerate().rev() {
608+ if !shared_keys.contains(&old_child.key()) {
609+ registry.remove_subtree(old_child);
610+ change_list.commit_traversal();
611+ change_list.remove_child(i + shared_prefix_count);
616+ // If there aren't any more new children, then we are done!
621+ // The longest increasing subsequence within `new_index_to_old_index`. This
622+ // is the longest sequence on DOM nodes in `old` that are relatively ordered
623+ // correctly within `new`. We will leave these nodes in place in the DOM,
624+ // and only move nodes that are not part of the LIS. This results in the
625+ // maximum number of DOM nodes left in place, AKA the minimum number of DOM
627+ let mut new_index_is_in_lis = FxHashSet::default();
628+ new_index_is_in_lis.reserve(new_index_to_old_index.len());
629+ let mut predecessors = vec![0; new_index_to_old_index.len()];
630+ let mut starts = vec![0; new_index_to_old_index.len()];
631+ longest_increasing_subsequence::lis_with(
632+ &new_index_to_old_index,
633+ &mut new_index_is_in_lis,
639+ // Now we will iterate from the end of the new children back to the
640+ // beginning, diffing old children we are reusing and if they aren't in the
641+ // LIS moving them to their new destination, or creating new children. Note
642+ // that iterating in reverse order lets us use `Node.prototype.insertBefore`
643+ // to move/insert children.
645+ // But first, we ensure that we have a child on the change list stack that
646+ // we can `insertBefore`. We handle this once before looping over `new`
647+ // children, so that we don't have to keep checking on every loop iteration.
648+ if shared_suffix_count > 0 {
649+ // There is a shared suffix after these middle children. We will be
650+ // inserting before that shared suffix, so add the first child of that
651+ // shared suffix to the change list stack.
654+ change_list.go_down_to_child(old_shared_suffix_start - removed_count);
655+ // [... parent first_child_of_shared_suffix]
657+ // There is no shared suffix coming after these middle children.
658+ // Therefore we have to process the last child in `new` and move it to
659+ // the end of the parent's children if it isn't already there.
660+ let last_index = new.len() - 1;
661+ let last = new.last().unwrap_throw();
662+ new = &new[..new.len() - 1];
663+ if shared_keys.contains(&last.key()) {
664+ let old_index = new_index_to_old_index[last_index];
665+ let temp = old_index_to_temp[old_index];
667+ change_list.go_down_to_temp_child(temp);
668+ // [... parent last]
677+ if new_index_is_in_lis.contains(&last_index) {
678+ // Don't move it, since it is already where it needs to be.
680+ change_list.commit_traversal();
681+ // [... parent last]
682+ change_list.append_child();
684+ change_list.go_down_to_temp_child(temp);
685+ // [... parent last]
688+ change_list.commit_traversal();
690+ create(cached_set, change_list, registry, last, cached_roots);
691+ // [... parent last]
692+ change_list.append_child();
694+ change_list.go_down_to_reverse_child(0);
695+ // [... parent last]
699+ for (new_index, new_child) in new.iter().enumerate().rev() {
700+ let old_index = new_index_to_old_index[new_index];
701+ if old_index == u32::MAX as usize {
702+ debug_assert!(!shared_keys.contains(&new_child.key()));
703+ change_list.commit_traversal();
704+ // [... parent successor]
705+ create(cached_set, change_list, registry, new_child, cached_roots);
706+ // [... parent successor new_child]
707+ change_list.insert_before();
708+ // [... parent new_child]
710+ debug_assert!(shared_keys.contains(&new_child.key()));
711+ let temp = old_index_to_temp[old_index];
712+ debug_assert_ne!(temp, u32::MAX);
714+ if new_index_is_in_lis.contains(&new_index) {
715+ // [... parent successor]
716+ change_list.go_to_temp_sibling(temp);
717+ // [... parent new_child]
719+ change_list.commit_traversal();
720+ // [... parent successor]
721+ change_list.push_temporary(temp);
722+ // [... parent successor new_child]
723+ change_list.insert_before();
724+ // [... parent new_child]
738+ // [... parent child]
739+ change_list.go_up();
743+// Diff the suffix of keyed children that share the same keys in the same order.
745+// The parent must be on the change list stack when we enter this function:
749+// When this function exits, the change list stack remains the same.
750+fn diff_keyed_suffix(
751+ cached_set: &CachedSet,
752+ change_list: &mut ChangeListBuilder,
753+ registry: &mut EventsRegistry,
756+ cached_roots: &mut FxHashSet<CacheId>,
757+ new_shared_suffix_start: usize,
759+ debug_assert_eq!(old.len(), new.len());
760+ debug_assert!(!old.is_empty());
763+ change_list.go_down();
764+ // [... parent new_child]
766+ for (i, (old_child, new_child)) in old.iter().zip(new.iter()).enumerate() {
767+ change_list.go_to_sibling(new_shared_suffix_start + i);
779+ change_list.go_up();
782+// Diff children that are not keyed.
784+// The parent must be on the top of the change list stack when entering this
789+// the change list stack is in the same state when this function returns.
790+fn diff_non_keyed_children(
791+ cached_set: &CachedSet,
792+ change_list: &mut ChangeListBuilder,
793+ registry: &mut EventsRegistry,
796+ cached_roots: &mut FxHashSet<CacheId>,
798+ // Handled these cases in `diff_children` before calling this function.
799+ debug_assert!(!new.is_empty());
800+ debug_assert!(!old.is_empty());
803+ change_list.go_down();
804+ // [... parent child]
806+ for (i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
807+ // [... parent prev_child]
808+ change_list.go_to_sibling(i);
809+ // [... parent this_child]
820+ match old.len().cmp(&new.len()) {
821+ Ordering::Greater => {
822+ // [... parent prev_child]
823+ change_list.go_to_sibling(new.len());
824+ // [... parent first_child_to_remove]
825+ change_list.commit_traversal();
826+ remove_self_and_next_siblings(change_list, registry, &old[new.len()..]);
830+ // [... parent last_child]
831+ change_list.go_up();
833+ change_list.commit_traversal();
834+ create_and_append_children(
842+ Ordering::Equal => {
843+ // [... parent child]
844+ change_list.go_up();
850+// Create the given children and append them to the parent node.
852+// The parent node must currently be on top of the change list stack:
856+// When this function returns, the change list stack is in the same state.
857+fn create_and_append_children(
858+ cached_set: &CachedSet,
859+ change_list: &mut ChangeListBuilder,
860+ registry: &mut EventsRegistry,
862+ cached_roots: &mut FxHashSet<CacheId>,
864+ debug_assert!(change_list.traversal_is_committed());
866+ create(cached_set, change_list, registry, child, cached_roots);
867+ change_list.append_child();
871+// Remove all of a node's children.
873+// The change list stack must have this shape upon entry to this function:
877+// When this function returns, the change list stack is in the same state.
878+fn remove_all_children(
879+ change_list: &mut ChangeListBuilder,
880+ registry: &mut EventsRegistry,
883+ debug_assert!(change_list.traversal_is_committed());
885+ registry.remove_subtree(child);
887+ // Fast way to remove all children: set the node's textContent to an empty
889+ change_list.set_text("");
892+// Remove the current child and all of its following siblings.
894+// The change list stack must have this shape upon entry to this function:
896+// [... parent child]
898+// After the function returns, the child is no longer on the change list stack:
901+fn remove_self_and_next_siblings(
902+ change_list: &mut ChangeListBuilder,
903+ registry: &mut EventsRegistry,
906+ debug_assert!(change_list.traversal_is_committed());
908+ registry.remove_subtree(child);
910+ change_list.remove_self_and_next_siblings();
913+// Emit instructions to create the given virtual node.
915+// The change list stack may have any shape upon entering this function:
919+// When this function returns, the new node is on top of the change list stack:
923+ cached_set: &CachedSet,
924+ change_list: &mut ChangeListBuilder,
925+ registry: &mut EventsRegistry,
927+ cached_roots: &mut FxHashSet<CacheId>,
929+ debug_assert!(change_list.traversal_is_committed());
931+ NodeKind::Text(TextNode { text }) => {
932+ change_list.create_text_node(text);
934+ NodeKind::Element(&ElementNode {
942+ if let Some(namespace) = namespace {
943+ change_list.create_element_ns(tag_name, namespace);
945+ change_list.create_element(tag_name);
948+ for l in listeners {
952+ change_list.new_event_listener(l);
955+ for attr in attributes {
956+ change_list.set_attribute(&attr.name, &attr.value, namespace.is_some());
959+ // Fast path: if there is a single text child, it is faster to
960+ // create-and-append the text node all at once via setting the
961+ // parent's `textContent` in a single change list instruction than
962+ // to emit three instructions to (1) create a text node, (2) set its
963+ // text content, and finally (3) append the text node to this
965+ if children.len() == 1 {
967+ kind: NodeKind::Text(TextNode { text }),
970+ change_list.set_text(text);
975+ for child in children {
976+ create(cached_set, change_list, registry, child, cached_roots);
977+ change_list.append_child();
980+ NodeKind::Cached(ref c) => {
981+ cached_roots.insert(c.id);
982+ let (node, template) = cached_set.get(c.id);
983+ if let Some(template) = template {
984+ create_with_template(
993+ create(cached_set, change_list, registry, node, cached_roots);
999+// Get or create the template.
1001+// Upon entering this function the change list stack may be in any shape:
1005+// When this function returns, it leaves a freshly cloned copy of the template
1006+// on the top of the change list stack:
1010+fn get_or_create_template<'a>(
1011+ cached_set: &'a CachedSet,
1012+ change_list: &mut ChangeListBuilder,
1013+ registry: &mut EventsRegistry,
1014+ cached_roots: &mut FxHashSet<CacheId>,
1015+ template_id: CacheId,
1016+) -> (&'a Node<'a>, bool) {
1017+ let (template, template_template) = cached_set.get(template_id);
1019+ template_template.is_none(),
1020+ "templates should not be templated themselves"
1023+ // If we haven't already created and saved the physical DOM subtree for this
1024+ // template, do that now.
1025+ if change_list.has_template(template_id) {
1026+ // Clone the template and push it onto the stack.
1029+ change_list.push_template(template_id);
1035+ create(cached_set, change_list, registry, template, cached_roots);
1037+ change_list.save_template(template_id);
1044+fn create_and_replace(
1045+ cached_set: &CachedSet,
1046+ change_list: &mut ChangeListBuilder,
1047+ registry: &mut EventsRegistry,
1048+ new_template: Option<CacheId>,
1051+ cached_roots: &mut FxHashSet<CacheId>,
1053+ debug_assert!(change_list.traversal_is_committed());
1055+ if let Some(template_id) = new_template {
1056+ let (template, needs_listeners) =
1057+ get_or_create_template(cached_set, change_list, registry, cached_roots, template_id);
1058+ change_list.replace_with();
1060+ let mut old_forcing = None;
1061+ if needs_listeners {
1062+ old_forcing = Some(change_list.push_force_new_listeners());
1074+ if let Some(old) = old_forcing {
1075+ change_list.pop_force_new_listeners(old);
1078+ change_list.commit_traversal();
1080+ create(cached_set, change_list, registry, new, cached_roots);
1081+ change_list.replace_with();
1083+ registry.remove_subtree(old);
1086+fn create_with_template(
1087+ cached_set: &CachedSet,
1088+ change_list: &mut ChangeListBuilder,
1089+ registry: &mut EventsRegistry,
1090+ template_id: CacheId,
1092+ cached_roots: &mut FxHashSet<CacheId>,
1094+ debug_assert!(change_list.traversal_is_committed());
1097+ let (template, needs_listeners) =
1098+ get_or_create_template(cached_set, change_list, registry, cached_roots, template_id);
1101+ // Now diff the node with its template.
1103+ // We must force adding new listeners instead of updating existing ones,
1104+ // since listeners don't get cloned in `cloneNode`.
1105+ let mut old_forcing = None;
1106+ if needs_listeners {
1107+ old_forcing = Some(change_list.push_force_new_listeners());
1119+ if let Some(old) = old_forcing {
1120+ change_list.pop_force_new_listeners(old);
1123+ // Make sure that we come back up to the level we were at originally.
1124+ change_list.commit_traversal();