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

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