OLD | NEW |
1 /** | 1 /** |
2 * @license | 2 * @license |
3 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | 3 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
4 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | 4 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
5 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | 5 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
6 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | 6 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
7 * Code distributed by Google as part of the polymer project is also | 7 * Code distributed by Google as part of the polymer project is also |
8 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | 8 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
9 */ | 9 */ |
10 window.PolymerGestures = {}; | 10 window.PolymerGestures = {}; |
(...skipping 573 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
584 0, | 584 0, |
585 0, | 585 0, |
586 function(){}, | 586 function(){}, |
587 false | 587 false |
588 ]; | 588 ]; |
589 | 589 |
590 var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined'); | 590 var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined'); |
591 | 591 |
592 var eventFactory = scope.eventFactory; | 592 var eventFactory = scope.eventFactory; |
593 | 593 |
| 594 // set of recognizers to run for the currently handled event |
| 595 var currentGestures; |
| 596 |
594 /** | 597 /** |
595 * This module is for normalizing events. Mouse and Touch events will be | 598 * This module is for normalizing events. Mouse and Touch events will be |
596 * collected here, and fire PointerEvents that have the same semantics, no | 599 * collected here, and fire PointerEvents that have the same semantics, no |
597 * matter the source. | 600 * matter the source. |
598 * Events fired: | 601 * Events fired: |
599 * - pointerdown: a pointing is added | 602 * - pointerdown: a pointing is added |
600 * - pointerup: a pointer is removed | 603 * - pointerup: a pointer is removed |
601 * - pointermove: a pointer is moved | 604 * - pointermove: a pointer is moved |
602 * - pointerover: a pointer crosses into an element | 605 * - pointerover: a pointer crosses into an element |
603 * - pointerout: a pointer leaves an element | 606 * - pointerout: a pointer leaves an element |
604 * - pointercancel: a pointer will no longer generate events | 607 * - pointercancel: a pointer will no longer generate events |
605 */ | 608 */ |
606 var dispatcher = { | 609 var dispatcher = { |
607 pointermap: new scope.PointerMap(), | 610 pointermap: new scope.PointerMap(), |
| 611 requiredGestures: new scope.PointerMap(), |
608 eventMap: Object.create(null), | 612 eventMap: Object.create(null), |
609 // Scope objects for native events. | 613 // Scope objects for native events. |
610 // This exists for ease of testing. | 614 // This exists for ease of testing. |
611 eventSources: Object.create(null), | 615 eventSources: Object.create(null), |
612 eventSourceList: [], | 616 eventSourceList: [], |
613 gestures: [], | 617 gestures: [], |
614 // map gesture event -> {listeners: int, index: gestures[int]} | 618 // map gesture event -> {listeners: int, index: gestures[int]} |
615 dependencyMap: { | 619 dependencyMap: { |
616 // make sure down and up are in the map to trigger "register" | 620 // make sure down and up are in the map to trigger "register" |
617 down: {listeners: 0, index: -1}, | 621 down: {listeners: 0, index: -1}, |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
658 }, | 662 }, |
659 unregister: function(element) { | 663 unregister: function(element) { |
660 var l = this.eventSourceList.length; | 664 var l = this.eventSourceList.length; |
661 for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { | 665 for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { |
662 // call eventsource register | 666 // call eventsource register |
663 es.unregister.call(es, element); | 667 es.unregister.call(es, element); |
664 } | 668 } |
665 }, | 669 }, |
666 // EVENTS | 670 // EVENTS |
667 down: function(inEvent) { | 671 down: function(inEvent) { |
| 672 this.requiredGestures.set(inEvent.pointerId, currentGestures); |
668 this.fireEvent('down', inEvent); | 673 this.fireEvent('down', inEvent); |
669 }, | 674 }, |
670 move: function(inEvent) { | 675 move: function(inEvent) { |
671 // pipe move events into gesture queue directly | 676 // pipe move events into gesture queue directly |
672 inEvent.type = 'move'; | 677 inEvent.type = 'move'; |
673 this.fillGestureQueue(inEvent); | 678 this.fillGestureQueue(inEvent); |
674 }, | 679 }, |
675 up: function(inEvent) { | 680 up: function(inEvent) { |
676 this.fireEvent('up', inEvent); | 681 this.fireEvent('up', inEvent); |
| 682 this.requiredGestures.delete(inEvent.pointerId); |
677 }, | 683 }, |
678 cancel: function(inEvent) { | 684 cancel: function(inEvent) { |
679 inEvent.tapPrevented = true; | 685 inEvent.tapPrevented = true; |
680 this.fireEvent('up', inEvent); | 686 this.fireEvent('up', inEvent); |
| 687 this.requiredGestures.delete(inEvent.pointerId); |
681 }, | 688 }, |
682 // LISTENER LOGIC | 689 // LISTENER LOGIC |
683 eventHandler: function(inEvent) { | 690 eventHandler: function(inEvent) { |
684 // This is used to prevent multiple dispatch of events from | 691 // This is used to prevent multiple dispatch of events from |
685 // platform events. This can happen when two elements in different scopes | 692 // platform events. This can happen when two elements in different scopes |
686 // are set up to create pointer events, which is relevant to Shadow DOM. | 693 // are set up to create pointer events, which is relevant to Shadow DOM. |
687 | 694 |
688 // TODO(dfreedm): make this check more granular, allow for minimal event g
eneration | 695 var type = inEvent.type; |
689 // e.g inEvent._handledByPG['tap'] and inEvent._handledByPG['track'], etc | 696 |
| 697 // only generate the list of desired events on "down" |
| 698 if (type === 'touchstart' || type === 'mousedown' || type === 'pointerdown
' || type === 'MSPointerDown') { |
| 699 if (!inEvent._handledByPG) { |
| 700 currentGestures = {}; |
| 701 } |
| 702 // map gesture names to ordered set of recognizers |
| 703 var gesturesWanted = inEvent.currentTarget._pgEvents; |
| 704 if (gesturesWanted) { |
| 705 var gk = Object.keys(gesturesWanted); |
| 706 for (var i = 0, r, ri, g; i < gk.length; i++) { |
| 707 // gesture |
| 708 g = gk[i]; |
| 709 if (gesturesWanted[g] > 0) { |
| 710 // lookup gesture recognizer |
| 711 r = this.dependencyMap[g]; |
| 712 // recognizer index |
| 713 ri = r ? r.index : -1; |
| 714 currentGestures[ri] = true; |
| 715 } |
| 716 } |
| 717 } |
| 718 } |
| 719 |
690 if (inEvent._handledByPG) { | 720 if (inEvent._handledByPG) { |
691 return; | 721 return; |
692 } | 722 } |
693 var type = inEvent.type; | |
694 var fn = this.eventMap && this.eventMap[type]; | 723 var fn = this.eventMap && this.eventMap[type]; |
695 if (fn) { | 724 if (fn) { |
696 fn(inEvent); | 725 fn(inEvent); |
697 } | 726 } |
698 inEvent._handledByPG = true; | 727 inEvent._handledByPG = true; |
699 }, | 728 }, |
700 // set up event listeners | 729 // set up event listeners |
701 listen: function(target, events) { | 730 listen: function(target, events) { |
702 for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) { | 731 for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) { |
703 this.addEvent(target, e); | 732 this.addEvent(target, e); |
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
775 t.dispatchEvent(inEvent); | 804 t.dispatchEvent(inEvent); |
776 // clone the event for the gesture system to process | 805 // clone the event for the gesture system to process |
777 // clone after dispatch to pick up gesture prevention code | 806 // clone after dispatch to pick up gesture prevention code |
778 var clone = this.cloneEvent(inEvent); | 807 var clone = this.cloneEvent(inEvent); |
779 clone.target = t; | 808 clone.target = t; |
780 this.fillGestureQueue(clone); | 809 this.fillGestureQueue(clone); |
781 } | 810 } |
782 }, | 811 }, |
783 gestureTrigger: function() { | 812 gestureTrigger: function() { |
784 // process the gesture queue | 813 // process the gesture queue |
785 for (var i = 0, e; i < this.gestureQueue.length; i++) { | 814 for (var i = 0, e, rg; i < this.gestureQueue.length; i++) { |
786 e = this.gestureQueue[i]; | 815 e = this.gestureQueue[i]; |
| 816 rg = e._requiredGestures; |
787 for (var j = 0, g, fn; j < this.gestures.length; j++) { | 817 for (var j = 0, g, fn; j < this.gestures.length; j++) { |
788 g = this.gestures[j]; | 818 // only run recognizer if an element in the source event's path is lis
tening for those gestures |
789 fn = g[e.type]; | 819 if (rg[j]) { |
790 if (g.enabled && fn) { | 820 g = this.gestures[j]; |
791 fn.call(g, e); | 821 fn = g[e.type]; |
| 822 if (fn) { |
| 823 fn.call(g, e); |
| 824 } |
792 } | 825 } |
793 } | 826 } |
794 } | 827 } |
795 this.gestureQueue.length = 0; | 828 this.gestureQueue.length = 0; |
796 }, | 829 }, |
797 fillGestureQueue: function(ev) { | 830 fillGestureQueue: function(ev) { |
798 // only trigger the gesture queue once | 831 // only trigger the gesture queue once |
799 if (!this.gestureQueue.length) { | 832 if (!this.gestureQueue.length) { |
800 requestAnimationFrame(this.boundGestureTrigger); | 833 requestAnimationFrame(this.boundGestureTrigger); |
801 } | 834 } |
| 835 ev._requiredGestures = this.requiredGestures.get(ev.pointerId); |
802 this.gestureQueue.push(ev); | 836 this.gestureQueue.push(ev); |
803 } | 837 } |
804 }; | 838 }; |
805 dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); | 839 dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); |
806 dispatcher.boundGestureTrigger = dispatcher.gestureTrigger.bind(dispatcher); | 840 dispatcher.boundGestureTrigger = dispatcher.gestureTrigger.bind(dispatcher); |
807 scope.dispatcher = dispatcher; | 841 scope.dispatcher = dispatcher; |
808 | 842 |
809 /** | 843 /** |
810 * Listen for `gesture` on `node` with the `handler` function | 844 * Listen for `gesture` on `node` with the `handler` function |
811 * | 845 * |
812 * If `handler` is the first listener for `gesture`, the underlying gesture re
cognizer is then enabled. | 846 * If `handler` is the first listener for `gesture`, the underlying gesture re
cognizer is then enabled. |
813 * | 847 * |
814 * @param {Element} node | 848 * @param {Element} node |
815 * @param {string} gesture | 849 * @param {string} gesture |
816 * @return Boolean `gesture` is a valid gesture | 850 * @return Boolean `gesture` is a valid gesture |
817 */ | 851 */ |
818 scope.activateGesture = function(node, gesture) { | 852 scope.activateGesture = function(node, gesture) { |
819 var g = gesture.toLowerCase(); | 853 var g = gesture.toLowerCase(); |
820 var dep = dispatcher.dependencyMap[g]; | 854 var dep = dispatcher.dependencyMap[g]; |
821 if (dep) { | 855 if (dep) { |
822 var recognizer = dispatcher.gestures[dep.index]; | 856 var recognizer = dispatcher.gestures[dep.index]; |
823 if (dep.listeners === 0) { | |
824 if (recognizer) { | |
825 recognizer.enabled = true; | |
826 } | |
827 } | |
828 dep.listeners++; | |
829 if (!node._pgListeners) { | 857 if (!node._pgListeners) { |
830 dispatcher.register(node); | 858 dispatcher.register(node); |
831 node._pgListeners = 0; | 859 node._pgListeners = 0; |
832 } | 860 } |
833 // TODO(dfreedm): re-evaluate bookkeeping to avoid using attributes | 861 // TODO(dfreedm): re-evaluate bookkeeping to avoid using attributes |
834 if (recognizer) { | 862 if (recognizer) { |
835 var touchAction = recognizer.defaultActions && recognizer.defaultActions
[g]; | 863 var touchAction = recognizer.defaultActions && recognizer.defaultActions
[g]; |
836 var actionNode; | 864 var actionNode; |
837 switch(node.nodeType) { | 865 switch(node.nodeType) { |
838 case Node.ELEMENT_NODE: | 866 case Node.ELEMENT_NODE: |
839 actionNode = node; | 867 actionNode = node; |
840 break; | 868 break; |
841 case Node.DOCUMENT_FRAGMENT_NODE: | 869 case Node.DOCUMENT_FRAGMENT_NODE: |
842 actionNode = node.host; | 870 actionNode = node.host; |
843 break; | 871 break; |
844 default: | 872 default: |
845 actionNode = null; | 873 actionNode = null; |
846 break; | 874 break; |
847 } | 875 } |
848 if (touchAction && actionNode && !actionNode.hasAttribute('touch-action'
)) { | 876 if (touchAction && actionNode && !actionNode.hasAttribute('touch-action'
)) { |
849 actionNode.setAttribute('touch-action', touchAction); | 877 actionNode.setAttribute('touch-action', touchAction); |
850 } | 878 } |
851 } | 879 } |
| 880 if (!node._pgEvents) { |
| 881 node._pgEvents = {}; |
| 882 } |
| 883 node._pgEvents[g] = (node._pgEvents[g] || 0) + 1; |
852 node._pgListeners++; | 884 node._pgListeners++; |
853 } | 885 } |
854 return Boolean(dep); | 886 return Boolean(dep); |
855 }; | 887 }; |
856 | 888 |
857 /** | 889 /** |
858 * | 890 * |
859 * Listen for `gesture` from `node` with `handler` function. | 891 * Listen for `gesture` from `node` with `handler` function. |
860 * | 892 * |
861 * @param {Element} node | 893 * @param {Element} node |
(...skipping 14 matching lines...) Expand all Loading... |
876 * If `handler` is the last listener for `gesture`, the underlying gesture rec
ognizer is disabled. | 908 * If `handler` is the last listener for `gesture`, the underlying gesture rec
ognizer is disabled. |
877 * | 909 * |
878 * @param {Element} node | 910 * @param {Element} node |
879 * @param {string} gesture | 911 * @param {string} gesture |
880 * @return Boolean `gesture` is a valid gesture | 912 * @return Boolean `gesture` is a valid gesture |
881 */ | 913 */ |
882 scope.deactivateGesture = function(node, gesture) { | 914 scope.deactivateGesture = function(node, gesture) { |
883 var g = gesture.toLowerCase(); | 915 var g = gesture.toLowerCase(); |
884 var dep = dispatcher.dependencyMap[g]; | 916 var dep = dispatcher.dependencyMap[g]; |
885 if (dep) { | 917 if (dep) { |
886 if (dep.listeners > 0) { | |
887 dep.listeners--; | |
888 } | |
889 if (dep.listeners === 0) { | |
890 var recognizer = dispatcher.gestures[dep.index]; | |
891 if (recognizer) { | |
892 recognizer.enabled = false; | |
893 } | |
894 } | |
895 if (node._pgListeners > 0) { | 918 if (node._pgListeners > 0) { |
896 node._pgListeners--; | 919 node._pgListeners--; |
897 } | 920 } |
898 if (node._pgListeners === 0) { | 921 if (node._pgListeners === 0) { |
899 dispatcher.unregister(node); | 922 dispatcher.unregister(node); |
900 } | 923 } |
| 924 if (node._pgEvents) { |
| 925 if (node._pgEvents[g] > 0) { |
| 926 node._pgEvents[g]--; |
| 927 } else { |
| 928 node._pgEvents[g] = 0; |
| 929 } |
| 930 } |
901 } | 931 } |
902 return Boolean(dep); | 932 return Boolean(dep); |
903 }; | 933 }; |
904 | 934 |
905 /** | 935 /** |
906 * Stop listening for `gesture` from `node` with `handler` function. | 936 * Stop listening for `gesture` from `node` with `handler` function. |
907 * | 937 * |
908 * @param {Element} node | 938 * @param {Element} node |
909 * @param {string} gesture | 939 * @param {string} gesture |
910 * @param {Function} handler | 940 * @param {Function} handler |
(...skipping 447 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1358 var pointermap = dispatcher.pointermap; | 1388 var pointermap = dispatcher.pointermap; |
1359 var HAS_BITMAP_TYPE = window.MSPointerEvent && typeof window.MSPointerEvent.MS
POINTER_TYPE_MOUSE === 'number'; | 1389 var HAS_BITMAP_TYPE = window.MSPointerEvent && typeof window.MSPointerEvent.MS
POINTER_TYPE_MOUSE === 'number'; |
1360 var msEvents = { | 1390 var msEvents = { |
1361 events: [ | 1391 events: [ |
1362 'MSPointerDown', | 1392 'MSPointerDown', |
1363 'MSPointerMove', | 1393 'MSPointerMove', |
1364 'MSPointerUp', | 1394 'MSPointerUp', |
1365 'MSPointerCancel', | 1395 'MSPointerCancel', |
1366 ], | 1396 ], |
1367 register: function(target) { | 1397 register: function(target) { |
1368 if (target !== document) { | |
1369 return; | |
1370 } | |
1371 dispatcher.listen(target, this.events); | 1398 dispatcher.listen(target, this.events); |
1372 }, | 1399 }, |
1373 unregister: function(target) { | 1400 unregister: function(target) { |
1374 dispatcher.unlisten(target, this.events); | 1401 dispatcher.unlisten(target, this.events); |
1375 }, | 1402 }, |
1376 POINTER_TYPES: [ | 1403 POINTER_TYPES: [ |
1377 '', | 1404 '', |
1378 'unavailable', | 1405 'unavailable', |
1379 'touch', | 1406 'touch', |
1380 'pen', | 1407 'pen', |
(...skipping 11 matching lines...) Expand all Loading... |
1392 cleanup: function(id) { | 1419 cleanup: function(id) { |
1393 pointermap['delete'](id); | 1420 pointermap['delete'](id); |
1394 }, | 1421 }, |
1395 MSPointerDown: function(inEvent) { | 1422 MSPointerDown: function(inEvent) { |
1396 var e = this.prepareEvent(inEvent); | 1423 var e = this.prepareEvent(inEvent); |
1397 e.target = scope.findTarget(inEvent); | 1424 e.target = scope.findTarget(inEvent); |
1398 pointermap.set(inEvent.pointerId, e.target); | 1425 pointermap.set(inEvent.pointerId, e.target); |
1399 dispatcher.down(e); | 1426 dispatcher.down(e); |
1400 }, | 1427 }, |
1401 MSPointerMove: function(inEvent) { | 1428 MSPointerMove: function(inEvent) { |
1402 var e = this.prepareEvent(inEvent); | 1429 var target = pointermap.get(inEvent.pointerId); |
1403 e.target = pointermap.get(e.pointerId); | 1430 if (target) { |
1404 dispatcher.move(e); | 1431 var e = this.prepareEvent(inEvent); |
| 1432 e.target = target; |
| 1433 dispatcher.move(e); |
| 1434 } |
1405 }, | 1435 }, |
1406 MSPointerUp: function(inEvent) { | 1436 MSPointerUp: function(inEvent) { |
1407 var e = this.prepareEvent(inEvent); | 1437 var e = this.prepareEvent(inEvent); |
1408 e.relatedTarget = scope.findTarget(inEvent); | 1438 e.relatedTarget = scope.findTarget(inEvent); |
1409 e.target = pointermap.get(e.pointerId); | 1439 e.target = pointermap.get(e.pointerId); |
1410 dispatcher.up(e); | 1440 dispatcher.up(e); |
1411 this.cleanup(inEvent.pointerId); | 1441 this.cleanup(inEvent.pointerId); |
1412 }, | 1442 }, |
1413 MSPointerCancel: function(inEvent) { | 1443 MSPointerCancel: function(inEvent) { |
1414 var e = this.prepareEvent(inEvent); | 1444 var e = this.prepareEvent(inEvent); |
(...skipping 25 matching lines...) Expand all Loading... |
1440 'pointermove', | 1470 'pointermove', |
1441 'pointerup', | 1471 'pointerup', |
1442 'pointercancel' | 1472 'pointercancel' |
1443 ], | 1473 ], |
1444 prepareEvent: function(inEvent) { | 1474 prepareEvent: function(inEvent) { |
1445 var e = dispatcher.cloneEvent(inEvent); | 1475 var e = dispatcher.cloneEvent(inEvent); |
1446 e._source = 'pointer'; | 1476 e._source = 'pointer'; |
1447 return e; | 1477 return e; |
1448 }, | 1478 }, |
1449 register: function(target) { | 1479 register: function(target) { |
1450 if (target !== document) { | |
1451 return; | |
1452 } | |
1453 dispatcher.listen(target, this.events); | 1480 dispatcher.listen(target, this.events); |
1454 }, | 1481 }, |
1455 unregister: function(target) { | 1482 unregister: function(target) { |
1456 dispatcher.unlisten(target, this.events); | 1483 dispatcher.unlisten(target, this.events); |
1457 }, | 1484 }, |
1458 cleanup: function(id) { | 1485 cleanup: function(id) { |
1459 pointermap['delete'](id); | 1486 pointermap['delete'](id); |
1460 }, | 1487 }, |
1461 pointerdown: function(inEvent) { | 1488 pointerdown: function(inEvent) { |
1462 var e = this.prepareEvent(inEvent); | 1489 var e = this.prepareEvent(inEvent); |
1463 e.target = scope.findTarget(inEvent); | 1490 e.target = scope.findTarget(inEvent); |
1464 pointermap.set(e.pointerId, e.target); | 1491 pointermap.set(e.pointerId, e.target); |
1465 dispatcher.down(e); | 1492 dispatcher.down(e); |
1466 }, | 1493 }, |
1467 pointermove: function(inEvent) { | 1494 pointermove: function(inEvent) { |
1468 var e = this.prepareEvent(inEvent); | 1495 var target = pointermap.get(inEvent.pointerId); |
1469 e.target = pointermap.get(e.pointerId); | 1496 if (target) { |
1470 dispatcher.move(e); | 1497 var e = this.prepareEvent(inEvent); |
| 1498 e.target = target; |
| 1499 dispatcher.move(e); |
| 1500 } |
1471 }, | 1501 }, |
1472 pointerup: function(inEvent) { | 1502 pointerup: function(inEvent) { |
1473 var e = this.prepareEvent(inEvent); | 1503 var e = this.prepareEvent(inEvent); |
1474 e.relatedTarget = scope.findTarget(inEvent); | 1504 e.relatedTarget = scope.findTarget(inEvent); |
1475 e.target = pointermap.get(e.pointerId); | 1505 e.target = pointermap.get(e.pointerId); |
1476 dispatcher.up(e); | 1506 dispatcher.up(e); |
1477 this.cleanup(inEvent.pointerId); | 1507 this.cleanup(inEvent.pointerId); |
1478 }, | 1508 }, |
1479 pointercancel: function(inEvent) { | 1509 pointercancel: function(inEvent) { |
1480 var e = this.prepareEvent(inEvent); | 1510 var e = this.prepareEvent(inEvent); |
(...skipping 2180 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3661 | 3691 |
3662 /* | 3692 /* |
3663 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | 3693 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
3664 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | 3694 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
3665 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | 3695 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
3666 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | 3696 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
3667 * Code distributed by Google as part of the polymer project is also | 3697 * Code distributed by Google as part of the polymer project is also |
3668 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | 3698 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
3669 */ | 3699 */ |
3670 Polymer = { | 3700 Polymer = { |
3671 version: '0.3.5-5d00e4b' | 3701 version: '0.4.0-d66a86e' |
3672 }; | 3702 }; |
3673 | 3703 |
3674 /* | 3704 /* |
3675 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | 3705 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
3676 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | 3706 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
3677 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | 3707 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
3678 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | 3708 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
3679 * Code distributed by Google as part of the polymer project is also | 3709 * Code distributed by Google as part of the polymer project is also |
3680 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | 3710 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
3681 */ | 3711 */ |
3682 | 3712 |
3683 // TODO(sorvell): this ensures Polymer is an object and not a function | 3713 // TODO(sorvell): this ensures Polymer is an object and not a function |
3684 // Platform is currently defining it as a function to allow for async loading | 3714 // Platform is currently defining it as a function to allow for async loading |
3685 // of polymer; once we refine the loading process this likely goes away. | 3715 // of polymer; once we refine the loading process this likely goes away. |
3686 if (typeof window.Polymer === 'function') { | 3716 if (typeof window.Polymer === 'function') { |
3687 Polymer = {}; | 3717 Polymer = {}; |
3688 } | 3718 } |
3689 | 3719 |
3690 | 3720 |
3691 /* | 3721 /* |
3692 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | 3722 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
3693 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | 3723 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
3694 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | 3724 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
3695 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | 3725 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
3696 * Code distributed by Google as part of the polymer project is also | 3726 * Code distributed by Google as part of the polymer project is also |
3697 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | 3727 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
3698 */ | 3728 */ |
3699 | 3729 |
3700 (function(scope) { | 3730 /* |
| 3731 » On supported platforms, platform.js is not needed. To retain compatibili
ty |
| 3732 » with the polyfills, we stub out minimal functionality. |
| 3733 */ |
| 3734 if (!window.Platform) { |
| 3735 logFlags = window.logFlags || {}; |
3701 | 3736 |
3702 // copy own properties from 'api' to 'prototype, with name hinting for 'super' | 3737 |
3703 function extend(prototype, api) { | 3738 Platform = { |
3704 if (prototype && api) { | 3739 » flush: function() {} |
3705 // use only own properties of 'api' | 3740 }; |
3706 Object.getOwnPropertyNames(api).forEach(function(n) { | 3741 |
3707 // acquire property descriptor | 3742 CustomElements = { |
3708 var pd = Object.getOwnPropertyDescriptor(api, n); | 3743 » useNative: true, |
3709 if (pd) { | 3744 ready: true, |
3710 // clone property via descriptor | 3745 takeRecords: function() {}, |
3711 Object.defineProperty(prototype, n, pd); | 3746 instanceof: function(obj, base) { |
3712 // cache name-of-method for 'super' engine | 3747 return obj instanceof base; |
3713 if (typeof pd.value == 'function') { | |
3714 // hint the 'super' engine | |
3715 pd.value.nom = n; | |
3716 } | |
3717 } | |
3718 }); | |
3719 } | 3748 } |
3720 return prototype; | 3749 }; |
3721 } | |
3722 | 3750 |
3723 // exports | 3751 HTMLImports = { |
| 3752 » useNative: true |
| 3753 }; |
3724 | 3754 |
3725 scope.extend = extend; | 3755 |
| 3756 addEventListener('HTMLImportsLoaded', function() { |
| 3757 document.dispatchEvent( |
| 3758 new CustomEvent('WebComponentsReady', {bubbles: true}) |
| 3759 ); |
| 3760 }); |
3726 | 3761 |
3727 })(Polymer); | 3762 |
| 3763 // ShadowDOM |
| 3764 ShadowDOMPolyfill = null; |
| 3765 wrap = unwrap = function(n){ |
| 3766 return n; |
| 3767 }; |
| 3768 |
| 3769 } |
3728 | 3770 |
3729 /* | 3771 /* |
3730 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | 3772 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
3731 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | |
3732 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | |
3733 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | |
3734 * Code distributed by Google as part of the polymer project is also | |
3735 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | |
3736 */ | |
3737 | |
3738 (function(scope) { | |
3739 | |
3740 // usage | |
3741 | |
3742 // invoke cb.call(this) in 100ms, unless the job is re-registered, | |
3743 // which resets the timer | |
3744 // | |
3745 // this.myJob = this.job(this.myJob, cb, 100) | |
3746 // | |
3747 // returns a job handle which can be used to re-register a job | |
3748 | |
3749 var Job = function(inContext) { | |
3750 this.context = inContext; | |
3751 this.boundComplete = this.complete.bind(this) | |
3752 }; | |
3753 Job.prototype = { | |
3754 go: function(callback, wait) { | |
3755 this.callback = callback; | |
3756 var h; | |
3757 if (!wait) { | |
3758 h = requestAnimationFrame(this.boundComplete); | |
3759 this.handle = function() { | |
3760 cancelAnimationFrame(h); | |
3761 } | |
3762 } else { | |
3763 h = setTimeout(this.boundComplete, wait); | |
3764 this.handle = function() { | |
3765 clearTimeout(h); | |
3766 } | |
3767 } | |
3768 }, | |
3769 stop: function() { | |
3770 if (this.handle) { | |
3771 this.handle(); | |
3772 this.handle = null; | |
3773 } | |
3774 }, | |
3775 complete: function() { | |
3776 if (this.handle) { | |
3777 this.stop(); | |
3778 this.callback.call(this.context); | |
3779 } | |
3780 } | |
3781 }; | |
3782 | |
3783 function job(job, callback, wait) { | |
3784 if (job) { | |
3785 job.stop(); | |
3786 } else { | |
3787 job = new Job(this); | |
3788 } | |
3789 job.go(callback, wait); | |
3790 return job; | |
3791 } | |
3792 | |
3793 // exports | |
3794 | |
3795 scope.job = job; | |
3796 | |
3797 })(Polymer); | |
3798 | |
3799 /* | |
3800 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
3801 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | |
3802 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | |
3803 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | |
3804 * Code distributed by Google as part of the polymer project is also | |
3805 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | |
3806 */ | |
3807 | |
3808 (function(scope) { | |
3809 | |
3810 var registry = {}; | |
3811 | |
3812 HTMLElement.register = function(tag, prototype) { | |
3813 registry[tag] = prototype; | |
3814 } | |
3815 | |
3816 // get prototype mapped to node <tag> | |
3817 HTMLElement.getPrototypeForTag = function(tag) { | |
3818 var prototype = !tag ? HTMLElement.prototype : registry[tag]; | |
3819 // TODO(sjmiles): creating <tag> is likely to have wasteful side-effects | |
3820 return prototype || Object.getPrototypeOf(document.createElement(tag)); | |
3821 }; | |
3822 | |
3823 // we have to flag propagation stoppage for the event dispatcher | |
3824 var originalStopPropagation = Event.prototype.stopPropagation; | |
3825 Event.prototype.stopPropagation = function() { | |
3826 this.cancelBubble = true; | |
3827 originalStopPropagation.apply(this, arguments); | |
3828 }; | |
3829 | |
3830 // TODO(sorvell): remove when we're sure imports does not need | |
3831 // to load stylesheets | |
3832 /* | |
3833 HTMLImports.importer.preloadSelectors += | |
3834 ', polymer-element link[rel=stylesheet]'; | |
3835 */ | |
3836 })(Polymer); | |
3837 | |
3838 /* | |
3839 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
3840 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | 3773 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
3841 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | 3774 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
3842 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | 3775 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
3843 * Code distributed by Google as part of the polymer project is also | 3776 * Code distributed by Google as part of the polymer project is also |
3844 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | 3777 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
3845 */ | 3778 */ |
3846 | 3779 |
| 3780 (function(scope) { |
| 3781 |
| 3782 var hasNative = ('import' in document.createElement('link')); |
| 3783 var useNative = hasNative; |
| 3784 |
| 3785 isIE = /Trident/.test(navigator.userAgent); |
| 3786 |
| 3787 // TODO(sorvell): SD polyfill intrusion |
| 3788 var hasShadowDOMPolyfill = Boolean(window.ShadowDOMPolyfill); |
| 3789 var wrap = function(node) { |
| 3790 return hasShadowDOMPolyfill ? ShadowDOMPolyfill.wrapIfNeeded(node) : node; |
| 3791 }; |
| 3792 var mainDoc = wrap(document); |
| 3793 |
| 3794 // NOTE: We cannot polyfill document.currentScript because it's not possible |
| 3795 // both to override and maintain the ability to capture the native value; |
| 3796 // therefore we choose to expose _currentScript both when native imports |
| 3797 // and the polyfill are in use. |
| 3798 var currentScriptDescriptor = { |
| 3799 get: function() { |
| 3800 var script = HTMLImports.currentScript || document.currentScript || |
| 3801 // NOTE: only works when called in synchronously executing code. |
| 3802 // readyState should check if `loading` but IE10 is |
| 3803 // interactive when scripts run so we cheat. |
| 3804 (document.readyState !== 'complete' ? |
| 3805 document.scripts[document.scripts.length - 1] : null); |
| 3806 return wrap(script); |
| 3807 }, |
| 3808 configurable: true |
| 3809 }; |
| 3810 |
| 3811 Object.defineProperty(document, '_currentScript', currentScriptDescriptor); |
| 3812 Object.defineProperty(mainDoc, '_currentScript', currentScriptDescriptor); |
| 3813 |
| 3814 // call a callback when all HTMLImports in the document at call (or at least |
| 3815 // document ready) time have loaded. |
| 3816 // 1. ensure the document is in a ready state (has dom), then |
| 3817 // 2. watch for loading of imports and call callback when done |
| 3818 function whenImportsReady(callback, doc) { |
| 3819 doc = doc || mainDoc; |
| 3820 // if document is loading, wait and try again |
| 3821 whenDocumentReady(function() { |
| 3822 watchImportsLoad(callback, doc); |
| 3823 }, doc); |
| 3824 } |
| 3825 |
| 3826 // call the callback when the document is in a ready state (has dom) |
| 3827 var requiredReadyState = isIE ? 'complete' : 'interactive'; |
| 3828 var READY_EVENT = 'readystatechange'; |
| 3829 function isDocumentReady(doc) { |
| 3830 return (doc.readyState === 'complete' || |
| 3831 doc.readyState === requiredReadyState); |
| 3832 } |
| 3833 |
| 3834 // call <callback> when we ensure the document is in a ready state |
| 3835 function whenDocumentReady(callback, doc) { |
| 3836 if (!isDocumentReady(doc)) { |
| 3837 var checkReady = function() { |
| 3838 if (doc.readyState === 'complete' || |
| 3839 doc.readyState === requiredReadyState) { |
| 3840 doc.removeEventListener(READY_EVENT, checkReady); |
| 3841 whenDocumentReady(callback, doc); |
| 3842 } |
| 3843 }; |
| 3844 doc.addEventListener(READY_EVENT, checkReady); |
| 3845 } else if (callback) { |
| 3846 callback(); |
| 3847 } |
| 3848 } |
| 3849 |
| 3850 function markTargetLoaded(event) { |
| 3851 event.target.__loaded = true; |
| 3852 } |
| 3853 |
| 3854 // call <callback> when we ensure all imports have loaded |
| 3855 function watchImportsLoad(callback, doc) { |
| 3856 var imports = doc.querySelectorAll('link[rel=import]'); |
| 3857 var loaded = 0, l = imports.length; |
| 3858 function checkDone(d) { |
| 3859 if (loaded == l) { |
| 3860 callback && callback(); |
| 3861 } |
| 3862 } |
| 3863 function loadedImport(e) { |
| 3864 markTargetLoaded(e); |
| 3865 loaded++; |
| 3866 checkDone(); |
| 3867 } |
| 3868 if (l) { |
| 3869 for (var i=0, imp; (i<l) && (imp=imports[i]); i++) { |
| 3870 if (isImportLoaded(imp)) { |
| 3871 loadedImport.call(imp, {target: imp}); |
| 3872 } else { |
| 3873 imp.addEventListener('load', loadedImport); |
| 3874 imp.addEventListener('error', loadedImport); |
| 3875 } |
| 3876 } |
| 3877 } else { |
| 3878 checkDone(); |
| 3879 } |
| 3880 } |
| 3881 |
| 3882 // NOTE: test for native imports loading is based on explicitly watching |
| 3883 // all imports (see below). |
| 3884 function isImportLoaded(link) { |
| 3885 return useNative ? link.__loaded : link.__importParsed; |
| 3886 } |
| 3887 |
| 3888 // TODO(sorvell): Workaround for |
| 3889 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=25007, should be removed when |
| 3890 // this bug is addressed. |
| 3891 // (1) Install a mutation observer to see when HTMLImports have loaded |
| 3892 // (2) if this script is run during document load it will watch any existing |
| 3893 // imports for loading. |
| 3894 // |
| 3895 // NOTE: The workaround has restricted functionality: (1) it's only compatible |
| 3896 // with imports that are added to document.head since the mutation observer |
| 3897 // watches only head for perf reasons, (2) it requires this script |
| 3898 // to run before any imports have completed loading. |
| 3899 if (useNative) { |
| 3900 new MutationObserver(function(mxns) { |
| 3901 for (var i=0, l=mxns.length, m; (i < l) && (m=mxns[i]); i++) { |
| 3902 if (m.addedNodes) { |
| 3903 handleImports(m.addedNodes); |
| 3904 } |
| 3905 } |
| 3906 }).observe(document.head, {childList: true}); |
| 3907 |
| 3908 function handleImports(nodes) { |
| 3909 for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) { |
| 3910 if (isImport(n)) { |
| 3911 handleImport(n); |
| 3912 } |
| 3913 } |
| 3914 } |
| 3915 |
| 3916 function isImport(element) { |
| 3917 return element.localName === 'link' && element.rel === 'import'; |
| 3918 } |
| 3919 |
| 3920 function handleImport(element) { |
| 3921 var loaded = element.import; |
| 3922 if (loaded) { |
| 3923 markTargetLoaded({target: element}); |
| 3924 } else { |
| 3925 element.addEventListener('load', markTargetLoaded); |
| 3926 element.addEventListener('error', markTargetLoaded); |
| 3927 } |
| 3928 } |
| 3929 |
| 3930 // make sure to catch any imports that are in the process of loading |
| 3931 // when this script is run. |
| 3932 (function() { |
| 3933 if (document.readyState === 'loading') { |
| 3934 var imports = document.querySelectorAll('link[rel=import]'); |
| 3935 for (var i=0, l=imports.length, imp; (i<l) && (imp=imports[i]); i++) { |
| 3936 handleImport(imp); |
| 3937 } |
| 3938 } |
| 3939 })(); |
| 3940 |
| 3941 } |
| 3942 |
| 3943 // Fire the 'HTMLImportsLoaded' event when imports in document at load time |
| 3944 // have loaded. This event is required to simulate the script blocking |
| 3945 // behavior of native imports. A main document script that needs to be sure |
| 3946 // imports have loaded should wait for this event. |
| 3947 whenImportsReady(function() { |
| 3948 HTMLImports.ready = true; |
| 3949 HTMLImports.readyTime = new Date().getTime(); |
| 3950 mainDoc.dispatchEvent( |
| 3951 new CustomEvent('HTMLImportsLoaded', {bubbles: true}) |
| 3952 ); |
| 3953 }); |
| 3954 |
| 3955 // exports |
| 3956 scope.useNative = useNative; |
| 3957 scope.isImportLoaded = isImportLoaded; |
| 3958 scope.whenReady = whenImportsReady; |
| 3959 scope.isIE = isIE; |
| 3960 |
| 3961 // deprecated |
| 3962 scope.whenImportsReady = whenImportsReady; |
| 3963 |
| 3964 })(window.HTMLImports); |
| 3965 /* |
| 3966 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 3967 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
| 3968 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
| 3969 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
| 3970 * Code distributed by Google as part of the polymer project is also |
| 3971 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
| 3972 */ |
| 3973 |
| 3974 (function(scope) { |
| 3975 |
| 3976 function withDependencies(task, depends) { |
| 3977 depends = depends || []; |
| 3978 if (!depends.map) { |
| 3979 depends = [depends]; |
| 3980 } |
| 3981 return task.apply(this, depends.map(marshal)); |
| 3982 } |
| 3983 |
| 3984 function module(name, dependsOrFactory, moduleFactory) { |
| 3985 var module; |
| 3986 switch (arguments.length) { |
| 3987 case 0: |
| 3988 return; |
| 3989 case 1: |
| 3990 module = null; |
| 3991 break; |
| 3992 case 2: |
| 3993 // dependsOrFactory is `factory` in this case |
| 3994 module = dependsOrFactory.apply(this); |
| 3995 break; |
| 3996 default: |
| 3997 // dependsOrFactory is `depends` in this case |
| 3998 module = withDependencies(moduleFactory, dependsOrFactory); |
| 3999 break; |
| 4000 } |
| 4001 modules[name] = module; |
| 4002 }; |
| 4003 |
| 4004 function marshal(name) { |
| 4005 return modules[name]; |
| 4006 } |
| 4007 |
| 4008 var modules = {}; |
| 4009 |
| 4010 function using(depends, task) { |
| 4011 HTMLImports.whenImportsReady(function() { |
| 4012 withDependencies(task, depends); |
| 4013 }); |
| 4014 }; |
| 4015 |
| 4016 // exports |
| 4017 |
| 4018 scope.marshal = marshal; |
| 4019 // `module` confuses commonjs detectors |
| 4020 scope.modularize = module; |
| 4021 scope.using = using; |
| 4022 |
| 4023 })(window); |
| 4024 |
| 4025 /* |
| 4026 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 4027 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
| 4028 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
| 4029 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
| 4030 * Code distributed by Google as part of the polymer project is also |
| 4031 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
| 4032 */ |
| 4033 |
| 4034 (function(scope) { |
| 4035 |
| 4036 // TODO(sorvell): It's desireable to provide a default stylesheet |
| 4037 // that's convenient for styling unresolved elements, but |
| 4038 // it's cumbersome to have to include this manually in every page. |
| 4039 // It would make sense to put inside some HTMLImport but |
| 4040 // the HTMLImports polyfill does not allow loading of stylesheets |
| 4041 // that block rendering. Therefore this injection is tolerated here. |
| 4042 var style = document.createElement('style'); |
| 4043 style.textContent = '' |
| 4044 + 'body {' |
| 4045 + 'transition: opacity ease-in 0.2s;' |
| 4046 + ' } \n' |
| 4047 + 'body[unresolved] {' |
| 4048 + 'opacity: 0; display: block; overflow: hidden;' |
| 4049 + ' } \n' |
| 4050 ; |
| 4051 var head = document.querySelector('head'); |
| 4052 head.insertBefore(style, head.firstChild); |
| 4053 |
| 4054 })(Platform); |
| 4055 |
| 4056 // Copyright 2012 Google Inc. |
| 4057 // |
| 4058 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4059 // you may not use this file except in compliance with the License. |
| 4060 // You may obtain a copy of the License at |
| 4061 // |
| 4062 // http://www.apache.org/licenses/LICENSE-2.0 |
| 4063 // |
| 4064 // Unless required by applicable law or agreed to in writing, software |
| 4065 // distributed under the License is distributed on an "AS IS" BASIS, |
| 4066 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 4067 // See the License for the specific language governing permissions and |
| 4068 // limitations under the License. |
| 4069 |
| 4070 (function(global) { |
| 4071 'use strict'; |
| 4072 |
| 4073 var testingExposeCycleCount = global.testingExposeCycleCount; |
| 4074 |
| 4075 // Detect and do basic sanity checking on Object/Array.observe. |
| 4076 function detectObjectObserve() { |
| 4077 if (typeof Object.observe !== 'function' || |
| 4078 typeof Array.observe !== 'function') { |
| 4079 return false; |
| 4080 } |
| 4081 |
| 4082 var records = []; |
| 4083 |
| 4084 function callback(recs) { |
| 4085 records = recs; |
| 4086 } |
| 4087 |
| 4088 var test = {}; |
| 4089 var arr = []; |
| 4090 Object.observe(test, callback); |
| 4091 Array.observe(arr, callback); |
| 4092 test.id = 1; |
| 4093 test.id = 2; |
| 4094 delete test.id; |
| 4095 arr.push(1, 2); |
| 4096 arr.length = 0; |
| 4097 |
| 4098 Object.deliverChangeRecords(callback); |
| 4099 if (records.length !== 5) |
| 4100 return false; |
| 4101 |
| 4102 if (records[0].type != 'add' || |
| 4103 records[1].type != 'update' || |
| 4104 records[2].type != 'delete' || |
| 4105 records[3].type != 'splice' || |
| 4106 records[4].type != 'splice') { |
| 4107 return false; |
| 4108 } |
| 4109 |
| 4110 Object.unobserve(test, callback); |
| 4111 Array.unobserve(arr, callback); |
| 4112 |
| 4113 return true; |
| 4114 } |
| 4115 |
| 4116 var hasObserve = detectObjectObserve(); |
| 4117 |
| 4118 function detectEval() { |
| 4119 // Don't test for eval if we're running in a Chrome App environment. |
| 4120 // We check for APIs set that only exist in a Chrome App context. |
| 4121 if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) { |
| 4122 return false; |
| 4123 } |
| 4124 |
| 4125 // Firefox OS Apps do not allow eval. This feature detection is very hacky |
| 4126 // but even if some other platform adds support for this function this code |
| 4127 // will continue to work. |
| 4128 if (navigator.getDeviceStorage) { |
| 4129 return false; |
| 4130 } |
| 4131 |
| 4132 try { |
| 4133 var f = new Function('', 'return true;'); |
| 4134 return f(); |
| 4135 } catch (ex) { |
| 4136 return false; |
| 4137 } |
| 4138 } |
| 4139 |
| 4140 var hasEval = detectEval(); |
| 4141 |
| 4142 function isIndex(s) { |
| 4143 return +s === s >>> 0; |
| 4144 } |
| 4145 |
| 4146 function toNumber(s) { |
| 4147 return +s; |
| 4148 } |
| 4149 |
| 4150 function isObject(obj) { |
| 4151 return obj === Object(obj); |
| 4152 } |
| 4153 |
| 4154 var numberIsNaN = global.Number.isNaN || function(value) { |
| 4155 return typeof value === 'number' && global.isNaN(value); |
| 4156 } |
| 4157 |
| 4158 function areSameValue(left, right) { |
| 4159 if (left === right) |
| 4160 return left !== 0 || 1 / left === 1 / right; |
| 4161 if (numberIsNaN(left) && numberIsNaN(right)) |
| 4162 return true; |
| 4163 |
| 4164 return left !== left && right !== right; |
| 4165 } |
| 4166 |
| 4167 var createObject = ('__proto__' in {}) ? |
| 4168 function(obj) { return obj; } : |
| 4169 function(obj) { |
| 4170 var proto = obj.__proto__; |
| 4171 if (!proto) |
| 4172 return obj; |
| 4173 var newObject = Object.create(proto); |
| 4174 Object.getOwnPropertyNames(obj).forEach(function(name) { |
| 4175 Object.defineProperty(newObject, name, |
| 4176 Object.getOwnPropertyDescriptor(obj, name)); |
| 4177 }); |
| 4178 return newObject; |
| 4179 }; |
| 4180 |
| 4181 var identStart = '[\$_a-zA-Z]'; |
| 4182 var identPart = '[\$_a-zA-Z0-9]'; |
| 4183 var identRegExp = new RegExp('^' + identStart + '+' + identPart + '*' + '$'); |
| 4184 |
| 4185 function getPathCharType(char) { |
| 4186 if (char === undefined) |
| 4187 return 'eof'; |
| 4188 |
| 4189 var code = char.charCodeAt(0); |
| 4190 |
| 4191 switch(code) { |
| 4192 case 0x5B: // [ |
| 4193 case 0x5D: // ] |
| 4194 case 0x2E: // . |
| 4195 case 0x22: // " |
| 4196 case 0x27: // ' |
| 4197 case 0x30: // 0 |
| 4198 return char; |
| 4199 |
| 4200 case 0x5F: // _ |
| 4201 case 0x24: // $ |
| 4202 return 'ident'; |
| 4203 |
| 4204 case 0x20: // Space |
| 4205 case 0x09: // Tab |
| 4206 case 0x0A: // Newline |
| 4207 case 0x0D: // Return |
| 4208 case 0xA0: // No-break space |
| 4209 case 0xFEFF: // Byte Order Mark |
| 4210 case 0x2028: // Line Separator |
| 4211 case 0x2029: // Paragraph Separator |
| 4212 return 'ws'; |
| 4213 } |
| 4214 |
| 4215 // a-z, A-Z |
| 4216 if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A)) |
| 4217 return 'ident'; |
| 4218 |
| 4219 // 1-9 |
| 4220 if (0x31 <= code && code <= 0x39) |
| 4221 return 'number'; |
| 4222 |
| 4223 return 'else'; |
| 4224 } |
| 4225 |
| 4226 var pathStateMachine = { |
| 4227 'beforePath': { |
| 4228 'ws': ['beforePath'], |
| 4229 'ident': ['inIdent', 'append'], |
| 4230 '[': ['beforeElement'], |
| 4231 'eof': ['afterPath'] |
| 4232 }, |
| 4233 |
| 4234 'inPath': { |
| 4235 'ws': ['inPath'], |
| 4236 '.': ['beforeIdent'], |
| 4237 '[': ['beforeElement'], |
| 4238 'eof': ['afterPath'] |
| 4239 }, |
| 4240 |
| 4241 'beforeIdent': { |
| 4242 'ws': ['beforeIdent'], |
| 4243 'ident': ['inIdent', 'append'] |
| 4244 }, |
| 4245 |
| 4246 'inIdent': { |
| 4247 'ident': ['inIdent', 'append'], |
| 4248 '0': ['inIdent', 'append'], |
| 4249 'number': ['inIdent', 'append'], |
| 4250 'ws': ['inPath', 'push'], |
| 4251 '.': ['beforeIdent', 'push'], |
| 4252 '[': ['beforeElement', 'push'], |
| 4253 'eof': ['afterPath', 'push'] |
| 4254 }, |
| 4255 |
| 4256 'beforeElement': { |
| 4257 'ws': ['beforeElement'], |
| 4258 '0': ['afterZero', 'append'], |
| 4259 'number': ['inIndex', 'append'], |
| 4260 "'": ['inSingleQuote', 'append', ''], |
| 4261 '"': ['inDoubleQuote', 'append', ''] |
| 4262 }, |
| 4263 |
| 4264 'afterZero': { |
| 4265 'ws': ['afterElement', 'push'], |
| 4266 ']': ['inPath', 'push'] |
| 4267 }, |
| 4268 |
| 4269 'inIndex': { |
| 4270 '0': ['inIndex', 'append'], |
| 4271 'number': ['inIndex', 'append'], |
| 4272 'ws': ['afterElement'], |
| 4273 ']': ['inPath', 'push'] |
| 4274 }, |
| 4275 |
| 4276 'inSingleQuote': { |
| 4277 "'": ['afterElement'], |
| 4278 'eof': ['error'], |
| 4279 'else': ['inSingleQuote', 'append'] |
| 4280 }, |
| 4281 |
| 4282 'inDoubleQuote': { |
| 4283 '"': ['afterElement'], |
| 4284 'eof': ['error'], |
| 4285 'else': ['inDoubleQuote', 'append'] |
| 4286 }, |
| 4287 |
| 4288 'afterElement': { |
| 4289 'ws': ['afterElement'], |
| 4290 ']': ['inPath', 'push'] |
| 4291 } |
| 4292 } |
| 4293 |
| 4294 function noop() {} |
| 4295 |
| 4296 function parsePath(path) { |
| 4297 var keys = []; |
| 4298 var index = -1; |
| 4299 var c, newChar, key, type, transition, action, typeMap, mode = 'beforePath'; |
| 4300 |
| 4301 var actions = { |
| 4302 push: function() { |
| 4303 if (key === undefined) |
| 4304 return; |
| 4305 |
| 4306 keys.push(key); |
| 4307 key = undefined; |
| 4308 }, |
| 4309 |
| 4310 append: function() { |
| 4311 if (key === undefined) |
| 4312 key = newChar |
| 4313 else |
| 4314 key += newChar; |
| 4315 } |
| 4316 }; |
| 4317 |
| 4318 function maybeUnescapeQuote() { |
| 4319 if (index >= path.length) |
| 4320 return; |
| 4321 |
| 4322 var nextChar = path[index + 1]; |
| 4323 if ((mode == 'inSingleQuote' && nextChar == "'") || |
| 4324 (mode == 'inDoubleQuote' && nextChar == '"')) { |
| 4325 index++; |
| 4326 newChar = nextChar; |
| 4327 actions.append(); |
| 4328 return true; |
| 4329 } |
| 4330 } |
| 4331 |
| 4332 while (mode) { |
| 4333 index++; |
| 4334 c = path[index]; |
| 4335 |
| 4336 if (c == '\\' && maybeUnescapeQuote(mode)) |
| 4337 continue; |
| 4338 |
| 4339 type = getPathCharType(c); |
| 4340 typeMap = pathStateMachine[mode]; |
| 4341 transition = typeMap[type] || typeMap['else'] || 'error'; |
| 4342 |
| 4343 if (transition == 'error') |
| 4344 return; // parse error; |
| 4345 |
| 4346 mode = transition[0]; |
| 4347 action = actions[transition[1]] || noop; |
| 4348 newChar = transition[2] === undefined ? c : transition[2]; |
| 4349 action(); |
| 4350 |
| 4351 if (mode === 'afterPath') { |
| 4352 return keys; |
| 4353 } |
| 4354 } |
| 4355 |
| 4356 return; // parse error |
| 4357 } |
| 4358 |
| 4359 function isIdent(s) { |
| 4360 return identRegExp.test(s); |
| 4361 } |
| 4362 |
| 4363 var constructorIsPrivate = {}; |
| 4364 |
| 4365 function Path(parts, privateToken) { |
| 4366 if (privateToken !== constructorIsPrivate) |
| 4367 throw Error('Use Path.get to retrieve path objects'); |
| 4368 |
| 4369 for (var i = 0; i < parts.length; i++) { |
| 4370 this.push(String(parts[i])); |
| 4371 } |
| 4372 |
| 4373 if (hasEval && this.length) { |
| 4374 this.getValueFrom = this.compiledGetValueFromFn(); |
| 4375 } |
| 4376 } |
| 4377 |
| 4378 // TODO(rafaelw): Make simple LRU cache |
| 4379 var pathCache = {}; |
| 4380 |
| 4381 function getPath(pathString) { |
| 4382 if (pathString instanceof Path) |
| 4383 return pathString; |
| 4384 |
| 4385 if (pathString == null || pathString.length == 0) |
| 4386 pathString = ''; |
| 4387 |
| 4388 if (typeof pathString != 'string') { |
| 4389 if (isIndex(pathString.length)) { |
| 4390 // Constructed with array-like (pre-parsed) keys |
| 4391 return new Path(pathString, constructorIsPrivate); |
| 4392 } |
| 4393 |
| 4394 pathString = String(pathString); |
| 4395 } |
| 4396 |
| 4397 var path = pathCache[pathString]; |
| 4398 if (path) |
| 4399 return path; |
| 4400 |
| 4401 var parts = parsePath(pathString); |
| 4402 if (!parts) |
| 4403 return invalidPath; |
| 4404 |
| 4405 var path = new Path(parts, constructorIsPrivate); |
| 4406 pathCache[pathString] = path; |
| 4407 return path; |
| 4408 } |
| 4409 |
| 4410 Path.get = getPath; |
| 4411 |
| 4412 function formatAccessor(key) { |
| 4413 if (isIndex(key)) { |
| 4414 return '[' + key + ']'; |
| 4415 } else { |
| 4416 return '["' + key.replace(/"/g, '\\"') + '"]'; |
| 4417 } |
| 4418 } |
| 4419 |
| 4420 Path.prototype = createObject({ |
| 4421 __proto__: [], |
| 4422 valid: true, |
| 4423 |
| 4424 toString: function() { |
| 4425 var pathString = ''; |
| 4426 for (var i = 0; i < this.length; i++) { |
| 4427 var key = this[i]; |
| 4428 if (isIdent(key)) { |
| 4429 pathString += i ? '.' + key : key; |
| 4430 } else { |
| 4431 pathString += formatAccessor(key); |
| 4432 } |
| 4433 } |
| 4434 |
| 4435 return pathString; |
| 4436 }, |
| 4437 |
| 4438 getValueFrom: function(obj, directObserver) { |
| 4439 for (var i = 0; i < this.length; i++) { |
| 4440 if (obj == null) |
| 4441 return; |
| 4442 obj = obj[this[i]]; |
| 4443 } |
| 4444 return obj; |
| 4445 }, |
| 4446 |
| 4447 iterateObjects: function(obj, observe) { |
| 4448 for (var i = 0; i < this.length; i++) { |
| 4449 if (i) |
| 4450 obj = obj[this[i - 1]]; |
| 4451 if (!isObject(obj)) |
| 4452 return; |
| 4453 observe(obj, this[0]); |
| 4454 } |
| 4455 }, |
| 4456 |
| 4457 compiledGetValueFromFn: function() { |
| 4458 var str = ''; |
| 4459 var pathString = 'obj'; |
| 4460 str += 'if (obj != null'; |
| 4461 var i = 0; |
| 4462 var key; |
| 4463 for (; i < (this.length - 1); i++) { |
| 4464 key = this[i]; |
| 4465 pathString += isIdent(key) ? '.' + key : formatAccessor(key); |
| 4466 str += ' &&\n ' + pathString + ' != null'; |
| 4467 } |
| 4468 str += ')\n'; |
| 4469 |
| 4470 var key = this[i]; |
| 4471 pathString += isIdent(key) ? '.' + key : formatAccessor(key); |
| 4472 |
| 4473 str += ' return ' + pathString + ';\nelse\n return undefined;'; |
| 4474 return new Function('obj', str); |
| 4475 }, |
| 4476 |
| 4477 setValueFrom: function(obj, value) { |
| 4478 if (!this.length) |
| 4479 return false; |
| 4480 |
| 4481 for (var i = 0; i < this.length - 1; i++) { |
| 4482 if (!isObject(obj)) |
| 4483 return false; |
| 4484 obj = obj[this[i]]; |
| 4485 } |
| 4486 |
| 4487 if (!isObject(obj)) |
| 4488 return false; |
| 4489 |
| 4490 obj[this[i]] = value; |
| 4491 return true; |
| 4492 } |
| 4493 }); |
| 4494 |
| 4495 var invalidPath = new Path('', constructorIsPrivate); |
| 4496 invalidPath.valid = false; |
| 4497 invalidPath.getValueFrom = invalidPath.setValueFrom = function() {}; |
| 4498 |
| 4499 var MAX_DIRTY_CHECK_CYCLES = 1000; |
| 4500 |
| 4501 function dirtyCheck(observer) { |
| 4502 var cycles = 0; |
| 4503 while (cycles < MAX_DIRTY_CHECK_CYCLES && observer.check_()) { |
| 4504 cycles++; |
| 4505 } |
| 4506 if (testingExposeCycleCount) |
| 4507 global.dirtyCheckCycleCount = cycles; |
| 4508 |
| 4509 return cycles > 0; |
| 4510 } |
| 4511 |
| 4512 function objectIsEmpty(object) { |
| 4513 for (var prop in object) |
| 4514 return false; |
| 4515 return true; |
| 4516 } |
| 4517 |
| 4518 function diffIsEmpty(diff) { |
| 4519 return objectIsEmpty(diff.added) && |
| 4520 objectIsEmpty(diff.removed) && |
| 4521 objectIsEmpty(diff.changed); |
| 4522 } |
| 4523 |
| 4524 function diffObjectFromOldObject(object, oldObject) { |
| 4525 var added = {}; |
| 4526 var removed = {}; |
| 4527 var changed = {}; |
| 4528 |
| 4529 for (var prop in oldObject) { |
| 4530 var newValue = object[prop]; |
| 4531 |
| 4532 if (newValue !== undefined && newValue === oldObject[prop]) |
| 4533 continue; |
| 4534 |
| 4535 if (!(prop in object)) { |
| 4536 removed[prop] = undefined; |
| 4537 continue; |
| 4538 } |
| 4539 |
| 4540 if (newValue !== oldObject[prop]) |
| 4541 changed[prop] = newValue; |
| 4542 } |
| 4543 |
| 4544 for (var prop in object) { |
| 4545 if (prop in oldObject) |
| 4546 continue; |
| 4547 |
| 4548 added[prop] = object[prop]; |
| 4549 } |
| 4550 |
| 4551 if (Array.isArray(object) && object.length !== oldObject.length) |
| 4552 changed.length = object.length; |
| 4553 |
| 4554 return { |
| 4555 added: added, |
| 4556 removed: removed, |
| 4557 changed: changed |
| 4558 }; |
| 4559 } |
| 4560 |
| 4561 var eomTasks = []; |
| 4562 function runEOMTasks() { |
| 4563 if (!eomTasks.length) |
| 4564 return false; |
| 4565 |
| 4566 for (var i = 0; i < eomTasks.length; i++) { |
| 4567 eomTasks[i](); |
| 4568 } |
| 4569 eomTasks.length = 0; |
| 4570 return true; |
| 4571 } |
| 4572 |
| 4573 var runEOM = hasObserve ? (function(){ |
| 4574 var eomObj = { pingPong: true }; |
| 4575 var eomRunScheduled = false; |
| 4576 |
| 4577 Object.observe(eomObj, function() { |
| 4578 runEOMTasks(); |
| 4579 eomRunScheduled = false; |
| 4580 }); |
| 4581 |
| 4582 return function(fn) { |
| 4583 eomTasks.push(fn); |
| 4584 if (!eomRunScheduled) { |
| 4585 eomRunScheduled = true; |
| 4586 eomObj.pingPong = !eomObj.pingPong; |
| 4587 } |
| 4588 }; |
| 4589 })() : |
| 4590 (function() { |
| 4591 return function(fn) { |
| 4592 eomTasks.push(fn); |
| 4593 }; |
| 4594 })(); |
| 4595 |
| 4596 var observedObjectCache = []; |
| 4597 |
| 4598 function newObservedObject() { |
| 4599 var observer; |
| 4600 var object; |
| 4601 var discardRecords = false; |
| 4602 var first = true; |
| 4603 |
| 4604 function callback(records) { |
| 4605 if (observer && observer.state_ === OPENED && !discardRecords) |
| 4606 observer.check_(records); |
| 4607 } |
| 4608 |
| 4609 return { |
| 4610 open: function(obs) { |
| 4611 if (observer) |
| 4612 throw Error('ObservedObject in use'); |
| 4613 |
| 4614 if (!first) |
| 4615 Object.deliverChangeRecords(callback); |
| 4616 |
| 4617 observer = obs; |
| 4618 first = false; |
| 4619 }, |
| 4620 observe: function(obj, arrayObserve) { |
| 4621 object = obj; |
| 4622 if (arrayObserve) |
| 4623 Array.observe(object, callback); |
| 4624 else |
| 4625 Object.observe(object, callback); |
| 4626 }, |
| 4627 deliver: function(discard) { |
| 4628 discardRecords = discard; |
| 4629 Object.deliverChangeRecords(callback); |
| 4630 discardRecords = false; |
| 4631 }, |
| 4632 close: function() { |
| 4633 observer = undefined; |
| 4634 Object.unobserve(object, callback); |
| 4635 observedObjectCache.push(this); |
| 4636 } |
| 4637 }; |
| 4638 } |
| 4639 |
| 4640 /* |
| 4641 * The observedSet abstraction is a perf optimization which reduces the total |
| 4642 * number of Object.observe observations of a set of objects. The idea is that |
| 4643 * groups of Observers will have some object dependencies in common and this |
| 4644 * observed set ensures that each object in the transitive closure of |
| 4645 * dependencies is only observed once. The observedSet acts as a write barrier |
| 4646 * such that whenever any change comes through, all Observers are checked for |
| 4647 * changed values. |
| 4648 * |
| 4649 * Note that this optimization is explicitly moving work from setup-time to |
| 4650 * change-time. |
| 4651 * |
| 4652 * TODO(rafaelw): Implement "garbage collection". In order to move work off |
| 4653 * the critical path, when Observers are closed, their observed objects are |
| 4654 * not Object.unobserve(d). As a result, it's possible that if the observedSet |
| 4655 * is kept open, but some Observers have been closed, it could cause "leaks" |
| 4656 * (prevent otherwise collectable objects from being collected). At some |
| 4657 * point, we should implement incremental "gc" which keeps a list of |
| 4658 * observedSets which may need clean-up and does small amounts of cleanup on a |
| 4659 * timeout until all is clean. |
| 4660 */ |
| 4661 |
| 4662 function getObservedObject(observer, object, arrayObserve) { |
| 4663 var dir = observedObjectCache.pop() || newObservedObject(); |
| 4664 dir.open(observer); |
| 4665 dir.observe(object, arrayObserve); |
| 4666 return dir; |
| 4667 } |
| 4668 |
| 4669 var observedSetCache = []; |
| 4670 |
| 4671 function newObservedSet() { |
| 4672 var observerCount = 0; |
| 4673 var observers = []; |
| 4674 var objects = []; |
| 4675 var rootObj; |
| 4676 var rootObjProps; |
| 4677 |
| 4678 function observe(obj, prop) { |
| 4679 if (!obj) |
| 4680 return; |
| 4681 |
| 4682 if (obj === rootObj) |
| 4683 rootObjProps[prop] = true; |
| 4684 |
| 4685 if (objects.indexOf(obj) < 0) { |
| 4686 objects.push(obj); |
| 4687 Object.observe(obj, callback); |
| 4688 } |
| 4689 |
| 4690 observe(Object.getPrototypeOf(obj), prop); |
| 4691 } |
| 4692 |
| 4693 function allRootObjNonObservedProps(recs) { |
| 4694 for (var i = 0; i < recs.length; i++) { |
| 4695 var rec = recs[i]; |
| 4696 if (rec.object !== rootObj || |
| 4697 rootObjProps[rec.name] || |
| 4698 rec.type === 'setPrototype') { |
| 4699 return false; |
| 4700 } |
| 4701 } |
| 4702 return true; |
| 4703 } |
| 4704 |
| 4705 function callback(recs) { |
| 4706 if (allRootObjNonObservedProps(recs)) |
| 4707 return; |
| 4708 |
| 4709 var observer; |
| 4710 for (var i = 0; i < observers.length; i++) { |
| 4711 observer = observers[i]; |
| 4712 if (observer.state_ == OPENED) { |
| 4713 observer.iterateObjects_(observe); |
| 4714 } |
| 4715 } |
| 4716 |
| 4717 for (var i = 0; i < observers.length; i++) { |
| 4718 observer = observers[i]; |
| 4719 if (observer.state_ == OPENED) { |
| 4720 observer.check_(); |
| 4721 } |
| 4722 } |
| 4723 } |
| 4724 |
| 4725 var record = { |
| 4726 object: undefined, |
| 4727 objects: objects, |
| 4728 open: function(obs, object) { |
| 4729 if (!rootObj) { |
| 4730 rootObj = object; |
| 4731 rootObjProps = {}; |
| 4732 } |
| 4733 |
| 4734 observers.push(obs); |
| 4735 observerCount++; |
| 4736 obs.iterateObjects_(observe); |
| 4737 }, |
| 4738 close: function(obs) { |
| 4739 observerCount--; |
| 4740 if (observerCount > 0) { |
| 4741 return; |
| 4742 } |
| 4743 |
| 4744 for (var i = 0; i < objects.length; i++) { |
| 4745 Object.unobserve(objects[i], callback); |
| 4746 Observer.unobservedCount++; |
| 4747 } |
| 4748 |
| 4749 observers.length = 0; |
| 4750 objects.length = 0; |
| 4751 rootObj = undefined; |
| 4752 rootObjProps = undefined; |
| 4753 observedSetCache.push(this); |
| 4754 } |
| 4755 }; |
| 4756 |
| 4757 return record; |
| 4758 } |
| 4759 |
| 4760 var lastObservedSet; |
| 4761 |
| 4762 function getObservedSet(observer, obj) { |
| 4763 if (!lastObservedSet || lastObservedSet.object !== obj) { |
| 4764 lastObservedSet = observedSetCache.pop() || newObservedSet(); |
| 4765 lastObservedSet.object = obj; |
| 4766 } |
| 4767 lastObservedSet.open(observer, obj); |
| 4768 return lastObservedSet; |
| 4769 } |
| 4770 |
| 4771 var UNOPENED = 0; |
| 4772 var OPENED = 1; |
| 4773 var CLOSED = 2; |
| 4774 var RESETTING = 3; |
| 4775 |
| 4776 var nextObserverId = 1; |
| 4777 |
| 4778 function Observer() { |
| 4779 this.state_ = UNOPENED; |
| 4780 this.callback_ = undefined; |
| 4781 this.target_ = undefined; // TODO(rafaelw): Should be WeakRef |
| 4782 this.directObserver_ = undefined; |
| 4783 this.value_ = undefined; |
| 4784 this.id_ = nextObserverId++; |
| 4785 } |
| 4786 |
| 4787 Observer.prototype = { |
| 4788 open: function(callback, target) { |
| 4789 if (this.state_ != UNOPENED) |
| 4790 throw Error('Observer has already been opened.'); |
| 4791 |
| 4792 addToAll(this); |
| 4793 this.callback_ = callback; |
| 4794 this.target_ = target; |
| 4795 this.connect_(); |
| 4796 this.state_ = OPENED; |
| 4797 return this.value_; |
| 4798 }, |
| 4799 |
| 4800 close: function() { |
| 4801 if (this.state_ != OPENED) |
| 4802 return; |
| 4803 |
| 4804 removeFromAll(this); |
| 4805 this.disconnect_(); |
| 4806 this.value_ = undefined; |
| 4807 this.callback_ = undefined; |
| 4808 this.target_ = undefined; |
| 4809 this.state_ = CLOSED; |
| 4810 }, |
| 4811 |
| 4812 deliver: function() { |
| 4813 if (this.state_ != OPENED) |
| 4814 return; |
| 4815 |
| 4816 dirtyCheck(this); |
| 4817 }, |
| 4818 |
| 4819 report_: function(changes) { |
| 4820 try { |
| 4821 this.callback_.apply(this.target_, changes); |
| 4822 } catch (ex) { |
| 4823 Observer._errorThrownDuringCallback = true; |
| 4824 console.error('Exception caught during observer callback: ' + |
| 4825 (ex.stack || ex)); |
| 4826 } |
| 4827 }, |
| 4828 |
| 4829 discardChanges: function() { |
| 4830 this.check_(undefined, true); |
| 4831 return this.value_; |
| 4832 } |
| 4833 } |
| 4834 |
| 4835 var collectObservers = !hasObserve; |
| 4836 var allObservers; |
| 4837 Observer._allObserversCount = 0; |
| 4838 |
| 4839 if (collectObservers) { |
| 4840 allObservers = []; |
| 4841 } |
| 4842 |
| 4843 function addToAll(observer) { |
| 4844 Observer._allObserversCount++; |
| 4845 if (!collectObservers) |
| 4846 return; |
| 4847 |
| 4848 allObservers.push(observer); |
| 4849 } |
| 4850 |
| 4851 function removeFromAll(observer) { |
| 4852 Observer._allObserversCount--; |
| 4853 } |
| 4854 |
| 4855 var runningMicrotaskCheckpoint = false; |
| 4856 |
| 4857 var hasDebugForceFullDelivery = hasObserve && hasEval && (function() { |
| 4858 try { |
| 4859 eval('%RunMicrotasks()'); |
| 4860 return true; |
| 4861 } catch (ex) { |
| 4862 return false; |
| 4863 } |
| 4864 })(); |
| 4865 |
| 4866 global.Platform = global.Platform || {}; |
| 4867 |
| 4868 global.Platform.performMicrotaskCheckpoint = function() { |
| 4869 if (runningMicrotaskCheckpoint) |
| 4870 return; |
| 4871 |
| 4872 if (hasDebugForceFullDelivery) { |
| 4873 eval('%RunMicrotasks()'); |
| 4874 return; |
| 4875 } |
| 4876 |
| 4877 if (!collectObservers) |
| 4878 return; |
| 4879 |
| 4880 runningMicrotaskCheckpoint = true; |
| 4881 |
| 4882 var cycles = 0; |
| 4883 var anyChanged, toCheck; |
| 4884 |
| 4885 do { |
| 4886 cycles++; |
| 4887 toCheck = allObservers; |
| 4888 allObservers = []; |
| 4889 anyChanged = false; |
| 4890 |
| 4891 for (var i = 0; i < toCheck.length; i++) { |
| 4892 var observer = toCheck[i]; |
| 4893 if (observer.state_ != OPENED) |
| 4894 continue; |
| 4895 |
| 4896 if (observer.check_()) |
| 4897 anyChanged = true; |
| 4898 |
| 4899 allObservers.push(observer); |
| 4900 } |
| 4901 if (runEOMTasks()) |
| 4902 anyChanged = true; |
| 4903 } while (cycles < MAX_DIRTY_CHECK_CYCLES && anyChanged); |
| 4904 |
| 4905 if (testingExposeCycleCount) |
| 4906 global.dirtyCheckCycleCount = cycles; |
| 4907 |
| 4908 runningMicrotaskCheckpoint = false; |
| 4909 }; |
| 4910 |
| 4911 if (collectObservers) { |
| 4912 global.Platform.clearObservers = function() { |
| 4913 allObservers = []; |
| 4914 }; |
| 4915 } |
| 4916 |
| 4917 function ObjectObserver(object) { |
| 4918 Observer.call(this); |
| 4919 this.value_ = object; |
| 4920 this.oldObject_ = undefined; |
| 4921 } |
| 4922 |
| 4923 ObjectObserver.prototype = createObject({ |
| 4924 __proto__: Observer.prototype, |
| 4925 |
| 4926 arrayObserve: false, |
| 4927 |
| 4928 connect_: function(callback, target) { |
| 4929 if (hasObserve) { |
| 4930 this.directObserver_ = getObservedObject(this, this.value_, |
| 4931 this.arrayObserve); |
| 4932 } else { |
| 4933 this.oldObject_ = this.copyObject(this.value_); |
| 4934 } |
| 4935 |
| 4936 }, |
| 4937 |
| 4938 copyObject: function(object) { |
| 4939 var copy = Array.isArray(object) ? [] : {}; |
| 4940 for (var prop in object) { |
| 4941 copy[prop] = object[prop]; |
| 4942 }; |
| 4943 if (Array.isArray(object)) |
| 4944 copy.length = object.length; |
| 4945 return copy; |
| 4946 }, |
| 4947 |
| 4948 check_: function(changeRecords, skipChanges) { |
| 4949 var diff; |
| 4950 var oldValues; |
| 4951 if (hasObserve) { |
| 4952 if (!changeRecords) |
| 4953 return false; |
| 4954 |
| 4955 oldValues = {}; |
| 4956 diff = diffObjectFromChangeRecords(this.value_, changeRecords, |
| 4957 oldValues); |
| 4958 } else { |
| 4959 oldValues = this.oldObject_; |
| 4960 diff = diffObjectFromOldObject(this.value_, this.oldObject_); |
| 4961 } |
| 4962 |
| 4963 if (diffIsEmpty(diff)) |
| 4964 return false; |
| 4965 |
| 4966 if (!hasObserve) |
| 4967 this.oldObject_ = this.copyObject(this.value_); |
| 4968 |
| 4969 this.report_([ |
| 4970 diff.added || {}, |
| 4971 diff.removed || {}, |
| 4972 diff.changed || {}, |
| 4973 function(property) { |
| 4974 return oldValues[property]; |
| 4975 } |
| 4976 ]); |
| 4977 |
| 4978 return true; |
| 4979 }, |
| 4980 |
| 4981 disconnect_: function() { |
| 4982 if (hasObserve) { |
| 4983 this.directObserver_.close(); |
| 4984 this.directObserver_ = undefined; |
| 4985 } else { |
| 4986 this.oldObject_ = undefined; |
| 4987 } |
| 4988 }, |
| 4989 |
| 4990 deliver: function() { |
| 4991 if (this.state_ != OPENED) |
| 4992 return; |
| 4993 |
| 4994 if (hasObserve) |
| 4995 this.directObserver_.deliver(false); |
| 4996 else |
| 4997 dirtyCheck(this); |
| 4998 }, |
| 4999 |
| 5000 discardChanges: function() { |
| 5001 if (this.directObserver_) |
| 5002 this.directObserver_.deliver(true); |
| 5003 else |
| 5004 this.oldObject_ = this.copyObject(this.value_); |
| 5005 |
| 5006 return this.value_; |
| 5007 } |
| 5008 }); |
| 5009 |
| 5010 function ArrayObserver(array) { |
| 5011 if (!Array.isArray(array)) |
| 5012 throw Error('Provided object is not an Array'); |
| 5013 ObjectObserver.call(this, array); |
| 5014 } |
| 5015 |
| 5016 ArrayObserver.prototype = createObject({ |
| 5017 |
| 5018 __proto__: ObjectObserver.prototype, |
| 5019 |
| 5020 arrayObserve: true, |
| 5021 |
| 5022 copyObject: function(arr) { |
| 5023 return arr.slice(); |
| 5024 }, |
| 5025 |
| 5026 check_: function(changeRecords) { |
| 5027 var splices; |
| 5028 if (hasObserve) { |
| 5029 if (!changeRecords) |
| 5030 return false; |
| 5031 splices = projectArraySplices(this.value_, changeRecords); |
| 5032 } else { |
| 5033 splices = calcSplices(this.value_, 0, this.value_.length, |
| 5034 this.oldObject_, 0, this.oldObject_.length); |
| 5035 } |
| 5036 |
| 5037 if (!splices || !splices.length) |
| 5038 return false; |
| 5039 |
| 5040 if (!hasObserve) |
| 5041 this.oldObject_ = this.copyObject(this.value_); |
| 5042 |
| 5043 this.report_([splices]); |
| 5044 return true; |
| 5045 } |
| 5046 }); |
| 5047 |
| 5048 ArrayObserver.applySplices = function(previous, current, splices) { |
| 5049 splices.forEach(function(splice) { |
| 5050 var spliceArgs = [splice.index, splice.removed.length]; |
| 5051 var addIndex = splice.index; |
| 5052 while (addIndex < splice.index + splice.addedCount) { |
| 5053 spliceArgs.push(current[addIndex]); |
| 5054 addIndex++; |
| 5055 } |
| 5056 |
| 5057 Array.prototype.splice.apply(previous, spliceArgs); |
| 5058 }); |
| 5059 }; |
| 5060 |
| 5061 function PathObserver(object, path) { |
| 5062 Observer.call(this); |
| 5063 |
| 5064 this.object_ = object; |
| 5065 this.path_ = getPath(path); |
| 5066 this.directObserver_ = undefined; |
| 5067 } |
| 5068 |
| 5069 PathObserver.prototype = createObject({ |
| 5070 __proto__: Observer.prototype, |
| 5071 |
| 5072 get path() { |
| 5073 return this.path_; |
| 5074 }, |
| 5075 |
| 5076 connect_: function() { |
| 5077 if (hasObserve) |
| 5078 this.directObserver_ = getObservedSet(this, this.object_); |
| 5079 |
| 5080 this.check_(undefined, true); |
| 5081 }, |
| 5082 |
| 5083 disconnect_: function() { |
| 5084 this.value_ = undefined; |
| 5085 |
| 5086 if (this.directObserver_) { |
| 5087 this.directObserver_.close(this); |
| 5088 this.directObserver_ = undefined; |
| 5089 } |
| 5090 }, |
| 5091 |
| 5092 iterateObjects_: function(observe) { |
| 5093 this.path_.iterateObjects(this.object_, observe); |
| 5094 }, |
| 5095 |
| 5096 check_: function(changeRecords, skipChanges) { |
| 5097 var oldValue = this.value_; |
| 5098 this.value_ = this.path_.getValueFrom(this.object_); |
| 5099 if (skipChanges || areSameValue(this.value_, oldValue)) |
| 5100 return false; |
| 5101 |
| 5102 this.report_([this.value_, oldValue, this]); |
| 5103 return true; |
| 5104 }, |
| 5105 |
| 5106 setValue: function(newValue) { |
| 5107 if (this.path_) |
| 5108 this.path_.setValueFrom(this.object_, newValue); |
| 5109 } |
| 5110 }); |
| 5111 |
| 5112 function CompoundObserver(reportChangesOnOpen) { |
| 5113 Observer.call(this); |
| 5114 |
| 5115 this.reportChangesOnOpen_ = reportChangesOnOpen; |
| 5116 this.value_ = []; |
| 5117 this.directObserver_ = undefined; |
| 5118 this.observed_ = []; |
| 5119 } |
| 5120 |
| 5121 var observerSentinel = {}; |
| 5122 |
| 5123 CompoundObserver.prototype = createObject({ |
| 5124 __proto__: Observer.prototype, |
| 5125 |
| 5126 connect_: function() { |
| 5127 if (hasObserve) { |
| 5128 var object; |
| 5129 var needsDirectObserver = false; |
| 5130 for (var i = 0; i < this.observed_.length; i += 2) { |
| 5131 object = this.observed_[i] |
| 5132 if (object !== observerSentinel) { |
| 5133 needsDirectObserver = true; |
| 5134 break; |
| 5135 } |
| 5136 } |
| 5137 |
| 5138 if (needsDirectObserver) |
| 5139 this.directObserver_ = getObservedSet(this, object); |
| 5140 } |
| 5141 |
| 5142 this.check_(undefined, !this.reportChangesOnOpen_); |
| 5143 }, |
| 5144 |
| 5145 disconnect_: function() { |
| 5146 for (var i = 0; i < this.observed_.length; i += 2) { |
| 5147 if (this.observed_[i] === observerSentinel) |
| 5148 this.observed_[i + 1].close(); |
| 5149 } |
| 5150 this.observed_.length = 0; |
| 5151 this.value_.length = 0; |
| 5152 |
| 5153 if (this.directObserver_) { |
| 5154 this.directObserver_.close(this); |
| 5155 this.directObserver_ = undefined; |
| 5156 } |
| 5157 }, |
| 5158 |
| 5159 addPath: function(object, path) { |
| 5160 if (this.state_ != UNOPENED && this.state_ != RESETTING) |
| 5161 throw Error('Cannot add paths once started.'); |
| 5162 |
| 5163 var path = getPath(path); |
| 5164 this.observed_.push(object, path); |
| 5165 if (!this.reportChangesOnOpen_) |
| 5166 return; |
| 5167 var index = this.observed_.length / 2 - 1; |
| 5168 this.value_[index] = path.getValueFrom(object); |
| 5169 }, |
| 5170 |
| 5171 addObserver: function(observer) { |
| 5172 if (this.state_ != UNOPENED && this.state_ != RESETTING) |
| 5173 throw Error('Cannot add observers once started.'); |
| 5174 |
| 5175 this.observed_.push(observerSentinel, observer); |
| 5176 if (!this.reportChangesOnOpen_) |
| 5177 return; |
| 5178 var index = this.observed_.length / 2 - 1; |
| 5179 this.value_[index] = observer.open(this.deliver, this); |
| 5180 }, |
| 5181 |
| 5182 startReset: function() { |
| 5183 if (this.state_ != OPENED) |
| 5184 throw Error('Can only reset while open'); |
| 5185 |
| 5186 this.state_ = RESETTING; |
| 5187 this.disconnect_(); |
| 5188 }, |
| 5189 |
| 5190 finishReset: function() { |
| 5191 if (this.state_ != RESETTING) |
| 5192 throw Error('Can only finishReset after startReset'); |
| 5193 this.state_ = OPENED; |
| 5194 this.connect_(); |
| 5195 |
| 5196 return this.value_; |
| 5197 }, |
| 5198 |
| 5199 iterateObjects_: function(observe) { |
| 5200 var object; |
| 5201 for (var i = 0; i < this.observed_.length; i += 2) { |
| 5202 object = this.observed_[i] |
| 5203 if (object !== observerSentinel) |
| 5204 this.observed_[i + 1].iterateObjects(object, observe) |
| 5205 } |
| 5206 }, |
| 5207 |
| 5208 check_: function(changeRecords, skipChanges) { |
| 5209 var oldValues; |
| 5210 for (var i = 0; i < this.observed_.length; i += 2) { |
| 5211 var object = this.observed_[i]; |
| 5212 var path = this.observed_[i+1]; |
| 5213 var value; |
| 5214 if (object === observerSentinel) { |
| 5215 var observable = path; |
| 5216 value = this.state_ === UNOPENED ? |
| 5217 observable.open(this.deliver, this) : |
| 5218 observable.discardChanges(); |
| 5219 } else { |
| 5220 value = path.getValueFrom(object); |
| 5221 } |
| 5222 |
| 5223 if (skipChanges) { |
| 5224 this.value_[i / 2] = value; |
| 5225 continue; |
| 5226 } |
| 5227 |
| 5228 if (areSameValue(value, this.value_[i / 2])) |
| 5229 continue; |
| 5230 |
| 5231 oldValues = oldValues || []; |
| 5232 oldValues[i / 2] = this.value_[i / 2]; |
| 5233 this.value_[i / 2] = value; |
| 5234 } |
| 5235 |
| 5236 if (!oldValues) |
| 5237 return false; |
| 5238 |
| 5239 // TODO(rafaelw): Having observed_ as the third callback arg here is |
| 5240 // pretty lame API. Fix. |
| 5241 this.report_([this.value_, oldValues, this.observed_]); |
| 5242 return true; |
| 5243 } |
| 5244 }); |
| 5245 |
| 5246 function identFn(value) { return value; } |
| 5247 |
| 5248 function ObserverTransform(observable, getValueFn, setValueFn, |
| 5249 dontPassThroughSet) { |
| 5250 this.callback_ = undefined; |
| 5251 this.target_ = undefined; |
| 5252 this.value_ = undefined; |
| 5253 this.observable_ = observable; |
| 5254 this.getValueFn_ = getValueFn || identFn; |
| 5255 this.setValueFn_ = setValueFn || identFn; |
| 5256 // TODO(rafaelw): This is a temporary hack. PolymerExpressions needs this |
| 5257 // at the moment because of a bug in it's dependency tracking. |
| 5258 this.dontPassThroughSet_ = dontPassThroughSet; |
| 5259 } |
| 5260 |
| 5261 ObserverTransform.prototype = { |
| 5262 open: function(callback, target) { |
| 5263 this.callback_ = callback; |
| 5264 this.target_ = target; |
| 5265 this.value_ = |
| 5266 this.getValueFn_(this.observable_.open(this.observedCallback_, this)); |
| 5267 return this.value_; |
| 5268 }, |
| 5269 |
| 5270 observedCallback_: function(value) { |
| 5271 value = this.getValueFn_(value); |
| 5272 if (areSameValue(value, this.value_)) |
| 5273 return; |
| 5274 var oldValue = this.value_; |
| 5275 this.value_ = value; |
| 5276 this.callback_.call(this.target_, this.value_, oldValue); |
| 5277 }, |
| 5278 |
| 5279 discardChanges: function() { |
| 5280 this.value_ = this.getValueFn_(this.observable_.discardChanges()); |
| 5281 return this.value_; |
| 5282 }, |
| 5283 |
| 5284 deliver: function() { |
| 5285 return this.observable_.deliver(); |
| 5286 }, |
| 5287 |
| 5288 setValue: function(value) { |
| 5289 value = this.setValueFn_(value); |
| 5290 if (!this.dontPassThroughSet_ && this.observable_.setValue) |
| 5291 return this.observable_.setValue(value); |
| 5292 }, |
| 5293 |
| 5294 close: function() { |
| 5295 if (this.observable_) |
| 5296 this.observable_.close(); |
| 5297 this.callback_ = undefined; |
| 5298 this.target_ = undefined; |
| 5299 this.observable_ = undefined; |
| 5300 this.value_ = undefined; |
| 5301 this.getValueFn_ = undefined; |
| 5302 this.setValueFn_ = undefined; |
| 5303 } |
| 5304 } |
| 5305 |
| 5306 var expectedRecordTypes = { |
| 5307 add: true, |
| 5308 update: true, |
| 5309 delete: true |
| 5310 }; |
| 5311 |
| 5312 function diffObjectFromChangeRecords(object, changeRecords, oldValues) { |
| 5313 var added = {}; |
| 5314 var removed = {}; |
| 5315 |
| 5316 for (var i = 0; i < changeRecords.length; i++) { |
| 5317 var record = changeRecords[i]; |
| 5318 if (!expectedRecordTypes[record.type]) { |
| 5319 console.error('Unknown changeRecord type: ' + record.type); |
| 5320 console.error(record); |
| 5321 continue; |
| 5322 } |
| 5323 |
| 5324 if (!(record.name in oldValues)) |
| 5325 oldValues[record.name] = record.oldValue; |
| 5326 |
| 5327 if (record.type == 'update') |
| 5328 continue; |
| 5329 |
| 5330 if (record.type == 'add') { |
| 5331 if (record.name in removed) |
| 5332 delete removed[record.name]; |
| 5333 else |
| 5334 added[record.name] = true; |
| 5335 |
| 5336 continue; |
| 5337 } |
| 5338 |
| 5339 // type = 'delete' |
| 5340 if (record.name in added) { |
| 5341 delete added[record.name]; |
| 5342 delete oldValues[record.name]; |
| 5343 } else { |
| 5344 removed[record.name] = true; |
| 5345 } |
| 5346 } |
| 5347 |
| 5348 for (var prop in added) |
| 5349 added[prop] = object[prop]; |
| 5350 |
| 5351 for (var prop in removed) |
| 5352 removed[prop] = undefined; |
| 5353 |
| 5354 var changed = {}; |
| 5355 for (var prop in oldValues) { |
| 5356 if (prop in added || prop in removed) |
| 5357 continue; |
| 5358 |
| 5359 var newValue = object[prop]; |
| 5360 if (oldValues[prop] !== newValue) |
| 5361 changed[prop] = newValue; |
| 5362 } |
| 5363 |
| 5364 return { |
| 5365 added: added, |
| 5366 removed: removed, |
| 5367 changed: changed |
| 5368 }; |
| 5369 } |
| 5370 |
| 5371 function newSplice(index, removed, addedCount) { |
| 5372 return { |
| 5373 index: index, |
| 5374 removed: removed, |
| 5375 addedCount: addedCount |
| 5376 }; |
| 5377 } |
| 5378 |
| 5379 var EDIT_LEAVE = 0; |
| 5380 var EDIT_UPDATE = 1; |
| 5381 var EDIT_ADD = 2; |
| 5382 var EDIT_DELETE = 3; |
| 5383 |
| 5384 function ArraySplice() {} |
| 5385 |
| 5386 ArraySplice.prototype = { |
| 5387 |
| 5388 // Note: This function is *based* on the computation of the Levenshtein |
| 5389 // "edit" distance. The one change is that "updates" are treated as two |
| 5390 // edits - not one. With Array splices, an update is really a delete |
| 5391 // followed by an add. By retaining this, we optimize for "keeping" the |
| 5392 // maximum array items in the original array. For example: |
| 5393 // |
| 5394 // 'xxxx123' -> '123yyyy' |
| 5395 // |
| 5396 // With 1-edit updates, the shortest path would be just to update all seven |
| 5397 // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This |
| 5398 // leaves the substring '123' intact. |
| 5399 calcEditDistances: function(current, currentStart, currentEnd, |
| 5400 old, oldStart, oldEnd) { |
| 5401 // "Deletion" columns |
| 5402 var rowCount = oldEnd - oldStart + 1; |
| 5403 var columnCount = currentEnd - currentStart + 1; |
| 5404 var distances = new Array(rowCount); |
| 5405 |
| 5406 // "Addition" rows. Initialize null column. |
| 5407 for (var i = 0; i < rowCount; i++) { |
| 5408 distances[i] = new Array(columnCount); |
| 5409 distances[i][0] = i; |
| 5410 } |
| 5411 |
| 5412 // Initialize null row |
| 5413 for (var j = 0; j < columnCount; j++) |
| 5414 distances[0][j] = j; |
| 5415 |
| 5416 for (var i = 1; i < rowCount; i++) { |
| 5417 for (var j = 1; j < columnCount; j++) { |
| 5418 if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1])) |
| 5419 distances[i][j] = distances[i - 1][j - 1]; |
| 5420 else { |
| 5421 var north = distances[i - 1][j] + 1; |
| 5422 var west = distances[i][j - 1] + 1; |
| 5423 distances[i][j] = north < west ? north : west; |
| 5424 } |
| 5425 } |
| 5426 } |
| 5427 |
| 5428 return distances; |
| 5429 }, |
| 5430 |
| 5431 // This starts at the final weight, and walks "backward" by finding |
| 5432 // the minimum previous weight recursively until the origin of the weight |
| 5433 // matrix. |
| 5434 spliceOperationsFromEditDistances: function(distances) { |
| 5435 var i = distances.length - 1; |
| 5436 var j = distances[0].length - 1; |
| 5437 var current = distances[i][j]; |
| 5438 var edits = []; |
| 5439 while (i > 0 || j > 0) { |
| 5440 if (i == 0) { |
| 5441 edits.push(EDIT_ADD); |
| 5442 j--; |
| 5443 continue; |
| 5444 } |
| 5445 if (j == 0) { |
| 5446 edits.push(EDIT_DELETE); |
| 5447 i--; |
| 5448 continue; |
| 5449 } |
| 5450 var northWest = distances[i - 1][j - 1]; |
| 5451 var west = distances[i - 1][j]; |
| 5452 var north = distances[i][j - 1]; |
| 5453 |
| 5454 var min; |
| 5455 if (west < north) |
| 5456 min = west < northWest ? west : northWest; |
| 5457 else |
| 5458 min = north < northWest ? north : northWest; |
| 5459 |
| 5460 if (min == northWest) { |
| 5461 if (northWest == current) { |
| 5462 edits.push(EDIT_LEAVE); |
| 5463 } else { |
| 5464 edits.push(EDIT_UPDATE); |
| 5465 current = northWest; |
| 5466 } |
| 5467 i--; |
| 5468 j--; |
| 5469 } else if (min == west) { |
| 5470 edits.push(EDIT_DELETE); |
| 5471 i--; |
| 5472 current = west; |
| 5473 } else { |
| 5474 edits.push(EDIT_ADD); |
| 5475 j--; |
| 5476 current = north; |
| 5477 } |
| 5478 } |
| 5479 |
| 5480 edits.reverse(); |
| 5481 return edits; |
| 5482 }, |
| 5483 |
| 5484 /** |
| 5485 * Splice Projection functions: |
| 5486 * |
| 5487 * A splice map is a representation of how a previous array of items |
| 5488 * was transformed into a new array of items. Conceptually it is a list of |
| 5489 * tuples of |
| 5490 * |
| 5491 * <index, removed, addedCount> |
| 5492 * |
| 5493 * which are kept in ascending index order of. The tuple represents that at |
| 5494 * the |index|, |removed| sequence of items were removed, and counting forwa
rd |
| 5495 * from |index|, |addedCount| items were added. |
| 5496 */ |
| 5497 |
| 5498 /** |
| 5499 * Lacking individual splice mutation information, the minimal set of |
| 5500 * splices can be synthesized given the previous state and final state of an |
| 5501 * array. The basic approach is to calculate the edit distance matrix and |
| 5502 * choose the shortest path through it. |
| 5503 * |
| 5504 * Complexity: O(l * p) |
| 5505 * l: The length of the current array |
| 5506 * p: The length of the old array |
| 5507 */ |
| 5508 calcSplices: function(current, currentStart, currentEnd, |
| 5509 old, oldStart, oldEnd) { |
| 5510 var prefixCount = 0; |
| 5511 var suffixCount = 0; |
| 5512 |
| 5513 var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart); |
| 5514 if (currentStart == 0 && oldStart == 0) |
| 5515 prefixCount = this.sharedPrefix(current, old, minLength); |
| 5516 |
| 5517 if (currentEnd == current.length && oldEnd == old.length) |
| 5518 suffixCount = this.sharedSuffix(current, old, minLength - prefixCount); |
| 5519 |
| 5520 currentStart += prefixCount; |
| 5521 oldStart += prefixCount; |
| 5522 currentEnd -= suffixCount; |
| 5523 oldEnd -= suffixCount; |
| 5524 |
| 5525 if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) |
| 5526 return []; |
| 5527 |
| 5528 if (currentStart == currentEnd) { |
| 5529 var splice = newSplice(currentStart, [], 0); |
| 5530 while (oldStart < oldEnd) |
| 5531 splice.removed.push(old[oldStart++]); |
| 5532 |
| 5533 return [ splice ]; |
| 5534 } else if (oldStart == oldEnd) |
| 5535 return [ newSplice(currentStart, [], currentEnd - currentStart) ]; |
| 5536 |
| 5537 var ops = this.spliceOperationsFromEditDistances( |
| 5538 this.calcEditDistances(current, currentStart, currentEnd, |
| 5539 old, oldStart, oldEnd)); |
| 5540 |
| 5541 var splice = undefined; |
| 5542 var splices = []; |
| 5543 var index = currentStart; |
| 5544 var oldIndex = oldStart; |
| 5545 for (var i = 0; i < ops.length; i++) { |
| 5546 switch(ops[i]) { |
| 5547 case EDIT_LEAVE: |
| 5548 if (splice) { |
| 5549 splices.push(splice); |
| 5550 splice = undefined; |
| 5551 } |
| 5552 |
| 5553 index++; |
| 5554 oldIndex++; |
| 5555 break; |
| 5556 case EDIT_UPDATE: |
| 5557 if (!splice) |
| 5558 splice = newSplice(index, [], 0); |
| 5559 |
| 5560 splice.addedCount++; |
| 5561 index++; |
| 5562 |
| 5563 splice.removed.push(old[oldIndex]); |
| 5564 oldIndex++; |
| 5565 break; |
| 5566 case EDIT_ADD: |
| 5567 if (!splice) |
| 5568 splice = newSplice(index, [], 0); |
| 5569 |
| 5570 splice.addedCount++; |
| 5571 index++; |
| 5572 break; |
| 5573 case EDIT_DELETE: |
| 5574 if (!splice) |
| 5575 splice = newSplice(index, [], 0); |
| 5576 |
| 5577 splice.removed.push(old[oldIndex]); |
| 5578 oldIndex++; |
| 5579 break; |
| 5580 } |
| 5581 } |
| 5582 |
| 5583 if (splice) { |
| 5584 splices.push(splice); |
| 5585 } |
| 5586 return splices; |
| 5587 }, |
| 5588 |
| 5589 sharedPrefix: function(current, old, searchLength) { |
| 5590 for (var i = 0; i < searchLength; i++) |
| 5591 if (!this.equals(current[i], old[i])) |
| 5592 return i; |
| 5593 return searchLength; |
| 5594 }, |
| 5595 |
| 5596 sharedSuffix: function(current, old, searchLength) { |
| 5597 var index1 = current.length; |
| 5598 var index2 = old.length; |
| 5599 var count = 0; |
| 5600 while (count < searchLength && this.equals(current[--index1], old[--index2
])) |
| 5601 count++; |
| 5602 |
| 5603 return count; |
| 5604 }, |
| 5605 |
| 5606 calculateSplices: function(current, previous) { |
| 5607 return this.calcSplices(current, 0, current.length, previous, 0, |
| 5608 previous.length); |
| 5609 }, |
| 5610 |
| 5611 equals: function(currentValue, previousValue) { |
| 5612 return currentValue === previousValue; |
| 5613 } |
| 5614 }; |
| 5615 |
| 5616 var arraySplice = new ArraySplice(); |
| 5617 |
| 5618 function calcSplices(current, currentStart, currentEnd, |
| 5619 old, oldStart, oldEnd) { |
| 5620 return arraySplice.calcSplices(current, currentStart, currentEnd, |
| 5621 old, oldStart, oldEnd); |
| 5622 } |
| 5623 |
| 5624 function intersect(start1, end1, start2, end2) { |
| 5625 // Disjoint |
| 5626 if (end1 < start2 || end2 < start1) |
| 5627 return -1; |
| 5628 |
| 5629 // Adjacent |
| 5630 if (end1 == start2 || end2 == start1) |
| 5631 return 0; |
| 5632 |
| 5633 // Non-zero intersect, span1 first |
| 5634 if (start1 < start2) { |
| 5635 if (end1 < end2) |
| 5636 return end1 - start2; // Overlap |
| 5637 else |
| 5638 return end2 - start2; // Contained |
| 5639 } else { |
| 5640 // Non-zero intersect, span2 first |
| 5641 if (end2 < end1) |
| 5642 return end2 - start1; // Overlap |
| 5643 else |
| 5644 return end1 - start1; // Contained |
| 5645 } |
| 5646 } |
| 5647 |
| 5648 function mergeSplice(splices, index, removed, addedCount) { |
| 5649 |
| 5650 var splice = newSplice(index, removed, addedCount); |
| 5651 |
| 5652 var inserted = false; |
| 5653 var insertionOffset = 0; |
| 5654 |
| 5655 for (var i = 0; i < splices.length; i++) { |
| 5656 var current = splices[i]; |
| 5657 current.index += insertionOffset; |
| 5658 |
| 5659 if (inserted) |
| 5660 continue; |
| 5661 |
| 5662 var intersectCount = intersect(splice.index, |
| 5663 splice.index + splice.removed.length, |
| 5664 current.index, |
| 5665 current.index + current.addedCount); |
| 5666 |
| 5667 if (intersectCount >= 0) { |
| 5668 // Merge the two splices |
| 5669 |
| 5670 splices.splice(i, 1); |
| 5671 i--; |
| 5672 |
| 5673 insertionOffset -= current.addedCount - current.removed.length; |
| 5674 |
| 5675 splice.addedCount += current.addedCount - intersectCount; |
| 5676 var deleteCount = splice.removed.length + |
| 5677 current.removed.length - intersectCount; |
| 5678 |
| 5679 if (!splice.addedCount && !deleteCount) { |
| 5680 // merged splice is a noop. discard. |
| 5681 inserted = true; |
| 5682 } else { |
| 5683 var removed = current.removed; |
| 5684 |
| 5685 if (splice.index < current.index) { |
| 5686 // some prefix of splice.removed is prepended to current.removed. |
| 5687 var prepend = splice.removed.slice(0, current.index - splice.index); |
| 5688 Array.prototype.push.apply(prepend, removed); |
| 5689 removed = prepend; |
| 5690 } |
| 5691 |
| 5692 if (splice.index + splice.removed.length > current.index + current.add
edCount) { |
| 5693 // some suffix of splice.removed is appended to current.removed. |
| 5694 var append = splice.removed.slice(current.index + current.addedCount
- splice.index); |
| 5695 Array.prototype.push.apply(removed, append); |
| 5696 } |
| 5697 |
| 5698 splice.removed = removed; |
| 5699 if (current.index < splice.index) { |
| 5700 splice.index = current.index; |
| 5701 } |
| 5702 } |
| 5703 } else if (splice.index < current.index) { |
| 5704 // Insert splice here. |
| 5705 |
| 5706 inserted = true; |
| 5707 |
| 5708 splices.splice(i, 0, splice); |
| 5709 i++; |
| 5710 |
| 5711 var offset = splice.addedCount - splice.removed.length |
| 5712 current.index += offset; |
| 5713 insertionOffset += offset; |
| 5714 } |
| 5715 } |
| 5716 |
| 5717 if (!inserted) |
| 5718 splices.push(splice); |
| 5719 } |
| 5720 |
| 5721 function createInitialSplices(array, changeRecords) { |
| 5722 var splices = []; |
| 5723 |
| 5724 for (var i = 0; i < changeRecords.length; i++) { |
| 5725 var record = changeRecords[i]; |
| 5726 switch(record.type) { |
| 5727 case 'splice': |
| 5728 mergeSplice(splices, record.index, record.removed.slice(), record.adde
dCount); |
| 5729 break; |
| 5730 case 'add': |
| 5731 case 'update': |
| 5732 case 'delete': |
| 5733 if (!isIndex(record.name)) |
| 5734 continue; |
| 5735 var index = toNumber(record.name); |
| 5736 if (index < 0) |
| 5737 continue; |
| 5738 mergeSplice(splices, index, [record.oldValue], 1); |
| 5739 break; |
| 5740 default: |
| 5741 console.error('Unexpected record type: ' + JSON.stringify(record)); |
| 5742 break; |
| 5743 } |
| 5744 } |
| 5745 |
| 5746 return splices; |
| 5747 } |
| 5748 |
| 5749 function projectArraySplices(array, changeRecords) { |
| 5750 var splices = []; |
| 5751 |
| 5752 createInitialSplices(array, changeRecords).forEach(function(splice) { |
| 5753 if (splice.addedCount == 1 && splice.removed.length == 1) { |
| 5754 if (splice.removed[0] !== array[splice.index]) |
| 5755 splices.push(splice); |
| 5756 |
| 5757 return |
| 5758 }; |
| 5759 |
| 5760 splices = splices.concat(calcSplices(array, splice.index, splice.index + s
plice.addedCount, |
| 5761 splice.removed, 0, splice.removed.len
gth)); |
| 5762 }); |
| 5763 |
| 5764 return splices; |
| 5765 } |
| 5766 |
| 5767 global.Observer = Observer; |
| 5768 global.Observer.runEOM_ = runEOM; |
| 5769 global.Observer.observerSentinel_ = observerSentinel; // for testing. |
| 5770 global.Observer.hasObjectObserve = hasObserve; |
| 5771 global.ArrayObserver = ArrayObserver; |
| 5772 global.ArrayObserver.calculateSplices = function(current, previous) { |
| 5773 return arraySplice.calculateSplices(current, previous); |
| 5774 }; |
| 5775 |
| 5776 global.ArraySplice = ArraySplice; |
| 5777 global.ObjectObserver = ObjectObserver; |
| 5778 global.PathObserver = PathObserver; |
| 5779 global.CompoundObserver = CompoundObserver; |
| 5780 global.Path = Path; |
| 5781 global.ObserverTransform = ObserverTransform; |
| 5782 })(typeof global !== 'undefined' && global && typeof module !== 'undefined' && m
odule ? global : this || window); |
| 5783 |
| 5784 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 5785 // This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
| 5786 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
| 5787 // The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
| 5788 // Code distributed by Google as part of the polymer project is also |
| 5789 // subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
| 5790 |
| 5791 (function(global) { |
| 5792 'use strict'; |
| 5793 |
| 5794 var filter = Array.prototype.filter.call.bind(Array.prototype.filter); |
| 5795 |
| 5796 function getTreeScope(node) { |
| 5797 while (node.parentNode) { |
| 5798 node = node.parentNode; |
| 5799 } |
| 5800 |
| 5801 return typeof node.getElementById === 'function' ? node : null; |
| 5802 } |
| 5803 |
| 5804 Node.prototype.bind = function(name, observable) { |
| 5805 console.error('Unhandled binding to Node: ', this, name, observable); |
| 5806 }; |
| 5807 |
| 5808 Node.prototype.bindFinished = function() {}; |
| 5809 |
| 5810 function updateBindings(node, name, binding) { |
| 5811 var bindings = node.bindings_; |
| 5812 if (!bindings) |
| 5813 bindings = node.bindings_ = {}; |
| 5814 |
| 5815 if (bindings[name]) |
| 5816 binding[name].close(); |
| 5817 |
| 5818 return bindings[name] = binding; |
| 5819 } |
| 5820 |
| 5821 function returnBinding(node, name, binding) { |
| 5822 return binding; |
| 5823 } |
| 5824 |
| 5825 function sanitizeValue(value) { |
| 5826 return value == null ? '' : value; |
| 5827 } |
| 5828 |
| 5829 function updateText(node, value) { |
| 5830 node.data = sanitizeValue(value); |
| 5831 } |
| 5832 |
| 5833 function textBinding(node) { |
| 5834 return function(value) { |
| 5835 return updateText(node, value); |
| 5836 }; |
| 5837 } |
| 5838 |
| 5839 var maybeUpdateBindings = returnBinding; |
| 5840 |
| 5841 Object.defineProperty(Platform, 'enableBindingsReflection', { |
| 5842 get: function() { |
| 5843 return maybeUpdateBindings === updateBindings; |
| 5844 }, |
| 5845 set: function(enable) { |
| 5846 maybeUpdateBindings = enable ? updateBindings : returnBinding; |
| 5847 return enable; |
| 5848 }, |
| 5849 configurable: true |
| 5850 }); |
| 5851 |
| 5852 Text.prototype.bind = function(name, value, oneTime) { |
| 5853 if (name !== 'textContent') |
| 5854 return Node.prototype.bind.call(this, name, value, oneTime); |
| 5855 |
| 5856 if (oneTime) |
| 5857 return updateText(this, value); |
| 5858 |
| 5859 var observable = value; |
| 5860 updateText(this, observable.open(textBinding(this))); |
| 5861 return maybeUpdateBindings(this, name, observable); |
| 5862 } |
| 5863 |
| 5864 function updateAttribute(el, name, conditional, value) { |
| 5865 if (conditional) { |
| 5866 if (value) |
| 5867 el.setAttribute(name, ''); |
| 5868 else |
| 5869 el.removeAttribute(name); |
| 5870 return; |
| 5871 } |
| 5872 |
| 5873 el.setAttribute(name, sanitizeValue(value)); |
| 5874 } |
| 5875 |
| 5876 function attributeBinding(el, name, conditional) { |
| 5877 return function(value) { |
| 5878 updateAttribute(el, name, conditional, value); |
| 5879 }; |
| 5880 } |
| 5881 |
| 5882 Element.prototype.bind = function(name, value, oneTime) { |
| 5883 var conditional = name[name.length - 1] == '?'; |
| 5884 if (conditional) { |
| 5885 this.removeAttribute(name); |
| 5886 name = name.slice(0, -1); |
| 5887 } |
| 5888 |
| 5889 if (oneTime) |
| 5890 return updateAttribute(this, name, conditional, value); |
| 5891 |
| 5892 |
| 5893 var observable = value; |
| 5894 updateAttribute(this, name, conditional, |
| 5895 observable.open(attributeBinding(this, name, conditional))); |
| 5896 |
| 5897 return maybeUpdateBindings(this, name, observable); |
| 5898 }; |
| 5899 |
| 5900 var checkboxEventType; |
| 5901 (function() { |
| 5902 // Attempt to feature-detect which event (change or click) is fired first |
| 5903 // for checkboxes. |
| 5904 var div = document.createElement('div'); |
| 5905 var checkbox = div.appendChild(document.createElement('input')); |
| 5906 checkbox.setAttribute('type', 'checkbox'); |
| 5907 var first; |
| 5908 var count = 0; |
| 5909 checkbox.addEventListener('click', function(e) { |
| 5910 count++; |
| 5911 first = first || 'click'; |
| 5912 }); |
| 5913 checkbox.addEventListener('change', function() { |
| 5914 count++; |
| 5915 first = first || 'change'; |
| 5916 }); |
| 5917 |
| 5918 var event = document.createEvent('MouseEvent'); |
| 5919 event.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, |
| 5920 false, false, false, 0, null); |
| 5921 checkbox.dispatchEvent(event); |
| 5922 // WebKit/Blink don't fire the change event if the element is outside the |
| 5923 // document, so assume 'change' for that case. |
| 5924 checkboxEventType = count == 1 ? 'change' : first; |
| 5925 })(); |
| 5926 |
| 5927 function getEventForInputType(element) { |
| 5928 switch (element.type) { |
| 5929 case 'checkbox': |
| 5930 return checkboxEventType; |
| 5931 case 'radio': |
| 5932 case 'select-multiple': |
| 5933 case 'select-one': |
| 5934 return 'change'; |
| 5935 case 'range': |
| 5936 if (/Trident|MSIE/.test(navigator.userAgent)) |
| 5937 return 'change'; |
| 5938 default: |
| 5939 return 'input'; |
| 5940 } |
| 5941 } |
| 5942 |
| 5943 function updateInput(input, property, value, santizeFn) { |
| 5944 input[property] = (santizeFn || sanitizeValue)(value); |
| 5945 } |
| 5946 |
| 5947 function inputBinding(input, property, santizeFn) { |
| 5948 return function(value) { |
| 5949 return updateInput(input, property, value, santizeFn); |
| 5950 } |
| 5951 } |
| 5952 |
| 5953 function noop() {} |
| 5954 |
| 5955 function bindInputEvent(input, property, observable, postEventFn) { |
| 5956 var eventType = getEventForInputType(input); |
| 5957 |
| 5958 function eventHandler() { |
| 5959 observable.setValue(input[property]); |
| 5960 observable.discardChanges(); |
| 5961 (postEventFn || noop)(input); |
| 5962 Platform.performMicrotaskCheckpoint(); |
| 5963 } |
| 5964 input.addEventListener(eventType, eventHandler); |
| 5965 |
| 5966 return { |
| 5967 close: function() { |
| 5968 input.removeEventListener(eventType, eventHandler); |
| 5969 observable.close(); |
| 5970 }, |
| 5971 |
| 5972 observable_: observable |
| 5973 } |
| 5974 } |
| 5975 |
| 5976 function booleanSanitize(value) { |
| 5977 return Boolean(value); |
| 5978 } |
| 5979 |
| 5980 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'. |
| 5981 // Returns an array containing all radio buttons other than |element| that |
| 5982 // have the same |name|, either in the form that |element| belongs to or, |
| 5983 // if no form, in the document tree to which |element| belongs. |
| 5984 // |
| 5985 // This implementation is based upon the HTML spec definition of a |
| 5986 // "radio button group": |
| 5987 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.
html#radio-button-group |
| 5988 // |
| 5989 function getAssociatedRadioButtons(element) { |
| 5990 if (element.form) { |
| 5991 return filter(element.form.elements, function(el) { |
| 5992 return el != element && |
| 5993 el.tagName == 'INPUT' && |
| 5994 el.type == 'radio' && |
| 5995 el.name == element.name; |
| 5996 }); |
| 5997 } else { |
| 5998 var treeScope = getTreeScope(element); |
| 5999 if (!treeScope) |
| 6000 return []; |
| 6001 var radios = treeScope.querySelectorAll( |
| 6002 'input[type="radio"][name="' + element.name + '"]'); |
| 6003 return filter(radios, function(el) { |
| 6004 return el != element && !el.form; |
| 6005 }); |
| 6006 } |
| 6007 } |
| 6008 |
| 6009 function checkedPostEvent(input) { |
| 6010 // Only the radio button that is getting checked gets an event. We |
| 6011 // therefore find all the associated radio buttons and update their |
| 6012 // check binding manually. |
| 6013 if (input.tagName === 'INPUT' && |
| 6014 input.type === 'radio') { |
| 6015 getAssociatedRadioButtons(input).forEach(function(radio) { |
| 6016 var checkedBinding = radio.bindings_.checked; |
| 6017 if (checkedBinding) { |
| 6018 // Set the value directly to avoid an infinite call stack. |
| 6019 checkedBinding.observable_.setValue(false); |
| 6020 } |
| 6021 }); |
| 6022 } |
| 6023 } |
| 6024 |
| 6025 HTMLInputElement.prototype.bind = function(name, value, oneTime) { |
| 6026 if (name !== 'value' && name !== 'checked') |
| 6027 return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
| 6028 |
| 6029 this.removeAttribute(name); |
| 6030 var sanitizeFn = name == 'checked' ? booleanSanitize : sanitizeValue; |
| 6031 var postEventFn = name == 'checked' ? checkedPostEvent : noop; |
| 6032 |
| 6033 if (oneTime) |
| 6034 return updateInput(this, name, value, sanitizeFn); |
| 6035 |
| 6036 |
| 6037 var observable = value; |
| 6038 var binding = bindInputEvent(this, name, observable, postEventFn); |
| 6039 updateInput(this, name, |
| 6040 observable.open(inputBinding(this, name, sanitizeFn)), |
| 6041 sanitizeFn); |
| 6042 |
| 6043 // Checkboxes may need to update bindings of other checkboxes. |
| 6044 return updateBindings(this, name, binding); |
| 6045 } |
| 6046 |
| 6047 HTMLTextAreaElement.prototype.bind = function(name, value, oneTime) { |
| 6048 if (name !== 'value') |
| 6049 return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
| 6050 |
| 6051 this.removeAttribute('value'); |
| 6052 |
| 6053 if (oneTime) |
| 6054 return updateInput(this, 'value', value); |
| 6055 |
| 6056 var observable = value; |
| 6057 var binding = bindInputEvent(this, 'value', observable); |
| 6058 updateInput(this, 'value', |
| 6059 observable.open(inputBinding(this, 'value', sanitizeValue))); |
| 6060 return maybeUpdateBindings(this, name, binding); |
| 6061 } |
| 6062 |
| 6063 function updateOption(option, value) { |
| 6064 var parentNode = option.parentNode;; |
| 6065 var select; |
| 6066 var selectBinding; |
| 6067 var oldValue; |
| 6068 if (parentNode instanceof HTMLSelectElement && |
| 6069 parentNode.bindings_ && |
| 6070 parentNode.bindings_.value) { |
| 6071 select = parentNode; |
| 6072 selectBinding = select.bindings_.value; |
| 6073 oldValue = select.value; |
| 6074 } |
| 6075 |
| 6076 option.value = sanitizeValue(value); |
| 6077 |
| 6078 if (select && select.value != oldValue) { |
| 6079 selectBinding.observable_.setValue(select.value); |
| 6080 selectBinding.observable_.discardChanges(); |
| 6081 Platform.performMicrotaskCheckpoint(); |
| 6082 } |
| 6083 } |
| 6084 |
| 6085 function optionBinding(option) { |
| 6086 return function(value) { |
| 6087 updateOption(option, value); |
| 6088 } |
| 6089 } |
| 6090 |
| 6091 HTMLOptionElement.prototype.bind = function(name, value, oneTime) { |
| 6092 if (name !== 'value') |
| 6093 return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
| 6094 |
| 6095 this.removeAttribute('value'); |
| 6096 |
| 6097 if (oneTime) |
| 6098 return updateOption(this, value); |
| 6099 |
| 6100 var observable = value; |
| 6101 var binding = bindInputEvent(this, 'value', observable); |
| 6102 updateOption(this, observable.open(optionBinding(this))); |
| 6103 return maybeUpdateBindings(this, name, binding); |
| 6104 } |
| 6105 |
| 6106 HTMLSelectElement.prototype.bind = function(name, value, oneTime) { |
| 6107 if (name === 'selectedindex') |
| 6108 name = 'selectedIndex'; |
| 6109 |
| 6110 if (name !== 'selectedIndex' && name !== 'value') |
| 6111 return HTMLElement.prototype.bind.call(this, name, value, oneTime); |
| 6112 |
| 6113 this.removeAttribute(name); |
| 6114 |
| 6115 if (oneTime) |
| 6116 return updateInput(this, name, value); |
| 6117 |
| 6118 var observable = value; |
| 6119 var binding = bindInputEvent(this, name, observable); |
| 6120 updateInput(this, name, |
| 6121 observable.open(inputBinding(this, name))); |
| 6122 |
| 6123 // Option update events may need to access select bindings. |
| 6124 return updateBindings(this, name, binding); |
| 6125 } |
| 6126 })(this); |
| 6127 |
| 6128 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 6129 // This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
| 6130 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
| 6131 // The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
| 6132 // Code distributed by Google as part of the polymer project is also |
| 6133 // subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
| 6134 |
| 6135 (function(global) { |
| 6136 'use strict'; |
| 6137 |
| 6138 function assert(v) { |
| 6139 if (!v) |
| 6140 throw new Error('Assertion failed'); |
| 6141 } |
| 6142 |
| 6143 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); |
| 6144 |
| 6145 function getFragmentRoot(node) { |
| 6146 var p; |
| 6147 while (p = node.parentNode) { |
| 6148 node = p; |
| 6149 } |
| 6150 |
| 6151 return node; |
| 6152 } |
| 6153 |
| 6154 function searchRefId(node, id) { |
| 6155 if (!id) |
| 6156 return; |
| 6157 |
| 6158 var ref; |
| 6159 var selector = '#' + id; |
| 6160 while (!ref) { |
| 6161 node = getFragmentRoot(node); |
| 6162 |
| 6163 if (node.protoContent_) |
| 6164 ref = node.protoContent_.querySelector(selector); |
| 6165 else if (node.getElementById) |
| 6166 ref = node.getElementById(id); |
| 6167 |
| 6168 if (ref || !node.templateCreator_) |
| 6169 break |
| 6170 |
| 6171 node = node.templateCreator_; |
| 6172 } |
| 6173 |
| 6174 return ref; |
| 6175 } |
| 6176 |
| 6177 function getInstanceRoot(node) { |
| 6178 while (node.parentNode) { |
| 6179 node = node.parentNode; |
| 6180 } |
| 6181 return node.templateCreator_ ? node : null; |
| 6182 } |
| 6183 |
| 6184 var Map; |
| 6185 if (global.Map && typeof global.Map.prototype.forEach === 'function') { |
| 6186 Map = global.Map; |
| 6187 } else { |
| 6188 Map = function() { |
| 6189 this.keys = []; |
| 6190 this.values = []; |
| 6191 }; |
| 6192 |
| 6193 Map.prototype = { |
| 6194 set: function(key, value) { |
| 6195 var index = this.keys.indexOf(key); |
| 6196 if (index < 0) { |
| 6197 this.keys.push(key); |
| 6198 this.values.push(value); |
| 6199 } else { |
| 6200 this.values[index] = value; |
| 6201 } |
| 6202 }, |
| 6203 |
| 6204 get: function(key) { |
| 6205 var index = this.keys.indexOf(key); |
| 6206 if (index < 0) |
| 6207 return; |
| 6208 |
| 6209 return this.values[index]; |
| 6210 }, |
| 6211 |
| 6212 delete: function(key, value) { |
| 6213 var index = this.keys.indexOf(key); |
| 6214 if (index < 0) |
| 6215 return false; |
| 6216 |
| 6217 this.keys.splice(index, 1); |
| 6218 this.values.splice(index, 1); |
| 6219 return true; |
| 6220 }, |
| 6221 |
| 6222 forEach: function(f, opt_this) { |
| 6223 for (var i = 0; i < this.keys.length; i++) |
| 6224 f.call(opt_this || this, this.values[i], this.keys[i], this); |
| 6225 } |
| 6226 }; |
| 6227 } |
| 6228 |
| 6229 // JScript does not have __proto__. We wrap all object literals with |
| 6230 // createObject which uses Object.create, Object.defineProperty and |
| 6231 // Object.getOwnPropertyDescriptor to create a new object that does the exact |
| 6232 // same thing. The main downside to this solution is that we have to extract |
| 6233 // all those property descriptors for IE. |
| 6234 var createObject = ('__proto__' in {}) ? |
| 6235 function(obj) { return obj; } : |
| 6236 function(obj) { |
| 6237 var proto = obj.__proto__; |
| 6238 if (!proto) |
| 6239 return obj; |
| 6240 var newObject = Object.create(proto); |
| 6241 Object.getOwnPropertyNames(obj).forEach(function(name) { |
| 6242 Object.defineProperty(newObject, name, |
| 6243 Object.getOwnPropertyDescriptor(obj, name)); |
| 6244 }); |
| 6245 return newObject; |
| 6246 }; |
| 6247 |
| 6248 // IE does not support have Document.prototype.contains. |
| 6249 if (typeof document.contains != 'function') { |
| 6250 Document.prototype.contains = function(node) { |
| 6251 if (node === this || node.parentNode === this) |
| 6252 return true; |
| 6253 return this.documentElement.contains(node); |
| 6254 } |
| 6255 } |
| 6256 |
| 6257 var BIND = 'bind'; |
| 6258 var REPEAT = 'repeat'; |
| 6259 var IF = 'if'; |
| 6260 |
| 6261 var templateAttributeDirectives = { |
| 6262 'template': true, |
| 6263 'repeat': true, |
| 6264 'bind': true, |
| 6265 'ref': true |
| 6266 }; |
| 6267 |
| 6268 var semanticTemplateElements = { |
| 6269 'THEAD': true, |
| 6270 'TBODY': true, |
| 6271 'TFOOT': true, |
| 6272 'TH': true, |
| 6273 'TR': true, |
| 6274 'TD': true, |
| 6275 'COLGROUP': true, |
| 6276 'COL': true, |
| 6277 'CAPTION': true, |
| 6278 'OPTION': true, |
| 6279 'OPTGROUP': true |
| 6280 }; |
| 6281 |
| 6282 var hasTemplateElement = typeof HTMLTemplateElement !== 'undefined'; |
| 6283 if (hasTemplateElement) { |
| 6284 // TODO(rafaelw): Remove when fix for |
| 6285 // https://codereview.chromium.org/164803002/ |
| 6286 // makes it to Chrome release. |
| 6287 (function() { |
| 6288 var t = document.createElement('template'); |
| 6289 var d = t.content.ownerDocument; |
| 6290 var html = d.appendChild(d.createElement('html')); |
| 6291 var head = html.appendChild(d.createElement('head')); |
| 6292 var base = d.createElement('base'); |
| 6293 base.href = document.baseURI; |
| 6294 head.appendChild(base); |
| 6295 })(); |
| 6296 } |
| 6297 |
| 6298 var allTemplatesSelectors = 'template, ' + |
| 6299 Object.keys(semanticTemplateElements).map(function(tagName) { |
| 6300 return tagName.toLowerCase() + '[template]'; |
| 6301 }).join(', '); |
| 6302 |
| 6303 function isSVGTemplate(el) { |
| 6304 return el.tagName == 'template' && |
| 6305 el.namespaceURI == 'http://www.w3.org/2000/svg'; |
| 6306 } |
| 6307 |
| 6308 function isHTMLTemplate(el) { |
| 6309 return el.tagName == 'TEMPLATE' && |
| 6310 el.namespaceURI == 'http://www.w3.org/1999/xhtml'; |
| 6311 } |
| 6312 |
| 6313 function isAttributeTemplate(el) { |
| 6314 return Boolean(semanticTemplateElements[el.tagName] && |
| 6315 el.hasAttribute('template')); |
| 6316 } |
| 6317 |
| 6318 function isTemplate(el) { |
| 6319 if (el.isTemplate_ === undefined) |
| 6320 el.isTemplate_ = el.tagName == 'TEMPLATE' || isAttributeTemplate(el); |
| 6321 |
| 6322 return el.isTemplate_; |
| 6323 } |
| 6324 |
| 6325 // FIXME: Observe templates being added/removed from documents |
| 6326 // FIXME: Expose imperative API to decorate and observe templates in |
| 6327 // "disconnected tress" (e.g. ShadowRoot) |
| 6328 document.addEventListener('DOMContentLoaded', function(e) { |
| 6329 bootstrapTemplatesRecursivelyFrom(document); |
| 6330 // FIXME: Is this needed? Seems like it shouldn't be. |
| 6331 Platform.performMicrotaskCheckpoint(); |
| 6332 }, false); |
| 6333 |
| 6334 function forAllTemplatesFrom(node, fn) { |
| 6335 var subTemplates = node.querySelectorAll(allTemplatesSelectors); |
| 6336 |
| 6337 if (isTemplate(node)) |
| 6338 fn(node) |
| 6339 forEach(subTemplates, fn); |
| 6340 } |
| 6341 |
| 6342 function bootstrapTemplatesRecursivelyFrom(node) { |
| 6343 function bootstrap(template) { |
| 6344 if (!HTMLTemplateElement.decorate(template)) |
| 6345 bootstrapTemplatesRecursivelyFrom(template.content); |
| 6346 } |
| 6347 |
| 6348 forAllTemplatesFrom(node, bootstrap); |
| 6349 } |
| 6350 |
| 6351 if (!hasTemplateElement) { |
| 6352 /** |
| 6353 * This represents a <template> element. |
| 6354 * @constructor |
| 6355 * @extends {HTMLElement} |
| 6356 */ |
| 6357 global.HTMLTemplateElement = function() { |
| 6358 throw TypeError('Illegal constructor'); |
| 6359 }; |
| 6360 } |
| 6361 |
| 6362 var hasProto = '__proto__' in {}; |
| 6363 |
| 6364 function mixin(to, from) { |
| 6365 Object.getOwnPropertyNames(from).forEach(function(name) { |
| 6366 Object.defineProperty(to, name, |
| 6367 Object.getOwnPropertyDescriptor(from, name)); |
| 6368 }); |
| 6369 } |
| 6370 |
| 6371 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#
dfn-template-contents-owner |
| 6372 function getOrCreateTemplateContentsOwner(template) { |
| 6373 var doc = template.ownerDocument |
| 6374 if (!doc.defaultView) |
| 6375 return doc; |
| 6376 var d = doc.templateContentsOwner_; |
| 6377 if (!d) { |
| 6378 // TODO(arv): This should either be a Document or HTMLDocument depending |
| 6379 // on doc. |
| 6380 d = doc.implementation.createHTMLDocument(''); |
| 6381 while (d.lastChild) { |
| 6382 d.removeChild(d.lastChild); |
| 6383 } |
| 6384 doc.templateContentsOwner_ = d; |
| 6385 } |
| 6386 return d; |
| 6387 } |
| 6388 |
| 6389 function getTemplateStagingDocument(template) { |
| 6390 if (!template.stagingDocument_) { |
| 6391 var owner = template.ownerDocument; |
| 6392 if (!owner.stagingDocument_) { |
| 6393 owner.stagingDocument_ = owner.implementation.createHTMLDocument(''); |
| 6394 owner.stagingDocument_.isStagingDocument = true; |
| 6395 // TODO(rafaelw): Remove when fix for |
| 6396 // https://codereview.chromium.org/164803002/ |
| 6397 // makes it to Chrome release. |
| 6398 var base = owner.stagingDocument_.createElement('base'); |
| 6399 base.href = document.baseURI; |
| 6400 owner.stagingDocument_.head.appendChild(base); |
| 6401 |
| 6402 owner.stagingDocument_.stagingDocument_ = owner.stagingDocument_; |
| 6403 } |
| 6404 |
| 6405 template.stagingDocument_ = owner.stagingDocument_; |
| 6406 } |
| 6407 |
| 6408 return template.stagingDocument_; |
| 6409 } |
| 6410 |
| 6411 // For non-template browsers, the parser will disallow <template> in certain |
| 6412 // locations, so we allow "attribute templates" which combine the template |
| 6413 // element with the top-level container node of the content, e.g. |
| 6414 // |
| 6415 // <tr template repeat="{{ foo }}"" class="bar"><td>Bar</td></tr> |
| 6416 // |
| 6417 // becomes |
| 6418 // |
| 6419 // <template repeat="{{ foo }}"> |
| 6420 // + #document-fragment |
| 6421 // + <tr class="bar"> |
| 6422 // + <td>Bar</td> |
| 6423 // |
| 6424 function extractTemplateFromAttributeTemplate(el) { |
| 6425 var template = el.ownerDocument.createElement('template'); |
| 6426 el.parentNode.insertBefore(template, el); |
| 6427 |
| 6428 var attribs = el.attributes; |
| 6429 var count = attribs.length; |
| 6430 while (count-- > 0) { |
| 6431 var attrib = attribs[count]; |
| 6432 if (templateAttributeDirectives[attrib.name]) { |
| 6433 if (attrib.name !== 'template') |
| 6434 template.setAttribute(attrib.name, attrib.value); |
| 6435 el.removeAttribute(attrib.name); |
| 6436 } |
| 6437 } |
| 6438 |
| 6439 return template; |
| 6440 } |
| 6441 |
| 6442 function extractTemplateFromSVGTemplate(el) { |
| 6443 var template = el.ownerDocument.createElement('template'); |
| 6444 el.parentNode.insertBefore(template, el); |
| 6445 |
| 6446 var attribs = el.attributes; |
| 6447 var count = attribs.length; |
| 6448 while (count-- > 0) { |
| 6449 var attrib = attribs[count]; |
| 6450 template.setAttribute(attrib.name, attrib.value); |
| 6451 el.removeAttribute(attrib.name); |
| 6452 } |
| 6453 |
| 6454 el.parentNode.removeChild(el); |
| 6455 return template; |
| 6456 } |
| 6457 |
| 6458 function liftNonNativeTemplateChildrenIntoContent(template, el, useRoot) { |
| 6459 var content = template.content; |
| 6460 if (useRoot) { |
| 6461 content.appendChild(el); |
| 6462 return; |
| 6463 } |
| 6464 |
| 6465 var child; |
| 6466 while (child = el.firstChild) { |
| 6467 content.appendChild(child); |
| 6468 } |
| 6469 } |
| 6470 |
| 6471 var templateObserver; |
| 6472 if (typeof MutationObserver == 'function') { |
| 6473 templateObserver = new MutationObserver(function(records) { |
| 6474 for (var i = 0; i < records.length; i++) { |
| 6475 records[i].target.refChanged_(); |
| 6476 } |
| 6477 }); |
| 6478 } |
| 6479 |
| 6480 /** |
| 6481 * Ensures proper API and content model for template elements. |
| 6482 * @param {HTMLTemplateElement} opt_instanceRef The template element which |
| 6483 * |el| template element will return as the value of its ref(), and whose |
| 6484 * content will be used as source when createInstance() is invoked. |
| 6485 */ |
| 6486 HTMLTemplateElement.decorate = function(el, opt_instanceRef) { |
| 6487 if (el.templateIsDecorated_) |
| 6488 return false; |
| 6489 |
| 6490 var templateElement = el; |
| 6491 templateElement.templateIsDecorated_ = true; |
| 6492 |
| 6493 var isNativeHTMLTemplate = isHTMLTemplate(templateElement) && |
| 6494 hasTemplateElement; |
| 6495 var bootstrapContents = isNativeHTMLTemplate; |
| 6496 var liftContents = !isNativeHTMLTemplate; |
| 6497 var liftRoot = false; |
| 6498 |
| 6499 if (!isNativeHTMLTemplate) { |
| 6500 if (isAttributeTemplate(templateElement)) { |
| 6501 assert(!opt_instanceRef); |
| 6502 templateElement = extractTemplateFromAttributeTemplate(el); |
| 6503 templateElement.templateIsDecorated_ = true; |
| 6504 isNativeHTMLTemplate = hasTemplateElement; |
| 6505 liftRoot = true; |
| 6506 } else if (isSVGTemplate(templateElement)) { |
| 6507 templateElement = extractTemplateFromSVGTemplate(el); |
| 6508 templateElement.templateIsDecorated_ = true; |
| 6509 isNativeHTMLTemplate = hasTemplateElement; |
| 6510 } |
| 6511 } |
| 6512 |
| 6513 if (!isNativeHTMLTemplate) { |
| 6514 fixTemplateElementPrototype(templateElement); |
| 6515 var doc = getOrCreateTemplateContentsOwner(templateElement); |
| 6516 templateElement.content_ = doc.createDocumentFragment(); |
| 6517 } |
| 6518 |
| 6519 if (opt_instanceRef) { |
| 6520 // template is contained within an instance, its direct content must be |
| 6521 // empty |
| 6522 templateElement.instanceRef_ = opt_instanceRef; |
| 6523 } else if (liftContents) { |
| 6524 liftNonNativeTemplateChildrenIntoContent(templateElement, |
| 6525 el, |
| 6526 liftRoot); |
| 6527 } else if (bootstrapContents) { |
| 6528 bootstrapTemplatesRecursivelyFrom(templateElement.content); |
| 6529 } |
| 6530 |
| 6531 return true; |
| 6532 }; |
| 6533 |
| 6534 // TODO(rafaelw): This used to decorate recursively all templates from a given |
| 6535 // node. This happens by default on 'DOMContentLoaded', but may be needed |
| 6536 // in subtrees not descendent from document (e.g. ShadowRoot). |
| 6537 // Review whether this is the right public API. |
| 6538 HTMLTemplateElement.bootstrap = bootstrapTemplatesRecursivelyFrom; |
| 6539 |
| 6540 var htmlElement = global.HTMLUnknownElement || HTMLElement; |
| 6541 |
| 6542 var contentDescriptor = { |
| 6543 get: function() { |
| 6544 return this.content_; |
| 6545 }, |
| 6546 enumerable: true, |
| 6547 configurable: true |
| 6548 }; |
| 6549 |
| 6550 if (!hasTemplateElement) { |
| 6551 // Gecko is more picky with the prototype than WebKit. Make sure to use the |
| 6552 // same prototype as created in the constructor. |
| 6553 HTMLTemplateElement.prototype = Object.create(htmlElement.prototype); |
| 6554 |
| 6555 Object.defineProperty(HTMLTemplateElement.prototype, 'content', |
| 6556 contentDescriptor); |
| 6557 } |
| 6558 |
| 6559 function fixTemplateElementPrototype(el) { |
| 6560 if (hasProto) |
| 6561 el.__proto__ = HTMLTemplateElement.prototype; |
| 6562 else |
| 6563 mixin(el, HTMLTemplateElement.prototype); |
| 6564 } |
| 6565 |
| 6566 function ensureSetModelScheduled(template) { |
| 6567 if (!template.setModelFn_) { |
| 6568 template.setModelFn_ = function() { |
| 6569 template.setModelFnScheduled_ = false; |
| 6570 var map = getBindings(template, |
| 6571 template.delegate_ && template.delegate_.prepareBinding); |
| 6572 processBindings(template, map, template.model_); |
| 6573 }; |
| 6574 } |
| 6575 |
| 6576 if (!template.setModelFnScheduled_) { |
| 6577 template.setModelFnScheduled_ = true; |
| 6578 Observer.runEOM_(template.setModelFn_); |
| 6579 } |
| 6580 } |
| 6581 |
| 6582 mixin(HTMLTemplateElement.prototype, { |
| 6583 bind: function(name, value, oneTime) { |
| 6584 if (name != 'ref') |
| 6585 return Element.prototype.bind.call(this, name, value, oneTime); |
| 6586 |
| 6587 var self = this; |
| 6588 var ref = oneTime ? value : value.open(function(ref) { |
| 6589 self.setAttribute('ref', ref); |
| 6590 self.refChanged_(); |
| 6591 }); |
| 6592 |
| 6593 this.setAttribute('ref', ref); |
| 6594 this.refChanged_(); |
| 6595 if (oneTime) |
| 6596 return; |
| 6597 |
| 6598 if (!this.bindings_) { |
| 6599 this.bindings_ = { ref: value }; |
| 6600 } else { |
| 6601 this.bindings_.ref = value; |
| 6602 } |
| 6603 |
| 6604 return value; |
| 6605 }, |
| 6606 |
| 6607 processBindingDirectives_: function(directives) { |
| 6608 if (this.iterator_) |
| 6609 this.iterator_.closeDeps(); |
| 6610 |
| 6611 if (!directives.if && !directives.bind && !directives.repeat) { |
| 6612 if (this.iterator_) { |
| 6613 this.iterator_.close(); |
| 6614 this.iterator_ = undefined; |
| 6615 } |
| 6616 |
| 6617 return; |
| 6618 } |
| 6619 |
| 6620 if (!this.iterator_) { |
| 6621 this.iterator_ = new TemplateIterator(this); |
| 6622 } |
| 6623 |
| 6624 this.iterator_.updateDependencies(directives, this.model_); |
| 6625 |
| 6626 if (templateObserver) { |
| 6627 templateObserver.observe(this, { attributes: true, |
| 6628 attributeFilter: ['ref'] }); |
| 6629 } |
| 6630 |
| 6631 return this.iterator_; |
| 6632 }, |
| 6633 |
| 6634 createInstance: function(model, bindingDelegate, delegate_) { |
| 6635 if (bindingDelegate) |
| 6636 delegate_ = this.newDelegate_(bindingDelegate); |
| 6637 else if (!delegate_) |
| 6638 delegate_ = this.delegate_; |
| 6639 |
| 6640 if (!this.refContent_) |
| 6641 this.refContent_ = this.ref_.content; |
| 6642 var content = this.refContent_; |
| 6643 if (content.firstChild === null) |
| 6644 return emptyInstance; |
| 6645 |
| 6646 var map = getInstanceBindingMap(content, delegate_); |
| 6647 var stagingDocument = getTemplateStagingDocument(this); |
| 6648 var instance = stagingDocument.createDocumentFragment(); |
| 6649 instance.templateCreator_ = this; |
| 6650 instance.protoContent_ = content; |
| 6651 instance.bindings_ = []; |
| 6652 instance.terminator_ = null; |
| 6653 var instanceRecord = instance.templateInstance_ = { |
| 6654 firstNode: null, |
| 6655 lastNode: null, |
| 6656 model: model |
| 6657 }; |
| 6658 |
| 6659 var i = 0; |
| 6660 var collectTerminator = false; |
| 6661 for (var child = content.firstChild; child; child = child.nextSibling) { |
| 6662 // The terminator of the instance is the clone of the last child of the |
| 6663 // content. If the last child is an active template, it may produce |
| 6664 // instances as a result of production, so simply collecting the last |
| 6665 // child of the instance after it has finished producing may be wrong. |
| 6666 if (child.nextSibling === null) |
| 6667 collectTerminator = true; |
| 6668 |
| 6669 var clone = cloneAndBindInstance(child, instance, stagingDocument, |
| 6670 map.children[i++], |
| 6671 model, |
| 6672 delegate_, |
| 6673 instance.bindings_); |
| 6674 clone.templateInstance_ = instanceRecord; |
| 6675 if (collectTerminator) |
| 6676 instance.terminator_ = clone; |
| 6677 } |
| 6678 |
| 6679 instanceRecord.firstNode = instance.firstChild; |
| 6680 instanceRecord.lastNode = instance.lastChild; |
| 6681 instance.templateCreator_ = undefined; |
| 6682 instance.protoContent_ = undefined; |
| 6683 return instance; |
| 6684 }, |
| 6685 |
| 6686 get model() { |
| 6687 return this.model_; |
| 6688 }, |
| 6689 |
| 6690 set model(model) { |
| 6691 this.model_ = model; |
| 6692 ensureSetModelScheduled(this); |
| 6693 }, |
| 6694 |
| 6695 get bindingDelegate() { |
| 6696 return this.delegate_ && this.delegate_.raw; |
| 6697 }, |
| 6698 |
| 6699 refChanged_: function() { |
| 6700 if (!this.iterator_ || this.refContent_ === this.ref_.content) |
| 6701 return; |
| 6702 |
| 6703 this.refContent_ = undefined; |
| 6704 this.iterator_.valueChanged(); |
| 6705 this.iterator_.updateIteratedValue(this.iterator_.getUpdatedValue()); |
| 6706 }, |
| 6707 |
| 6708 clear: function() { |
| 6709 this.model_ = undefined; |
| 6710 this.delegate_ = undefined; |
| 6711 if (this.bindings_ && this.bindings_.ref) |
| 6712 this.bindings_.ref.close() |
| 6713 this.refContent_ = undefined; |
| 6714 if (!this.iterator_) |
| 6715 return; |
| 6716 this.iterator_.valueChanged(); |
| 6717 this.iterator_.close() |
| 6718 this.iterator_ = undefined; |
| 6719 }, |
| 6720 |
| 6721 setDelegate_: function(delegate) { |
| 6722 this.delegate_ = delegate; |
| 6723 this.bindingMap_ = undefined; |
| 6724 if (this.iterator_) { |
| 6725 this.iterator_.instancePositionChangedFn_ = undefined; |
| 6726 this.iterator_.instanceModelFn_ = undefined; |
| 6727 } |
| 6728 }, |
| 6729 |
| 6730 newDelegate_: function(bindingDelegate) { |
| 6731 if (!bindingDelegate) |
| 6732 return; |
| 6733 |
| 6734 function delegateFn(name) { |
| 6735 var fn = bindingDelegate && bindingDelegate[name]; |
| 6736 if (typeof fn != 'function') |
| 6737 return; |
| 6738 |
| 6739 return function() { |
| 6740 return fn.apply(bindingDelegate, arguments); |
| 6741 }; |
| 6742 } |
| 6743 |
| 6744 return { |
| 6745 bindingMaps: {}, |
| 6746 raw: bindingDelegate, |
| 6747 prepareBinding: delegateFn('prepareBinding'), |
| 6748 prepareInstanceModel: delegateFn('prepareInstanceModel'), |
| 6749 prepareInstancePositionChanged: |
| 6750 delegateFn('prepareInstancePositionChanged') |
| 6751 }; |
| 6752 }, |
| 6753 |
| 6754 set bindingDelegate(bindingDelegate) { |
| 6755 if (this.delegate_) { |
| 6756 throw Error('Template must be cleared before a new bindingDelegate ' + |
| 6757 'can be assigned'); |
| 6758 } |
| 6759 |
| 6760 this.setDelegate_(this.newDelegate_(bindingDelegate)); |
| 6761 }, |
| 6762 |
| 6763 get ref_() { |
| 6764 var ref = searchRefId(this, this.getAttribute('ref')); |
| 6765 if (!ref) |
| 6766 ref = this.instanceRef_; |
| 6767 |
| 6768 if (!ref) |
| 6769 return this; |
| 6770 |
| 6771 var nextRef = ref.ref_; |
| 6772 return nextRef ? nextRef : ref; |
| 6773 } |
| 6774 }); |
| 6775 |
| 6776 // Returns |
| 6777 // a) undefined if there are no mustaches. |
| 6778 // b) [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least one
mustache. |
| 6779 function parseMustaches(s, name, node, prepareBindingFn) { |
| 6780 if (!s || !s.length) |
| 6781 return; |
| 6782 |
| 6783 var tokens; |
| 6784 var length = s.length; |
| 6785 var startIndex = 0, lastIndex = 0, endIndex = 0; |
| 6786 var onlyOneTime = true; |
| 6787 while (lastIndex < length) { |
| 6788 var startIndex = s.indexOf('{{', lastIndex); |
| 6789 var oneTimeStart = s.indexOf('[[', lastIndex); |
| 6790 var oneTime = false; |
| 6791 var terminator = '}}'; |
| 6792 |
| 6793 if (oneTimeStart >= 0 && |
| 6794 (startIndex < 0 || oneTimeStart < startIndex)) { |
| 6795 startIndex = oneTimeStart; |
| 6796 oneTime = true; |
| 6797 terminator = ']]'; |
| 6798 } |
| 6799 |
| 6800 endIndex = startIndex < 0 ? -1 : s.indexOf(terminator, startIndex + 2); |
| 6801 |
| 6802 if (endIndex < 0) { |
| 6803 if (!tokens) |
| 6804 return; |
| 6805 |
| 6806 tokens.push(s.slice(lastIndex)); // TEXT |
| 6807 break; |
| 6808 } |
| 6809 |
| 6810 tokens = tokens || []; |
| 6811 tokens.push(s.slice(lastIndex, startIndex)); // TEXT |
| 6812 var pathString = s.slice(startIndex + 2, endIndex).trim(); |
| 6813 tokens.push(oneTime); // ONE_TIME? |
| 6814 onlyOneTime = onlyOneTime && oneTime; |
| 6815 var delegateFn = prepareBindingFn && |
| 6816 prepareBindingFn(pathString, name, node); |
| 6817 // Don't try to parse the expression if there's a prepareBinding function |
| 6818 if (delegateFn == null) { |
| 6819 tokens.push(Path.get(pathString)); // PATH |
| 6820 } else { |
| 6821 tokens.push(null); |
| 6822 } |
| 6823 tokens.push(delegateFn); // DELEGATE_FN |
| 6824 lastIndex = endIndex + 2; |
| 6825 } |
| 6826 |
| 6827 if (lastIndex === length) |
| 6828 tokens.push(''); // TEXT |
| 6829 |
| 6830 tokens.hasOnePath = tokens.length === 5; |
| 6831 tokens.isSimplePath = tokens.hasOnePath && |
| 6832 tokens[0] == '' && |
| 6833 tokens[4] == ''; |
| 6834 tokens.onlyOneTime = onlyOneTime; |
| 6835 |
| 6836 tokens.combinator = function(values) { |
| 6837 var newValue = tokens[0]; |
| 6838 |
| 6839 for (var i = 1; i < tokens.length; i += 4) { |
| 6840 var value = tokens.hasOnePath ? values : values[(i - 1) / 4]; |
| 6841 if (value !== undefined) |
| 6842 newValue += value; |
| 6843 newValue += tokens[i + 3]; |
| 6844 } |
| 6845 |
| 6846 return newValue; |
| 6847 } |
| 6848 |
| 6849 return tokens; |
| 6850 }; |
| 6851 |
| 6852 function processOneTimeBinding(name, tokens, node, model) { |
| 6853 if (tokens.hasOnePath) { |
| 6854 var delegateFn = tokens[3]; |
| 6855 var value = delegateFn ? delegateFn(model, node, true) : |
| 6856 tokens[2].getValueFrom(model); |
| 6857 return tokens.isSimplePath ? value : tokens.combinator(value); |
| 6858 } |
| 6859 |
| 6860 var values = []; |
| 6861 for (var i = 1; i < tokens.length; i += 4) { |
| 6862 var delegateFn = tokens[i + 2]; |
| 6863 values[(i - 1) / 4] = delegateFn ? delegateFn(model, node) : |
| 6864 tokens[i + 1].getValueFrom(model); |
| 6865 } |
| 6866 |
| 6867 return tokens.combinator(values); |
| 6868 } |
| 6869 |
| 6870 function processSinglePathBinding(name, tokens, node, model) { |
| 6871 var delegateFn = tokens[3]; |
| 6872 var observer = delegateFn ? delegateFn(model, node, false) : |
| 6873 new PathObserver(model, tokens[2]); |
| 6874 |
| 6875 return tokens.isSimplePath ? observer : |
| 6876 new ObserverTransform(observer, tokens.combinator); |
| 6877 } |
| 6878 |
| 6879 function processBinding(name, tokens, node, model) { |
| 6880 if (tokens.onlyOneTime) |
| 6881 return processOneTimeBinding(name, tokens, node, model); |
| 6882 |
| 6883 if (tokens.hasOnePath) |
| 6884 return processSinglePathBinding(name, tokens, node, model); |
| 6885 |
| 6886 var observer = new CompoundObserver(); |
| 6887 |
| 6888 for (var i = 1; i < tokens.length; i += 4) { |
| 6889 var oneTime = tokens[i]; |
| 6890 var delegateFn = tokens[i + 2]; |
| 6891 |
| 6892 if (delegateFn) { |
| 6893 var value = delegateFn(model, node, oneTime); |
| 6894 if (oneTime) |
| 6895 observer.addPath(value) |
| 6896 else |
| 6897 observer.addObserver(value); |
| 6898 continue; |
| 6899 } |
| 6900 |
| 6901 var path = tokens[i + 1]; |
| 6902 if (oneTime) |
| 6903 observer.addPath(path.getValueFrom(model)) |
| 6904 else |
| 6905 observer.addPath(model, path); |
| 6906 } |
| 6907 |
| 6908 return new ObserverTransform(observer, tokens.combinator); |
| 6909 } |
| 6910 |
| 6911 function processBindings(node, bindings, model, instanceBindings) { |
| 6912 for (var i = 0; i < bindings.length; i += 2) { |
| 6913 var name = bindings[i] |
| 6914 var tokens = bindings[i + 1]; |
| 6915 var value = processBinding(name, tokens, node, model); |
| 6916 var binding = node.bind(name, value, tokens.onlyOneTime); |
| 6917 if (binding && instanceBindings) |
| 6918 instanceBindings.push(binding); |
| 6919 } |
| 6920 |
| 6921 node.bindFinished(); |
| 6922 if (!bindings.isTemplate) |
| 6923 return; |
| 6924 |
| 6925 node.model_ = model; |
| 6926 var iter = node.processBindingDirectives_(bindings); |
| 6927 if (instanceBindings && iter) |
| 6928 instanceBindings.push(iter); |
| 6929 } |
| 6930 |
| 6931 function parseWithDefault(el, name, prepareBindingFn) { |
| 6932 var v = el.getAttribute(name); |
| 6933 return parseMustaches(v == '' ? '{{}}' : v, name, el, prepareBindingFn); |
| 6934 } |
| 6935 |
| 6936 function parseAttributeBindings(element, prepareBindingFn) { |
| 6937 assert(element); |
| 6938 |
| 6939 var bindings = []; |
| 6940 var ifFound = false; |
| 6941 var bindFound = false; |
| 6942 |
| 6943 for (var i = 0; i < element.attributes.length; i++) { |
| 6944 var attr = element.attributes[i]; |
| 6945 var name = attr.name; |
| 6946 var value = attr.value; |
| 6947 |
| 6948 // Allow bindings expressed in attributes to be prefixed with underbars. |
| 6949 // We do this to allow correct semantics for browsers that don't implement |
| 6950 // <template> where certain attributes might trigger side-effects -- and |
| 6951 // for IE which sanitizes certain attributes, disallowing mustache |
| 6952 // replacements in their text. |
| 6953 while (name[0] === '_') { |
| 6954 name = name.substring(1); |
| 6955 } |
| 6956 |
| 6957 if (isTemplate(element) && |
| 6958 (name === IF || name === BIND || name === REPEAT)) { |
| 6959 continue; |
| 6960 } |
| 6961 |
| 6962 var tokens = parseMustaches(value, name, element, |
| 6963 prepareBindingFn); |
| 6964 if (!tokens) |
| 6965 continue; |
| 6966 |
| 6967 bindings.push(name, tokens); |
| 6968 } |
| 6969 |
| 6970 if (isTemplate(element)) { |
| 6971 bindings.isTemplate = true; |
| 6972 bindings.if = parseWithDefault(element, IF, prepareBindingFn); |
| 6973 bindings.bind = parseWithDefault(element, BIND, prepareBindingFn); |
| 6974 bindings.repeat = parseWithDefault(element, REPEAT, prepareBindingFn); |
| 6975 |
| 6976 if (bindings.if && !bindings.bind && !bindings.repeat) |
| 6977 bindings.bind = parseMustaches('{{}}', BIND, element, prepareBindingFn); |
| 6978 } |
| 6979 |
| 6980 return bindings; |
| 6981 } |
| 6982 |
| 6983 function getBindings(node, prepareBindingFn) { |
| 6984 if (node.nodeType === Node.ELEMENT_NODE) |
| 6985 return parseAttributeBindings(node, prepareBindingFn); |
| 6986 |
| 6987 if (node.nodeType === Node.TEXT_NODE) { |
| 6988 var tokens = parseMustaches(node.data, 'textContent', node, |
| 6989 prepareBindingFn); |
| 6990 if (tokens) |
| 6991 return ['textContent', tokens]; |
| 6992 } |
| 6993 |
| 6994 return []; |
| 6995 } |
| 6996 |
| 6997 function cloneAndBindInstance(node, parent, stagingDocument, bindings, model, |
| 6998 delegate, |
| 6999 instanceBindings, |
| 7000 instanceRecord) { |
| 7001 var clone = parent.appendChild(stagingDocument.importNode(node, false)); |
| 7002 |
| 7003 var i = 0; |
| 7004 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 7005 cloneAndBindInstance(child, clone, stagingDocument, |
| 7006 bindings.children[i++], |
| 7007 model, |
| 7008 delegate, |
| 7009 instanceBindings); |
| 7010 } |
| 7011 |
| 7012 if (bindings.isTemplate) { |
| 7013 HTMLTemplateElement.decorate(clone, node); |
| 7014 if (delegate) |
| 7015 clone.setDelegate_(delegate); |
| 7016 } |
| 7017 |
| 7018 processBindings(clone, bindings, model, instanceBindings); |
| 7019 return clone; |
| 7020 } |
| 7021 |
| 7022 function createInstanceBindingMap(node, prepareBindingFn) { |
| 7023 var map = getBindings(node, prepareBindingFn); |
| 7024 map.children = {}; |
| 7025 var index = 0; |
| 7026 for (var child = node.firstChild; child; child = child.nextSibling) { |
| 7027 map.children[index++] = createInstanceBindingMap(child, prepareBindingFn); |
| 7028 } |
| 7029 |
| 7030 return map; |
| 7031 } |
| 7032 |
| 7033 var contentUidCounter = 1; |
| 7034 |
| 7035 // TODO(rafaelw): Setup a MutationObserver on content which clears the id |
| 7036 // so that bindingMaps regenerate when the template.content changes. |
| 7037 function getContentUid(content) { |
| 7038 var id = content.id_; |
| 7039 if (!id) |
| 7040 id = content.id_ = contentUidCounter++; |
| 7041 return id; |
| 7042 } |
| 7043 |
| 7044 // Each delegate is associated with a set of bindingMaps, one for each |
| 7045 // content which may be used by a template. The intent is that each binding |
| 7046 // delegate gets the opportunity to prepare the instance (via the prepare* |
| 7047 // delegate calls) once across all uses. |
| 7048 // TODO(rafaelw): Separate out the parse map from the binding map. In the |
| 7049 // current implementation, if two delegates need a binding map for the same |
| 7050 // content, the second will have to reparse. |
| 7051 function getInstanceBindingMap(content, delegate_) { |
| 7052 var contentId = getContentUid(content); |
| 7053 if (delegate_) { |
| 7054 var map = delegate_.bindingMaps[contentId]; |
| 7055 if (!map) { |
| 7056 map = delegate_.bindingMaps[contentId] = |
| 7057 createInstanceBindingMap(content, delegate_.prepareBinding) || []; |
| 7058 } |
| 7059 return map; |
| 7060 } |
| 7061 |
| 7062 var map = content.bindingMap_; |
| 7063 if (!map) { |
| 7064 map = content.bindingMap_ = |
| 7065 createInstanceBindingMap(content, undefined) || []; |
| 7066 } |
| 7067 return map; |
| 7068 } |
| 7069 |
| 7070 Object.defineProperty(Node.prototype, 'templateInstance', { |
| 7071 get: function() { |
| 7072 var instance = this.templateInstance_; |
| 7073 return instance ? instance : |
| 7074 (this.parentNode ? this.parentNode.templateInstance : undefined); |
| 7075 } |
| 7076 }); |
| 7077 |
| 7078 var emptyInstance = document.createDocumentFragment(); |
| 7079 emptyInstance.bindings_ = []; |
| 7080 emptyInstance.terminator_ = null; |
| 7081 |
| 7082 function TemplateIterator(templateElement) { |
| 7083 this.closed = false; |
| 7084 this.templateElement_ = templateElement; |
| 7085 this.instances = []; |
| 7086 this.deps = undefined; |
| 7087 this.iteratedValue = []; |
| 7088 this.presentValue = undefined; |
| 7089 this.arrayObserver = undefined; |
| 7090 } |
| 7091 |
| 7092 TemplateIterator.prototype = { |
| 7093 closeDeps: function() { |
| 7094 var deps = this.deps; |
| 7095 if (deps) { |
| 7096 if (deps.ifOneTime === false) |
| 7097 deps.ifValue.close(); |
| 7098 if (deps.oneTime === false) |
| 7099 deps.value.close(); |
| 7100 } |
| 7101 }, |
| 7102 |
| 7103 updateDependencies: function(directives, model) { |
| 7104 this.closeDeps(); |
| 7105 |
| 7106 var deps = this.deps = {}; |
| 7107 var template = this.templateElement_; |
| 7108 |
| 7109 var ifValue = true; |
| 7110 if (directives.if) { |
| 7111 deps.hasIf = true; |
| 7112 deps.ifOneTime = directives.if.onlyOneTime; |
| 7113 deps.ifValue = processBinding(IF, directives.if, template, model); |
| 7114 |
| 7115 ifValue = deps.ifValue; |
| 7116 |
| 7117 // oneTime if & predicate is false. nothing else to do. |
| 7118 if (deps.ifOneTime && !ifValue) { |
| 7119 this.valueChanged(); |
| 7120 return; |
| 7121 } |
| 7122 |
| 7123 if (!deps.ifOneTime) |
| 7124 ifValue = ifValue.open(this.updateIfValue, this); |
| 7125 } |
| 7126 |
| 7127 if (directives.repeat) { |
| 7128 deps.repeat = true; |
| 7129 deps.oneTime = directives.repeat.onlyOneTime; |
| 7130 deps.value = processBinding(REPEAT, directives.repeat, template, model); |
| 7131 } else { |
| 7132 deps.repeat = false; |
| 7133 deps.oneTime = directives.bind.onlyOneTime; |
| 7134 deps.value = processBinding(BIND, directives.bind, template, model); |
| 7135 } |
| 7136 |
| 7137 var value = deps.value; |
| 7138 if (!deps.oneTime) |
| 7139 value = value.open(this.updateIteratedValue, this); |
| 7140 |
| 7141 if (!ifValue) { |
| 7142 this.valueChanged(); |
| 7143 return; |
| 7144 } |
| 7145 |
| 7146 this.updateValue(value); |
| 7147 }, |
| 7148 |
| 7149 /** |
| 7150 * Gets the updated value of the bind/repeat. This can potentially call |
| 7151 * user code (if a bindingDelegate is set up) so we try to avoid it if we |
| 7152 * already have the value in hand (from Observer.open). |
| 7153 */ |
| 7154 getUpdatedValue: function() { |
| 7155 var value = this.deps.value; |
| 7156 if (!this.deps.oneTime) |
| 7157 value = value.discardChanges(); |
| 7158 return value; |
| 7159 }, |
| 7160 |
| 7161 updateIfValue: function(ifValue) { |
| 7162 if (!ifValue) { |
| 7163 this.valueChanged(); |
| 7164 return; |
| 7165 } |
| 7166 |
| 7167 this.updateValue(this.getUpdatedValue()); |
| 7168 }, |
| 7169 |
| 7170 updateIteratedValue: function(value) { |
| 7171 if (this.deps.hasIf) { |
| 7172 var ifValue = this.deps.ifValue; |
| 7173 if (!this.deps.ifOneTime) |
| 7174 ifValue = ifValue.discardChanges(); |
| 7175 if (!ifValue) { |
| 7176 this.valueChanged(); |
| 7177 return; |
| 7178 } |
| 7179 } |
| 7180 |
| 7181 this.updateValue(value); |
| 7182 }, |
| 7183 |
| 7184 updateValue: function(value) { |
| 7185 if (!this.deps.repeat) |
| 7186 value = [value]; |
| 7187 var observe = this.deps.repeat && |
| 7188 !this.deps.oneTime && |
| 7189 Array.isArray(value); |
| 7190 this.valueChanged(value, observe); |
| 7191 }, |
| 7192 |
| 7193 valueChanged: function(value, observeValue) { |
| 7194 if (!Array.isArray(value)) |
| 7195 value = []; |
| 7196 |
| 7197 if (value === this.iteratedValue) |
| 7198 return; |
| 7199 |
| 7200 this.unobserve(); |
| 7201 this.presentValue = value; |
| 7202 if (observeValue) { |
| 7203 this.arrayObserver = new ArrayObserver(this.presentValue); |
| 7204 this.arrayObserver.open(this.handleSplices, this); |
| 7205 } |
| 7206 |
| 7207 this.handleSplices(ArrayObserver.calculateSplices(this.presentValue, |
| 7208 this.iteratedValue)); |
| 7209 }, |
| 7210 |
| 7211 getLastInstanceNode: function(index) { |
| 7212 if (index == -1) |
| 7213 return this.templateElement_; |
| 7214 var instance = this.instances[index]; |
| 7215 var terminator = instance.terminator_; |
| 7216 if (!terminator) |
| 7217 return this.getLastInstanceNode(index - 1); |
| 7218 |
| 7219 if (terminator.nodeType !== Node.ELEMENT_NODE || |
| 7220 this.templateElement_ === terminator) { |
| 7221 return terminator; |
| 7222 } |
| 7223 |
| 7224 var subtemplateIterator = terminator.iterator_; |
| 7225 if (!subtemplateIterator) |
| 7226 return terminator; |
| 7227 |
| 7228 return subtemplateIterator.getLastTemplateNode(); |
| 7229 }, |
| 7230 |
| 7231 getLastTemplateNode: function() { |
| 7232 return this.getLastInstanceNode(this.instances.length - 1); |
| 7233 }, |
| 7234 |
| 7235 insertInstanceAt: function(index, fragment) { |
| 7236 var previousInstanceLast = this.getLastInstanceNode(index - 1); |
| 7237 var parent = this.templateElement_.parentNode; |
| 7238 this.instances.splice(index, 0, fragment); |
| 7239 |
| 7240 parent.insertBefore(fragment, previousInstanceLast.nextSibling); |
| 7241 }, |
| 7242 |
| 7243 extractInstanceAt: function(index) { |
| 7244 var previousInstanceLast = this.getLastInstanceNode(index - 1); |
| 7245 var lastNode = this.getLastInstanceNode(index); |
| 7246 var parent = this.templateElement_.parentNode; |
| 7247 var instance = this.instances.splice(index, 1)[0]; |
| 7248 |
| 7249 while (lastNode !== previousInstanceLast) { |
| 7250 var node = previousInstanceLast.nextSibling; |
| 7251 if (node == lastNode) |
| 7252 lastNode = previousInstanceLast; |
| 7253 |
| 7254 instance.appendChild(parent.removeChild(node)); |
| 7255 } |
| 7256 |
| 7257 return instance; |
| 7258 }, |
| 7259 |
| 7260 getDelegateFn: function(fn) { |
| 7261 fn = fn && fn(this.templateElement_); |
| 7262 return typeof fn === 'function' ? fn : null; |
| 7263 }, |
| 7264 |
| 7265 handleSplices: function(splices) { |
| 7266 if (this.closed || !splices.length) |
| 7267 return; |
| 7268 |
| 7269 var template = this.templateElement_; |
| 7270 |
| 7271 if (!template.parentNode) { |
| 7272 this.close(); |
| 7273 return; |
| 7274 } |
| 7275 |
| 7276 ArrayObserver.applySplices(this.iteratedValue, this.presentValue, |
| 7277 splices); |
| 7278 |
| 7279 var delegate = template.delegate_; |
| 7280 if (this.instanceModelFn_ === undefined) { |
| 7281 this.instanceModelFn_ = |
| 7282 this.getDelegateFn(delegate && delegate.prepareInstanceModel); |
| 7283 } |
| 7284 |
| 7285 if (this.instancePositionChangedFn_ === undefined) { |
| 7286 this.instancePositionChangedFn_ = |
| 7287 this.getDelegateFn(delegate && |
| 7288 delegate.prepareInstancePositionChanged); |
| 7289 } |
| 7290 |
| 7291 // Instance Removals |
| 7292 var instanceCache = new Map; |
| 7293 var removeDelta = 0; |
| 7294 for (var i = 0; i < splices.length; i++) { |
| 7295 var splice = splices[i]; |
| 7296 var removed = splice.removed; |
| 7297 for (var j = 0; j < removed.length; j++) { |
| 7298 var model = removed[j]; |
| 7299 var instance = this.extractInstanceAt(splice.index + removeDelta); |
| 7300 if (instance !== emptyInstance) { |
| 7301 instanceCache.set(model, instance); |
| 7302 } |
| 7303 } |
| 7304 |
| 7305 removeDelta -= splice.addedCount; |
| 7306 } |
| 7307 |
| 7308 // Instance Insertions |
| 7309 for (var i = 0; i < splices.length; i++) { |
| 7310 var splice = splices[i]; |
| 7311 var addIndex = splice.index; |
| 7312 for (; addIndex < splice.index + splice.addedCount; addIndex++) { |
| 7313 var model = this.iteratedValue[addIndex]; |
| 7314 var instance = instanceCache.get(model); |
| 7315 if (instance) { |
| 7316 instanceCache.delete(model); |
| 7317 } else { |
| 7318 if (this.instanceModelFn_) { |
| 7319 model = this.instanceModelFn_(model); |
| 7320 } |
| 7321 |
| 7322 if (model === undefined) { |
| 7323 instance = emptyInstance; |
| 7324 } else { |
| 7325 instance = template.createInstance(model, undefined, delegate); |
| 7326 } |
| 7327 } |
| 7328 |
| 7329 this.insertInstanceAt(addIndex, instance); |
| 7330 } |
| 7331 } |
| 7332 |
| 7333 instanceCache.forEach(function(instance) { |
| 7334 this.closeInstanceBindings(instance); |
| 7335 }, this); |
| 7336 |
| 7337 if (this.instancePositionChangedFn_) |
| 7338 this.reportInstancesMoved(splices); |
| 7339 }, |
| 7340 |
| 7341 reportInstanceMoved: function(index) { |
| 7342 var instance = this.instances[index]; |
| 7343 if (instance === emptyInstance) |
| 7344 return; |
| 7345 |
| 7346 this.instancePositionChangedFn_(instance.templateInstance_, index); |
| 7347 }, |
| 7348 |
| 7349 reportInstancesMoved: function(splices) { |
| 7350 var index = 0; |
| 7351 var offset = 0; |
| 7352 for (var i = 0; i < splices.length; i++) { |
| 7353 var splice = splices[i]; |
| 7354 if (offset != 0) { |
| 7355 while (index < splice.index) { |
| 7356 this.reportInstanceMoved(index); |
| 7357 index++; |
| 7358 } |
| 7359 } else { |
| 7360 index = splice.index; |
| 7361 } |
| 7362 |
| 7363 while (index < splice.index + splice.addedCount) { |
| 7364 this.reportInstanceMoved(index); |
| 7365 index++; |
| 7366 } |
| 7367 |
| 7368 offset += splice.addedCount - splice.removed.length; |
| 7369 } |
| 7370 |
| 7371 if (offset == 0) |
| 7372 return; |
| 7373 |
| 7374 var length = this.instances.length; |
| 7375 while (index < length) { |
| 7376 this.reportInstanceMoved(index); |
| 7377 index++; |
| 7378 } |
| 7379 }, |
| 7380 |
| 7381 closeInstanceBindings: function(instance) { |
| 7382 var bindings = instance.bindings_; |
| 7383 for (var i = 0; i < bindings.length; i++) { |
| 7384 bindings[i].close(); |
| 7385 } |
| 7386 }, |
| 7387 |
| 7388 unobserve: function() { |
| 7389 if (!this.arrayObserver) |
| 7390 return; |
| 7391 |
| 7392 this.arrayObserver.close(); |
| 7393 this.arrayObserver = undefined; |
| 7394 }, |
| 7395 |
| 7396 close: function() { |
| 7397 if (this.closed) |
| 7398 return; |
| 7399 this.unobserve(); |
| 7400 for (var i = 0; i < this.instances.length; i++) { |
| 7401 this.closeInstanceBindings(this.instances[i]); |
| 7402 } |
| 7403 |
| 7404 this.instances.length = 0; |
| 7405 this.closeDeps(); |
| 7406 this.templateElement_.iterator_ = undefined; |
| 7407 this.closed = true; |
| 7408 } |
| 7409 }; |
| 7410 |
| 7411 // Polyfill-specific API. |
| 7412 HTMLTemplateElement.forAllTemplatesFrom_ = forAllTemplatesFrom; |
| 7413 })(this); |
| 7414 |
| 7415 /* |
| 7416 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 7417 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
| 7418 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
| 7419 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
| 7420 * Code distributed by Google as part of the polymer project is also |
| 7421 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
| 7422 */ |
| 7423 |
| 7424 (function(scope) { |
| 7425 |
| 7426 var iterations = 0; |
| 7427 var callbacks = []; |
| 7428 var twiddle = document.createTextNode(''); |
| 7429 |
| 7430 function endOfMicrotask(callback) { |
| 7431 twiddle.textContent = iterations++; |
| 7432 callbacks.push(callback); |
| 7433 } |
| 7434 |
| 7435 function atEndOfMicrotask() { |
| 7436 while (callbacks.length) { |
| 7437 callbacks.shift()(); |
| 7438 } |
| 7439 } |
| 7440 |
| 7441 new (window.MutationObserver || JsMutationObserver)(atEndOfMicrotask) |
| 7442 .observe(twiddle, {characterData: true}) |
| 7443 ; |
| 7444 |
| 7445 // exports |
| 7446 |
| 7447 scope.endOfMicrotask = endOfMicrotask; |
| 7448 |
| 7449 })(Platform); |
| 7450 |
| 7451 |
| 7452 /* |
| 7453 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 7454 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
| 7455 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
| 7456 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
| 7457 * Code distributed by Google as part of the polymer project is also |
| 7458 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
| 7459 */ |
| 7460 |
| 7461 (function(scope) { |
| 7462 |
| 7463 // inject style sheet |
| 7464 var style = document.createElement('style'); |
| 7465 style.textContent = 'template {display: none !important;} /* injected by platfor
m.js */'; |
| 7466 var head = document.querySelector('head'); |
| 7467 head.insertBefore(style, head.firstChild); |
| 7468 |
| 7469 // flush (with logging) |
| 7470 var flushing; |
| 7471 function flush() { |
| 7472 if (!flushing) { |
| 7473 flushing = true; |
| 7474 scope.endOfMicrotask(function() { |
| 7475 flushing = false; |
| 7476 logFlags.data && console.group('Platform.flush()'); |
| 7477 scope.performMicrotaskCheckpoint(); |
| 7478 logFlags.data && console.groupEnd(); |
| 7479 }); |
| 7480 } |
| 7481 }; |
| 7482 |
| 7483 // polling dirty checker |
| 7484 // flush periodically if platform does not have object observe. |
| 7485 if (!Observer.hasObjectObserve) { |
| 7486 var FLUSH_POLL_INTERVAL = 125; |
| 7487 window.addEventListener('WebComponentsReady', function() { |
| 7488 flush(); |
| 7489 scope.flushPoll = setInterval(flush, FLUSH_POLL_INTERVAL); |
| 7490 }); |
| 7491 } else { |
| 7492 // make flush a no-op when we have Object.observe |
| 7493 flush = function() {}; |
| 7494 } |
| 7495 |
| 7496 if (window.CustomElements && !CustomElements.useNative) { |
| 7497 var originalImportNode = Document.prototype.importNode; |
| 7498 Document.prototype.importNode = function(node, deep) { |
| 7499 var imported = originalImportNode.call(this, node, deep); |
| 7500 CustomElements.upgradeAll(imported); |
| 7501 return imported; |
| 7502 } |
| 7503 } |
| 7504 |
| 7505 // exports |
| 7506 scope.flush = flush; |
| 7507 |
| 7508 })(window.Platform); |
| 7509 |
| 7510 |
| 7511 /* |
| 7512 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 7513 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
| 7514 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
| 7515 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
| 7516 * Code distributed by Google as part of the polymer project is also |
| 7517 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
| 7518 */ |
| 7519 |
| 7520 (function(scope) { |
| 7521 |
| 7522 var urlResolver = { |
| 7523 resolveDom: function(root, url) { |
| 7524 url = url || root.ownerDocument.baseURI; |
| 7525 this.resolveAttributes(root, url); |
| 7526 this.resolveStyles(root, url); |
| 7527 // handle template.content |
| 7528 var templates = root.querySelectorAll('template'); |
| 7529 if (templates) { |
| 7530 for (var i = 0, l = templates.length, t; (i < l) && (t = templates[i]); i+
+) { |
| 7531 if (t.content) { |
| 7532 this.resolveDom(t.content, url); |
| 7533 } |
| 7534 } |
| 7535 } |
| 7536 }, |
| 7537 resolveTemplate: function(template) { |
| 7538 this.resolveDom(template.content, template.ownerDocument.baseURI); |
| 7539 }, |
| 7540 resolveStyles: function(root, url) { |
| 7541 var styles = root.querySelectorAll('style'); |
| 7542 if (styles) { |
| 7543 for (var i = 0, l = styles.length, s; (i < l) && (s = styles[i]); i++) { |
| 7544 this.resolveStyle(s, url); |
| 7545 } |
| 7546 } |
| 7547 }, |
| 7548 resolveStyle: function(style, url) { |
| 7549 url = url || style.ownerDocument.baseURI; |
| 7550 style.textContent = this.resolveCssText(style.textContent, url); |
| 7551 }, |
| 7552 resolveCssText: function(cssText, baseUrl, keepAbsolute) { |
| 7553 cssText = replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_URL_REGEX
P); |
| 7554 return replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_IMPORT_REGEX
P); |
| 7555 }, |
| 7556 resolveAttributes: function(root, url) { |
| 7557 if (root.hasAttributes && root.hasAttributes()) { |
| 7558 this.resolveElementAttributes(root, url); |
| 7559 } |
| 7560 // search for attributes that host urls |
| 7561 var nodes = root && root.querySelectorAll(URL_ATTRS_SELECTOR); |
| 7562 if (nodes) { |
| 7563 for (var i = 0, l = nodes.length, n; (i < l) && (n = nodes[i]); i++) { |
| 7564 this.resolveElementAttributes(n, url); |
| 7565 } |
| 7566 } |
| 7567 }, |
| 7568 resolveElementAttributes: function(node, url) { |
| 7569 url = url || node.ownerDocument.baseURI; |
| 7570 URL_ATTRS.forEach(function(v) { |
| 7571 var attr = node.attributes[v]; |
| 7572 var value = attr && attr.value; |
| 7573 var replacement; |
| 7574 if (value && value.search(URL_TEMPLATE_SEARCH) < 0) { |
| 7575 if (v === 'style') { |
| 7576 replacement = replaceUrlsInCssText(value, url, false, CSS_URL_REGEXP); |
| 7577 } else { |
| 7578 replacement = resolveRelativeUrl(url, value); |
| 7579 } |
| 7580 attr.value = replacement; |
| 7581 } |
| 7582 }); |
| 7583 } |
| 7584 }; |
| 7585 |
| 7586 var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g; |
| 7587 var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g; |
| 7588 var URL_ATTRS = ['href', 'src', 'action', 'style', 'url']; |
| 7589 var URL_ATTRS_SELECTOR = '[' + URL_ATTRS.join('],[') + ']'; |
| 7590 var URL_TEMPLATE_SEARCH = '{{.*}}'; |
| 7591 |
| 7592 function replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, regexp) { |
| 7593 return cssText.replace(regexp, function(m, pre, url, post) { |
| 7594 var urlPath = url.replace(/["']/g, ''); |
| 7595 urlPath = resolveRelativeUrl(baseUrl, urlPath, keepAbsolute); |
| 7596 return pre + '\'' + urlPath + '\'' + post; |
| 7597 }); |
| 7598 } |
| 7599 |
| 7600 function resolveRelativeUrl(baseUrl, url, keepAbsolute) { |
| 7601 // do not resolve '/' absolute urls |
| 7602 if (url && url[0] === '/') { |
| 7603 return url; |
| 7604 } |
| 7605 var u = new URL(url, baseUrl); |
| 7606 return keepAbsolute ? u.href : makeDocumentRelPath(u.href); |
| 7607 } |
| 7608 |
| 7609 function makeDocumentRelPath(url) { |
| 7610 var root = new URL(document.baseURI); |
| 7611 var u = new URL(url, root); |
| 7612 if (u.host === root.host && u.port === root.port && |
| 7613 u.protocol === root.protocol) { |
| 7614 return makeRelPath(root, u); |
| 7615 } else { |
| 7616 return url; |
| 7617 } |
| 7618 } |
| 7619 |
| 7620 // make a relative path from source to target |
| 7621 function makeRelPath(sourceUrl, targetUrl) { |
| 7622 var source = sourceUrl.pathname; |
| 7623 var target = targetUrl.pathname; |
| 7624 var s = source.split('/'); |
| 7625 var t = target.split('/'); |
| 7626 while (s.length && s[0] === t[0]){ |
| 7627 s.shift(); |
| 7628 t.shift(); |
| 7629 } |
| 7630 for (var i = 0, l = s.length - 1; i < l; i++) { |
| 7631 t.unshift('..'); |
| 7632 } |
| 7633 return t.join('/') + targetUrl.search + targetUrl.hash; |
| 7634 } |
| 7635 |
| 7636 // exports |
| 7637 scope.urlResolver = urlResolver; |
| 7638 |
| 7639 })(Polymer); |
| 7640 |
| 7641 /* |
| 7642 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 7643 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
| 7644 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
| 7645 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
| 7646 * Code distributed by Google as part of the polymer project is also |
| 7647 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
| 7648 */ |
| 7649 |
| 7650 (function(scope) { |
| 7651 var endOfMicrotask = Platform.endOfMicrotask; |
| 7652 |
| 7653 // Generic url loader |
| 7654 function Loader(regex) { |
| 7655 this.cache = Object.create(null); |
| 7656 this.map = Object.create(null); |
| 7657 this.requests = 0; |
| 7658 this.regex = regex; |
| 7659 } |
| 7660 Loader.prototype = { |
| 7661 |
| 7662 // TODO(dfreedm): there may be a better factoring here |
| 7663 // extract absolute urls from the text (full of relative urls) |
| 7664 extractUrls: function(text, base) { |
| 7665 var matches = []; |
| 7666 var matched, u; |
| 7667 while ((matched = this.regex.exec(text))) { |
| 7668 u = new URL(matched[1], base); |
| 7669 matches.push({matched: matched[0], url: u.href}); |
| 7670 } |
| 7671 return matches; |
| 7672 }, |
| 7673 // take a text blob, a root url, and a callback and load all the urls found
within the text |
| 7674 // returns a map of absolute url to text |
| 7675 process: function(text, root, callback) { |
| 7676 var matches = this.extractUrls(text, root); |
| 7677 |
| 7678 // every call to process returns all the text this loader has ever receive
d |
| 7679 var done = callback.bind(null, this.map); |
| 7680 this.fetch(matches, done); |
| 7681 }, |
| 7682 // build a mapping of url -> text from matches |
| 7683 fetch: function(matches, callback) { |
| 7684 var inflight = matches.length; |
| 7685 |
| 7686 // return early if there is no fetching to be done |
| 7687 if (!inflight) { |
| 7688 return callback(); |
| 7689 } |
| 7690 |
| 7691 // wait for all subrequests to return |
| 7692 var done = function() { |
| 7693 if (--inflight === 0) { |
| 7694 callback(); |
| 7695 } |
| 7696 }; |
| 7697 |
| 7698 // start fetching all subrequests |
| 7699 var m, req, url; |
| 7700 for (var i = 0; i < inflight; i++) { |
| 7701 m = matches[i]; |
| 7702 url = m.url; |
| 7703 req = this.cache[url]; |
| 7704 // if this url has already been requested, skip requesting it again |
| 7705 if (!req) { |
| 7706 req = this.xhr(url); |
| 7707 req.match = m; |
| 7708 this.cache[url] = req; |
| 7709 } |
| 7710 // wait for the request to process its subrequests |
| 7711 req.wait(done); |
| 7712 } |
| 7713 }, |
| 7714 handleXhr: function(request) { |
| 7715 var match = request.match; |
| 7716 var url = match.url; |
| 7717 |
| 7718 // handle errors with an empty string |
| 7719 var response = request.response || request.responseText || ''; |
| 7720 this.map[url] = response; |
| 7721 this.fetch(this.extractUrls(response, url), request.resolve); |
| 7722 }, |
| 7723 xhr: function(url) { |
| 7724 this.requests++; |
| 7725 var request = new XMLHttpRequest(); |
| 7726 request.open('GET', url, true); |
| 7727 request.send(); |
| 7728 request.onerror = request.onload = this.handleXhr.bind(this, request); |
| 7729 |
| 7730 // queue of tasks to run after XHR returns |
| 7731 request.pending = []; |
| 7732 request.resolve = function() { |
| 7733 var pending = request.pending; |
| 7734 for(var i = 0; i < pending.length; i++) { |
| 7735 pending[i](); |
| 7736 } |
| 7737 request.pending = null; |
| 7738 }; |
| 7739 |
| 7740 // if we have already resolved, pending is null, async call the callback |
| 7741 request.wait = function(fn) { |
| 7742 if (request.pending) { |
| 7743 request.pending.push(fn); |
| 7744 } else { |
| 7745 endOfMicrotask(fn); |
| 7746 } |
| 7747 }; |
| 7748 |
| 7749 return request; |
| 7750 } |
| 7751 }; |
| 7752 |
| 7753 scope.Loader = Loader; |
| 7754 })(Polymer); |
| 7755 |
| 7756 /* |
| 7757 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 7758 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
| 7759 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
| 7760 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
| 7761 * Code distributed by Google as part of the polymer project is also |
| 7762 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
| 7763 */ |
| 7764 |
| 7765 (function(scope) { |
| 7766 |
| 7767 var urlResolver = scope.urlResolver; |
| 7768 var Loader = scope.Loader; |
| 7769 |
| 7770 function StyleResolver() { |
| 7771 this.loader = new Loader(this.regex); |
| 7772 } |
| 7773 StyleResolver.prototype = { |
| 7774 regex: /@import\s+(?:url)?["'\(]*([^'"\)]*)['"\)]*;/g, |
| 7775 // Recursively replace @imports with the text at that url |
| 7776 resolve: function(text, url, callback) { |
| 7777 var done = function(map) { |
| 7778 callback(this.flatten(text, url, map)); |
| 7779 }.bind(this); |
| 7780 this.loader.process(text, url, done); |
| 7781 }, |
| 7782 // resolve the textContent of a style node |
| 7783 resolveNode: function(style, url, callback) { |
| 7784 var text = style.textContent; |
| 7785 var done = function(text) { |
| 7786 style.textContent = text; |
| 7787 callback(style); |
| 7788 }; |
| 7789 this.resolve(text, url, done); |
| 7790 }, |
| 7791 // flatten all the @imports to text |
| 7792 flatten: function(text, base, map) { |
| 7793 var matches = this.loader.extractUrls(text, base); |
| 7794 var match, url, intermediate; |
| 7795 for (var i = 0; i < matches.length; i++) { |
| 7796 match = matches[i]; |
| 7797 url = match.url; |
| 7798 // resolve any css text to be relative to the importer, keep absolute url |
| 7799 intermediate = urlResolver.resolveCssText(map[url], url, true); |
| 7800 // flatten intermediate @imports |
| 7801 intermediate = this.flatten(intermediate, base, map); |
| 7802 text = text.replace(match.matched, intermediate); |
| 7803 } |
| 7804 return text; |
| 7805 }, |
| 7806 loadStyles: function(styles, base, callback) { |
| 7807 var loaded=0, l = styles.length; |
| 7808 // called in the context of the style |
| 7809 function loadedStyle(style) { |
| 7810 loaded++; |
| 7811 if (loaded === l && callback) { |
| 7812 callback(); |
| 7813 } |
| 7814 } |
| 7815 for (var i=0, s; (i<l) && (s=styles[i]); i++) { |
| 7816 this.resolveNode(s, base, loadedStyle); |
| 7817 } |
| 7818 } |
| 7819 }; |
| 7820 |
| 7821 var styleResolver = new StyleResolver(); |
| 7822 |
| 7823 // exports |
| 7824 scope.styleResolver = styleResolver; |
| 7825 |
| 7826 })(Polymer); |
| 7827 |
| 7828 /* |
| 7829 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 7830 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
| 7831 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
| 7832 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
| 7833 * Code distributed by Google as part of the polymer project is also |
| 7834 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
| 7835 */ |
| 7836 |
| 7837 (function(scope) { |
| 7838 |
| 7839 // copy own properties from 'api' to 'prototype, with name hinting for 'super' |
| 7840 function extend(prototype, api) { |
| 7841 if (prototype && api) { |
| 7842 // use only own properties of 'api' |
| 7843 Object.getOwnPropertyNames(api).forEach(function(n) { |
| 7844 // acquire property descriptor |
| 7845 var pd = Object.getOwnPropertyDescriptor(api, n); |
| 7846 if (pd) { |
| 7847 // clone property via descriptor |
| 7848 Object.defineProperty(prototype, n, pd); |
| 7849 // cache name-of-method for 'super' engine |
| 7850 if (typeof pd.value == 'function') { |
| 7851 // hint the 'super' engine |
| 7852 pd.value.nom = n; |
| 7853 } |
| 7854 } |
| 7855 }); |
| 7856 } |
| 7857 return prototype; |
| 7858 } |
| 7859 |
| 7860 |
| 7861 // mixin |
| 7862 |
| 7863 // copy all properties from inProps (et al) to inObj |
| 7864 function mixin(inObj/*, inProps, inMoreProps, ...*/) { |
| 7865 var obj = inObj || {}; |
| 7866 for (var i = 1; i < arguments.length; i++) { |
| 7867 var p = arguments[i]; |
| 7868 try { |
| 7869 for (var n in p) { |
| 7870 copyProperty(n, p, obj); |
| 7871 } |
| 7872 } catch(x) { |
| 7873 } |
| 7874 } |
| 7875 return obj; |
| 7876 } |
| 7877 |
| 7878 // copy property inName from inSource object to inTarget object |
| 7879 function copyProperty(inName, inSource, inTarget) { |
| 7880 var pd = getPropertyDescriptor(inSource, inName); |
| 7881 Object.defineProperty(inTarget, inName, pd); |
| 7882 } |
| 7883 |
| 7884 // get property descriptor for inName on inObject, even if |
| 7885 // inName exists on some link in inObject's prototype chain |
| 7886 function getPropertyDescriptor(inObject, inName) { |
| 7887 if (inObject) { |
| 7888 var pd = Object.getOwnPropertyDescriptor(inObject, inName); |
| 7889 return pd || getPropertyDescriptor(Object.getPrototypeOf(inObject), inName
); |
| 7890 } |
| 7891 } |
| 7892 |
| 7893 // exports |
| 7894 |
| 7895 scope.extend = extend; |
| 7896 scope.mixin = mixin; |
| 7897 |
| 7898 // for bc |
| 7899 Platform.mixin = mixin; |
| 7900 |
| 7901 })(Polymer); |
| 7902 |
| 7903 /* |
| 7904 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 7905 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
| 7906 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
| 7907 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
| 7908 * Code distributed by Google as part of the polymer project is also |
| 7909 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
| 7910 */ |
| 7911 |
| 7912 (function(scope) { |
| 7913 |
| 7914 // usage |
| 7915 |
| 7916 // invoke cb.call(this) in 100ms, unless the job is re-registered, |
| 7917 // which resets the timer |
| 7918 // |
| 7919 // this.myJob = this.job(this.myJob, cb, 100) |
| 7920 // |
| 7921 // returns a job handle which can be used to re-register a job |
| 7922 |
| 7923 var Job = function(inContext) { |
| 7924 this.context = inContext; |
| 7925 this.boundComplete = this.complete.bind(this) |
| 7926 }; |
| 7927 Job.prototype = { |
| 7928 go: function(callback, wait) { |
| 7929 this.callback = callback; |
| 7930 var h; |
| 7931 if (!wait) { |
| 7932 h = requestAnimationFrame(this.boundComplete); |
| 7933 this.handle = function() { |
| 7934 cancelAnimationFrame(h); |
| 7935 } |
| 7936 } else { |
| 7937 h = setTimeout(this.boundComplete, wait); |
| 7938 this.handle = function() { |
| 7939 clearTimeout(h); |
| 7940 } |
| 7941 } |
| 7942 }, |
| 7943 stop: function() { |
| 7944 if (this.handle) { |
| 7945 this.handle(); |
| 7946 this.handle = null; |
| 7947 } |
| 7948 }, |
| 7949 complete: function() { |
| 7950 if (this.handle) { |
| 7951 this.stop(); |
| 7952 this.callback.call(this.context); |
| 7953 } |
| 7954 } |
| 7955 }; |
| 7956 |
| 7957 function job(job, callback, wait) { |
| 7958 if (job) { |
| 7959 job.stop(); |
| 7960 } else { |
| 7961 job = new Job(this); |
| 7962 } |
| 7963 job.go(callback, wait); |
| 7964 return job; |
| 7965 } |
| 7966 |
| 7967 // exports |
| 7968 |
| 7969 scope.job = job; |
| 7970 |
| 7971 })(Polymer); |
| 7972 |
| 7973 /* |
| 7974 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 7975 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
| 7976 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
| 7977 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
| 7978 * Code distributed by Google as part of the polymer project is also |
| 7979 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
| 7980 */ |
| 7981 |
| 7982 (function(scope) { |
| 7983 |
| 7984 var registry = {}; |
| 7985 |
| 7986 HTMLElement.register = function(tag, prototype) { |
| 7987 registry[tag] = prototype; |
| 7988 } |
| 7989 |
| 7990 // get prototype mapped to node <tag> |
| 7991 HTMLElement.getPrototypeForTag = function(tag) { |
| 7992 var prototype = !tag ? HTMLElement.prototype : registry[tag]; |
| 7993 // TODO(sjmiles): creating <tag> is likely to have wasteful side-effects |
| 7994 return prototype || Object.getPrototypeOf(document.createElement(tag)); |
| 7995 }; |
| 7996 |
| 7997 // we have to flag propagation stoppage for the event dispatcher |
| 7998 var originalStopPropagation = Event.prototype.stopPropagation; |
| 7999 Event.prototype.stopPropagation = function() { |
| 8000 this.cancelBubble = true; |
| 8001 originalStopPropagation.apply(this, arguments); |
| 8002 }; |
| 8003 |
| 8004 |
| 8005 // polyfill DOMTokenList |
| 8006 // * add/remove: allow these methods to take multiple classNames |
| 8007 // * toggle: add a 2nd argument which forces the given state rather |
| 8008 // than toggling. |
| 8009 |
| 8010 var add = DOMTokenList.prototype.add; |
| 8011 var remove = DOMTokenList.prototype.remove; |
| 8012 DOMTokenList.prototype.add = function() { |
| 8013 for (var i = 0; i < arguments.length; i++) { |
| 8014 add.call(this, arguments[i]); |
| 8015 } |
| 8016 }; |
| 8017 DOMTokenList.prototype.remove = function() { |
| 8018 for (var i = 0; i < arguments.length; i++) { |
| 8019 remove.call(this, arguments[i]); |
| 8020 } |
| 8021 }; |
| 8022 DOMTokenList.prototype.toggle = function(name, bool) { |
| 8023 if (arguments.length == 1) { |
| 8024 bool = !this.contains(name); |
| 8025 } |
| 8026 bool ? this.add(name) : this.remove(name); |
| 8027 }; |
| 8028 DOMTokenList.prototype.switch = function(oldName, newName) { |
| 8029 oldName && this.remove(oldName); |
| 8030 newName && this.add(newName); |
| 8031 }; |
| 8032 |
| 8033 // add array() to NodeList, NamedNodeMap, HTMLCollection |
| 8034 |
| 8035 var ArraySlice = function() { |
| 8036 return Array.prototype.slice.call(this); |
| 8037 }; |
| 8038 |
| 8039 var namedNodeMap = (window.NamedNodeMap || window.MozNamedAttrMap || {}); |
| 8040 |
| 8041 NodeList.prototype.array = ArraySlice; |
| 8042 namedNodeMap.prototype.array = ArraySlice; |
| 8043 HTMLCollection.prototype.array = ArraySlice; |
| 8044 |
| 8045 // utility |
| 8046 |
| 8047 function createDOM(inTagOrNode, inHTML, inAttrs) { |
| 8048 var dom = typeof inTagOrNode == 'string' ? |
| 8049 document.createElement(inTagOrNode) : inTagOrNode.cloneNode(true); |
| 8050 dom.innerHTML = inHTML; |
| 8051 if (inAttrs) { |
| 8052 for (var n in inAttrs) { |
| 8053 dom.setAttribute(n, inAttrs[n]); |
| 8054 } |
| 8055 } |
| 8056 return dom; |
| 8057 } |
| 8058 |
| 8059 // exports |
| 8060 |
| 8061 scope.createDOM = createDOM; |
| 8062 |
| 8063 })(Polymer); |
| 8064 |
| 8065 /* |
| 8066 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 8067 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
| 8068 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
| 8069 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
| 8070 * Code distributed by Google as part of the polymer project is also |
| 8071 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
| 8072 */ |
| 8073 |
3847 (function(scope) { | 8074 (function(scope) { |
3848 // super | 8075 // super |
3849 | 8076 |
3850 // `arrayOfArgs` is an optional array of args like one might pass | 8077 // `arrayOfArgs` is an optional array of args like one might pass |
3851 // to `Function.apply` | 8078 // to `Function.apply` |
3852 | 8079 |
3853 // TODO(sjmiles): | 8080 // TODO(sjmiles): |
3854 // $super must be installed on an instance or prototype chain | 8081 // $super must be installed on an instance or prototype chain |
3855 // as `super`, and invoked via `this`, e.g. | 8082 // as `super`, and invoked via `this`, e.g. |
3856 // `this.super();` | 8083 // `this.super();` |
(...skipping 613 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4470 if (callbackName) { | 8697 if (callbackName) { |
4471 // if we are observing the previous value, stop | 8698 // if we are observing the previous value, stop |
4472 if (Array.isArray(old)) { | 8699 if (Array.isArray(old)) { |
4473 log.observe && console.log('[%s] observeArrayValue: unregister observe
r [%s]', this.localName, name); | 8700 log.observe && console.log('[%s] observeArrayValue: unregister observe
r [%s]', this.localName, name); |
4474 this.closeNamedObserver(name + '__array'); | 8701 this.closeNamedObserver(name + '__array'); |
4475 } | 8702 } |
4476 // if the new value is an array, being observing it | 8703 // if the new value is an array, being observing it |
4477 if (Array.isArray(value)) { | 8704 if (Array.isArray(value)) { |
4478 log.observe && console.log('[%s] observeArrayValue: register observer
[%s]', this.localName, name, value); | 8705 log.observe && console.log('[%s] observeArrayValue: register observer
[%s]', this.localName, name, value); |
4479 var observer = new ArrayObserver(value); | 8706 var observer = new ArrayObserver(value); |
4480 observer.open(function(value, old) { | 8707 observer.open(function(splices) { |
4481 this.invokeMethod(callbackName, [old]); | 8708 this.invokeMethod(callbackName, [splices]); |
4482 }, this); | 8709 }, this); |
4483 this.registerNamedObserver(name + '__array', observer); | 8710 this.registerNamedObserver(name + '__array', observer); |
4484 } | 8711 } |
4485 } | 8712 } |
4486 }, | 8713 }, |
4487 emitPropertyChangeRecord: function(name, value, oldValue) { | 8714 emitPropertyChangeRecord: function(name, value, oldValue) { |
4488 var object = this; | 8715 var object = this; |
4489 if (areSameValue(value, oldValue)) | 8716 if (areSameValue(value, oldValue)) |
4490 return; | 8717 return; |
4491 | 8718 |
(...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4637 | 8864 |
4638 (function(scope) { | 8865 (function(scope) { |
4639 | 8866 |
4640 // imports | 8867 // imports |
4641 | 8868 |
4642 var log = window.logFlags || 0; | 8869 var log = window.logFlags || 0; |
4643 | 8870 |
4644 // element api supporting mdv | 8871 // element api supporting mdv |
4645 var mdv = { | 8872 var mdv = { |
4646 instanceTemplate: function(template) { | 8873 instanceTemplate: function(template) { |
| 8874 // ensure template is decorated (lets' things like <tr template ...> work) |
| 8875 HTMLTemplateElement.decorate(template); |
4647 // ensure a default bindingDelegate | 8876 // ensure a default bindingDelegate |
4648 var syntax = this.syntax || (!template.bindingDelegate && | 8877 var syntax = this.syntax || (!template.bindingDelegate && |
4649 this.element.syntax); | 8878 this.element.syntax); |
4650 var dom = template.createInstance(this, syntax); | 8879 var dom = template.createInstance(this, syntax); |
4651 var observers = dom.bindings_; | 8880 var observers = dom.bindings_; |
4652 for (var i = 0; i < observers.length; i++) { | 8881 for (var i = 0; i < observers.length; i++) { |
4653 this.registerObserver(observers[i]); | 8882 this.registerObserver(observers[i]); |
4654 } | 8883 } |
4655 return dom; | 8884 return dom; |
4656 }, | 8885 }, |
(...skipping 452 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5109 | 9338 |
5110 // imports | 9339 // imports |
5111 | 9340 |
5112 var extend = scope.extend; | 9341 var extend = scope.extend; |
5113 var api = scope.api; | 9342 var api = scope.api; |
5114 | 9343 |
5115 // imperative implementation: Polymer() | 9344 // imperative implementation: Polymer() |
5116 | 9345 |
5117 // specify an 'own' prototype for tag `name` | 9346 // specify an 'own' prototype for tag `name` |
5118 function element(name, prototype) { | 9347 function element(name, prototype) { |
5119 if (arguments.length === 1 && typeof arguments[0] !== 'string') { | 9348 if (typeof name !== 'string') { |
| 9349 var script = prototype || document._currentScript; |
5120 prototype = name; | 9350 prototype = name; |
5121 var script = document._currentScript; | |
5122 name = script && script.parentNode && script.parentNode.getAttribute ? | 9351 name = script && script.parentNode && script.parentNode.getAttribute ? |
5123 script.parentNode.getAttribute('name') : ''; | 9352 script.parentNode.getAttribute('name') : ''; |
5124 if (!name) { | 9353 if (!name) { |
5125 throw 'Element name could not be inferred.'; | 9354 throw 'Element name could not be inferred.'; |
5126 } | 9355 } |
5127 } | 9356 } |
5128 if (getRegisteredPrototype[name]) { | 9357 if (getRegisteredPrototype[name]) { |
5129 throw 'Already registered (Polymer) prototype for element ' + name; | 9358 throw 'Already registered (Polymer) prototype for element ' + name; |
5130 } | 9359 } |
5131 // cache the prototype | 9360 // cache the prototype |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5184 // TODO(sjmiles): find a way to do this that is less terrible | 9413 // TODO(sjmiles): find a way to do this that is less terrible |
5185 // copy window.Polymer properties onto `element()` | 9414 // copy window.Polymer properties onto `element()` |
5186 | 9415 |
5187 extend(Polymer, scope); | 9416 extend(Polymer, scope); |
5188 | 9417 |
5189 // Under the HTMLImports polyfill, scripts in the main document | 9418 // Under the HTMLImports polyfill, scripts in the main document |
5190 // do not block on imports; we want to allow calls to Polymer in the main | 9419 // do not block on imports; we want to allow calls to Polymer in the main |
5191 // document. Platform collects those calls until we can process them, which | 9420 // document. Platform collects those calls until we can process them, which |
5192 // we do here. | 9421 // we do here. |
5193 | 9422 |
5194 var declarations = Platform.deliverDeclarations(); | 9423 if (Platform.consumeDeclarations) { |
5195 if (declarations) { | 9424 Platform.consumeDeclarations(function(declarations) {; |
5196 for (var i=0, l=declarations.length, d; (i<l) && (d=declarations[i]); i++) { | 9425 if (declarations) { |
5197 element.apply(null, d); | 9426 for (var i=0, l=declarations.length, d; (i<l) && (d=declarations[i]); i+
+) { |
5198 } | 9427 element.apply(null, d); |
| 9428 } |
| 9429 } |
| 9430 }); |
5199 } | 9431 } |
5200 | 9432 |
5201 })(Polymer); | 9433 })(Polymer); |
5202 | 9434 |
5203 /* | 9435 /* |
5204 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | 9436 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
5205 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | 9437 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
5206 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | 9438 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
5207 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | 9439 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
5208 * Code distributed by Google as part of the polymer project is also | 9440 * Code distributed by Google as part of the polymer project is also |
5209 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | 9441 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
5210 */ | 9442 */ |
5211 | 9443 |
5212 (function(scope) { | 9444 (function(scope) { |
5213 | 9445 |
5214 var path = { | 9446 var path = { |
5215 resolveElementPaths: function(node) { | 9447 resolveElementPaths: function(node) { |
5216 Platform.urlResolver.resolveDom(node); | 9448 Polymer.urlResolver.resolveDom(node); |
5217 }, | 9449 }, |
5218 addResolvePathApi: function() { | 9450 addResolvePathApi: function() { |
5219 // let assetpath attribute modify the resolve path | 9451 // let assetpath attribute modify the resolve path |
5220 var assetPath = this.getAttribute('assetpath') || ''; | 9452 var assetPath = this.getAttribute('assetpath') || ''; |
5221 var root = new URL(assetPath, this.ownerDocument.baseURI); | 9453 var root = new URL(assetPath, this.ownerDocument.baseURI); |
5222 this.prototype.resolvePath = function(urlPath, base) { | 9454 this.prototype.resolvePath = function(urlPath, base) { |
5223 var u = new URL(urlPath, base || root); | 9455 var u = new URL(urlPath, base || root); |
5224 return u.href; | 9456 return u.href; |
5225 }; | 9457 }; |
5226 } | 9458 } |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5261 var styles = { | 9493 var styles = { |
5262 // returns true if resources are loading | 9494 // returns true if resources are loading |
5263 loadStyles: function(callback) { | 9495 loadStyles: function(callback) { |
5264 var template = this.fetchTemplate(); | 9496 var template = this.fetchTemplate(); |
5265 var content = template && this.templateContent(); | 9497 var content = template && this.templateContent(); |
5266 if (content) { | 9498 if (content) { |
5267 this.convertSheetsToStyles(content); | 9499 this.convertSheetsToStyles(content); |
5268 var styles = this.findLoadableStyles(content); | 9500 var styles = this.findLoadableStyles(content); |
5269 if (styles.length) { | 9501 if (styles.length) { |
5270 var templateUrl = template.ownerDocument.baseURI; | 9502 var templateUrl = template.ownerDocument.baseURI; |
5271 return Platform.styleResolver.loadStyles(styles, templateUrl, callback
); | 9503 return Polymer.styleResolver.loadStyles(styles, templateUrl, callback)
; |
5272 } | 9504 } |
5273 } | 9505 } |
5274 if (callback) { | 9506 if (callback) { |
5275 callback(); | 9507 callback(); |
5276 } | 9508 } |
5277 }, | 9509 }, |
5278 convertSheetsToStyles: function(root) { | 9510 convertSheetsToStyles: function(root) { |
5279 var s$ = root.querySelectorAll(SHEET_SELECTOR); | 9511 var s$ = root.querySelectorAll(SHEET_SELECTOR); |
5280 for (var i=0, l=s$.length, s, c; (i<l) && (s=s$[i]); i++) { | 9512 for (var i=0, l=s$.length, s, c; (i<l) && (s=s$[i]); i++) { |
5281 c = createStyleElement(importRuleForSheet(s, this.ownerDocument.baseURI)
, | 9513 c = createStyleElement(importRuleForSheet(s, this.ownerDocument.baseURI)
, |
(...skipping 612 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5894 }; | 10126 }; |
5895 | 10127 |
5896 // declaration api supporting mdv | 10128 // declaration api supporting mdv |
5897 var mdv = { | 10129 var mdv = { |
5898 syntax: syntax, | 10130 syntax: syntax, |
5899 fetchTemplate: function() { | 10131 fetchTemplate: function() { |
5900 return this.querySelector('template'); | 10132 return this.querySelector('template'); |
5901 }, | 10133 }, |
5902 templateContent: function() { | 10134 templateContent: function() { |
5903 var template = this.fetchTemplate(); | 10135 var template = this.fetchTemplate(); |
5904 return template && Platform.templateContent(template); | 10136 return template && template.content; |
5905 }, | 10137 }, |
5906 installBindingDelegate: function(template) { | 10138 installBindingDelegate: function(template) { |
5907 if (template) { | 10139 if (template) { |
5908 template.bindingDelegate = this.syntax; | 10140 template.bindingDelegate = this.syntax; |
5909 } | 10141 } |
5910 } | 10142 } |
5911 }; | 10143 }; |
5912 | 10144 |
5913 // exports | 10145 // exports |
5914 scope.api.declaration.mdv = mdv; | 10146 scope.api.declaration.mdv = mdv; |
(...skipping 379 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
6294 var element; | 10526 var element; |
6295 while (flushQueue.length) { | 10527 while (flushQueue.length) { |
6296 element = flushQueue.shift(); | 10528 element = flushQueue.shift(); |
6297 element.__queue.go.call(element); | 10529 element.__queue.go.call(element); |
6298 element.__queue = null; | 10530 element.__queue = null; |
6299 } | 10531 } |
6300 this.flushing = false; | 10532 this.flushing = false; |
6301 }, | 10533 }, |
6302 | 10534 |
6303 ready: function() { | 10535 ready: function() { |
6304 this.flush(); | |
6305 // TODO(sorvell): As an optimization, turn off CE polyfill upgrading | 10536 // TODO(sorvell): As an optimization, turn off CE polyfill upgrading |
6306 // while registering. This way we avoid having to upgrade each document | 10537 // while registering. This way we avoid having to upgrade each document |
6307 // piecemeal per registration and can instead register all elements | 10538 // piecemeal per registration and can instead register all elements |
6308 // and upgrade once in a batch. Without this optimization, upgrade time | 10539 // and upgrade once in a batch. Without this optimization, upgrade time |
6309 // degrades significantly when SD polyfill is used. This is mainly because | 10540 // degrades significantly when SD polyfill is used. This is mainly because |
6310 // querying the document tree for elements is slow under the SD polyfill. | 10541 // querying the document tree for elements is slow under the SD polyfill. |
6311 if (CustomElements.ready === false) { | 10542 var polyfillWasReady = CustomElements.ready; |
| 10543 CustomElements.ready = false; |
| 10544 this.flush(); |
| 10545 if (!CustomElements.useNative) { |
6312 CustomElements.upgradeDocumentTree(document); | 10546 CustomElements.upgradeDocumentTree(document); |
6313 CustomElements.ready = true; | |
6314 } | 10547 } |
| 10548 CustomElements.ready = polyfillWasReady; |
6315 Platform.flush(); | 10549 Platform.flush(); |
6316 requestAnimationFrame(this.flushReadyCallbacks); | 10550 requestAnimationFrame(this.flushReadyCallbacks); |
6317 }, | 10551 }, |
6318 | 10552 |
6319 addReadyCallback: function(callback) { | 10553 addReadyCallback: function(callback) { |
6320 if (callback) { | 10554 if (callback) { |
6321 readyCallbacks.push(callback); | 10555 readyCallbacks.push(callback); |
6322 } | 10556 } |
6323 }, | 10557 }, |
6324 | 10558 |
(...skipping 18 matching lines...) Expand all Loading... |
6343 var readyCallbacks = []; | 10577 var readyCallbacks = []; |
6344 | 10578 |
6345 function queueForElement(element) { | 10579 function queueForElement(element) { |
6346 return document.contains(element) ? mainQueue : importQueue; | 10580 return document.contains(element) ? mainQueue : importQueue; |
6347 } | 10581 } |
6348 | 10582 |
6349 function nextQueued() { | 10583 function nextQueued() { |
6350 return importQueue.length ? importQueue[0] : mainQueue[0]; | 10584 return importQueue.length ? importQueue[0] : mainQueue[0]; |
6351 } | 10585 } |
6352 | 10586 |
6353 var polymerReadied = false; | 10587 function whenReady(callback) { |
6354 | |
6355 document.addEventListener('WebComponentsReady', function() { | |
6356 CustomElements.ready = false; | |
6357 }); | |
6358 | |
6359 function whenPolymerReady(callback) { | |
6360 queue.waitToReady = true; | 10588 queue.waitToReady = true; |
6361 CustomElements.ready = false; | |
6362 HTMLImports.whenImportsReady(function() { | 10589 HTMLImports.whenImportsReady(function() { |
6363 queue.addReadyCallback(callback); | 10590 queue.addReadyCallback(callback); |
6364 queue.waitToReady = false; | 10591 queue.waitToReady = false; |
6365 queue.check(); | 10592 queue.check(); |
6366 }); | 10593 }); |
6367 } | 10594 } |
6368 | 10595 |
6369 // exports | 10596 // exports |
6370 scope.elements = elements; | 10597 scope.elements = elements; |
6371 scope.queue = queue; | 10598 scope.queue = queue; |
6372 scope.whenReady = scope.whenPolymerReady = whenPolymerReady; | 10599 scope.whenReady = scope.whenPolymerReady = whenReady; |
6373 })(Polymer); | 10600 })(Polymer); |
6374 | 10601 |
6375 /* | 10602 /* |
6376 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
6377 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | |
6378 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | |
6379 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | |
6380 * Code distributed by Google as part of the polymer project is also | |
6381 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | |
6382 */ | |
6383 | |
6384 (function(scope) { | |
6385 | |
6386 var whenPolymerReady = scope.whenPolymerReady; | |
6387 | |
6388 function importElements(elementOrFragment, callback) { | |
6389 if (elementOrFragment) { | |
6390 document.head.appendChild(elementOrFragment); | |
6391 whenPolymerReady(callback); | |
6392 } else if (callback) { | |
6393 callback(); | |
6394 } | |
6395 } | |
6396 | |
6397 function importUrls(urls, callback) { | |
6398 if (urls && urls.length) { | |
6399 var frag = document.createDocumentFragment(); | |
6400 for (var i=0, l=urls.length, url, link; (i<l) && (url=urls[i]); i++) { | |
6401 link = document.createElement('link'); | |
6402 link.rel = 'import'; | |
6403 link.href = url; | |
6404 frag.appendChild(link); | |
6405 } | |
6406 importElements(frag, callback); | |
6407 } else if (callback) { | |
6408 callback(); | |
6409 } | |
6410 } | |
6411 | |
6412 // exports | |
6413 scope.import = importUrls; | |
6414 scope.importElements = importElements; | |
6415 | |
6416 })(Polymer); | |
6417 | |
6418 /* | |
6419 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | 10603 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
6420 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | 10604 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
6421 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | 10605 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
6422 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | 10606 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
6423 * Code distributed by Google as part of the polymer project is also | 10607 * Code distributed by Google as part of the polymer project is also |
6424 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | 10608 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
6425 */ | 10609 */ |
6426 | 10610 |
6427 (function(scope) { | 10611 (function(scope) { |
6428 | 10612 |
6429 // imports | 10613 // imports |
6430 | 10614 |
6431 var extend = scope.extend; | 10615 var extend = scope.extend; |
6432 var api = scope.api; | 10616 var api = scope.api; |
6433 var queue = scope.queue; | 10617 var queue = scope.queue; |
6434 var whenPolymerReady = scope.whenPolymerReady; | 10618 var whenReady = scope.whenReady; |
6435 var getRegisteredPrototype = scope.getRegisteredPrototype; | 10619 var getRegisteredPrototype = scope.getRegisteredPrototype; |
6436 var waitingForPrototype = scope.waitingForPrototype; | 10620 var waitingForPrototype = scope.waitingForPrototype; |
6437 | 10621 |
6438 // declarative implementation: <polymer-element> | 10622 // declarative implementation: <polymer-element> |
6439 | 10623 |
6440 var prototype = extend(Object.create(HTMLElement.prototype), { | 10624 var prototype = extend(Object.create(HTMLElement.prototype), { |
6441 | 10625 |
6442 createdCallback: function() { | 10626 createdCallback: function() { |
6443 if (this.getAttribute('name')) { | 10627 if (this.getAttribute('name')) { |
6444 this.init(); | 10628 this.init(); |
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
6534 function isRegistered(name) { | 10718 function isRegistered(name) { |
6535 return Boolean(HTMLElement.getPrototypeForTag(name)); | 10719 return Boolean(HTMLElement.getPrototypeForTag(name)); |
6536 } | 10720 } |
6537 | 10721 |
6538 function isCustomTag(name) { | 10722 function isCustomTag(name) { |
6539 return (name && name.indexOf('-') >= 0); | 10723 return (name && name.indexOf('-') >= 0); |
6540 } | 10724 } |
6541 | 10725 |
6542 // boot tasks | 10726 // boot tasks |
6543 | 10727 |
6544 whenPolymerReady(function() { | 10728 whenReady(function() { |
6545 document.body.removeAttribute('unresolved'); | 10729 document.body.removeAttribute('unresolved'); |
6546 document.dispatchEvent( | 10730 document.dispatchEvent( |
6547 new CustomEvent('polymer-ready', {bubbles: true}) | 10731 new CustomEvent('polymer-ready', {bubbles: true}) |
6548 ); | 10732 ); |
6549 }); | 10733 }); |
6550 | 10734 |
6551 // register polymer-element with document | 10735 // register polymer-element with document |
6552 | 10736 |
6553 document.registerElement('polymer-element', {prototype: prototype}); | 10737 document.registerElement('polymer-element', {prototype: prototype}); |
6554 | 10738 |
6555 })(Polymer); | 10739 })(Polymer); |
6556 | 10740 |
6557 /* | 10741 /* |
6558 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | 10742 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
6559 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | 10743 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
6560 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | 10744 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
6561 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | 10745 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
6562 * Code distributed by Google as part of the polymer project is also | 10746 * Code distributed by Google as part of the polymer project is also |
6563 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | 10747 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
6564 */ | 10748 */ |
6565 | 10749 |
| 10750 (function(scope) { |
| 10751 |
| 10752 var whenPolymerReady = scope.whenPolymerReady; |
| 10753 |
| 10754 function importElements(elementOrFragment, callback) { |
| 10755 if (elementOrFragment) { |
| 10756 document.head.appendChild(elementOrFragment); |
| 10757 whenPolymerReady(callback); |
| 10758 } else if (callback) { |
| 10759 callback(); |
| 10760 } |
| 10761 } |
| 10762 |
| 10763 function importUrls(urls, callback) { |
| 10764 if (urls && urls.length) { |
| 10765 var frag = document.createDocumentFragment(); |
| 10766 for (var i=0, l=urls.length, url, link; (i<l) && (url=urls[i]); i++) { |
| 10767 link = document.createElement('link'); |
| 10768 link.rel = 'import'; |
| 10769 link.href = url; |
| 10770 frag.appendChild(link); |
| 10771 } |
| 10772 importElements(frag, callback); |
| 10773 } else if (callback) { |
| 10774 callback(); |
| 10775 } |
| 10776 } |
| 10777 |
| 10778 // exports |
| 10779 scope.import = importUrls; |
| 10780 scope.importElements = importElements; |
| 10781 |
| 10782 })(Polymer); |
| 10783 |
| 10784 /* |
| 10785 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
| 10786 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt |
| 10787 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt |
| 10788 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt |
| 10789 * Code distributed by Google as part of the polymer project is also |
| 10790 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt |
| 10791 */ |
| 10792 |
6566 /** | 10793 /** |
6567 * The `auto-binding` element extends the template element. It provides a quick | 10794 * The `auto-binding` element extends the template element. It provides a quick |
6568 * and easy way to do data binding without the need to setup a model. | 10795 * and easy way to do data binding without the need to setup a model. |
6569 * The `auto-binding` element itself serves as the model and controller for the | 10796 * The `auto-binding` element itself serves as the model and controller for the |
6570 * elements it contains. Both data and event handlers can be bound. | 10797 * elements it contains. Both data and event handlers can be bound. |
6571 * | 10798 * |
6572 * The `auto-binding` element acts just like a template that is bound to | 10799 * The `auto-binding` element acts just like a template that is bound to |
6573 * a model. It stamps its content in the dom adjacent to itself. When the | 10800 * a model. It stamps its content in the dom adjacent to itself. When the |
6574 * content is stamped, the `template-bound` event is fired. | 10801 * content is stamped, the `template-bound` event is fired. |
6575 * | 10802 * |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
6634 prepareBinding.call(syntax, pathString, name, node); | 10861 prepareBinding.call(syntax, pathString, name, node); |
6635 }; | 10862 }; |
6636 return syntax; | 10863 return syntax; |
6637 } | 10864 } |
6638 | 10865 |
6639 }); | 10866 }); |
6640 | 10867 |
6641 })(); | 10868 })(); |
6642 | 10869 |
6643 //# sourceMappingURL=polymer.concat.js.map | 10870 //# sourceMappingURL=polymer.concat.js.map |
OLD | NEW |