Home | History | Annotate | Download | only in polymer
      1 /**
      2  * @license
      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://polymer.github.io/LICENSE.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/CONTRIBUTORS.txt
      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/PATENTS.txt
      9  */
     10 // @version 0.5.5
     11 window.PolymerGestures = {};
     12 
     13 (function(scope) {
     14   var hasFullPath = false;
     15 
     16   // test for full event path support
     17   var pathTest = document.createElement('meta');
     18   if (pathTest.createShadowRoot) {
     19     var sr = pathTest.createShadowRoot();
     20     var s = document.createElement('span');
     21     sr.appendChild(s);
     22     pathTest.addEventListener('testpath', function(ev) {
     23       if (ev.path) {
     24         // if the span is in the event path, then path[0] is the real source for all events
     25         hasFullPath = ev.path[0] === s;
     26       }
     27       ev.stopPropagation();
     28     });
     29     var ev = new CustomEvent('testpath', {bubbles: true});
     30     // must add node to DOM to trigger event listener
     31     document.head.appendChild(pathTest);
     32     s.dispatchEvent(ev);
     33     pathTest.parentNode.removeChild(pathTest);
     34     sr = s = null;
     35   }
     36   pathTest = null;
     37 
     38   var target = {
     39     shadow: function(inEl) {
     40       if (inEl) {
     41         return inEl.shadowRoot || inEl.webkitShadowRoot;
     42       }
     43     },
     44     canTarget: function(shadow) {
     45       return shadow && Boolean(shadow.elementFromPoint);
     46     },
     47     targetingShadow: function(inEl) {
     48       var s = this.shadow(inEl);
     49       if (this.canTarget(s)) {
     50         return s;
     51       }
     52     },
     53     olderShadow: function(shadow) {
     54       var os = shadow.olderShadowRoot;
     55       if (!os) {
     56         var se = shadow.querySelector('shadow');
     57         if (se) {
     58           os = se.olderShadowRoot;
     59         }
     60       }
     61       return os;
     62     },
     63     allShadows: function(element) {
     64       var shadows = [], s = this.shadow(element);
     65       while(s) {
     66         shadows.push(s);
     67         s = this.olderShadow(s);
     68       }
     69       return shadows;
     70     },
     71     searchRoot: function(inRoot, x, y) {
     72       var t, st, sr, os;
     73       if (inRoot) {
     74         t = inRoot.elementFromPoint(x, y);
     75         if (t) {
     76           // found element, check if it has a ShadowRoot
     77           sr = this.targetingShadow(t);
     78         } else if (inRoot !== document) {
     79           // check for sibling roots
     80           sr = this.olderShadow(inRoot);
     81         }
     82         // search other roots, fall back to light dom element
     83         return this.searchRoot(sr, x, y) || t;
     84       }
     85     },
     86     owner: function(element) {
     87       if (!element) {
     88         return document;
     89       }
     90       var s = element;
     91       // walk up until you hit the shadow root or document
     92       while (s.parentNode) {
     93         s = s.parentNode;
     94       }
     95       // the owner element is expected to be a Document or ShadowRoot
     96       if (s.nodeType != Node.DOCUMENT_NODE && s.nodeType != Node.DOCUMENT_FRAGMENT_NODE) {
     97         s = document;
     98       }
     99       return s;
    100     },
    101     findTarget: function(inEvent) {
    102       if (hasFullPath && inEvent.path && inEvent.path.length) {
    103         return inEvent.path[0];
    104       }
    105       var x = inEvent.clientX, y = inEvent.clientY;
    106       // if the listener is in the shadow root, it is much faster to start there
    107       var s = this.owner(inEvent.target);
    108       // if x, y is not in this root, fall back to document search
    109       if (!s.elementFromPoint(x, y)) {
    110         s = document;
    111       }
    112       return this.searchRoot(s, x, y);
    113     },
    114     findTouchAction: function(inEvent) {
    115       var n;
    116       if (hasFullPath && inEvent.path && inEvent.path.length) {
    117         var path = inEvent.path;
    118         for (var i = 0; i < path.length; i++) {
    119           n = path[i];
    120           if (n.nodeType === Node.ELEMENT_NODE && n.hasAttribute('touch-action')) {
    121             return n.getAttribute('touch-action');
    122           }
    123         }
    124       } else {
    125         n = inEvent.target;
    126         while(n) {
    127           if (n.nodeType === Node.ELEMENT_NODE && n.hasAttribute('touch-action')) {
    128             return n.getAttribute('touch-action');
    129           }
    130           n = n.parentNode || n.host;
    131         }
    132       }
    133       // auto is default
    134       return "auto";
    135     },
    136     LCA: function(a, b) {
    137       if (a === b) {
    138         return a;
    139       }
    140       if (a && !b) {
    141         return a;
    142       }
    143       if (b && !a) {
    144         return b;
    145       }
    146       if (!b && !a) {
    147         return document;
    148       }
    149       // fast case, a is a direct descendant of b or vice versa
    150       if (a.contains && a.contains(b)) {
    151         return a;
    152       }
    153       if (b.contains && b.contains(a)) {
    154         return b;
    155       }
    156       var adepth = this.depth(a);
    157       var bdepth = this.depth(b);
    158       var d = adepth - bdepth;
    159       if (d >= 0) {
    160         a = this.walk(a, d);
    161       } else {
    162         b = this.walk(b, -d);
    163       }
    164       while (a && b && a !== b) {
    165         a = a.parentNode || a.host;
    166         b = b.parentNode || b.host;
    167       }
    168       return a;
    169     },
    170     walk: function(n, u) {
    171       for (var i = 0; n && (i < u); i++) {
    172         n = n.parentNode || n.host;
    173       }
    174       return n;
    175     },
    176     depth: function(n) {
    177       var d = 0;
    178       while(n) {
    179         d++;
    180         n = n.parentNode || n.host;
    181       }
    182       return d;
    183     },
    184     deepContains: function(a, b) {
    185       var common = this.LCA(a, b);
    186       // if a is the common ancestor, it must "deeply" contain b
    187       return common === a;
    188     },
    189     insideNode: function(node, x, y) {
    190       var rect = node.getBoundingClientRect();
    191       return (rect.left <= x) && (x <= rect.right) && (rect.top <= y) && (y <= rect.bottom);
    192     },
    193     path: function(event) {
    194       var p;
    195       if (hasFullPath && event.path && event.path.length) {
    196         p = event.path;
    197       } else {
    198         p = [];
    199         var n = this.findTarget(event);
    200         while (n) {
    201           p.push(n);
    202           n = n.parentNode || n.host;
    203         }
    204       }
    205       return p;
    206     }
    207   };
    208   scope.targetFinding = target;
    209   /**
    210    * Given an event, finds the "deepest" node that could have been the original target before ShadowDOM retargetting
    211    *
    212    * @param {Event} Event An event object with clientX and clientY properties
    213    * @return {Element} The probable event origninator
    214    */
    215   scope.findTarget = target.findTarget.bind(target);
    216   /**
    217    * Determines if the "container" node deeply contains the "containee" node, including situations where the "containee" is contained by one or more ShadowDOM
    218    * roots.
    219    *
    220    * @param {Node} container
    221    * @param {Node} containee
    222    * @return {Boolean}
    223    */
    224   scope.deepContains = target.deepContains.bind(target);
    225 
    226   /**
    227    * Determines if the x/y position is inside the given node.
    228    *
    229    * Example:
    230    *
    231    *     function upHandler(event) {
    232    *       var innode = PolymerGestures.insideNode(event.target, event.clientX, event.clientY);
    233    *       if (innode) {
    234    *         // wait for tap?
    235    *       } else {
    236    *         // tap will never happen
    237    *       }
    238    *     }
    239    *
    240    * @param {Node} node
    241    * @param {Number} x Screen X position
    242    * @param {Number} y screen Y position
    243    * @return {Boolean}
    244    */
    245   scope.insideNode = target.insideNode;
    246 
    247 })(window.PolymerGestures);
    248 
    249 (function() {
    250   function shadowSelector(v) {
    251     return 'html /deep/ ' + selector(v);
    252   }
    253   function selector(v) {
    254     return '[touch-action="' + v + '"]';
    255   }
    256   function rule(v) {
    257     return '{ -ms-touch-action: ' + v + '; touch-action: ' + v + ';}';
    258   }
    259   var attrib2css = [
    260     'none',
    261     'auto',
    262     'pan-x',
    263     'pan-y',
    264     {
    265       rule: 'pan-x pan-y',
    266       selectors: [
    267         'pan-x pan-y',
    268         'pan-y pan-x'
    269       ]
    270     },
    271     'manipulation'
    272   ];
    273   var styles = '';
    274   // only install stylesheet if the browser has touch action support
    275   var hasTouchAction = typeof document.head.style.touchAction === 'string';
    276   // only add shadow selectors if shadowdom is supported
    277   var hasShadowRoot = !window.ShadowDOMPolyfill && document.head.createShadowRoot;
    278 
    279   if (hasTouchAction) {
    280     attrib2css.forEach(function(r) {
    281       if (String(r) === r) {
    282         styles += selector(r) + rule(r) + '\n';
    283         if (hasShadowRoot) {
    284           styles += shadowSelector(r) + rule(r) + '\n';
    285         }
    286       } else {
    287         styles += r.selectors.map(selector) + rule(r.rule) + '\n';
    288         if (hasShadowRoot) {
    289           styles += r.selectors.map(shadowSelector) + rule(r.rule) + '\n';
    290         }
    291       }
    292     });
    293 
    294     var el = document.createElement('style');
    295     el.textContent = styles;
    296     document.head.appendChild(el);
    297   }
    298 })();
    299 
    300 /**
    301  * This is the constructor for new PointerEvents.
    302  *
    303  * New Pointer Events must be given a type, and an optional dictionary of
    304  * initialization properties.
    305  *
    306  * Due to certain platform requirements, events returned from the constructor
    307  * identify as MouseEvents.
    308  *
    309  * @constructor
    310  * @param {String} inType The type of the event to create.
    311  * @param {Object} [inDict] An optional dictionary of initial event properties.
    312  * @return {Event} A new PointerEvent of type `inType` and initialized with properties from `inDict`.
    313  */
    314 (function(scope) {
    315 
    316   var MOUSE_PROPS = [
    317     'bubbles',
    318     'cancelable',
    319     'view',
    320     'detail',
    321     'screenX',
    322     'screenY',
    323     'clientX',
    324     'clientY',
    325     'ctrlKey',
    326     'altKey',
    327     'shiftKey',
    328     'metaKey',
    329     'button',
    330     'relatedTarget',
    331     'pageX',
    332     'pageY'
    333   ];
    334 
    335   var MOUSE_DEFAULTS = [
    336     false,
    337     false,
    338     null,
    339     null,
    340     0,
    341     0,
    342     0,
    343     0,
    344     false,
    345     false,
    346     false,
    347     false,
    348     0,
    349     null,
    350     0,
    351     0
    352   ];
    353 
    354   var NOP_FACTORY = function(){ return function(){}; };
    355 
    356   var eventFactory = {
    357     // TODO(dfreedm): this is overridden by tap recognizer, needs review
    358     preventTap: NOP_FACTORY,
    359     makeBaseEvent: function(inType, inDict) {
    360       var e = document.createEvent('Event');
    361       e.initEvent(inType, inDict.bubbles || false, inDict.cancelable || false);
    362       e.preventTap = eventFactory.preventTap(e);
    363       return e;
    364     },
    365     makeGestureEvent: function(inType, inDict) {
    366       inDict = inDict || Object.create(null);
    367 
    368       var e = this.makeBaseEvent(inType, inDict);
    369       for (var i = 0, keys = Object.keys(inDict), k; i < keys.length; i++) {
    370         k = keys[i];
    371         if( k !== 'bubbles' && k !== 'cancelable' ) {
    372            e[k] = inDict[k];
    373         }
    374       }
    375       return e;
    376     },
    377     makePointerEvent: function(inType, inDict) {
    378       inDict = inDict || Object.create(null);
    379 
    380       var e = this.makeBaseEvent(inType, inDict);
    381       // define inherited MouseEvent properties
    382       for(var i = 2, p; i < MOUSE_PROPS.length; i++) {
    383         p = MOUSE_PROPS[i];
    384         e[p] = inDict[p] || MOUSE_DEFAULTS[i];
    385       }
    386       e.buttons = inDict.buttons || 0;
    387 
    388       // Spec requires that pointers without pressure specified use 0.5 for down
    389       // state and 0 for up state.
    390       var pressure = 0;
    391       if (inDict.pressure) {
    392         pressure = inDict.pressure;
    393       } else {
    394         pressure = e.buttons ? 0.5 : 0;
    395       }
    396 
    397       // add x/y properties aliased to clientX/Y
    398       e.x = e.clientX;
    399       e.y = e.clientY;
    400 
    401       // define the properties of the PointerEvent interface
    402       e.pointerId = inDict.pointerId || 0;
    403       e.width = inDict.width || 0;
    404       e.height = inDict.height || 0;
    405       e.pressure = pressure;
    406       e.tiltX = inDict.tiltX || 0;
    407       e.tiltY = inDict.tiltY || 0;
    408       e.pointerType = inDict.pointerType || '';
    409       e.hwTimestamp = inDict.hwTimestamp || 0;
    410       e.isPrimary = inDict.isPrimary || false;
    411       e._source = inDict._source || '';
    412       return e;
    413     }
    414   };
    415 
    416   scope.eventFactory = eventFactory;
    417 })(window.PolymerGestures);
    418 
    419 /**
    420  * This module implements an map of pointer states
    421  */
    422 (function(scope) {
    423   var USE_MAP = window.Map && window.Map.prototype.forEach;
    424   var POINTERS_FN = function(){ return this.size; };
    425   function PointerMap() {
    426     if (USE_MAP) {
    427       var m = new Map();
    428       m.pointers = POINTERS_FN;
    429       return m;
    430     } else {
    431       this.keys = [];
    432       this.values = [];
    433     }
    434   }
    435 
    436   PointerMap.prototype = {
    437     set: function(inId, inEvent) {
    438       var i = this.keys.indexOf(inId);
    439       if (i > -1) {
    440         this.values[i] = inEvent;
    441       } else {
    442         this.keys.push(inId);
    443         this.values.push(inEvent);
    444       }
    445     },
    446     has: function(inId) {
    447       return this.keys.indexOf(inId) > -1;
    448     },
    449     'delete': function(inId) {
    450       var i = this.keys.indexOf(inId);
    451       if (i > -1) {
    452         this.keys.splice(i, 1);
    453         this.values.splice(i, 1);
    454       }
    455     },
    456     get: function(inId) {
    457       var i = this.keys.indexOf(inId);
    458       return this.values[i];
    459     },
    460     clear: function() {
    461       this.keys.length = 0;
    462       this.values.length = 0;
    463     },
    464     // return value, key, map
    465     forEach: function(callback, thisArg) {
    466       this.values.forEach(function(v, i) {
    467         callback.call(thisArg, v, this.keys[i], this);
    468       }, this);
    469     },
    470     pointers: function() {
    471       return this.keys.length;
    472     }
    473   };
    474 
    475   scope.PointerMap = PointerMap;
    476 })(window.PolymerGestures);
    477 
    478 (function(scope) {
    479   var CLONE_PROPS = [
    480     // MouseEvent
    481     'bubbles',
    482     'cancelable',
    483     'view',
    484     'detail',
    485     'screenX',
    486     'screenY',
    487     'clientX',
    488     'clientY',
    489     'ctrlKey',
    490     'altKey',
    491     'shiftKey',
    492     'metaKey',
    493     'button',
    494     'relatedTarget',
    495     // DOM Level 3
    496     'buttons',
    497     // PointerEvent
    498     'pointerId',
    499     'width',
    500     'height',
    501     'pressure',
    502     'tiltX',
    503     'tiltY',
    504     'pointerType',
    505     'hwTimestamp',
    506     'isPrimary',
    507     // event instance
    508     'type',
    509     'target',
    510     'currentTarget',
    511     'which',
    512     'pageX',
    513     'pageY',
    514     'timeStamp',
    515     // gesture addons
    516     'preventTap',
    517     'tapPrevented',
    518     '_source'
    519   ];
    520 
    521   var CLONE_DEFAULTS = [
    522     // MouseEvent
    523     false,
    524     false,
    525     null,
    526     null,
    527     0,
    528     0,
    529     0,
    530     0,
    531     false,
    532     false,
    533     false,
    534     false,
    535     0,
    536     null,
    537     // DOM Level 3
    538     0,
    539     // PointerEvent
    540     0,
    541     0,
    542     0,
    543     0,
    544     0,
    545     0,
    546     '',
    547     0,
    548     false,
    549     // event instance
    550     '',
    551     null,
    552     null,
    553     0,
    554     0,
    555     0,
    556     0,
    557     function(){},
    558     false
    559   ];
    560 
    561   var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined');
    562 
    563   var eventFactory = scope.eventFactory;
    564 
    565   // set of recognizers to run for the currently handled event
    566   var currentGestures;
    567 
    568   /**
    569    * This module is for normalizing events. Mouse and Touch events will be
    570    * collected here, and fire PointerEvents that have the same semantics, no
    571    * matter the source.
    572    * Events fired:
    573    *   - pointerdown: a pointing is added
    574    *   - pointerup: a pointer is removed
    575    *   - pointermove: a pointer is moved
    576    *   - pointerover: a pointer crosses into an element
    577    *   - pointerout: a pointer leaves an element
    578    *   - pointercancel: a pointer will no longer generate events
    579    */
    580   var dispatcher = {
    581     IS_IOS: false,
    582     pointermap: new scope.PointerMap(),
    583     requiredGestures: new scope.PointerMap(),
    584     eventMap: Object.create(null),
    585     // Scope objects for native events.
    586     // This exists for ease of testing.
    587     eventSources: Object.create(null),
    588     eventSourceList: [],
    589     gestures: [],
    590     // map gesture event -> {listeners: int, index: gestures[int]}
    591     dependencyMap: {
    592       // make sure down and up are in the map to trigger "register"
    593       down: {listeners: 0, index: -1},
    594       up: {listeners: 0, index: -1}
    595     },
    596     gestureQueue: [],
    597     /**
    598      * Add a new event source that will generate pointer events.
    599      *
    600      * `inSource` must contain an array of event names named `events`, and
    601      * functions with the names specified in the `events` array.
    602      * @param {string} name A name for the event source
    603      * @param {Object} source A new source of platform events.
    604      */
    605     registerSource: function(name, source) {
    606       var s = source;
    607       var newEvents = s.events;
    608       if (newEvents) {
    609         newEvents.forEach(function(e) {
    610           if (s[e]) {
    611             this.eventMap[e] = s[e].bind(s);
    612           }
    613         }, this);
    614         this.eventSources[name] = s;
    615         this.eventSourceList.push(s);
    616       }
    617     },
    618     registerGesture: function(name, source) {
    619       var obj = Object.create(null);
    620       obj.listeners = 0;
    621       obj.index = this.gestures.length;
    622       for (var i = 0, g; i < source.exposes.length; i++) {
    623         g = source.exposes[i].toLowerCase();
    624         this.dependencyMap[g] = obj;
    625       }
    626       this.gestures.push(source);
    627     },
    628     register: function(element, initial) {
    629       var l = this.eventSourceList.length;
    630       for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) {
    631         // call eventsource register
    632         es.register.call(es, element, initial);
    633       }
    634     },
    635     unregister: function(element) {
    636       var l = this.eventSourceList.length;
    637       for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) {
    638         // call eventsource register
    639         es.unregister.call(es, element);
    640       }
    641     },
    642     // EVENTS
    643     down: function(inEvent) {
    644       this.requiredGestures.set(inEvent.pointerId, currentGestures);
    645       this.fireEvent('down', inEvent);
    646     },
    647     move: function(inEvent) {
    648       // pipe move events into gesture queue directly
    649       inEvent.type = 'move';
    650       this.fillGestureQueue(inEvent);
    651     },
    652     up: function(inEvent) {
    653       this.fireEvent('up', inEvent);
    654       this.requiredGestures.delete(inEvent.pointerId);
    655     },
    656     cancel: function(inEvent) {
    657       inEvent.tapPrevented = true;
    658       this.fireEvent('up', inEvent);
    659       this.requiredGestures.delete(inEvent.pointerId);
    660     },
    661     addGestureDependency: function(node, currentGestures) {
    662       var gesturesWanted = node._pgEvents;
    663       if (gesturesWanted && currentGestures) {
    664         var gk = Object.keys(gesturesWanted);
    665         for (var i = 0, r, ri, g; i < gk.length; i++) {
    666           // gesture
    667           g = gk[i];
    668           if (gesturesWanted[g] > 0) {
    669             // lookup gesture recognizer
    670             r = this.dependencyMap[g];
    671             // recognizer index
    672             ri = r ? r.index : -1;
    673             currentGestures[ri] = true;
    674           }
    675         }
    676       }
    677     },
    678     // LISTENER LOGIC
    679     eventHandler: function(inEvent) {
    680       // This is used to prevent multiple dispatch of events from
    681       // platform events. This can happen when two elements in different scopes
    682       // are set up to create pointer events, which is relevant to Shadow DOM.
    683 
    684       var type = inEvent.type;
    685 
    686       // only generate the list of desired events on "down"
    687       if (type === 'touchstart' || type === 'mousedown' || type === 'pointerdown' || type === 'MSPointerDown') {
    688         if (!inEvent._handledByPG) {
    689           currentGestures = {};
    690         }
    691 
    692         // in IOS mode, there is only a listener on the document, so this is not re-entrant
    693         if (this.IS_IOS) {
    694           var ev = inEvent;
    695           if (type === 'touchstart') {
    696             var ct = inEvent.changedTouches[0];
    697             // set up a fake event to give to the path builder
    698             ev = {target: inEvent.target, clientX: ct.clientX, clientY: ct.clientY, path: inEvent.path};
    699           }
    700           // use event path if available, otherwise build a path from target finding
    701           var nodes = inEvent.path || scope.targetFinding.path(ev);
    702           for (var i = 0, n; i < nodes.length; i++) {
    703             n = nodes[i];
    704             this.addGestureDependency(n, currentGestures);
    705           }
    706         } else {
    707           this.addGestureDependency(inEvent.currentTarget, currentGestures);
    708         }
    709       }
    710 
    711       if (inEvent._handledByPG) {
    712         return;
    713       }
    714       var fn = this.eventMap && this.eventMap[type];
    715       if (fn) {
    716         fn(inEvent);
    717       }
    718       inEvent._handledByPG = true;
    719     },
    720     // set up event listeners
    721     listen: function(target, events) {
    722       for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) {
    723         this.addEvent(target, e);
    724       }
    725     },
    726     // remove event listeners
    727     unlisten: function(target, events) {
    728       for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) {
    729         this.removeEvent(target, e);
    730       }
    731     },
    732     addEvent: function(target, eventName) {
    733       target.addEventListener(eventName, this.boundHandler);
    734     },
    735     removeEvent: function(target, eventName) {
    736       target.removeEventListener(eventName, this.boundHandler);
    737     },
    738     // EVENT CREATION AND TRACKING
    739     /**
    740      * Creates a new Event of type `inType`, based on the information in
    741      * `inEvent`.
    742      *
    743      * @param {string} inType A string representing the type of event to create
    744      * @param {Event} inEvent A platform event with a target
    745      * @return {Event} A PointerEvent of type `inType`
    746      */
    747     makeEvent: function(inType, inEvent) {
    748       var e = eventFactory.makePointerEvent(inType, inEvent);
    749       e.preventDefault = inEvent.preventDefault;
    750       e.tapPrevented = inEvent.tapPrevented;
    751       e._target = e._target || inEvent.target;
    752       return e;
    753     },
    754     // make and dispatch an event in one call
    755     fireEvent: function(inType, inEvent) {
    756       var e = this.makeEvent(inType, inEvent);
    757       return this.dispatchEvent(e);
    758     },
    759     /**
    760      * Returns a snapshot of inEvent, with writable properties.
    761      *
    762      * @param {Event} inEvent An event that contains properties to copy.
    763      * @return {Object} An object containing shallow copies of `inEvent`'s
    764      *    properties.
    765      */
    766     cloneEvent: function(inEvent) {
    767       var eventCopy = Object.create(null), p;
    768       for (var i = 0; i < CLONE_PROPS.length; i++) {
    769         p = CLONE_PROPS[i];
    770         eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i];
    771         // Work around SVGInstanceElement shadow tree
    772         // Return the <use> element that is represented by the instance for Safari, Chrome, IE.
    773         // This is the behavior implemented by Firefox.
    774         if (p === 'target' || p === 'relatedTarget') {
    775           if (HAS_SVG_INSTANCE && eventCopy[p] instanceof SVGElementInstance) {
    776             eventCopy[p] = eventCopy[p].correspondingUseElement;
    777           }
    778         }
    779       }
    780       // keep the semantics of preventDefault
    781       eventCopy.preventDefault = function() {
    782         inEvent.preventDefault();
    783       };
    784       return eventCopy;
    785     },
    786     /**
    787      * Dispatches the event to its target.
    788      *
    789      * @param {Event} inEvent The event to be dispatched.
    790      * @return {Boolean} True if an event handler returns true, false otherwise.
    791      */
    792     dispatchEvent: function(inEvent) {
    793       var t = inEvent._target;
    794       if (t) {
    795         t.dispatchEvent(inEvent);
    796         // clone the event for the gesture system to process
    797         // clone after dispatch to pick up gesture prevention code
    798         var clone = this.cloneEvent(inEvent);
    799         clone.target = t;
    800         this.fillGestureQueue(clone);
    801       }
    802     },
    803     gestureTrigger: function() {
    804       // process the gesture queue
    805       for (var i = 0, e, rg; i < this.gestureQueue.length; i++) {
    806         e = this.gestureQueue[i];
    807         rg = e._requiredGestures;
    808         if (rg) {
    809           for (var j = 0, g, fn; j < this.gestures.length; j++) {
    810             // only run recognizer if an element in the source event's path is listening for those gestures
    811             if (rg[j]) {
    812               g = this.gestures[j];
    813               fn = g[e.type];
    814               if (fn) {
    815                 fn.call(g, e);
    816               }
    817             }
    818           }
    819         }
    820       }
    821       this.gestureQueue.length = 0;
    822     },
    823     fillGestureQueue: function(ev) {
    824       // only trigger the gesture queue once
    825       if (!this.gestureQueue.length) {
    826         requestAnimationFrame(this.boundGestureTrigger);
    827       }
    828       ev._requiredGestures = this.requiredGestures.get(ev.pointerId);
    829       this.gestureQueue.push(ev);
    830     }
    831   };
    832   dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher);
    833   dispatcher.boundGestureTrigger = dispatcher.gestureTrigger.bind(dispatcher);
    834   scope.dispatcher = dispatcher;
    835 
    836   /**
    837    * Listen for `gesture` on `node` with the `handler` function
    838    *
    839    * If `handler` is the first listener for `gesture`, the underlying gesture recognizer is then enabled.
    840    *
    841    * @param {Element} node
    842    * @param {string} gesture
    843    * @return Boolean `gesture` is a valid gesture
    844    */
    845   scope.activateGesture = function(node, gesture) {
    846     var g = gesture.toLowerCase();
    847     var dep = dispatcher.dependencyMap[g];
    848     if (dep) {
    849       var recognizer = dispatcher.gestures[dep.index];
    850       if (!node._pgListeners) {
    851         dispatcher.register(node);
    852         node._pgListeners = 0;
    853       }
    854       // TODO(dfreedm): re-evaluate bookkeeping to avoid using attributes
    855       if (recognizer) {
    856         var touchAction = recognizer.defaultActions && recognizer.defaultActions[g];
    857         var actionNode;
    858         switch(node.nodeType) {
    859           case Node.ELEMENT_NODE:
    860             actionNode = node;
    861           break;
    862           case Node.DOCUMENT_FRAGMENT_NODE:
    863             actionNode = node.host;
    864           break;
    865           default:
    866             actionNode = null;
    867           break;
    868         }
    869         if (touchAction && actionNode && !actionNode.hasAttribute('touch-action')) {
    870           actionNode.setAttribute('touch-action', touchAction);
    871         }
    872       }
    873       if (!node._pgEvents) {
    874         node._pgEvents = {};
    875       }
    876       node._pgEvents[g] = (node._pgEvents[g] || 0) + 1;
    877       node._pgListeners++;
    878     }
    879     return Boolean(dep);
    880   };
    881 
    882   /**
    883    *
    884    * Listen for `gesture` from `node` with `handler` function.
    885    *
    886    * @param {Element} node
    887    * @param {string} gesture
    888    * @param {Function} handler
    889    * @param {Boolean} capture
    890    */
    891   scope.addEventListener = function(node, gesture, handler, capture) {
    892     if (handler) {
    893       scope.activateGesture(node, gesture);
    894       node.addEventListener(gesture, handler, capture);
    895     }
    896   };
    897 
    898   /**
    899    * Tears down the gesture configuration for `node`
    900    *
    901    * If `handler` is the last listener for `gesture`, the underlying gesture recognizer is disabled.
    902    *
    903    * @param {Element} node
    904    * @param {string} gesture
    905    * @return Boolean `gesture` is a valid gesture
    906    */
    907   scope.deactivateGesture = function(node, gesture) {
    908     var g = gesture.toLowerCase();
    909     var dep = dispatcher.dependencyMap[g];
    910     if (dep) {
    911       if (node._pgListeners > 0) {
    912         node._pgListeners--;
    913       }
    914       if (node._pgListeners === 0) {
    915         dispatcher.unregister(node);
    916       }
    917       if (node._pgEvents) {
    918         if (node._pgEvents[g] > 0) {
    919           node._pgEvents[g]--;
    920         } else {
    921           node._pgEvents[g] = 0;
    922         }
    923       }
    924     }
    925     return Boolean(dep);
    926   };
    927 
    928   /**
    929    * Stop listening for `gesture` from `node` with `handler` function.
    930    *
    931    * @param {Element} node
    932    * @param {string} gesture
    933    * @param {Function} handler
    934    * @param {Boolean} capture
    935    */
    936   scope.removeEventListener = function(node, gesture, handler, capture) {
    937     if (handler) {
    938       scope.deactivateGesture(node, gesture);
    939       node.removeEventListener(gesture, handler, capture);
    940     }
    941   };
    942 })(window.PolymerGestures);
    943 
    944 (function(scope) {
    945   var dispatcher = scope.dispatcher;
    946   var pointermap = dispatcher.pointermap;
    947   // radius around touchend that swallows mouse events
    948   var DEDUP_DIST = 25;
    949 
    950   var WHICH_TO_BUTTONS = [0, 1, 4, 2];
    951 
    952   var currentButtons = 0;
    953 
    954   var FIREFOX_LINUX = /Linux.*Firefox\//i;
    955 
    956   var HAS_BUTTONS = (function() {
    957     // firefox on linux returns spec-incorrect values for mouseup.buttons
    958     // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent.buttons#See_also
    959     // https://codereview.chromium.org/727593003/#msg16
    960     if (FIREFOX_LINUX.test(navigator.userAgent)) {
    961       return false;
    962     }
    963     try {
    964       return new MouseEvent('test', {buttons: 1}).buttons === 1;
    965     } catch (e) {
    966       return false;
    967     }
    968   })();
    969 
    970   // handler block for native mouse events
    971   var mouseEvents = {
    972     POINTER_ID: 1,
    973     POINTER_TYPE: 'mouse',
    974     events: [
    975       'mousedown',
    976       'mousemove',
    977       'mouseup'
    978     ],
    979     exposes: [
    980       'down',
    981       'up',
    982       'move'
    983     ],
    984     register: function(target) {
    985       dispatcher.listen(target, this.events);
    986     },
    987     unregister: function(target) {
    988       if (target.nodeType === Node.DOCUMENT_NODE) {
    989         return;
    990       }
    991       dispatcher.unlisten(target, this.events);
    992     },
    993     lastTouches: [],
    994     // collide with the global mouse listener
    995     isEventSimulatedFromTouch: function(inEvent) {
    996       var lts = this.lastTouches;
    997       var x = inEvent.clientX, y = inEvent.clientY;
    998       for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) {
    999         // simulated mouse events will be swallowed near a primary touchend
   1000         var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
   1001         if (dx <= DEDUP_DIST && dy <= DEDUP_DIST) {
   1002           return true;
   1003         }
   1004       }
   1005     },
   1006     prepareEvent: function(inEvent) {
   1007       var e = dispatcher.cloneEvent(inEvent);
   1008       e.pointerId = this.POINTER_ID;
   1009       e.isPrimary = true;
   1010       e.pointerType = this.POINTER_TYPE;
   1011       e._source = 'mouse';
   1012       if (!HAS_BUTTONS) {
   1013         var type = inEvent.type;
   1014         var bit = WHICH_TO_BUTTONS[inEvent.which] || 0;
   1015         if (type === 'mousedown') {
   1016           currentButtons |= bit;
   1017         } else if (type === 'mouseup') {
   1018           currentButtons &= ~bit;
   1019         }
   1020         e.buttons = currentButtons;
   1021       }
   1022       return e;
   1023     },
   1024     mousedown: function(inEvent) {
   1025       if (!this.isEventSimulatedFromTouch(inEvent)) {
   1026         var p = pointermap.has(this.POINTER_ID);
   1027         var e = this.prepareEvent(inEvent);
   1028         e.target = scope.findTarget(inEvent);
   1029         pointermap.set(this.POINTER_ID, e.target);
   1030         dispatcher.down(e);
   1031       }
   1032     },
   1033     mousemove: function(inEvent) {
   1034       if (!this.isEventSimulatedFromTouch(inEvent)) {
   1035         var target = pointermap.get(this.POINTER_ID);
   1036         if (target) {
   1037           var e = this.prepareEvent(inEvent);
   1038           e.target = target;
   1039           // handle case where we missed a mouseup
   1040           if ((HAS_BUTTONS ? e.buttons : e.which) === 0) {
   1041             if (!HAS_BUTTONS) {
   1042               currentButtons = e.buttons = 0;
   1043             }
   1044             dispatcher.cancel(e);
   1045             this.cleanupMouse(e.buttons);
   1046           } else {
   1047             dispatcher.move(e);
   1048           }
   1049         }
   1050       }
   1051     },
   1052     mouseup: function(inEvent) {
   1053       if (!this.isEventSimulatedFromTouch(inEvent)) {
   1054         var e = this.prepareEvent(inEvent);
   1055         e.relatedTarget = scope.findTarget(inEvent);
   1056         e.target = pointermap.get(this.POINTER_ID);
   1057         dispatcher.up(e);
   1058         this.cleanupMouse(e.buttons);
   1059       }
   1060     },
   1061     cleanupMouse: function(buttons) {
   1062       if (buttons === 0) {
   1063         pointermap.delete(this.POINTER_ID);
   1064       }
   1065     }
   1066   };
   1067 
   1068   scope.mouseEvents = mouseEvents;
   1069 })(window.PolymerGestures);
   1070 
   1071 (function(scope) {
   1072   var dispatcher = scope.dispatcher;
   1073   var allShadows = scope.targetFinding.allShadows.bind(scope.targetFinding);
   1074   var pointermap = dispatcher.pointermap;
   1075   var touchMap = Array.prototype.map.call.bind(Array.prototype.map);
   1076   // This should be long enough to ignore compat mouse events made by touch
   1077   var DEDUP_TIMEOUT = 2500;
   1078   var DEDUP_DIST = 25;
   1079   var CLICK_COUNT_TIMEOUT = 200;
   1080   var HYSTERESIS = 20;
   1081   var ATTRIB = 'touch-action';
   1082   // TODO(dfreedm): disable until http://crbug.com/399765 is resolved
   1083   // var HAS_TOUCH_ACTION = ATTRIB in document.head.style;
   1084   var HAS_TOUCH_ACTION = false;
   1085 
   1086   // handler block for native touch events
   1087   var touchEvents = {
   1088     IS_IOS: false,
   1089     events: [
   1090       'touchstart',
   1091       'touchmove',
   1092       'touchend',
   1093       'touchcancel'
   1094     ],
   1095     exposes: [
   1096       'down',
   1097       'up',
   1098       'move'
   1099     ],
   1100     register: function(target, initial) {
   1101       if (this.IS_IOS ? initial : !initial) {
   1102         dispatcher.listen(target, this.events);
   1103       }
   1104     },
   1105     unregister: function(target) {
   1106       if (!this.IS_IOS) {
   1107         dispatcher.unlisten(target, this.events);
   1108       }
   1109     },
   1110     scrollTypes: {
   1111       EMITTER: 'none',
   1112       XSCROLLER: 'pan-x',
   1113       YSCROLLER: 'pan-y',
   1114     },
   1115     touchActionToScrollType: function(touchAction) {
   1116       var t = touchAction;
   1117       var st = this.scrollTypes;
   1118       if (t === st.EMITTER) {
   1119         return 'none';
   1120       } else if (t === st.XSCROLLER) {
   1121         return 'X';
   1122       } else if (t === st.YSCROLLER) {
   1123         return 'Y';
   1124       } else {
   1125         return 'XY';
   1126       }
   1127     },
   1128     POINTER_TYPE: 'touch',
   1129     firstTouch: null,
   1130     isPrimaryTouch: function(inTouch) {
   1131       return this.firstTouch === inTouch.identifier;
   1132     },
   1133     setPrimaryTouch: function(inTouch) {
   1134       // set primary touch if there no pointers, or the only pointer is the mouse
   1135       if (pointermap.pointers() === 0 || (pointermap.pointers() === 1 && pointermap.has(1))) {
   1136         this.firstTouch = inTouch.identifier;
   1137         this.firstXY = {X: inTouch.clientX, Y: inTouch.clientY};
   1138         this.firstTarget = inTouch.target;
   1139         this.scrolling = null;
   1140         this.cancelResetClickCount();
   1141       }
   1142     },
   1143     removePrimaryPointer: function(inPointer) {
   1144       if (inPointer.isPrimary) {
   1145         this.firstTouch = null;
   1146         this.firstXY = null;
   1147         this.resetClickCount();
   1148       }
   1149     },
   1150     clickCount: 0,
   1151     resetId: null,
   1152     resetClickCount: function() {
   1153       var fn = function() {
   1154         this.clickCount = 0;
   1155         this.resetId = null;
   1156       }.bind(this);
   1157       this.resetId = setTimeout(fn, CLICK_COUNT_TIMEOUT);
   1158     },
   1159     cancelResetClickCount: function() {
   1160       if (this.resetId) {
   1161         clearTimeout(this.resetId);
   1162       }
   1163     },
   1164     typeToButtons: function(type) {
   1165       var ret = 0;
   1166       if (type === 'touchstart' || type === 'touchmove') {
   1167         ret = 1;
   1168       }
   1169       return ret;
   1170     },
   1171     findTarget: function(touch, id) {
   1172       if (this.currentTouchEvent.type === 'touchstart') {
   1173         if (this.isPrimaryTouch(touch)) {
   1174           var fastPath = {
   1175             clientX: touch.clientX,
   1176             clientY: touch.clientY,
   1177             path: this.currentTouchEvent.path,
   1178             target: this.currentTouchEvent.target
   1179           };
   1180           return scope.findTarget(fastPath);
   1181         } else {
   1182           return scope.findTarget(touch);
   1183         }
   1184       }
   1185       // reuse target we found in touchstart
   1186       return pointermap.get(id);
   1187     },
   1188     touchToPointer: function(inTouch) {
   1189       var cte = this.currentTouchEvent;
   1190       var e = dispatcher.cloneEvent(inTouch);
   1191       // Spec specifies that pointerId 1 is reserved for Mouse.
   1192       // Touch identifiers can start at 0.
   1193       // Add 2 to the touch identifier for compatibility.
   1194       var id = e.pointerId = inTouch.identifier + 2;
   1195       e.target = this.findTarget(inTouch, id);
   1196       e.bubbles = true;
   1197       e.cancelable = true;
   1198       e.detail = this.clickCount;
   1199       e.buttons = this.typeToButtons(cte.type);
   1200       e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0;
   1201       e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0;
   1202       e.pressure = inTouch.webkitForce || inTouch.force || 0.5;
   1203       e.isPrimary = this.isPrimaryTouch(inTouch);
   1204       e.pointerType = this.POINTER_TYPE;
   1205       e._source = 'touch';
   1206       // forward touch preventDefaults
   1207       var self = this;
   1208       e.preventDefault = function() {
   1209         self.scrolling = false;
   1210         self.firstXY = null;
   1211         cte.preventDefault();
   1212       };
   1213       return e;
   1214     },
   1215     processTouches: function(inEvent, inFunction) {
   1216       var tl = inEvent.changedTouches;
   1217       this.currentTouchEvent = inEvent;
   1218       for (var i = 0, t, p; i < tl.length; i++) {
   1219         t = tl[i];
   1220         p = this.touchToPointer(t);
   1221         if (inEvent.type === 'touchstart') {
   1222           pointermap.set(p.pointerId, p.target);
   1223         }
   1224         if (pointermap.has(p.pointerId)) {
   1225           inFunction.call(this, p);
   1226         }
   1227         if (inEvent.type === 'touchend' || inEvent._cancel) {
   1228           this.cleanUpPointer(p);
   1229         }
   1230       }
   1231     },
   1232     // For single axis scrollers, determines whether the element should emit
   1233     // pointer events or behave as a scroller
   1234     shouldScroll: function(inEvent) {
   1235       if (this.firstXY) {
   1236         var ret;
   1237         var touchAction = scope.targetFinding.findTouchAction(inEvent);
   1238         var scrollAxis = this.touchActionToScrollType(touchAction);
   1239         if (scrollAxis === 'none') {
   1240           // this element is a touch-action: none, should never scroll
   1241           ret = false;
   1242         } else if (scrollAxis === 'XY') {
   1243           // this element should always scroll
   1244           ret = true;
   1245         } else {
   1246           var t = inEvent.changedTouches[0];
   1247           // check the intended scroll axis, and other axis
   1248           var a = scrollAxis;
   1249           var oa = scrollAxis === 'Y' ? 'X' : 'Y';
   1250           var da = Math.abs(t['client' + a] - this.firstXY[a]);
   1251           var doa = Math.abs(t['client' + oa] - this.firstXY[oa]);
   1252           // if delta in the scroll axis > delta other axis, scroll instead of
   1253           // making events
   1254           ret = da >= doa;
   1255         }
   1256         return ret;
   1257       }
   1258     },
   1259     findTouch: function(inTL, inId) {
   1260       for (var i = 0, l = inTL.length, t; i < l && (t = inTL[i]); i++) {
   1261         if (t.identifier === inId) {
   1262           return true;
   1263         }
   1264       }
   1265     },
   1266     // In some instances, a touchstart can happen without a touchend. This
   1267     // leaves the pointermap in a broken state.
   1268     // Therefore, on every touchstart, we remove the touches that did not fire a
   1269     // touchend event.
   1270     // To keep state globally consistent, we fire a
   1271     // pointercancel for this "abandoned" touch
   1272     vacuumTouches: function(inEvent) {
   1273       var tl = inEvent.touches;
   1274       // pointermap.pointers() should be < tl.length here, as the touchstart has not
   1275       // been processed yet.
   1276       if (pointermap.pointers() >= tl.length) {
   1277         var d = [];
   1278         pointermap.forEach(function(value, key) {
   1279           // Never remove pointerId == 1, which is mouse.
   1280           // Touch identifiers are 2 smaller than their pointerId, which is the
   1281           // index in pointermap.
   1282           if (key !== 1 && !this.findTouch(tl, key - 2)) {
   1283             var p = value;
   1284             d.push(p);
   1285           }
   1286         }, this);
   1287         d.forEach(function(p) {
   1288           this.cancel(p);
   1289           pointermap.delete(p.pointerId);
   1290         }, this);
   1291       }
   1292     },
   1293     touchstart: function(inEvent) {
   1294       this.vacuumTouches(inEvent);
   1295       this.setPrimaryTouch(inEvent.changedTouches[0]);
   1296       this.dedupSynthMouse(inEvent);
   1297       if (!this.scrolling) {
   1298         this.clickCount++;
   1299         this.processTouches(inEvent, this.down);
   1300       }
   1301     },
   1302     down: function(inPointer) {
   1303       dispatcher.down(inPointer);
   1304     },
   1305     touchmove: function(inEvent) {
   1306       if (HAS_TOUCH_ACTION) {
   1307         // touchevent.cancelable == false is sent when the page is scrolling under native Touch Action in Chrome 36
   1308         // https://groups.google.com/a/chromium.org/d/msg/input-dev/wHnyukcYBcA/b9kmtwM1jJQJ
   1309         if (inEvent.cancelable) {
   1310           this.processTouches(inEvent, this.move);
   1311         }
   1312       } else {
   1313         if (!this.scrolling) {
   1314           if (this.scrolling === null && this.shouldScroll(inEvent)) {
   1315             this.scrolling = true;
   1316           } else {
   1317             this.scrolling = false;
   1318             inEvent.preventDefault();
   1319             this.processTouches(inEvent, this.move);
   1320           }
   1321         } else if (this.firstXY) {
   1322           var t = inEvent.changedTouches[0];
   1323           var dx = t.clientX - this.firstXY.X;
   1324           var dy = t.clientY - this.firstXY.Y;
   1325           var dd = Math.sqrt(dx * dx + dy * dy);
   1326           if (dd >= HYSTERESIS) {
   1327             this.touchcancel(inEvent);
   1328             this.scrolling = true;
   1329             this.firstXY = null;
   1330           }
   1331         }
   1332       }
   1333     },
   1334     move: function(inPointer) {
   1335       dispatcher.move(inPointer);
   1336     },
   1337     touchend: function(inEvent) {
   1338       this.dedupSynthMouse(inEvent);
   1339       this.processTouches(inEvent, this.up);
   1340     },
   1341     up: function(inPointer) {
   1342       inPointer.relatedTarget = scope.findTarget(inPointer);
   1343       dispatcher.up(inPointer);
   1344     },
   1345     cancel: function(inPointer) {
   1346       dispatcher.cancel(inPointer);
   1347     },
   1348     touchcancel: function(inEvent) {
   1349       inEvent._cancel = true;
   1350       this.processTouches(inEvent, this.cancel);
   1351     },
   1352     cleanUpPointer: function(inPointer) {
   1353       pointermap['delete'](inPointer.pointerId);
   1354       this.removePrimaryPointer(inPointer);
   1355     },
   1356     // prevent synth mouse events from creating pointer events
   1357     dedupSynthMouse: function(inEvent) {
   1358       var lts = scope.mouseEvents.lastTouches;
   1359       var t = inEvent.changedTouches[0];
   1360       // only the primary finger will synth mouse events
   1361       if (this.isPrimaryTouch(t)) {
   1362         // remember x/y of last touch
   1363         var lt = {x: t.clientX, y: t.clientY};
   1364         lts.push(lt);
   1365         var fn = (function(lts, lt){
   1366           var i = lts.indexOf(lt);
   1367           if (i > -1) {
   1368             lts.splice(i, 1);
   1369           }
   1370         }).bind(null, lts, lt);
   1371         setTimeout(fn, DEDUP_TIMEOUT);
   1372       }
   1373     }
   1374   };
   1375 
   1376   // prevent "ghost clicks" that come from elements that were removed in a touch handler
   1377   var STOP_PROP_FN = Event.prototype.stopImmediatePropagation || Event.prototype.stopPropagation;
   1378   document.addEventListener('click', function(ev) {
   1379     var x = ev.clientX, y = ev.clientY;
   1380     // check if a click is within DEDUP_DIST px radius of the touchstart
   1381     var closeTo = function(touch) {
   1382       var dx = Math.abs(x - touch.x), dy = Math.abs(y - touch.y);
   1383       return (dx <= DEDUP_DIST && dy <= DEDUP_DIST);
   1384     };
   1385     // if click coordinates are close to touch coordinates, assume the click came from a touch
   1386     var wasTouched = scope.mouseEvents.lastTouches.some(closeTo);
   1387     // if the click came from touch, and the touchstart target is not in the path of the click event,
   1388     // then the touchstart target was probably removed, and the click should be "busted"
   1389     var path = scope.targetFinding.path(ev);
   1390     if (wasTouched) {
   1391       for (var i = 0; i < path.length; i++) {
   1392         if (path[i] === touchEvents.firstTarget) {
   1393           return;
   1394         }
   1395       }
   1396       ev.preventDefault();
   1397       STOP_PROP_FN.call(ev);
   1398     }
   1399   }, true);
   1400 
   1401   scope.touchEvents = touchEvents;
   1402 })(window.PolymerGestures);
   1403 
   1404 (function(scope) {
   1405   var dispatcher = scope.dispatcher;
   1406   var pointermap = dispatcher.pointermap;
   1407   var HAS_BITMAP_TYPE = window.MSPointerEvent && typeof window.MSPointerEvent.MSPOINTER_TYPE_MOUSE === 'number';
   1408   var msEvents = {
   1409     events: [
   1410       'MSPointerDown',
   1411       'MSPointerMove',
   1412       'MSPointerUp',
   1413       'MSPointerCancel',
   1414     ],
   1415     register: function(target) {
   1416       dispatcher.listen(target, this.events);
   1417     },
   1418     unregister: function(target) {
   1419       if (target.nodeType === Node.DOCUMENT_NODE) {
   1420         return;
   1421       }
   1422       dispatcher.unlisten(target, this.events);
   1423     },
   1424     POINTER_TYPES: [
   1425       '',
   1426       'unavailable',
   1427       'touch',
   1428       'pen',
   1429       'mouse'
   1430     ],
   1431     prepareEvent: function(inEvent) {
   1432       var e = inEvent;
   1433       e = dispatcher.cloneEvent(inEvent);
   1434       if (HAS_BITMAP_TYPE) {
   1435         e.pointerType = this.POINTER_TYPES[inEvent.pointerType];
   1436       }
   1437       e._source = 'ms';
   1438       return e;
   1439     },
   1440     cleanup: function(id) {
   1441       pointermap['delete'](id);
   1442     },
   1443     MSPointerDown: function(inEvent) {
   1444       var e = this.prepareEvent(inEvent);
   1445       e.target = scope.findTarget(inEvent);
   1446       pointermap.set(inEvent.pointerId, e.target);
   1447       dispatcher.down(e);
   1448     },
   1449     MSPointerMove: function(inEvent) {
   1450       var target = pointermap.get(inEvent.pointerId);
   1451       if (target) {
   1452         var e = this.prepareEvent(inEvent);
   1453         e.target = target;
   1454         dispatcher.move(e);
   1455       }
   1456     },
   1457     MSPointerUp: function(inEvent) {
   1458       var e = this.prepareEvent(inEvent);
   1459       e.relatedTarget = scope.findTarget(inEvent);
   1460       e.target = pointermap.get(e.pointerId);
   1461       dispatcher.up(e);
   1462       this.cleanup(inEvent.pointerId);
   1463     },
   1464     MSPointerCancel: function(inEvent) {
   1465       var e = this.prepareEvent(inEvent);
   1466       e.relatedTarget = scope.findTarget(inEvent);
   1467       e.target = pointermap.get(e.pointerId);
   1468       dispatcher.cancel(e);
   1469       this.cleanup(inEvent.pointerId);
   1470     }
   1471   };
   1472 
   1473   scope.msEvents = msEvents;
   1474 })(window.PolymerGestures);
   1475 
   1476 (function(scope) {
   1477   var dispatcher = scope.dispatcher;
   1478   var pointermap = dispatcher.pointermap;
   1479   var pointerEvents = {
   1480     events: [
   1481       'pointerdown',
   1482       'pointermove',
   1483       'pointerup',
   1484       'pointercancel'
   1485     ],
   1486     prepareEvent: function(inEvent) {
   1487       var e = dispatcher.cloneEvent(inEvent);
   1488       e._source = 'pointer';
   1489       return e;
   1490     },
   1491     register: function(target) {
   1492       dispatcher.listen(target, this.events);
   1493     },
   1494     unregister: function(target) {
   1495       if (target.nodeType === Node.DOCUMENT_NODE) {
   1496         return;
   1497       }
   1498       dispatcher.unlisten(target, this.events);
   1499     },
   1500     cleanup: function(id) {
   1501       pointermap['delete'](id);
   1502     },
   1503     pointerdown: function(inEvent) {
   1504       var e = this.prepareEvent(inEvent);
   1505       e.target = scope.findTarget(inEvent);
   1506       pointermap.set(e.pointerId, e.target);
   1507       dispatcher.down(e);
   1508     },
   1509     pointermove: function(inEvent) {
   1510       var target = pointermap.get(inEvent.pointerId);
   1511       if (target) {
   1512         var e = this.prepareEvent(inEvent);
   1513         e.target = target;
   1514         dispatcher.move(e);
   1515       }
   1516     },
   1517     pointerup: function(inEvent) {
   1518       var e = this.prepareEvent(inEvent);
   1519       e.relatedTarget = scope.findTarget(inEvent);
   1520       e.target = pointermap.get(e.pointerId);
   1521       dispatcher.up(e);
   1522       this.cleanup(inEvent.pointerId);
   1523     },
   1524     pointercancel: function(inEvent) {
   1525       var e = this.prepareEvent(inEvent);
   1526       e.relatedTarget = scope.findTarget(inEvent);
   1527       e.target = pointermap.get(e.pointerId);
   1528       dispatcher.cancel(e);
   1529       this.cleanup(inEvent.pointerId);
   1530     }
   1531   };
   1532 
   1533   scope.pointerEvents = pointerEvents;
   1534 })(window.PolymerGestures);
   1535 
   1536 /**
   1537  * This module contains the handlers for native platform events.
   1538  * From here, the dispatcher is called to create unified pointer events.
   1539  * Included are touch events (v1), mouse events, and MSPointerEvents.
   1540  */
   1541 (function(scope) {
   1542 
   1543   var dispatcher = scope.dispatcher;
   1544   var nav = window.navigator;
   1545 
   1546   if (window.PointerEvent) {
   1547     dispatcher.registerSource('pointer', scope.pointerEvents);
   1548   } else if (nav.msPointerEnabled) {
   1549     dispatcher.registerSource('ms', scope.msEvents);
   1550   } else {
   1551     dispatcher.registerSource('mouse', scope.mouseEvents);
   1552     if (window.ontouchstart !== undefined) {
   1553       dispatcher.registerSource('touch', scope.touchEvents);
   1554     }
   1555   }
   1556 
   1557   // Work around iOS bugs https://bugs.webkit.org/show_bug.cgi?id=135628 and https://bugs.webkit.org/show_bug.cgi?id=136506
   1558   var ua = navigator.userAgent;
   1559   var IS_IOS = ua.match(/iPad|iPhone|iPod/) && 'ontouchstart' in window;
   1560 
   1561   dispatcher.IS_IOS = IS_IOS;
   1562   scope.touchEvents.IS_IOS = IS_IOS;
   1563 
   1564   dispatcher.register(document, true);
   1565 })(window.PolymerGestures);
   1566 
   1567 /**
   1568  * This event denotes the beginning of a series of tracking events.
   1569  *
   1570  * @module PointerGestures
   1571  * @submodule Events
   1572  * @class trackstart
   1573  */
   1574 /**
   1575  * Pixels moved in the x direction since trackstart.
   1576  * @type Number
   1577  * @property dx
   1578  */
   1579 /**
   1580  * Pixes moved in the y direction since trackstart.
   1581  * @type Number
   1582  * @property dy
   1583  */
   1584 /**
   1585  * Pixels moved in the x direction since the last track.
   1586  * @type Number
   1587  * @property ddx
   1588  */
   1589 /**
   1590  * Pixles moved in the y direction since the last track.
   1591  * @type Number
   1592  * @property ddy
   1593  */
   1594 /**
   1595  * The clientX position of the track gesture.
   1596  * @type Number
   1597  * @property clientX
   1598  */
   1599 /**
   1600  * The clientY position of the track gesture.
   1601  * @type Number
   1602  * @property clientY
   1603  */
   1604 /**
   1605  * The pageX position of the track gesture.
   1606  * @type Number
   1607  * @property pageX
   1608  */
   1609 /**
   1610  * The pageY position of the track gesture.
   1611  * @type Number
   1612  * @property pageY
   1613  */
   1614 /**
   1615  * The screenX position of the track gesture.
   1616  * @type Number
   1617  * @property screenX
   1618  */
   1619 /**
   1620  * The screenY position of the track gesture.
   1621  * @type Number
   1622  * @property screenY
   1623  */
   1624 /**
   1625  * The last x axis direction of the pointer.
   1626  * @type Number
   1627  * @property xDirection
   1628  */
   1629 /**
   1630  * The last y axis direction of the pointer.
   1631  * @type Number
   1632  * @property yDirection
   1633  */
   1634 /**
   1635  * A shared object between all tracking events.
   1636  * @type Object
   1637  * @property trackInfo
   1638  */
   1639 /**
   1640  * The element currently under the pointer.
   1641  * @type Element
   1642  * @property relatedTarget
   1643  */
   1644 /**
   1645  * The type of pointer that make the track gesture.
   1646  * @type String
   1647  * @property pointerType
   1648  */
   1649 /**
   1650  *
   1651  * This event fires for all pointer movement being tracked.
   1652  *
   1653  * @class track
   1654  * @extends trackstart
   1655  */
   1656 /**
   1657  * This event fires when the pointer is no longer being tracked.
   1658  *
   1659  * @class trackend
   1660  * @extends trackstart
   1661  */
   1662 
   1663  (function(scope) {
   1664    var dispatcher = scope.dispatcher;
   1665    var eventFactory = scope.eventFactory;
   1666    var pointermap = new scope.PointerMap();
   1667    var track = {
   1668      events: [
   1669        'down',
   1670        'move',
   1671        'up',
   1672      ],
   1673      exposes: [
   1674       'trackstart',
   1675       'track',
   1676       'trackx',
   1677       'tracky',
   1678       'trackend'
   1679      ],
   1680      defaultActions: {
   1681        'track': 'none',
   1682        'trackx': 'pan-y',
   1683        'tracky': 'pan-x'
   1684      },
   1685      WIGGLE_THRESHOLD: 4,
   1686      clampDir: function(inDelta) {
   1687        return inDelta > 0 ? 1 : -1;
   1688      },
   1689      calcPositionDelta: function(inA, inB) {
   1690        var x = 0, y = 0;
   1691        if (inA && inB) {
   1692          x = inB.pageX - inA.pageX;
   1693          y = inB.pageY - inA.pageY;
   1694        }
   1695        return {x: x, y: y};
   1696      },
   1697      fireTrack: function(inType, inEvent, inTrackingData) {
   1698        var t = inTrackingData;
   1699        var d = this.calcPositionDelta(t.downEvent, inEvent);
   1700        var dd = this.calcPositionDelta(t.lastMoveEvent, inEvent);
   1701        if (dd.x) {
   1702          t.xDirection = this.clampDir(dd.x);
   1703        } else if (inType === 'trackx') {
   1704          return;
   1705        }
   1706        if (dd.y) {
   1707          t.yDirection = this.clampDir(dd.y);
   1708        } else if (inType === 'tracky') {
   1709          return;
   1710        }
   1711        var gestureProto = {
   1712          bubbles: true,
   1713          cancelable: true,
   1714          trackInfo: t.trackInfo,
   1715          relatedTarget: inEvent.relatedTarget,
   1716          pointerType: inEvent.pointerType,
   1717          pointerId: inEvent.pointerId,
   1718          _source: 'track'
   1719        };
   1720        if (inType !== 'tracky') {
   1721          gestureProto.x = inEvent.x;
   1722          gestureProto.dx = d.x;
   1723          gestureProto.ddx = dd.x;
   1724          gestureProto.clientX = inEvent.clientX;
   1725          gestureProto.pageX = inEvent.pageX;
   1726          gestureProto.screenX = inEvent.screenX;
   1727          gestureProto.xDirection = t.xDirection;
   1728        }
   1729        if (inType !== 'trackx') {
   1730          gestureProto.dy = d.y;
   1731          gestureProto.ddy = dd.y;
   1732          gestureProto.y = inEvent.y;
   1733          gestureProto.clientY = inEvent.clientY;
   1734          gestureProto.pageY = inEvent.pageY;
   1735          gestureProto.screenY = inEvent.screenY;
   1736          gestureProto.yDirection = t.yDirection;
   1737        }
   1738        var e = eventFactory.makeGestureEvent(inType, gestureProto);
   1739        t.downTarget.dispatchEvent(e);
   1740      },
   1741      down: function(inEvent) {
   1742        if (inEvent.isPrimary && (inEvent.pointerType === 'mouse' ? inEvent.buttons === 1 : true)) {
   1743          var p = {
   1744            downEvent: inEvent,
   1745            downTarget: inEvent.target,
   1746            trackInfo: {},
   1747            lastMoveEvent: null,
   1748            xDirection: 0,
   1749            yDirection: 0,
   1750            tracking: false
   1751          };
   1752          pointermap.set(inEvent.pointerId, p);
   1753        }
   1754      },
   1755      move: function(inEvent) {
   1756        var p = pointermap.get(inEvent.pointerId);
   1757        if (p) {
   1758          if (!p.tracking) {
   1759            var d = this.calcPositionDelta(p.downEvent, inEvent);
   1760            var move = d.x * d.x + d.y * d.y;
   1761            // start tracking only if finger moves more than WIGGLE_THRESHOLD
   1762            if (move > this.WIGGLE_THRESHOLD) {
   1763              p.tracking = true;
   1764              p.lastMoveEvent = p.downEvent;
   1765              this.fireTrack('trackstart', inEvent, p);
   1766            }
   1767          }
   1768          if (p.tracking) {
   1769            this.fireTrack('track', inEvent, p);
   1770            this.fireTrack('trackx', inEvent, p);
   1771            this.fireTrack('tracky', inEvent, p);
   1772          }
   1773          p.lastMoveEvent = inEvent;
   1774        }
   1775      },
   1776      up: function(inEvent) {
   1777        var p = pointermap.get(inEvent.pointerId);
   1778        if (p) {
   1779          if (p.tracking) {
   1780            this.fireTrack('trackend', inEvent, p);
   1781          }
   1782          pointermap.delete(inEvent.pointerId);
   1783        }
   1784      }
   1785    };
   1786    dispatcher.registerGesture('track', track);
   1787  })(window.PolymerGestures);
   1788 
   1789 /**
   1790  * This event is fired when a pointer is held down for 200ms.
   1791  *
   1792  * @module PointerGestures
   1793  * @submodule Events
   1794  * @class hold
   1795  */
   1796 /**
   1797  * Type of pointer that made the holding event.
   1798  * @type String
   1799  * @property pointerType
   1800  */
   1801 /**
   1802  * Screen X axis position of the held pointer
   1803  * @type Number
   1804  * @property clientX
   1805  */
   1806 /**
   1807  * Screen Y axis position of the held pointer
   1808  * @type Number
   1809  * @property clientY
   1810  */
   1811 /**
   1812  * Type of pointer that made the holding event.
   1813  * @type String
   1814  * @property pointerType
   1815  */
   1816 /**
   1817  * This event is fired every 200ms while a pointer is held down.
   1818  *
   1819  * @class holdpulse
   1820  * @extends hold
   1821  */
   1822 /**
   1823  * Milliseconds pointer has been held down.
   1824  * @type Number
   1825  * @property holdTime
   1826  */
   1827 /**
   1828  * This event is fired when a held pointer is released or moved.
   1829  *
   1830  * @class release
   1831  */
   1832 
   1833 (function(scope) {
   1834   var dispatcher = scope.dispatcher;
   1835   var eventFactory = scope.eventFactory;
   1836   var hold = {
   1837     // wait at least HOLD_DELAY ms between hold and pulse events
   1838     HOLD_DELAY: 200,
   1839     // pointer can move WIGGLE_THRESHOLD pixels before not counting as a hold
   1840     WIGGLE_THRESHOLD: 16,
   1841     events: [
   1842       'down',
   1843       'move',
   1844       'up',
   1845     ],
   1846     exposes: [
   1847       'hold',
   1848       'holdpulse',
   1849       'release'
   1850     ],
   1851     heldPointer: null,
   1852     holdJob: null,
   1853     pulse: function() {
   1854       var hold = Date.now() - this.heldPointer.timeStamp;
   1855       var type = this.held ? 'holdpulse' : 'hold';
   1856       this.fireHold(type, hold);
   1857       this.held = true;
   1858     },
   1859     cancel: function() {
   1860       clearInterval(this.holdJob);
   1861       if (this.held) {
   1862         this.fireHold('release');
   1863       }
   1864       this.held = false;
   1865       this.heldPointer = null;
   1866       this.target = null;
   1867       this.holdJob = null;
   1868     },
   1869     down: function(inEvent) {
   1870       if (inEvent.isPrimary && !this.heldPointer) {
   1871         this.heldPointer = inEvent;
   1872         this.target = inEvent.target;
   1873         this.holdJob = setInterval(this.pulse.bind(this), this.HOLD_DELAY);
   1874       }
   1875     },
   1876     up: function(inEvent) {
   1877       if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId) {
   1878         this.cancel();
   1879       }
   1880     },
   1881     move: function(inEvent) {
   1882       if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId) {
   1883         var x = inEvent.clientX - this.heldPointer.clientX;
   1884         var y = inEvent.clientY - this.heldPointer.clientY;
   1885         if ((x * x + y * y) > this.WIGGLE_THRESHOLD) {
   1886           this.cancel();
   1887         }
   1888       }
   1889     },
   1890     fireHold: function(inType, inHoldTime) {
   1891       var p = {
   1892         bubbles: true,
   1893         cancelable: true,
   1894         pointerType: this.heldPointer.pointerType,
   1895         pointerId: this.heldPointer.pointerId,
   1896         x: this.heldPointer.clientX,
   1897         y: this.heldPointer.clientY,
   1898         _source: 'hold'
   1899       };
   1900       if (inHoldTime) {
   1901         p.holdTime = inHoldTime;
   1902       }
   1903       var e = eventFactory.makeGestureEvent(inType, p);
   1904       this.target.dispatchEvent(e);
   1905     }
   1906   };
   1907   dispatcher.registerGesture('hold', hold);
   1908 })(window.PolymerGestures);
   1909 
   1910 /**
   1911  * This event is fired when a pointer quickly goes down and up, and is used to
   1912  * denote activation.
   1913  *
   1914  * Any gesture event can prevent the tap event from being created by calling
   1915  * `event.preventTap`.
   1916  *
   1917  * Any pointer event can prevent the tap by setting the `tapPrevented` property
   1918  * on itself.
   1919  *
   1920  * @module PointerGestures
   1921  * @submodule Events
   1922  * @class tap
   1923  */
   1924 /**
   1925  * X axis position of the tap.
   1926  * @property x
   1927  * @type Number
   1928  */
   1929 /**
   1930  * Y axis position of the tap.
   1931  * @property y
   1932  * @type Number
   1933  */
   1934 /**
   1935  * Type of the pointer that made the tap.
   1936  * @property pointerType
   1937  * @type String
   1938  */
   1939 (function(scope) {
   1940   var dispatcher = scope.dispatcher;
   1941   var eventFactory = scope.eventFactory;
   1942   var pointermap = new scope.PointerMap();
   1943   var tap = {
   1944     events: [
   1945       'down',
   1946       'up'
   1947     ],
   1948     exposes: [
   1949       'tap'
   1950     ],
   1951     down: function(inEvent) {
   1952       if (inEvent.isPrimary && !inEvent.tapPrevented) {
   1953         pointermap.set(inEvent.pointerId, {
   1954           target: inEvent.target,
   1955           buttons: inEvent.buttons,
   1956           x: inEvent.clientX,
   1957           y: inEvent.clientY
   1958         });
   1959       }
   1960     },
   1961     shouldTap: function(e, downState) {
   1962       var tap = true;
   1963       if (e.pointerType === 'mouse') {
   1964         // only allow left click to tap for mouse
   1965         tap = (e.buttons ^ 1) && (downState.buttons & 1);
   1966       }
   1967       return tap && !e.tapPrevented;
   1968     },
   1969     up: function(inEvent) {
   1970       var start = pointermap.get(inEvent.pointerId);
   1971       if (start && this.shouldTap(inEvent, start)) {
   1972         // up.relatedTarget is target currently under finger
   1973         var t = scope.targetFinding.LCA(start.target, inEvent.relatedTarget);
   1974         if (t) {
   1975           var e = eventFactory.makeGestureEvent('tap', {
   1976             bubbles: true,
   1977             cancelable: true,
   1978             x: inEvent.clientX,
   1979             y: inEvent.clientY,
   1980             detail: inEvent.detail,
   1981             pointerType: inEvent.pointerType,
   1982             pointerId: inEvent.pointerId,
   1983             altKey: inEvent.altKey,
   1984             ctrlKey: inEvent.ctrlKey,
   1985             metaKey: inEvent.metaKey,
   1986             shiftKey: inEvent.shiftKey,
   1987             _source: 'tap'
   1988           });
   1989           t.dispatchEvent(e);
   1990         }
   1991       }
   1992       pointermap.delete(inEvent.pointerId);
   1993     }
   1994   };
   1995   // patch eventFactory to remove id from tap's pointermap for preventTap calls
   1996   eventFactory.preventTap = function(e) {
   1997     return function() {
   1998       e.tapPrevented = true;
   1999       pointermap.delete(e.pointerId);
   2000     };
   2001   };
   2002   dispatcher.registerGesture('tap', tap);
   2003 })(window.PolymerGestures);
   2004 
   2005 /*
   2006  * Basic strategy: find the farthest apart points, use as diameter of circle
   2007  * react to size change and rotation of the chord
   2008  */
   2009 
   2010 /**
   2011  * @module pointer-gestures
   2012  * @submodule Events
   2013  * @class pinch
   2014  */
   2015 /**
   2016  * Scale of the pinch zoom gesture
   2017  * @property scale
   2018  * @type Number
   2019  */
   2020 /**
   2021  * Center X position of pointers causing pinch
   2022  * @property centerX
   2023  * @type Number
   2024  */
   2025 /**
   2026  * Center Y position of pointers causing pinch
   2027  * @property centerY
   2028  * @type Number
   2029  */
   2030 
   2031 /**
   2032  * @module pointer-gestures
   2033  * @submodule Events
   2034  * @class rotate
   2035  */
   2036 /**
   2037  * Angle (in degrees) of rotation. Measured from starting positions of pointers.
   2038  * @property angle
   2039  * @type Number
   2040  */
   2041 /**
   2042  * Center X position of pointers causing rotation
   2043  * @property centerX
   2044  * @type Number
   2045  */
   2046 /**
   2047  * Center Y position of pointers causing rotation
   2048  * @property centerY
   2049  * @type Number
   2050  */
   2051 (function(scope) {
   2052   var dispatcher = scope.dispatcher;
   2053   var eventFactory = scope.eventFactory;
   2054   var pointermap = new scope.PointerMap();
   2055   var RAD_TO_DEG = 180 / Math.PI;
   2056   var pinch = {
   2057     events: [
   2058       'down',
   2059       'up',
   2060       'move',
   2061       'cancel'
   2062     ],
   2063     exposes: [
   2064       'pinchstart',
   2065       'pinch',
   2066       'pinchend',
   2067       'rotate'
   2068     ],
   2069     defaultActions: {
   2070       'pinch': 'none',
   2071       'rotate': 'none'
   2072     },
   2073     reference: {},
   2074     down: function(inEvent) {
   2075       pointermap.set(inEvent.pointerId, inEvent);
   2076       if (pointermap.pointers() == 2) {
   2077         var points = this.calcChord();
   2078         var angle = this.calcAngle(points);
   2079         this.reference = {
   2080           angle: angle,
   2081           diameter: points.diameter,
   2082           target: scope.targetFinding.LCA(points.a.target, points.b.target)
   2083         };
   2084 
   2085         this.firePinch('pinchstart', points.diameter, points);
   2086       }
   2087     },
   2088     up: function(inEvent) {
   2089       var p = pointermap.get(inEvent.pointerId);
   2090       var num = pointermap.pointers();
   2091       if (p) {
   2092         if (num === 2) {
   2093           // fire 'pinchend' before deleting pointer
   2094           var points = this.calcChord();
   2095           this.firePinch('pinchend', points.diameter, points);
   2096         }
   2097         pointermap.delete(inEvent.pointerId);
   2098       }
   2099     },
   2100     move: function(inEvent) {
   2101       if (pointermap.has(inEvent.pointerId)) {
   2102         pointermap.set(inEvent.pointerId, inEvent);
   2103         if (pointermap.pointers() > 1) {
   2104           this.calcPinchRotate();
   2105         }
   2106       }
   2107     },
   2108     cancel: function(inEvent) {
   2109         this.up(inEvent);
   2110     },
   2111     firePinch: function(type, diameter, points) {
   2112       var zoom = diameter / this.reference.diameter;
   2113       var e = eventFactory.makeGestureEvent(type, {
   2114         bubbles: true,
   2115         cancelable: true,
   2116         scale: zoom,
   2117         centerX: points.center.x,
   2118         centerY: points.center.y,
   2119         _source: 'pinch'
   2120       });
   2121       this.reference.target.dispatchEvent(e);
   2122     },
   2123     fireRotate: function(angle, points) {
   2124       var diff = Math.round((angle - this.reference.angle) % 360);
   2125       var e = eventFactory.makeGestureEvent('rotate', {
   2126         bubbles: true,
   2127         cancelable: true,
   2128         angle: diff,
   2129         centerX: points.center.x,
   2130         centerY: points.center.y,
   2131         _source: 'pinch'
   2132       });
   2133       this.reference.target.dispatchEvent(e);
   2134     },
   2135     calcPinchRotate: function() {
   2136       var points = this.calcChord();
   2137       var diameter = points.diameter;
   2138       var angle = this.calcAngle(points);
   2139       if (diameter != this.reference.diameter) {
   2140         this.firePinch('pinch', diameter, points);
   2141       }
   2142       if (angle != this.reference.angle) {
   2143         this.fireRotate(angle, points);
   2144       }
   2145     },
   2146     calcChord: function() {
   2147       var pointers = [];
   2148       pointermap.forEach(function(p) {
   2149         pointers.push(p);
   2150       });
   2151       var dist = 0;
   2152       // start with at least two pointers
   2153       var points = {a: pointers[0], b: pointers[1]};
   2154       var x, y, d;
   2155       for (var i = 0; i < pointers.length; i++) {
   2156         var a = pointers[i];
   2157         for (var j = i + 1; j < pointers.length; j++) {
   2158           var b = pointers[j];
   2159           x = Math.abs(a.clientX - b.clientX);
   2160           y = Math.abs(a.clientY - b.clientY);
   2161           d = x + y;
   2162           if (d > dist) {
   2163             dist = d;
   2164             points = {a: a, b: b};
   2165           }
   2166         }
   2167       }
   2168       x = Math.abs(points.a.clientX + points.b.clientX) / 2;
   2169       y = Math.abs(points.a.clientY + points.b.clientY) / 2;
   2170       points.center = { x: x, y: y };
   2171       points.diameter = dist;
   2172       return points;
   2173     },
   2174     calcAngle: function(points) {
   2175       var x = points.a.clientX - points.b.clientX;
   2176       var y = points.a.clientY - points.b.clientY;
   2177       return (360 + Math.atan2(y, x) * RAD_TO_DEG) % 360;
   2178     }
   2179   };
   2180   dispatcher.registerGesture('pinch', pinch);
   2181 })(window.PolymerGestures);
   2182 
   2183 (function (global) {
   2184     'use strict';
   2185 
   2186     var Token,
   2187         TokenName,
   2188         Syntax,
   2189         Messages,
   2190         source,
   2191         index,
   2192         length,
   2193         delegate,
   2194         lookahead,
   2195         state;
   2196 
   2197     Token = {
   2198         BooleanLiteral: 1,
   2199         EOF: 2,
   2200         Identifier: 3,
   2201         Keyword: 4,
   2202         NullLiteral: 5,
   2203         NumericLiteral: 6,
   2204         Punctuator: 7,
   2205         StringLiteral: 8
   2206     };
   2207 
   2208     TokenName = {};
   2209     TokenName[Token.BooleanLiteral] = 'Boolean';
   2210     TokenName[Token.EOF] = '<end>';
   2211     TokenName[Token.Identifier] = 'Identifier';
   2212     TokenName[Token.Keyword] = 'Keyword';
   2213     TokenName[Token.NullLiteral] = 'Null';
   2214     TokenName[Token.NumericLiteral] = 'Numeric';
   2215     TokenName[Token.Punctuator] = 'Punctuator';
   2216     TokenName[Token.StringLiteral] = 'String';
   2217 
   2218     Syntax = {
   2219         ArrayExpression: 'ArrayExpression',
   2220         BinaryExpression: 'BinaryExpression',
   2221         CallExpression: 'CallExpression',
   2222         ConditionalExpression: 'ConditionalExpression',
   2223         EmptyStatement: 'EmptyStatement',
   2224         ExpressionStatement: 'ExpressionStatement',
   2225         Identifier: 'Identifier',
   2226         Literal: 'Literal',
   2227         LabeledStatement: 'LabeledStatement',
   2228         LogicalExpression: 'LogicalExpression',
   2229         MemberExpression: 'MemberExpression',
   2230         ObjectExpression: 'ObjectExpression',
   2231         Program: 'Program',
   2232         Property: 'Property',
   2233         ThisExpression: 'ThisExpression',
   2234         UnaryExpression: 'UnaryExpression'
   2235     };
   2236 
   2237     // Error messages should be identical to V8.
   2238     Messages = {
   2239         UnexpectedToken:  'Unexpected token %0',
   2240         UnknownLabel: 'Undefined label \'%0\'',
   2241         Redeclaration: '%0 \'%1\' has already been declared'
   2242     };
   2243 
   2244     // Ensure the condition is true, otherwise throw an error.
   2245     // This is only to have a better contract semantic, i.e. another safety net
   2246     // to catch a logic error. The condition shall be fulfilled in normal case.
   2247     // Do NOT use this to enforce a certain condition on any user input.
   2248 
   2249     function assert(condition, message) {
   2250         if (!condition) {
   2251             throw new Error('ASSERT: ' + message);
   2252         }
   2253     }
   2254 
   2255     function isDecimalDigit(ch) {
   2256         return (ch >= 48 && ch <= 57);   // 0..9
   2257     }
   2258 
   2259 
   2260     // 7.2 White Space
   2261 
   2262     function isWhiteSpace(ch) {
   2263         return (ch === 32) ||  // space
   2264             (ch === 9) ||      // tab
   2265             (ch === 0xB) ||
   2266             (ch === 0xC) ||
   2267             (ch === 0xA0) ||
   2268             (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(String.fromCharCode(ch)) > 0);
   2269     }
   2270 
   2271     // 7.3 Line Terminators
   2272 
   2273     function isLineTerminator(ch) {
   2274         return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029);
   2275     }
   2276 
   2277     // 7.6 Identifier Names and Identifiers
   2278 
   2279     function isIdentifierStart(ch) {
   2280         return (ch === 36) || (ch === 95) ||  // $ (dollar) and _ (underscore)
   2281             (ch >= 65 && ch <= 90) ||         // A..Z
   2282             (ch >= 97 && ch <= 122);          // a..z
   2283     }
   2284 
   2285     function isIdentifierPart(ch) {
   2286         return (ch === 36) || (ch === 95) ||  // $ (dollar) and _ (underscore)
   2287             (ch >= 65 && ch <= 90) ||         // A..Z
   2288             (ch >= 97 && ch <= 122) ||        // a..z
   2289             (ch >= 48 && ch <= 57);           // 0..9
   2290     }
   2291 
   2292     // 7.6.1.1 Keywords
   2293 
   2294     function isKeyword(id) {
   2295         return (id === 'this')
   2296     }
   2297 
   2298     // 7.4 Comments
   2299 
   2300     function skipWhitespace() {
   2301         while (index < length && isWhiteSpace(source.charCodeAt(index))) {
   2302            ++index;
   2303         }
   2304     }
   2305 
   2306     function getIdentifier() {
   2307         var start, ch;
   2308 
   2309         start = index++;
   2310         while (index < length) {
   2311             ch = source.charCodeAt(index);
   2312             if (isIdentifierPart(ch)) {
   2313                 ++index;
   2314             } else {
   2315                 break;
   2316             }
   2317         }
   2318 
   2319         return source.slice(start, index);
   2320     }
   2321 
   2322     function scanIdentifier() {
   2323         var start, id, type;
   2324 
   2325         start = index;
   2326 
   2327         id = getIdentifier();
   2328 
   2329         // There is no keyword or literal with only one character.
   2330         // Thus, it must be an identifier.
   2331         if (id.length === 1) {
   2332             type = Token.Identifier;
   2333         } else if (isKeyword(id)) {
   2334             type = Token.Keyword;
   2335         } else if (id === 'null') {
   2336             type = Token.NullLiteral;
   2337         } else if (id === 'true' || id === 'false') {
   2338             type = Token.BooleanLiteral;
   2339         } else {
   2340             type = Token.Identifier;
   2341         }
   2342 
   2343         return {
   2344             type: type,
   2345             value: id,
   2346             range: [start, index]
   2347         };
   2348     }
   2349 
   2350 
   2351     // 7.7 Punctuators
   2352 
   2353     function scanPunctuator() {
   2354         var start = index,
   2355             code = source.charCodeAt(index),
   2356             code2,
   2357             ch1 = source[index],
   2358             ch2;
   2359 
   2360         switch (code) {
   2361 
   2362         // Check for most common single-character punctuators.
   2363         case 46:   // . dot
   2364         case 40:   // ( open bracket
   2365         case 41:   // ) close bracket
   2366         case 59:   // ; semicolon
   2367         case 44:   // , comma
   2368         case 123:  // { open curly brace
   2369         case 125:  // } close curly brace
   2370         case 91:   // [
   2371         case 93:   // ]
   2372         case 58:   // :
   2373         case 63:   // ?
   2374             ++index;
   2375             return {
   2376                 type: Token.Punctuator,
   2377                 value: String.fromCharCode(code),
   2378                 range: [start, index]
   2379             };
   2380 
   2381         default:
   2382             code2 = source.charCodeAt(index + 1);
   2383 
   2384             // '=' (char #61) marks an assignment or comparison operator.
   2385             if (code2 === 61) {
   2386                 switch (code) {
   2387                 case 37:  // %
   2388                 case 38:  // &
   2389                 case 42:  // *:
   2390                 case 43:  // +
   2391                 case 45:  // -
   2392                 case 47:  // /
   2393                 case 60:  // <
   2394                 case 62:  // >
   2395                 case 124: // |
   2396                     index += 2;
   2397                     return {
   2398                         type: Token.Punctuator,
   2399                         value: String.fromCharCode(code) + String.fromCharCode(code2),
   2400                         range: [start, index]
   2401                     };
   2402 
   2403                 case 33: // !
   2404                 case 61: // =
   2405                     index += 2;
   2406 
   2407                     // !== and ===
   2408                     if (source.charCodeAt(index) === 61) {
   2409                         ++index;
   2410                     }
   2411                     return {
   2412                         type: Token.Punctuator,
   2413                         value: source.slice(start, index),
   2414                         range: [start, index]
   2415                     };
   2416                 default:
   2417                     break;
   2418                 }
   2419             }
   2420             break;
   2421         }
   2422 
   2423         // Peek more characters.
   2424 
   2425         ch2 = source[index + 1];
   2426 
   2427         // Other 2-character punctuators: && ||
   2428 
   2429         if (ch1 === ch2 && ('&|'.indexOf(ch1) >= 0)) {
   2430             index += 2;
   2431             return {
   2432                 type: Token.Punctuator,
   2433                 value: ch1 + ch2,
   2434                 range: [start, index]
   2435             };
   2436         }
   2437 
   2438         if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) {
   2439             ++index;
   2440             return {
   2441                 type: Token.Punctuator,
   2442                 value: ch1,
   2443                 range: [start, index]
   2444             };
   2445         }
   2446 
   2447         throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
   2448     }
   2449 
   2450     // 7.8.3 Numeric Literals
   2451     function scanNumericLiteral() {
   2452         var number, start, ch;
   2453 
   2454         ch = source[index];
   2455         assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'),
   2456             'Numeric literal must start with a decimal digit or a decimal point');
   2457 
   2458         start = index;
   2459         number = '';
   2460         if (ch !== '.') {
   2461             number = source[index++];
   2462             ch = source[index];
   2463 
   2464             // Hex number starts with '0x'.
   2465             // Octal number starts with '0'.
   2466             if (number === '0') {
   2467                 // decimal number starts with '0' such as '09' is illegal.
   2468                 if (ch && isDecimalDigit(ch.charCodeAt(0))) {
   2469                     throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
   2470                 }
   2471             }
   2472 
   2473             while (isDecimalDigit(source.charCodeAt(index))) {
   2474                 number += source[index++];
   2475             }
   2476             ch = source[index];
   2477         }
   2478 
   2479         if (ch === '.') {
   2480             number += source[index++];
   2481             while (isDecimalDigit(source.charCodeAt(index))) {
   2482                 number += source[index++];
   2483             }
   2484             ch = source[index];
   2485         }
   2486 
   2487         if (ch === 'e' || ch === 'E') {
   2488             number += source[index++];
   2489 
   2490             ch = source[index];
   2491             if (ch === '+' || ch === '-') {
   2492                 number += source[index++];
   2493             }
   2494             if (isDecimalDigit(source.charCodeAt(index))) {
   2495                 while (isDecimalDigit(source.charCodeAt(index))) {
   2496                     number += source[index++];
   2497                 }
   2498             } else {
   2499                 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
   2500             }
   2501         }
   2502 
   2503         if (isIdentifierStart(source.charCodeAt(index))) {
   2504             throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
   2505         }
   2506 
   2507         return {
   2508             type: Token.NumericLiteral,
   2509             value: parseFloat(number),
   2510             range: [start, index]
   2511         };
   2512     }
   2513 
   2514     // 7.8.4 String Literals
   2515 
   2516     function scanStringLiteral() {
   2517         var str = '', quote, start, ch, octal = false;
   2518 
   2519         quote = source[index];
   2520         assert((quote === '\'' || quote === '"'),
   2521             'String literal must starts with a quote');
   2522 
   2523         start = index;
   2524         ++index;
   2525 
   2526         while (index < length) {
   2527             ch = source[index++];
   2528 
   2529             if (ch === quote) {
   2530                 quote = '';
   2531                 break;
   2532             } else if (ch === '\\') {
   2533                 ch = source[index++];
   2534                 if (!ch || !isLineTerminator(ch.charCodeAt(0))) {
   2535                     switch (ch) {
   2536                     case 'n':
   2537                         str += '\n';
   2538                         break;
   2539                     case 'r':
   2540                         str += '\r';
   2541                         break;
   2542                     case 't':
   2543                         str += '\t';
   2544                         break;
   2545                     case 'b':
   2546                         str += '\b';
   2547                         break;
   2548                     case 'f':
   2549                         str += '\f';
   2550                         break;
   2551                     case 'v':
   2552                         str += '\x0B';
   2553                         break;
   2554 
   2555                     default:
   2556                         str += ch;
   2557                         break;
   2558                     }
   2559                 } else {
   2560                     if (ch ===  '\r' && source[index] === '\n') {
   2561                         ++index;
   2562                     }
   2563                 }
   2564             } else if (isLineTerminator(ch.charCodeAt(0))) {
   2565                 break;
   2566             } else {
   2567                 str += ch;
   2568             }
   2569         }
   2570 
   2571         if (quote !== '') {
   2572             throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
   2573         }
   2574 
   2575         return {
   2576             type: Token.StringLiteral,
   2577             value: str,
   2578             octal: octal,
   2579             range: [start, index]
   2580         };
   2581     }
   2582 
   2583     function isIdentifierName(token) {
   2584         return token.type === Token.Identifier ||
   2585             token.type === Token.Keyword ||
   2586             token.type === Token.BooleanLiteral ||
   2587             token.type === Token.NullLiteral;
   2588     }
   2589 
   2590     function advance() {
   2591         var ch;
   2592 
   2593         skipWhitespace();
   2594 
   2595         if (index >= length) {
   2596             return {
   2597                 type: Token.EOF,
   2598                 range: [index, index]
   2599             };
   2600         }
   2601 
   2602         ch = source.charCodeAt(index);
   2603 
   2604         // Very common: ( and ) and ;
   2605         if (ch === 40 || ch === 41 || ch === 58) {
   2606             return scanPunctuator();
   2607         }
   2608 
   2609         // String literal starts with single quote (#39) or double quote (#34).
   2610         if (ch === 39 || ch === 34) {
   2611             return scanStringLiteral();
   2612         }
   2613 
   2614         if (isIdentifierStart(ch)) {
   2615             return scanIdentifier();
   2616         }
   2617 
   2618         // Dot (.) char #46 can also start a floating-point number, hence the need
   2619         // to check the next character.
   2620         if (ch === 46) {
   2621             if (isDecimalDigit(source.charCodeAt(index + 1))) {
   2622                 return scanNumericLiteral();
   2623             }
   2624             return scanPunctuator();
   2625         }
   2626 
   2627         if (isDecimalDigit(ch)) {
   2628             return scanNumericLiteral();
   2629         }
   2630 
   2631         return scanPunctuator();
   2632     }
   2633 
   2634     function lex() {
   2635         var token;
   2636 
   2637         token = lookahead;
   2638         index = token.range[1];
   2639 
   2640         lookahead = advance();
   2641 
   2642         index = token.range[1];
   2643 
   2644         return token;
   2645     }
   2646 
   2647     function peek() {
   2648         var pos;
   2649 
   2650         pos = index;
   2651         lookahead = advance();
   2652         index = pos;
   2653     }
   2654 
   2655     // Throw an exception
   2656 
   2657     function throwError(token, messageFormat) {
   2658         var error,
   2659             args = Array.prototype.slice.call(arguments, 2),
   2660             msg = messageFormat.replace(
   2661                 /%(\d)/g,
   2662                 function (whole, index) {
   2663                     assert(index < args.length, 'Message reference must be in range');
   2664                     return args[index];
   2665                 }
   2666             );
   2667 
   2668         error = new Error(msg);
   2669         error.index = index;
   2670         error.description = msg;
   2671         throw error;
   2672     }
   2673 
   2674     // Throw an exception because of the token.
   2675 
   2676     function throwUnexpected(token) {
   2677         throwError(token, Messages.UnexpectedToken, token.value);
   2678     }
   2679 
   2680     // Expect the next token to match the specified punctuator.
   2681     // If not, an exception will be thrown.
   2682 
   2683     function expect(value) {
   2684         var token = lex();
   2685         if (token.type !== Token.Punctuator || token.value !== value) {
   2686             throwUnexpected(token);
   2687         }
   2688     }
   2689 
   2690     // Return true if the next token matches the specified punctuator.
   2691 
   2692     function match(value) {
   2693         return lookahead.type === Token.Punctuator && lookahead.value === value;
   2694     }
   2695 
   2696     // Return true if the next token matches the specified keyword
   2697 
   2698     function matchKeyword(keyword) {
   2699         return lookahead.type === Token.Keyword && lookahead.value === keyword;
   2700     }
   2701 
   2702     function consumeSemicolon() {
   2703         // Catch the very common case first: immediately a semicolon (char #59).
   2704         if (source.charCodeAt(index) === 59) {
   2705             lex();
   2706             return;
   2707         }
   2708 
   2709         skipWhitespace();
   2710 
   2711         if (match(';')) {
   2712             lex();
   2713             return;
   2714         }
   2715 
   2716         if (lookahead.type !== Token.EOF && !match('}')) {
   2717             throwUnexpected(lookahead);
   2718         }
   2719     }
   2720 
   2721     // 11.1.4 Array Initialiser
   2722 
   2723     function parseArrayInitialiser() {
   2724         var elements = [];
   2725 
   2726         expect('[');
   2727 
   2728         while (!match(']')) {
   2729             if (match(',')) {
   2730                 lex();
   2731                 elements.push(null);
   2732             } else {
   2733                 elements.push(parseExpression());
   2734 
   2735                 if (!match(']')) {
   2736                     expect(',');
   2737                 }
   2738             }
   2739         }
   2740 
   2741         expect(']');
   2742 
   2743         return delegate.createArrayExpression(elements);
   2744     }
   2745 
   2746     // 11.1.5 Object Initialiser
   2747 
   2748     function parseObjectPropertyKey() {
   2749         var token;
   2750 
   2751         skipWhitespace();
   2752         token = lex();
   2753 
   2754         // Note: This function is called only from parseObjectProperty(), where
   2755         // EOF and Punctuator tokens are already filtered out.
   2756         if (token.type === Token.StringLiteral || token.type === Token.NumericLiteral) {
   2757             return delegate.createLiteral(token);
   2758         }
   2759 
   2760         return delegate.createIdentifier(token.value);
   2761     }
   2762 
   2763     function parseObjectProperty() {
   2764         var token, key;
   2765 
   2766         token = lookahead;
   2767         skipWhitespace();
   2768 
   2769         if (token.type === Token.EOF || token.type === Token.Punctuator) {
   2770             throwUnexpected(token);
   2771         }
   2772 
   2773         key = parseObjectPropertyKey();
   2774         expect(':');
   2775         return delegate.createProperty('init', key, parseExpression());
   2776     }
   2777 
   2778     function parseObjectInitialiser() {
   2779         var properties = [];
   2780 
   2781         expect('{');
   2782 
   2783         while (!match('}')) {
   2784             properties.push(parseObjectProperty());
   2785 
   2786             if (!match('}')) {
   2787                 expect(',');
   2788             }
   2789         }
   2790 
   2791         expect('}');
   2792 
   2793         return delegate.createObjectExpression(properties);
   2794     }
   2795 
   2796     // 11.1.6 The Grouping Operator
   2797 
   2798     function parseGroupExpression() {
   2799         var expr;
   2800 
   2801         expect('(');
   2802 
   2803         expr = parseExpression();
   2804 
   2805         expect(')');
   2806 
   2807         return expr;
   2808     }
   2809 
   2810 
   2811     // 11.1 Primary Expressions
   2812 
   2813     function parsePrimaryExpression() {
   2814         var type, token, expr;
   2815 
   2816         if (match('(')) {
   2817             return parseGroupExpression();
   2818         }
   2819 
   2820         type = lookahead.type;
   2821 
   2822         if (type === Token.Identifier) {
   2823             expr = delegate.createIdentifier(lex().value);
   2824         } else if (type === Token.StringLiteral || type === Token.NumericLiteral) {
   2825             expr = delegate.createLiteral(lex());
   2826         } else if (type === Token.Keyword) {
   2827             if (matchKeyword('this')) {
   2828                 lex();
   2829                 expr = delegate.createThisExpression();
   2830             }
   2831         } else if (type === Token.BooleanLiteral) {
   2832             token = lex();
   2833             token.value = (token.value === 'true');
   2834             expr = delegate.createLiteral(token);
   2835         } else if (type === Token.NullLiteral) {
   2836             token = lex();
   2837             token.value = null;
   2838             expr = delegate.createLiteral(token);
   2839         } else if (match('[')) {
   2840             expr = parseArrayInitialiser();
   2841         } else if (match('{')) {
   2842             expr = parseObjectInitialiser();
   2843         }
   2844 
   2845         if (expr) {
   2846             return expr;
   2847         }
   2848 
   2849         throwUnexpected(lex());
   2850     }
   2851 
   2852     // 11.2 Left-Hand-Side Expressions
   2853 
   2854     function parseArguments() {
   2855         var args = [];
   2856 
   2857         expect('(');
   2858 
   2859         if (!match(')')) {
   2860             while (index < length) {
   2861                 args.push(parseExpression());
   2862                 if (match(')')) {
   2863                     break;
   2864                 }
   2865                 expect(',');
   2866             }
   2867         }
   2868 
   2869         expect(')');
   2870 
   2871         return args;
   2872     }
   2873 
   2874     function parseNonComputedProperty() {
   2875         var token;
   2876 
   2877         token = lex();
   2878 
   2879         if (!isIdentifierName(token)) {
   2880             throwUnexpected(token);
   2881         }
   2882 
   2883         return delegate.createIdentifier(token.value);
   2884     }
   2885 
   2886     function parseNonComputedMember() {
   2887         expect('.');
   2888 
   2889         return parseNonComputedProperty();
   2890     }
   2891 
   2892     function parseComputedMember() {
   2893         var expr;
   2894 
   2895         expect('[');
   2896 
   2897         expr = parseExpression();
   2898 
   2899         expect(']');
   2900 
   2901         return expr;
   2902     }
   2903 
   2904     function parseLeftHandSideExpression() {
   2905         var expr, args, property;
   2906 
   2907         expr = parsePrimaryExpression();
   2908 
   2909         while (true) {
   2910             if (match('[')) {
   2911                 property = parseComputedMember();
   2912                 expr = delegate.createMemberExpression('[', expr, property);
   2913             } else if (match('.')) {
   2914                 property = parseNonComputedMember();
   2915                 expr = delegate.createMemberExpression('.', expr, property);
   2916             } else if (match('(')) {
   2917                 args = parseArguments();
   2918                 expr = delegate.createCallExpression(expr, args);
   2919             } else {
   2920                 break;
   2921             }
   2922         }
   2923 
   2924         return expr;
   2925     }
   2926 
   2927     // 11.3 Postfix Expressions
   2928 
   2929     var parsePostfixExpression = parseLeftHandSideExpression;
   2930 
   2931     // 11.4 Unary Operators
   2932 
   2933     function parseUnaryExpression() {
   2934         var token, expr;
   2935 
   2936         if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyword) {
   2937             expr = parsePostfixExpression();
   2938         } else if (match('+') || match('-') || match('!')) {
   2939             token = lex();
   2940             expr = parseUnaryExpression();
   2941             expr = delegate.createUnaryExpression(token.value, expr);
   2942         } else if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) {
   2943             throwError({}, Messages.UnexpectedToken);
   2944         } else {
   2945             expr = parsePostfixExpression();
   2946         }
   2947 
   2948         return expr;
   2949     }
   2950 
   2951     function binaryPrecedence(token) {
   2952         var prec = 0;
   2953 
   2954         if (token.type !== Token.Punctuator && token.type !== Token.Keyword) {
   2955             return 0;
   2956         }
   2957 
   2958         switch (token.value) {
   2959         case '||':
   2960             prec = 1;
   2961             break;
   2962 
   2963         case '&&':
   2964             prec = 2;
   2965             break;
   2966 
   2967         case '==':
   2968         case '!=':
   2969         case '===':
   2970         case '!==':
   2971             prec = 6;
   2972             break;
   2973 
   2974         case '<':
   2975         case '>':
   2976         case '<=':
   2977         case '>=':
   2978         case 'instanceof':
   2979             prec = 7;
   2980             break;
   2981 
   2982         case 'in':
   2983             prec = 7;
   2984             break;
   2985 
   2986         case '+':
   2987         case '-':
   2988             prec = 9;
   2989             break;
   2990 
   2991         case '*':
   2992         case '/':
   2993         case '%':
   2994             prec = 11;
   2995             break;
   2996 
   2997         default:
   2998             break;
   2999         }
   3000 
   3001         return prec;
   3002     }
   3003 
   3004     // 11.5 Multiplicative Operators
   3005     // 11.6 Additive Operators
   3006     // 11.7 Bitwise Shift Operators
   3007     // 11.8 Relational Operators
   3008     // 11.9 Equality Operators
   3009     // 11.10 Binary Bitwise Operators
   3010     // 11.11 Binary Logical Operators
   3011 
   3012     function parseBinaryExpression() {
   3013         var expr, token, prec, stack, right, operator, left, i;
   3014 
   3015         left = parseUnaryExpression();
   3016 
   3017         token = lookahead;
   3018         prec = binaryPrecedence(token);
   3019         if (prec === 0) {
   3020             return left;
   3021         }
   3022         token.prec = prec;
   3023         lex();
   3024 
   3025         right = parseUnaryExpression();
   3026 
   3027         stack = [left, token, right];
   3028 
   3029         while ((prec = binaryPrecedence(lookahead)) > 0) {
   3030 
   3031             // Reduce: make a binary expression from the three topmost entries.
   3032             while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) {
   3033                 right = stack.pop();
   3034                 operator = stack.pop().value;
   3035                 left = stack.pop();
   3036                 expr = delegate.createBinaryExpression(operator, left, right);
   3037                 stack.push(expr);
   3038             }
   3039 
   3040             // Shift.
   3041             token = lex();
   3042             token.prec = prec;
   3043             stack.push(token);
   3044             expr = parseUnaryExpression();
   3045             stack.push(expr);
   3046         }
   3047 
   3048         // Final reduce to clean-up the stack.
   3049         i = stack.length - 1;
   3050         expr = stack[i];
   3051         while (i > 1) {
   3052             expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i - 2], expr);
   3053             i -= 2;
   3054         }
   3055 
   3056         return expr;
   3057     }
   3058 
   3059 
   3060     // 11.12 Conditional Operator
   3061 
   3062     function parseConditionalExpression() {
   3063         var expr, consequent, alternate;
   3064 
   3065         expr = parseBinaryExpression();
   3066 
   3067         if (match('?')) {
   3068             lex();
   3069             consequent = parseConditionalExpression();
   3070             expect(':');
   3071             alternate = parseConditionalExpression();
   3072 
   3073             expr = delegate.createConditionalExpression(expr, consequent, alternate);
   3074         }
   3075 
   3076         return expr;
   3077     }
   3078 
   3079     // Simplification since we do not support AssignmentExpression.
   3080     var parseExpression = parseConditionalExpression;
   3081 
   3082     // Polymer Syntax extensions
   3083 
   3084     // Filter ::
   3085     //   Identifier
   3086     //   Identifier "(" ")"
   3087     //   Identifier "(" FilterArguments ")"
   3088 
   3089     function parseFilter() {
   3090         var identifier, args;
   3091 
   3092         identifier = lex();
   3093 
   3094         if (identifier.type !== Token.Identifier) {
   3095             throwUnexpected(identifier);
   3096         }
   3097 
   3098         args = match('(') ? parseArguments() : [];
   3099 
   3100         return delegate.createFilter(identifier.value, args);
   3101     }
   3102 
   3103     // Filters ::
   3104     //   "|" Filter
   3105     //   Filters "|" Filter
   3106 
   3107     function parseFilters() {
   3108         while (match('|')) {
   3109             lex();
   3110             parseFilter();
   3111         }
   3112     }
   3113 
   3114     // TopLevel ::
   3115     //   LabelledExpressions
   3116     //   AsExpression
   3117     //   InExpression
   3118     //   FilterExpression
   3119 
   3120     // AsExpression ::
   3121     //   FilterExpression as Identifier
   3122 
   3123     // InExpression ::
   3124     //   Identifier, Identifier in FilterExpression
   3125     //   Identifier in FilterExpression
   3126 
   3127     // FilterExpression ::
   3128     //   Expression
   3129     //   Expression Filters
   3130 
   3131     function parseTopLevel() {
   3132         skipWhitespace();
   3133         peek();
   3134 
   3135         var expr = parseExpression();
   3136         if (expr) {
   3137             if (lookahead.value === ',' || lookahead.value == 'in' &&
   3138                        expr.type === Syntax.Identifier) {
   3139                 parseInExpression(expr);
   3140             } else {
   3141                 parseFilters();
   3142                 if (lookahead.value === 'as') {
   3143                     parseAsExpression(expr);
   3144                 } else {
   3145                     delegate.createTopLevel(expr);
   3146                 }
   3147             }
   3148         }
   3149 
   3150         if (lookahead.type !== Token.EOF) {
   3151             throwUnexpected(lookahead);
   3152         }
   3153     }
   3154 
   3155     function parseAsExpression(expr) {
   3156         lex();  // as
   3157         var identifier = lex().value;
   3158         delegate.createAsExpression(expr, identifier);
   3159     }
   3160 
   3161     function parseInExpression(identifier) {
   3162         var indexName;
   3163         if (lookahead.value === ',') {
   3164             lex();
   3165             if (lookahead.type !== Token.Identifier)
   3166                 throwUnexpected(lookahead);
   3167             indexName = lex().value;
   3168         }
   3169 
   3170         lex();  // in
   3171         var expr = parseExpression();
   3172         parseFilters();
   3173         delegate.createInExpression(identifier.name, indexName, expr);
   3174     }
   3175 
   3176     function parse(code, inDelegate) {
   3177         delegate = inDelegate;
   3178         source = code;
   3179         index = 0;
   3180         length = source.length;
   3181         lookahead = null;
   3182         state = {
   3183             labelSet: {}
   3184         };
   3185 
   3186         return parseTopLevel();
   3187     }
   3188 
   3189     global.esprima = {
   3190         parse: parse
   3191     };
   3192 })(this);
   3193 
   3194 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
   3195 // This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
   3196 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
   3197 // The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
   3198 // Code distributed by Google as part of the polymer project is also
   3199 // subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
   3200 
   3201 (function (global) {
   3202   'use strict';
   3203 
   3204   function prepareBinding(expressionText, name, node, filterRegistry) {
   3205     var expression;
   3206     try {
   3207       expression = getExpression(expressionText);
   3208       if (expression.scopeIdent &&
   3209           (node.nodeType !== Node.ELEMENT_NODE ||
   3210            node.tagName !== 'TEMPLATE' ||
   3211            (name !== 'bind' && name !== 'repeat'))) {
   3212         throw Error('as and in can only be used within <template bind/repeat>');
   3213       }
   3214     } catch (ex) {
   3215       console.error('Invalid expression syntax: ' + expressionText, ex);
   3216       return;
   3217     }
   3218 
   3219     return function(model, node, oneTime) {
   3220       var binding = expression.getBinding(model, filterRegistry, oneTime);
   3221       if (expression.scopeIdent && binding) {
   3222         node.polymerExpressionScopeIdent_ = expression.scopeIdent;
   3223         if (expression.indexIdent)
   3224           node.polymerExpressionIndexIdent_ = expression.indexIdent;
   3225       }
   3226 
   3227       return binding;
   3228     }
   3229   }
   3230 
   3231   // TODO(rafaelw): Implement simple LRU.
   3232   var expressionParseCache = Object.create(null);
   3233 
   3234   function getExpression(expressionText) {
   3235     var expression = expressionParseCache[expressionText];
   3236     if (!expression) {
   3237       var delegate = new ASTDelegate();
   3238       esprima.parse(expressionText, delegate);
   3239       expression = new Expression(delegate);
   3240       expressionParseCache[expressionText] = expression;
   3241     }
   3242     return expression;
   3243   }
   3244 
   3245   function Literal(value) {
   3246     this.value = value;
   3247     this.valueFn_ = undefined;
   3248   }
   3249 
   3250   Literal.prototype = {
   3251     valueFn: function() {
   3252       if (!this.valueFn_) {
   3253         var value = this.value;
   3254         this.valueFn_ = function() {
   3255           return value;
   3256         }
   3257       }
   3258 
   3259       return this.valueFn_;
   3260     }
   3261   }
   3262 
   3263   function IdentPath(name) {
   3264     this.name = name;
   3265     this.path = Path.get(name);
   3266   }
   3267 
   3268   IdentPath.prototype = {
   3269     valueFn: function() {
   3270       if (!this.valueFn_) {
   3271         var name = this.name;
   3272         var path = this.path;
   3273         this.valueFn_ = function(model, observer) {
   3274           if (observer)
   3275             observer.addPath(model, path);
   3276 
   3277           return path.getValueFrom(model);
   3278         }
   3279       }
   3280 
   3281       return this.valueFn_;
   3282     },
   3283 
   3284     setValue: function(model, newValue) {
   3285       if (this.path.length == 1)
   3286         model = findScope(model, this.path[0]);
   3287 
   3288       return this.path.setValueFrom(model, newValue);
   3289     }
   3290   };
   3291 
   3292   function MemberExpression(object, property, accessor) {
   3293     this.computed = accessor == '[';
   3294 
   3295     this.dynamicDeps = typeof object == 'function' ||
   3296                        object.dynamicDeps ||
   3297                        (this.computed && !(property instanceof Literal));
   3298 
   3299     this.simplePath =
   3300         !this.dynamicDeps &&
   3301         (property instanceof IdentPath || property instanceof Literal) &&
   3302         (object instanceof MemberExpression || object instanceof IdentPath);
   3303 
   3304     this.object = this.simplePath ? object : getFn(object);
   3305     this.property = !this.computed || this.simplePath ?
   3306         property : getFn(property);
   3307   }
   3308 
   3309   MemberExpression.prototype = {
   3310     get fullPath() {
   3311       if (!this.fullPath_) {
   3312 
   3313         var parts = this.object instanceof MemberExpression ?
   3314             this.object.fullPath.slice() : [this.object.name];
   3315         parts.push(this.property instanceof IdentPath ?
   3316             this.property.name : this.property.value);
   3317         this.fullPath_ = Path.get(parts);
   3318       }
   3319 
   3320       return this.fullPath_;
   3321     },
   3322 
   3323     valueFn: function() {
   3324       if (!this.valueFn_) {
   3325         var object = this.object;
   3326 
   3327         if (this.simplePath) {
   3328           var path = this.fullPath;
   3329 
   3330           this.valueFn_ = function(model, observer) {
   3331             if (observer)
   3332               observer.addPath(model, path);
   3333 
   3334             return path.getValueFrom(model);
   3335           };
   3336         } else if (!this.computed) {
   3337           var path = Path.get(this.property.name);
   3338 
   3339           this.valueFn_ = function(model, observer, filterRegistry) {
   3340             var context = object(model, observer, filterRegistry);
   3341 
   3342             if (observer)
   3343               observer.addPath(context, path);
   3344 
   3345             return path.getValueFrom(context);
   3346           }
   3347         } else {
   3348           // Computed property.
   3349           var property = this.property;
   3350 
   3351           this.valueFn_ = function(model, observer, filterRegistry) {
   3352             var context = object(model, observer, filterRegistry);
   3353             var propName = property(model, observer, filterRegistry);
   3354             if (observer)
   3355               observer.addPath(context, [propName]);
   3356 
   3357             return context ? context[propName] : undefined;
   3358           };
   3359         }
   3360       }
   3361       return this.valueFn_;
   3362     },
   3363 
   3364     setValue: function(model, newValue) {
   3365       if (this.simplePath) {
   3366         this.fullPath.setValueFrom(model, newValue);
   3367         return newValue;
   3368       }
   3369 
   3370       var object = this.object(model);
   3371       var propName = this.property instanceof IdentPath ? this.property.name :
   3372           this.property(model);
   3373       return object[propName] = newValue;
   3374     }
   3375   };
   3376 
   3377   function Filter(name, args) {
   3378     this.name = name;
   3379     this.args = [];
   3380     for (var i = 0; i < args.length; i++) {
   3381       this.args[i] = getFn(args[i]);
   3382     }
   3383   }
   3384 
   3385   Filter.prototype = {
   3386     transform: function(model, observer, filterRegistry, toModelDirection,
   3387                         initialArgs) {
   3388       var context = model;
   3389       var fn = context[this.name];
   3390 
   3391       if (!fn) {
   3392         fn = filterRegistry[this.name];
   3393         if (!fn) {
   3394           console.error('Cannot find function or filter: ' + this.name);
   3395           return;
   3396         }
   3397       }
   3398 
   3399       // If toModelDirection is falsey, then the "normal" (dom-bound) direction
   3400       // is used. Otherwise, it looks for a 'toModel' property function on the
   3401       // object.
   3402       if (toModelDirection) {
   3403         fn = fn.toModel;
   3404       } else if (typeof fn.toDOM == 'function') {
   3405         fn = fn.toDOM;
   3406       }
   3407 
   3408       if (typeof fn != 'function') {
   3409         console.error('Cannot find function or filter: ' + this.name);
   3410         return;
   3411       }
   3412 
   3413       var args = initialArgs || [];
   3414       for (var i = 0; i < this.args.length; i++) {
   3415         args.push(getFn(this.args[i])(model, observer, filterRegistry));
   3416       }
   3417 
   3418       return fn.apply(context, args);
   3419     }
   3420   };
   3421 
   3422   function notImplemented() { throw Error('Not Implemented'); }
   3423 
   3424   var unaryOperators = {
   3425     '+': function(v) { return +v; },
   3426     '-': function(v) { return -v; },
   3427     '!': function(v) { return !v; }
   3428   };
   3429 
   3430   var binaryOperators = {
   3431     '+': function(l, r) { return l+r; },
   3432     '-': function(l, r) { return l-r; },
   3433     '*': function(l, r) { return l*r; },
   3434     '/': function(l, r) { return l/r; },
   3435     '%': function(l, r) { return l%r; },
   3436     '<': function(l, r) { return l<r; },
   3437     '>': function(l, r) { return l>r; },
   3438     '<=': function(l, r) { return l<=r; },
   3439     '>=': function(l, r) { return l>=r; },
   3440     '==': function(l, r) { return l==r; },
   3441     '!=': function(l, r) { return l!=r; },
   3442     '===': function(l, r) { return l===r; },
   3443     '!==': function(l, r) { return l!==r; },
   3444     '&&': function(l, r) { return l&&r; },
   3445     '||': function(l, r) { return l||r; },
   3446   };
   3447 
   3448   function getFn(arg) {
   3449     return typeof arg == 'function' ? arg : arg.valueFn();
   3450   }
   3451 
   3452   function ASTDelegate() {
   3453     this.expression = null;
   3454     this.filters = [];
   3455     this.deps = {};
   3456     this.currentPath = undefined;
   3457     this.scopeIdent = undefined;
   3458     this.indexIdent = undefined;
   3459     this.dynamicDeps = false;
   3460   }
   3461 
   3462   ASTDelegate.prototype = {
   3463     createUnaryExpression: function(op, argument) {
   3464       if (!unaryOperators[op])
   3465         throw Error('Disallowed operator: ' + op);
   3466 
   3467       argument = getFn(argument);
   3468 
   3469       return function(model, observer, filterRegistry) {
   3470         return unaryOperators[op](argument(model, observer, filterRegistry));
   3471       };
   3472     },
   3473 
   3474     createBinaryExpression: function(op, left, right) {
   3475       if (!binaryOperators[op])
   3476         throw Error('Disallowed operator: ' + op);
   3477 
   3478       left = getFn(left);
   3479       right = getFn(right);
   3480 
   3481       switch (op) {
   3482         case '||':
   3483           this.dynamicDeps = true;
   3484           return function(model, observer, filterRegistry) {
   3485             return left(model, observer, filterRegistry) ||
   3486                 right(model, observer, filterRegistry);
   3487           };
   3488         case '&&':
   3489           this.dynamicDeps = true;
   3490           return function(model, observer, filterRegistry) {
   3491             return left(model, observer, filterRegistry) &&
   3492                 right(model, observer, filterRegistry);
   3493           };
   3494       }
   3495 
   3496       return function(model, observer, filterRegistry) {
   3497         return binaryOperators[op](left(model, observer, filterRegistry),
   3498                                    right(model, observer, filterRegistry));
   3499       };
   3500     },
   3501 
   3502     createConditionalExpression: function(test, consequent, alternate) {
   3503       test = getFn(test);
   3504       consequent = getFn(consequent);
   3505       alternate = getFn(alternate);
   3506 
   3507       this.dynamicDeps = true;
   3508 
   3509       return function(model, observer, filterRegistry) {
   3510         return test(model, observer, filterRegistry) ?
   3511             consequent(model, observer, filterRegistry) :
   3512             alternate(model, observer, filterRegistry);
   3513       }
   3514     },
   3515 
   3516     createIdentifier: function(name) {
   3517       var ident = new IdentPath(name);
   3518       ident.type = 'Identifier';
   3519       return ident;
   3520     },
   3521 
   3522     createMemberExpression: function(accessor, object, property) {
   3523       var ex = new MemberExpression(object, property, accessor);
   3524       if (ex.dynamicDeps)
   3525         this.dynamicDeps = true;
   3526       return ex;
   3527     },
   3528 
   3529     createCallExpression: function(expression, args) {
   3530       if (!(expression instanceof IdentPath))
   3531         throw Error('Only identifier function invocations are allowed');
   3532 
   3533       var filter = new Filter(expression.name, args);
   3534 
   3535       return function(model, observer, filterRegistry) {
   3536         return filter.transform(model, observer, filterRegistry, false);
   3537       };
   3538     },
   3539 
   3540     createLiteral: function(token) {
   3541       return new Literal(token.value);
   3542     },
   3543 
   3544     createArrayExpression: function(elements) {
   3545       for (var i = 0; i < elements.length; i++)
   3546         elements[i] = getFn(elements[i]);
   3547 
   3548       return function(model, observer, filterRegistry) {
   3549         var arr = []
   3550         for (var i = 0; i < elements.length; i++)
   3551           arr.push(elements[i](model, observer, filterRegistry));
   3552         return arr;
   3553       }
   3554     },
   3555 
   3556     createProperty: function(kind, key, value) {
   3557       return {
   3558         key: key instanceof IdentPath ? key.name : key.value,
   3559         value: value
   3560       };
   3561     },
   3562 
   3563     createObjectExpression: function(properties) {
   3564       for (var i = 0; i < properties.length; i++)
   3565         properties[i].value = getFn(properties[i].value);
   3566 
   3567       return function(model, observer, filterRegistry) {
   3568         var obj = {};
   3569         for (var i = 0; i < properties.length; i++)
   3570           obj[properties[i].key] =
   3571               properties[i].value(model, observer, filterRegistry);
   3572         return obj;
   3573       }
   3574     },
   3575 
   3576     createFilter: function(name, args) {
   3577       this.filters.push(new Filter(name, args));
   3578     },
   3579 
   3580     createAsExpression: function(expression, scopeIdent) {
   3581       this.expression = expression;
   3582       this.scopeIdent = scopeIdent;
   3583     },
   3584 
   3585     createInExpression: function(scopeIdent, indexIdent, expression) {
   3586       this.expression = expression;
   3587       this.scopeIdent = scopeIdent;
   3588       this.indexIdent = indexIdent;
   3589     },
   3590 
   3591     createTopLevel: function(expression) {
   3592       this.expression = expression;
   3593     },
   3594 
   3595     createThisExpression: notImplemented
   3596   }
   3597 
   3598   function ConstantObservable(value) {
   3599     this.value_ = value;
   3600   }
   3601 
   3602   ConstantObservable.prototype = {
   3603     open: function() { return this.value_; },
   3604     discardChanges: function() { return this.value_; },
   3605     deliver: function() {},
   3606     close: function() {},
   3607   }
   3608 
   3609   function Expression(delegate) {
   3610     this.scopeIdent = delegate.scopeIdent;
   3611     this.indexIdent = delegate.indexIdent;
   3612 
   3613     if (!delegate.expression)
   3614       throw Error('No expression found.');
   3615 
   3616     this.expression = delegate.expression;
   3617     getFn(this.expression); // forces enumeration of path dependencies
   3618 
   3619     this.filters = delegate.filters;
   3620     this.dynamicDeps = delegate.dynamicDeps;
   3621   }
   3622 
   3623   Expression.prototype = {
   3624     getBinding: function(model, filterRegistry, oneTime) {
   3625       if (oneTime)
   3626         return this.getValue(model, undefined, filterRegistry);
   3627 
   3628       var observer = new CompoundObserver();
   3629       // captures deps.
   3630       var firstValue = this.getValue(model, observer, filterRegistry);
   3631       var firstTime = true;
   3632       var self = this;
   3633 
   3634       function valueFn() {
   3635         // deps cannot have changed on first value retrieval.
   3636         if (firstTime) {
   3637           firstTime = false;
   3638           return firstValue;
   3639         }
   3640 
   3641         if (self.dynamicDeps)
   3642           observer.startReset();
   3643 
   3644         var value = self.getValue(model,
   3645                                   self.dynamicDeps ? observer : undefined,
   3646                                   filterRegistry);
   3647         if (self.dynamicDeps)
   3648           observer.finishReset();
   3649 
   3650         return value;
   3651       }
   3652 
   3653       function setValueFn(newValue) {
   3654         self.setValue(model, newValue, filterRegistry);
   3655         return newValue;
   3656       }
   3657 
   3658       return new ObserverTransform(observer, valueFn, setValueFn, true);
   3659     },
   3660 
   3661     getValue: function(model, observer, filterRegistry) {
   3662       var value = getFn(this.expression)(model, observer, filterRegistry);
   3663       for (var i = 0; i < this.filters.length; i++) {
   3664         value = this.filters[i].transform(model, observer, filterRegistry,
   3665             false, [value]);
   3666       }
   3667 
   3668       return value;
   3669     },
   3670 
   3671     setValue: function(model, newValue, filterRegistry) {
   3672       var count = this.filters ? this.filters.length : 0;
   3673       while (count-- > 0) {
   3674         newValue = this.filters[count].transform(model, undefined,
   3675             filterRegistry, true, [newValue]);
   3676       }
   3677 
   3678       if (this.expression.setValue)
   3679         return this.expression.setValue(model, newValue);
   3680     }
   3681   }
   3682 
   3683   /**
   3684    * Converts a style property name to a css property name. For example:
   3685    * "WebkitUserSelect" to "-webkit-user-select"
   3686    */
   3687   function convertStylePropertyName(name) {
   3688     return String(name).replace(/[A-Z]/g, function(c) {
   3689       return '-' + c.toLowerCase();
   3690     });
   3691   }
   3692 
   3693   var parentScopeName = '@' + Math.random().toString(36).slice(2);
   3694 
   3695   // Single ident paths must bind directly to the appropriate scope object.
   3696   // I.e. Pushed values in two-bindings need to be assigned to the actual model
   3697   // object.
   3698   function findScope(model, prop) {
   3699     while (model[parentScopeName] &&
   3700            !Object.prototype.hasOwnProperty.call(model, prop)) {
   3701       model = model[parentScopeName];
   3702     }
   3703 
   3704     return model;
   3705   }
   3706 
   3707   function isLiteralExpression(pathString) {
   3708     switch (pathString) {
   3709       case '':
   3710         return false;
   3711 
   3712       case 'false':
   3713       case 'null':
   3714       case 'true':
   3715         return true;
   3716     }
   3717 
   3718     if (!isNaN(Number(pathString)))
   3719       return true;
   3720 
   3721     return false;
   3722   };
   3723 
   3724   function PolymerExpressions() {}
   3725 
   3726   PolymerExpressions.prototype = {
   3727     // "built-in" filters
   3728     styleObject: function(value) {
   3729       var parts = [];
   3730       for (var key in value) {
   3731         parts.push(convertStylePropertyName(key) + ': ' + value[key]);
   3732       }
   3733       return parts.join('; ');
   3734     },
   3735 
   3736     tokenList: function(value) {
   3737       var tokens = [];
   3738       for (var key in value) {
   3739         if (value[key])
   3740           tokens.push(key);
   3741       }
   3742       return tokens.join(' ');
   3743     },
   3744 
   3745     // binding delegate API
   3746     prepareInstancePositionChanged: function(template) {
   3747       var indexIdent = template.polymerExpressionIndexIdent_;
   3748       if (!indexIdent)
   3749         return;
   3750 
   3751       return function(templateInstance, index) {
   3752         templateInstance.model[indexIdent] = index;
   3753       };
   3754     },
   3755 
   3756     prepareBinding: function(pathString, name, node) {
   3757       var path = Path.get(pathString);
   3758 
   3759       if (!isLiteralExpression(pathString) && path.valid) {
   3760         if (path.length == 1) {
   3761           return function(model, node, oneTime) {
   3762             if (oneTime)
   3763               return path.getValueFrom(model);
   3764 
   3765             var scope = findScope(model, path[0]);
   3766             return new PathObserver(scope, path);
   3767           };
   3768         }
   3769         return; // bail out early if pathString is simple path.
   3770       }
   3771 
   3772       return prepareBinding(pathString, name, node, this);
   3773     },
   3774 
   3775     prepareInstanceModel: function(template) {
   3776       var scopeName = template.polymerExpressionScopeIdent_;
   3777       if (!scopeName)
   3778         return;
   3779 
   3780       var parentScope = template.templateInstance ?
   3781           template.templateInstance.model :
   3782           template.model;
   3783 
   3784       var indexName = template.polymerExpressionIndexIdent_;
   3785 
   3786       return function(model) {
   3787         return createScopeObject(parentScope, model, scopeName, indexName);
   3788       };
   3789     }
   3790   };
   3791 
   3792   var createScopeObject = ('__proto__' in {}) ?
   3793     function(parentScope, model, scopeName, indexName) {
   3794       var scope = {};
   3795       scope[scopeName] = model;
   3796       scope[indexName] = undefined;
   3797       scope[parentScopeName] = parentScope;
   3798       scope.__proto__ = parentScope;
   3799       return scope;
   3800     } :
   3801     function(parentScope, model, scopeName, indexName) {
   3802       var scope = Object.create(parentScope);
   3803       Object.defineProperty(scope, scopeName,
   3804           { value: model, configurable: true, writable: true });
   3805       Object.defineProperty(scope, indexName,
   3806           { value: undefined, configurable: true, writable: true });
   3807       Object.defineProperty(scope, parentScopeName,
   3808           { value: parentScope, configurable: true, writable: true });
   3809       return scope;
   3810     };
   3811 
   3812   global.PolymerExpressions = PolymerExpressions;
   3813   PolymerExpressions.getExpression = getExpression;
   3814 })(this);
   3815 
   3816 Polymer = {
   3817   version: '0.5.5'
   3818 };
   3819 
   3820 // TODO(sorvell): this ensures Polymer is an object and not a function
   3821 // Platform is currently defining it as a function to allow for async loading
   3822 // of polymer; once we refine the loading process this likely goes away.
   3823 if (typeof window.Polymer === 'function') {
   3824   Polymer = {};
   3825 }
   3826 
   3827 
   3828 (function(scope) {
   3829 
   3830   function withDependencies(task, depends) {
   3831     depends = depends || [];
   3832     if (!depends.map) {
   3833       depends = [depends];
   3834     }
   3835     return task.apply(this, depends.map(marshal));
   3836   }
   3837 
   3838   function module(name, dependsOrFactory, moduleFactory) {
   3839     var module;
   3840     switch (arguments.length) {
   3841       case 0:
   3842         return;
   3843       case 1:
   3844         module = null;
   3845         break;
   3846       case 2:
   3847         // dependsOrFactory is `factory` in this case
   3848         module = dependsOrFactory.apply(this);
   3849         break;
   3850       default:
   3851         // dependsOrFactory is `depends` in this case
   3852         module = withDependencies(moduleFactory, dependsOrFactory);
   3853         break;
   3854     }
   3855     modules[name] = module;
   3856   };
   3857 
   3858   function marshal(name) {
   3859     return modules[name];
   3860   }
   3861 
   3862   var modules = {};
   3863 
   3864   function using(depends, task) {
   3865     HTMLImports.whenImportsReady(function() {
   3866       withDependencies(task, depends);
   3867     });
   3868   };
   3869 
   3870   // exports
   3871 
   3872   scope.marshal = marshal;
   3873   // `module` confuses commonjs detectors
   3874   scope.modularize = module;
   3875   scope.using = using;
   3876 
   3877 })(window);
   3878 
   3879 /*
   3880 	Build only script.
   3881 
   3882   Ensures scripts needed for basic x-platform compatibility
   3883   will be run when platform.js is not loaded.
   3884  */
   3885 if (!window.WebComponents) {
   3886 
   3887 /*
   3888 	On supported platforms, platform.js is not needed. To retain compatibility
   3889 	with the polyfills, we stub out minimal functionality.
   3890  */
   3891 if (!window.WebComponents) {
   3892 
   3893   WebComponents = {
   3894   	flush: function() {},
   3895     flags: {log: {}}
   3896   };
   3897 
   3898   Platform = WebComponents;
   3899 
   3900   CustomElements = {
   3901   	useNative: true,
   3902     ready: true,
   3903     takeRecords: function() {},
   3904     instanceof: function(obj, base) {
   3905       return obj instanceof base;
   3906     }
   3907   };
   3908 
   3909   HTMLImports = {
   3910   	useNative: true
   3911   };
   3912 
   3913 
   3914   addEventListener('HTMLImportsLoaded', function() {
   3915     document.dispatchEvent(
   3916       new CustomEvent('WebComponentsReady', {bubbles: true})
   3917     );
   3918   });
   3919 
   3920 
   3921   // ShadowDOM
   3922   ShadowDOMPolyfill = null;
   3923   wrap = unwrap = function(n){
   3924     return n;
   3925   };
   3926 
   3927 }
   3928 
   3929 /*
   3930   Create polyfill scope and feature detect native support.
   3931 */
   3932 window.HTMLImports = window.HTMLImports || {flags:{}};
   3933 
   3934 (function(scope) {
   3935 
   3936 /**
   3937   Basic setup and simple module executer. We collect modules and then execute
   3938   the code later, only if it's necessary for polyfilling.
   3939 */
   3940 var IMPORT_LINK_TYPE = 'import';
   3941 var useNative = Boolean(IMPORT_LINK_TYPE in document.createElement('link'));
   3942 
   3943 /**
   3944   Support `currentScript` on all browsers as `document._currentScript.`
   3945 
   3946   NOTE: We cannot polyfill `document.currentScript` because it's not possible
   3947   both to override and maintain the ability to capture the native value.
   3948   Therefore we choose to expose `_currentScript` both when native imports
   3949   and the polyfill are in use.
   3950 */
   3951 // NOTE: ShadowDOMPolyfill intrusion.
   3952 var hasShadowDOMPolyfill = Boolean(window.ShadowDOMPolyfill);
   3953 var wrap = function(node) {
   3954   return hasShadowDOMPolyfill ? ShadowDOMPolyfill.wrapIfNeeded(node) : node;
   3955 };
   3956 var rootDocument = wrap(document);
   3957 
   3958 var currentScriptDescriptor = {
   3959   get: function() {
   3960     var script = HTMLImports.currentScript || document.currentScript ||
   3961         // NOTE: only works when called in synchronously executing code.
   3962         // readyState should check if `loading` but IE10 is
   3963         // interactive when scripts run so we cheat.
   3964         (document.readyState !== 'complete' ?
   3965         document.scripts[document.scripts.length - 1] : null);
   3966     return wrap(script);
   3967   },
   3968   configurable: true
   3969 };
   3970 
   3971 Object.defineProperty(document, '_currentScript', currentScriptDescriptor);
   3972 Object.defineProperty(rootDocument, '_currentScript', currentScriptDescriptor);
   3973 
   3974 /**
   3975   Add support for the `HTMLImportsLoaded` event and the `HTMLImports.whenReady`
   3976   method. This api is necessary because unlike the native implementation,
   3977   script elements do not force imports to resolve. Instead, users should wrap
   3978   code in either an `HTMLImportsLoaded` hander or after load time in an
   3979   `HTMLImports.whenReady(callback)` call.
   3980 
   3981   NOTE: This module also supports these apis under the native implementation.
   3982   Therefore, if this file is loaded, the same code can be used under both
   3983   the polyfill and native implementation.
   3984  */
   3985 
   3986 var isIE = /Trident/.test(navigator.userAgent);
   3987 
   3988 // call a callback when all HTMLImports in the document at call time
   3989 // (or at least document ready) have loaded.
   3990 // 1. ensure the document is in a ready state (has dom), then
   3991 // 2. watch for loading of imports and call callback when done
   3992 function whenReady(callback, doc) {
   3993   doc = doc || rootDocument;
   3994   // if document is loading, wait and try again
   3995   whenDocumentReady(function() {
   3996     watchImportsLoad(callback, doc);
   3997   }, doc);
   3998 }
   3999 
   4000 // call the callback when the document is in a ready state (has dom)
   4001 var requiredReadyState = isIE ? 'complete' : 'interactive';
   4002 var READY_EVENT = 'readystatechange';
   4003 function isDocumentReady(doc) {
   4004   return (doc.readyState === 'complete' ||
   4005       doc.readyState === requiredReadyState);
   4006 }
   4007 
   4008 // call <callback> when we ensure the document is in a ready state
   4009 function whenDocumentReady(callback, doc) {
   4010   if (!isDocumentReady(doc)) {
   4011     var checkReady = function() {
   4012       if (doc.readyState === 'complete' ||
   4013           doc.readyState === requiredReadyState) {
   4014         doc.removeEventListener(READY_EVENT, checkReady);
   4015         whenDocumentReady(callback, doc);
   4016       }
   4017     };
   4018     doc.addEventListener(READY_EVENT, checkReady);
   4019   } else if (callback) {
   4020     callback();
   4021   }
   4022 }
   4023 
   4024 function markTargetLoaded(event) {
   4025   event.target.__loaded = true;
   4026 }
   4027 
   4028 // call <callback> when we ensure all imports have loaded
   4029 function watchImportsLoad(callback, doc) {
   4030   var imports = doc.querySelectorAll('link[rel=import]');
   4031   var loaded = 0, l = imports.length;
   4032   function checkDone(d) {
   4033     if ((loaded == l) && callback) {
   4034        callback();
   4035     }
   4036   }
   4037   function loadedImport(e) {
   4038     markTargetLoaded(e);
   4039     loaded++;
   4040     checkDone();
   4041   }
   4042   if (l) {
   4043     for (var i=0, imp; (i<l) && (imp=imports[i]); i++) {
   4044       if (isImportLoaded(imp)) {
   4045         loadedImport.call(imp, {target: imp});
   4046       } else {
   4047         imp.addEventListener('load', loadedImport);
   4048         imp.addEventListener('error', loadedImport);
   4049       }
   4050     }
   4051   } else {
   4052     checkDone();
   4053   }
   4054 }
   4055 
   4056 // NOTE: test for native imports loading is based on explicitly watching
   4057 // all imports (see below).
   4058 // However, we cannot rely on this entirely without watching the entire document
   4059 // for import links. For perf reasons, currently only head is watched.
   4060 // Instead, we fallback to checking if the import property is available
   4061 // and the document is not itself loading.
   4062 function isImportLoaded(link) {
   4063   return useNative ? link.__loaded ||
   4064       (link.import && link.import.readyState !== 'loading') :
   4065       link.__importParsed;
   4066 }
   4067 
   4068 // TODO(sorvell): Workaround for
   4069 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=25007, should be removed when
   4070 // this bug is addressed.
   4071 // (1) Install a mutation observer to see when HTMLImports have loaded
   4072 // (2) if this script is run during document load it will watch any existing
   4073 // imports for loading.
   4074 //
   4075 // NOTE: The workaround has restricted functionality: (1) it's only compatible
   4076 // with imports that are added to document.head since the mutation observer
   4077 // watches only head for perf reasons, (2) it requires this script
   4078 // to run before any imports have completed loading.
   4079 if (useNative) {
   4080   new MutationObserver(function(mxns) {
   4081     for (var i=0, l=mxns.length, m; (i < l) && (m=mxns[i]); i++) {
   4082       if (m.addedNodes) {
   4083         handleImports(m.addedNodes);
   4084       }
   4085     }
   4086   }).observe(document.head, {childList: true});
   4087 
   4088   function handleImports(nodes) {
   4089     for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) {
   4090       if (isImport(n)) {
   4091         handleImport(n);
   4092       }
   4093     }
   4094   }
   4095 
   4096   function isImport(element) {
   4097     return element.localName === 'link' && element.rel === 'import';
   4098   }
   4099 
   4100   function handleImport(element) {
   4101     var loaded = element.import;
   4102     if (loaded) {
   4103       markTargetLoaded({target: element});
   4104     } else {
   4105       element.addEventListener('load', markTargetLoaded);
   4106       element.addEventListener('error', markTargetLoaded);
   4107     }
   4108   }
   4109 
   4110   // make sure to catch any imports that are in the process of loading
   4111   // when this script is run.
   4112   (function() {
   4113     if (document.readyState === 'loading') {
   4114       var imports = document.querySelectorAll('link[rel=import]');
   4115       for (var i=0, l=imports.length, imp; (i<l) && (imp=imports[i]); i++) {
   4116         handleImport(imp);
   4117       }
   4118     }
   4119   })();
   4120 
   4121 }
   4122 
   4123 // Fire the 'HTMLImportsLoaded' event when imports in document at load time
   4124 // have loaded. This event is required to simulate the script blocking
   4125 // behavior of native imports. A main document script that needs to be sure
   4126 // imports have loaded should wait for this event.
   4127 whenReady(function() {
   4128   HTMLImports.ready = true;
   4129   HTMLImports.readyTime = new Date().getTime();
   4130   rootDocument.dispatchEvent(
   4131     new CustomEvent('HTMLImportsLoaded', {bubbles: true})
   4132   );
   4133 });
   4134 
   4135 // exports
   4136 scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE;
   4137 scope.useNative = useNative;
   4138 scope.rootDocument = rootDocument;
   4139 scope.whenReady = whenReady;
   4140 scope.isIE = isIE;
   4141 
   4142 })(HTMLImports);
   4143 
   4144 (function(scope) {
   4145 
   4146   // TODO(sorvell): It's desireable to provide a default stylesheet
   4147   // that's convenient for styling unresolved elements, but
   4148   // it's cumbersome to have to include this manually in every page.
   4149   // It would make sense to put inside some HTMLImport but
   4150   // the HTMLImports polyfill does not allow loading of stylesheets
   4151   // that block rendering. Therefore this injection is tolerated here.
   4152   var style = document.createElement('style');
   4153   style.textContent = ''
   4154       + 'body {'
   4155       + 'transition: opacity ease-in 0.2s;'
   4156       + ' } \n'
   4157       + 'body[unresolved] {'
   4158       + 'opacity: 0; display: block; overflow: hidden;'
   4159       + ' } \n'
   4160       ;
   4161   var head = document.querySelector('head');
   4162   head.insertBefore(style, head.firstChild);
   4163 
   4164 })(Platform);
   4165 
   4166 /*
   4167 	Build only script.
   4168 
   4169   Ensures scripts needed for basic x-platform compatibility
   4170   will be run when platform.js is not loaded.
   4171  */
   4172 }
   4173 (function(global) {
   4174   'use strict';
   4175 
   4176   var testingExposeCycleCount = global.testingExposeCycleCount;
   4177 
   4178   // Detect and do basic sanity checking on Object/Array.observe.
   4179   function detectObjectObserve() {
   4180     if (typeof Object.observe !== 'function' ||
   4181         typeof Array.observe !== 'function') {
   4182       return false;
   4183     }
   4184 
   4185     var records = [];
   4186 
   4187     function callback(recs) {
   4188       records = recs;
   4189     }
   4190 
   4191     var test = {};
   4192     var arr = [];
   4193     Object.observe(test, callback);
   4194     Array.observe(arr, callback);
   4195     test.id = 1;
   4196     test.id = 2;
   4197     delete test.id;
   4198     arr.push(1, 2);
   4199     arr.length = 0;
   4200 
   4201     Object.deliverChangeRecords(callback);
   4202     if (records.length !== 5)
   4203       return false;
   4204 
   4205     if (records[0].type != 'add' ||
   4206         records[1].type != 'update' ||
   4207         records[2].type != 'delete' ||
   4208         records[3].type != 'splice' ||
   4209         records[4].type != 'splice') {
   4210       return false;
   4211     }
   4212 
   4213     Object.unobserve(test, callback);
   4214     Array.unobserve(arr, callback);
   4215 
   4216     return true;
   4217   }
   4218 
   4219   var hasObserve = detectObjectObserve();
   4220 
   4221   function detectEval() {
   4222     // Don't test for eval if we're running in a Chrome App environment.
   4223     // We check for APIs set that only exist in a Chrome App context.
   4224     if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) {
   4225       return false;
   4226     }
   4227 
   4228     // Firefox OS Apps do not allow eval. This feature detection is very hacky
   4229     // but even if some other platform adds support for this function this code
   4230     // will continue to work.
   4231     if (typeof navigator != 'undefined' && navigator.getDeviceStorage) {
   4232       return false;
   4233     }
   4234 
   4235     try {
   4236       var f = new Function('', 'return true;');
   4237       return f();
   4238     } catch (ex) {
   4239       return false;
   4240     }
   4241   }
   4242 
   4243   var hasEval = detectEval();
   4244 
   4245   function isIndex(s) {
   4246     return +s === s >>> 0 && s !== '';
   4247   }
   4248 
   4249   function toNumber(s) {
   4250     return +s;
   4251   }
   4252 
   4253   function isObject(obj) {
   4254     return obj === Object(obj);
   4255   }
   4256 
   4257   var numberIsNaN = global.Number.isNaN || function(value) {
   4258     return typeof value === 'number' && global.isNaN(value);
   4259   }
   4260 
   4261   function areSameValue(left, right) {
   4262     if (left === right)
   4263       return left !== 0 || 1 / left === 1 / right;
   4264     if (numberIsNaN(left) && numberIsNaN(right))
   4265       return true;
   4266 
   4267     return left !== left && right !== right;
   4268   }
   4269 
   4270   var createObject = ('__proto__' in {}) ?
   4271     function(obj) { return obj; } :
   4272     function(obj) {
   4273       var proto = obj.__proto__;
   4274       if (!proto)
   4275         return obj;
   4276       var newObject = Object.create(proto);
   4277       Object.getOwnPropertyNames(obj).forEach(function(name) {
   4278         Object.defineProperty(newObject, name,
   4279                              Object.getOwnPropertyDescriptor(obj, name));
   4280       });
   4281       return newObject;
   4282     };
   4283 
   4284   var identStart = '[\$_a-zA-Z]';
   4285   var identPart = '[\$_a-zA-Z0-9]';
   4286   var identRegExp = new RegExp('^' + identStart + '+' + identPart + '*' + '$');
   4287 
   4288   function getPathCharType(char) {
   4289     if (char === undefined)
   4290       return 'eof';
   4291 
   4292     var code = char.charCodeAt(0);
   4293 
   4294     switch(code) {
   4295       case 0x5B: // [
   4296       case 0x5D: // ]
   4297       case 0x2E: // .
   4298       case 0x22: // "
   4299       case 0x27: // '
   4300       case 0x30: // 0
   4301         return char;
   4302 
   4303       case 0x5F: // _
   4304       case 0x24: // $
   4305         return 'ident';
   4306 
   4307       case 0x20: // Space
   4308       case 0x09: // Tab
   4309       case 0x0A: // Newline
   4310       case 0x0D: // Return
   4311       case 0xA0:  // No-break space
   4312       case 0xFEFF:  // Byte Order Mark
   4313       case 0x2028:  // Line Separator
   4314       case 0x2029:  // Paragraph Separator
   4315         return 'ws';
   4316     }
   4317 
   4318     // a-z, A-Z
   4319     if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A))
   4320       return 'ident';
   4321 
   4322     // 1-9
   4323     if (0x31 <= code && code <= 0x39)
   4324       return 'number';
   4325 
   4326     return 'else';
   4327   }
   4328 
   4329   var pathStateMachine = {
   4330     'beforePath': {
   4331       'ws': ['beforePath'],
   4332       'ident': ['inIdent', 'append'],
   4333       '[': ['beforeElement'],
   4334       'eof': ['afterPath']
   4335     },
   4336 
   4337     'inPath': {
   4338       'ws': ['inPath'],
   4339       '.': ['beforeIdent'],
   4340       '[': ['beforeElement'],
   4341       'eof': ['afterPath']
   4342     },
   4343 
   4344     'beforeIdent': {
   4345       'ws': ['beforeIdent'],
   4346       'ident': ['inIdent', 'append']
   4347     },
   4348 
   4349     'inIdent': {
   4350       'ident': ['inIdent', 'append'],
   4351       '0': ['inIdent', 'append'],
   4352       'number': ['inIdent', 'append'],
   4353       'ws': ['inPath', 'push'],
   4354       '.': ['beforeIdent', 'push'],
   4355       '[': ['beforeElement', 'push'],
   4356       'eof': ['afterPath', 'push']
   4357     },
   4358 
   4359     'beforeElement': {
   4360       'ws': ['beforeElement'],
   4361       '0': ['afterZero', 'append'],
   4362       'number': ['inIndex', 'append'],
   4363       "'": ['inSingleQuote', 'append', ''],
   4364       '"': ['inDoubleQuote', 'append', '']
   4365     },
   4366 
   4367     'afterZero': {
   4368       'ws': ['afterElement', 'push'],
   4369       ']': ['inPath', 'push']
   4370     },
   4371 
   4372     'inIndex': {
   4373       '0': ['inIndex', 'append'],
   4374       'number': ['inIndex', 'append'],
   4375       'ws': ['afterElement'],
   4376       ']': ['inPath', 'push']
   4377     },
   4378 
   4379     'inSingleQuote': {
   4380       "'": ['afterElement'],
   4381       'eof': ['error'],
   4382       'else': ['inSingleQuote', 'append']
   4383     },
   4384 
   4385     'inDoubleQuote': {
   4386       '"': ['afterElement'],
   4387       'eof': ['error'],
   4388       'else': ['inDoubleQuote', 'append']
   4389     },
   4390 
   4391     'afterElement': {
   4392       'ws': ['afterElement'],
   4393       ']': ['inPath', 'push']
   4394     }
   4395   }
   4396 
   4397   function noop() {}
   4398 
   4399   function parsePath(path) {
   4400     var keys = [];
   4401     var index = -1;
   4402     var c, newChar, key, type, transition, action, typeMap, mode = 'beforePath';
   4403 
   4404     var actions = {
   4405       push: function() {
   4406         if (key === undefined)
   4407           return;
   4408 
   4409         keys.push(key);
   4410         key = undefined;
   4411       },
   4412 
   4413       append: function() {
   4414         if (key === undefined)
   4415           key = newChar
   4416         else
   4417           key += newChar;
   4418       }
   4419     };
   4420 
   4421     function maybeUnescapeQuote() {
   4422       if (index >= path.length)
   4423         return;
   4424 
   4425       var nextChar = path[index + 1];
   4426       if ((mode == 'inSingleQuote' && nextChar == "'") ||
   4427           (mode == 'inDoubleQuote' && nextChar == '"')) {
   4428         index++;
   4429         newChar = nextChar;
   4430         actions.append();
   4431         return true;
   4432       }
   4433     }
   4434 
   4435     while (mode) {
   4436       index++;
   4437       c = path[index];
   4438 
   4439       if (c == '\\' && maybeUnescapeQuote(mode))
   4440         continue;
   4441 
   4442       type = getPathCharType(c);
   4443       typeMap = pathStateMachine[mode];
   4444       transition = typeMap[type] || typeMap['else'] || 'error';
   4445 
   4446       if (transition == 'error')
   4447         return; // parse error;
   4448 
   4449       mode = transition[0];
   4450       action = actions[transition[1]] || noop;
   4451       newChar = transition[2] === undefined ? c : transition[2];
   4452       action();
   4453 
   4454       if (mode === 'afterPath') {
   4455         return keys;
   4456       }
   4457     }
   4458 
   4459     return; // parse error
   4460   }
   4461 
   4462   function isIdent(s) {
   4463     return identRegExp.test(s);
   4464   }
   4465 
   4466   var constructorIsPrivate = {};
   4467 
   4468   function Path(parts, privateToken) {
   4469     if (privateToken !== constructorIsPrivate)
   4470       throw Error('Use Path.get to retrieve path objects');
   4471 
   4472     for (var i = 0; i < parts.length; i++) {
   4473       this.push(String(parts[i]));
   4474     }
   4475 
   4476     if (hasEval && this.length) {
   4477       this.getValueFrom = this.compiledGetValueFromFn();
   4478     }
   4479   }
   4480 
   4481   // TODO(rafaelw): Make simple LRU cache
   4482   var pathCache = {};
   4483 
   4484   function getPath(pathString) {
   4485     if (pathString instanceof Path)
   4486       return pathString;
   4487 
   4488     if (pathString == null || pathString.length == 0)
   4489       pathString = '';
   4490 
   4491     if (typeof pathString != 'string') {
   4492       if (isIndex(pathString.length)) {
   4493         // Constructed with array-like (pre-parsed) keys
   4494         return new Path(pathString, constructorIsPrivate);
   4495       }
   4496 
   4497       pathString = String(pathString);
   4498     }
   4499 
   4500     var path = pathCache[pathString];
   4501     if (path)
   4502       return path;
   4503 
   4504     var parts = parsePath(pathString);
   4505     if (!parts)
   4506       return invalidPath;
   4507 
   4508     var path = new Path(parts, constructorIsPrivate);
   4509     pathCache[pathString] = path;
   4510     return path;
   4511   }
   4512 
   4513   Path.get = getPath;
   4514 
   4515   function formatAccessor(key) {
   4516     if (isIndex(key)) {
   4517       return '[' + key + ']';
   4518     } else {
   4519       return '["' + key.replace(/"/g, '\\"') + '"]';
   4520     }
   4521   }
   4522 
   4523   Path.prototype = createObject({
   4524     __proto__: [],
   4525     valid: true,
   4526 
   4527     toString: function() {
   4528       var pathString = '';
   4529       for (var i = 0; i < this.length; i++) {
   4530         var key = this[i];
   4531         if (isIdent(key)) {
   4532           pathString += i ? '.' + key : key;
   4533         } else {
   4534           pathString += formatAccessor(key);
   4535         }
   4536       }
   4537 
   4538       return pathString;
   4539     },
   4540 
   4541     getValueFrom: function(obj, directObserver) {
   4542       for (var i = 0; i < this.length; i++) {
   4543         if (obj == null)
   4544           return;
   4545         obj = obj[this[i]];
   4546       }
   4547       return obj;
   4548     },
   4549 
   4550     iterateObjects: function(obj, observe) {
   4551       for (var i = 0; i < this.length; i++) {
   4552         if (i)
   4553           obj = obj[this[i - 1]];
   4554         if (!isObject(obj))
   4555           return;
   4556         observe(obj, this[i]);
   4557       }
   4558     },
   4559 
   4560     compiledGetValueFromFn: function() {
   4561       var str = '';
   4562       var pathString = 'obj';
   4563       str += 'if (obj != null';
   4564       var i = 0;
   4565       var key;
   4566       for (; i < (this.length - 1); i++) {
   4567         key = this[i];
   4568         pathString += isIdent(key) ? '.' + key : formatAccessor(key);
   4569         str += ' &&\n     ' + pathString + ' != null';
   4570       }
   4571       str += ')\n';
   4572 
   4573       var key = this[i];
   4574       pathString += isIdent(key) ? '.' + key : formatAccessor(key);
   4575 
   4576       str += '  return ' + pathString + ';\nelse\n  return undefined;';
   4577       return new Function('obj', str);
   4578     },
   4579 
   4580     setValueFrom: function(obj, value) {
   4581       if (!this.length)
   4582         return false;
   4583 
   4584       for (var i = 0; i < this.length - 1; i++) {
   4585         if (!isObject(obj))
   4586           return false;
   4587         obj = obj[this[i]];
   4588       }
   4589 
   4590       if (!isObject(obj))
   4591         return false;
   4592 
   4593       obj[this[i]] = value;
   4594       return true;
   4595     }
   4596   });
   4597 
   4598   var invalidPath = new Path('', constructorIsPrivate);
   4599   invalidPath.valid = false;
   4600   invalidPath.getValueFrom = invalidPath.setValueFrom = function() {};
   4601 
   4602   var MAX_DIRTY_CHECK_CYCLES = 1000;
   4603 
   4604   function dirtyCheck(observer) {
   4605     var cycles = 0;
   4606     while (cycles < MAX_DIRTY_CHECK_CYCLES && observer.check_()) {
   4607       cycles++;
   4608     }
   4609     if (testingExposeCycleCount)
   4610       global.dirtyCheckCycleCount = cycles;
   4611 
   4612     return cycles > 0;
   4613   }
   4614 
   4615   function objectIsEmpty(object) {
   4616     for (var prop in object)
   4617       return false;
   4618     return true;
   4619   }
   4620 
   4621   function diffIsEmpty(diff) {
   4622     return objectIsEmpty(diff.added) &&
   4623            objectIsEmpty(diff.removed) &&
   4624            objectIsEmpty(diff.changed);
   4625   }
   4626 
   4627   function diffObjectFromOldObject(object, oldObject) {
   4628     var added = {};
   4629     var removed = {};
   4630     var changed = {};
   4631 
   4632     for (var prop in oldObject) {
   4633       var newValue = object[prop];
   4634 
   4635       if (newValue !== undefined && newValue === oldObject[prop])
   4636         continue;
   4637 
   4638       if (!(prop in object)) {
   4639         removed[prop] = undefined;
   4640         continue;
   4641       }
   4642 
   4643       if (newValue !== oldObject[prop])
   4644         changed[prop] = newValue;
   4645     }
   4646 
   4647     for (var prop in object) {
   4648       if (prop in oldObject)
   4649         continue;
   4650 
   4651       added[prop] = object[prop];
   4652     }
   4653 
   4654     if (Array.isArray(object) && object.length !== oldObject.length)
   4655       changed.length = object.length;
   4656 
   4657     return {
   4658       added: added,
   4659       removed: removed,
   4660       changed: changed
   4661     };
   4662   }
   4663 
   4664   var eomTasks = [];
   4665   function runEOMTasks() {
   4666     if (!eomTasks.length)
   4667       return false;
   4668 
   4669     for (var i = 0; i < eomTasks.length; i++) {
   4670       eomTasks[i]();
   4671     }
   4672     eomTasks.length = 0;
   4673     return true;
   4674   }
   4675 
   4676   var runEOM = hasObserve ? (function(){
   4677     return function(fn) {
   4678       return Promise.resolve().then(fn);
   4679     }
   4680   })() :
   4681   (function() {
   4682     return function(fn) {
   4683       eomTasks.push(fn);
   4684     };
   4685   })();
   4686 
   4687   var observedObjectCache = [];
   4688 
   4689   function newObservedObject() {
   4690     var observer;
   4691     var object;
   4692     var discardRecords = false;
   4693     var first = true;
   4694 
   4695     function callback(records) {
   4696       if (observer && observer.state_ === OPENED && !discardRecords)
   4697         observer.check_(records);
   4698     }
   4699 
   4700     return {
   4701       open: function(obs) {
   4702         if (observer)
   4703           throw Error('ObservedObject in use');
   4704 
   4705         if (!first)
   4706           Object.deliverChangeRecords(callback);
   4707 
   4708         observer = obs;
   4709         first = false;
   4710       },
   4711       observe: function(obj, arrayObserve) {
   4712         object = obj;
   4713         if (arrayObserve)
   4714           Array.observe(object, callback);
   4715         else
   4716           Object.observe(object, callback);
   4717       },
   4718       deliver: function(discard) {
   4719         discardRecords = discard;
   4720         Object.deliverChangeRecords(callback);
   4721         discardRecords = false;
   4722       },
   4723       close: function() {
   4724         observer = undefined;
   4725         Object.unobserve(object, callback);
   4726         observedObjectCache.push(this);
   4727       }
   4728     };
   4729   }
   4730 
   4731   /*
   4732    * The observedSet abstraction is a perf optimization which reduces the total
   4733    * number of Object.observe observations of a set of objects. The idea is that
   4734    * groups of Observers will have some object dependencies in common and this
   4735    * observed set ensures that each object in the transitive closure of
   4736    * dependencies is only observed once. The observedSet acts as a write barrier
   4737    * such that whenever any change comes through, all Observers are checked for
   4738    * changed values.
   4739    *
   4740    * Note that this optimization is explicitly moving work from setup-time to
   4741    * change-time.
   4742    *
   4743    * TODO(rafaelw): Implement "garbage collection". In order to move work off
   4744    * the critical path, when Observers are closed, their observed objects are
   4745    * not Object.unobserve(d). As a result, it's possible that if the observedSet
   4746    * is kept open, but some Observers have been closed, it could cause "leaks"
   4747    * (prevent otherwise collectable objects from being collected). At some
   4748    * point, we should implement incremental "gc" which keeps a list of
   4749    * observedSets which may need clean-up and does small amounts of cleanup on a
   4750    * timeout until all is clean.
   4751    */
   4752 
   4753   function getObservedObject(observer, object, arrayObserve) {
   4754     var dir = observedObjectCache.pop() || newObservedObject();
   4755     dir.open(observer);
   4756     dir.observe(object, arrayObserve);
   4757     return dir;
   4758   }
   4759 
   4760   var observedSetCache = [];
   4761 
   4762   function newObservedSet() {
   4763     var observerCount = 0;
   4764     var observers = [];
   4765     var objects = [];
   4766     var rootObj;
   4767     var rootObjProps;
   4768 
   4769     function observe(obj, prop) {
   4770       if (!obj)
   4771         return;
   4772 
   4773       if (obj === rootObj)
   4774         rootObjProps[prop] = true;
   4775 
   4776       if (objects.indexOf(obj) < 0) {
   4777         objects.push(obj);
   4778         Object.observe(obj, callback);
   4779       }
   4780 
   4781       observe(Object.getPrototypeOf(obj), prop);
   4782     }
   4783 
   4784     function allRootObjNonObservedProps(recs) {
   4785       for (var i = 0; i < recs.length; i++) {
   4786         var rec = recs[i];
   4787         if (rec.object !== rootObj ||
   4788             rootObjProps[rec.name] ||
   4789             rec.type === 'setPrototype') {
   4790           return false;
   4791         }
   4792       }
   4793       return true;
   4794     }
   4795 
   4796     function callback(recs) {
   4797       if (allRootObjNonObservedProps(recs))
   4798         return;
   4799 
   4800       var observer;
   4801       for (var i = 0; i < observers.length; i++) {
   4802         observer = observers[i];
   4803         if (observer.state_ == OPENED) {
   4804           observer.iterateObjects_(observe);
   4805         }
   4806       }
   4807 
   4808       for (var i = 0; i < observers.length; i++) {
   4809         observer = observers[i];
   4810         if (observer.state_ == OPENED) {
   4811           observer.check_();
   4812         }
   4813       }
   4814     }
   4815 
   4816     var record = {
   4817       objects: objects,
   4818       get rootObject() { return rootObj; },
   4819       set rootObject(value) {
   4820         rootObj = value;
   4821         rootObjProps = {};
   4822       },
   4823       open: function(obs, object) {
   4824         observers.push(obs);
   4825         observerCount++;
   4826         obs.iterateObjects_(observe);
   4827       },
   4828       close: function(obs) {
   4829         observerCount--;
   4830         if (observerCount > 0) {
   4831           return;
   4832         }
   4833 
   4834         for (var i = 0; i < objects.length; i++) {
   4835           Object.unobserve(objects[i], callback);
   4836           Observer.unobservedCount++;
   4837         }
   4838 
   4839         observers.length = 0;
   4840         objects.length = 0;
   4841         rootObj = undefined;
   4842         rootObjProps = undefined;
   4843         observedSetCache.push(this);
   4844         if (lastObservedSet === this)
   4845           lastObservedSet = null;
   4846       },
   4847     };
   4848 
   4849     return record;
   4850   }
   4851 
   4852   var lastObservedSet;
   4853 
   4854   function getObservedSet(observer, obj) {
   4855     if (!lastObservedSet || lastObservedSet.rootObject !== obj) {
   4856       lastObservedSet = observedSetCache.pop() || newObservedSet();
   4857       lastObservedSet.rootObject = obj;
   4858     }
   4859     lastObservedSet.open(observer, obj);
   4860     return lastObservedSet;
   4861   }
   4862 
   4863   var UNOPENED = 0;
   4864   var OPENED = 1;
   4865   var CLOSED = 2;
   4866   var RESETTING = 3;
   4867 
   4868   var nextObserverId = 1;
   4869 
   4870   function Observer() {
   4871     this.state_ = UNOPENED;
   4872     this.callback_ = undefined;
   4873     this.target_ = undefined; // TODO(rafaelw): Should be WeakRef
   4874     this.directObserver_ = undefined;
   4875     this.value_ = undefined;
   4876     this.id_ = nextObserverId++;
   4877   }
   4878 
   4879   Observer.prototype = {
   4880     open: function(callback, target) {
   4881       if (this.state_ != UNOPENED)
   4882         throw Error('Observer has already been opened.');
   4883 
   4884       addToAll(this);
   4885       this.callback_ = callback;
   4886       this.target_ = target;
   4887       this.connect_();
   4888       this.state_ = OPENED;
   4889       return this.value_;
   4890     },
   4891 
   4892     close: function() {
   4893       if (this.state_ != OPENED)
   4894         return;
   4895 
   4896       removeFromAll(this);
   4897       this.disconnect_();
   4898       this.value_ = undefined;
   4899       this.callback_ = undefined;
   4900       this.target_ = undefined;
   4901       this.state_ = CLOSED;
   4902     },
   4903 
   4904     deliver: function() {
   4905       if (this.state_ != OPENED)
   4906         return;
   4907 
   4908       dirtyCheck(this);
   4909     },
   4910 
   4911     report_: function(changes) {
   4912       try {
   4913         this.callback_.apply(this.target_, changes);
   4914       } catch (ex) {
   4915         Observer._errorThrownDuringCallback = true;
   4916         console.error('Exception caught during observer callback: ' +
   4917                        (ex.stack || ex));
   4918       }
   4919     },
   4920 
   4921     discardChanges: function() {
   4922       this.check_(undefined, true);
   4923       return this.value_;
   4924     }
   4925   }
   4926 
   4927   var collectObservers = !hasObserve;
   4928   var allObservers;
   4929   Observer._allObserversCount = 0;
   4930 
   4931   if (collectObservers) {
   4932     allObservers = [];
   4933   }
   4934 
   4935   function addToAll(observer) {
   4936     Observer._allObserversCount++;
   4937     if (!collectObservers)
   4938       return;
   4939 
   4940     allObservers.push(observer);
   4941   }
   4942 
   4943   function removeFromAll(observer) {
   4944     Observer._allObserversCount--;
   4945   }
   4946 
   4947   var runningMicrotaskCheckpoint = false;
   4948 
   4949   global.Platform = global.Platform || {};
   4950 
   4951   global.Platform.performMicrotaskCheckpoint = function() {
   4952     if (runningMicrotaskCheckpoint)
   4953       return;
   4954 
   4955     if (!collectObservers)
   4956       return;
   4957 
   4958     runningMicrotaskCheckpoint = true;
   4959 
   4960     var cycles = 0;
   4961     var anyChanged, toCheck;
   4962 
   4963     do {
   4964       cycles++;
   4965       toCheck = allObservers;
   4966       allObservers = [];
   4967       anyChanged = false;
   4968 
   4969       for (var i = 0; i < toCheck.length; i++) {
   4970         var observer = toCheck[i];
   4971         if (observer.state_ != OPENED)
   4972           continue;
   4973 
   4974         if (observer.check_())
   4975           anyChanged = true;
   4976 
   4977         allObservers.push(observer);
   4978       }
   4979       if (runEOMTasks())
   4980         anyChanged = true;
   4981     } while (cycles < MAX_DIRTY_CHECK_CYCLES && anyChanged);
   4982 
   4983     if (testingExposeCycleCount)
   4984       global.dirtyCheckCycleCount = cycles;
   4985 
   4986     runningMicrotaskCheckpoint = false;
   4987   };
   4988 
   4989   if (collectObservers) {
   4990     global.Platform.clearObservers = function() {
   4991       allObservers = [];
   4992     };
   4993   }
   4994 
   4995   function ObjectObserver(object) {
   4996     Observer.call(this);
   4997     this.value_ = object;
   4998     this.oldObject_ = undefined;
   4999   }
   5000 
   5001   ObjectObserver.prototype = createObject({
   5002     __proto__: Observer.prototype,
   5003 
   5004     arrayObserve: false,
   5005 
   5006     connect_: function(callback, target) {
   5007       if (hasObserve) {
   5008         this.directObserver_ = getObservedObject(this, this.value_,
   5009                                                  this.arrayObserve);
   5010       } else {
   5011         this.oldObject_ = this.copyObject(this.value_);
   5012       }
   5013 
   5014     },
   5015 
   5016     copyObject: function(object) {
   5017       var copy = Array.isArray(object) ? [] : {};
   5018       for (var prop in object) {
   5019         copy[prop] = object[prop];
   5020       };
   5021       if (Array.isArray(object))
   5022         copy.length = object.length;
   5023       return copy;
   5024     },
   5025 
   5026     check_: function(changeRecords, skipChanges) {
   5027       var diff;
   5028       var oldValues;
   5029       if (hasObserve) {
   5030         if (!changeRecords)
   5031           return false;
   5032 
   5033         oldValues = {};
   5034         diff = diffObjectFromChangeRecords(this.value_, changeRecords,
   5035                                            oldValues);
   5036       } else {
   5037         oldValues = this.oldObject_;
   5038         diff = diffObjectFromOldObject(this.value_, this.oldObject_);
   5039       }
   5040 
   5041       if (diffIsEmpty(diff))
   5042         return false;
   5043 
   5044       if (!hasObserve)
   5045         this.oldObject_ = this.copyObject(this.value_);
   5046 
   5047       this.report_([
   5048         diff.added || {},
   5049         diff.removed || {},
   5050         diff.changed || {},
   5051         function(property) {
   5052           return oldValues[property];
   5053         }
   5054       ]);
   5055 
   5056       return true;
   5057     },
   5058 
   5059     disconnect_: function() {
   5060       if (hasObserve) {
   5061         this.directObserver_.close();
   5062         this.directObserver_ = undefined;
   5063       } else {
   5064         this.oldObject_ = undefined;
   5065       }
   5066     },
   5067 
   5068     deliver: function() {
   5069       if (this.state_ != OPENED)
   5070         return;
   5071 
   5072       if (hasObserve)
   5073         this.directObserver_.deliver(false);
   5074       else
   5075         dirtyCheck(this);
   5076     },
   5077 
   5078     discardChanges: function() {
   5079       if (this.directObserver_)
   5080         this.directObserver_.deliver(true);
   5081       else
   5082         this.oldObject_ = this.copyObject(this.value_);
   5083 
   5084       return this.value_;
   5085     }
   5086   });
   5087 
   5088   function ArrayObserver(array) {
   5089     if (!Array.isArray(array))
   5090       throw Error('Provided object is not an Array');
   5091     ObjectObserver.call(this, array);
   5092   }
   5093 
   5094   ArrayObserver.prototype = createObject({
   5095 
   5096     __proto__: ObjectObserver.prototype,
   5097 
   5098     arrayObserve: true,
   5099 
   5100     copyObject: function(arr) {
   5101       return arr.slice();
   5102     },
   5103 
   5104     check_: function(changeRecords) {
   5105       var splices;
   5106       if (hasObserve) {
   5107         if (!changeRecords)
   5108           return false;
   5109         splices = projectArraySplices(this.value_, changeRecords);
   5110       } else {
   5111         splices = calcSplices(this.value_, 0, this.value_.length,
   5112                               this.oldObject_, 0, this.oldObject_.length);
   5113       }
   5114 
   5115       if (!splices || !splices.length)
   5116         return false;
   5117 
   5118       if (!hasObserve)
   5119         this.oldObject_ = this.copyObject(this.value_);
   5120 
   5121       this.report_([splices]);
   5122       return true;
   5123     }
   5124   });
   5125 
   5126   ArrayObserver.applySplices = function(previous, current, splices) {
   5127     splices.forEach(function(splice) {
   5128       var spliceArgs = [splice.index, splice.removed.length];
   5129       var addIndex = splice.index;
   5130       while (addIndex < splice.index + splice.addedCount) {
   5131         spliceArgs.push(current[addIndex]);
   5132         addIndex++;
   5133       }
   5134 
   5135       Array.prototype.splice.apply(previous, spliceArgs);
   5136     });
   5137   };
   5138 
   5139   function PathObserver(object, path) {
   5140     Observer.call(this);
   5141 
   5142     this.object_ = object;
   5143     this.path_ = getPath(path);
   5144     this.directObserver_ = undefined;
   5145   }
   5146 
   5147   PathObserver.prototype = createObject({
   5148     __proto__: Observer.prototype,
   5149 
   5150     get path() {
   5151       return this.path_;
   5152     },
   5153 
   5154     connect_: function() {
   5155       if (hasObserve)
   5156         this.directObserver_ = getObservedSet(this, this.object_);
   5157 
   5158       this.check_(undefined, true);
   5159     },
   5160 
   5161     disconnect_: function() {
   5162       this.value_ = undefined;
   5163 
   5164       if (this.directObserver_) {
   5165         this.directObserver_.close(this);
   5166         this.directObserver_ = undefined;
   5167       }
   5168     },
   5169 
   5170     iterateObjects_: function(observe) {
   5171       this.path_.iterateObjects(this.object_, observe);
   5172     },
   5173 
   5174     check_: function(changeRecords, skipChanges) {
   5175       var oldValue = this.value_;
   5176       this.value_ = this.path_.getValueFrom(this.object_);
   5177       if (skipChanges || areSameValue(this.value_, oldValue))
   5178         return false;
   5179 
   5180       this.report_([this.value_, oldValue, this]);
   5181       return true;
   5182     },
   5183 
   5184     setValue: function(newValue) {
   5185       if (this.path_)
   5186         this.path_.setValueFrom(this.object_, newValue);
   5187     }
   5188   });
   5189 
   5190   function CompoundObserver(reportChangesOnOpen) {
   5191     Observer.call(this);
   5192 
   5193     this.reportChangesOnOpen_ = reportChangesOnOpen;
   5194     this.value_ = [];
   5195     this.directObserver_ = undefined;
   5196     this.observed_ = [];
   5197   }
   5198 
   5199   var observerSentinel = {};
   5200 
   5201   CompoundObserver.prototype = createObject({
   5202     __proto__: Observer.prototype,
   5203 
   5204     connect_: function() {
   5205       if (hasObserve) {
   5206         var object;
   5207         var needsDirectObserver = false;
   5208         for (var i = 0; i < this.observed_.length; i += 2) {
   5209           object = this.observed_[i]
   5210           if (object !== observerSentinel) {
   5211             needsDirectObserver = true;
   5212             break;
   5213           }
   5214         }
   5215 
   5216         if (needsDirectObserver)
   5217           this.directObserver_ = getObservedSet(this, object);
   5218       }
   5219 
   5220       this.check_(undefined, !this.reportChangesOnOpen_);
   5221     },
   5222 
   5223     disconnect_: function() {
   5224       for (var i = 0; i < this.observed_.length; i += 2) {
   5225         if (this.observed_[i] === observerSentinel)
   5226           this.observed_[i + 1].close();
   5227       }
   5228       this.observed_.length = 0;
   5229       this.value_.length = 0;
   5230 
   5231       if (this.directObserver_) {
   5232         this.directObserver_.close(this);
   5233         this.directObserver_ = undefined;
   5234       }
   5235     },
   5236 
   5237     addPath: function(object, path) {
   5238       if (this.state_ != UNOPENED && this.state_ != RESETTING)
   5239         throw Error('Cannot add paths once started.');
   5240 
   5241       var path = getPath(path);
   5242       this.observed_.push(object, path);
   5243       if (!this.reportChangesOnOpen_)
   5244         return;
   5245       var index = this.observed_.length / 2 - 1;
   5246       this.value_[index] = path.getValueFrom(object);
   5247     },
   5248 
   5249     addObserver: function(observer) {
   5250       if (this.state_ != UNOPENED && this.state_ != RESETTING)
   5251         throw Error('Cannot add observers once started.');
   5252 
   5253       this.observed_.push(observerSentinel, observer);
   5254       if (!this.reportChangesOnOpen_)
   5255         return;
   5256       var index = this.observed_.length / 2 - 1;
   5257       this.value_[index] = observer.open(this.deliver, this);
   5258     },
   5259 
   5260     startReset: function() {
   5261       if (this.state_ != OPENED)
   5262         throw Error('Can only reset while open');
   5263 
   5264       this.state_ = RESETTING;
   5265       this.disconnect_();
   5266     },
   5267 
   5268     finishReset: function() {
   5269       if (this.state_ != RESETTING)
   5270         throw Error('Can only finishReset after startReset');
   5271       this.state_ = OPENED;
   5272       this.connect_();
   5273 
   5274       return this.value_;
   5275     },
   5276 
   5277     iterateObjects_: function(observe) {
   5278       var object;
   5279       for (var i = 0; i < this.observed_.length; i += 2) {
   5280         object = this.observed_[i]
   5281         if (object !== observerSentinel)
   5282           this.observed_[i + 1].iterateObjects(object, observe)
   5283       }
   5284     },
   5285 
   5286     check_: function(changeRecords, skipChanges) {
   5287       var oldValues;
   5288       for (var i = 0; i < this.observed_.length; i += 2) {
   5289         var object = this.observed_[i];
   5290         var path = this.observed_[i+1];
   5291         var value;
   5292         if (object === observerSentinel) {
   5293           var observable = path;
   5294           value = this.state_ === UNOPENED ?
   5295               observable.open(this.deliver, this) :
   5296               observable.discardChanges();
   5297         } else {
   5298           value = path.getValueFrom(object);
   5299         }
   5300 
   5301         if (skipChanges) {
   5302           this.value_[i / 2] = value;
   5303           continue;
   5304         }
   5305 
   5306         if (areSameValue(value, this.value_[i / 2]))
   5307           continue;
   5308 
   5309         oldValues = oldValues || [];
   5310         oldValues[i / 2] = this.value_[i / 2];
   5311         this.value_[i / 2] = value;
   5312       }
   5313 
   5314       if (!oldValues)
   5315         return false;
   5316 
   5317       // TODO(rafaelw): Having observed_ as the third callback arg here is
   5318       // pretty lame API. Fix.
   5319       this.report_([this.value_, oldValues, this.observed_]);
   5320       return true;
   5321     }
   5322   });
   5323 
   5324   function identFn(value) { return value; }
   5325 
   5326   function ObserverTransform(observable, getValueFn, setValueFn,
   5327                              dontPassThroughSet) {
   5328     this.callback_ = undefined;
   5329     this.target_ = undefined;
   5330     this.value_ = undefined;
   5331     this.observable_ = observable;
   5332     this.getValueFn_ = getValueFn || identFn;
   5333     this.setValueFn_ = setValueFn || identFn;
   5334     // TODO(rafaelw): This is a temporary hack. PolymerExpressions needs this
   5335     // at the moment because of a bug in it's dependency tracking.
   5336     this.dontPassThroughSet_ = dontPassThroughSet;
   5337   }
   5338 
   5339   ObserverTransform.prototype = {
   5340     open: function(callback, target) {
   5341       this.callback_ = callback;
   5342       this.target_ = target;
   5343       this.value_ =
   5344           this.getValueFn_(this.observable_.open(this.observedCallback_, this));
   5345       return this.value_;
   5346     },
   5347 
   5348     observedCallback_: function(value) {
   5349       value = this.getValueFn_(value);
   5350       if (areSameValue(value, this.value_))
   5351         return;
   5352       var oldValue = this.value_;
   5353       this.value_ = value;
   5354       this.callback_.call(this.target_, this.value_, oldValue);
   5355     },
   5356 
   5357     discardChanges: function() {
   5358       this.value_ = this.getValueFn_(this.observable_.discardChanges());
   5359       return this.value_;
   5360     },
   5361 
   5362     deliver: function() {
   5363       return this.observable_.deliver();
   5364     },
   5365 
   5366     setValue: function(value) {
   5367       value = this.setValueFn_(value);
   5368       if (!this.dontPassThroughSet_ && this.observable_.setValue)
   5369         return this.observable_.setValue(value);
   5370     },
   5371 
   5372     close: function() {
   5373       if (this.observable_)
   5374         this.observable_.close();
   5375       this.callback_ = undefined;
   5376       this.target_ = undefined;
   5377       this.observable_ = undefined;
   5378       this.value_ = undefined;
   5379       this.getValueFn_ = undefined;
   5380       this.setValueFn_ = undefined;
   5381     }
   5382   }
   5383 
   5384   var expectedRecordTypes = {
   5385     add: true,
   5386     update: true,
   5387     delete: true
   5388   };
   5389 
   5390   function diffObjectFromChangeRecords(object, changeRecords, oldValues) {
   5391     var added = {};
   5392     var removed = {};
   5393 
   5394     for (var i = 0; i < changeRecords.length; i++) {
   5395       var record = changeRecords[i];
   5396       if (!expectedRecordTypes[record.type]) {
   5397         console.error('Unknown changeRecord type: ' + record.type);
   5398         console.error(record);
   5399         continue;
   5400       }
   5401 
   5402       if (!(record.name in oldValues))
   5403         oldValues[record.name] = record.oldValue;
   5404 
   5405       if (record.type == 'update')
   5406         continue;
   5407 
   5408       if (record.type == 'add') {
   5409         if (record.name in removed)
   5410           delete removed[record.name];
   5411         else
   5412           added[record.name] = true;
   5413 
   5414         continue;
   5415       }
   5416 
   5417       // type = 'delete'
   5418       if (record.name in added) {
   5419         delete added[record.name];
   5420         delete oldValues[record.name];
   5421       } else {
   5422         removed[record.name] = true;
   5423       }
   5424     }
   5425 
   5426     for (var prop in added)
   5427       added[prop] = object[prop];
   5428 
   5429     for (var prop in removed)
   5430       removed[prop] = undefined;
   5431 
   5432     var changed = {};
   5433     for (var prop in oldValues) {
   5434       if (prop in added || prop in removed)
   5435         continue;
   5436 
   5437       var newValue = object[prop];
   5438       if (oldValues[prop] !== newValue)
   5439         changed[prop] = newValue;
   5440     }
   5441 
   5442     return {
   5443       added: added,
   5444       removed: removed,
   5445       changed: changed
   5446     };
   5447   }
   5448 
   5449   function newSplice(index, removed, addedCount) {
   5450     return {
   5451       index: index,
   5452       removed: removed,
   5453       addedCount: addedCount
   5454     };
   5455   }
   5456 
   5457   var EDIT_LEAVE = 0;
   5458   var EDIT_UPDATE = 1;
   5459   var EDIT_ADD = 2;
   5460   var EDIT_DELETE = 3;
   5461 
   5462   function ArraySplice() {}
   5463 
   5464   ArraySplice.prototype = {
   5465 
   5466     // Note: This function is *based* on the computation of the Levenshtein
   5467     // "edit" distance. The one change is that "updates" are treated as two
   5468     // edits - not one. With Array splices, an update is really a delete
   5469     // followed by an add. By retaining this, we optimize for "keeping" the
   5470     // maximum array items in the original array. For example:
   5471     //
   5472     //   'xxxx123' -> '123yyyy'
   5473     //
   5474     // With 1-edit updates, the shortest path would be just to update all seven
   5475     // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
   5476     // leaves the substring '123' intact.
   5477     calcEditDistances: function(current, currentStart, currentEnd,
   5478                                 old, oldStart, oldEnd) {
   5479       // "Deletion" columns
   5480       var rowCount = oldEnd - oldStart + 1;
   5481       var columnCount = currentEnd - currentStart + 1;
   5482       var distances = new Array(rowCount);
   5483 
   5484       // "Addition" rows. Initialize null column.
   5485       for (var i = 0; i < rowCount; i++) {
   5486         distances[i] = new Array(columnCount);
   5487         distances[i][0] = i;
   5488       }
   5489 
   5490       // Initialize null row
   5491       for (var j = 0; j < columnCount; j++)
   5492         distances[0][j] = j;
   5493 
   5494       for (var i = 1; i < rowCount; i++) {
   5495         for (var j = 1; j < columnCount; j++) {
   5496           if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1]))
   5497             distances[i][j] = distances[i - 1][j - 1];
   5498           else {
   5499             var north = distances[i - 1][j] + 1;
   5500             var west = distances[i][j - 1] + 1;
   5501             distances[i][j] = north < west ? north : west;
   5502           }
   5503         }
   5504       }
   5505 
   5506       return distances;
   5507     },
   5508 
   5509     // This starts at the final weight, and walks "backward" by finding
   5510     // the minimum previous weight recursively until the origin of the weight
   5511     // matrix.
   5512     spliceOperationsFromEditDistances: function(distances) {
   5513       var i = distances.length - 1;
   5514       var j = distances[0].length - 1;
   5515       var current = distances[i][j];
   5516       var edits = [];
   5517       while (i > 0 || j > 0) {
   5518         if (i == 0) {
   5519           edits.push(EDIT_ADD);
   5520           j--;
   5521           continue;
   5522         }
   5523         if (j == 0) {
   5524           edits.push(EDIT_DELETE);
   5525           i--;
   5526           continue;
   5527         }
   5528         var northWest = distances[i - 1][j - 1];
   5529         var west = distances[i - 1][j];
   5530         var north = distances[i][j - 1];
   5531 
   5532         var min;
   5533         if (west < north)
   5534           min = west < northWest ? west : northWest;
   5535         else
   5536           min = north < northWest ? north : northWest;
   5537 
   5538         if (min == northWest) {
   5539           if (northWest == current) {
   5540             edits.push(EDIT_LEAVE);
   5541           } else {
   5542             edits.push(EDIT_UPDATE);
   5543             current = northWest;
   5544           }
   5545           i--;
   5546           j--;
   5547         } else if (min == west) {
   5548           edits.push(EDIT_DELETE);
   5549           i--;
   5550           current = west;
   5551         } else {
   5552           edits.push(EDIT_ADD);
   5553           j--;
   5554           current = north;
   5555         }
   5556       }
   5557 
   5558       edits.reverse();
   5559       return edits;
   5560     },
   5561 
   5562     /**
   5563      * Splice Projection functions:
   5564      *
   5565      * A splice map is a representation of how a previous array of items
   5566      * was transformed into a new array of items. Conceptually it is a list of
   5567      * tuples of
   5568      *
   5569      *   <index, removed, addedCount>
   5570      *
   5571      * which are kept in ascending index order of. The tuple represents that at
   5572      * the |index|, |removed| sequence of items were removed, and counting forward
   5573      * from |index|, |addedCount| items were added.
   5574      */
   5575 
   5576     /**
   5577      * Lacking individual splice mutation information, the minimal set of
   5578      * splices can be synthesized given the previous state and final state of an
   5579      * array. The basic approach is to calculate the edit distance matrix and
   5580      * choose the shortest path through it.
   5581      *
   5582      * Complexity: O(l * p)
   5583      *   l: The length of the current array
   5584      *   p: The length of the old array
   5585      */
   5586     calcSplices: function(current, currentStart, currentEnd,
   5587                           old, oldStart, oldEnd) {
   5588       var prefixCount = 0;
   5589       var suffixCount = 0;
   5590 
   5591       var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart);
   5592       if (currentStart == 0 && oldStart == 0)
   5593         prefixCount = this.sharedPrefix(current, old, minLength);
   5594 
   5595       if (currentEnd == current.length && oldEnd == old.length)
   5596         suffixCount = this.sharedSuffix(current, old, minLength - prefixCount);
   5597 
   5598       currentStart += prefixCount;
   5599       oldStart += prefixCount;
   5600       currentEnd -= suffixCount;
   5601       oldEnd -= suffixCount;
   5602 
   5603       if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0)
   5604         return [];
   5605 
   5606       if (currentStart == currentEnd) {
   5607         var splice = newSplice(currentStart, [], 0);
   5608         while (oldStart < oldEnd)
   5609           splice.removed.push(old[oldStart++]);
   5610 
   5611         return [ splice ];
   5612       } else if (oldStart == oldEnd)
   5613         return [ newSplice(currentStart, [], currentEnd - currentStart) ];
   5614 
   5615       var ops = this.spliceOperationsFromEditDistances(
   5616           this.calcEditDistances(current, currentStart, currentEnd,
   5617                                  old, oldStart, oldEnd));
   5618 
   5619       var splice = undefined;
   5620       var splices = [];
   5621       var index = currentStart;
   5622       var oldIndex = oldStart;
   5623       for (var i = 0; i < ops.length; i++) {
   5624         switch(ops[i]) {
   5625           case EDIT_LEAVE:
   5626             if (splice) {
   5627               splices.push(splice);
   5628               splice = undefined;
   5629             }
   5630 
   5631             index++;
   5632             oldIndex++;
   5633             break;
   5634           case EDIT_UPDATE:
   5635             if (!splice)
   5636               splice = newSplice(index, [], 0);
   5637 
   5638             splice.addedCount++;
   5639             index++;
   5640 
   5641             splice.removed.push(old[oldIndex]);
   5642             oldIndex++;
   5643             break;
   5644           case EDIT_ADD:
   5645             if (!splice)
   5646               splice = newSplice(index, [], 0);
   5647 
   5648             splice.addedCount++;
   5649             index++;
   5650             break;
   5651           case EDIT_DELETE:
   5652             if (!splice)
   5653               splice = newSplice(index, [], 0);
   5654 
   5655             splice.removed.push(old[oldIndex]);
   5656             oldIndex++;
   5657             break;
   5658         }
   5659       }
   5660 
   5661       if (splice) {
   5662         splices.push(splice);
   5663       }
   5664       return splices;
   5665     },
   5666 
   5667     sharedPrefix: function(current, old, searchLength) {
   5668       for (var i = 0; i < searchLength; i++)
   5669         if (!this.equals(current[i], old[i]))
   5670           return i;
   5671       return searchLength;
   5672     },
   5673 
   5674     sharedSuffix: function(current, old, searchLength) {
   5675       var index1 = current.length;
   5676       var index2 = old.length;
   5677       var count = 0;
   5678       while (count < searchLength && this.equals(current[--index1], old[--index2]))
   5679         count++;
   5680 
   5681       return count;
   5682     },
   5683 
   5684     calculateSplices: function(current, previous) {
   5685       return this.calcSplices(current, 0, current.length, previous, 0,
   5686                               previous.length);
   5687     },
   5688 
   5689     equals: function(currentValue, previousValue) {
   5690       return currentValue === previousValue;
   5691     }
   5692   };
   5693 
   5694   var arraySplice = new ArraySplice();
   5695 
   5696   function calcSplices(current, currentStart, currentEnd,
   5697                        old, oldStart, oldEnd) {
   5698     return arraySplice.calcSplices(current, currentStart, currentEnd,
   5699                                    old, oldStart, oldEnd);
   5700   }
   5701 
   5702   function intersect(start1, end1, start2, end2) {
   5703     // Disjoint
   5704     if (end1 < start2 || end2 < start1)
   5705       return -1;
   5706 
   5707     // Adjacent
   5708     if (end1 == start2 || end2 == start1)
   5709       return 0;
   5710 
   5711     // Non-zero intersect, span1 first
   5712     if (start1 < start2) {
   5713       if (end1 < end2)
   5714         return end1 - start2; // Overlap
   5715       else
   5716         return end2 - start2; // Contained
   5717     } else {
   5718       // Non-zero intersect, span2 first
   5719       if (end2 < end1)
   5720         return end2 - start1; // Overlap
   5721       else
   5722         return end1 - start1; // Contained
   5723     }
   5724   }
   5725 
   5726   function mergeSplice(splices, index, removed, addedCount) {
   5727 
   5728     var splice = newSplice(index, removed, addedCount);
   5729 
   5730     var inserted = false;
   5731     var insertionOffset = 0;
   5732 
   5733     for (var i = 0; i < splices.length; i++) {
   5734       var current = splices[i];
   5735       current.index += insertionOffset;
   5736 
   5737       if (inserted)
   5738         continue;
   5739 
   5740       var intersectCount = intersect(splice.index,
   5741                                      splice.index + splice.removed.length,
   5742                                      current.index,
   5743                                      current.index + current.addedCount);
   5744 
   5745       if (intersectCount >= 0) {
   5746         // Merge the two splices
   5747 
   5748         splices.splice(i, 1);
   5749         i--;
   5750 
   5751         insertionOffset -= current.addedCount - current.removed.length;
   5752 
   5753         splice.addedCount += current.addedCount - intersectCount;
   5754         var deleteCount = splice.removed.length +
   5755                           current.removed.length - intersectCount;
   5756 
   5757         if (!splice.addedCount && !deleteCount) {
   5758           // merged splice is a noop. discard.
   5759           inserted = true;
   5760         } else {
   5761           var removed = current.removed;
   5762 
   5763           if (splice.index < current.index) {
   5764             // some prefix of splice.removed is prepended to current.removed.
   5765             var prepend = splice.removed.slice(0, current.index - splice.index);
   5766             Array.prototype.push.apply(prepend, removed);
   5767             removed = prepend;
   5768           }
   5769 
   5770           if (splice.index + splice.removed.length > current.index + current.addedCount) {
   5771             // some suffix of splice.removed is appended to current.removed.
   5772             var append = splice.removed.slice(current.index + current.addedCount - splice.index);
   5773             Array.prototype.push.apply(removed, append);
   5774           }
   5775 
   5776           splice.removed = removed;
   5777           if (current.index < splice.index) {
   5778             splice.index = current.index;
   5779           }
   5780         }
   5781       } else if (splice.index < current.index) {
   5782         // Insert splice here.
   5783 
   5784         inserted = true;
   5785 
   5786         splices.splice(i, 0, splice);
   5787         i++;
   5788 
   5789         var offset = splice.addedCount - splice.removed.length
   5790         current.index += offset;
   5791         insertionOffset += offset;
   5792       }
   5793     }
   5794 
   5795     if (!inserted)
   5796       splices.push(splice);
   5797   }
   5798 
   5799   function createInitialSplices(array, changeRecords) {
   5800     var splices = [];
   5801 
   5802     for (var i = 0; i < changeRecords.length; i++) {
   5803       var record = changeRecords[i];
   5804       switch(record.type) {
   5805         case 'splice':
   5806           mergeSplice(splices, record.index, record.removed.slice(), record.addedCount);
   5807           break;
   5808         case 'add':
   5809         case 'update':
   5810         case 'delete':
   5811           if (!isIndex(record.name))
   5812             continue;
   5813           var index = toNumber(record.name);
   5814           if (index < 0)
   5815             continue;
   5816           mergeSplice(splices, index, [record.oldValue], 1);
   5817           break;
   5818         default:
   5819           console.error('Unexpected record type: ' + JSON.stringify(record));
   5820           break;
   5821       }
   5822     }
   5823 
   5824     return splices;
   5825   }
   5826 
   5827   function projectArraySplices(array, changeRecords) {
   5828     var splices = [];
   5829 
   5830     createInitialSplices(array, changeRecords).forEach(function(splice) {
   5831       if (splice.addedCount == 1 && splice.removed.length == 1) {
   5832         if (splice.removed[0] !== array[splice.index])
   5833           splices.push(splice);
   5834 
   5835         return
   5836       };
   5837 
   5838       splices = splices.concat(calcSplices(array, splice.index, splice.index + splice.addedCount,
   5839                                            splice.removed, 0, splice.removed.length));
   5840     });
   5841 
   5842     return splices;
   5843   }
   5844 
   5845   // Export the observe-js object for **Node.js**, with backwards-compatibility
   5846   // for the old `require()` API. Also ensure `exports` is not a DOM Element.
   5847   // If we're in the browser, export as a global object.
   5848 
   5849   var expose = global;
   5850 
   5851   if (typeof exports !== 'undefined' && !exports.nodeType) {
   5852     if (typeof module !== 'undefined' && module.exports) {
   5853       exports = module.exports;
   5854     }
   5855     expose = exports;
   5856   }
   5857 
   5858   expose.Observer = Observer;
   5859   expose.Observer.runEOM_ = runEOM;
   5860   expose.Observer.observerSentinel_ = observerSentinel; // for testing.
   5861   expose.Observer.hasObjectObserve = hasObserve;
   5862   expose.ArrayObserver = ArrayObserver;
   5863   expose.ArrayObserver.calculateSplices = function(current, previous) {
   5864     return arraySplice.calculateSplices(current, previous);
   5865   };
   5866 
   5867   expose.ArraySplice = ArraySplice;
   5868   expose.ObjectObserver = ObjectObserver;
   5869   expose.PathObserver = PathObserver;
   5870   expose.CompoundObserver = CompoundObserver;
   5871   expose.Path = Path;
   5872   expose.ObserverTransform = ObserverTransform;
   5873 
   5874 })(typeof global !== 'undefined' && global && typeof module !== 'undefined' && module ? global : this || window);
   5875 
   5876 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
   5877 // This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
   5878 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
   5879 // The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
   5880 // Code distributed by Google as part of the polymer project is also
   5881 // subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
   5882 
   5883 (function(global) {
   5884   'use strict';
   5885 
   5886   var filter = Array.prototype.filter.call.bind(Array.prototype.filter);
   5887 
   5888   function getTreeScope(node) {
   5889     while (node.parentNode) {
   5890       node = node.parentNode;
   5891     }
   5892 
   5893     return typeof node.getElementById === 'function' ? node : null;
   5894   }
   5895 
   5896   Node.prototype.bind = function(name, observable) {
   5897     console.error('Unhandled binding to Node: ', this, name, observable);
   5898   };
   5899 
   5900   Node.prototype.bindFinished = function() {};
   5901 
   5902   function updateBindings(node, name, binding) {
   5903     var bindings = node.bindings_;
   5904     if (!bindings)
   5905       bindings = node.bindings_ = {};
   5906 
   5907     if (bindings[name])
   5908       binding[name].close();
   5909 
   5910     return bindings[name] = binding;
   5911   }
   5912 
   5913   function returnBinding(node, name, binding) {
   5914     return binding;
   5915   }
   5916 
   5917   function sanitizeValue(value) {
   5918     return value == null ? '' : value;
   5919   }
   5920 
   5921   function updateText(node, value) {
   5922     node.data = sanitizeValue(value);
   5923   }
   5924 
   5925   function textBinding(node) {
   5926     return function(value) {
   5927       return updateText(node, value);
   5928     };
   5929   }
   5930 
   5931   var maybeUpdateBindings = returnBinding;
   5932 
   5933   Object.defineProperty(Platform, 'enableBindingsReflection', {
   5934     get: function() {
   5935       return maybeUpdateBindings === updateBindings;
   5936     },
   5937     set: function(enable) {
   5938       maybeUpdateBindings = enable ? updateBindings : returnBinding;
   5939       return enable;
   5940     },
   5941     configurable: true
   5942   });
   5943 
   5944   Text.prototype.bind = function(name, value, oneTime) {
   5945     if (name !== 'textContent')
   5946       return Node.prototype.bind.call(this, name, value, oneTime);
   5947 
   5948     if (oneTime)
   5949       return updateText(this, value);
   5950 
   5951     var observable = value;
   5952     updateText(this, observable.open(textBinding(this)));
   5953     return maybeUpdateBindings(this, name, observable);
   5954   }
   5955 
   5956   function updateAttribute(el, name, conditional, value) {
   5957     if (conditional) {
   5958       if (value)
   5959         el.setAttribute(name, '');
   5960       else
   5961         el.removeAttribute(name);
   5962       return;
   5963     }
   5964 
   5965     el.setAttribute(name, sanitizeValue(value));
   5966   }
   5967 
   5968   function attributeBinding(el, name, conditional) {
   5969     return function(value) {
   5970       updateAttribute(el, name, conditional, value);
   5971     };
   5972   }
   5973 
   5974   Element.prototype.bind = function(name, value, oneTime) {
   5975     var conditional = name[name.length - 1] == '?';
   5976     if (conditional) {
   5977       this.removeAttribute(name);
   5978       name = name.slice(0, -1);
   5979     }
   5980 
   5981     if (oneTime)
   5982       return updateAttribute(this, name, conditional, value);
   5983 
   5984 
   5985     var observable = value;
   5986     updateAttribute(this, name, conditional,
   5987         observable.open(attributeBinding(this, name, conditional)));
   5988 
   5989     return maybeUpdateBindings(this, name, observable);
   5990   };
   5991 
   5992   var checkboxEventType;
   5993   (function() {
   5994     // Attempt to feature-detect which event (change or click) is fired first
   5995     // for checkboxes.
   5996     var div = document.createElement('div');
   5997     var checkbox = div.appendChild(document.createElement('input'));
   5998     checkbox.setAttribute('type', 'checkbox');
   5999     var first;
   6000     var count = 0;
   6001     checkbox.addEventListener('click', function(e) {
   6002       count++;
   6003       first = first || 'click';
   6004     });
   6005     checkbox.addEventListener('change', function() {
   6006       count++;
   6007       first = first || 'change';
   6008     });
   6009 
   6010     var event = document.createEvent('MouseEvent');
   6011     event.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false,
   6012         false, false, false, 0, null);
   6013     checkbox.dispatchEvent(event);
   6014     // WebKit/Blink don't fire the change event if the element is outside the
   6015     // document, so assume 'change' for that case.
   6016     checkboxEventType = count == 1 ? 'change' : first;
   6017   })();
   6018 
   6019   function getEventForInputType(element) {
   6020     switch (element.type) {
   6021       case 'checkbox':
   6022         return checkboxEventType;
   6023       case 'radio':
   6024       case 'select-multiple':
   6025       case 'select-one':
   6026         return 'change';
   6027       case 'range':
   6028         if (/Trident|MSIE/.test(navigator.userAgent))
   6029           return 'change';
   6030       default:
   6031         return 'input';
   6032     }
   6033   }
   6034 
   6035   function updateInput(input, property, value, santizeFn) {
   6036     input[property] = (santizeFn || sanitizeValue)(value);
   6037   }
   6038 
   6039   function inputBinding(input, property, santizeFn) {
   6040     return function(value) {
   6041       return updateInput(input, property, value, santizeFn);
   6042     }
   6043   }
   6044 
   6045   function noop() {}
   6046 
   6047   function bindInputEvent(input, property, observable, postEventFn) {
   6048     var eventType = getEventForInputType(input);
   6049 
   6050     function eventHandler() {
   6051       var isNum = property == 'value' && input.type == 'number';
   6052       observable.setValue(isNum ? input.valueAsNumber : input[property]);
   6053       observable.discardChanges();
   6054       (postEventFn || noop)(input);
   6055       Platform.performMicrotaskCheckpoint();
   6056     }
   6057     input.addEventListener(eventType, eventHandler);
   6058 
   6059     return {
   6060       close: function() {
   6061         input.removeEventListener(eventType, eventHandler);
   6062         observable.close();
   6063       },
   6064 
   6065       observable_: observable
   6066     }
   6067   }
   6068 
   6069   function booleanSanitize(value) {
   6070     return Boolean(value);
   6071   }
   6072 
   6073   // |element| is assumed to be an HTMLInputElement with |type| == 'radio'.
   6074   // Returns an array containing all radio buttons other than |element| that
   6075   // have the same |name|, either in the form that |element| belongs to or,
   6076   // if no form, in the document tree to which |element| belongs.
   6077   //
   6078   // This implementation is based upon the HTML spec definition of a
   6079   // "radio button group":
   6080   //   http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.html#radio-button-group
   6081   //
   6082   function getAssociatedRadioButtons(element) {
   6083     if (element.form) {
   6084       return filter(element.form.elements, function(el) {
   6085         return el != element &&
   6086             el.tagName == 'INPUT' &&
   6087             el.type == 'radio' &&
   6088             el.name == element.name;
   6089       });
   6090     } else {
   6091       var treeScope = getTreeScope(element);
   6092       if (!treeScope)
   6093         return [];
   6094       var radios = treeScope.querySelectorAll(
   6095           'input[type="radio"][name="' + element.name + '"]');
   6096       return filter(radios, function(el) {
   6097         return el != element && !el.form;
   6098       });
   6099     }
   6100   }
   6101 
   6102   function checkedPostEvent(input) {
   6103     // Only the radio button that is getting checked gets an event. We
   6104     // therefore find all the associated radio buttons and update their
   6105     // check binding manually.
   6106     if (input.tagName === 'INPUT' &&
   6107         input.type === 'radio') {
   6108       getAssociatedRadioButtons(input).forEach(function(radio) {
   6109         var checkedBinding = radio.bindings_.checked;
   6110         if (checkedBinding) {
   6111           // Set the value directly to avoid an infinite call stack.
   6112           checkedBinding.observable_.setValue(false);
   6113         }
   6114       });
   6115     }
   6116   }
   6117 
   6118   HTMLInputElement.prototype.bind = function(name, value, oneTime) {
   6119     if (name !== 'value' && name !== 'checked')
   6120       return HTMLElement.prototype.bind.call(this, name, value, oneTime);
   6121 
   6122     this.removeAttribute(name);
   6123     var sanitizeFn = name == 'checked' ? booleanSanitize : sanitizeValue;
   6124     var postEventFn = name == 'checked' ? checkedPostEvent : noop;
   6125 
   6126     if (oneTime)
   6127       return updateInput(this, name, value, sanitizeFn);
   6128 
   6129 
   6130     var observable = value;
   6131     var binding = bindInputEvent(this, name, observable, postEventFn);
   6132     updateInput(this, name,
   6133                 observable.open(inputBinding(this, name, sanitizeFn)),
   6134                 sanitizeFn);
   6135 
   6136     // Checkboxes may need to update bindings of other checkboxes.
   6137     return updateBindings(this, name, binding);
   6138   }
   6139 
   6140   HTMLTextAreaElement.prototype.bind = function(name, value, oneTime) {
   6141     if (name !== 'value')
   6142       return HTMLElement.prototype.bind.call(this, name, value, oneTime);
   6143 
   6144     this.removeAttribute('value');
   6145 
   6146     if (oneTime)
   6147       return updateInput(this, 'value', value);
   6148 
   6149     var observable = value;
   6150     var binding = bindInputEvent(this, 'value', observable);
   6151     updateInput(this, 'value',
   6152                 observable.open(inputBinding(this, 'value', sanitizeValue)));
   6153     return maybeUpdateBindings(this, name, binding);
   6154   }
   6155 
   6156   function updateOption(option, value) {
   6157     var parentNode = option.parentNode;;
   6158     var select;
   6159     var selectBinding;
   6160     var oldValue;
   6161     if (parentNode instanceof HTMLSelectElement &&
   6162         parentNode.bindings_ &&
   6163         parentNode.bindings_.value) {
   6164       select = parentNode;
   6165       selectBinding = select.bindings_.value;
   6166       oldValue = select.value;
   6167     }
   6168 
   6169     option.value = sanitizeValue(value);
   6170 
   6171     if (select && select.value != oldValue) {
   6172       selectBinding.observable_.setValue(select.value);
   6173       selectBinding.observable_.discardChanges();
   6174       Platform.performMicrotaskCheckpoint();
   6175     }
   6176   }
   6177 
   6178   function optionBinding(option) {
   6179     return function(value) {
   6180       updateOption(option, value);
   6181     }
   6182   }
   6183 
   6184   HTMLOptionElement.prototype.bind = function(name, value, oneTime) {
   6185     if (name !== 'value')
   6186       return HTMLElement.prototype.bind.call(this, name, value, oneTime);
   6187 
   6188     this.removeAttribute('value');
   6189 
   6190     if (oneTime)
   6191       return updateOption(this, value);
   6192 
   6193     var observable = value;
   6194     var binding = bindInputEvent(this, 'value', observable);
   6195     updateOption(this, observable.open(optionBinding(this)));
   6196     return maybeUpdateBindings(this, name, binding);
   6197   }
   6198 
   6199   HTMLSelectElement.prototype.bind = function(name, value, oneTime) {
   6200     if (name === 'selectedindex')
   6201       name = 'selectedIndex';
   6202 
   6203     if (name !== 'selectedIndex' && name !== 'value')
   6204       return HTMLElement.prototype.bind.call(this, name, value, oneTime);
   6205 
   6206     this.removeAttribute(name);
   6207 
   6208     if (oneTime)
   6209       return updateInput(this, name, value);
   6210 
   6211     var observable = value;
   6212     var binding = bindInputEvent(this, name, observable);
   6213     updateInput(this, name,
   6214                 observable.open(inputBinding(this, name)));
   6215 
   6216     // Option update events may need to access select bindings.
   6217     return updateBindings(this, name, binding);
   6218   }
   6219 })(this);
   6220 
   6221 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
   6222 // This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
   6223 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
   6224 // The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
   6225 // Code distributed by Google as part of the polymer project is also
   6226 // subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
   6227 
   6228 (function(global) {
   6229   'use strict';
   6230 
   6231   function assert(v) {
   6232     if (!v)
   6233       throw new Error('Assertion failed');
   6234   }
   6235 
   6236   var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
   6237 
   6238   function getFragmentRoot(node) {
   6239     var p;
   6240     while (p = node.parentNode) {
   6241       node = p;
   6242     }
   6243 
   6244     return node;
   6245   }
   6246 
   6247   function searchRefId(node, id) {
   6248     if (!id)
   6249       return;
   6250 
   6251     var ref;
   6252     var selector = '#' + id;
   6253     while (!ref) {
   6254       node = getFragmentRoot(node);
   6255 
   6256       if (node.protoContent_)
   6257         ref = node.protoContent_.querySelector(selector);
   6258       else if (node.getElementById)
   6259         ref = node.getElementById(id);
   6260 
   6261       if (ref || !node.templateCreator_)
   6262         break
   6263 
   6264       node = node.templateCreator_;
   6265     }
   6266 
   6267     return ref;
   6268   }
   6269 
   6270   function getInstanceRoot(node) {
   6271     while (node.parentNode) {
   6272       node = node.parentNode;
   6273     }
   6274     return node.templateCreator_ ? node : null;
   6275   }
   6276 
   6277   var Map;
   6278   if (global.Map && typeof global.Map.prototype.forEach === 'function') {
   6279     Map = global.Map;
   6280   } else {
   6281     Map = function() {
   6282       this.keys = [];
   6283       this.values = [];
   6284     };
   6285 
   6286     Map.prototype = {
   6287       set: function(key, value) {
   6288         var index = this.keys.indexOf(key);
   6289         if (index < 0) {
   6290           this.keys.push(key);
   6291           this.values.push(value);
   6292         } else {
   6293           this.values[index] = value;
   6294         }
   6295       },
   6296 
   6297       get: function(key) {
   6298         var index = this.keys.indexOf(key);
   6299         if (index < 0)
   6300           return;
   6301 
   6302         return this.values[index];
   6303       },
   6304 
   6305       delete: function(key, value) {
   6306         var index = this.keys.indexOf(key);
   6307         if (index < 0)
   6308           return false;
   6309 
   6310         this.keys.splice(index, 1);
   6311         this.values.splice(index, 1);
   6312         return true;
   6313       },
   6314 
   6315       forEach: function(f, opt_this) {
   6316         for (var i = 0; i < this.keys.length; i++)
   6317           f.call(opt_this || this, this.values[i], this.keys[i], this);
   6318       }
   6319     };
   6320   }
   6321 
   6322   // JScript does not have __proto__. We wrap all object literals with
   6323   // createObject which uses Object.create, Object.defineProperty and
   6324   // Object.getOwnPropertyDescriptor to create a new object that does the exact
   6325   // same thing. The main downside to this solution is that we have to extract
   6326   // all those property descriptors for IE.
   6327   var createObject = ('__proto__' in {}) ?
   6328       function(obj) { return obj; } :
   6329       function(obj) {
   6330         var proto = obj.__proto__;
   6331         if (!proto)
   6332           return obj;
   6333         var newObject = Object.create(proto);
   6334         Object.getOwnPropertyNames(obj).forEach(function(name) {
   6335           Object.defineProperty(newObject, name,
   6336                                Object.getOwnPropertyDescriptor(obj, name));
   6337         });
   6338         return newObject;
   6339       };
   6340 
   6341   // IE does not support have Document.prototype.contains.
   6342   if (typeof document.contains != 'function') {
   6343     Document.prototype.contains = function(node) {
   6344       if (node === this || node.parentNode === this)
   6345         return true;
   6346       return this.documentElement.contains(node);
   6347     }
   6348   }
   6349 
   6350   var BIND = 'bind';
   6351   var REPEAT = 'repeat';
   6352   var IF = 'if';
   6353 
   6354   var templateAttributeDirectives = {
   6355     'template': true,
   6356     'repeat': true,
   6357     'bind': true,
   6358     'ref': true,
   6359     'if': true
   6360   };
   6361 
   6362   var semanticTemplateElements = {
   6363     'THEAD': true,
   6364     'TBODY': true,
   6365     'TFOOT': true,
   6366     'TH': true,
   6367     'TR': true,
   6368     'TD': true,
   6369     'COLGROUP': true,
   6370     'COL': true,
   6371     'CAPTION': true,
   6372     'OPTION': true,
   6373     'OPTGROUP': true
   6374   };
   6375 
   6376   var hasTemplateElement = typeof HTMLTemplateElement !== 'undefined';
   6377   if (hasTemplateElement) {
   6378     // TODO(rafaelw): Remove when fix for
   6379     // https://codereview.chromium.org/164803002/
   6380     // makes it to Chrome release.
   6381     (function() {
   6382       var t = document.createElement('template');
   6383       var d = t.content.ownerDocument;
   6384       var html = d.appendChild(d.createElement('html'));
   6385       var head = html.appendChild(d.createElement('head'));
   6386       var base = d.createElement('base');
   6387       base.href = document.baseURI;
   6388       head.appendChild(base);
   6389     })();
   6390   }
   6391 
   6392   var allTemplatesSelectors = 'template, ' +
   6393       Object.keys(semanticTemplateElements).map(function(tagName) {
   6394         return tagName.toLowerCase() + '[template]';
   6395       }).join(', ');
   6396 
   6397   function isSVGTemplate(el) {
   6398     return el.tagName == 'template' &&
   6399            el.namespaceURI == 'http://www.w3.org/2000/svg';
   6400   }
   6401 
   6402   function isHTMLTemplate(el) {
   6403     return el.tagName == 'TEMPLATE' &&
   6404            el.namespaceURI == 'http://www.w3.org/1999/xhtml';
   6405   }
   6406 
   6407   function isAttributeTemplate(el) {
   6408     return Boolean(semanticTemplateElements[el.tagName] &&
   6409                    el.hasAttribute('template'));
   6410   }
   6411 
   6412   function isTemplate(el) {
   6413     if (el.isTemplate_ === undefined)
   6414       el.isTemplate_ = el.tagName == 'TEMPLATE' || isAttributeTemplate(el);
   6415 
   6416     return el.isTemplate_;
   6417   }
   6418 
   6419   // FIXME: Observe templates being added/removed from documents
   6420   // FIXME: Expose imperative API to decorate and observe templates in
   6421   // "disconnected tress" (e.g. ShadowRoot)
   6422   document.addEventListener('DOMContentLoaded', function(e) {
   6423     bootstrapTemplatesRecursivelyFrom(document);
   6424     // FIXME: Is this needed? Seems like it shouldn't be.
   6425     Platform.performMicrotaskCheckpoint();
   6426   }, false);
   6427 
   6428   function forAllTemplatesFrom(node, fn) {
   6429     var subTemplates = node.querySelectorAll(allTemplatesSelectors);
   6430 
   6431     if (isTemplate(node))
   6432       fn(node)
   6433     forEach(subTemplates, fn);
   6434   }
   6435 
   6436   function bootstrapTemplatesRecursivelyFrom(node) {
   6437     function bootstrap(template) {
   6438       if (!HTMLTemplateElement.decorate(template))
   6439         bootstrapTemplatesRecursivelyFrom(template.content);
   6440     }
   6441 
   6442     forAllTemplatesFrom(node, bootstrap);
   6443   }
   6444 
   6445   if (!hasTemplateElement) {
   6446     /**
   6447      * This represents a <template> element.
   6448      * @constructor
   6449      * @extends {HTMLElement}
   6450      */
   6451     global.HTMLTemplateElement = function() {
   6452       throw TypeError('Illegal constructor');
   6453     };
   6454   }
   6455 
   6456   var hasProto = '__proto__' in {};
   6457 
   6458   function mixin(to, from) {
   6459     Object.getOwnPropertyNames(from).forEach(function(name) {
   6460       Object.defineProperty(to, name,
   6461                             Object.getOwnPropertyDescriptor(from, name));
   6462     });
   6463   }
   6464 
   6465   // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#dfn-template-contents-owner
   6466   function getOrCreateTemplateContentsOwner(template) {
   6467     var doc = template.ownerDocument
   6468     if (!doc.defaultView)
   6469       return doc;
   6470     var d = doc.templateContentsOwner_;
   6471     if (!d) {
   6472       // TODO(arv): This should either be a Document or HTMLDocument depending
   6473       // on doc.
   6474       d = doc.implementation.createHTMLDocument('');
   6475       while (d.lastChild) {
   6476         d.removeChild(d.lastChild);
   6477       }
   6478       doc.templateContentsOwner_ = d;
   6479     }
   6480     return d;
   6481   }
   6482 
   6483   function getTemplateStagingDocument(template) {
   6484     if (!template.stagingDocument_) {
   6485       var owner = template.ownerDocument;
   6486       if (!owner.stagingDocument_) {
   6487         owner.stagingDocument_ = owner.implementation.createHTMLDocument('');
   6488         owner.stagingDocument_.isStagingDocument = true;
   6489         // TODO(rafaelw): Remove when fix for
   6490         // https://codereview.chromium.org/164803002/
   6491         // makes it to Chrome release.
   6492         var base = owner.stagingDocument_.createElement('base');
   6493         base.href = document.baseURI;
   6494         owner.stagingDocument_.head.appendChild(base);
   6495 
   6496         owner.stagingDocument_.stagingDocument_ = owner.stagingDocument_;
   6497       }
   6498 
   6499       template.stagingDocument_ = owner.stagingDocument_;
   6500     }
   6501 
   6502     return template.stagingDocument_;
   6503   }
   6504 
   6505   // For non-template browsers, the parser will disallow <template> in certain
   6506   // locations, so we allow "attribute templates" which combine the template
   6507   // element with the top-level container node of the content, e.g.
   6508   //
   6509   //   <tr template repeat="{{ foo }}"" class="bar"><td>Bar</td></tr>
   6510   //
   6511   // becomes
   6512   //
   6513   //   <template repeat="{{ foo }}">
   6514   //   + #document-fragment
   6515   //     + <tr class="bar">
   6516   //       + <td>Bar</td>
   6517   //
   6518   function extractTemplateFromAttributeTemplate(el) {
   6519     var template = el.ownerDocument.createElement('template');
   6520     el.parentNode.insertBefore(template, el);
   6521 
   6522     var attribs = el.attributes;
   6523     var count = attribs.length;
   6524     while (count-- > 0) {
   6525       var attrib = attribs[count];
   6526       if (templateAttributeDirectives[attrib.name]) {
   6527         if (attrib.name !== 'template')
   6528           template.setAttribute(attrib.name, attrib.value);
   6529         el.removeAttribute(attrib.name);
   6530       }
   6531     }
   6532 
   6533     return template;
   6534   }
   6535 
   6536   function extractTemplateFromSVGTemplate(el) {
   6537     var template = el.ownerDocument.createElement('template');
   6538     el.parentNode.insertBefore(template, el);
   6539 
   6540     var attribs = el.attributes;
   6541     var count = attribs.length;
   6542     while (count-- > 0) {
   6543       var attrib = attribs[count];
   6544       template.setAttribute(attrib.name, attrib.value);
   6545       el.removeAttribute(attrib.name);
   6546     }
   6547 
   6548     el.parentNode.removeChild(el);
   6549     return template;
   6550   }
   6551 
   6552   function liftNonNativeTemplateChildrenIntoContent(template, el, useRoot) {
   6553     var content = template.content;
   6554     if (useRoot) {
   6555       content.appendChild(el);
   6556       return;
   6557     }
   6558 
   6559     var child;
   6560     while (child = el.firstChild) {
   6561       content.appendChild(child);
   6562     }
   6563   }
   6564 
   6565   var templateObserver;
   6566   if (typeof MutationObserver == 'function') {
   6567     templateObserver = new MutationObserver(function(records) {
   6568       for (var i = 0; i < records.length; i++) {
   6569         records[i].target.refChanged_();
   6570       }
   6571     });
   6572   }
   6573 
   6574   /**
   6575    * Ensures proper API and content model for template elements.
   6576    * @param {HTMLTemplateElement} opt_instanceRef The template element which
   6577    *     |el| template element will return as the value of its ref(), and whose
   6578    *     content will be used as source when createInstance() is invoked.
   6579    */
   6580   HTMLTemplateElement.decorate = function(el, opt_instanceRef) {
   6581     if (el.templateIsDecorated_)
   6582       return false;
   6583 
   6584     var templateElement = el;
   6585     templateElement.templateIsDecorated_ = true;
   6586 
   6587     var isNativeHTMLTemplate = isHTMLTemplate(templateElement) &&
   6588                                hasTemplateElement;
   6589     var bootstrapContents = isNativeHTMLTemplate;
   6590     var liftContents = !isNativeHTMLTemplate;
   6591     var liftRoot = false;
   6592 
   6593     if (!isNativeHTMLTemplate) {
   6594       if (isAttributeTemplate(templateElement)) {
   6595         assert(!opt_instanceRef);
   6596         templateElement = extractTemplateFromAttributeTemplate(el);
   6597         templateElement.templateIsDecorated_ = true;
   6598         isNativeHTMLTemplate = hasTemplateElement;
   6599         liftRoot = true;
   6600       } else if (isSVGTemplate(templateElement)) {
   6601         templateElement = extractTemplateFromSVGTemplate(el);
   6602         templateElement.templateIsDecorated_ = true;
   6603         isNativeHTMLTemplate = hasTemplateElement;
   6604       }
   6605     }
   6606 
   6607     if (!isNativeHTMLTemplate) {
   6608       fixTemplateElementPrototype(templateElement);
   6609       var doc = getOrCreateTemplateContentsOwner(templateElement);
   6610       templateElement.content_ = doc.createDocumentFragment();
   6611     }
   6612 
   6613     if (opt_instanceRef) {
   6614       // template is contained within an instance, its direct content must be
   6615       // empty
   6616       templateElement.instanceRef_ = opt_instanceRef;
   6617     } else if (liftContents) {
   6618       liftNonNativeTemplateChildrenIntoContent(templateElement,
   6619                                                el,
   6620                                                liftRoot);
   6621     } else if (bootstrapContents) {
   6622       bootstrapTemplatesRecursivelyFrom(templateElement.content);
   6623     }
   6624 
   6625     return true;
   6626   };
   6627 
   6628   // TODO(rafaelw): This used to decorate recursively all templates from a given
   6629   // node. This happens by default on 'DOMContentLoaded', but may be needed
   6630   // in subtrees not descendent from document (e.g. ShadowRoot).
   6631   // Review whether this is the right public API.
   6632   HTMLTemplateElement.bootstrap = bootstrapTemplatesRecursivelyFrom;
   6633 
   6634   var htmlElement = global.HTMLUnknownElement || HTMLElement;
   6635 
   6636   var contentDescriptor = {
   6637     get: function() {
   6638       return this.content_;
   6639     },
   6640     enumerable: true,
   6641     configurable: true
   6642   };
   6643 
   6644   if (!hasTemplateElement) {
   6645     // Gecko is more picky with the prototype than WebKit. Make sure to use the
   6646     // same prototype as created in the constructor.
   6647     HTMLTemplateElement.prototype = Object.create(htmlElement.prototype);
   6648 
   6649     Object.defineProperty(HTMLTemplateElement.prototype, 'content',
   6650                           contentDescriptor);
   6651   }
   6652 
   6653   function fixTemplateElementPrototype(el) {
   6654     if (hasProto)
   6655       el.__proto__ = HTMLTemplateElement.prototype;
   6656     else
   6657       mixin(el, HTMLTemplateElement.prototype);
   6658   }
   6659 
   6660   function ensureSetModelScheduled(template) {
   6661     if (!template.setModelFn_) {
   6662       template.setModelFn_ = function() {
   6663         template.setModelFnScheduled_ = false;
   6664         var map = getBindings(template,
   6665             template.delegate_ && template.delegate_.prepareBinding);
   6666         processBindings(template, map, template.model_);
   6667       };
   6668     }
   6669 
   6670     if (!template.setModelFnScheduled_) {
   6671       template.setModelFnScheduled_ = true;
   6672       Observer.runEOM_(template.setModelFn_);
   6673     }
   6674   }
   6675 
   6676   mixin(HTMLTemplateElement.prototype, {
   6677     bind: function(name, value, oneTime) {
   6678       if (name != 'ref')
   6679         return Element.prototype.bind.call(this, name, value, oneTime);
   6680 
   6681       var self = this;
   6682       var ref = oneTime ? value : value.open(function(ref) {
   6683         self.setAttribute('ref', ref);
   6684         self.refChanged_();
   6685       });
   6686 
   6687       this.setAttribute('ref', ref);
   6688       this.refChanged_();
   6689       if (oneTime)
   6690         return;
   6691 
   6692       if (!this.bindings_) {
   6693         this.bindings_ = { ref: value };
   6694       } else {
   6695         this.bindings_.ref = value;
   6696       }
   6697 
   6698       return value;
   6699     },
   6700 
   6701     processBindingDirectives_: function(directives) {
   6702       if (this.iterator_)
   6703         this.iterator_.closeDeps();
   6704 
   6705       if (!directives.if && !directives.bind && !directives.repeat) {
   6706         if (this.iterator_) {
   6707           this.iterator_.close();
   6708           this.iterator_ = undefined;
   6709         }
   6710 
   6711         return;
   6712       }
   6713 
   6714       if (!this.iterator_) {
   6715         this.iterator_ = new TemplateIterator(this);
   6716       }
   6717 
   6718       this.iterator_.updateDependencies(directives, this.model_);
   6719 
   6720       if (templateObserver) {
   6721         templateObserver.observe(this, { attributes: true,
   6722                                          attributeFilter: ['ref'] });
   6723       }
   6724 
   6725       return this.iterator_;
   6726     },
   6727 
   6728     createInstance: function(model, bindingDelegate, delegate_) {
   6729       if (bindingDelegate)
   6730         delegate_ = this.newDelegate_(bindingDelegate);
   6731       else if (!delegate_)
   6732         delegate_ = this.delegate_;
   6733 
   6734       if (!this.refContent_)
   6735         this.refContent_ = this.ref_.content;
   6736       var content = this.refContent_;
   6737       if (content.firstChild === null)
   6738         return emptyInstance;
   6739 
   6740       var map = getInstanceBindingMap(content, delegate_);
   6741       var stagingDocument = getTemplateStagingDocument(this);
   6742       var instance = stagingDocument.createDocumentFragment();
   6743       instance.templateCreator_ = this;
   6744       instance.protoContent_ = content;
   6745       instance.bindings_ = [];
   6746       instance.terminator_ = null;
   6747       var instanceRecord = instance.templateInstance_ = {
   6748         firstNode: null,
   6749         lastNode: null,
   6750         model: model
   6751       };
   6752 
   6753       var i = 0;
   6754       var collectTerminator = false;
   6755       for (var child = content.firstChild; child; child = child.nextSibling) {
   6756         // The terminator of the instance is the clone of the last child of the
   6757         // content. If the last child is an active template, it may produce
   6758         // instances as a result of production, so simply collecting the last
   6759         // child of the instance after it has finished producing may be wrong.
   6760         if (child.nextSibling === null)
   6761           collectTerminator = true;
   6762 
   6763         var clone = cloneAndBindInstance(child, instance, stagingDocument,
   6764                                          map.children[i++],
   6765                                          model,
   6766                                          delegate_,
   6767                                          instance.bindings_);
   6768         clone.templateInstance_ = instanceRecord;
   6769         if (collectTerminator)
   6770           instance.terminator_ = clone;
   6771       }
   6772 
   6773       instanceRecord.firstNode = instance.firstChild;
   6774       instanceRecord.lastNode = instance.lastChild;
   6775       instance.templateCreator_ = undefined;
   6776       instance.protoContent_ = undefined;
   6777       return instance;
   6778     },
   6779 
   6780     get model() {
   6781       return this.model_;
   6782     },
   6783 
   6784     set model(model) {
   6785       this.model_ = model;
   6786       ensureSetModelScheduled(this);
   6787     },
   6788 
   6789     get bindingDelegate() {
   6790       return this.delegate_ && this.delegate_.raw;
   6791     },
   6792 
   6793     refChanged_: function() {
   6794       if (!this.iterator_ || this.refContent_ === this.ref_.content)
   6795         return;
   6796 
   6797       this.refContent_ = undefined;
   6798       this.iterator_.valueChanged();
   6799       this.iterator_.updateIteratedValue(this.iterator_.getUpdatedValue());
   6800     },
   6801 
   6802     clear: function() {
   6803       this.model_ = undefined;
   6804       this.delegate_ = undefined;
   6805       if (this.bindings_ && this.bindings_.ref)
   6806         this.bindings_.ref.close()
   6807       this.refContent_ = undefined;
   6808       if (!this.iterator_)
   6809         return;
   6810       this.iterator_.valueChanged();
   6811       this.iterator_.close()
   6812       this.iterator_ = undefined;
   6813     },
   6814 
   6815     setDelegate_: function(delegate) {
   6816       this.delegate_ = delegate;
   6817       this.bindingMap_ = undefined;
   6818       if (this.iterator_) {
   6819         this.iterator_.instancePositionChangedFn_ = undefined;
   6820         this.iterator_.instanceModelFn_ = undefined;
   6821       }
   6822     },
   6823 
   6824     newDelegate_: function(bindingDelegate) {
   6825       if (!bindingDelegate)
   6826         return;
   6827 
   6828       function delegateFn(name) {
   6829         var fn = bindingDelegate && bindingDelegate[name];
   6830         if (typeof fn != 'function')
   6831           return;
   6832 
   6833         return function() {
   6834           return fn.apply(bindingDelegate, arguments);
   6835         };
   6836       }
   6837 
   6838       return {
   6839         bindingMaps: {},
   6840         raw: bindingDelegate,
   6841         prepareBinding: delegateFn('prepareBinding'),
   6842         prepareInstanceModel: delegateFn('prepareInstanceModel'),
   6843         prepareInstancePositionChanged:
   6844             delegateFn('prepareInstancePositionChanged')
   6845       };
   6846     },
   6847 
   6848     set bindingDelegate(bindingDelegate) {
   6849       if (this.delegate_) {
   6850         throw Error('Template must be cleared before a new bindingDelegate ' +
   6851                     'can be assigned');
   6852       }
   6853 
   6854       this.setDelegate_(this.newDelegate_(bindingDelegate));
   6855     },
   6856 
   6857     get ref_() {
   6858       var ref = searchRefId(this, this.getAttribute('ref'));
   6859       if (!ref)
   6860         ref = this.instanceRef_;
   6861 
   6862       if (!ref)
   6863         return this;
   6864 
   6865       var nextRef = ref.ref_;
   6866       return nextRef ? nextRef : ref;
   6867     }
   6868   });
   6869 
   6870   // Returns
   6871   //   a) undefined if there are no mustaches.
   6872   //   b) [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least one mustache.
   6873   function parseMustaches(s, name, node, prepareBindingFn) {
   6874     if (!s || !s.length)
   6875       return;
   6876 
   6877     var tokens;
   6878     var length = s.length;
   6879     var startIndex = 0, lastIndex = 0, endIndex = 0;
   6880     var onlyOneTime = true;
   6881     while (lastIndex < length) {
   6882       var startIndex = s.indexOf('{{', lastIndex);
   6883       var oneTimeStart = s.indexOf('[[', lastIndex);
   6884       var oneTime = false;
   6885       var terminator = '}}';
   6886 
   6887       if (oneTimeStart >= 0 &&
   6888           (startIndex < 0 || oneTimeStart < startIndex)) {
   6889         startIndex = oneTimeStart;
   6890         oneTime = true;
   6891         terminator = ']]';
   6892       }
   6893 
   6894       endIndex = startIndex < 0 ? -1 : s.indexOf(terminator, startIndex + 2);
   6895 
   6896       if (endIndex < 0) {
   6897         if (!tokens)
   6898           return;
   6899 
   6900         tokens.push(s.slice(lastIndex)); // TEXT
   6901         break;
   6902       }
   6903 
   6904       tokens = tokens || [];
   6905       tokens.push(s.slice(lastIndex, startIndex)); // TEXT
   6906       var pathString = s.slice(startIndex + 2, endIndex).trim();
   6907       tokens.push(oneTime); // ONE_TIME?
   6908       onlyOneTime = onlyOneTime && oneTime;
   6909       var delegateFn = prepareBindingFn &&
   6910                        prepareBindingFn(pathString, name, node);
   6911       // Don't try to parse the expression if there's a prepareBinding function
   6912       if (delegateFn == null) {
   6913         tokens.push(Path.get(pathString)); // PATH
   6914       } else {
   6915         tokens.push(null);
   6916       }
   6917       tokens.push(delegateFn); // DELEGATE_FN
   6918       lastIndex = endIndex + 2;
   6919     }
   6920 
   6921     if (lastIndex === length)
   6922       tokens.push(''); // TEXT
   6923 
   6924     tokens.hasOnePath = tokens.length === 5;
   6925     tokens.isSimplePath = tokens.hasOnePath &&
   6926                           tokens[0] == '' &&
   6927                           tokens[4] == '';
   6928     tokens.onlyOneTime = onlyOneTime;
   6929 
   6930     tokens.combinator = function(values) {
   6931       var newValue = tokens[0];
   6932 
   6933       for (var i = 1; i < tokens.length; i += 4) {
   6934         var value = tokens.hasOnePath ? values : values[(i - 1) / 4];
   6935         if (value !== undefined)
   6936           newValue += value;
   6937         newValue += tokens[i + 3];
   6938       }
   6939 
   6940       return newValue;
   6941     }
   6942 
   6943     return tokens;
   6944   };
   6945 
   6946   function processOneTimeBinding(name, tokens, node, model) {
   6947     if (tokens.hasOnePath) {
   6948       var delegateFn = tokens[3];
   6949       var value = delegateFn ? delegateFn(model, node, true) :
   6950                                tokens[2].getValueFrom(model);
   6951       return tokens.isSimplePath ? value : tokens.combinator(value);
   6952     }
   6953 
   6954     var values = [];
   6955     for (var i = 1; i < tokens.length; i += 4) {
   6956       var delegateFn = tokens[i + 2];
   6957       values[(i - 1) / 4] = delegateFn ? delegateFn(model, node) :
   6958           tokens[i + 1].getValueFrom(model);
   6959     }
   6960 
   6961     return tokens.combinator(values);
   6962   }
   6963 
   6964   function processSinglePathBinding(name, tokens, node, model) {
   6965     var delegateFn = tokens[3];
   6966     var observer = delegateFn ? delegateFn(model, node, false) :
   6967         new PathObserver(model, tokens[2]);
   6968 
   6969     return tokens.isSimplePath ? observer :
   6970         new ObserverTransform(observer, tokens.combinator);
   6971   }
   6972 
   6973   function processBinding(name, tokens, node, model) {
   6974     if (tokens.onlyOneTime)
   6975       return processOneTimeBinding(name, tokens, node, model);
   6976 
   6977     if (tokens.hasOnePath)
   6978       return processSinglePathBinding(name, tokens, node, model);
   6979 
   6980     var observer = new CompoundObserver();
   6981 
   6982     for (var i = 1; i < tokens.length; i += 4) {
   6983       var oneTime = tokens[i];
   6984       var delegateFn = tokens[i + 2];
   6985 
   6986       if (delegateFn) {
   6987         var value = delegateFn(model, node, oneTime);
   6988         if (oneTime)
   6989           observer.addPath(value)
   6990         else
   6991           observer.addObserver(value);
   6992         continue;
   6993       }
   6994 
   6995       var path = tokens[i + 1];
   6996       if (oneTime)
   6997         observer.addPath(path.getValueFrom(model))
   6998       else
   6999         observer.addPath(model, path);
   7000     }
   7001 
   7002     return new ObserverTransform(observer, tokens.combinator);
   7003   }
   7004 
   7005   function processBindings(node, bindings, model, instanceBindings) {
   7006     for (var i = 0; i < bindings.length; i += 2) {
   7007       var name = bindings[i]
   7008       var tokens = bindings[i + 1];
   7009       var value = processBinding(name, tokens, node, model);
   7010       var binding = node.bind(name, value, tokens.onlyOneTime);
   7011       if (binding && instanceBindings)
   7012         instanceBindings.push(binding);
   7013     }
   7014 
   7015     node.bindFinished();
   7016     if (!bindings.isTemplate)
   7017       return;
   7018 
   7019     node.model_ = model;
   7020     var iter = node.processBindingDirectives_(bindings);
   7021     if (instanceBindings && iter)
   7022       instanceBindings.push(iter);
   7023   }
   7024 
   7025   function parseWithDefault(el, name, prepareBindingFn) {
   7026     var v = el.getAttribute(name);
   7027     return parseMustaches(v == '' ? '{{}}' : v, name, el, prepareBindingFn);
   7028   }
   7029 
   7030   function parseAttributeBindings(element, prepareBindingFn) {
   7031     assert(element);
   7032 
   7033     var bindings = [];
   7034     var ifFound = false;
   7035     var bindFound = false;
   7036 
   7037     for (var i = 0; i < element.attributes.length; i++) {
   7038       var attr = element.attributes[i];
   7039       var name = attr.name;
   7040       var value = attr.value;
   7041 
   7042       // Allow bindings expressed in attributes to be prefixed with underbars.
   7043       // We do this to allow correct semantics for browsers that don't implement
   7044       // <template> where certain attributes might trigger side-effects -- and
   7045       // for IE which sanitizes certain attributes, disallowing mustache
   7046       // replacements in their text.
   7047       while (name[0] === '_') {
   7048         name = name.substring(1);
   7049       }
   7050 
   7051       if (isTemplate(element) &&
   7052           (name === IF || name === BIND || name === REPEAT)) {
   7053         continue;
   7054       }
   7055 
   7056       var tokens = parseMustaches(value, name, element,
   7057                                   prepareBindingFn);
   7058       if (!tokens)
   7059         continue;
   7060 
   7061       bindings.push(name, tokens);
   7062     }
   7063 
   7064     if (isTemplate(element)) {
   7065       bindings.isTemplate = true;
   7066       bindings.if = parseWithDefault(element, IF, prepareBindingFn);
   7067       bindings.bind = parseWithDefault(element, BIND, prepareBindingFn);
   7068       bindings.repeat = parseWithDefault(element, REPEAT, prepareBindingFn);
   7069 
   7070       if (bindings.if && !bindings.bind && !bindings.repeat)
   7071         bindings.bind = parseMustaches('{{}}', BIND, element, prepareBindingFn);
   7072     }
   7073 
   7074     return bindings;
   7075   }
   7076 
   7077   function getBindings(node, prepareBindingFn) {
   7078     if (node.nodeType === Node.ELEMENT_NODE)
   7079       return parseAttributeBindings(node, prepareBindingFn);
   7080 
   7081     if (node.nodeType === Node.TEXT_NODE) {
   7082       var tokens = parseMustaches(node.data, 'textContent', node,
   7083                                   prepareBindingFn);
   7084       if (tokens)
   7085         return ['textContent', tokens];
   7086     }
   7087 
   7088     return [];
   7089   }
   7090 
   7091   function cloneAndBindInstance(node, parent, stagingDocument, bindings, model,
   7092                                 delegate,
   7093                                 instanceBindings,
   7094                                 instanceRecord) {
   7095     var clone = parent.appendChild(stagingDocument.importNode(node, false));
   7096 
   7097     var i = 0;
   7098     for (var child = node.firstChild; child; child = child.nextSibling) {
   7099       cloneAndBindInstance(child, clone, stagingDocument,
   7100                             bindings.children[i++],
   7101                             model,
   7102                             delegate,
   7103                             instanceBindings);
   7104     }
   7105 
   7106     if (bindings.isTemplate) {
   7107       HTMLTemplateElement.decorate(clone, node);
   7108       if (delegate)
   7109         clone.setDelegate_(delegate);
   7110     }
   7111 
   7112     processBindings(clone, bindings, model, instanceBindings);
   7113     return clone;
   7114   }
   7115 
   7116   function createInstanceBindingMap(node, prepareBindingFn) {
   7117     var map = getBindings(node, prepareBindingFn);
   7118     map.children = {};
   7119     var index = 0;
   7120     for (var child = node.firstChild; child; child = child.nextSibling) {
   7121       map.children[index++] = createInstanceBindingMap(child, prepareBindingFn);
   7122     }
   7123 
   7124     return map;
   7125   }
   7126 
   7127   var contentUidCounter = 1;
   7128 
   7129   // TODO(rafaelw): Setup a MutationObserver on content which clears the id
   7130   // so that bindingMaps regenerate when the template.content changes.
   7131   function getContentUid(content) {
   7132     var id = content.id_;
   7133     if (!id)
   7134       id = content.id_ = contentUidCounter++;
   7135     return id;
   7136   }
   7137 
   7138   // Each delegate is associated with a set of bindingMaps, one for each
   7139   // content which may be used by a template. The intent is that each binding
   7140   // delegate gets the opportunity to prepare the instance (via the prepare*
   7141   // delegate calls) once across all uses.
   7142   // TODO(rafaelw): Separate out the parse map from the binding map. In the
   7143   // current implementation, if two delegates need a binding map for the same
   7144   // content, the second will have to reparse.
   7145   function getInstanceBindingMap(content, delegate_) {
   7146     var contentId = getContentUid(content);
   7147     if (delegate_) {
   7148       var map = delegate_.bindingMaps[contentId];
   7149       if (!map) {
   7150         map = delegate_.bindingMaps[contentId] =
   7151             createInstanceBindingMap(content, delegate_.prepareBinding) || [];
   7152       }
   7153       return map;
   7154     }
   7155 
   7156     var map = content.bindingMap_;
   7157     if (!map) {
   7158       map = content.bindingMap_ =
   7159           createInstanceBindingMap(content, undefined) || [];
   7160     }
   7161     return map;
   7162   }
   7163 
   7164   Object.defineProperty(Node.prototype, 'templateInstance', {
   7165     get: function() {
   7166       var instance = this.templateInstance_;
   7167       return instance ? instance :
   7168           (this.parentNode ? this.parentNode.templateInstance : undefined);
   7169     }
   7170   });
   7171 
   7172   var emptyInstance = document.createDocumentFragment();
   7173   emptyInstance.bindings_ = [];
   7174   emptyInstance.terminator_ = null;
   7175 
   7176   function TemplateIterator(templateElement) {
   7177     this.closed = false;
   7178     this.templateElement_ = templateElement;
   7179     this.instances = [];
   7180     this.deps = undefined;
   7181     this.iteratedValue = [];
   7182     this.presentValue = undefined;
   7183     this.arrayObserver = undefined;
   7184   }
   7185 
   7186   TemplateIterator.prototype = {
   7187     closeDeps: function() {
   7188       var deps = this.deps;
   7189       if (deps) {
   7190         if (deps.ifOneTime === false)
   7191           deps.ifValue.close();
   7192         if (deps.oneTime === false)
   7193           deps.value.close();
   7194       }
   7195     },
   7196 
   7197     updateDependencies: function(directives, model) {
   7198       this.closeDeps();
   7199 
   7200       var deps = this.deps = {};
   7201       var template = this.templateElement_;
   7202 
   7203       var ifValue = true;
   7204       if (directives.if) {
   7205         deps.hasIf = true;
   7206         deps.ifOneTime = directives.if.onlyOneTime;
   7207         deps.ifValue = processBinding(IF, directives.if, template, model);
   7208 
   7209         ifValue = deps.ifValue;
   7210 
   7211         // oneTime if & predicate is false. nothing else to do.
   7212         if (deps.ifOneTime && !ifValue) {
   7213           this.valueChanged();
   7214           return;
   7215         }
   7216 
   7217         if (!deps.ifOneTime)
   7218           ifValue = ifValue.open(this.updateIfValue, this);
   7219       }
   7220 
   7221       if (directives.repeat) {
   7222         deps.repeat = true;
   7223         deps.oneTime = directives.repeat.onlyOneTime;
   7224         deps.value = processBinding(REPEAT, directives.repeat, template, model);
   7225       } else {
   7226         deps.repeat = false;
   7227         deps.oneTime = directives.bind.onlyOneTime;
   7228         deps.value = processBinding(BIND, directives.bind, template, model);
   7229       }
   7230 
   7231       var value = deps.value;
   7232       if (!deps.oneTime)
   7233         value = value.open(this.updateIteratedValue, this);
   7234 
   7235       if (!ifValue) {
   7236         this.valueChanged();
   7237         return;
   7238       }
   7239 
   7240       this.updateValue(value);
   7241     },
   7242 
   7243     /**
   7244      * Gets the updated value of the bind/repeat. This can potentially call
   7245      * user code (if a bindingDelegate is set up) so we try to avoid it if we
   7246      * already have the value in hand (from Observer.open).
   7247      */
   7248     getUpdatedValue: function() {
   7249       var value = this.deps.value;
   7250       if (!this.deps.oneTime)
   7251         value = value.discardChanges();
   7252       return value;
   7253     },
   7254 
   7255     updateIfValue: function(ifValue) {
   7256       if (!ifValue) {
   7257         this.valueChanged();
   7258         return;
   7259       }
   7260 
   7261       this.updateValue(this.getUpdatedValue());
   7262     },
   7263 
   7264     updateIteratedValue: function(value) {
   7265       if (this.deps.hasIf) {
   7266         var ifValue = this.deps.ifValue;
   7267         if (!this.deps.ifOneTime)
   7268           ifValue = ifValue.discardChanges();
   7269         if (!ifValue) {
   7270           this.valueChanged();
   7271           return;
   7272         }
   7273       }
   7274 
   7275       this.updateValue(value);
   7276     },
   7277 
   7278     updateValue: function(value) {
   7279       if (!this.deps.repeat)
   7280         value = [value];
   7281       var observe = this.deps.repeat &&
   7282                     !this.deps.oneTime &&
   7283                     Array.isArray(value);
   7284       this.valueChanged(value, observe);
   7285     },
   7286 
   7287     valueChanged: function(value, observeValue) {
   7288       if (!Array.isArray(value))
   7289         value = [];
   7290 
   7291       if (value === this.iteratedValue)
   7292         return;
   7293 
   7294       this.unobserve();
   7295       this.presentValue = value;
   7296       if (observeValue) {
   7297         this.arrayObserver = new ArrayObserver(this.presentValue);
   7298         this.arrayObserver.open(this.handleSplices, this);
   7299       }
   7300 
   7301       this.handleSplices(ArrayObserver.calculateSplices(this.presentValue,
   7302                                                         this.iteratedValue));
   7303     },
   7304 
   7305     getLastInstanceNode: function(index) {
   7306       if (index == -1)
   7307         return this.templateElement_;
   7308       var instance = this.instances[index];
   7309       var terminator = instance.terminator_;
   7310       if (!terminator)
   7311         return this.getLastInstanceNode(index - 1);
   7312 
   7313       if (terminator.nodeType !== Node.ELEMENT_NODE ||
   7314           this.templateElement_ === terminator) {
   7315         return terminator;
   7316       }
   7317 
   7318       var subtemplateIterator = terminator.iterator_;
   7319       if (!subtemplateIterator)
   7320         return terminator;
   7321 
   7322       return subtemplateIterator.getLastTemplateNode();
   7323     },
   7324 
   7325     getLastTemplateNode: function() {
   7326       return this.getLastInstanceNode(this.instances.length - 1);
   7327     },
   7328 
   7329     insertInstanceAt: function(index, fragment) {
   7330       var previousInstanceLast = this.getLastInstanceNode(index - 1);
   7331       var parent = this.templateElement_.parentNode;
   7332       this.instances.splice(index, 0, fragment);
   7333 
   7334       parent.insertBefore(fragment, previousInstanceLast.nextSibling);
   7335     },
   7336 
   7337     extractInstanceAt: function(index) {
   7338       var previousInstanceLast = this.getLastInstanceNode(index - 1);
   7339       var lastNode = this.getLastInstanceNode(index);
   7340       var parent = this.templateElement_.parentNode;
   7341       var instance = this.instances.splice(index, 1)[0];
   7342 
   7343       while (lastNode !== previousInstanceLast) {
   7344         var node = previousInstanceLast.nextSibling;
   7345         if (node == lastNode)
   7346           lastNode = previousInstanceLast;
   7347 
   7348         instance.appendChild(parent.removeChild(node));
   7349       }
   7350 
   7351       return instance;
   7352     },
   7353 
   7354     getDelegateFn: function(fn) {
   7355       fn = fn && fn(this.templateElement_);
   7356       return typeof fn === 'function' ? fn : null;
   7357     },
   7358 
   7359     handleSplices: function(splices) {
   7360       if (this.closed || !splices.length)
   7361         return;
   7362 
   7363       var template = this.templateElement_;
   7364 
   7365       if (!template.parentNode) {
   7366         this.close();
   7367         return;
   7368       }
   7369 
   7370       ArrayObserver.applySplices(this.iteratedValue, this.presentValue,
   7371                                  splices);
   7372 
   7373       var delegate = template.delegate_;
   7374       if (this.instanceModelFn_ === undefined) {
   7375         this.instanceModelFn_ =
   7376             this.getDelegateFn(delegate && delegate.prepareInstanceModel);
   7377       }
   7378 
   7379       if (this.instancePositionChangedFn_ === undefined) {
   7380         this.instancePositionChangedFn_ =
   7381             this.getDelegateFn(delegate &&
   7382                                delegate.prepareInstancePositionChanged);
   7383       }
   7384 
   7385       // Instance Removals
   7386       var instanceCache = new Map;
   7387       var removeDelta = 0;
   7388       for (var i = 0; i < splices.length; i++) {
   7389         var splice = splices[i];
   7390         var removed = splice.removed;
   7391         for (var j = 0; j < removed.length; j++) {
   7392           var model = removed[j];
   7393           var instance = this.extractInstanceAt(splice.index + removeDelta);
   7394           if (instance !== emptyInstance) {
   7395             instanceCache.set(model, instance);
   7396           }
   7397         }
   7398 
   7399         removeDelta -= splice.addedCount;
   7400       }
   7401 
   7402       // Instance Insertions
   7403       for (var i = 0; i < splices.length; i++) {
   7404         var splice = splices[i];
   7405         var addIndex = splice.index;
   7406         for (; addIndex < splice.index + splice.addedCount; addIndex++) {
   7407