| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 library fn; | 5 library fn; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 import 'dart:sky' as sky; | 9 import 'dart:sky' as sky; |
| 10 import 'reflect.dart' as reflect; | 10 import 'reflect.dart' as reflect; |
| 11 | 11 |
| 12 bool _initIsInCheckedMode() { | 12 bool _initIsInCheckedMode() { |
| 13 String testFn(i) { double d = i; return d.toString(); } | 13 String testFn(i) { double d = i; return d.toString(); } |
| 14 try { | 14 try { |
| 15 testFn('not a double'); | 15 testFn('not a double'); |
| 16 } catch (ex) { | 16 } catch (ex) { |
| 17 return true; | 17 return true; |
| 18 } | 18 } |
| 19 return false; | 19 return false; |
| 20 } | 20 } |
| 21 | 21 |
| 22 final bool _isInCheckedMode = _initIsInCheckedMode(); | 22 final bool _isInCheckedMode = _initIsInCheckedMode(); |
| 23 final bool _shouldLogRenderDuration = false; | 23 final bool _shouldLogRenderDuration = false; |
| 24 final bool _shouldTrace = false; |
| 24 | 25 |
| 25 class Style { | 26 class Style { |
| 26 final String _className; | 27 final String _className; |
| 27 static final Map<String, Style> _cache = new HashMap<String, Style>(); | 28 static final Map<String, Style> _cache = new HashMap<String, Style>(); |
| 28 | 29 |
| 29 static int _nextStyleId = 1; | 30 static int _nextStyleId = 1; |
| 30 | 31 |
| 31 static String _getNextClassName() { return "style${_nextStyleId++}"; } | 32 static String _getNextClassName() { return "style${_nextStyleId++}"; } |
| 32 | 33 |
| 33 Style extend(Style other) { | 34 Style extend(Style other) { |
| (...skipping 29 matching lines...) Expand all Loading... |
| 63 | 64 |
| 64 /* | 65 /* |
| 65 * All Effen nodes derive from Node. All nodes have a _parent, a _key and | 66 * All Effen nodes derive from Node. All nodes have a _parent, a _key and |
| 66 * can be sync'd. | 67 * can be sync'd. |
| 67 */ | 68 */ |
| 68 abstract class Node { | 69 abstract class Node { |
| 69 String _key; | 70 String _key; |
| 70 Node _parent; | 71 Node _parent; |
| 71 sky.Node _root; | 72 sky.Node _root; |
| 72 bool _defunct = false; | 73 bool _defunct = false; |
| 74 int _nodeDepth; |
| 73 | 75 |
| 74 Node({ Object key }) { | 76 Node({ Object key }) { |
| 75 _key = key == null ? "$runtimeType" : "$runtimeType-$key"; | 77 _key = key == null ? "$runtimeType" : "$runtimeType-$key"; |
| 76 } | 78 } |
| 77 | 79 |
| 78 // Subclasses which implements Nodes that become stateful may return true | 80 // Subclasses which implements Nodes that become stateful may return true |
| 79 // if the |old| node has become stateful and should be retained. | 81 // if the |old| node has become stateful and should be retained. |
| 80 bool _willSync(Node old) => false; | 82 bool _willSync(Node old) => false; |
| 81 | 83 |
| 82 void _sync(Node old, sky.ParentNode host, sky.Node insertBefore); | 84 void _sync(Node old, sky.ParentNode host, sky.Node insertBefore); |
| 83 | 85 |
| 84 void _remove() { | 86 void _remove() { |
| 85 _defunct = true; | 87 _defunct = true; |
| 86 _root = null; | 88 _root = null; |
| 87 } | 89 } |
| 88 | 90 |
| 91 void _ensureDepth() { |
| 92 if (_nodeDepth == null) { |
| 93 _nodeDepth = 0; |
| 94 Node parent = _parent; |
| 95 while (parent != null) { |
| 96 _nodeDepth++; |
| 97 parent = parent._parent; |
| 98 } |
| 99 } |
| 100 } |
| 101 |
| 102 void _trace(String message) { |
| 103 if (!_shouldTrace) |
| 104 return; |
| 105 |
| 106 _ensureDepth(); |
| 107 StringBuffer buffer = new StringBuffer(); |
| 108 int depth = _nodeDepth; |
| 109 while (depth-- > 0) { |
| 110 buffer.write(' '); |
| 111 } |
| 112 buffer.write(message); |
| 113 print(buffer); |
| 114 } |
| 115 |
| 116 void _removeChild(Node node) { |
| 117 _trace('_sync(remove) ${node._key}'); |
| 118 node._remove(); |
| 119 } |
| 120 |
| 89 // Returns the child which should be retained as the child of this node. | 121 // Returns the child which should be retained as the child of this node. |
| 90 Node _syncChild(Node node, Node oldNode, sky.ParentNode host, | 122 Node _syncChild(Node node, Node oldNode, sky.ParentNode host, |
| 91 sky.Node insertBefore) { | 123 sky.Node insertBefore) { |
| 92 if (node == oldNode) | 124 |
| 125 if (node == oldNode) { |
| 126 _trace('_sync(identical) ${node._key}'); |
| 93 return node; // Nothing to do. Subtrees must be identical. | 127 return node; // Nothing to do. Subtrees must be identical. |
| 128 } |
| 94 | 129 |
| 95 // TODO(rafaelw): This eagerly removes the old DOM. It may be that a | 130 // TODO(rafaelw): This eagerly removes the old DOM. It may be that a |
| 96 // new component was built that could re-use some of it. Consider | 131 // new component was built that could re-use some of it. Consider |
| 97 // syncing the new VDOM against the old one. | 132 // syncing the new VDOM against the old one. |
| 98 if (oldNode != null && node._key != oldNode._key) { | 133 if (oldNode != null && node._key != oldNode._key) { |
| 134 _trace('_sync(remove) ${node._key}'); |
| 99 oldNode._remove(); | 135 oldNode._remove(); |
| 100 } | 136 } |
| 101 | 137 |
| 102 if (node._willSync(oldNode)) { | 138 if (node._willSync(oldNode)) { |
| 139 _trace('_sync(statefull) ${node._key} -> ${oldNode._key}'); |
| 103 oldNode._sync(node, host, insertBefore); | 140 oldNode._sync(node, host, insertBefore); |
| 104 node._defunct = true; | 141 node._defunct = true; |
| 105 assert(oldNode._root is sky.Node); | 142 assert(oldNode._root is sky.Node); |
| 106 return oldNode; | 143 return oldNode; |
| 107 } | 144 } |
| 108 | 145 |
| 109 node._parent = this; | 146 node._parent = this; |
| 147 |
| 148 if (oldNode == null) { |
| 149 _trace('_sync(insert) ${node._key}'); |
| 150 } else { |
| 151 _trace('_sync(stateless) ${node._key} <- ${oldNode._key}'); |
| 152 } |
| 110 node._sync(oldNode, host, insertBefore); | 153 node._sync(oldNode, host, insertBefore); |
| 111 if (oldNode != null) | 154 if (oldNode != null) |
| 112 oldNode._defunct = true; | 155 oldNode._defunct = true; |
| 113 | 156 |
| 114 assert(node._root is sky.Node); | 157 assert(node._root is sky.Node); |
| 115 return node; | 158 return node; |
| 116 } | 159 } |
| 117 } | 160 } |
| 118 | 161 |
| 119 /* | 162 /* |
| (...skipping 152 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 272 for (var type in listeners.keys) { | 315 for (var type in listeners.keys) { |
| 273 _ensureDocumentListener(type); | 316 _ensureDocumentListener(type); |
| 274 } | 317 } |
| 275 | 318 |
| 276 Node oldContent = old == null ? null : (old as EventTarget).content; | 319 Node oldContent = old == null ? null : (old as EventTarget).content; |
| 277 content = _syncChild(content, oldContent , host, insertBefore); | 320 content = _syncChild(content, oldContent , host, insertBefore); |
| 278 _root = content._root; | 321 _root = content._root; |
| 279 } | 322 } |
| 280 | 323 |
| 281 void _remove() { | 324 void _remove() { |
| 282 content._remove(); | 325 _removeChild(content); |
| 283 super._remove(); | 326 super._remove(); |
| 284 } | 327 } |
| 285 } | 328 } |
| 286 | 329 |
| 287 class Text extends RenderNode { | 330 class Text extends RenderNode { |
| 288 final String data; | 331 final String data; |
| 289 | 332 |
| 290 // Text nodes are special cases of having non-unique keys (which don't need | 333 // Text nodes are special cases of having non-unique keys (which don't need |
| 291 // to be assigned as part of the API). Since they are unique in not having | 334 // to be assigned as part of the API). Since they are unique in not having |
| 292 // children, there's little point to reordering, so we always just re-assign | 335 // children, there's little point to reordering, so we always just re-assign |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 333 | 376 |
| 334 if (_isInCheckedMode) { | 377 if (_isInCheckedMode) { |
| 335 _debugReportDuplicateIds(); | 378 _debugReportDuplicateIds(); |
| 336 } | 379 } |
| 337 } | 380 } |
| 338 | 381 |
| 339 void _remove() { | 382 void _remove() { |
| 340 super._remove(); | 383 super._remove(); |
| 341 if (_children != null) { | 384 if (_children != null) { |
| 342 for (var child in _children) { | 385 for (var child in _children) { |
| 343 child._remove(); | 386 _removeChild(child); |
| 344 } | 387 } |
| 345 } | 388 } |
| 346 } | 389 } |
| 347 | 390 |
| 348 void _debugReportDuplicateIds() { | 391 void _debugReportDuplicateIds() { |
| 349 var idSet = new HashSet<String>(); | 392 var idSet = new HashSet<String>(); |
| 350 for (var child in _children) { | 393 for (var child in _children) { |
| 351 if (child is Text) { | 394 if (child is Text) { |
| 352 continue; // Text nodes all have the same key and are never reordered. | 395 continue; // Text nodes all have the same key and are never reordered. |
| 353 } | 396 } |
| (...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 476 while (startIndex < endIndex) { | 519 while (startIndex < endIndex) { |
| 477 currentNode = _children[startIndex]; | 520 currentNode = _children[startIndex]; |
| 478 sync(startIndex); | 521 sync(startIndex); |
| 479 startIndex++; | 522 startIndex++; |
| 480 } | 523 } |
| 481 | 524 |
| 482 // Removals | 525 // Removals |
| 483 currentNode = null; | 526 currentNode = null; |
| 484 while (oldStartIndex < oldEndIndex) { | 527 while (oldStartIndex < oldEndIndex) { |
| 485 oldNode = oldChildren[oldStartIndex]; | 528 oldNode = oldChildren[oldStartIndex]; |
| 486 oldNode._remove(); | 529 _removeChild(oldNode); |
| 487 advanceOldStartIndex(); | 530 advanceOldStartIndex(); |
| 488 } | 531 } |
| 489 } | 532 } |
| 490 } | 533 } |
| 491 | 534 |
| 492 class Container extends Element { | 535 class Container extends Element { |
| 493 | 536 |
| 494 String get _tagName => 'div'; | 537 String get _tagName => 'div'; |
| 495 | 538 |
| 496 static final Container _emptyContainer = new Container(); | 539 static final Container _emptyContainer = new Container(); |
| (...skipping 170 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 667 void didMount() {} | 710 void didMount() {} |
| 668 void didUnmount() {} | 711 void didUnmount() {} |
| 669 | 712 |
| 670 // TODO(rafaelw): It seems wrong to expose DOM at all. This is presently | 713 // TODO(rafaelw): It seems wrong to expose DOM at all. This is presently |
| 671 // needed to get sizing info. | 714 // needed to get sizing info. |
| 672 sky.Node getRoot() => _root; | 715 sky.Node getRoot() => _root; |
| 673 | 716 |
| 674 void _remove() { | 717 void _remove() { |
| 675 assert(_built != null); | 718 assert(_built != null); |
| 676 assert(_root != null); | 719 assert(_root != null); |
| 677 _built._remove(); | 720 _removeChild(_built); |
| 678 _built = null; | 721 _built = null; |
| 679 _unmountedComponents.add(this); | 722 _unmountedComponents.add(this); |
| 680 super._remove(); | 723 super._remove(); |
| 681 } | 724 } |
| 682 | 725 |
| 683 bool _willSync(Node old) { | 726 bool _willSync(Node old) { |
| 684 Component oldComponent = old as Component; | 727 Component oldComponent = old as Component; |
| 685 if (oldComponent == null || !oldComponent._stateful) | 728 if (oldComponent == null || !oldComponent._stateful) |
| 686 return false; | 729 return false; |
| 687 | 730 |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 732 _built = _syncChild(_built, oldBuilt, host, insertBefore); | 775 _built = _syncChild(_built, oldBuilt, host, insertBefore); |
| 733 _dirty = false; | 776 _dirty = false; |
| 734 _root = _built._root; | 777 _root = _built._root; |
| 735 } | 778 } |
| 736 | 779 |
| 737 void _buildIfDirty() { | 780 void _buildIfDirty() { |
| 738 if (!_dirty || _defunct) | 781 if (!_dirty || _defunct) |
| 739 return; | 782 return; |
| 740 | 783 |
| 741 assert(_host != null); | 784 assert(_host != null); |
| 785 _trace('$_key rebuilding...'); |
| 742 _sync(null, _host, _insertionPoint); | 786 _sync(null, _host, _insertionPoint); |
| 743 } | 787 } |
| 744 | 788 |
| 745 void scheduleBuild() { | 789 void scheduleBuild() { |
| 746 setState(() {}); | 790 setState(() {}); |
| 747 } | 791 } |
| 748 | 792 |
| 749 void setState(Function fn()) { | 793 void setState(Function fn()) { |
| 750 _stateful = true; | 794 _stateful = true; |
| 751 fn(); | 795 fn(); |
| 752 if (_isBuilding || _dirty || _defunct) | 796 if (_isBuilding || _dirty || _defunct) |
| 753 return; | 797 return; |
| 754 | 798 |
| 755 _dirty = true; | 799 _dirty = true; |
| 756 _scheduleComponentForRender(this); | 800 _scheduleComponentForRender(this); |
| 757 } | 801 } |
| 758 | 802 |
| 759 Node build(); | 803 Node build(); |
| 760 } | 804 } |
| 761 | 805 |
| 762 abstract class App extends Component { | 806 abstract class App extends Component { |
| 763 sky.Node _host; | 807 sky.Node _host; |
| 764 | 808 |
| 765 App() : super(stateful: true) { | 809 App() : super(stateful: true) { |
| 766 _host = sky.document.createElement('div'); | 810 _host = sky.document.createElement('div'); |
| 767 sky.document.appendChild(_host); | 811 sky.document.appendChild(_host); |
| 768 _scheduleComponentForRender(this); | 812 _scheduleComponentForRender(this); |
| 769 } | 813 } |
| 770 } | 814 } |
| OLD | NEW |