| OLD | NEW | 
|---|
|  | (Empty) | 
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |  | 
| 2 // Use of this source code is governed by a BSD-style license that can be |  | 
| 3 // found in the LICENSE file. |  | 
| 4 |  | 
| 5 part of fn; |  | 
| 6 |  | 
| 7 void _parentInsertBefore(sky.ParentNode parent, |  | 
| 8                          sky.Node node, |  | 
| 9                          sky.Node ref) { |  | 
| 10   if (ref != null) { |  | 
| 11     ref.insertBefore([node]); |  | 
| 12   } else { |  | 
| 13     parent.appendChild(node); |  | 
| 14   } |  | 
| 15 } |  | 
| 16 |  | 
| 17 abstract class Node { |  | 
| 18   String _key = null; |  | 
| 19   sky.Node _root = null; |  | 
| 20 |  | 
| 21   // TODO(abarth): Both Elements and Components have |events| but |Text| |  | 
| 22   // doesn't. Should we add a common base class to contain |events|? |  | 
| 23   final EventMap events = new EventMap(); |  | 
| 24 |  | 
| 25   Node({ Object key }) { |  | 
| 26     _key = key == null ? "$runtimeType" : "$runtimeType-$key"; |  | 
| 27   } |  | 
| 28 |  | 
| 29   // Return true IFF the old node has *become* the new node (should be |  | 
| 30   // retained because it is stateful) |  | 
| 31   bool _sync(Node old, sky.ParentNode host, sky.Node insertBefore); |  | 
| 32 |  | 
| 33   void _remove() { |  | 
| 34     assert(_root != null); |  | 
| 35     _root.remove(); |  | 
| 36     _root = null; |  | 
| 37   } |  | 
| 38 } |  | 
| 39 |  | 
| 40 class Text extends Node { |  | 
| 41   String data; |  | 
| 42 |  | 
| 43   // Text nodes are special cases of having non-unique keys (which don't need |  | 
| 44   // to be assigned as part of the API). Since they are unique in not having |  | 
| 45   // children, there's little point to reordering, so we always just re-assign |  | 
| 46   // the data. |  | 
| 47   Text(this.data) : super(key:'*text*'); |  | 
| 48 |  | 
| 49   bool _sync(Node old, sky.ParentNode host, sky.Node insertBefore) { |  | 
| 50     if (old == null) { |  | 
| 51       _root = new sky.Text(data); |  | 
| 52       _parentInsertBefore(host, _root, insertBefore); |  | 
| 53       return false; |  | 
| 54     } |  | 
| 55 |  | 
| 56     _root = old._root; |  | 
| 57     (_root as sky.Text).data = data; |  | 
| 58     return false; |  | 
| 59   } |  | 
| 60 } |  | 
| 61 |  | 
| 62 final List<Node> _emptyList = new List<Node>(); |  | 
| 63 |  | 
| 64 abstract class Element extends Node { |  | 
| 65 |  | 
| 66   String get _tagName; |  | 
| 67 |  | 
| 68   Element get _emptyElement; |  | 
| 69 |  | 
| 70   String inlineStyle; |  | 
| 71 |  | 
| 72   List<Node> _children = null; |  | 
| 73   String _className = ''; |  | 
| 74 |  | 
| 75   Element({ |  | 
| 76     Object key, |  | 
| 77     List<Node> children, |  | 
| 78     Style style, |  | 
| 79 |  | 
| 80     this.inlineStyle |  | 
| 81   }) : super(key:key) { |  | 
| 82 |  | 
| 83     _className = style == null ? '': style._className; |  | 
| 84     _children = children == null ? _emptyList : children; |  | 
| 85 |  | 
| 86     if (_debugWarnings()) { |  | 
| 87       _debugReportDuplicateIds(); |  | 
| 88     } |  | 
| 89   } |  | 
| 90 |  | 
| 91   void _remove() { |  | 
| 92     super._remove(); |  | 
| 93     if (_children != null) { |  | 
| 94       for (var child in _children) { |  | 
| 95         child._remove(); |  | 
| 96       } |  | 
| 97     } |  | 
| 98     _children = null; |  | 
| 99   } |  | 
| 100 |  | 
| 101   void _debugReportDuplicateIds() { |  | 
| 102     var idSet = new HashSet<String>(); |  | 
| 103     for (var child in _children) { |  | 
| 104       if (child is Text) { |  | 
| 105         continue; // Text nodes all have the same key and are never reordered. |  | 
| 106       } |  | 
| 107 |  | 
| 108       if (!idSet.add(child._key)) { |  | 
| 109         throw '''If multiple (non-Text) nodes of the same type exist as children |  | 
| 110                  of another node, they must have unique keys.'''; |  | 
| 111       } |  | 
| 112     } |  | 
| 113   } |  | 
| 114 |  | 
| 115   void _syncEvents([Element old]) { |  | 
| 116     List<EventHandler> newHandlers = events._handlers; |  | 
| 117     int newStartIndex = 0; |  | 
| 118     int newEndIndex = newHandlers.length; |  | 
| 119 |  | 
| 120     List<EventHandler> oldHandlers = old.events._handlers; |  | 
| 121     int oldStartIndex = 0; |  | 
| 122     int oldEndIndex = oldHandlers.length; |  | 
| 123 |  | 
| 124     // Skip over leading handlers that match. |  | 
| 125     while (newStartIndex < newEndIndex && oldStartIndex < oldEndIndex) { |  | 
| 126       EventHandler newHander = newHandlers[newStartIndex]; |  | 
| 127       EventHandler oldHandler = oldHandlers[oldStartIndex]; |  | 
| 128       if (newHander.type != oldHandler.type |  | 
| 129           || newHander.listener != oldHandler.listener) |  | 
| 130         break; |  | 
| 131       ++newStartIndex; |  | 
| 132       ++oldStartIndex; |  | 
| 133     } |  | 
| 134 |  | 
| 135     // Skip over trailing handlers that match. |  | 
| 136     while (newStartIndex < newEndIndex && oldStartIndex < oldEndIndex) { |  | 
| 137       EventHandler newHander = newHandlers[newEndIndex - 1]; |  | 
| 138       EventHandler oldHandler = oldHandlers[oldEndIndex - 1]; |  | 
| 139       if (newHander.type != oldHandler.type |  | 
| 140           || newHander.listener != oldHandler.listener) |  | 
| 141         break; |  | 
| 142       --newEndIndex; |  | 
| 143       --oldEndIndex; |  | 
| 144     } |  | 
| 145 |  | 
| 146     sky.Element root = _root as sky.Element; |  | 
| 147 |  | 
| 148     for (int i = oldStartIndex; i < oldEndIndex; ++i) { |  | 
| 149       EventHandler oldHandler = oldHandlers[i]; |  | 
| 150       root.removeEventListener(oldHandler.type, oldHandler.listener); |  | 
| 151     } |  | 
| 152 |  | 
| 153     for (int i = newStartIndex; i < newEndIndex; ++i) { |  | 
| 154       EventHandler newHander = newHandlers[i]; |  | 
| 155       root.addEventListener(newHander.type, newHander.listener); |  | 
| 156     } |  | 
| 157   } |  | 
| 158 |  | 
| 159   void _syncNode([Element old]) { |  | 
| 160     if (old == null) { |  | 
| 161       old = _emptyElement; |  | 
| 162     } |  | 
| 163 |  | 
| 164     _syncEvents(old); |  | 
| 165 |  | 
| 166     sky.Element root = _root as sky.Element; |  | 
| 167     if (_className != old._className) { |  | 
| 168       root.setAttribute('class', _className); |  | 
| 169     } |  | 
| 170 |  | 
| 171     if (inlineStyle != old.inlineStyle) { |  | 
| 172       root.setAttribute('style', inlineStyle); |  | 
| 173     } |  | 
| 174   } |  | 
| 175 |  | 
| 176   bool _sync(Node old, sky.ParentNode host, sky.Node insertBefore) { |  | 
| 177     // print("---Syncing children of $_key"); |  | 
| 178 |  | 
| 179     Element oldElement = old as Element; |  | 
| 180 |  | 
| 181     if (oldElement == null) { |  | 
| 182       // print("...no oldElement, initial render"); |  | 
| 183 |  | 
| 184       _root = sky.document.createElement(_tagName); |  | 
| 185       _syncNode(); |  | 
| 186 |  | 
| 187       for (var child in _children) { |  | 
| 188         child._sync(null, _root, null); |  | 
| 189         assert(child._root is sky.Node); |  | 
| 190       } |  | 
| 191 |  | 
| 192       _parentInsertBefore(host, _root, insertBefore); |  | 
| 193       return false; |  | 
| 194     } |  | 
| 195 |  | 
| 196     _root = oldElement._root; |  | 
| 197     oldElement._root = null; |  | 
| 198     sky.Element root = (_root as sky.Element); |  | 
| 199 |  | 
| 200     _syncNode(oldElement); |  | 
| 201 |  | 
| 202     var startIndex = 0; |  | 
| 203     var endIndex = _children.length; |  | 
| 204 |  | 
| 205     var oldChildren = oldElement._children; |  | 
| 206     var oldStartIndex = 0; |  | 
| 207     var oldEndIndex = oldChildren.length; |  | 
| 208 |  | 
| 209     sky.Node nextSibling = null; |  | 
| 210     Node currentNode = null; |  | 
| 211     Node oldNode = null; |  | 
| 212 |  | 
| 213     void sync(int atIndex) { |  | 
| 214       if (currentNode._sync(oldNode, root, nextSibling)) { |  | 
| 215         // oldNode was stateful and must be retained. |  | 
| 216         assert(oldNode != null); |  | 
| 217         currentNode = oldNode; |  | 
| 218         _children[atIndex] = currentNode; |  | 
| 219       } |  | 
| 220       assert(currentNode._root is sky.Node); |  | 
| 221     } |  | 
| 222 |  | 
| 223     // Scan backwards from end of list while nodes can be directly synced |  | 
| 224     // without reordering. |  | 
| 225     // print("...scanning backwards"); |  | 
| 226     while (endIndex > startIndex && oldEndIndex > oldStartIndex) { |  | 
| 227       currentNode = _children[endIndex - 1]; |  | 
| 228       oldNode = oldChildren[oldEndIndex - 1]; |  | 
| 229 |  | 
| 230       if (currentNode._key != oldNode._key) { |  | 
| 231         break; |  | 
| 232       } |  | 
| 233 |  | 
| 234       // print('> syncing matched at: $endIndex : $oldEndIndex'); |  | 
| 235       endIndex--; |  | 
| 236       oldEndIndex--; |  | 
| 237       sync(endIndex); |  | 
| 238       nextSibling = currentNode._root; |  | 
| 239     } |  | 
| 240 |  | 
| 241     HashMap<String, Node> oldNodeIdMap = null; |  | 
| 242 |  | 
| 243     bool oldNodeReordered(String key) { |  | 
| 244       return oldNodeIdMap != null && |  | 
| 245              oldNodeIdMap.containsKey(key) && |  | 
| 246              oldNodeIdMap[key] == null; |  | 
| 247     } |  | 
| 248 |  | 
| 249     void advanceOldStartIndex() { |  | 
| 250       oldStartIndex++; |  | 
| 251       while (oldStartIndex < oldEndIndex && |  | 
| 252              oldNodeReordered(oldChildren[oldStartIndex]._key)) { |  | 
| 253         oldStartIndex++; |  | 
| 254       } |  | 
| 255     } |  | 
| 256 |  | 
| 257     void ensureOldIdMap() { |  | 
| 258       if (oldNodeIdMap != null) |  | 
| 259         return; |  | 
| 260 |  | 
| 261       oldNodeIdMap = new HashMap<String, Node>(); |  | 
| 262       for (int i = oldStartIndex; i < oldEndIndex; i++) { |  | 
| 263         var node = oldChildren[i]; |  | 
| 264         if (node is! Text) { |  | 
| 265           oldNodeIdMap.putIfAbsent(node._key, () => node); |  | 
| 266         } |  | 
| 267       } |  | 
| 268     } |  | 
| 269 |  | 
| 270     bool searchForOldNode() { |  | 
| 271       if (currentNode is Text) |  | 
| 272         return false; // Never re-order Text nodes. |  | 
| 273 |  | 
| 274       ensureOldIdMap(); |  | 
| 275       oldNode = oldNodeIdMap[currentNode._key]; |  | 
| 276       if (oldNode == null) |  | 
| 277         return false; |  | 
| 278 |  | 
| 279       oldNodeIdMap[currentNode._key] = null; // mark it reordered. |  | 
| 280       // print("Reparenting ${currentNode._key}"); |  | 
| 281       _parentInsertBefore(root, oldNode._root, nextSibling); |  | 
| 282       return true; |  | 
| 283     } |  | 
| 284 |  | 
| 285     // Scan forwards, this time we may re-order; |  | 
| 286     // print("...scanning forward"); |  | 
| 287     nextSibling = root.firstChild; |  | 
| 288     while (startIndex < endIndex && oldStartIndex < oldEndIndex) { |  | 
| 289       currentNode = _children[startIndex]; |  | 
| 290       oldNode = oldChildren[oldStartIndex]; |  | 
| 291 |  | 
| 292       if (currentNode._key == oldNode._key) { |  | 
| 293         // print('> syncing matched at: $startIndex : $oldStartIndex'); |  | 
| 294         assert(currentNode.runtimeType == oldNode.runtimeType); |  | 
| 295         nextSibling = nextSibling.nextSibling; |  | 
| 296         sync(startIndex); |  | 
| 297         startIndex++; |  | 
| 298         advanceOldStartIndex(); |  | 
| 299         continue; |  | 
| 300       } |  | 
| 301 |  | 
| 302       oldNode = null; |  | 
| 303       if (searchForOldNode()) { |  | 
| 304         // print('> reordered to $startIndex'); |  | 
| 305       } else { |  | 
| 306         // print('> inserting at $startIndex'); |  | 
| 307       } |  | 
| 308 |  | 
| 309       sync(startIndex); |  | 
| 310       startIndex++; |  | 
| 311     } |  | 
| 312 |  | 
| 313     // New insertions |  | 
| 314     oldNode = null; |  | 
| 315     // print('...processing remaining insertions'); |  | 
| 316     while (startIndex < endIndex) { |  | 
| 317       // print('> inserting at $startIndex'); |  | 
| 318       currentNode = _children[startIndex]; |  | 
| 319       sync(startIndex); |  | 
| 320       startIndex++; |  | 
| 321     } |  | 
| 322 |  | 
| 323     // Removals |  | 
| 324     // print('...processing remaining removals'); |  | 
| 325     currentNode = null; |  | 
| 326     while (oldStartIndex < oldEndIndex) { |  | 
| 327       oldNode = oldChildren[oldStartIndex]; |  | 
| 328       // print('> ${oldNode._key} removing from $oldEndIndex'); |  | 
| 329       oldNode._remove(); |  | 
| 330       advanceOldStartIndex(); |  | 
| 331     } |  | 
| 332 |  | 
| 333     oldElement._children = null; |  | 
| 334     return false; |  | 
| 335   } |  | 
| 336 } |  | 
| 337 |  | 
| 338 class Container extends Element { |  | 
| 339 |  | 
| 340   String get _tagName => 'div'; |  | 
| 341 |  | 
| 342   static final Container _emptyContainer = new Container(); |  | 
| 343 |  | 
| 344   Element get _emptyElement => _emptyContainer; |  | 
| 345 |  | 
| 346   Container({ |  | 
| 347     Object key, |  | 
| 348     List<Node> children, |  | 
| 349     Style style, |  | 
| 350     String inlineStyle |  | 
| 351   }) : super( |  | 
| 352     key: key, |  | 
| 353     children: children, |  | 
| 354     style: style, |  | 
| 355     inlineStyle: inlineStyle |  | 
| 356   ); |  | 
| 357 } |  | 
| 358 |  | 
| 359 class Image extends Element { |  | 
| 360 |  | 
| 361   String get _tagName => 'img'; |  | 
| 362 |  | 
| 363   static final Image _emptyImage = new Image(); |  | 
| 364   Element get _emptyElement => _emptyImage; |  | 
| 365 |  | 
| 366   String src; |  | 
| 367   int width; |  | 
| 368   int height; |  | 
| 369 |  | 
| 370   Image({ |  | 
| 371     Object key, |  | 
| 372     List<Node> children, |  | 
| 373     Style style, |  | 
| 374     String inlineStyle, |  | 
| 375     this.width, |  | 
| 376     this.height, |  | 
| 377     this.src |  | 
| 378   }) : super( |  | 
| 379     key: key, |  | 
| 380     children: children, |  | 
| 381     style: style, |  | 
| 382     inlineStyle: inlineStyle |  | 
| 383   ); |  | 
| 384 |  | 
| 385   void _syncNode([Element old]) { |  | 
| 386     super._syncNode(old); |  | 
| 387 |  | 
| 388     Image oldImage = old != null ? old : _emptyImage; |  | 
| 389     sky.HTMLImageElement skyImage = _root as sky.HTMLImageElement; |  | 
| 390     if (src != oldImage.src) { |  | 
| 391       skyImage.src = src; |  | 
| 392     } |  | 
| 393 |  | 
| 394     if (width != oldImage.width) { |  | 
| 395       skyImage.style['width'] = '${width}px'; |  | 
| 396     } |  | 
| 397     if (height != oldImage.height) { |  | 
| 398       skyImage.style['height'] = '${height}px'; |  | 
| 399     } |  | 
| 400   } |  | 
| 401 } |  | 
| 402 |  | 
| 403 class Anchor extends Element { |  | 
| 404 |  | 
| 405   String get _tagName => 'a'; |  | 
| 406 |  | 
| 407   static final Anchor _emptyAnchor = new Anchor(); |  | 
| 408 |  | 
| 409   String href; |  | 
| 410 |  | 
| 411   Anchor({ |  | 
| 412     Object key, |  | 
| 413     List<Node> children, |  | 
| 414     Style style, |  | 
| 415     String inlineStyle, |  | 
| 416     this.width, |  | 
| 417     this.height, |  | 
| 418     this.href |  | 
| 419   }) : super( |  | 
| 420     key: key, |  | 
| 421     children: children, |  | 
| 422     style: style, |  | 
| 423     inlineStyle: inlineStyle |  | 
| 424   ); |  | 
| 425 |  | 
| 426   void _syncNode([Element old]) { |  | 
| 427     Anchor oldAnchor = old != null ? old as Anchor : _emptyAnchor; |  | 
| 428     super._syncNode(oldAnchor); |  | 
| 429 |  | 
| 430     sky.HTMLAnchorElement skyAnchor = _root as sky.HTMLAnchorElement; |  | 
| 431     if (href != oldAnchor.href) { |  | 
| 432       skyAnchor.href = href; |  | 
| 433     } |  | 
| 434   } |  | 
| 435 } |  | 
| OLD | NEW | 
|---|