@@ -98,9 +98,27 @@ pub(crate) fn diff(
101− let new = cached_set.get(new.id);
101+ let (new, new_template) = cached_set.get(new.id);
102− let old = cached_set.get(old.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
103− diff(cached_set, change_list, registry, old, new, cached_roots);
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();
106 // New cached node when the old node was not cached. In this scenario,
124 // New cached node when the old node was not cached. In this scenario,
@@ -109,10 +127,16 @@ pub(crate) fn diff(
109 (&NodeKind::Cached(ref c), _) => {
127 (&NodeKind::Cached(ref c), _) => {
110 change_list.commit_traversal();
128 change_list.commit_traversal();
111 cached_roots.insert(c.id);
129 cached_roots.insert(c.id);
112− let new = cached_set.get(c.id);
130+ let (new, new_template) = cached_set.get(c.id);
113− create(cached_set, change_list, registry, new, cached_roots);
131+ create_and_replace(
114− registry.remove_subtree(&old);
132+ cached_set,
115− change_list.replace_with();
133+ change_list,
118 // Old cached node and new non-cached node. Again, assume that they are
142 // Old cached node and new non-cached node. Again, assume that they are
@@ -239,6 +263,35 @@ fn diff_children(
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);
242 if old.is_empty() {
295 if old.is_empty() {
243 if !new.is_empty() {
296 if !new.is_empty() {
244 change_list.commit_traversal();
297 change_list.commit_traversal();
@@ -926,8 +979,147 @@ fn create(
927 NodeKind::Cached(ref c) => {
980 NodeKind::Cached(ref c) => {
928 cached_roots.insert(c.id);
981 cached_roots.insert(c.id);
929− let node = cached_set.get(c.id);
982+ let (node, template) = cached_set.get(c.id);
983+ if let Some(template) = template {
984+ create_with_template(
930− create(cached_set, change_list, registry, node, cached_roots)
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();