Chromium Code Reviews| 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 | 24 |
| 25 class EventHandler { | |
| 26 final String type; | |
| 27 final sky.EventListener listener; | |
| 28 | |
| 29 EventHandler(this.type, this.listener); | |
| 30 } | |
| 31 | |
| 32 class EventMap { | |
| 33 final List<EventHandler> _handlers = new List<EventHandler>(); | |
| 34 | |
| 35 void listen(String type, sky.EventListener listener) { | |
| 36 assert(listener != null); | |
| 37 _handlers.add(new EventHandler(type, listener)); | |
| 38 } | |
| 39 | |
| 40 void addAll(EventMap events) { | |
| 41 _handlers.addAll(events._handlers); | |
| 42 } | |
| 43 } | |
| 44 | |
| 45 class Style { | 25 class Style { |
| 46 final String _className; | 26 final String _className; |
| 47 static final Map<String, Style> _cache = new HashMap<String, Style>(); | 27 static final Map<String, Style> _cache = new HashMap<String, Style>(); |
| 48 | 28 |
| 49 static int _nextStyleId = 1; | 29 static int _nextStyleId = 1; |
| 50 | 30 |
| 51 static String _getNextClassName() { return "style${_nextStyleId++}"; } | 31 static String _getNextClassName() { return "style${_nextStyleId++}"; } |
| 52 | 32 |
| 53 Style extend(Style other) { | 33 Style extend(Style other) { |
| 54 var className = "$_className ${other._className}"; | 34 var className = "$_className ${other._className}"; |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 84 /* | 64 /* |
| 85 * All Effen nodes derive from Node. All nodes have a _parent, a _key and | 65 * All Effen nodes derive from Node. All nodes have a _parent, a _key and |
| 86 * can be sync'd. | 66 * can be sync'd. |
| 87 */ | 67 */ |
| 88 abstract class Node { | 68 abstract class Node { |
| 89 String _key; | 69 String _key; |
| 90 Node _parent; | 70 Node _parent; |
| 91 sky.Node _root; | 71 sky.Node _root; |
| 92 bool _defunct = false; | 72 bool _defunct = false; |
| 93 | 73 |
| 94 // TODO(abarth): Both Elements and Components have |events| but |Text| | |
| 95 // doesn't. Should we add a common base class to contain |events|? | |
| 96 final EventMap events = new EventMap(); | |
| 97 | |
| 98 Node({ Object key }) { | 74 Node({ Object key }) { |
| 99 _key = key == null ? "$runtimeType" : "$runtimeType-$key"; | 75 _key = key == null ? "$runtimeType" : "$runtimeType-$key"; |
| 100 } | 76 } |
| 101 | 77 |
| 102 // Subclasses which implements Nodes that become stateful may return true | 78 // Subclasses which implements Nodes that become stateful may return true |
| 103 // if the |old| node has become stateful and should be retained. | 79 // if the |old| node has become stateful and should be retained. |
| 104 bool _willSync(Node old) => false; | 80 bool _willSync(Node old) => false; |
| 105 | 81 |
| 106 void _sync(Node old, sky.ParentNode host, sky.Node insertBefore); | 82 void _sync(Node old, sky.ParentNode host, sky.Node insertBefore); |
| 107 | 83 |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 131 } | 107 } |
| 132 | 108 |
| 133 node._parent = this; | 109 node._parent = this; |
| 134 node._sync(oldNode, host, insertBefore); | 110 node._sync(oldNode, host, insertBefore); |
| 135 if (oldNode != null) | 111 if (oldNode != null) |
| 136 oldNode._defunct = true; | 112 oldNode._defunct = true; |
| 137 | 113 |
| 138 assert(node._root is sky.Node); | 114 assert(node._root is sky.Node); |
| 139 return node; | 115 return node; |
| 140 } | 116 } |
| 141 | |
| 142 void _syncEvents(EventMap oldEventMap) { | |
| 143 List<EventHandler> newHandlers = events._handlers; | |
| 144 int newStartIndex = 0; | |
| 145 int newEndIndex = newHandlers.length; | |
| 146 | |
| 147 List<EventHandler> oldHandlers = oldEventMap._handlers; | |
| 148 int oldStartIndex = 0; | |
| 149 int oldEndIndex = oldHandlers.length; | |
| 150 | |
| 151 // Skip over leading handlers that match. | |
| 152 while (newStartIndex < newEndIndex && oldStartIndex < oldEndIndex) { | |
| 153 EventHandler newHandler = newHandlers[newStartIndex]; | |
| 154 EventHandler oldHandler = oldHandlers[oldStartIndex]; | |
| 155 if (newHandler.type != oldHandler.type | |
| 156 || newHandler.listener != oldHandler.listener) | |
| 157 break; | |
| 158 ++newStartIndex; | |
| 159 ++oldStartIndex; | |
| 160 } | |
| 161 | |
| 162 // Skip over trailing handlers that match. | |
| 163 while (newStartIndex < newEndIndex && oldStartIndex < oldEndIndex) { | |
| 164 EventHandler newHandler = newHandlers[newEndIndex - 1]; | |
| 165 EventHandler oldHandler = oldHandlers[oldEndIndex - 1]; | |
| 166 if (newHandler.type != oldHandler.type | |
| 167 || newHandler.listener != oldHandler.listener) | |
| 168 break; | |
| 169 --newEndIndex; | |
| 170 --oldEndIndex; | |
| 171 } | |
| 172 | |
| 173 sky.Element root = _root as sky.Element; | |
| 174 | |
| 175 for (int i = oldStartIndex; i < oldEndIndex; ++i) { | |
| 176 EventHandler oldHandler = oldHandlers[i]; | |
| 177 root.removeEventListener(oldHandler.type, oldHandler.listener); | |
| 178 } | |
| 179 | |
| 180 for (int i = newStartIndex; i < newEndIndex; ++i) { | |
| 181 EventHandler newHandler = newHandlers[i]; | |
| 182 root.addEventListener(newHandler.type, newHandler.listener); | |
| 183 } | |
| 184 } | |
| 185 | |
| 186 } | 117 } |
| 187 | 118 |
| 188 /* | 119 /* |
| 189 * RenderNodes correspond to a desired state of a sky.Node. They are fully | 120 * RenderNodes correspond to a desired state of a sky.Node. They are fully |
| 190 * immutable, with one exception: A Node which is a Component which lives within | 121 * immutable, with one exception: A Node which is a Component which lives within |
| 191 * an Element's children list, may be replaced with the "old" instance if it | 122 * an Element's children list, may be replaced with the "old" instance if it |
| 192 * has become stateful. | 123 * has become stateful. |
| 193 */ | 124 */ |
| 194 abstract class RenderNode extends Node { | 125 abstract class RenderNode extends Node { |
| 195 | 126 |
| 127 static Map<sky.Node, RenderNode> _nodeMap = | |
|
abarth-chromium
2015/03/19 14:19:59
final
rafaelw
2015/03/19 15:03:50
Done.
| |
| 128 new HashMap<sky.Node, RenderNode>(); | |
| 129 | |
| 130 static RenderNode _getMounted(sky.Node node) => _nodeMap[node]; | |
| 131 | |
| 196 RenderNode({ Object key }) : super(key: key); | 132 RenderNode({ Object key }) : super(key: key); |
| 197 | 133 |
| 198 RenderNode get _emptyNode; | 134 RenderNode get _emptyNode; |
| 199 | 135 |
| 200 sky.Node _createNode(); | 136 sky.Node _createNode(); |
| 201 | 137 |
| 202 void _sync(Node old, sky.ParentNode host, sky.Node insertBefore) { | 138 void _sync(Node old, sky.ParentNode host, sky.Node insertBefore) { |
| 203 if (old == null) { | 139 if (old == null) { |
| 204 _root = _createNode(); | 140 _root = _createNode(); |
| 205 _parentInsertBefore(host, _root, insertBefore); | 141 _parentInsertBefore(host, _root, insertBefore); |
| 206 old = _emptyNode; | 142 old = _emptyNode; |
| 207 } else { | 143 } else { |
| 208 _root = old._root; | 144 _root = old._root; |
| 209 } | 145 } |
| 210 | 146 |
| 147 _nodeMap[_root] = this; | |
| 211 _syncNode(old); | 148 _syncNode(old); |
| 212 } | 149 } |
| 213 | 150 |
| 214 void _syncNode(RenderNode old); | 151 void _syncNode(RenderNode old); |
| 215 | 152 |
| 216 void _remove() { | 153 void _remove() { |
| 217 assert(_root != null); | 154 assert(_root != null); |
| 218 _root.remove(); | 155 _root.remove(); |
| 156 _nodeMap.remove(_root); | |
| 219 super._remove(); | 157 super._remove(); |
| 220 } | 158 } |
| 221 } | 159 } |
| 160 | |
| 161 typedef GestureEventListener(sky.GestureEvent e); | |
| 162 typedef PointerEventListener(sky.PointerEvent e); | |
| 163 typedef EventListener(sky.Event e); | |
| 164 | |
| 165 class EventTarget extends Node { | |
| 166 Node content; | |
| 167 final Map<String, sky.EventListener> listeners; | |
| 168 bool _docListenersRegistered = false; | |
|
abarth-chromium
2015/03/19 14:19:59
style nit: _docListenersRegistered -> _haveRegiste
rafaelw
2015/03/19 15:03:50
Removed this.
| |
| 169 | |
| 170 static final Set<String> _registeredEvents = new HashSet<String>(); | |
| 171 | |
| 172 static Map<String, sky.EventListener> _createListeners({ | |
| 173 EventListener onWheel, | |
| 174 GestureEventListener onGestureFlingCancel, | |
| 175 GestureEventListener onGestureFlingStart, | |
| 176 GestureEventListener onGestureScrollStart, | |
| 177 GestureEventListener onGestureScrollUpdate, | |
| 178 GestureEventListener onGestureTap, | |
| 179 PointerEventListener onPointerCancel, | |
| 180 PointerEventListener onPointerDown, | |
| 181 PointerEventListener onPointerMove, | |
| 182 PointerEventListener onPointerUp, | |
| 183 Map<String, sky.EventListener> custom | |
| 184 }) { | |
| 185 var listeners = custom != null ? custom | |
| 186 : new HashMap<String, sky.EventListener>(); | |
| 187 | |
| 188 if (onWheel != null) | |
| 189 listeners['wheel'] = onWheel; | |
|
abarth-chromium
2015/03/19 14:19:59
This if-cascade is kind of ugly, but I think you'r
rafaelw
2015/03/19 15:03:50
Yeah. We could also do this with reflection (i.e.
| |
| 190 if (onGestureFlingCancel != null) | |
| 191 listeners['gestureflingcancel'] = onGestureFlingCancel; | |
| 192 if (onGestureFlingStart != null) | |
| 193 listeners['gestureflingstart'] = onGestureFlingStart; | |
| 194 if (onGestureScrollStart != null) | |
| 195 listeners['gesturescrollstart'] = onGestureScrollStart; | |
| 196 if (onGestureScrollUpdate != null) | |
| 197 listeners['gesturescrollupdate'] = onGestureScrollUpdate; | |
| 198 if (onGestureTap != null) | |
| 199 listeners['gesturetap'] = onGestureTap; | |
| 200 if (onPointerCancel != null) | |
| 201 listeners['pointercancel'] = onPointerCancel; | |
| 202 if (onPointerDown != null) | |
| 203 listeners['pointerdown'] = onPointerDown; | |
| 204 if (onPointerMove != null) | |
| 205 listeners['pointermove'] = onPointerMove; | |
| 206 if (onPointerUp != null) | |
| 207 listeners['pointerup'] = onPointerUp; | |
|
abarth-chromium
2015/03/19 14:19:59
Do you actually want to mutate the |custom| argume
rafaelw
2015/03/19 15:03:50
Done.
| |
| 208 | |
| 209 return listeners; | |
| 210 } | |
| 211 | |
| 212 EventTarget(Node content, { | |
| 213 EventListener onWheel, | |
| 214 GestureEventListener onGestureFlingCancel, | |
| 215 GestureEventListener onGestureFlingStart, | |
| 216 GestureEventListener onGestureScrollStart, | |
| 217 GestureEventListener onGestureScrollUpdate, | |
| 218 GestureEventListener onGestureTap, | |
| 219 PointerEventListener onPointerCancel, | |
| 220 PointerEventListener onPointerDown, | |
| 221 PointerEventListener onPointerMove, | |
| 222 PointerEventListener onPointerUp, | |
| 223 Map<String, sky.EventListener> custom | |
| 224 }) : this.content = content, | |
|
abarth-chromium
2015/03/19 14:19:59
You can just write |this.content| in the argument
rafaelw
2015/03/19 15:03:50
Not if you want to use content in the initializer
| |
| 225 listeners = _createListeners( | |
| 226 onWheel: onWheel, | |
| 227 onGestureFlingCancel: onGestureFlingCancel, | |
| 228 onGestureFlingStart: onGestureFlingStart, | |
| 229 onGestureScrollUpdate: onGestureScrollUpdate, | |
| 230 onGestureScrollStart: onGestureScrollStart, | |
| 231 onGestureTap: onGestureTap, | |
| 232 onPointerCancel: onPointerCancel, | |
| 233 onPointerDown: onPointerDown, | |
| 234 onPointerMove: onPointerMove, | |
| 235 onPointerUp: onPointerUp, | |
| 236 custom: custom | |
| 237 ), | |
| 238 super(key: content._key); | |
| 239 | |
| 240 void _handleEvent(sky.Event e) { | |
| 241 sky.EventListener listener = listeners[e.type]; | |
| 242 if (listener != null) { | |
| 243 listener(e); | |
| 244 } | |
| 245 } | |
| 246 | |
| 247 static void _dispatchEvent(sky.Event e) { | |
| 248 Node target = RenderNode._getMounted(e.target); | |
| 249 | |
| 250 // TODO(rafaelw): StopPropagation? | |
| 251 while (target != null) { | |
| 252 if (target is EventTarget) { | |
| 253 (target as EventTarget)._handleEvent(e); | |
| 254 } | |
| 255 | |
| 256 target = target._parent; | |
| 257 } | |
| 258 } | |
| 259 | |
| 260 static void _ensureDocListener(String eventType) { | |
|
abarth-chromium
2015/03/19 14:19:59
style nit: s/Doc/Document/
rafaelw
2015/03/19 15:03:50
Done.
| |
| 261 if (_registeredEvents.add(eventType)) { | |
| 262 sky.document.addEventListener(eventType, _dispatchEvent); | |
| 263 } | |
| 264 } | |
|
abarth-chromium
2015/03/19 14:19:59
Something is off with your indentation here.
rafaelw
2015/03/19 15:03:50
Done.
| |
| 265 | |
| 266 void _sync(Node old, sky.ParentNode host, sky.Node insertBefore) { | |
| 267 if (!_docListenersRegistered) { | |
| 268 for (var type in listeners.keys) { | |
| 269 _ensureDocListener(type); | |
| 270 } | |
| 271 | |
| 272 _docListenersRegistered = true; | |
|
abarth-chromium
2015/03/19 14:19:59
Do we ever sync the same Node twice? It seems lik
rafaelw
2015/03/19 15:03:50
No, I think you're right. This buys us very little
| |
| 273 } | |
| 274 | |
| 275 Node oldContent = old == null ? null : (old as EventTarget).content; | |
| 276 content = _syncChild(content, oldContent , host, insertBefore); | |
|
abarth-chromium
2015/03/19 14:19:59
s/ ,/,/
rafaelw
2015/03/19 15:03:51
Done.
| |
| 277 _root = content._root; | |
| 278 } | |
| 279 | |
| 280 void _remove() { | |
| 281 content._remove(); | |
| 282 super._remove(); | |
| 283 } | |
| 284 } | |
| 222 | 285 |
| 223 class Text extends RenderNode { | 286 class Text extends RenderNode { |
| 224 final String data; | 287 final String data; |
| 225 | 288 |
| 226 // Text nodes are special cases of having non-unique keys (which don't need | 289 // Text nodes are special cases of having non-unique keys (which don't need |
| 227 // to be assigned as part of the API). Since they are unique in not having | 290 // to be assigned as part of the API). Since they are unique in not having |
| 228 // children, there's little point to reordering, so we always just re-assign | 291 // children, there's little point to reordering, so we always just re-assign |
| 229 // the data. | 292 // the data. |
| 230 Text(this.data) : super(key:'*text*'); | 293 Text(this.data) : super(key:'*text*'); |
| 231 | 294 |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 292 throw '''If multiple (non-Text) nodes of the same type exist as children | 355 throw '''If multiple (non-Text) nodes of the same type exist as children |
| 293 of another node, they must have unique keys.'''; | 356 of another node, they must have unique keys.'''; |
| 294 } | 357 } |
| 295 } | 358 } |
| 296 } | 359 } |
| 297 | 360 |
| 298 void _syncNode(RenderNode old) { | 361 void _syncNode(RenderNode old) { |
| 299 Element oldElement = old as Element; | 362 Element oldElement = old as Element; |
| 300 sky.Element root = _root as sky.Element; | 363 sky.Element root = _root as sky.Element; |
| 301 | 364 |
| 302 _syncEvents(oldElement.events); | |
| 303 | |
| 304 if (_class != oldElement._class) | 365 if (_class != oldElement._class) |
| 305 root.setAttribute('class', _class); | 366 root.setAttribute('class', _class); |
| 306 | 367 |
| 307 if (inlineStyle != oldElement.inlineStyle) | 368 if (inlineStyle != oldElement.inlineStyle) |
| 308 root.setAttribute('style', inlineStyle); | 369 root.setAttribute('style', inlineStyle); |
| 309 | 370 |
| 310 _syncChildren(oldElement); | 371 _syncChildren(oldElement); |
| 311 } | 372 } |
| 312 | 373 |
| 313 void _syncChildren(Element oldElement) { | 374 void _syncChildren(Element oldElement) { |
| (...skipping 260 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 574 void _scheduleComponentForRender(Component c) { | 635 void _scheduleComponentForRender(Component c) { |
| 575 assert(!_inRenderDirtyComponents); | 636 assert(!_inRenderDirtyComponents); |
| 576 _dirtyComponents.add(c); | 637 _dirtyComponents.add(c); |
| 577 | 638 |
| 578 if (!_buildScheduled) { | 639 if (!_buildScheduled) { |
| 579 _buildScheduled = true; | 640 _buildScheduled = true; |
| 580 new Future.microtask(_buildDirtyComponents); | 641 new Future.microtask(_buildDirtyComponents); |
| 581 } | 642 } |
| 582 } | 643 } |
| 583 | 644 |
| 584 EventMap _emptyEventMap = new EventMap(); | |
| 585 | |
| 586 abstract class Component extends Node { | 645 abstract class Component extends Node { |
| 587 bool get _isBuilding => _currentlyBuilding == this; | 646 bool get _isBuilding => _currentlyBuilding == this; |
| 588 bool _dirty = true; | 647 bool _dirty = true; |
| 589 | 648 |
| 590 Node _built; | 649 Node _built; |
| 591 final int _order; | 650 final int _order; |
| 592 static int _currentOrder = 0; | 651 static int _currentOrder = 0; |
| 593 bool _stateful; | 652 bool _stateful; |
| 594 static Component _currentlyBuilding; | 653 static Component _currentlyBuilding; |
| 595 | 654 |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 659 int lastOrder = _currentOrder; | 718 int lastOrder = _currentOrder; |
| 660 _currentOrder = _order; | 719 _currentOrder = _order; |
| 661 _currentlyBuilding = this; | 720 _currentlyBuilding = this; |
| 662 _built = build(); | 721 _built = build(); |
| 663 _currentlyBuilding = null; | 722 _currentlyBuilding = null; |
| 664 _currentOrder = lastOrder; | 723 _currentOrder = lastOrder; |
| 665 | 724 |
| 666 _built = _syncChild(_built, oldBuilt, host, insertBefore); | 725 _built = _syncChild(_built, oldBuilt, host, insertBefore); |
| 667 _dirty = false; | 726 _dirty = false; |
| 668 _root = _built._root; | 727 _root = _built._root; |
| 669 | |
| 670 _built.events.addAll(events); | |
| 671 _syncEvents(oldComponent != null ? oldComponent.events : _emptyEventMap); | |
| 672 } | 728 } |
| 673 | 729 |
| 674 void _buildIfDirty() { | 730 void _buildIfDirty() { |
| 675 if (!_dirty || _defunct) | 731 if (!_dirty || _defunct) |
| 676 return; | 732 return; |
| 677 | 733 |
| 678 assert(_root != null); | 734 assert(_root != null); |
| 679 _sync(null, _root.parentNode, _root.nextSibling); | 735 _sync(null, _root.parentNode, _root.nextSibling); |
| 680 } | 736 } |
| 681 | 737 |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 707 | 763 |
| 708 _sync(null, _host, null); | 764 _sync(null, _host, null); |
| 709 assert(_root is sky.Node); | 765 assert(_root is sky.Node); |
| 710 | 766 |
| 711 sw.stop(); | 767 sw.stop(); |
| 712 if (_shouldLogRenderDuration) | 768 if (_shouldLogRenderDuration) |
| 713 print("Initial build: ${sw.elapsedMicroseconds} microseconds"); | 769 print("Initial build: ${sw.elapsedMicroseconds} microseconds"); |
| 714 }); | 770 }); |
| 715 } | 771 } |
| 716 } | 772 } |
| OLD | NEW |