Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(356)

Side by Side Diff: sky/framework/fn.dart

Issue 1009543008: Refactor Effen to make explicit the distinction between render & non-render nodes. (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: cr changes Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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;
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
74 void _parentInsertBefore(sky.ParentNode parent, 74 void _parentInsertBefore(sky.ParentNode parent,
75 sky.Node node, 75 sky.Node node,
76 sky.Node ref) { 76 sky.Node ref) {
77 if (ref != null) { 77 if (ref != null) {
78 ref.insertBefore([node]); 78 ref.insertBefore([node]);
79 } else { 79 } else {
80 parent.appendChild(node); 80 parent.appendChild(node);
81 } 81 }
82 } 82 }
83 83
84 /*
85 * All Effen nodes derive from Node. All nodes have a _parent, a _key and
86 * can be sync'd.
87 */
84 abstract class Node { 88 abstract class Node {
85 String _key; 89 String _key;
86 Node _parent; 90 Node _parent;
87 sky.Node _root; 91 sky.Node _root;
88 bool _defunct = false; 92 bool _defunct = false;
89 93
90 // TODO(abarth): Both Elements and Components have |events| but |Text| 94 // TODO(abarth): Both Elements and Components have |events| but |Text|
91 // doesn't. Should we add a common base class to contain |events|? 95 // doesn't. Should we add a common base class to contain |events|?
92 final EventMap events = new EventMap(); 96 final EventMap events = new EventMap();
93 97
94 Node({ Object key }) { 98 Node({ Object key }) {
95 _key = key == null ? "$runtimeType" : "$runtimeType-$key"; 99 _key = key == null ? "$runtimeType" : "$runtimeType-$key";
96 } 100 }
97 101
98 Node get _emptyNode; 102 // Subclasses which implements Nodes that become stateful may return true
103 // if the |old| node has become stateful and should be retained.
104 bool _willSync(Node old) => false;
105
106 void _sync(Node old, sky.ParentNode host, sky.Node insertBefore);
107
108 void _remove() {
109 _defunct = true;
110 _root = null;
111 }
112
113 // Returns the child which should be retained as the child of this node.
114 Node _syncChild(Node node, Node oldNode, sky.ParentNode host,
115 sky.Node insertBefore) {
116 if (node == oldNode)
117 return node; // Nothing to do. Subtrees must be identical.
118
119 // TODO(rafaelw): This eagerly removes the old DOM. It may be that a
120 // new component was built that could re-use some of it. Consider
121 // syncing the new VDOM against the old one.
122 if (oldNode != null && node._key != oldNode._key) {
123 oldNode._remove();
124 }
125
126 if (node._willSync(oldNode)) {
127 oldNode._sync(node, host, insertBefore);
128 node._defunct = true;
129 assert(oldNode._root is sky.Node);
130 return oldNode;
131 }
132
133 node._parent = this;
134 node._sync(oldNode, host, insertBefore);
135 if (oldNode != null)
136 oldNode._defunct = true;
137
138 assert(node._root is sky.Node);
139 return node;
140 }
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 }
187
188 /*
189 * 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
191 * an Element's children list, may be replaced with the "old" instance if it
192 * has become stateful.
193 */
194 abstract class RenderNode extends Node {
195
196 RenderNode({ Object key }) : super(key: key);
197
198 RenderNode get _emptyNode;
99 199
100 sky.Node _createNode(); 200 sky.Node _createNode();
101 201
102 void _mount(Node parent, sky.ParentNode host, sky.Node insertBefore) { 202 void _sync(Node old, sky.ParentNode host, sky.Node insertBefore) {
103 var node = _emptyNode; 203 if (old == null) {
104 node._parent = parent; 204 _root = _createNode();
205 _parentInsertBefore(host, _root, insertBefore);
206 old = _emptyNode;
207 } else {
208 _root = old._root;
209 }
105 210
106 node._root = _createNode(); 211 _syncNode(old);
107 assert(node._root != null);
108
109 _parentInsertBefore(host, node._root, insertBefore);
110
111 _syncNode(node);
112 } 212 }
113 213
114 bool _sync(Node old, Node parent, sky.ParentNode host, 214 void _syncNode(RenderNode old);
115 sky.Node insertBefore) {
116 215
117 if (old == null) { 216 void _remove() {
118 _mount(parent, host, insertBefore);
119 return false;
120 }
121
122 return _syncNode(old);
123 }
124
125 // Return true IFF the old node has *become* the new node (should be
126 // retained because it is stateful)
127 bool _syncNode(Node old) {
128 assert(!old._defunct);
129 _root = old._root;
130 _parent = old._parent;
131 return false;
132 }
133
134 void _unmount() {
135 assert(_root != null); 217 assert(_root != null);
136 _parent = null;
137 _root.remove(); 218 _root.remove();
138 _defunct = true; 219 super._remove();
139 _root = null;
140 } 220 }
141 } 221 }
142 222
143 class Text extends Node { 223 class Text extends RenderNode {
144 final String data; 224 final String data;
145 225
146 // Text nodes are special cases of having non-unique keys (which don't need 226 // Text nodes are special cases of having non-unique keys (which don't need
147 // to be assigned as part of the API). Since they are unique in not having 227 // to be assigned as part of the API). Since they are unique in not having
148 // children, there's little point to reordering, so we always just re-assign 228 // children, there's little point to reordering, so we always just re-assign
149 // the data. 229 // the data.
150 Text(this.data) : super(key:'*text*'); 230 Text(this.data) : super(key:'*text*');
151 231
152 static Text _emptyText = new Text(null); 232 static final Text _emptyText = new Text(null);
153 233
154 Node get _emptyNode => _emptyText; 234 RenderNode get _emptyNode => _emptyText;
155 235
156 sky.Node _createNode() { 236 sky.Node _createNode() {
157 return new sky.Text(data); 237 return new sky.Text(data);
158 } 238 }
159 239
160 bool _syncNode(Node old) { 240 void _syncNode(RenderNode old) {
161 super._syncNode(old);
162 if (old == _emptyText) 241 if (old == _emptyText)
163 return false; // we set inside _createNode(); 242 return; // we set inside _createNode();
164 243
165 (_root as sky.Text).data = data; 244 (_root as sky.Text).data = data;
166 return false;
167 } 245 }
168 } 246 }
169 247
170 final List<Node> _emptyList = new List<Node>(); 248 final List<Node> _emptyList = new List<Node>();
171 249
172 abstract class Element extends Node { 250 abstract class Element extends RenderNode {
173 251
174 String get _tagName; 252 String get _tagName;
175 253
176 sky.Node _createNode() => sky.document.createElement(_tagName); 254 sky.Node _createNode() => sky.document.createElement(_tagName);
177 255
178 final String inlineStyle; 256 final String inlineStyle;
179 257
180 final List<Node> _children; 258 final List<Node> _children;
181 final String _class; 259 final String _class;
182 260
183 Element({ 261 Element({
184 Object key, 262 Object key,
185 List<Node> children, 263 List<Node> children,
186 Style style, 264 Style style,
187 this.inlineStyle 265 this.inlineStyle
188 }) : _class = style == null ? '' : style._className, 266 }) : _class = style == null ? '' : style._className,
189 _children = children == null ? _emptyList : children, 267 _children = children == null ? _emptyList : children,
190 super(key:key) { 268 super(key:key) {
191 269
192 if (_isInCheckedMode) { 270 if (_isInCheckedMode) {
193 _debugReportDuplicateIds(); 271 _debugReportDuplicateIds();
194 } 272 }
195 } 273 }
196 274
197 void _unmount() { 275 void _remove() {
198 super._unmount(); 276 super._remove();
199 if (_children != null) { 277 if (_children != null) {
200 for (var child in _children) { 278 for (var child in _children) {
201 child._unmount(); 279 child._remove();
202 } 280 }
203 } 281 }
204 } 282 }
205 283
206 void _debugReportDuplicateIds() { 284 void _debugReportDuplicateIds() {
207 var idSet = new HashSet<String>(); 285 var idSet = new HashSet<String>();
208 for (var child in _children) { 286 for (var child in _children) {
209 if (child is Text) { 287 if (child is Text) {
210 continue; // Text nodes all have the same key and are never reordered. 288 continue; // Text nodes all have the same key and are never reordered.
211 } 289 }
212 290
213 if (!idSet.add(child._key)) { 291 if (!idSet.add(child._key)) {
214 throw '''If multiple (non-Text) nodes of the same type exist as children 292 throw '''If multiple (non-Text) nodes of the same type exist as children
215 of another node, they must have unique keys.'''; 293 of another node, they must have unique keys.''';
216 } 294 }
217 } 295 }
218 } 296 }
219 297
220 void _syncEvents([Element old]) { 298 void _syncNode(RenderNode old) {
221 List<EventHandler> newHandlers = events._handlers;
222 int newStartIndex = 0;
223 int newEndIndex = newHandlers.length;
224
225 List<EventHandler> oldHandlers = old.events._handlers;
226 int oldStartIndex = 0;
227 int oldEndIndex = oldHandlers.length;
228
229 // Skip over leading handlers that match.
230 while (newStartIndex < newEndIndex && oldStartIndex < oldEndIndex) {
231 EventHandler newHandler = newHandlers[newStartIndex];
232 EventHandler oldHandler = oldHandlers[oldStartIndex];
233 if (newHandler.type != oldHandler.type
234 || newHandler.listener != oldHandler.listener)
235 break;
236 ++newStartIndex;
237 ++oldStartIndex;
238 }
239
240 // Skip over trailing handlers that match.
241 while (newStartIndex < newEndIndex && oldStartIndex < oldEndIndex) {
242 EventHandler newHandler = newHandlers[newEndIndex - 1];
243 EventHandler oldHandler = oldHandlers[oldEndIndex - 1];
244 if (newHandler.type != oldHandler.type
245 || newHandler.listener != oldHandler.listener)
246 break;
247 --newEndIndex;
248 --oldEndIndex;
249 }
250
251 sky.Element root = _root as sky.Element;
252
253 for (int i = oldStartIndex; i < oldEndIndex; ++i) {
254 EventHandler oldHandler = oldHandlers[i];
255 root.removeEventListener(oldHandler.type, oldHandler.listener);
256 }
257
258 for (int i = newStartIndex; i < newEndIndex; ++i) {
259 EventHandler newHandler = newHandlers[i];
260 root.addEventListener(newHandler.type, newHandler.listener);
261 }
262 }
263
264 bool _syncNode(Node old) {
265 super._syncNode(old);
266
267 Element oldElement = old as Element; 299 Element oldElement = old as Element;
268 sky.Element root = _root as sky.Element; 300 sky.Element root = _root as sky.Element;
269 301
270 _syncEvents(oldElement); 302 _syncEvents(oldElement.events);
271 303
272 if (_class != oldElement._class) 304 if (_class != oldElement._class)
273 root.setAttribute('class', _class); 305 root.setAttribute('class', _class);
274 306
275 if (inlineStyle != oldElement.inlineStyle) 307 if (inlineStyle != oldElement.inlineStyle)
276 root.setAttribute('style', inlineStyle); 308 root.setAttribute('style', inlineStyle);
277 309
278 _syncChildren(oldElement); 310 _syncChildren(oldElement);
279
280 return false;
281 } 311 }
282 312
283 void _syncChildren(Element oldElement) { 313 void _syncChildren(Element oldElement) {
284 // print("---Syncing children of $_key");
285 sky.Element root = _root as sky.Element; 314 sky.Element root = _root as sky.Element;
286 assert(root != null); 315 assert(root != null);
287 316
288 var startIndex = 0; 317 var startIndex = 0;
289 var endIndex = _children.length; 318 var endIndex = _children.length;
290 319
291 var oldChildren = oldElement._children; 320 var oldChildren = oldElement._children;
292 var oldStartIndex = 0; 321 var oldStartIndex = 0;
293 var oldEndIndex = oldChildren.length; 322 var oldEndIndex = oldChildren.length;
294 323
295 sky.Node nextSibling = null; 324 sky.Node nextSibling = null;
296 Node currentNode = null; 325 Node currentNode = null;
297 Node oldNode = null; 326 Node oldNode = null;
298 327
299 void sync(int atIndex) { 328 void sync(int atIndex) {
300 if (currentNode._sync(oldNode, this, _root, nextSibling)) { 329 _children[atIndex] = _syncChild(currentNode, oldNode, _root, nextSibling);
301 // oldNode was stateful and must be retained.
302 currentNode = oldNode;
303 _children[atIndex] = currentNode;
304 }
305
306 assert(currentNode._root is sky.Node);
307 } 330 }
308 331
309 // Scan backwards from end of list while nodes can be directly synced 332 // Scan backwards from end of list while nodes can be directly synced
310 // without reordering. 333 // without reordering.
311 // print("...scanning backwards");
312 while (endIndex > startIndex && oldEndIndex > oldStartIndex) { 334 while (endIndex > startIndex && oldEndIndex > oldStartIndex) {
313 currentNode = _children[endIndex - 1]; 335 currentNode = _children[endIndex - 1];
314 oldNode = oldChildren[oldEndIndex - 1]; 336 oldNode = oldChildren[oldEndIndex - 1];
315 337
316 if (currentNode._key != oldNode._key) { 338 if (currentNode._key != oldNode._key) {
317 break; 339 break;
318 } 340 }
319 341
320 // print('> syncing matched at: $endIndex : $oldEndIndex');
321 endIndex--; 342 endIndex--;
322 oldEndIndex--; 343 oldEndIndex--;
323 sync(endIndex); 344 sync(endIndex);
324 nextSibling = currentNode._root; 345 nextSibling = currentNode._root;
325 } 346 }
326 347
327 HashMap<String, Node> oldNodeIdMap = null; 348 HashMap<String, Node> oldNodeIdMap = null;
328 349
329 bool oldNodeReordered(String key) { 350 bool oldNodeReordered(String key) {
330 return oldNodeIdMap != null && 351 return oldNodeIdMap != null &&
(...skipping 25 matching lines...) Expand all
356 bool searchForOldNode() { 377 bool searchForOldNode() {
357 if (currentNode is Text) 378 if (currentNode is Text)
358 return false; // Never re-order Text nodes. 379 return false; // Never re-order Text nodes.
359 380
360 ensureOldIdMap(); 381 ensureOldIdMap();
361 oldNode = oldNodeIdMap[currentNode._key]; 382 oldNode = oldNodeIdMap[currentNode._key];
362 if (oldNode == null) 383 if (oldNode == null)
363 return false; 384 return false;
364 385
365 oldNodeIdMap[currentNode._key] = null; // mark it reordered. 386 oldNodeIdMap[currentNode._key] = null; // mark it reordered.
366 // print("Reparenting ${currentNode._key}");
367 _parentInsertBefore(root, oldNode._root, nextSibling); 387 _parentInsertBefore(root, oldNode._root, nextSibling);
368 return true; 388 return true;
369 } 389 }
370 390
371 // Scan forwards, this time we may re-order; 391 // Scan forwards, this time we may re-order;
372 // print("...scanning forward");
373 nextSibling = root.firstChild; 392 nextSibling = root.firstChild;
374 while (startIndex < endIndex && oldStartIndex < oldEndIndex) { 393 while (startIndex < endIndex && oldStartIndex < oldEndIndex) {
375 currentNode = _children[startIndex]; 394 currentNode = _children[startIndex];
376 oldNode = oldChildren[oldStartIndex]; 395 oldNode = oldChildren[oldStartIndex];
377 396
378 if (currentNode._key == oldNode._key) { 397 if (currentNode._key == oldNode._key) {
379 // print('> syncing matched at: $startIndex : $oldStartIndex');
380 assert(currentNode.runtimeType == oldNode.runtimeType); 398 assert(currentNode.runtimeType == oldNode.runtimeType);
381 nextSibling = nextSibling.nextSibling; 399 nextSibling = nextSibling.nextSibling;
382 sync(startIndex); 400 sync(startIndex);
383 startIndex++; 401 startIndex++;
384 advanceOldStartIndex(); 402 advanceOldStartIndex();
385 continue; 403 continue;
386 } 404 }
387 405
388 oldNode = null; 406 oldNode = null;
389 if (searchForOldNode()) { 407 searchForOldNode();
390 // print('> reordered to $startIndex');
391 } else {
392 // print('> inserting at $startIndex');
393 }
394
395 sync(startIndex); 408 sync(startIndex);
396 startIndex++; 409 startIndex++;
397 } 410 }
398 411
399 // New insertions 412 // New insertions
400 oldNode = null; 413 oldNode = null;
401 // print('...processing remaining insertions');
402 while (startIndex < endIndex) { 414 while (startIndex < endIndex) {
403 // print('> inserting at $startIndex');
404 currentNode = _children[startIndex]; 415 currentNode = _children[startIndex];
405 sync(startIndex); 416 sync(startIndex);
406 startIndex++; 417 startIndex++;
407 } 418 }
408 419
409 // Removals 420 // Removals
410 // print('...processing remaining removals');
411 currentNode = null; 421 currentNode = null;
412 while (oldStartIndex < oldEndIndex) { 422 while (oldStartIndex < oldEndIndex) {
413 oldNode = oldChildren[oldStartIndex]; 423 oldNode = oldChildren[oldStartIndex];
414 // print('> ${oldNode._key} removing from $oldEndIndex'); 424 oldNode._remove();
415 oldNode._unmount();
416 advanceOldStartIndex(); 425 advanceOldStartIndex();
417 } 426 }
418 } 427 }
419 } 428 }
420 429
421 class Container extends Element { 430 class Container extends Element {
422 431
423 String get _tagName => 'div'; 432 String get _tagName => 'div';
424 433
425 static final Container _emptyContainer = new Container(); 434 static final Container _emptyContainer = new Container();
426 435
427 Node get _emptyNode => _emptyContainer; 436 RenderNode get _emptyNode => _emptyContainer;
428 437
429 Container({ 438 Container({
430 Object key, 439 Object key,
431 List<Node> children, 440 List<Node> children,
432 Style style, 441 Style style,
433 String inlineStyle 442 String inlineStyle
434 }) : super( 443 }) : super(
435 key: key, 444 key: key,
436 children: children, 445 children: children,
437 style: style, 446 style: style,
438 inlineStyle: inlineStyle 447 inlineStyle: inlineStyle
439 ); 448 );
440 } 449 }
441 450
442 class Image extends Element { 451 class Image extends Element {
443 452
444 String get _tagName => 'img'; 453 String get _tagName => 'img';
445 454
446 static final Image _emptyImage = new Image(); 455 static final Image _emptyImage = new Image();
447 456
448 Node get _emptyNode => _emptyImage; 457 RenderNode get _emptyNode => _emptyImage;
449 458
450 String src; 459 final String src;
451 int width; 460 final int width;
452 int height; 461 final int height;
453 462
454 Image({ 463 Image({
455 Object key, 464 Object key,
456 List<Node> children, 465 List<Node> children,
457 Style style, 466 Style style,
458 String inlineStyle, 467 String inlineStyle,
459 this.width, 468 this.width,
460 this.height, 469 this.height,
461 this.src 470 this.src
462 }) : super( 471 }) : super(
463 key: key, 472 key: key,
464 children: children, 473 children: children,
465 style: style, 474 style: style,
466 inlineStyle: inlineStyle 475 inlineStyle: inlineStyle
467 ); 476 );
468 477
469 bool _syncNode(Node old) { 478 void _syncNode(Node old) {
470 super._syncNode(old); 479 super._syncNode(old);
471 480
472 Image oldImage = old as Image; 481 Image oldImage = old as Image;
473 sky.HTMLImageElement skyImage = _root as sky.HTMLImageElement; 482 sky.HTMLImageElement skyImage = _root as sky.HTMLImageElement;
474 483
475 if (src != oldImage.src) 484 if (src != oldImage.src)
476 skyImage.src = src; 485 skyImage.src = src;
477 486
478 if (width != oldImage.width) 487 if (width != oldImage.width)
479 skyImage.style['width'] = '${width}px'; 488 skyImage.style['width'] = '${width}px';
480 489
481 if (height != oldImage.height) 490 if (height != oldImage.height)
482 skyImage.style['height'] = '${height}px'; 491 skyImage.style['height'] = '${height}px';
483
484 return false;
485 } 492 }
486 } 493 }
487 494
488 class Anchor extends Element { 495 class Anchor extends Element {
489 496
490 String get _tagName => 'a'; 497 String get _tagName => 'a';
491 498
492 static final Anchor _emptyAnchor = new Anchor(); 499 static final Anchor _emptyAnchor = new Anchor();
493 500
494 Node get _emptyNode => _emptyAnchor; 501 Node get _emptyNode => _emptyAnchor;
495 502
496 String href; 503 final String href;
497 int width; 504 final int width;
498 int height; 505 final int height;
499 506
500 Anchor({ 507 Anchor({
501 Object key, 508 Object key,
502 List<Node> children, 509 List<Node> children,
503 Style style, 510 Style style,
504 String inlineStyle, 511 String inlineStyle,
505 this.width, 512 this.width,
506 this.height, 513 this.height,
507 this.href 514 this.href
508 }) : super( 515 }) : super(
509 key: key, 516 key: key,
510 children: children, 517 children: children,
511 style: style, 518 style: style,
512 inlineStyle: inlineStyle 519 inlineStyle: inlineStyle
513 ); 520 );
514 521
515 bool _syncNode(Node old) { 522 void _syncNode(Node old) {
516 super._syncNode(old); 523 super._syncNode(old);
517 524
518 Anchor oldAnchor = old as Anchor; 525 Anchor oldAnchor = old as Anchor;
519 sky.HTMLAnchorElement skyAnchor = _root as sky.HTMLAnchorElement; 526 sky.HTMLAnchorElement skyAnchor = _root as sky.HTMLAnchorElement;
520 527
521 if (href != oldAnchor.href) 528 if (href != oldAnchor.href)
522 skyAnchor.href = href; 529 skyAnchor.href = href;
523
524 return false;
525 } 530 }
526 } 531 }
527 532
528 List<Component> _dirtyComponents = new List<Component>(); 533 List<Component> _dirtyComponents = new List<Component>();
534 Set<Component> _mountedComponents = new HashSet<Component>();
535 Set<Component> _unmountedComponents = new HashSet<Component>();
536
529 bool _buildScheduled = false; 537 bool _buildScheduled = false;
530 bool _inRenderDirtyComponents = false; 538 bool _inRenderDirtyComponents = false;
531 539
540 void _notifyMountStatusChanged() {
541 _unmountedComponents.forEach((c) => c.didUnmount());
542 _mountedComponents.forEach((c) => c.didMount());
543 _mountedComponents.clear();
544 _unmountedComponents.clear();
545 }
546
532 void _buildDirtyComponents() { 547 void _buildDirtyComponents() {
548 Stopwatch sw;
549 if (_shouldLogRenderDuration)
550 sw = new Stopwatch()..start();
551
533 try { 552 try {
534 _inRenderDirtyComponents = true; 553 _inRenderDirtyComponents = true;
535 Stopwatch sw = new Stopwatch()..start();
536 554
537 _dirtyComponents.sort((a, b) => a._order - b._order); 555 _dirtyComponents.sort((a, b) => a._order - b._order);
538 for (var comp in _dirtyComponents) { 556 for (var comp in _dirtyComponents) {
539 comp._buildIfDirty(); 557 comp._buildIfDirty();
540 } 558 }
541 559
542 _dirtyComponents.clear(); 560 _dirtyComponents.clear();
543 _buildScheduled = false; 561 _buildScheduled = false;
544
545 sw.stop();
546 if (_shouldLogRenderDuration)
547 print("Render took ${sw.elapsedMicroseconds} microseconds");
548 } finally { 562 } finally {
549 _inRenderDirtyComponents = false; 563 _inRenderDirtyComponents = false;
550 } 564 }
565
566 _notifyMountStatusChanged();
567
568 if (_shouldLogRenderDuration) {
569 sw.stop();
570 print("Render took ${sw.elapsedMicroseconds} microseconds");
571 }
551 } 572 }
552 573
553 void _scheduleComponentForRender(Component c) { 574 void _scheduleComponentForRender(Component c) {
554 assert(!_inRenderDirtyComponents); 575 assert(!_inRenderDirtyComponents);
555 _dirtyComponents.add(c); 576 _dirtyComponents.add(c);
556 577
557 if (!_buildScheduled) { 578 if (!_buildScheduled) {
558 _buildScheduled = true; 579 _buildScheduled = true;
559 new Future.microtask(_buildDirtyComponents); 580 new Future.microtask(_buildDirtyComponents);
560 } 581 }
561 } 582 }
562 583
584 EventMap _emptyEventMap = new EventMap();
585
563 abstract class Component extends Node { 586 abstract class Component extends Node {
564 bool _dirty = true; // components begin dirty because they haven't built. 587 bool get _isBuilding => _currentlyBuilding == this;
565 Node _vdom = null; 588 bool _dirty = true;
589
590 Node _built;
566 final int _order; 591 final int _order;
567 static int _currentOrder = 0; 592 static int _currentOrder = 0;
568 bool _stateful; 593 bool _stateful;
569 static Component _currentlyRendering; 594 static Component _currentlyBuilding;
570 595
571 Component({ Object key, bool stateful }) 596 Component({ Object key, bool stateful })
572 : _stateful = stateful != null ? stateful : false, 597 : _stateful = stateful != null ? stateful : false,
573 _order = _currentOrder + 1, 598 _order = _currentOrder + 1,
574 super(key:key); 599 super(key:key);
575 600
576 void didMount() {} 601 void didMount() {}
577 void didUnmount() {} 602 void didUnmount() {}
578 603
579 // TODO(rafaelw): It seems wrong to expose DOM at all. This is presently 604 // TODO(rafaelw): It seems wrong to expose DOM at all. This is presently
580 // needed to get sizing info. 605 // needed to get sizing info.
581 sky.Node getRoot() => _root; 606 sky.Node getRoot() => _root;
582 607
583 void _mount(Node parent, sky.ParentNode host, sky.Node insertBefore) { 608 void _remove() {
584 _parent = parent; 609 assert(_built != null);
585 610 assert(_root != null);
586 _syncInternal(host, insertBefore); 611 _built._remove();
587 612 _built = null;
588 didMount(); 613 _unmountedComponents.add(this);
614 super._remove();
589 } 615 }
590 616
591 void _unmount() { 617 bool _willSync(Node old) {
592 assert(_vdom != null); 618 Component oldComponent = old as Component;
593 assert(_root != null); 619 if (oldComponent == null || !oldComponent._stateful)
594 _vdom._unmount(); 620 return false;
595 _vdom = null; 621
596 _root = null; 622 // Make |this| the "old" Component
597 _parent = null; 623 _stateful = false;
598 _defunct = true; 624 _built = oldComponent._built;
599 didUnmount(); 625 assert(_built != null);
626
627 // Make |oldComponent| the "new" component
628 reflect.copyPublicFields(this, oldComponent);
629 oldComponent._built = null;
630 oldComponent._dirty = true;
631 return true;
600 } 632 }
601 633
602 bool _syncNode(Node old) { 634 /* There are three cases here:
635 * 1) Building for the first time:
636 * assert(_built == null && old == null)
637 * 2) Re-building (because a dirty flag got set):
638 * assert(_built != null && old == null)
639 * 3) Syncing against an old version
640 * assert(_built == null && old != null)
641 */
642 void _sync(Node old, sky.ParentNode host, sky.Node insertBefore) {
643 assert(!_defunct);
644 assert(_built == null || old == null);
645
603 Component oldComponent = old as Component; 646 Component oldComponent = old as Component;
604 647
605 if (!oldComponent._stateful) { 648 var oldBuilt;
606 _vdom = oldComponent._vdom; 649 if (oldComponent == null) {
607 _syncInternal(); 650 oldBuilt = _built;
608 651 } else {
609 return false; 652 assert(_built == null);
653 oldBuilt = oldComponent._built;
610 } 654 }
611 655
612 _stateful = false; // prevent iloop from _syncInternal below. 656 if (oldBuilt == null)
613 657 _mountedComponents.add(this);
614 reflect.copyPublicFields(this, oldComponent);
615
616 oldComponent._dirty = true;
617 _dirty = false;
618
619 oldComponent._syncInternal();
620 return true; // Retain old component
621 }
622
623 void _syncInternal([sky.Node host, sky.Node insertBefore]) {
624 if (!_dirty) {
625 assert(_vdom != null);
626 return;
627 }
628
629 var oldRendered = _vdom;
630 bool mounting = oldRendered == null;
631 658
632 int lastOrder = _currentOrder; 659 int lastOrder = _currentOrder;
633 _currentOrder = _order; 660 _currentOrder = _order;
634 _currentlyRendering = this; 661 _currentlyBuilding = this;
635 _vdom = build(); 662 _built = build();
636 _currentlyRendering = null; 663 _currentlyBuilding = null;
637 _currentOrder = lastOrder; 664 _currentOrder = lastOrder;
638 665
639 _vdom.events.addAll(events); 666 _built = _syncChild(_built, oldBuilt, host, insertBefore);
667 _dirty = false;
668 _root = _built._root;
640 669
641 _dirty = false; 670 _built.events.addAll(events);
642 671 _syncEvents(oldComponent != null ? oldComponent.events : _emptyEventMap);
643 // TODO(rafaelw): This eagerly removes the old DOM. It may be that a
644 // new component was built that could re-use some of it. Consider
645 // syncing the new VDOM against the old one.
646 if (oldRendered != null &&
647 _vdom.runtimeType != oldRendered.runtimeType) {
648 var oldRoot = oldRendered._root;
649 host = oldRoot.parentNode;
650 insertBefore = oldRoot.nextSibling;
651 oldRendered._unmount();
652 oldRendered = null;
653 }
654
655 if (_vdom._sync(oldRendered, this, host, insertBefore)) {
656 _vdom = oldRendered; // retain stateful component
657 }
658
659 _root = _vdom._root;
660 assert(_vdom._root is sky.Node);
661
662 if (mounting) {
663 didMount();
664 }
665 } 672 }
666 673
667 void _buildIfDirty() { 674 void _buildIfDirty() {
668 if (_defunct) 675 if (!_dirty || _defunct)
669 return; 676 return;
670 677
671 assert(_vdom != null); 678 assert(_root != null);
672 679 _sync(null, _root.parentNode, _root.nextSibling);
673 var vdom = _vdom;
674 while (vdom is Component) {
675 vdom = vdom._vdom;
676 }
677
678 assert(vdom._root != null);
679 _syncInternal();
680 } 680 }
681 681
682 void scheduleBuild() { 682 void scheduleBuild() {
683 setState(() {}); 683 setState(() {});
684 } 684 }
685 685
686 void setState(Function fn()) { 686 void setState(Function fn()) {
687 assert(_vdom != null || _defunct); // cannot setState before mounting.
688 _stateful = true; 687 _stateful = true;
689 fn(); 688 fn();
690 if (!_defunct && _currentlyRendering != this) { 689 if (_isBuilding || _dirty || _defunct)
691 _dirty = true; 690 return;
692 _scheduleComponentForRender(this); 691
693 } 692 _dirty = true;
693 _scheduleComponentForRender(this);
694 } 694 }
695 695
696 Node build(); 696 Node build();
697 } 697 }
698 698
699 abstract class App extends Component { 699 abstract class App extends Component {
700 sky.Node _host = null; 700 sky.Node _host = null;
701 App() : super(stateful: true) { 701 App() : super(stateful: true) {
702 _host = sky.document.createElement('div'); 702 _host = sky.document.createElement('div');
703 sky.document.appendChild(_host); 703 sky.document.appendChild(_host);
704 704
705 new Future.microtask(() { 705 new Future.microtask(() {
706 Stopwatch sw = new Stopwatch()..start(); 706 Stopwatch sw = new Stopwatch()..start();
707 707
708 _mount(null, _host, null); 708 _sync(null, _host, null);
709 assert(_root is sky.Node); 709 assert(_root is sky.Node);
710 710
711 sw.stop(); 711 sw.stop();
712 if (_shouldLogRenderDuration) 712 if (_shouldLogRenderDuration)
713 print("Initial build: ${sw.elapsedMicroseconds} microseconds"); 713 print("Initial build: ${sw.elapsedMicroseconds} microseconds");
714 }); 714 });
715 } 715 }
716 } 716 }
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698