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 part of fn; | 5 library fn; |
| 6 |
| 7 import 'dart:async'; |
| 8 import 'dart:collection'; |
| 9 import 'dart:sky' as sky; |
| 10 import 'reflect.dart' as reflect; |
| 11 |
| 12 bool _checkedMode; |
| 13 |
| 14 bool _debugWarnings() { |
| 15 void testFn(double i) {} |
| 16 |
| 17 if (_checkedMode == null) { |
| 18 _checkedMode = false; |
| 19 try { |
| 20 testFn('not a double'); |
| 21 } catch (ex) { |
| 22 _checkedMode = true; |
| 23 } |
| 24 } |
| 25 |
| 26 return _checkedMode; |
| 27 } |
| 28 |
| 29 class EventHandler { |
| 30 final String type; |
| 31 final sky.EventListener listener; |
| 32 |
| 33 EventHandler(this.type, this.listener); |
| 34 } |
| 35 |
| 36 class EventMap { |
| 37 final List<EventHandler> _handlers = new List<EventHandler>(); |
| 38 |
| 39 void listen(String type, sky.EventListener listener) { |
| 40 assert(listener != null); |
| 41 _handlers.add(new EventHandler(type, listener)); |
| 42 } |
| 43 |
| 44 void addAll(EventMap events) { |
| 45 _handlers.addAll(events._handlers); |
| 46 } |
| 47 } |
| 48 |
| 49 class Style { |
| 50 final String _className; |
| 51 static final Map<String, Style> _cache = new HashMap<String, Style>(); |
| 52 |
| 53 static int nextStyleId = 1; |
| 54 |
| 55 static String nextClassName(String styles) { |
| 56 assert(sky.document != null); |
| 57 String className = "style$nextStyleId"; |
| 58 nextStyleId++; |
| 59 |
| 60 sky.Element styleNode = sky.document.createElement('style'); |
| 61 styleNode.setChild(new sky.Text(".$className { $styles }")); |
| 62 sky.document.appendChild(styleNode); |
| 63 |
| 64 return className; |
| 65 } |
| 66 |
| 67 factory Style(String styles) { |
| 68 return _cache.putIfAbsent(styles, () { |
| 69 return new Style._internal(nextClassName(styles)); |
| 70 }); |
| 71 } |
| 72 |
| 73 Style._internal(this._className); |
| 74 } |
6 | 75 |
7 void _parentInsertBefore(sky.ParentNode parent, | 76 void _parentInsertBefore(sky.ParentNode parent, |
8 sky.Node node, | 77 sky.Node node, |
9 sky.Node ref) { | 78 sky.Node ref) { |
10 if (ref != null) { | 79 if (ref != null) { |
11 ref.insertBefore([node]); | 80 ref.insertBefore([node]); |
12 } else { | 81 } else { |
13 parent.appendChild(node); | 82 parent.appendChild(node); |
14 } | 83 } |
15 } | 84 } |
(...skipping 410 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
426 void _syncNode([Element old]) { | 495 void _syncNode([Element old]) { |
427 Anchor oldAnchor = old != null ? old as Anchor : _emptyAnchor; | 496 Anchor oldAnchor = old != null ? old as Anchor : _emptyAnchor; |
428 super._syncNode(oldAnchor); | 497 super._syncNode(oldAnchor); |
429 | 498 |
430 sky.HTMLAnchorElement skyAnchor = _root as sky.HTMLAnchorElement; | 499 sky.HTMLAnchorElement skyAnchor = _root as sky.HTMLAnchorElement; |
431 if (href != oldAnchor.href) { | 500 if (href != oldAnchor.href) { |
432 skyAnchor.href = href; | 501 skyAnchor.href = href; |
433 } | 502 } |
434 } | 503 } |
435 } | 504 } |
| 505 |
| 506 List<Component> _dirtyComponents = new List<Component>(); |
| 507 bool _renderScheduled = false; |
| 508 |
| 509 void _renderDirtyComponents() { |
| 510 Stopwatch sw = new Stopwatch()..start(); |
| 511 |
| 512 _dirtyComponents.sort((a, b) => a._order - b._order); |
| 513 for (var comp in _dirtyComponents) { |
| 514 comp._renderIfDirty(); |
| 515 } |
| 516 |
| 517 _dirtyComponents.clear(); |
| 518 _renderScheduled = false; |
| 519 sw.stop(); |
| 520 print("Render took ${sw.elapsedMicroseconds} microseconds"); |
| 521 } |
| 522 |
| 523 void _scheduleComponentForRender(Component c) { |
| 524 _dirtyComponents.add(c); |
| 525 |
| 526 if (!_renderScheduled) { |
| 527 _renderScheduled = true; |
| 528 new Future.microtask(_renderDirtyComponents); |
| 529 } |
| 530 } |
| 531 |
| 532 abstract class Component extends Node { |
| 533 bool _dirty = true; // components begin dirty because they haven't rendered. |
| 534 Node _rendered = null; |
| 535 bool _removed = false; |
| 536 final int _order; |
| 537 static int _currentOrder = 0; |
| 538 bool _stateful; |
| 539 static Component _currentlyRendering; |
| 540 |
| 541 Component({ Object key, bool stateful }) |
| 542 : _stateful = stateful != null ? stateful : false, |
| 543 _order = _currentOrder + 1, |
| 544 super(key:key); |
| 545 |
| 546 void willUnmount() {} |
| 547 |
| 548 void _remove() { |
| 549 assert(_rendered != null); |
| 550 assert(_root != null); |
| 551 willUnmount(); |
| 552 _rendered._remove(); |
| 553 _rendered = null; |
| 554 _root = null; |
| 555 _removed = true; |
| 556 } |
| 557 |
| 558 // TODO(rafaelw): It seems wrong to expose DOM at all. This is presently |
| 559 // needed to get sizing info. |
| 560 sky.Node getRoot() => _root; |
| 561 |
| 562 bool _sync(Node old, sky.Node host, sky.Node insertBefore) { |
| 563 Component oldComponent = old as Component; |
| 564 |
| 565 if (oldComponent == null || oldComponent == this) { |
| 566 _renderInternal(host, insertBefore); |
| 567 return false; |
| 568 } |
| 569 |
| 570 assert(oldComponent != null); |
| 571 assert(_dirty); |
| 572 assert(_rendered == null); |
| 573 |
| 574 if (oldComponent._stateful) { |
| 575 _stateful = false; // prevent iloop from _renderInternal below. |
| 576 |
| 577 reflect.copyPublicFields(this, oldComponent); |
| 578 |
| 579 oldComponent._dirty = true; |
| 580 _dirty = false; |
| 581 |
| 582 oldComponent._renderInternal(host, insertBefore); |
| 583 return true; // Must retain old component |
| 584 } |
| 585 |
| 586 _rendered = oldComponent._rendered; |
| 587 _renderInternal(host, insertBefore); |
| 588 return false; |
| 589 } |
| 590 |
| 591 void _renderInternal(sky.Node host, sky.Node insertBefore) { |
| 592 if (!_dirty) { |
| 593 assert(_rendered != null); |
| 594 return; |
| 595 } |
| 596 |
| 597 var oldRendered = _rendered; |
| 598 int lastOrder = _currentOrder; |
| 599 _currentOrder = _order; |
| 600 _currentlyRendering = this; |
| 601 _rendered = render(); |
| 602 _currentlyRendering = null; |
| 603 _currentOrder = lastOrder; |
| 604 |
| 605 _rendered.events.addAll(events); |
| 606 |
| 607 _dirty = false; |
| 608 |
| 609 // TODO(rafaelw): This prevents components from returning different node |
| 610 // types as their root node at different times. Consider relaxing. |
| 611 assert(oldRendered == null || |
| 612 _rendered.runtimeType == oldRendered.runtimeType); |
| 613 |
| 614 if (_rendered._sync(oldRendered, host, insertBefore)) { |
| 615 _rendered = oldRendered; // retain stateful component |
| 616 } |
| 617 _root = _rendered._root; |
| 618 assert(_rendered._root is sky.Node); |
| 619 } |
| 620 |
| 621 void _renderIfDirty() { |
| 622 assert(_rendered != null); |
| 623 assert(!_removed); |
| 624 |
| 625 var rendered = _rendered; |
| 626 while (rendered is Component) { |
| 627 rendered = rendered._rendered; |
| 628 } |
| 629 sky.Node root = rendered._root; |
| 630 |
| 631 _renderInternal(root.parentNode, root.nextSibling); |
| 632 } |
| 633 |
| 634 void setState(Function fn()) { |
| 635 assert(_rendered != null); // cannot setState before mounting. |
| 636 _stateful = true; |
| 637 fn(); |
| 638 if (_currentlyRendering != this) { |
| 639 _dirty = true; |
| 640 _scheduleComponentForRender(this); |
| 641 } |
| 642 } |
| 643 |
| 644 Node render(); |
| 645 } |
| 646 |
| 647 abstract class App extends Component { |
| 648 sky.Node _host = null; |
| 649 App() |
| 650 : super(stateful: true) { |
| 651 |
| 652 _host = sky.document.createElement('div'); |
| 653 sky.document.appendChild(_host); |
| 654 |
| 655 new Future.microtask(() { |
| 656 Stopwatch sw = new Stopwatch()..start(); |
| 657 _sync(null, _host, null); |
| 658 assert(_root is sky.Node); |
| 659 sw.stop(); |
| 660 print("Initial render: ${sw.elapsedMicroseconds} microseconds"); |
| 661 }); |
| 662 } |
| 663 } |
OLD | NEW |