Chromium Code Reviews

Side by Side Diff: polymer_0.5.0/bower_components/polymer/polymer.js

Issue 786953007: npm_modules: Fork bower_components into Polymer 0.4.0 and 0.5.0 versions (Closed) Base URL: https://chromium.googlesource.com/infra/third_party/npm_modules.git@master
Patch Set: Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff |
OLDNEW
(Empty)
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://polyme r.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/CON TRIBUTORS.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/PA TENTS.txt
9 */
10 // @version 0.5.1
11 window.PolymerGestures = {};
12
13 (function(scope) {
14 var HAS_FULL_PATH = 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 HAS_FULL_PATH = 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_FRAGME NT_NODE) {
97 s = document;
98 }
99 return s;
100 },
101 findTarget: function(inEvent) {
102 if (HAS_FULL_PATH && 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 (HAS_FULL_PATH && 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 <= r ect.bottom);
192 },
193 path: function(event) {
194 var p;
195 if (HAS_FULL_PATH && 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, in cluding 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.createShadowRoo t;
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 prop erties 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 e[k] = inDict[k];
372 }
373 return e;
374 },
375 makePointerEvent: function(inType, inDict) {
376 inDict = inDict || Object.create(null);
377
378 var e = this.makeBaseEvent(inType, inDict);
379 // define inherited MouseEvent properties
380 for(var i = 0, p; i < MOUSE_PROPS.length; i++) {
381 p = MOUSE_PROPS[i];
382 e[p] = inDict[p] || MOUSE_DEFAULTS[i];
383 }
384 e.buttons = inDict.buttons || 0;
385
386 // Spec requires that pointers without pressure specified use 0.5 for down
387 // state and 0 for up state.
388 var pressure = 0;
389 if (inDict.pressure) {
390 pressure = inDict.pressure;
391 } else {
392 pressure = e.buttons ? 0.5 : 0;
393 }
394
395 // add x/y properties aliased to clientX/Y
396 e.x = e.clientX;
397 e.y = e.clientY;
398
399 // define the properties of the PointerEvent interface
400 e.pointerId = inDict.pointerId || 0;
401 e.width = inDict.width || 0;
402 e.height = inDict.height || 0;
403 e.pressure = pressure;
404 e.tiltX = inDict.tiltX || 0;
405 e.tiltY = inDict.tiltY || 0;
406 e.pointerType = inDict.pointerType || '';
407 e.hwTimestamp = inDict.hwTimestamp || 0;
408 e.isPrimary = inDict.isPrimary || false;
409 e._source = inDict._source || '';
410 return e;
411 }
412 };
413
414 scope.eventFactory = eventFactory;
415 })(window.PolymerGestures);
416
417 /**
418 * This module implements an map of pointer states
419 */
420 (function(scope) {
421 var USE_MAP = window.Map && window.Map.prototype.forEach;
422 var POINTERS_FN = function(){ return this.size; };
423 function PointerMap() {
424 if (USE_MAP) {
425 var m = new Map();
426 m.pointers = POINTERS_FN;
427 return m;
428 } else {
429 this.keys = [];
430 this.values = [];
431 }
432 }
433
434 PointerMap.prototype = {
435 set: function(inId, inEvent) {
436 var i = this.keys.indexOf(inId);
437 if (i > -1) {
438 this.values[i] = inEvent;
439 } else {
440 this.keys.push(inId);
441 this.values.push(inEvent);
442 }
443 },
444 has: function(inId) {
445 return this.keys.indexOf(inId) > -1;
446 },
447 'delete': function(inId) {
448 var i = this.keys.indexOf(inId);
449 if (i > -1) {
450 this.keys.splice(i, 1);
451 this.values.splice(i, 1);
452 }
453 },
454 get: function(inId) {
455 var i = this.keys.indexOf(inId);
456 return this.values[i];
457 },
458 clear: function() {
459 this.keys.length = 0;
460 this.values.length = 0;
461 },
462 // return value, key, map
463 forEach: function(callback, thisArg) {
464 this.values.forEach(function(v, i) {
465 callback.call(thisArg, v, this.keys[i], this);
466 }, this);
467 },
468 pointers: function() {
469 return this.keys.length;
470 }
471 };
472
473 scope.PointerMap = PointerMap;
474 })(window.PolymerGestures);
475
476 (function(scope) {
477 var CLONE_PROPS = [
478 // MouseEvent
479 'bubbles',
480 'cancelable',
481 'view',
482 'detail',
483 'screenX',
484 'screenY',
485 'clientX',
486 'clientY',
487 'ctrlKey',
488 'altKey',
489 'shiftKey',
490 'metaKey',
491 'button',
492 'relatedTarget',
493 // DOM Level 3
494 'buttons',
495 // PointerEvent
496 'pointerId',
497 'width',
498 'height',
499 'pressure',
500 'tiltX',
501 'tiltY',
502 'pointerType',
503 'hwTimestamp',
504 'isPrimary',
505 // event instance
506 'type',
507 'target',
508 'currentTarget',
509 'which',
510 'pageX',
511 'pageY',
512 'timeStamp',
513 // gesture addons
514 'preventTap',
515 'tapPrevented',
516 '_source'
517 ];
518
519 var CLONE_DEFAULTS = [
520 // MouseEvent
521 false,
522 false,
523 null,
524 null,
525 0,
526 0,
527 0,
528 0,
529 false,
530 false,
531 false,
532 false,
533 0,
534 null,
535 // DOM Level 3
536 0,
537 // PointerEvent
538 0,
539 0,
540 0,
541 0,
542 0,
543 0,
544 '',
545 0,
546 false,
547 // event instance
548 '',
549 null,
550 null,
551 0,
552 0,
553 0,
554 0,
555 function(){},
556 false
557 ];
558
559 var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined');
560
561 var eventFactory = scope.eventFactory;
562
563 // set of recognizers to run for the currently handled event
564 var currentGestures;
565
566 /**
567 * This module is for normalizing events. Mouse and Touch events will be
568 * collected here, and fire PointerEvents that have the same semantics, no
569 * matter the source.
570 * Events fired:
571 * - pointerdown: a pointing is added
572 * - pointerup: a pointer is removed
573 * - pointermove: a pointer is moved
574 * - pointerover: a pointer crosses into an element
575 * - pointerout: a pointer leaves an element
576 * - pointercancel: a pointer will no longer generate events
577 */
578 var dispatcher = {
579 IS_IOS: false,
580 pointermap: new scope.PointerMap(),
581 requiredGestures: new scope.PointerMap(),
582 eventMap: Object.create(null),
583 // Scope objects for native events.
584 // This exists for ease of testing.
585 eventSources: Object.create(null),
586 eventSourceList: [],
587 gestures: [],
588 // map gesture event -> {listeners: int, index: gestures[int]}
589 dependencyMap: {
590 // make sure down and up are in the map to trigger "register"
591 down: {listeners: 0, index: -1},
592 up: {listeners: 0, index: -1}
593 },
594 gestureQueue: [],
595 /**
596 * Add a new event source that will generate pointer events.
597 *
598 * `inSource` must contain an array of event names named `events`, and
599 * functions with the names specified in the `events` array.
600 * @param {string} name A name for the event source
601 * @param {Object} source A new source of platform events.
602 */
603 registerSource: function(name, source) {
604 var s = source;
605 var newEvents = s.events;
606 if (newEvents) {
607 newEvents.forEach(function(e) {
608 if (s[e]) {
609 this.eventMap[e] = s[e].bind(s);
610 }
611 }, this);
612 this.eventSources[name] = s;
613 this.eventSourceList.push(s);
614 }
615 },
616 registerGesture: function(name, source) {
617 var obj = Object.create(null);
618 obj.listeners = 0;
619 obj.index = this.gestures.length;
620 for (var i = 0, g; i < source.exposes.length; i++) {
621 g = source.exposes[i].toLowerCase();
622 this.dependencyMap[g] = obj;
623 }
624 this.gestures.push(source);
625 },
626 register: function(element, initial) {
627 var l = this.eventSourceList.length;
628 for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) {
629 // call eventsource register
630 es.register.call(es, element, initial);
631 }
632 },
633 unregister: function(element) {
634 var l = this.eventSourceList.length;
635 for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) {
636 // call eventsource register
637 es.unregister.call(es, element);
638 }
639 },
640 // EVENTS
641 down: function(inEvent) {
642 this.requiredGestures.set(inEvent.pointerId, currentGestures);
643 this.fireEvent('down', inEvent);
644 },
645 move: function(inEvent) {
646 // pipe move events into gesture queue directly
647 inEvent.type = 'move';
648 this.fillGestureQueue(inEvent);
649 },
650 up: function(inEvent) {
651 this.fireEvent('up', inEvent);
652 this.requiredGestures.delete(inEvent.pointerId);
653 },
654 cancel: function(inEvent) {
655 inEvent.tapPrevented = true;
656 this.fireEvent('up', inEvent);
657 this.requiredGestures.delete(inEvent.pointerId);
658 },
659 addGestureDependency: function(node, currentGestures) {
660 var gesturesWanted = node._pgEvents;
661 if (gesturesWanted && currentGestures) {
662 var gk = Object.keys(gesturesWanted);
663 for (var i = 0, r, ri, g; i < gk.length; i++) {
664 // gesture
665 g = gk[i];
666 if (gesturesWanted[g] > 0) {
667 // lookup gesture recognizer
668 r = this.dependencyMap[g];
669 // recognizer index
670 ri = r ? r.index : -1;
671 currentGestures[ri] = true;
672 }
673 }
674 }
675 },
676 // LISTENER LOGIC
677 eventHandler: function(inEvent) {
678 // This is used to prevent multiple dispatch of events from
679 // platform events. This can happen when two elements in different scopes
680 // are set up to create pointer events, which is relevant to Shadow DOM.
681
682 var type = inEvent.type;
683
684 // only generate the list of desired events on "down"
685 if (type === 'touchstart' || type === 'mousedown' || type === 'pointerdown ' || type === 'MSPointerDown') {
686 if (!inEvent._handledByPG) {
687 currentGestures = {};
688 }
689
690 // in IOS mode, there is only a listener on the document, so this is not re-entrant
691 if (this.IS_IOS) {
692 var ev = inEvent;
693 if (type === 'touchstart') {
694 var ct = inEvent.changedTouches[0];
695 // set up a fake event to give to the path builder
696 ev = {target: inEvent.target, clientX: ct.clientX, clientY: ct.clien tY, path: inEvent.path};
697 }
698 // use event path if available, otherwise build a path from target fin ding
699 var nodes = inEvent.path || scope.targetFinding.path(ev);
700 for (var i = 0, n; i < nodes.length; i++) {
701 n = nodes[i];
702 this.addGestureDependency(n, currentGestures);
703 }
704 } else {
705 this.addGestureDependency(inEvent.currentTarget, currentGestures);
706 }
707 }
708
709 if (inEvent._handledByPG) {
710 return;
711 }
712 var fn = this.eventMap && this.eventMap[type];
713 if (fn) {
714 fn(inEvent);
715 }
716 inEvent._handledByPG = true;
717 },
718 // set up event listeners
719 listen: function(target, events) {
720 for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) {
721 this.addEvent(target, e);
722 }
723 },
724 // remove event listeners
725 unlisten: function(target, events) {
726 for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) {
727 this.removeEvent(target, e);
728 }
729 },
730 addEvent: function(target, eventName) {
731 target.addEventListener(eventName, this.boundHandler);
732 },
733 removeEvent: function(target, eventName) {
734 target.removeEventListener(eventName, this.boundHandler);
735 },
736 // EVENT CREATION AND TRACKING
737 /**
738 * Creates a new Event of type `inType`, based on the information in
739 * `inEvent`.
740 *
741 * @param {string} inType A string representing the type of event to create
742 * @param {Event} inEvent A platform event with a target
743 * @return {Event} A PointerEvent of type `inType`
744 */
745 makeEvent: function(inType, inEvent) {
746 var e = eventFactory.makePointerEvent(inType, inEvent);
747 e.preventDefault = inEvent.preventDefault;
748 e.tapPrevented = inEvent.tapPrevented;
749 e._target = e._target || inEvent.target;
750 return e;
751 },
752 // make and dispatch an event in one call
753 fireEvent: function(inType, inEvent) {
754 var e = this.makeEvent(inType, inEvent);
755 return this.dispatchEvent(e);
756 },
757 /**
758 * Returns a snapshot of inEvent, with writable properties.
759 *
760 * @param {Event} inEvent An event that contains properties to copy.
761 * @return {Object} An object containing shallow copies of `inEvent`'s
762 * properties.
763 */
764 cloneEvent: function(inEvent) {
765 var eventCopy = Object.create(null), p;
766 for (var i = 0; i < CLONE_PROPS.length; i++) {
767 p = CLONE_PROPS[i];
768 eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i];
769 // Work around SVGInstanceElement shadow tree
770 // Return the <use> element that is represented by the instance for Safa ri, Chrome, IE.
771 // This is the behavior implemented by Firefox.
772 if (p === 'target' || p === 'relatedTarget') {
773 if (HAS_SVG_INSTANCE && eventCopy[p] instanceof SVGElementInstance) {
774 eventCopy[p] = eventCopy[p].correspondingUseElement;
775 }
776 }
777 }
778 // keep the semantics of preventDefault
779 eventCopy.preventDefault = function() {
780 inEvent.preventDefault();
781 };
782 return eventCopy;
783 },
784 /**
785 * Dispatches the event to its target.
786 *
787 * @param {Event} inEvent The event to be dispatched.
788 * @return {Boolean} True if an event handler returns true, false otherwise.
789 */
790 dispatchEvent: function(inEvent) {
791 var t = inEvent._target;
792 if (t) {
793 t.dispatchEvent(inEvent);
794 // clone the event for the gesture system to process
795 // clone after dispatch to pick up gesture prevention code
796 var clone = this.cloneEvent(inEvent);
797 clone.target = t;
798 this.fillGestureQueue(clone);
799 }
800 },
801 gestureTrigger: function() {
802 // process the gesture queue
803 for (var i = 0, e, rg; i < this.gestureQueue.length; i++) {
804 e = this.gestureQueue[i];
805 rg = e._requiredGestures;
806 if (rg) {
807 for (var j = 0, g, fn; j < this.gestures.length; j++) {
808 // only run recognizer if an element in the source event's path is l istening for those gestures
809 if (rg[j]) {
810 g = this.gestures[j];
811 fn = g[e.type];
812 if (fn) {
813 fn.call(g, e);
814 }
815 }
816 }
817 }
818 }
819 this.gestureQueue.length = 0;
820 },
821 fillGestureQueue: function(ev) {
822 // only trigger the gesture queue once
823 if (!this.gestureQueue.length) {
824 requestAnimationFrame(this.boundGestureTrigger);
825 }
826 ev._requiredGestures = this.requiredGestures.get(ev.pointerId);
827 this.gestureQueue.push(ev);
828 }
829 };
830 dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher);
831 dispatcher.boundGestureTrigger = dispatcher.gestureTrigger.bind(dispatcher);
832 scope.dispatcher = dispatcher;
833
834 /**
835 * Listen for `gesture` on `node` with the `handler` function
836 *
837 * If `handler` is the first listener for `gesture`, the underlying gesture re cognizer is then enabled.
838 *
839 * @param {Element} node
840 * @param {string} gesture
841 * @return Boolean `gesture` is a valid gesture
842 */
843 scope.activateGesture = function(node, gesture) {
844 var g = gesture.toLowerCase();
845 var dep = dispatcher.dependencyMap[g];
846 if (dep) {
847 var recognizer = dispatcher.gestures[dep.index];
848 if (!node._pgListeners) {
849 dispatcher.register(node);
850 node._pgListeners = 0;
851 }
852 // TODO(dfreedm): re-evaluate bookkeeping to avoid using attributes
853 if (recognizer) {
854 var touchAction = recognizer.defaultActions && recognizer.defaultActions [g];
855 var actionNode;
856 switch(node.nodeType) {
857 case Node.ELEMENT_NODE:
858 actionNode = node;
859 break;
860 case Node.DOCUMENT_FRAGMENT_NODE:
861 actionNode = node.host;
862 break;
863 default:
864 actionNode = null;
865 break;
866 }
867 if (touchAction && actionNode && !actionNode.hasAttribute('touch-action' )) {
868 actionNode.setAttribute('touch-action', touchAction);
869 }
870 }
871 if (!node._pgEvents) {
872 node._pgEvents = {};
873 }
874 node._pgEvents[g] = (node._pgEvents[g] || 0) + 1;
875 node._pgListeners++;
876 }
877 return Boolean(dep);
878 };
879
880 /**
881 *
882 * Listen for `gesture` from `node` with `handler` function.
883 *
884 * @param {Element} node
885 * @param {string} gesture
886 * @param {Function} handler
887 * @param {Boolean} capture
888 */
889 scope.addEventListener = function(node, gesture, handler, capture) {
890 if (handler) {
891 scope.activateGesture(node, gesture);
892 node.addEventListener(gesture, handler, capture);
893 }
894 };
895
896 /**
897 * Tears down the gesture configuration for `node`
898 *
899 * If `handler` is the last listener for `gesture`, the underlying gesture rec ognizer is disabled.
900 *
901 * @param {Element} node
902 * @param {string} gesture
903 * @return Boolean `gesture` is a valid gesture
904 */
905 scope.deactivateGesture = function(node, gesture) {
906 var g = gesture.toLowerCase();
907 var dep = dispatcher.dependencyMap[g];
908 if (dep) {
909 if (node._pgListeners > 0) {
910 node._pgListeners--;
911 }
912 if (node._pgListeners === 0) {
913 dispatcher.unregister(node);
914 }
915 if (node._pgEvents) {
916 if (node._pgEvents[g] > 0) {
917 node._pgEvents[g]--;
918 } else {
919 node._pgEvents[g] = 0;
920 }
921 }
922 }
923 return Boolean(dep);
924 };
925
926 /**
927 * Stop listening for `gesture` from `node` with `handler` function.
928 *
929 * @param {Element} node
930 * @param {string} gesture
931 * @param {Function} handler
932 * @param {Boolean} capture
933 */
934 scope.removeEventListener = function(node, gesture, handler, capture) {
935 if (handler) {
936 scope.deactivateGesture(node, gesture);
937 node.removeEventListener(gesture, handler, capture);
938 }
939 };
940 })(window.PolymerGestures);
941
942 (function(scope) {
943 var dispatcher = scope.dispatcher;
944 var pointermap = dispatcher.pointermap;
945 // radius around touchend that swallows mouse events
946 var DEDUP_DIST = 25;
947
948 var WHICH_TO_BUTTONS = [0, 1, 4, 2];
949
950 var CURRENT_BUTTONS = 0;
951
952 var FIREFOX_LINUX = /Linux.*Firefox\//i;
953
954 var HAS_BUTTONS = (function() {
955 // firefox on linux returns spec-incorrect values for mouseup.buttons
956 // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent.buttons#See_a lso
957 // https://codereview.chromium.org/727593003/#msg16
958 if (FIREFOX_LINUX.test(navigator.userAgent)) {
959 return false;
960 }
961 try {
962 return new MouseEvent('test', {buttons: 1}).buttons === 1;
963 } catch (e) {
964 return false;
965 }
966 })();
967
968 // handler block for native mouse events
969 var mouseEvents = {
970 POINTER_ID: 1,
971 POINTER_TYPE: 'mouse',
972 events: [
973 'mousedown',
974 'mousemove',
975 'mouseup'
976 ],
977 exposes: [
978 'down',
979 'up',
980 'move'
981 ],
982 register: function(target) {
983 dispatcher.listen(target, this.events);
984 },
985 unregister: function(target) {
986 if (target === document) {
987 return;
988 }
989 dispatcher.unlisten(target, this.events);
990 },
991 lastTouches: [],
992 // collide with the global mouse listener
993 isEventSimulatedFromTouch: function(inEvent) {
994 var lts = this.lastTouches;
995 var x = inEvent.clientX, y = inEvent.clientY;
996 for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) {
997 // simulated mouse events will be swallowed near a primary touchend
998 var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
999 if (dx <= DEDUP_DIST && dy <= DEDUP_DIST) {
1000 return true;
1001 }
1002 }
1003 },
1004 prepareEvent: function(inEvent) {
1005 var e = dispatcher.cloneEvent(inEvent);
1006 e.pointerId = this.POINTER_ID;
1007 e.isPrimary = true;
1008 e.pointerType = this.POINTER_TYPE;
1009 e._source = 'mouse';
1010 if (!HAS_BUTTONS) {
1011 var type = inEvent.type;
1012 var bit = WHICH_TO_BUTTONS[inEvent.which] || 0;
1013 if (type === 'mousedown') {
1014 CURRENT_BUTTONS |= bit;
1015 } else if (type === 'mouseup') {
1016 CURRENT_BUTTONS &= ~bit;
1017 }
1018 e.buttons = CURRENT_BUTTONS;
1019 }
1020 return e;
1021 },
1022 mousedown: function(inEvent) {
1023 if (!this.isEventSimulatedFromTouch(inEvent)) {
1024 var p = pointermap.has(this.POINTER_ID);
1025 var e = this.prepareEvent(inEvent);
1026 e.target = scope.findTarget(inEvent);
1027 pointermap.set(this.POINTER_ID, e.target);
1028 dispatcher.down(e);
1029 }
1030 },
1031 mousemove: function(inEvent) {
1032 if (!this.isEventSimulatedFromTouch(inEvent)) {
1033 var target = pointermap.get(this.POINTER_ID);
1034 if (target) {
1035 var e = this.prepareEvent(inEvent);
1036 e.target = target;
1037 // handle case where we missed a mouseup
1038 if ((HAS_BUTTONS ? e.buttons : e.which) === 0) {
1039 if (!HAS_BUTTONS) {
1040 CURRENT_BUTTONS = e.buttons = 0;
1041 }
1042 dispatcher.cancel(e);
1043 this.cleanupMouse(e.buttons);
1044 } else {
1045 dispatcher.move(e);
1046 }
1047 }
1048 }
1049 },
1050 mouseup: function(inEvent) {
1051 if (!this.isEventSimulatedFromTouch(inEvent)) {
1052 var e = this.prepareEvent(inEvent);
1053 e.relatedTarget = scope.findTarget(inEvent);
1054 e.target = pointermap.get(this.POINTER_ID);
1055 dispatcher.up(e);
1056 this.cleanupMouse(e.buttons);
1057 }
1058 },
1059 cleanupMouse: function(buttons) {
1060 if (buttons === 0) {
1061 pointermap.delete(this.POINTER_ID);
1062 }
1063 }
1064 };
1065
1066 scope.mouseEvents = mouseEvents;
1067 })(window.PolymerGestures);
1068
1069 (function(scope) {
1070 var dispatcher = scope.dispatcher;
1071 var allShadows = scope.targetFinding.allShadows.bind(scope.targetFinding);
1072 var pointermap = dispatcher.pointermap;
1073 var touchMap = Array.prototype.map.call.bind(Array.prototype.map);
1074 // This should be long enough to ignore compat mouse events made by touch
1075 var DEDUP_TIMEOUT = 2500;
1076 var DEDUP_DIST = 25;
1077 var CLICK_COUNT_TIMEOUT = 200;
1078 var HYSTERESIS = 20;
1079 var ATTRIB = 'touch-action';
1080 // TODO(dfreedm): disable until http://crbug.com/399765 is resolved
1081 // var HAS_TOUCH_ACTION = ATTRIB in document.head.style;
1082 var HAS_TOUCH_ACTION = false;
1083
1084 // handler block for native touch events
1085 var touchEvents = {
1086 IS_IOS: false,
1087 events: [
1088 'touchstart',
1089 'touchmove',
1090 'touchend',
1091 'touchcancel'
1092 ],
1093 exposes: [
1094 'down',
1095 'up',
1096 'move'
1097 ],
1098 register: function(target, initial) {
1099 if (this.IS_IOS ? initial : !initial) {
1100 dispatcher.listen(target, this.events);
1101 }
1102 },
1103 unregister: function(target) {
1104 if (!this.IS_IOS) {
1105 dispatcher.unlisten(target, this.events);
1106 }
1107 },
1108 scrollTypes: {
1109 EMITTER: 'none',
1110 XSCROLLER: 'pan-x',
1111 YSCROLLER: 'pan-y',
1112 },
1113 touchActionToScrollType: function(touchAction) {
1114 var t = touchAction;
1115 var st = this.scrollTypes;
1116 if (t === st.EMITTER) {
1117 return 'none';
1118 } else if (t === st.XSCROLLER) {
1119 return 'X';
1120 } else if (t === st.YSCROLLER) {
1121 return 'Y';
1122 } else {
1123 return 'XY';
1124 }
1125 },
1126 POINTER_TYPE: 'touch',
1127 firstTouch: null,
1128 isPrimaryTouch: function(inTouch) {
1129 return this.firstTouch === inTouch.identifier;
1130 },
1131 setPrimaryTouch: function(inTouch) {
1132 // set primary touch if there no pointers, or the only pointer is the mous e
1133 if (pointermap.pointers() === 0 || (pointermap.pointers() === 1 && pointer map.has(1))) {
1134 this.firstTouch = inTouch.identifier;
1135 this.firstXY = {X: inTouch.clientX, Y: inTouch.clientY};
1136 this.firstTarget = inTouch.target;
1137 this.scrolling = null;
1138 this.cancelResetClickCount();
1139 }
1140 },
1141 removePrimaryPointer: function(inPointer) {
1142 if (inPointer.isPrimary) {
1143 this.firstTouch = null;
1144 this.firstXY = null;
1145 this.resetClickCount();
1146 }
1147 },
1148 clickCount: 0,
1149 resetId: null,
1150 resetClickCount: function() {
1151 var fn = function() {
1152 this.clickCount = 0;
1153 this.resetId = null;
1154 }.bind(this);
1155 this.resetId = setTimeout(fn, CLICK_COUNT_TIMEOUT);
1156 },
1157 cancelResetClickCount: function() {
1158 if (this.resetId) {
1159 clearTimeout(this.resetId);
1160 }
1161 },
1162 typeToButtons: function(type) {
1163 var ret = 0;
1164 if (type === 'touchstart' || type === 'touchmove') {
1165 ret = 1;
1166 }
1167 return ret;
1168 },
1169 findTarget: function(touch, id) {
1170 if (this.currentTouchEvent.type === 'touchstart') {
1171 if (this.isPrimaryTouch(touch)) {
1172 var fastPath = {
1173 clientX: touch.clientX,
1174 clientY: touch.clientY,
1175 path: this.currentTouchEvent.path,
1176 target: this.currentTouchEvent.target
1177 };
1178 return scope.findTarget(fastPath);
1179 } else {
1180 return scope.findTarget(touch);
1181 }
1182 }
1183 // reuse target we found in touchstart
1184 return pointermap.get(id);
1185 },
1186 touchToPointer: function(inTouch) {
1187 var cte = this.currentTouchEvent;
1188 var e = dispatcher.cloneEvent(inTouch);
1189 // Spec specifies that pointerId 1 is reserved for Mouse.
1190 // Touch identifiers can start at 0.
1191 // Add 2 to the touch identifier for compatibility.
1192 var id = e.pointerId = inTouch.identifier + 2;
1193 e.target = this.findTarget(inTouch, id);
1194 e.bubbles = true;
1195 e.cancelable = true;
1196 e.detail = this.clickCount;
1197 e.buttons = this.typeToButtons(cte.type);
1198 e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0;
1199 e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0;
1200 e.pressure = inTouch.webkitForce || inTouch.force || 0.5;
1201 e.isPrimary = this.isPrimaryTouch(inTouch);
1202 e.pointerType = this.POINTER_TYPE;
1203 e._source = 'touch';
1204 // forward touch preventDefaults
1205 var self = this;
1206 e.preventDefault = function() {
1207 self.scrolling = false;
1208 self.firstXY = null;
1209 cte.preventDefault();
1210 };
1211 return e;
1212 },
1213 processTouches: function(inEvent, inFunction) {
1214 var tl = inEvent.changedTouches;
1215 this.currentTouchEvent = inEvent;
1216 for (var i = 0, t, p; i < tl.length; i++) {
1217 t = tl[i];
1218 p = this.touchToPointer(t);
1219 if (inEvent.type === 'touchstart') {
1220 pointermap.set(p.pointerId, p.target);
1221 }
1222 if (pointermap.has(p.pointerId)) {
1223 inFunction.call(this, p);
1224 }
1225 if (inEvent.type === 'touchend' || inEvent._cancel) {
1226 this.cleanUpPointer(p);
1227 }
1228 }
1229 },
1230 // For single axis scrollers, determines whether the element should emit
1231 // pointer events or behave as a scroller
1232 shouldScroll: function(inEvent) {
1233 if (this.firstXY) {
1234 var ret;
1235 var touchAction = scope.targetFinding.findTouchAction(inEvent);
1236 var scrollAxis = this.touchActionToScrollType(touchAction);
1237 if (scrollAxis === 'none') {
1238 // this element is a touch-action: none, should never scroll
1239 ret = false;
1240 } else if (scrollAxis === 'XY') {
1241 // this element should always scroll
1242 ret = true;
1243 } else {
1244 var t = inEvent.changedTouches[0];
1245 // check the intended scroll axis, and other axis
1246 var a = scrollAxis;
1247 var oa = scrollAxis === 'Y' ? 'X' : 'Y';
1248 var da = Math.abs(t['client' + a] - this.firstXY[a]);
1249 var doa = Math.abs(t['client' + oa] - this.firstXY[oa]);
1250 // if delta in the scroll axis > delta other axis, scroll instead of
1251 // making events
1252 ret = da >= doa;
1253 }
1254 return ret;
1255 }
1256 },
1257 findTouch: function(inTL, inId) {
1258 for (var i = 0, l = inTL.length, t; i < l && (t = inTL[i]); i++) {
1259 if (t.identifier === inId) {
1260 return true;
1261 }
1262 }
1263 },
1264 // In some instances, a touchstart can happen without a touchend. This
1265 // leaves the pointermap in a broken state.
1266 // Therefore, on every touchstart, we remove the touches that did not fire a
1267 // touchend event.
1268 // To keep state globally consistent, we fire a
1269 // pointercancel for this "abandoned" touch
1270 vacuumTouches: function(inEvent) {
1271 var tl = inEvent.touches;
1272 // pointermap.pointers() should be < tl.length here, as the touchstart has not
1273 // been processed yet.
1274 if (pointermap.pointers() >= tl.length) {
1275 var d = [];
1276 pointermap.forEach(function(value, key) {
1277 // Never remove pointerId == 1, which is mouse.
1278 // Touch identifiers are 2 smaller than their pointerId, which is the
1279 // index in pointermap.
1280 if (key !== 1 && !this.findTouch(tl, key - 2)) {
1281 var p = value;
1282 d.push(p);
1283 }
1284 }, this);
1285 d.forEach(function(p) {
1286 this.cancel(p);
1287 pointermap.delete(p.pointerId);
1288 }, this);
1289 }
1290 },
1291 touchstart: function(inEvent) {
1292 this.vacuumTouches(inEvent);
1293 this.setPrimaryTouch(inEvent.changedTouches[0]);
1294 this.dedupSynthMouse(inEvent);
1295 if (!this.scrolling) {
1296 this.clickCount++;
1297 this.processTouches(inEvent, this.down);
1298 }
1299 },
1300 down: function(inPointer) {
1301 dispatcher.down(inPointer);
1302 },
1303 touchmove: function(inEvent) {
1304 if (HAS_TOUCH_ACTION) {
1305 // touchevent.cancelable == false is sent when the page is scrolling und er native Touch Action in Chrome 36
1306 // https://groups.google.com/a/chromium.org/d/msg/input-dev/wHnyukcYBcA/ b9kmtwM1jJQJ
1307 if (inEvent.cancelable) {
1308 this.processTouches(inEvent, this.move);
1309 }
1310 } else {
1311 if (!this.scrolling) {
1312 if (this.scrolling === null && this.shouldScroll(inEvent)) {
1313 this.scrolling = true;
1314 } else {
1315 this.scrolling = false;
1316 inEvent.preventDefault();
1317 this.processTouches(inEvent, this.move);
1318 }
1319 } else if (this.firstXY) {
1320 var t = inEvent.changedTouches[0];
1321 var dx = t.clientX - this.firstXY.X;
1322 var dy = t.clientY - this.firstXY.Y;
1323 var dd = Math.sqrt(dx * dx + dy * dy);
1324 if (dd >= HYSTERESIS) {
1325 this.touchcancel(inEvent);
1326 this.scrolling = true;
1327 this.firstXY = null;
1328 }
1329 }
1330 }
1331 },
1332 move: function(inPointer) {
1333 dispatcher.move(inPointer);
1334 },
1335 touchend: function(inEvent) {
1336 this.dedupSynthMouse(inEvent);
1337 this.processTouches(inEvent, this.up);
1338 },
1339 up: function(inPointer) {
1340 inPointer.relatedTarget = scope.findTarget(inPointer);
1341 dispatcher.up(inPointer);
1342 },
1343 cancel: function(inPointer) {
1344 dispatcher.cancel(inPointer);
1345 },
1346 touchcancel: function(inEvent) {
1347 inEvent._cancel = true;
1348 this.processTouches(inEvent, this.cancel);
1349 },
1350 cleanUpPointer: function(inPointer) {
1351 pointermap['delete'](inPointer.pointerId);
1352 this.removePrimaryPointer(inPointer);
1353 },
1354 // prevent synth mouse events from creating pointer events
1355 dedupSynthMouse: function(inEvent) {
1356 var lts = scope.mouseEvents.lastTouches;
1357 var t = inEvent.changedTouches[0];
1358 // only the primary finger will synth mouse events
1359 if (this.isPrimaryTouch(t)) {
1360 // remember x/y of last touch
1361 var lt = {x: t.clientX, y: t.clientY};
1362 lts.push(lt);
1363 var fn = (function(lts, lt){
1364 var i = lts.indexOf(lt);
1365 if (i > -1) {
1366 lts.splice(i, 1);
1367 }
1368 }).bind(null, lts, lt);
1369 setTimeout(fn, DEDUP_TIMEOUT);
1370 }
1371 }
1372 };
1373
1374 // prevent "ghost clicks" that come from elements that were removed in a touch handler
1375 var STOP_PROP_FN = Event.prototype.stopImmediatePropagation || Event.prototype .stopPropagation;
1376 document.addEventListener('click', function(ev) {
1377 var x = ev.clientX, y = ev.clientY;
1378 // check if a click is within DEDUP_DIST px radius of the touchstart
1379 var closeTo = function(touch) {
1380 var dx = Math.abs(x - touch.x), dy = Math.abs(y - touch.y);
1381 return (dx <= DEDUP_DIST && dy <= DEDUP_DIST);
1382 };
1383 // if click coordinates are close to touch coordinates, assume the click cam e from a touch
1384 var wasTouched = scope.mouseEvents.lastTouches.some(closeTo);
1385 // if the click came from touch, and the touchstart target is not in the pat h of the click event,
1386 // then the touchstart target was probably removed, and the click should be "busted"
1387 var path = scope.targetFinding.path(ev);
1388 if (wasTouched) {
1389 for (var i = 0; i < path.length; i++) {
1390 if (path[i] === touchEvents.firstTarget) {
1391 return;
1392 }
1393 }
1394 ev.preventDefault();
1395 STOP_PROP_FN.call(ev);
1396 }
1397 }, true);
1398
1399 scope.touchEvents = touchEvents;
1400 })(window.PolymerGestures);
1401
1402 (function(scope) {
1403 var dispatcher = scope.dispatcher;
1404 var pointermap = dispatcher.pointermap;
1405 var HAS_BITMAP_TYPE = window.MSPointerEvent && typeof window.MSPointerEvent.MS POINTER_TYPE_MOUSE === 'number';
1406 var msEvents = {
1407 events: [
1408 'MSPointerDown',
1409 'MSPointerMove',
1410 'MSPointerUp',
1411 'MSPointerCancel',
1412 ],
1413 register: function(target) {
1414 dispatcher.listen(target, this.events);
1415 },
1416 unregister: function(target) {
1417 if (target === document) {
1418 return;
1419 }
1420 dispatcher.unlisten(target, this.events);
1421 },
1422 POINTER_TYPES: [
1423 '',
1424 'unavailable',
1425 'touch',
1426 'pen',
1427 'mouse'
1428 ],
1429 prepareEvent: function(inEvent) {
1430 var e = inEvent;
1431 e = dispatcher.cloneEvent(inEvent);
1432 if (HAS_BITMAP_TYPE) {
1433 e.pointerType = this.POINTER_TYPES[inEvent.pointerType];
1434 }
1435 e._source = 'ms';
1436 return e;
1437 },
1438 cleanup: function(id) {
1439 pointermap['delete'](id);
1440 },
1441 MSPointerDown: function(inEvent) {
1442 var e = this.prepareEvent(inEvent);
1443 e.target = scope.findTarget(inEvent);
1444 pointermap.set(inEvent.pointerId, e.target);
1445 dispatcher.down(e);
1446 },
1447 MSPointerMove: function(inEvent) {
1448 var target = pointermap.get(inEvent.pointerId);
1449 if (target) {
1450 var e = this.prepareEvent(inEvent);
1451 e.target = target;
1452 dispatcher.move(e);
1453 }
1454 },
1455 MSPointerUp: function(inEvent) {
1456 var e = this.prepareEvent(inEvent);
1457 e.relatedTarget = scope.findTarget(inEvent);
1458 e.target = pointermap.get(e.pointerId);
1459 dispatcher.up(e);
1460 this.cleanup(inEvent.pointerId);
1461 },
1462 MSPointerCancel: function(inEvent) {
1463 var e = this.prepareEvent(inEvent);
1464 e.relatedTarget = scope.findTarget(inEvent);
1465 e.target = pointermap.get(e.pointerId);
1466 dispatcher.cancel(e);
1467 this.cleanup(inEvent.pointerId);
1468 }
1469 };
1470
1471 scope.msEvents = msEvents;
1472 })(window.PolymerGestures);
1473
1474 (function(scope) {
1475 var dispatcher = scope.dispatcher;
1476 var pointermap = dispatcher.pointermap;
1477 var pointerEvents = {
1478 events: [
1479 'pointerdown',
1480 'pointermove',
1481 'pointerup',
1482 'pointercancel'
1483 ],
1484 prepareEvent: function(inEvent) {
1485 var e = dispatcher.cloneEvent(inEvent);
1486 e._source = 'pointer';
1487 return e;
1488 },
1489 register: function(target) {
1490 dispatcher.listen(target, this.events);
1491 },
1492 unregister: function(target) {
1493 if (target === document) {
1494 return;
1495 }
1496 dispatcher.unlisten(target, this.events);
1497 },
1498 cleanup: function(id) {
1499 pointermap['delete'](id);
1500 },
1501 pointerdown: function(inEvent) {
1502 var e = this.prepareEvent(inEvent);
1503 e.target = scope.findTarget(inEvent);
1504 pointermap.set(e.pointerId, e.target);
1505 dispatcher.down(e);
1506 },
1507 pointermove: function(inEvent) {
1508 var target = pointermap.get(inEvent.pointerId);
1509 if (target) {
1510 var e = this.prepareEvent(inEvent);
1511 e.target = target;
1512 dispatcher.move(e);
1513 }
1514 },
1515 pointerup: function(inEvent) {
1516 var e = this.prepareEvent(inEvent);
1517 e.relatedTarget = scope.findTarget(inEvent);
1518 e.target = pointermap.get(e.pointerId);
1519 dispatcher.up(e);
1520 this.cleanup(inEvent.pointerId);
1521 },
1522 pointercancel: function(inEvent) {
1523 var e = this.prepareEvent(inEvent);
1524 e.relatedTarget = scope.findTarget(inEvent);
1525 e.target = pointermap.get(e.pointerId);
1526 dispatcher.cancel(e);
1527 this.cleanup(inEvent.pointerId);
1528 }
1529 };
1530
1531 scope.pointerEvents = pointerEvents;
1532 })(window.PolymerGestures);
1533
1534 /**
1535 * This module contains the handlers for native platform events.
1536 * From here, the dispatcher is called to create unified pointer events.
1537 * Included are touch events (v1), mouse events, and MSPointerEvents.
1538 */
1539 (function(scope) {
1540
1541 var dispatcher = scope.dispatcher;
1542 var nav = window.navigator;
1543
1544 if (window.PointerEvent) {
1545 dispatcher.registerSource('pointer', scope.pointerEvents);
1546 } else if (nav.msPointerEnabled) {
1547 dispatcher.registerSource('ms', scope.msEvents);
1548 } else {
1549 dispatcher.registerSource('mouse', scope.mouseEvents);
1550 if (window.ontouchstart !== undefined) {
1551 dispatcher.registerSource('touch', scope.touchEvents);
1552 }
1553 }
1554
1555 // Work around iOS bugs https://bugs.webkit.org/show_bug.cgi?id=135628 and htt ps://bugs.webkit.org/show_bug.cgi?id=136506
1556 var ua = navigator.userAgent;
1557 var IS_IOS = ua.match(/iPad|iPhone|iPod/) && 'ontouchstart' in window;
1558
1559 dispatcher.IS_IOS = IS_IOS;
1560 scope.touchEvents.IS_IOS = IS_IOS;
1561
1562 dispatcher.register(document, true);
1563 })(window.PolymerGestures);
1564
1565 /**
1566 * This event denotes the beginning of a series of tracking events.
1567 *
1568 * @module PointerGestures
1569 * @submodule Events
1570 * @class trackstart
1571 */
1572 /**
1573 * Pixels moved in the x direction since trackstart.
1574 * @type Number
1575 * @property dx
1576 */
1577 /**
1578 * Pixes moved in the y direction since trackstart.
1579 * @type Number
1580 * @property dy
1581 */
1582 /**
1583 * Pixels moved in the x direction since the last track.
1584 * @type Number
1585 * @property ddx
1586 */
1587 /**
1588 * Pixles moved in the y direction since the last track.
1589 * @type Number
1590 * @property ddy
1591 */
1592 /**
1593 * The clientX position of the track gesture.
1594 * @type Number
1595 * @property clientX
1596 */
1597 /**
1598 * The clientY position of the track gesture.
1599 * @type Number
1600 * @property clientY
1601 */
1602 /**
1603 * The pageX position of the track gesture.
1604 * @type Number
1605 * @property pageX
1606 */
1607 /**
1608 * The pageY position of the track gesture.
1609 * @type Number
1610 * @property pageY
1611 */
1612 /**
1613 * The screenX position of the track gesture.
1614 * @type Number
1615 * @property screenX
1616 */
1617 /**
1618 * The screenY position of the track gesture.
1619 * @type Number
1620 * @property screenY
1621 */
1622 /**
1623 * The last x axis direction of the pointer.
1624 * @type Number
1625 * @property xDirection
1626 */
1627 /**
1628 * The last y axis direction of the pointer.
1629 * @type Number
1630 * @property yDirection
1631 */
1632 /**
1633 * A shared object between all tracking events.
1634 * @type Object
1635 * @property trackInfo
1636 */
1637 /**
1638 * The element currently under the pointer.
1639 * @type Element
1640 * @property relatedTarget
1641 */
1642 /**
1643 * The type of pointer that make the track gesture.
1644 * @type String
1645 * @property pointerType
1646 */
1647 /**
1648 *
1649 * This event fires for all pointer movement being tracked.
1650 *
1651 * @class track
1652 * @extends trackstart
1653 */
1654 /**
1655 * This event fires when the pointer is no longer being tracked.
1656 *
1657 * @class trackend
1658 * @extends trackstart
1659 */
1660
1661 (function(scope) {
1662 var dispatcher = scope.dispatcher;
1663 var eventFactory = scope.eventFactory;
1664 var pointermap = new scope.PointerMap();
1665 var track = {
1666 events: [
1667 'down',
1668 'move',
1669 'up',
1670 ],
1671 exposes: [
1672 'trackstart',
1673 'track',
1674 'trackx',
1675 'tracky',
1676 'trackend'
1677 ],
1678 defaultActions: {
1679 'track': 'none',
1680 'trackx': 'pan-y',
1681 'tracky': 'pan-x'
1682 },
1683 WIGGLE_THRESHOLD: 4,
1684 clampDir: function(inDelta) {
1685 return inDelta > 0 ? 1 : -1;
1686 },
1687 calcPositionDelta: function(inA, inB) {
1688 var x = 0, y = 0;
1689 if (inA && inB) {
1690 x = inB.pageX - inA.pageX;
1691 y = inB.pageY - inA.pageY;
1692 }
1693 return {x: x, y: y};
1694 },
1695 fireTrack: function(inType, inEvent, inTrackingData) {
1696 var t = inTrackingData;
1697 var d = this.calcPositionDelta(t.downEvent, inEvent);
1698 var dd = this.calcPositionDelta(t.lastMoveEvent, inEvent);
1699 if (dd.x) {
1700 t.xDirection = this.clampDir(dd.x);
1701 } else if (inType === 'trackx') {
1702 return;
1703 }
1704 if (dd.y) {
1705 t.yDirection = this.clampDir(dd.y);
1706 } else if (inType === 'tracky') {
1707 return;
1708 }
1709 var gestureProto = {
1710 bubbles: true,
1711 cancelable: true,
1712 trackInfo: t.trackInfo,
1713 relatedTarget: inEvent.relatedTarget,
1714 pointerType: inEvent.pointerType,
1715 pointerId: inEvent.pointerId,
1716 _source: 'track'
1717 };
1718 if (inType !== 'tracky') {
1719 gestureProto.x = inEvent.x;
1720 gestureProto.dx = d.x;
1721 gestureProto.ddx = dd.x;
1722 gestureProto.clientX = inEvent.clientX;
1723 gestureProto.pageX = inEvent.pageX;
1724 gestureProto.screenX = inEvent.screenX;
1725 gestureProto.xDirection = t.xDirection;
1726 }
1727 if (inType !== 'trackx') {
1728 gestureProto.dy = d.y;
1729 gestureProto.ddy = dd.y;
1730 gestureProto.y = inEvent.y;
1731 gestureProto.clientY = inEvent.clientY;
1732 gestureProto.pageY = inEvent.pageY;
1733 gestureProto.screenY = inEvent.screenY;
1734 gestureProto.yDirection = t.yDirection;
1735 }
1736 var e = eventFactory.makeGestureEvent(inType, gestureProto);
1737 t.downTarget.dispatchEvent(e);
1738 },
1739 down: function(inEvent) {
1740 if (inEvent.isPrimary && (inEvent.pointerType === 'mouse' ? inEvent.butto ns === 1 : true)) {
1741 var p = {
1742 downEvent: inEvent,
1743 downTarget: inEvent.target,
1744 trackInfo: {},
1745 lastMoveEvent: null,
1746 xDirection: 0,
1747 yDirection: 0,
1748 tracking: false
1749 };
1750 pointermap.set(inEvent.pointerId, p);
1751 }
1752 },
1753 move: function(inEvent) {
1754 var p = pointermap.get(inEvent.pointerId);
1755 if (p) {
1756 if (!p.tracking) {
1757 var d = this.calcPositionDelta(p.downEvent, inEvent);
1758 var move = d.x * d.x + d.y * d.y;
1759 // start tracking only if finger moves more than WIGGLE_THRESHOLD
1760 if (move > this.WIGGLE_THRESHOLD) {
1761 p.tracking = true;
1762 p.lastMoveEvent = p.downEvent;
1763 this.fireTrack('trackstart', inEvent, p);
1764 }
1765 }
1766 if (p.tracking) {
1767 this.fireTrack('track', inEvent, p);
1768 this.fireTrack('trackx', inEvent, p);
1769 this.fireTrack('tracky', inEvent, p);
1770 }
1771 p.lastMoveEvent = inEvent;
1772 }
1773 },
1774 up: function(inEvent) {
1775 var p = pointermap.get(inEvent.pointerId);
1776 if (p) {
1777 if (p.tracking) {
1778 this.fireTrack('trackend', inEvent, p);
1779 }
1780 pointermap.delete(inEvent.pointerId);
1781 }
1782 }
1783 };
1784 dispatcher.registerGesture('track', track);
1785 })(window.PolymerGestures);
1786
1787 /**
1788 * This event is fired when a pointer is held down for 200ms.
1789 *
1790 * @module PointerGestures
1791 * @submodule Events
1792 * @class hold
1793 */
1794 /**
1795 * Type of pointer that made the holding event.
1796 * @type String
1797 * @property pointerType
1798 */
1799 /**
1800 * Screen X axis position of the held pointer
1801 * @type Number
1802 * @property clientX
1803 */
1804 /**
1805 * Screen Y axis position of the held pointer
1806 * @type Number
1807 * @property clientY
1808 */
1809 /**
1810 * Type of pointer that made the holding event.
1811 * @type String
1812 * @property pointerType
1813 */
1814 /**
1815 * This event is fired every 200ms while a pointer is held down.
1816 *
1817 * @class holdpulse
1818 * @extends hold
1819 */
1820 /**
1821 * Milliseconds pointer has been held down.
1822 * @type Number
1823 * @property holdTime
1824 */
1825 /**
1826 * This event is fired when a held pointer is released or moved.
1827 *
1828 * @class release
1829 */
1830
1831 (function(scope) {
1832 var dispatcher = scope.dispatcher;
1833 var eventFactory = scope.eventFactory;
1834 var hold = {
1835 // wait at least HOLD_DELAY ms between hold and pulse events
1836 HOLD_DELAY: 200,
1837 // pointer can move WIGGLE_THRESHOLD pixels before not counting as a hold
1838 WIGGLE_THRESHOLD: 16,
1839 events: [
1840 'down',
1841 'move',
1842 'up',
1843 ],
1844 exposes: [
1845 'hold',
1846 'holdpulse',
1847 'release'
1848 ],
1849 heldPointer: null,
1850 holdJob: null,
1851 pulse: function() {
1852 var hold = Date.now() - this.heldPointer.timeStamp;
1853 var type = this.held ? 'holdpulse' : 'hold';
1854 this.fireHold(type, hold);
1855 this.held = true;
1856 },
1857 cancel: function() {
1858 clearInterval(this.holdJob);
1859 if (this.held) {
1860 this.fireHold('release');
1861 }
1862 this.held = false;
1863 this.heldPointer = null;
1864 this.target = null;
1865 this.holdJob = null;
1866 },
1867 down: function(inEvent) {
1868 if (inEvent.isPrimary && !this.heldPointer) {
1869 this.heldPointer = inEvent;
1870 this.target = inEvent.target;
1871 this.holdJob = setInterval(this.pulse.bind(this), this.HOLD_DELAY);
1872 }
1873 },
1874 up: function(inEvent) {
1875 if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId) {
1876 this.cancel();
1877 }
1878 },
1879 move: function(inEvent) {
1880 if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId) {
1881 var x = inEvent.clientX - this.heldPointer.clientX;
1882 var y = inEvent.clientY - this.heldPointer.clientY;
1883 if ((x * x + y * y) > this.WIGGLE_THRESHOLD) {
1884 this.cancel();
1885 }
1886 }
1887 },
1888 fireHold: function(inType, inHoldTime) {
1889 var p = {
1890 bubbles: true,
1891 cancelable: true,
1892 pointerType: this.heldPointer.pointerType,
1893 pointerId: this.heldPointer.pointerId,
1894 x: this.heldPointer.clientX,
1895 y: this.heldPointer.clientY,
1896 _source: 'hold'
1897 };
1898 if (inHoldTime) {
1899 p.holdTime = inHoldTime;
1900 }
1901 var e = eventFactory.makeGestureEvent(inType, p);
1902 this.target.dispatchEvent(e);
1903 }
1904 };
1905 dispatcher.registerGesture('hold', hold);
1906 })(window.PolymerGestures);
1907
1908 /**
1909 * This event is fired when a pointer quickly goes down and up, and is used to
1910 * denote activation.
1911 *
1912 * Any gesture event can prevent the tap event from being created by calling
1913 * `event.preventTap`.
1914 *
1915 * Any pointer event can prevent the tap by setting the `tapPrevented` property
1916 * on itself.
1917 *
1918 * @module PointerGestures
1919 * @submodule Events
1920 * @class tap
1921 */
1922 /**
1923 * X axis position of the tap.
1924 * @property x
1925 * @type Number
1926 */
1927 /**
1928 * Y axis position of the tap.
1929 * @property y
1930 * @type Number
1931 */
1932 /**
1933 * Type of the pointer that made the tap.
1934 * @property pointerType
1935 * @type String
1936 */
1937 (function(scope) {
1938 var dispatcher = scope.dispatcher;
1939 var eventFactory = scope.eventFactory;
1940 var pointermap = new scope.PointerMap();
1941 var tap = {
1942 events: [
1943 'down',
1944 'up'
1945 ],
1946 exposes: [
1947 'tap'
1948 ],
1949 down: function(inEvent) {
1950 if (inEvent.isPrimary && !inEvent.tapPrevented) {
1951 pointermap.set(inEvent.pointerId, {
1952 target: inEvent.target,
1953 buttons: inEvent.buttons,
1954 x: inEvent.clientX,
1955 y: inEvent.clientY
1956 });
1957 }
1958 },
1959 shouldTap: function(e, downState) {
1960 var tap = true;
1961 if (e.pointerType === 'mouse') {
1962 // only allow left click to tap for mouse
1963 tap = (e.buttons ^ 1) && (downState.buttons & 1);
1964 }
1965 return tap && !e.tapPrevented;
1966 },
1967 up: function(inEvent) {
1968 var start = pointermap.get(inEvent.pointerId);
1969 if (start && this.shouldTap(inEvent, start)) {
1970 // up.relatedTarget is target currently under finger
1971 var t = scope.targetFinding.LCA(start.target, inEvent.relatedTarget);
1972 if (t) {
1973 var e = eventFactory.makeGestureEvent('tap', {
1974 bubbles: true,
1975 cancelable: true,
1976 x: inEvent.clientX,
1977 y: inEvent.clientY,
1978 detail: inEvent.detail,
1979 pointerType: inEvent.pointerType,
1980 pointerId: inEvent.pointerId,
1981 altKey: inEvent.altKey,
1982 ctrlKey: inEvent.ctrlKey,
1983 metaKey: inEvent.metaKey,
1984 shiftKey: inEvent.shiftKey,
1985 _source: 'tap'
1986 });
1987 t.dispatchEvent(e);
1988 }
1989 }
1990 pointermap.delete(inEvent.pointerId);
1991 }
1992 };
1993 // patch eventFactory to remove id from tap's pointermap for preventTap calls
1994 eventFactory.preventTap = function(e) {
1995 return function() {
1996 e.tapPrevented = true;
1997 pointermap.delete(e.pointerId);
1998 };
1999 };
2000 dispatcher.registerGesture('tap', tap);
2001 })(window.PolymerGestures);
2002
2003 /*
2004 * Basic strategy: find the farthest apart points, use as diameter of circle
2005 * react to size change and rotation of the chord
2006 */
2007
2008 /**
2009 * @module pointer-gestures
2010 * @submodule Events
2011 * @class pinch
2012 */
2013 /**
2014 * Scale of the pinch zoom gesture
2015 * @property scale
2016 * @type Number
2017 */
2018 /**
2019 * Center X position of pointers causing pinch
2020 * @property centerX
2021 * @type Number
2022 */
2023 /**
2024 * Center Y position of pointers causing pinch
2025 * @property centerY
2026 * @type Number
2027 */
2028
2029 /**
2030 * @module pointer-gestures
2031 * @submodule Events
2032 * @class rotate
2033 */
2034 /**
2035 * Angle (in degrees) of rotation. Measured from starting positions of pointers.
2036 * @property angle
2037 * @type Number
2038 */
2039 /**
2040 * Center X position of pointers causing rotation
2041 * @property centerX
2042 * @type Number
2043 */
2044 /**
2045 * Center Y position of pointers causing rotation
2046 * @property centerY
2047 * @type Number
2048 */
2049 (function(scope) {
2050 var dispatcher = scope.dispatcher;
2051 var eventFactory = scope.eventFactory;
2052 var pointermap = new scope.PointerMap();
2053 var RAD_TO_DEG = 180 / Math.PI;
2054 var pinch = {
2055 events: [
2056 'down',
2057 'up',
2058 'move',
2059 'cancel'
2060 ],
2061 exposes: [
2062 'pinchstart',
2063 'pinch',
2064 'pinchend',
2065 'rotate'
2066 ],
2067 defaultActions: {
2068 'pinch': 'none',
2069 'rotate': 'none'
2070 },
2071 reference: {},
2072 down: function(inEvent) {
2073 pointermap.set(inEvent.pointerId, inEvent);
2074 if (pointermap.pointers() == 2) {
2075 var points = this.calcChord();
2076 var angle = this.calcAngle(points);
2077 this.reference = {
2078 angle: angle,
2079 diameter: points.diameter,
2080 target: scope.targetFinding.LCA(points.a.target, points.b.target)
2081 };
2082
2083 this.firePinch('pinchstart', points.diameter, points);
2084 }
2085 },
2086 up: function(inEvent) {
2087 var p = pointermap.get(inEvent.pointerId);
2088 var num = pointermap.pointers();
2089 if (p) {
2090 if (num === 2) {
2091 // fire 'pinchend' before deleting pointer
2092 var points = this.calcChord();
2093 this.firePinch('pinchend', points.diameter, points);
2094 }
2095 pointermap.delete(inEvent.pointerId);
2096 }
2097 },
2098 move: function(inEvent) {
2099 if (pointermap.has(inEvent.pointerId)) {
2100 pointermap.set(inEvent.pointerId, inEvent);
2101 if (pointermap.pointers() > 1) {
2102 this.calcPinchRotate();
2103 }
2104 }
2105 },
2106 cancel: function(inEvent) {
2107 this.up(inEvent);
2108 },
2109 firePinch: function(type, diameter, points) {
2110 var zoom = diameter / this.reference.diameter;
2111 var e = eventFactory.makeGestureEvent(type, {
2112 bubbles: true,
2113 cancelable: true,
2114 scale: zoom,
2115 centerX: points.center.x,
2116 centerY: points.center.y,
2117 _source: 'pinch'
2118 });
2119 this.reference.target.dispatchEvent(e);
2120 },
2121 fireRotate: function(angle, points) {
2122 var diff = Math.round((angle - this.reference.angle) % 360);
2123 var e = eventFactory.makeGestureEvent('rotate', {
2124 bubbles: true,
2125 cancelable: true,
2126 angle: diff,
2127 centerX: points.center.x,
2128 centerY: points.center.y,
2129 _source: 'pinch'
2130 });
2131 this.reference.target.dispatchEvent(e);
2132 },
2133 calcPinchRotate: function() {
2134 var points = this.calcChord();
2135 var diameter = points.diameter;
2136 var angle = this.calcAngle(points);
2137 if (diameter != this.reference.diameter) {
2138 this.firePinch('pinch', diameter, points);
2139 }
2140 if (angle != this.reference.angle) {
2141 this.fireRotate(angle, points);
2142 }
2143 },
2144 calcChord: function() {
2145 var pointers = [];
2146 pointermap.forEach(function(p) {
2147 pointers.push(p);
2148 });
2149 var dist = 0;
2150 // start with at least two pointers
2151 var points = {a: pointers[0], b: pointers[1]};
2152 var x, y, d;
2153 for (var i = 0; i < pointers.length; i++) {
2154 var a = pointers[i];
2155 for (var j = i + 1; j < pointers.length; j++) {
2156 var b = pointers[j];
2157 x = Math.abs(a.clientX - b.clientX);
2158 y = Math.abs(a.clientY - b.clientY);
2159 d = x + y;
2160 if (d > dist) {
2161 dist = d;
2162 points = {a: a, b: b};
2163 }
2164 }
2165 }
2166 x = Math.abs(points.a.clientX + points.b.clientX) / 2;
2167 y = Math.abs(points.a.clientY + points.b.clientY) / 2;
2168 points.center = { x: x, y: y };
2169 points.diameter = dist;
2170 return points;
2171 },
2172 calcAngle: function(points) {
2173 var x = points.a.clientX - points.b.clientX;
2174 var y = points.a.clientY - points.b.clientY;
2175 return (360 + Math.atan2(y, x) * RAD_TO_DEG) % 360;
2176 }
2177 };
2178 dispatcher.registerGesture('pinch', pinch);
2179 })(window.PolymerGestures);
2180
2181 (function (global) {
2182 'use strict';
2183
2184 var Token,
2185 TokenName,
2186 Syntax,
2187 Messages,
2188 source,
2189 index,
2190 length,
2191 delegate,
2192 lookahead,
2193 state;
2194
2195 Token = {
2196 BooleanLiteral: 1,
2197 EOF: 2,
2198 Identifier: 3,
2199 Keyword: 4,
2200 NullLiteral: 5,
2201 NumericLiteral: 6,
2202 Punctuator: 7,
2203 StringLiteral: 8
2204 };
2205
2206 TokenName = {};
2207 TokenName[Token.BooleanLiteral] = 'Boolean';
2208 TokenName[Token.EOF] = '<end>';
2209 TokenName[Token.Identifier] = 'Identifier';
2210 TokenName[Token.Keyword] = 'Keyword';
2211 TokenName[Token.NullLiteral] = 'Null';
2212 TokenName[Token.NumericLiteral] = 'Numeric';
2213 TokenName[Token.Punctuator] = 'Punctuator';
2214 TokenName[Token.StringLiteral] = 'String';
2215
2216 Syntax = {
2217 ArrayExpression: 'ArrayExpression',
2218 BinaryExpression: 'BinaryExpression',
2219 CallExpression: 'CallExpression',
2220 ConditionalExpression: 'ConditionalExpression',
2221 EmptyStatement: 'EmptyStatement',
2222 ExpressionStatement: 'ExpressionStatement',
2223 Identifier: 'Identifier',
2224 Literal: 'Literal',
2225 LabeledStatement: 'LabeledStatement',
2226 LogicalExpression: 'LogicalExpression',
2227 MemberExpression: 'MemberExpression',
2228 ObjectExpression: 'ObjectExpression',
2229 Program: 'Program',
2230 Property: 'Property',
2231 ThisExpression: 'ThisExpression',
2232 UnaryExpression: 'UnaryExpression'
2233 };
2234
2235 // Error messages should be identical to V8.
2236 Messages = {
2237 UnexpectedToken: 'Unexpected token %0',
2238 UnknownLabel: 'Undefined label \'%0\'',
2239 Redeclaration: '%0 \'%1\' has already been declared'
2240 };
2241
2242 // Ensure the condition is true, otherwise throw an error.
2243 // This is only to have a better contract semantic, i.e. another safety net
2244 // to catch a logic error. The condition shall be fulfilled in normal case.
2245 // Do NOT use this to enforce a certain condition on any user input.
2246
2247 function assert(condition, message) {
2248 if (!condition) {
2249 throw new Error('ASSERT: ' + message);
2250 }
2251 }
2252
2253 function isDecimalDigit(ch) {
2254 return (ch >= 48 && ch <= 57); // 0..9
2255 }
2256
2257
2258 // 7.2 White Space
2259
2260 function isWhiteSpace(ch) {
2261 return (ch === 32) || // space
2262 (ch === 9) || // tab
2263 (ch === 0xB) ||
2264 (ch === 0xC) ||
2265 (ch === 0xA0) ||
2266 (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u 2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(String.fromCharCod e(ch)) > 0);
2267 }
2268
2269 // 7.3 Line Terminators
2270
2271 function isLineTerminator(ch) {
2272 return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029);
2273 }
2274
2275 // 7.6 Identifier Names and Identifiers
2276
2277 function isIdentifierStart(ch) {
2278 return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore)
2279 (ch >= 65 && ch <= 90) || // A..Z
2280 (ch >= 97 && ch <= 122); // a..z
2281 }
2282
2283 function isIdentifierPart(ch) {
2284 return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore)
2285 (ch >= 65 && ch <= 90) || // A..Z
2286 (ch >= 97 && ch <= 122) || // a..z
2287 (ch >= 48 && ch <= 57); // 0..9
2288 }
2289
2290 // 7.6.1.1 Keywords
2291
2292 function isKeyword(id) {
2293 return (id === 'this')
2294 }
2295
2296 // 7.4 Comments
2297
2298 function skipWhitespace() {
2299 while (index < length && isWhiteSpace(source.charCodeAt(index))) {
2300 ++index;
2301 }
2302 }
2303
2304 function getIdentifier() {
2305 var start, ch;
2306
2307 start = index++;
2308 while (index < length) {
2309 ch = source.charCodeAt(index);
2310 if (isIdentifierPart(ch)) {
2311 ++index;
2312 } else {
2313 break;
2314 }
2315 }
2316
2317 return source.slice(start, index);
2318 }
2319
2320 function scanIdentifier() {
2321 var start, id, type;
2322
2323 start = index;
2324
2325 id = getIdentifier();
2326
2327 // There is no keyword or literal with only one character.
2328 // Thus, it must be an identifier.
2329 if (id.length === 1) {
2330 type = Token.Identifier;
2331 } else if (isKeyword(id)) {
2332 type = Token.Keyword;
2333 } else if (id === 'null') {
2334 type = Token.NullLiteral;
2335 } else if (id === 'true' || id === 'false') {
2336 type = Token.BooleanLiteral;
2337 } else {
2338 type = Token.Identifier;
2339 }
2340
2341 return {
2342 type: type,
2343 value: id,
2344 range: [start, index]
2345 };
2346 }
2347
2348
2349 // 7.7 Punctuators
2350
2351 function scanPunctuator() {
2352 var start = index,
2353 code = source.charCodeAt(index),
2354 code2,
2355 ch1 = source[index],
2356 ch2;
2357
2358 switch (code) {
2359
2360 // Check for most common single-character punctuators.
2361 case 46: // . dot
2362 case 40: // ( open bracket
2363 case 41: // ) close bracket
2364 case 59: // ; semicolon
2365 case 44: // , comma
2366 case 123: // { open curly brace
2367 case 125: // } close curly brace
2368 case 91: // [
2369 case 93: // ]
2370 case 58: // :
2371 case 63: // ?
2372 ++index;
2373 return {
2374 type: Token.Punctuator,
2375 value: String.fromCharCode(code),
2376 range: [start, index]
2377 };
2378
2379 default:
2380 code2 = source.charCodeAt(index + 1);
2381
2382 // '=' (char #61) marks an assignment or comparison operator.
2383 if (code2 === 61) {
2384 switch (code) {
2385 case 37: // %
2386 case 38: // &
2387 case 42: // *:
2388 case 43: // +
2389 case 45: // -
2390 case 47: // /
2391 case 60: // <
2392 case 62: // >
2393 case 124: // |
2394 index += 2;
2395 return {
2396 type: Token.Punctuator,
2397 value: String.fromCharCode(code) + String.fromCharCode(c ode2),
2398 range: [start, index]
2399 };
2400
2401 case 33: // !
2402 case 61: // =
2403 index += 2;
2404
2405 // !== and ===
2406 if (source.charCodeAt(index) === 61) {
2407 ++index;
2408 }
2409 return {
2410 type: Token.Punctuator,
2411 value: source.slice(start, index),
2412 range: [start, index]
2413 };
2414 default:
2415 break;
2416 }
2417 }
2418 break;
2419 }
2420
2421 // Peek more characters.
2422
2423 ch2 = source[index + 1];
2424
2425 // Other 2-character punctuators: && ||
2426
2427 if (ch1 === ch2 && ('&|'.indexOf(ch1) >= 0)) {
2428 index += 2;
2429 return {
2430 type: Token.Punctuator,
2431 value: ch1 + ch2,
2432 range: [start, index]
2433 };
2434 }
2435
2436 if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) {
2437 ++index;
2438 return {
2439 type: Token.Punctuator,
2440 value: ch1,
2441 range: [start, index]
2442 };
2443 }
2444
2445 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
2446 }
2447
2448 // 7.8.3 Numeric Literals
2449 function scanNumericLiteral() {
2450 var number, start, ch;
2451
2452 ch = source[index];
2453 assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'),
2454 'Numeric literal must start with a decimal digit or a decimal point' );
2455
2456 start = index;
2457 number = '';
2458 if (ch !== '.') {
2459 number = source[index++];
2460 ch = source[index];
2461
2462 // Hex number starts with '0x'.
2463 // Octal number starts with '0'.
2464 if (number === '0') {
2465 // decimal number starts with '0' such as '09' is illegal.
2466 if (ch && isDecimalDigit(ch.charCodeAt(0))) {
2467 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
2468 }
2469 }
2470
2471 while (isDecimalDigit(source.charCodeAt(index))) {
2472 number += source[index++];
2473 }
2474 ch = source[index];
2475 }
2476
2477 if (ch === '.') {
2478 number += source[index++];
2479 while (isDecimalDigit(source.charCodeAt(index))) {
2480 number += source[index++];
2481 }
2482 ch = source[index];
2483 }
2484
2485 if (ch === 'e' || ch === 'E') {
2486 number += source[index++];
2487
2488 ch = source[index];
2489 if (ch === '+' || ch === '-') {
2490 number += source[index++];
2491 }
2492 if (isDecimalDigit(source.charCodeAt(index))) {
2493 while (isDecimalDigit(source.charCodeAt(index))) {
2494 number += source[index++];
2495 }
2496 } else {
2497 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
2498 }
2499 }
2500
2501 if (isIdentifierStart(source.charCodeAt(index))) {
2502 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
2503 }
2504
2505 return {
2506 type: Token.NumericLiteral,
2507 value: parseFloat(number),
2508 range: [start, index]
2509 };
2510 }
2511
2512 // 7.8.4 String Literals
2513
2514 function scanStringLiteral() {
2515 var str = '', quote, start, ch, octal = false;
2516
2517 quote = source[index];
2518 assert((quote === '\'' || quote === '"'),
2519 'String literal must starts with a quote');
2520
2521 start = index;
2522 ++index;
2523
2524 while (index < length) {
2525 ch = source[index++];
2526
2527 if (ch === quote) {
2528 quote = '';
2529 break;
2530 } else if (ch === '\\') {
2531 ch = source[index++];
2532 if (!ch || !isLineTerminator(ch.charCodeAt(0))) {
2533 switch (ch) {
2534 case 'n':
2535 str += '\n';
2536 break;
2537 case 'r':
2538 str += '\r';
2539 break;
2540 case 't':
2541 str += '\t';
2542 break;
2543 case 'b':
2544 str += '\b';
2545 break;
2546 case 'f':
2547 str += '\f';
2548 break;
2549 case 'v':
2550 str += '\x0B';
2551 break;
2552
2553 default:
2554 str += ch;
2555 break;
2556 }
2557 } else {
2558 if (ch === '\r' && source[index] === '\n') {
2559 ++index;
2560 }
2561 }
2562 } else if (isLineTerminator(ch.charCodeAt(0))) {
2563 break;
2564 } else {
2565 str += ch;
2566 }
2567 }
2568
2569 if (quote !== '') {
2570 throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
2571 }
2572
2573 return {
2574 type: Token.StringLiteral,
2575 value: str,
2576 octal: octal,
2577 range: [start, index]
2578 };
2579 }
2580
2581 function isIdentifierName(token) {
2582 return token.type === Token.Identifier ||
2583 token.type === Token.Keyword ||
2584 token.type === Token.BooleanLiteral ||
2585 token.type === Token.NullLiteral;
2586 }
2587
2588 function advance() {
2589 var ch;
2590
2591 skipWhitespace();
2592
2593 if (index >= length) {
2594 return {
2595 type: Token.EOF,
2596 range: [index, index]
2597 };
2598 }
2599
2600 ch = source.charCodeAt(index);
2601
2602 // Very common: ( and ) and ;
2603 if (ch === 40 || ch === 41 || ch === 58) {
2604 return scanPunctuator();
2605 }
2606
2607 // String literal starts with single quote (#39) or double quote (#34).
2608 if (ch === 39 || ch === 34) {
2609 return scanStringLiteral();
2610 }
2611
2612 if (isIdentifierStart(ch)) {
2613 return scanIdentifier();
2614 }
2615
2616 // Dot (.) char #46 can also start a floating-point number, hence the ne ed
2617 // to check the next character.
2618 if (ch === 46) {
2619 if (isDecimalDigit(source.charCodeAt(index + 1))) {
2620 return scanNumericLiteral();
2621 }
2622 return scanPunctuator();
2623 }
2624
2625 if (isDecimalDigit(ch)) {
2626 return scanNumericLiteral();
2627 }
2628
2629 return scanPunctuator();
2630 }
2631
2632 function lex() {
2633 var token;
2634
2635 token = lookahead;
2636 index = token.range[1];
2637
2638 lookahead = advance();
2639
2640 index = token.range[1];
2641
2642 return token;
2643 }
2644
2645 function peek() {
2646 var pos;
2647
2648 pos = index;
2649 lookahead = advance();
2650 index = pos;
2651 }
2652
2653 // Throw an exception
2654
2655 function throwError(token, messageFormat) {
2656 var error,
2657 args = Array.prototype.slice.call(arguments, 2),
2658 msg = messageFormat.replace(
2659 /%(\d)/g,
2660 function (whole, index) {
2661 assert(index < args.length, 'Message reference must be in ra nge');
2662 return args[index];
2663 }
2664 );
2665
2666 error = new Error(msg);
2667 error.index = index;
2668 error.description = msg;
2669 throw error;
2670 }
2671
2672 // Throw an exception because of the token.
2673
2674 function throwUnexpected(token) {
2675 throwError(token, Messages.UnexpectedToken, token.value);
2676 }
2677
2678 // Expect the next token to match the specified punctuator.
2679 // If not, an exception will be thrown.
2680
2681 function expect(value) {
2682 var token = lex();
2683 if (token.type !== Token.Punctuator || token.value !== value) {
2684 throwUnexpected(token);
2685 }
2686 }
2687
2688 // Return true if the next token matches the specified punctuator.
2689
2690 function match(value) {
2691 return lookahead.type === Token.Punctuator && lookahead.value === value;
2692 }
2693
2694 // Return true if the next token matches the specified keyword
2695
2696 function matchKeyword(keyword) {
2697 return lookahead.type === Token.Keyword && lookahead.value === keyword;
2698 }
2699
2700 function consumeSemicolon() {
2701 // Catch the very common case first: immediately a semicolon (char #59).
2702 if (source.charCodeAt(index) === 59) {
2703 lex();
2704 return;
2705 }
2706
2707 skipWhitespace();
2708
2709 if (match(';')) {
2710 lex();
2711 return;
2712 }
2713
2714 if (lookahead.type !== Token.EOF && !match('}')) {
2715 throwUnexpected(lookahead);
2716 }
2717 }
2718
2719 // 11.1.4 Array Initialiser
2720
2721 function parseArrayInitialiser() {
2722 var elements = [];
2723
2724 expect('[');
2725
2726 while (!match(']')) {
2727 if (match(',')) {
2728 lex();
2729 elements.push(null);
2730 } else {
2731 elements.push(parseExpression());
2732
2733 if (!match(']')) {
2734 expect(',');
2735 }
2736 }
2737 }
2738
2739 expect(']');
2740
2741 return delegate.createArrayExpression(elements);
2742 }
2743
2744 // 11.1.5 Object Initialiser
2745
2746 function parseObjectPropertyKey() {
2747 var token;
2748
2749 skipWhitespace();
2750 token = lex();
2751
2752 // Note: This function is called only from parseObjectProperty(), where
2753 // EOF and Punctuator tokens are already filtered out.
2754 if (token.type === Token.StringLiteral || token.type === Token.NumericLi teral) {
2755 return delegate.createLiteral(token);
2756 }
2757
2758 return delegate.createIdentifier(token.value);
2759 }
2760
2761 function parseObjectProperty() {
2762 var token, key;
2763
2764 token = lookahead;
2765 skipWhitespace();
2766
2767 if (token.type === Token.EOF || token.type === Token.Punctuator) {
2768 throwUnexpected(token);
2769 }
2770
2771 key = parseObjectPropertyKey();
2772 expect(':');
2773 return delegate.createProperty('init', key, parseExpression());
2774 }
2775
2776 function parseObjectInitialiser() {
2777 var properties = [];
2778
2779 expect('{');
2780
2781 while (!match('}')) {
2782 properties.push(parseObjectProperty());
2783
2784 if (!match('}')) {
2785 expect(',');
2786 }
2787 }
2788
2789 expect('}');
2790
2791 return delegate.createObjectExpression(properties);
2792 }
2793
2794 // 11.1.6 The Grouping Operator
2795
2796 function parseGroupExpression() {
2797 var expr;
2798
2799 expect('(');
2800
2801 expr = parseExpression();
2802
2803 expect(')');
2804
2805 return expr;
2806 }
2807
2808
2809 // 11.1 Primary Expressions
2810
2811 function parsePrimaryExpression() {
2812 var type, token, expr;
2813
2814 if (match('(')) {
2815 return parseGroupExpression();
2816 }
2817
2818 type = lookahead.type;
2819
2820 if (type === Token.Identifier) {
2821 expr = delegate.createIdentifier(lex().value);
2822 } else if (type === Token.StringLiteral || type === Token.NumericLiteral ) {
2823 expr = delegate.createLiteral(lex());
2824 } else if (type === Token.Keyword) {
2825 if (matchKeyword('this')) {
2826 lex();
2827 expr = delegate.createThisExpression();
2828 }
2829 } else if (type === Token.BooleanLiteral) {
2830 token = lex();
2831 token.value = (token.value === 'true');
2832 expr = delegate.createLiteral(token);
2833 } else if (type === Token.NullLiteral) {
2834 token = lex();
2835 token.value = null;
2836 expr = delegate.createLiteral(token);
2837 } else if (match('[')) {
2838 expr = parseArrayInitialiser();
2839 } else if (match('{')) {
2840 expr = parseObjectInitialiser();
2841 }
2842
2843 if (expr) {
2844 return expr;
2845 }
2846
2847 throwUnexpected(lex());
2848 }
2849
2850 // 11.2 Left-Hand-Side Expressions
2851
2852 function parseArguments() {
2853 var args = [];
2854
2855 expect('(');
2856
2857 if (!match(')')) {
2858 while (index < length) {
2859 args.push(parseExpression());
2860 if (match(')')) {
2861 break;
2862 }
2863 expect(',');
2864 }
2865 }
2866
2867 expect(')');
2868
2869 return args;
2870 }
2871
2872 function parseNonComputedProperty() {
2873 var token;
2874
2875 token = lex();
2876
2877 if (!isIdentifierName(token)) {
2878 throwUnexpected(token);
2879 }
2880
2881 return delegate.createIdentifier(token.value);
2882 }
2883
2884 function parseNonComputedMember() {
2885 expect('.');
2886
2887 return parseNonComputedProperty();
2888 }
2889
2890 function parseComputedMember() {
2891 var expr;
2892
2893 expect('[');
2894
2895 expr = parseExpression();
2896
2897 expect(']');
2898
2899 return expr;
2900 }
2901
2902 function parseLeftHandSideExpression() {
2903 var expr, args, property;
2904
2905 expr = parsePrimaryExpression();
2906
2907 while (true) {
2908 if (match('[')) {
2909 property = parseComputedMember();
2910 expr = delegate.createMemberExpression('[', expr, property);
2911 } else if (match('.')) {
2912 property = parseNonComputedMember();
2913 expr = delegate.createMemberExpression('.', expr, property);
2914 } else if (match('(')) {
2915 args = parseArguments();
2916 expr = delegate.createCallExpression(expr, args);
2917 } else {
2918 break;
2919 }
2920 }
2921
2922 return expr;
2923 }
2924
2925 // 11.3 Postfix Expressions
2926
2927 var parsePostfixExpression = parseLeftHandSideExpression;
2928
2929 // 11.4 Unary Operators
2930
2931 function parseUnaryExpression() {
2932 var token, expr;
2933
2934 if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyw ord) {
2935 expr = parsePostfixExpression();
2936 } else if (match('+') || match('-') || match('!')) {
2937 token = lex();
2938 expr = parseUnaryExpression();
2939 expr = delegate.createUnaryExpression(token.value, expr);
2940 } else if (matchKeyword('delete') || matchKeyword('void') || matchKeywor d('typeof')) {
2941 throwError({}, Messages.UnexpectedToken);
2942 } else {
2943 expr = parsePostfixExpression();
2944 }
2945
2946 return expr;
2947 }
2948
2949 function binaryPrecedence(token) {
2950 var prec = 0;
2951
2952 if (token.type !== Token.Punctuator && token.type !== Token.Keyword) {
2953 return 0;
2954 }
2955
2956 switch (token.value) {
2957 case '||':
2958 prec = 1;
2959 break;
2960
2961 case '&&':
2962 prec = 2;
2963 break;
2964
2965 case '==':
2966 case '!=':
2967 case '===':
2968 case '!==':
2969 prec = 6;
2970 break;
2971
2972 case '<':
2973 case '>':
2974 case '<=':
2975 case '>=':
2976 case 'instanceof':
2977 prec = 7;
2978 break;
2979
2980 case 'in':
2981 prec = 7;
2982 break;
2983
2984 case '+':
2985 case '-':
2986 prec = 9;
2987 break;
2988
2989 case '*':
2990 case '/':
2991 case '%':
2992 prec = 11;
2993 break;
2994
2995 default:
2996 break;
2997 }
2998
2999 return prec;
3000 }
3001
3002 // 11.5 Multiplicative Operators
3003 // 11.6 Additive Operators
3004 // 11.7 Bitwise Shift Operators
3005 // 11.8 Relational Operators
3006 // 11.9 Equality Operators
3007 // 11.10 Binary Bitwise Operators
3008 // 11.11 Binary Logical Operators
3009
3010 function parseBinaryExpression() {
3011 var expr, token, prec, stack, right, operator, left, i;
3012
3013 left = parseUnaryExpression();
3014
3015 token = lookahead;
3016 prec = binaryPrecedence(token);
3017 if (prec === 0) {
3018 return left;
3019 }
3020 token.prec = prec;
3021 lex();
3022
3023 right = parseUnaryExpression();
3024
3025 stack = [left, token, right];
3026
3027 while ((prec = binaryPrecedence(lookahead)) > 0) {
3028
3029 // Reduce: make a binary expression from the three topmost entries.
3030 while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) {
3031 right = stack.pop();
3032 operator = stack.pop().value;
3033 left = stack.pop();
3034 expr = delegate.createBinaryExpression(operator, left, right);
3035 stack.push(expr);
3036 }
3037
3038 // Shift.
3039 token = lex();
3040 token.prec = prec;
3041 stack.push(token);
3042 expr = parseUnaryExpression();
3043 stack.push(expr);
3044 }
3045
3046 // Final reduce to clean-up the stack.
3047 i = stack.length - 1;
3048 expr = stack[i];
3049 while (i > 1) {
3050 expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i - 2], expr);
3051 i -= 2;
3052 }
3053
3054 return expr;
3055 }
3056
3057
3058 // 11.12 Conditional Operator
3059
3060 function parseConditionalExpression() {
3061 var expr, consequent, alternate;
3062
3063 expr = parseBinaryExpression();
3064
3065 if (match('?')) {
3066 lex();
3067 consequent = parseConditionalExpression();
3068 expect(':');
3069 alternate = parseConditionalExpression();
3070
3071 expr = delegate.createConditionalExpression(expr, consequent, altern ate);
3072 }
3073
3074 return expr;
3075 }
3076
3077 // Simplification since we do not support AssignmentExpression.
3078 var parseExpression = parseConditionalExpression;
3079
3080 // Polymer Syntax extensions
3081
3082 // Filter ::
3083 // Identifier
3084 // Identifier "(" ")"
3085 // Identifier "(" FilterArguments ")"
3086
3087 function parseFilter() {
3088 var identifier, args;
3089
3090 identifier = lex();
3091
3092 if (identifier.type !== Token.Identifier) {
3093 throwUnexpected(identifier);
3094 }
3095
3096 args = match('(') ? parseArguments() : [];
3097
3098 return delegate.createFilter(identifier.value, args);
3099 }
3100
3101 // Filters ::
3102 // "|" Filter
3103 // Filters "|" Filter
3104
3105 function parseFilters() {
3106 while (match('|')) {
3107 lex();
3108 parseFilter();
3109 }
3110 }
3111
3112 // TopLevel ::
3113 // LabelledExpressions
3114 // AsExpression
3115 // InExpression
3116 // FilterExpression
3117
3118 // AsExpression ::
3119 // FilterExpression as Identifier
3120
3121 // InExpression ::
3122 // Identifier, Identifier in FilterExpression
3123 // Identifier in FilterExpression
3124
3125 // FilterExpression ::
3126 // Expression
3127 // Expression Filters
3128
3129 function parseTopLevel() {
3130 skipWhitespace();
3131 peek();
3132
3133 var expr = parseExpression();
3134 if (expr) {
3135 if (lookahead.value === ',' || lookahead.value == 'in' &&
3136 expr.type === Syntax.Identifier) {
3137 parseInExpression(expr);
3138 } else {
3139 parseFilters();
3140 if (lookahead.value === 'as') {
3141 parseAsExpression(expr);
3142 } else {
3143 delegate.createTopLevel(expr);
3144 }
3145 }
3146 }
3147
3148 if (lookahead.type !== Token.EOF) {
3149 throwUnexpected(lookahead);
3150 }
3151 }
3152
3153 function parseAsExpression(expr) {
3154 lex(); // as
3155 var identifier = lex().value;
3156 delegate.createAsExpression(expr, identifier);
3157 }
3158
3159 function parseInExpression(identifier) {
3160 var indexName;
3161 if (lookahead.value === ',') {
3162 lex();
3163 if (lookahead.type !== Token.Identifier)
3164 throwUnexpected(lookahead);
3165 indexName = lex().value;
3166 }
3167
3168 lex(); // in
3169 var expr = parseExpression();
3170 parseFilters();
3171 delegate.createInExpression(identifier.name, indexName, expr);
3172 }
3173
3174 function parse(code, inDelegate) {
3175 delegate = inDelegate;
3176 source = code;
3177 index = 0;
3178 length = source.length;
3179 lookahead = null;
3180 state = {
3181 labelSet: {}
3182 };
3183
3184 return parseTopLevel();
3185 }
3186
3187 global.esprima = {
3188 parse: parse
3189 };
3190 })(this);
3191
3192 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
3193 // This code may only be used under the BSD style license found at http://polyme r.github.io/LICENSE.txt
3194 // The complete set of authors may be found at http://polymer.github.io/AUTHORS. txt
3195 // The complete set of contributors may be found at http://polymer.github.io/CON TRIBUTORS.txt
3196 // Code distributed by Google as part of the polymer project is also
3197 // subject to an additional IP rights grant found at http://polymer.github.io/PA TENTS.txt
3198
3199 (function (global) {
3200 'use strict';
3201
3202 function prepareBinding(expressionText, name, node, filterRegistry) {
3203 var expression;
3204 try {
3205 expression = getExpression(expressionText);
3206 if (expression.scopeIdent &&
3207 (node.nodeType !== Node.ELEMENT_NODE ||
3208 node.tagName !== 'TEMPLATE' ||
3209 (name !== 'bind' && name !== 'repeat'))) {
3210 throw Error('as and in can only be used within <template bind/repeat>');
3211 }
3212 } catch (ex) {
3213 console.error('Invalid expression syntax: ' + expressionText, ex);
3214 return;
3215 }
3216
3217 return function(model, node, oneTime) {
3218 var binding = expression.getBinding(model, filterRegistry, oneTime);
3219 if (expression.scopeIdent && binding) {
3220 node.polymerExpressionScopeIdent_ = expression.scopeIdent;
3221 if (expression.indexIdent)
3222 node.polymerExpressionIndexIdent_ = expression.indexIdent;
3223 }
3224
3225 return binding;
3226 }
3227 }
3228
3229 // TODO(rafaelw): Implement simple LRU.
3230 var expressionParseCache = Object.create(null);
3231
3232 function getExpression(expressionText) {
3233 var expression = expressionParseCache[expressionText];
3234 if (!expression) {
3235 var delegate = new ASTDelegate();
3236 esprima.parse(expressionText, delegate);
3237 expression = new Expression(delegate);
3238 expressionParseCache[expressionText] = expression;
3239 }
3240 return expression;
3241 }
3242
3243 function Literal(value) {
3244 this.value = value;
3245 this.valueFn_ = undefined;
3246 }
3247
3248 Literal.prototype = {
3249 valueFn: function() {
3250 if (!this.valueFn_) {
3251 var value = this.value;
3252 this.valueFn_ = function() {
3253 return value;
3254 }
3255 }
3256
3257 return this.valueFn_;
3258 }
3259 }
3260
3261 function IdentPath(name) {
3262 this.name = name;
3263 this.path = Path.get(name);
3264 }
3265
3266 IdentPath.prototype = {
3267 valueFn: function() {
3268 if (!this.valueFn_) {
3269 var name = this.name;
3270 var path = this.path;
3271 this.valueFn_ = function(model, observer) {
3272 if (observer)
3273 observer.addPath(model, path);
3274
3275 return path.getValueFrom(model);
3276 }
3277 }
3278
3279 return this.valueFn_;
3280 },
3281
3282 setValue: function(model, newValue) {
3283 if (this.path.length == 1)
3284 model = findScope(model, this.path[0]);
3285
3286 return this.path.setValueFrom(model, newValue);
3287 }
3288 };
3289
3290 function MemberExpression(object, property, accessor) {
3291 this.computed = accessor == '[';
3292
3293 this.dynamicDeps = typeof object == 'function' ||
3294 object.dynamicDeps ||
3295 (this.computed && !(property instanceof Literal));
3296
3297 this.simplePath =
3298 !this.dynamicDeps &&
3299 (property instanceof IdentPath || property instanceof Literal) &&
3300 (object instanceof MemberExpression || object instanceof IdentPath);
3301
3302 this.object = this.simplePath ? object : getFn(object);
3303 this.property = !this.computed || this.simplePath ?
3304 property : getFn(property);
3305 }
3306
3307 MemberExpression.prototype = {
3308 get fullPath() {
3309 if (!this.fullPath_) {
3310
3311 var parts = this.object instanceof MemberExpression ?
3312 this.object.fullPath.slice() : [this.object.name];
3313 parts.push(this.property instanceof IdentPath ?
3314 this.property.name : this.property.value);
3315 this.fullPath_ = Path.get(parts);
3316 }
3317
3318 return this.fullPath_;
3319 },
3320
3321 valueFn: function() {
3322 if (!this.valueFn_) {
3323 var object = this.object;
3324
3325 if (this.simplePath) {
3326 var path = this.fullPath;
3327
3328 this.valueFn_ = function(model, observer) {
3329 if (observer)
3330 observer.addPath(model, path);
3331
3332 return path.getValueFrom(model);
3333 };
3334 } else if (!this.computed) {
3335 var path = Path.get(this.property.name);
3336
3337 this.valueFn_ = function(model, observer, filterRegistry) {
3338 var context = object(model, observer, filterRegistry);
3339
3340 if (observer)
3341 observer.addPath(context, path);
3342
3343 return path.getValueFrom(context);
3344 }
3345 } else {
3346 // Computed property.
3347 var property = this.property;
3348
3349 this.valueFn_ = function(model, observer, filterRegistry) {
3350 var context = object(model, observer, filterRegistry);
3351 var propName = property(model, observer, filterRegistry);
3352 if (observer)
3353 observer.addPath(context, [propName]);
3354
3355 return context ? context[propName] : undefined;
3356 };
3357 }
3358 }
3359 return this.valueFn_;
3360 },
3361
3362 setValue: function(model, newValue) {
3363 if (this.simplePath) {
3364 this.fullPath.setValueFrom(model, newValue);
3365 return newValue;
3366 }
3367
3368 var object = this.object(model);
3369 var propName = this.property instanceof IdentPath ? this.property.name :
3370 this.property(model);
3371 return object[propName] = newValue;
3372 }
3373 };
3374
3375 function Filter(name, args) {
3376 this.name = name;
3377 this.args = [];
3378 for (var i = 0; i < args.length; i++) {
3379 this.args[i] = getFn(args[i]);
3380 }
3381 }
3382
3383 Filter.prototype = {
3384 transform: function(model, observer, filterRegistry, toModelDirection,
3385 initialArgs) {
3386 var fn = filterRegistry[this.name];
3387 var context = model;
3388 if (fn) {
3389 context = undefined;
3390 } else {
3391 fn = context[this.name];
3392 if (!fn) {
3393 console.error('Cannot find function or filter: ' + this.name);
3394 return;
3395 }
3396 }
3397
3398 // If toModelDirection is falsey, then the "normal" (dom-bound) direction
3399 // is used. Otherwise, it looks for a 'toModel' property function on the
3400 // object.
3401 if (toModelDirection) {
3402 fn = fn.toModel;
3403 } else if (typeof fn.toDOM == 'function') {
3404 fn = fn.toDOM;
3405 }
3406
3407 if (typeof fn != 'function') {
3408 console.error('Cannot find function or filter: ' + this.name);
3409 return;
3410 }
3411
3412 var args = initialArgs || [];
3413 for (var i = 0; i < this.args.length; i++) {
3414 args.push(getFn(this.args[i])(model, observer, filterRegistry));
3415 }
3416
3417 return fn.apply(context, args);
3418 }
3419 };
3420
3421 function notImplemented() { throw Error('Not Implemented'); }
3422
3423 var unaryOperators = {
3424 '+': function(v) { return +v; },
3425 '-': function(v) { return -v; },
3426 '!': function(v) { return !v; }
3427 };
3428
3429 var binaryOperators = {
3430 '+': function(l, r) { return l+r; },
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 };
3446
3447 function getFn(arg) {
3448 return typeof arg == 'function' ? arg : arg.valueFn();
3449 }
3450
3451 function ASTDelegate() {
3452 this.expression = null;
3453 this.filters = [];
3454 this.deps = {};
3455 this.currentPath = undefined;
3456 this.scopeIdent = undefined;
3457 this.indexIdent = undefined;
3458 this.dynamicDeps = false;
3459 }
3460
3461 ASTDelegate.prototype = {
3462 createUnaryExpression: function(op, argument) {
3463 if (!unaryOperators[op])
3464 throw Error('Disallowed operator: ' + op);
3465
3466 argument = getFn(argument);
3467
3468 return function(model, observer, filterRegistry) {
3469 return unaryOperators[op](argument(model, observer, filterRegistry));
3470 };
3471 },
3472
3473 createBinaryExpression: function(op, left, right) {
3474 if (!binaryOperators[op])
3475 throw Error('Disallowed operator: ' + op);
3476
3477 left = getFn(left);
3478 right = getFn(right);
3479
3480 switch (op) {
3481 case '||':
3482 this.dynamicDeps = true;
3483 return function(model, observer, filterRegistry) {
3484 return left(model, observer, filterRegistry) ||
3485 right(model, observer, filterRegistry);
3486 };
3487 case '&&':
3488 this.dynamicDeps = true;
3489 return function(model, observer, filterRegistry) {
3490 return left(model, observer, filterRegistry) &&
3491 right(model, observer, filterRegistry);
3492 };
3493 }
3494
3495 return function(model, observer, filterRegistry) {
3496 return binaryOperators[op](left(model, observer, filterRegistry),
3497 right(model, observer, filterRegistry));
3498 };
3499 },
3500
3501 createConditionalExpression: function(test, consequent, alternate) {
3502 test = getFn(test);
3503 consequent = getFn(consequent);
3504 alternate = getFn(alternate);
3505
3506 this.dynamicDeps = true;
3507
3508 return function(model, observer, filterRegistry) {
3509 return test(model, observer, filterRegistry) ?
3510 consequent(model, observer, filterRegistry) :
3511 alternate(model, observer, filterRegistry);
3512 }
3513 },
3514
3515 createIdentifier: function(name) {
3516 var ident = new IdentPath(name);
3517 ident.type = 'Identifier';
3518 return ident;
3519 },
3520
3521 createMemberExpression: function(accessor, object, property) {
3522 var ex = new MemberExpression(object, property, accessor);
3523 if (ex.dynamicDeps)
3524 this.dynamicDeps = true;
3525 return ex;
3526 },
3527
3528 createCallExpression: function(expression, args) {
3529 if (!(expression instanceof IdentPath))
3530 throw Error('Only identifier function invocations are allowed');
3531
3532 var filter = new Filter(expression.name, args);
3533
3534 return function(model, observer, filterRegistry) {
3535 return filter.transform(model, observer, filterRegistry, false);
3536 };
3537 },
3538
3539 createLiteral: function(token) {
3540 return new Literal(token.value);
3541 },
3542
3543 createArrayExpression: function(elements) {
3544 for (var i = 0; i < elements.length; i++)
3545 elements[i] = getFn(elements[i]);
3546
3547 return function(model, observer, filterRegistry) {
3548 var arr = []
3549 for (var i = 0; i < elements.length; i++)
3550 arr.push(elements[i](model, observer, filterRegistry));
3551 return arr;
3552 }
3553 },
3554
3555 createProperty: function(kind, key, value) {
3556 return {
3557 key: key instanceof IdentPath ? key.name : key.value,
3558 value: value
3559 };
3560 },
3561
3562 createObjectExpression: function(properties) {
3563 for (var i = 0; i < properties.length; i++)
3564 properties[i].value = getFn(properties[i].value);
3565
3566 return function(model, observer, filterRegistry) {
3567 var obj = {};
3568 for (var i = 0; i < properties.length; i++)
3569 obj[properties[i].key] =
3570 properties[i].value(model, observer, filterRegistry);
3571 return obj;
3572 }
3573 },
3574
3575 createFilter: function(name, args) {
3576 this.filters.push(new Filter(name, args));
3577 },
3578
3579 createAsExpression: function(expression, scopeIdent) {
3580 this.expression = expression;
3581 this.scopeIdent = scopeIdent;
3582 },
3583
3584 createInExpression: function(scopeIdent, indexIdent, expression) {
3585 this.expression = expression;
3586 this.scopeIdent = scopeIdent;
3587 this.indexIdent = indexIdent;
3588 },
3589
3590 createTopLevel: function(expression) {
3591 this.expression = expression;
3592 },
3593
3594 createThisExpression: notImplemented
3595 }
3596
3597 function ConstantObservable(value) {
3598 this.value_ = value;
3599 }
3600
3601 ConstantObservable.prototype = {
3602 open: function() { return this.value_; },
3603 discardChanges: function() { return this.value_; },
3604 deliver: function() {},
3605 close: function() {},
3606 }
3607
3608 function Expression(delegate) {
3609 this.scopeIdent = delegate.scopeIdent;
3610 this.indexIdent = delegate.indexIdent;
3611
3612 if (!delegate.expression)
3613 throw Error('No expression found.');
3614
3615 this.expression = delegate.expression;
3616 getFn(this.expression); // forces enumeration of path dependencies
3617
3618 this.filters = delegate.filters;
3619 this.dynamicDeps = delegate.dynamicDeps;
3620 }
3621
3622 Expression.prototype = {
3623 getBinding: function(model, filterRegistry, oneTime) {
3624 if (oneTime)
3625 return this.getValue(model, undefined, filterRegistry);
3626
3627 var observer = new CompoundObserver();
3628 // captures deps.
3629 var firstValue = this.getValue(model, observer, filterRegistry);
3630 var firstTime = true;
3631 var self = this;
3632
3633 function valueFn() {
3634 // deps cannot have changed on first value retrieval.
3635 if (firstTime) {
3636 firstTime = false;
3637 return firstValue;
3638 }
3639
3640 if (self.dynamicDeps)
3641 observer.startReset();
3642
3643 var value = self.getValue(model,
3644 self.dynamicDeps ? observer : undefined,
3645 filterRegistry);
3646 if (self.dynamicDeps)
3647 observer.finishReset();
3648
3649 return value;
3650 }
3651
3652 function setValueFn(newValue) {
3653 self.setValue(model, newValue, filterRegistry);
3654 return newValue;
3655 }
3656
3657 return new ObserverTransform(observer, valueFn, setValueFn, true);
3658 },
3659
3660 getValue: function(model, observer, filterRegistry) {
3661 var value = getFn(this.expression)(model, observer, filterRegistry);
3662 for (var i = 0; i < this.filters.length; i++) {
3663 value = this.filters[i].transform(model, observer, filterRegistry,
3664 false, [value]);
3665 }
3666
3667 return value;
3668 },
3669
3670 setValue: function(model, newValue, filterRegistry) {
3671 var count = this.filters ? this.filters.length : 0;
3672 while (count-- > 0) {
3673 newValue = this.filters[count].transform(model, undefined,
3674 filterRegistry, true, [newValue]);
3675 }
3676
3677 if (this.expression.setValue)
3678 return this.expression.setValue(model, newValue);
3679 }
3680 }
3681
3682 /**
3683 * Converts a style property name to a css property name. For example:
3684 * "WebkitUserSelect" to "-webkit-user-select"
3685 */
3686 function convertStylePropertyName(name) {
3687 return String(name).replace(/[A-Z]/g, function(c) {
3688 return '-' + c.toLowerCase();
3689 });
3690 }
3691
3692 var parentScopeName = '@' + Math.random().toString(36).slice(2);
3693
3694 // Single ident paths must bind directly to the appropriate scope object.
3695 // I.e. Pushed values in two-bindings need to be assigned to the actual model
3696 // object.
3697 function findScope(model, prop) {
3698 while (model[parentScopeName] &&
3699 !Object.prototype.hasOwnProperty.call(model, prop)) {
3700 model = model[parentScopeName];
3701 }
3702
3703 return model;
3704 }
3705
3706 function isLiteralExpression(pathString) {
3707 switch (pathString) {
3708 case '':
3709 return false;
3710
3711 case 'false':
3712 case 'null':
3713 case 'true':
3714 return true;
3715 }
3716
3717 if (!isNaN(Number(pathString)))
3718 return true;
3719
3720 return false;
3721 };
3722
3723 function PolymerExpressions() {}
3724
3725 PolymerExpressions.prototype = {
3726 // "built-in" filters
3727 styleObject: function(value) {
3728 var parts = [];
3729 for (var key in value) {
3730 parts.push(convertStylePropertyName(key) + ': ' + value[key]);
3731 }
3732 return parts.join('; ');
3733 },
3734
3735 tokenList: function(value) {
3736 var tokens = [];
3737 for (var key in value) {
3738 if (value[key])
3739 tokens.push(key);
3740 }
3741 return tokens.join(' ');
3742 },
3743
3744 // binding delegate API
3745 prepareInstancePositionChanged: function(template) {
3746 var indexIdent = template.polymerExpressionIndexIdent_;
3747 if (!indexIdent)
3748 return;
3749
3750 return function(templateInstance, index) {
3751 templateInstance.model[indexIdent] = index;
3752 };
3753 },
3754
3755 prepareBinding: function(pathString, name, node) {
3756 var path = Path.get(pathString);
3757
3758 if (!isLiteralExpression(pathString) && path.valid) {
3759 if (path.length == 1) {
3760 return function(model, node, oneTime) {
3761 if (oneTime)
3762 return path.getValueFrom(model);
3763
3764 var scope = findScope(model, path[0]);
3765 return new PathObserver(scope, path);
3766 };
3767 }
3768 return; // bail out early if pathString is simple path.
3769 }
3770
3771 return prepareBinding(pathString, name, node, this);
3772 },
3773
3774 prepareInstanceModel: function(template) {
3775 var scopeName = template.polymerExpressionScopeIdent_;
3776 if (!scopeName)
3777 return;
3778
3779 var parentScope = template.templateInstance ?
3780 template.templateInstance.model :
3781 template.model;
3782
3783 var indexName = template.polymerExpressionIndexIdent_;
3784
3785 return function(model) {
3786 return createScopeObject(parentScope, model, scopeName, indexName);
3787 };
3788 }
3789 };
3790
3791 var createScopeObject = ('__proto__' in {}) ?
3792 function(parentScope, model, scopeName, indexName) {
3793 var scope = {};
3794 scope[scopeName] = model;
3795 scope[indexName] = undefined;
3796 scope[parentScopeName] = parentScope;
3797 scope.__proto__ = parentScope;
3798 return scope;
3799 } :
3800 function(parentScope, model, scopeName, indexName) {
3801 var scope = Object.create(parentScope);
3802 Object.defineProperty(scope, scopeName,
3803 { value: model, configurable: true, writable: true });
3804 Object.defineProperty(scope, indexName,
3805 { value: undefined, configurable: true, writable: true });
3806 Object.defineProperty(scope, parentScopeName,
3807 { value: parentScope, configurable: true, writable: true });
3808 return scope;
3809 };
3810
3811 global.PolymerExpressions = PolymerExpressions;
3812 PolymerExpressions.getExpression = getExpression;
3813 })(this);
3814
3815 Polymer = {
3816 version: '0.5.1'
3817 };
3818
3819 // TODO(sorvell): this ensures Polymer is an object and not a function
3820 // Platform is currently defining it as a function to allow for async loading
3821 // of polymer; once we refine the loading process this likely goes away.
3822 if (typeof window.Polymer === 'function') {
3823 Polymer = {};
3824 }
3825
3826
3827 (function(scope) {
3828
3829 function withDependencies(task, depends) {
3830 depends = depends || [];
3831 if (!depends.map) {
3832 depends = [depends];
3833 }
3834 return task.apply(this, depends.map(marshal));
3835 }
3836
3837 function module(name, dependsOrFactory, moduleFactory) {
3838 var module;
3839 switch (arguments.length) {
3840 case 0:
3841 return;
3842 case 1:
3843 module = null;
3844 break;
3845 case 2:
3846 // dependsOrFactory is `factory` in this case
3847 module = dependsOrFactory.apply(this);
3848 break;
3849 default:
3850 // dependsOrFactory is `depends` in this case
3851 module = withDependencies(moduleFactory, dependsOrFactory);
3852 break;
3853 }
3854 modules[name] = module;
3855 };
3856
3857 function marshal(name) {
3858 return modules[name];
3859 }
3860
3861 var modules = {};
3862
3863 function using(depends, task) {
3864 HTMLImports.whenImportsReady(function() {
3865 withDependencies(task, depends);
3866 });
3867 };
3868
3869 // exports
3870
3871 scope.marshal = marshal;
3872 // `module` confuses commonjs detectors
3873 scope.modularize = module;
3874 scope.using = using;
3875
3876 })(window);
3877
3878 /*
3879 Build only script.
3880
3881 Ensures scripts needed for basic x-platform compatibility
3882 will be run when platform.js is not loaded.
3883 */
3884 if (!window.WebComponents) {
3885
3886 /*
3887 On supported platforms, platform.js is not needed. To retain compatibili ty
3888 with the polyfills, we stub out minimal functionality.
3889 */
3890 if (!window.WebComponents) {
3891
3892 WebComponents = {
3893 flush: function() {},
3894 flags: {log: {}}
3895 };
3896
3897 Platform = WebComponents;
3898
3899 CustomElements = {
3900 useNative: true,
3901 ready: true,
3902 takeRecords: function() {},
3903 instanceof: function(obj, base) {
3904 return obj instanceof base;
3905 }
3906 };
3907
3908 HTMLImports = {
3909 useNative: true
3910 };
3911
3912
3913 addEventListener('HTMLImportsLoaded', function() {
3914 document.dispatchEvent(
3915 new CustomEvent('WebComponentsReady', {bubbles: true})
3916 );
3917 });
3918
3919
3920 // ShadowDOM
3921 ShadowDOMPolyfill = null;
3922 wrap = unwrap = function(n){
3923 return n;
3924 };
3925
3926 }
3927
3928 /*
3929 Create polyfill scope and feature detect native support.
3930 */
3931 window.HTMLImports = window.HTMLImports || {flags:{}};
3932
3933 (function(scope) {
3934
3935 /**
3936 Basic setup and simple module executer. We collect modules and then execute
3937 the code later, only if it's necessary for polyfilling.
3938 */
3939 var IMPORT_LINK_TYPE = 'import';
3940 var useNative = Boolean(IMPORT_LINK_TYPE in document.createElement('link'));
3941
3942 /**
3943 Support `currentScript` on all browsers as `document._currentScript.`
3944
3945 NOTE: We cannot polyfill `document.currentScript` because it's not possible
3946 both to override and maintain the ability to capture the native value.
3947 Therefore we choose to expose `_currentScript` both when native imports
3948 and the polyfill are in use.
3949 */
3950 // NOTE: ShadowDOMPolyfill intrusion.
3951 var hasShadowDOMPolyfill = Boolean(window.ShadowDOMPolyfill);
3952 var wrap = function(node) {
3953 return hasShadowDOMPolyfill ? ShadowDOMPolyfill.wrapIfNeeded(node) : node;
3954 };
3955 var rootDocument = wrap(document);
3956
3957 var currentScriptDescriptor = {
3958 get: function() {
3959 var script = HTMLImports.currentScript || document.currentScript ||
3960 // NOTE: only works when called in synchronously executing code.
3961 // readyState should check if `loading` but IE10 is
3962 // interactive when scripts run so we cheat.
3963 (document.readyState !== 'complete' ?
3964 document.scripts[document.scripts.length - 1] : null);
3965 return wrap(script);
3966 },
3967 configurable: true
3968 };
3969
3970 Object.defineProperty(document, '_currentScript', currentScriptDescriptor);
3971 Object.defineProperty(rootDocument, '_currentScript', currentScriptDescriptor);
3972
3973 /**
3974 Add support for the `HTMLImportsLoaded` event and the `HTMLImports.whenReady`
3975 method. This api is necessary because unlike the native implementation,
3976 script elements do not force imports to resolve. Instead, users should wrap
3977 code in either an `HTMLImportsLoaded` hander or after load time in an
3978 `HTMLImports.whenReady(callback)` call.
3979
3980 NOTE: This module also supports these apis under the native implementation.
3981 Therefore, if this file is loaded, the same code can be used under both
3982 the polyfill and native implementation.
3983 */
3984
3985 var isIE = /Trident/.test(navigator.userAgent);
3986
3987 // call a callback when all HTMLImports in the document at call time
3988 // (or at least document ready) have loaded.
3989 // 1. ensure the document is in a ready state (has dom), then
3990 // 2. watch for loading of imports and call callback when done
3991 function whenReady(callback, doc) {
3992 doc = doc || rootDocument;
3993 // if document is loading, wait and try again
3994 whenDocumentReady(function() {
3995 watchImportsLoad(callback, doc);
3996 }, doc);
3997 }
3998
3999 // call the callback when the document is in a ready state (has dom)
4000 var requiredReadyState = isIE ? 'complete' : 'interactive';
4001 var READY_EVENT = 'readystatechange';
4002 function isDocumentReady(doc) {
4003 return (doc.readyState === 'complete' ||
4004 doc.readyState === requiredReadyState);
4005 }
4006
4007 // call <callback> when we ensure the document is in a ready state
4008 function whenDocumentReady(callback, doc) {
4009 if (!isDocumentReady(doc)) {
4010 var checkReady = function() {
4011 if (doc.readyState === 'complete' ||
4012 doc.readyState === requiredReadyState) {
4013 doc.removeEventListener(READY_EVENT, checkReady);
4014 whenDocumentReady(callback, doc);
4015 }
4016 };
4017 doc.addEventListener(READY_EVENT, checkReady);
4018 } else if (callback) {
4019 callback();
4020 }
4021 }
4022
4023 function markTargetLoaded(event) {
4024 event.target.__loaded = true;
4025 }
4026
4027 // call <callback> when we ensure all imports have loaded
4028 function watchImportsLoad(callback, doc) {
4029 var imports = doc.querySelectorAll('link[rel=import]');
4030 var loaded = 0, l = imports.length;
4031 function checkDone(d) {
4032 if ((loaded == l) && callback) {
4033 callback();
4034 }
4035 }
4036 function loadedImport(e) {
4037 markTargetLoaded(e);
4038 loaded++;
4039 checkDone();
4040 }
4041 if (l) {
4042 for (var i=0, imp; (i<l) && (imp=imports[i]); i++) {
4043 if (isImportLoaded(imp)) {
4044 loadedImport.call(imp, {target: imp});
4045 } else {
4046 imp.addEventListener('load', loadedImport);
4047 imp.addEventListener('error', loadedImport);
4048 }
4049 }
4050 } else {
4051 checkDone();
4052 }
4053 }
4054
4055 // NOTE: test for native imports loading is based on explicitly watching
4056 // all imports (see below).
4057 // However, we cannot rely on this entirely without watching the entire document
4058 // for import links. For perf reasons, currently only head is watched.
4059 // Instead, we fallback to checking if the import property is available
4060 // and the document is not itself loading.
4061 function isImportLoaded(link) {
4062 return useNative ? link.__loaded ||
4063 (link.import && link.import.readyState !== 'loading') :
4064 link.__importParsed;
4065 }
4066
4067 // TODO(sorvell): Workaround for
4068 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=25007, should be removed when
4069 // this bug is addressed.
4070 // (1) Install a mutation observer to see when HTMLImports have loaded
4071 // (2) if this script is run during document load it will watch any existing
4072 // imports for loading.
4073 //
4074 // NOTE: The workaround has restricted functionality: (1) it's only compatible
4075 // with imports that are added to document.head since the mutation observer
4076 // watches only head for perf reasons, (2) it requires this script
4077 // to run before any imports have completed loading.
4078 if (useNative) {
4079 new MutationObserver(function(mxns) {
4080 for (var i=0, l=mxns.length, m; (i < l) && (m=mxns[i]); i++) {
4081 if (m.addedNodes) {
4082 handleImports(m.addedNodes);
4083 }
4084 }
4085 }).observe(document.head, {childList: true});
4086
4087 function handleImports(nodes) {
4088 for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) {
4089 if (isImport(n)) {
4090 handleImport(n);
4091 }
4092 }
4093 }
4094
4095 function isImport(element) {
4096 return element.localName === 'link' && element.rel === 'import';
4097 }
4098
4099 function handleImport(element) {
4100 var loaded = element.import;
4101 if (loaded) {
4102 markTargetLoaded({target: element});
4103 } else {
4104 element.addEventListener('load', markTargetLoaded);
4105 element.addEventListener('error', markTargetLoaded);
4106 }
4107 }
4108
4109 // make sure to catch any imports that are in the process of loading
4110 // when this script is run.
4111 (function() {
4112 if (document.readyState === 'loading') {
4113 var imports = document.querySelectorAll('link[rel=import]');
4114 for (var i=0, l=imports.length, imp; (i<l) && (imp=imports[i]); i++) {
4115 handleImport(imp);
4116 }
4117 }
4118 })();
4119
4120 }
4121
4122 // Fire the 'HTMLImportsLoaded' event when imports in document at load time
4123 // have loaded. This event is required to simulate the script blocking
4124 // behavior of native imports. A main document script that needs to be sure
4125 // imports have loaded should wait for this event.
4126 whenReady(function() {
4127 HTMLImports.ready = true;
4128 HTMLImports.readyTime = new Date().getTime();
4129 rootDocument.dispatchEvent(
4130 new CustomEvent('HTMLImportsLoaded', {bubbles: true})
4131 );
4132 });
4133
4134 // exports
4135 scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE;
4136 scope.useNative = useNative;
4137 scope.rootDocument = rootDocument;
4138 scope.whenReady = whenReady;
4139 scope.isIE = isIE;
4140
4141 })(HTMLImports);
4142
4143 (function(scope) {
4144
4145 // TODO(sorvell): It's desireable to provide a default stylesheet
4146 // that's convenient for styling unresolved elements, but
4147 // it's cumbersome to have to include this manually in every page.
4148 // It would make sense to put inside some HTMLImport but
4149 // the HTMLImports polyfill does not allow loading of stylesheets
4150 // that block rendering. Therefore this injection is tolerated here.
4151 var style = document.createElement('style');
4152 style.textContent = ''
4153 + 'body {'
4154 + 'transition: opacity ease-in 0.2s;'
4155 + ' } \n'
4156 + 'body[unresolved] {'
4157 + 'opacity: 0; display: block; overflow: hidden;'
4158 + ' } \n'
4159 ;
4160 var head = document.querySelector('head');
4161 head.insertBefore(style, head.firstChild);
4162
4163 })(Platform);
4164
4165 /*
4166 Build only script.
4167
4168 Ensures scripts needed for basic x-platform compatibility
4169 will be run when platform.js is not loaded.
4170 */
4171 }
4172 (function(global) {
4173 'use strict';
4174
4175 var testingExposeCycleCount = global.testingExposeCycleCount;
4176
4177 // Detect and do basic sanity checking on Object/Array.observe.
4178 function detectObjectObserve() {
4179 if (typeof Object.observe !== 'function' ||
4180 typeof Array.observe !== 'function') {
4181 return false;
4182 }
4183
4184 var records = [];
4185
4186 function callback(recs) {
4187 records = recs;
4188 }
4189
4190 var test = {};
4191 var arr = [];
4192 Object.observe(test, callback);
4193 Array.observe(arr, callback);
4194 test.id = 1;
4195 test.id = 2;
4196 delete test.id;
4197 arr.push(1, 2);
4198 arr.length = 0;
4199
4200 Object.deliverChangeRecords(callback);
4201 if (records.length !== 5)
4202 return false;
4203
4204 if (records[0].type != 'add' ||
4205 records[1].type != 'update' ||
4206 records[2].type != 'delete' ||
4207 records[3].type != 'splice' ||
4208 records[4].type != 'splice') {
4209 return false;
4210 }
4211
4212 Object.unobserve(test, callback);
4213 Array.unobserve(arr, callback);
4214
4215 return true;
4216 }
4217
4218 var hasObserve = detectObjectObserve();
4219
4220 function detectEval() {
4221 // Don't test for eval if we're running in a Chrome App environment.
4222 // We check for APIs set that only exist in a Chrome App context.
4223 if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) {
4224 return false;
4225 }
4226
4227 // Firefox OS Apps do not allow eval. This feature detection is very hacky
4228 // but even if some other platform adds support for this function this code
4229 // will continue to work.
4230 if (typeof navigator != 'undefined' && navigator.getDeviceStorage) {
4231 return false;
4232 }
4233
4234 try {
4235 var f = new Function('', 'return true;');
4236 return f();
4237 } catch (ex) {
4238 return false;
4239 }
4240 }
4241
4242 var hasEval = detectEval();
4243
4244 function isIndex(s) {
4245 return +s === s >>> 0 && s !== '';
4246 }
4247
4248 function toNumber(s) {
4249 return +s;
4250 }
4251
4252 function isObject(obj) {
4253 return obj === Object(obj);
4254 }
4255
4256 var numberIsNaN = global.Number.isNaN || function(value) {
4257 return typeof value === 'number' && global.isNaN(value);
4258 }
4259
4260 function areSameValue(left, right) {
4261 if (left === right)
4262 return left !== 0 || 1 / left === 1 / right;
4263 if (numberIsNaN(left) && numberIsNaN(right))
4264 return true;
4265
4266 return left !== left && right !== right;
4267 }
4268
4269 var createObject = ('__proto__' in {}) ?
4270 function(obj) { return obj; } :
4271 function(obj) {
4272 var proto = obj.__proto__;
4273 if (!proto)
4274 return obj;
4275 var newObject = Object.create(proto);
4276 Object.getOwnPropertyNames(obj).forEach(function(name) {
4277 Object.defineProperty(newObject, name,
4278 Object.getOwnPropertyDescriptor(obj, name));
4279 });
4280 return newObject;
4281 };
4282
4283 var identStart = '[\$_a-zA-Z]';
4284 var identPart = '[\$_a-zA-Z0-9]';
4285 var identRegExp = new RegExp('^' + identStart + '+' + identPart + '*' + '$');
4286
4287 function getPathCharType(char) {
4288 if (char === undefined)
4289 return 'eof';
4290
4291 var code = char.charCodeAt(0);
4292
4293 switch(code) {
4294 case 0x5B: // [
4295 case 0x5D: // ]
4296 case 0x2E: // .
4297 case 0x22: // "
4298 case 0x27: // '
4299 case 0x30: // 0
4300 return char;
4301
4302 case 0x5F: // _
4303 case 0x24: // $
4304 return 'ident';
4305
4306 case 0x20: // Space
4307 case 0x09: // Tab
4308 case 0x0A: // Newline
4309 case 0x0D: // Return
4310 case 0xA0: // No-break space
4311 case 0xFEFF: // Byte Order Mark
4312 case 0x2028: // Line Separator
4313 case 0x2029: // Paragraph Separator
4314 return 'ws';
4315 }
4316
4317 // a-z, A-Z
4318 if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A))
4319 return 'ident';
4320
4321 // 1-9
4322 if (0x31 <= code && code <= 0x39)
4323 return 'number';
4324
4325 return 'else';
4326 }
4327
4328 var pathStateMachine = {
4329 'beforePath': {
4330 'ws': ['beforePath'],
4331 'ident': ['inIdent', 'append'],
4332 '[': ['beforeElement'],
4333 'eof': ['afterPath']
4334 },
4335
4336 'inPath': {
4337 'ws': ['inPath'],
4338 '.': ['beforeIdent'],
4339 '[': ['beforeElement'],
4340 'eof': ['afterPath']
4341 },
4342
4343 'beforeIdent': {
4344 'ws': ['beforeIdent'],
4345 'ident': ['inIdent', 'append']
4346 },
4347
4348 'inIdent': {
4349 'ident': ['inIdent', 'append'],
4350 '0': ['inIdent', 'append'],
4351 'number': ['inIdent', 'append'],
4352 'ws': ['inPath', 'push'],
4353 '.': ['beforeIdent', 'push'],
4354 '[': ['beforeElement', 'push'],
4355 'eof': ['afterPath', 'push']
4356 },
4357
4358 'beforeElement': {
4359 'ws': ['beforeElement'],
4360 '0': ['afterZero', 'append'],
4361 'number': ['inIndex', 'append'],
4362 "'": ['inSingleQuote', 'append', ''],
4363 '"': ['inDoubleQuote', 'append', '']
4364 },
4365
4366 'afterZero': {
4367 'ws': ['afterElement', 'push'],
4368 ']': ['inPath', 'push']
4369 },
4370
4371 'inIndex': {
4372 '0': ['inIndex', 'append'],
4373 'number': ['inIndex', 'append'],
4374 'ws': ['afterElement'],
4375 ']': ['inPath', 'push']
4376 },
4377
4378 'inSingleQuote': {
4379 "'": ['afterElement'],
4380 'eof': ['error'],
4381 'else': ['inSingleQuote', 'append']
4382 },
4383
4384 'inDoubleQuote': {
4385 '"': ['afterElement'],
4386 'eof': ['error'],
4387 'else': ['inDoubleQuote', 'append']
4388 },
4389
4390 'afterElement': {
4391 'ws': ['afterElement'],
4392 ']': ['inPath', 'push']
4393 }
4394 }
4395
4396 function noop() {}
4397
4398 function parsePath(path) {
4399 var keys = [];
4400 var index = -1;
4401 var c, newChar, key, type, transition, action, typeMap, mode = 'beforePath';
4402
4403 var actions = {
4404 push: function() {
4405 if (key === undefined)
4406 return;
4407
4408 keys.push(key);
4409 key = undefined;
4410 },
4411
4412 append: function() {
4413 if (key === undefined)
4414 key = newChar
4415 else
4416 key += newChar;
4417 }
4418 };
4419
4420 function maybeUnescapeQuote() {
4421 if (index >= path.length)
4422 return;
4423
4424 var nextChar = path[index + 1];
4425 if ((mode == 'inSingleQuote' && nextChar == "'") ||
4426 (mode == 'inDoubleQuote' && nextChar == '"')) {
4427 index++;
4428 newChar = nextChar;
4429 actions.append();
4430 return true;
4431 }
4432 }
4433
4434 while (mode) {
4435 index++;
4436 c = path[index];
4437
4438 if (c == '\\' && maybeUnescapeQuote(mode))
4439 continue;
4440
4441 type = getPathCharType(c);
4442 typeMap = pathStateMachine[mode];
4443 transition = typeMap[type] || typeMap['else'] || 'error';
4444
4445 if (transition == 'error')
4446 return; // parse error;
4447
4448 mode = transition[0];
4449 action = actions[transition[1]] || noop;
4450 newChar = transition[2] === undefined ? c : transition[2];
4451 action();
4452
4453 if (mode === 'afterPath') {
4454 return keys;
4455 }
4456 }
4457
4458 return; // parse error
4459 }
4460
4461 function isIdent(s) {
4462 return identRegExp.test(s);
4463 }
4464
4465 var constructorIsPrivate = {};
4466
4467 function Path(parts, privateToken) {
4468 if (privateToken !== constructorIsPrivate)
4469 throw Error('Use Path.get to retrieve path objects');
4470
4471 for (var i = 0; i < parts.length; i++) {
4472 this.push(String(parts[i]));
4473 }
4474
4475 if (hasEval && this.length) {
4476 this.getValueFrom = this.compiledGetValueFromFn();
4477 }
4478 }
4479
4480 // TODO(rafaelw): Make simple LRU cache
4481 var pathCache = {};
4482
4483 function getPath(pathString) {
4484 if (pathString instanceof Path)
4485 return pathString;
4486
4487 if (pathString == null || pathString.length == 0)
4488 pathString = '';
4489
4490 if (typeof pathString != 'string') {
4491 if (isIndex(pathString.length)) {
4492 // Constructed with array-like (pre-parsed) keys
4493 return new Path(pathString, constructorIsPrivate);
4494 }
4495
4496 pathString = String(pathString);
4497 }
4498
4499 var path = pathCache[pathString];
4500 if (path)
4501 return path;
4502
4503 var parts = parsePath(pathString);
4504 if (!parts)
4505 return invalidPath;
4506
4507 var path = new Path(parts, constructorIsPrivate);
4508 pathCache[pathString] = path;
4509 return path;
4510 }
4511
4512 Path.get = getPath;
4513
4514 function formatAccessor(key) {
4515 if (isIndex(key)) {
4516 return '[' + key + ']';
4517 } else {
4518 return '["' + key.replace(/"/g, '\\"') + '"]';
4519 }
4520 }
4521
4522 Path.prototype = createObject({
4523 __proto__: [],
4524 valid: true,
4525
4526 toString: function() {
4527 var pathString = '';
4528 for (var i = 0; i < this.length; i++) {
4529 var key = this[i];
4530 if (isIdent(key)) {
4531 pathString += i ? '.' + key : key;
4532 } else {
4533 pathString += formatAccessor(key);
4534 }
4535 }
4536
4537 return pathString;
4538 },
4539
4540 getValueFrom: function(obj, directObserver) {
4541 for (var i = 0; i < this.length; i++) {
4542 if (obj == null)
4543 return;
4544 obj = obj[this[i]];
4545 }
4546 return obj;
4547 },
4548
4549 iterateObjects: function(obj, observe) {
4550 for (var i = 0; i < this.length; i++) {
4551 if (i)
4552 obj = obj[this[i - 1]];
4553 if (!isObject(obj))
4554 return;
4555 observe(obj, this[i]);
4556 }
4557 },
4558
4559 compiledGetValueFromFn: function() {
4560 var str = '';
4561 var pathString = 'obj';
4562 str += 'if (obj != null';
4563 var i = 0;
4564 var key;
4565 for (; i < (this.length - 1); i++) {
4566 key = this[i];
4567 pathString += isIdent(key) ? '.' + key : formatAccessor(key);
4568 str += ' &&\n ' + pathString + ' != null';
4569 }
4570 str += ')\n';
4571
4572 var key = this[i];
4573 pathString += isIdent(key) ? '.' + key : formatAccessor(key);
4574
4575 str += ' return ' + pathString + ';\nelse\n return undefined;';
4576 return new Function('obj', str);
4577 },
4578
4579 setValueFrom: function(obj, value) {
4580 if (!this.length)
4581 return false;
4582
4583 for (var i = 0; i < this.length - 1; i++) {
4584 if (!isObject(obj))
4585 return false;
4586 obj = obj[this[i]];
4587 }
4588
4589 if (!isObject(obj))
4590 return false;
4591
4592 obj[this[i]] = value;
4593 return true;
4594 }
4595 });
4596
4597 var invalidPath = new Path('', constructorIsPrivate);
4598 invalidPath.valid = false;
4599 invalidPath.getValueFrom = invalidPath.setValueFrom = function() {};
4600
4601 var MAX_DIRTY_CHECK_CYCLES = 1000;
4602
4603 function dirtyCheck(observer) {
4604 var cycles = 0;
4605 while (cycles < MAX_DIRTY_CHECK_CYCLES && observer.check_()) {
4606 cycles++;
4607 }
4608 if (testingExposeCycleCount)
4609 global.dirtyCheckCycleCount = cycles;
4610
4611 return cycles > 0;
4612 }
4613
4614 function objectIsEmpty(object) {
4615 for (var prop in object)
4616 return false;
4617 return true;
4618 }
4619
4620 function diffIsEmpty(diff) {
4621 return objectIsEmpty(diff.added) &&
4622 objectIsEmpty(diff.removed) &&
4623 objectIsEmpty(diff.changed);
4624 }
4625
4626 function diffObjectFromOldObject(object, oldObject) {
4627 var added = {};
4628 var removed = {};
4629 var changed = {};
4630
4631 for (var prop in oldObject) {
4632 var newValue = object[prop];
4633
4634 if (newValue !== undefined && newValue === oldObject[prop])
4635 continue;
4636
4637 if (!(prop in object)) {
4638 removed[prop] = undefined;
4639 continue;
4640 }
4641
4642 if (newValue !== oldObject[prop])
4643 changed[prop] = newValue;
4644 }
4645
4646 for (var prop in object) {
4647 if (prop in oldObject)
4648 continue;
4649
4650 added[prop] = object[prop];
4651 }
4652
4653 if (Array.isArray(object) && object.length !== oldObject.length)
4654 changed.length = object.length;
4655
4656 return {
4657 added: added,
4658 removed: removed,
4659 changed: changed
4660 };
4661 }
4662
4663 var eomTasks = [];
4664 function runEOMTasks() {
4665 if (!eomTasks.length)
4666 return false;
4667
4668 for (var i = 0; i < eomTasks.length; i++) {
4669 eomTasks[i]();
4670 }
4671 eomTasks.length = 0;
4672 return true;
4673 }
4674
4675 var runEOM = hasObserve ? (function(){
4676 return function(fn) {
4677 return Promise.resolve().then(fn);
4678 }
4679 })() :
4680 (function() {
4681 return function(fn) {
4682 eomTasks.push(fn);
4683 };
4684 })();
4685
4686 var observedObjectCache = [];
4687
4688 function newObservedObject() {
4689 var observer;
4690 var object;
4691 var discardRecords = false;
4692 var first = true;
4693
4694 function callback(records) {
4695 if (observer && observer.state_ === OPENED && !discardRecords)
4696 observer.check_(records);
4697 }
4698
4699 return {
4700 open: function(obs) {
4701 if (observer)
4702 throw Error('ObservedObject in use');
4703
4704 if (!first)
4705 Object.deliverChangeRecords(callback);
4706
4707 observer = obs;
4708 first = false;
4709 },
4710 observe: function(obj, arrayObserve) {
4711 object = obj;
4712 if (arrayObserve)
4713 Array.observe(object, callback);
4714 else
4715 Object.observe(object, callback);
4716 },
4717 deliver: function(discard) {
4718 discardRecords = discard;
4719 Object.deliverChangeRecords(callback);
4720 discardRecords = false;
4721 },
4722 close: function() {
4723 observer = undefined;
4724 Object.unobserve(object, callback);
4725 observedObjectCache.push(this);
4726 }
4727 };
4728 }
4729
4730 /*
4731 * The observedSet abstraction is a perf optimization which reduces the total
4732 * number of Object.observe observations of a set of objects. The idea is that
4733 * groups of Observers will have some object dependencies in common and this
4734 * observed set ensures that each object in the transitive closure of
4735 * dependencies is only observed once. The observedSet acts as a write barrier
4736 * such that whenever any change comes through, all Observers are checked for
4737 * changed values.
4738 *
4739 * Note that this optimization is explicitly moving work from setup-time to
4740 * change-time.
4741 *
4742 * TODO(rafaelw): Implement "garbage collection". In order to move work off
4743 * the critical path, when Observers are closed, their observed objects are
4744 * not Object.unobserve(d). As a result, it's possible that if the observedSet
4745 * is kept open, but some Observers have been closed, it could cause "leaks"
4746 * (prevent otherwise collectable objects from being collected). At some
4747 * point, we should implement incremental "gc" which keeps a list of
4748 * observedSets which may need clean-up and does small amounts of cleanup on a
4749 * timeout until all is clean.
4750 */
4751
4752 function getObservedObject(observer, object, arrayObserve) {
4753 var dir = observedObjectCache.pop() || newObservedObject();
4754 dir.open(observer);
4755 dir.observe(object, arrayObserve);
4756 return dir;
4757 }
4758
4759 var observedSetCache = [];
4760
4761 function newObservedSet() {
4762 var observerCount = 0;
4763 var observers = [];
4764 var objects = [];
4765 var rootObj;
4766 var rootObjProps;
4767
4768 function observe(obj, prop) {
4769 if (!obj)
4770 return;
4771
4772 if (obj === rootObj)
4773 rootObjProps[prop] = true;
4774
4775 if (objects.indexOf(obj) < 0) {
4776 objects.push(obj);
4777 Object.observe(obj, callback);
4778 }
4779
4780 observe(Object.getPrototypeOf(obj), prop);
4781 }
4782
4783 function allRootObjNonObservedProps(recs) {
4784 for (var i = 0; i < recs.length; i++) {
4785 var rec = recs[i];
4786 if (rec.object !== rootObj ||
4787 rootObjProps[rec.name] ||
4788 rec.type === 'setPrototype') {
4789 return false;
4790 }
4791 }
4792 return true;
4793 }
4794
4795 function callback(recs) {
4796 if (allRootObjNonObservedProps(recs))
4797 return;
4798
4799 var observer;
4800 for (var i = 0; i < observers.length; i++) {
4801 observer = observers[i];
4802 if (observer.state_ == OPENED) {
4803 observer.iterateObjects_(observe);
4804 }
4805 }
4806
4807 for (var i = 0; i < observers.length; i++) {
4808 observer = observers[i];
4809 if (observer.state_ == OPENED) {
4810 observer.check_();
4811 }
4812 }
4813 }
4814
4815 var record = {
4816 objects: objects,
4817 get rootObject() { return rootObj; },
4818 set rootObject(value) {
4819 rootObj = value;
4820 rootObjProps = {};
4821 },
4822 open: function(obs, object) {
4823 observers.push(obs);
4824 observerCount++;
4825 obs.iterateObjects_(observe);
4826 },
4827 close: function(obs) {
4828 observerCount--;
4829 if (observerCount > 0) {
4830 return;
4831 }
4832
4833 for (var i = 0; i < objects.length; i++) {
4834 Object.unobserve(objects[i], callback);
4835 Observer.unobservedCount++;
4836 }
4837
4838 observers.length = 0;
4839 objects.length = 0;
4840 rootObj = undefined;
4841 rootObjProps = undefined;
4842 observedSetCache.push(this);
4843 if (lastObservedSet === this)
4844 lastObservedSet = null;
4845 },
4846 };
4847
4848 return record;
4849 }
4850
4851 var lastObservedSet;
4852
4853 function getObservedSet(observer, obj) {
4854 if (!lastObservedSet || lastObservedSet.rootObject !== obj) {
4855 lastObservedSet = observedSetCache.pop() || newObservedSet();
4856 lastObservedSet.rootObject = obj;
4857 }
4858 lastObservedSet.open(observer, obj);
4859 return lastObservedSet;
4860 }
4861
4862 var UNOPENED = 0;
4863 var OPENED = 1;
4864 var CLOSED = 2;
4865 var RESETTING = 3;
4866
4867 var nextObserverId = 1;
4868
4869 function Observer() {
4870 this.state_ = UNOPENED;
4871 this.callback_ = undefined;
4872 this.target_ = undefined; // TODO(rafaelw): Should be WeakRef
4873 this.directObserver_ = undefined;
4874 this.value_ = undefined;
4875 this.id_ = nextObserverId++;
4876 }
4877
4878 Observer.prototype = {
4879 open: function(callback, target) {
4880 if (this.state_ != UNOPENED)
4881 throw Error('Observer has already been opened.');
4882
4883 addToAll(this);
4884 this.callback_ = callback;
4885 this.target_ = target;
4886 this.connect_();
4887 this.state_ = OPENED;
4888 return this.value_;
4889 },
4890
4891 close: function() {
4892 if (this.state_ != OPENED)
4893 return;
4894
4895 removeFromAll(this);
4896 this.disconnect_();
4897 this.value_ = undefined;
4898 this.callback_ = undefined;
4899 this.target_ = undefined;
4900 this.state_ = CLOSED;
4901 },
4902
4903 deliver: function() {
4904 if (this.state_ != OPENED)
4905 return;
4906
4907 dirtyCheck(this);
4908 },
4909
4910 report_: function(changes) {
4911 try {
4912 this.callback_.apply(this.target_, changes);
4913 } catch (ex) {
4914 Observer._errorThrownDuringCallback = true;
4915 console.error('Exception caught during observer callback: ' +
4916 (ex.stack || ex));
4917 }
4918 },
4919
4920 discardChanges: function() {
4921 this.check_(undefined, true);
4922 return this.value_;
4923 }
4924 }
4925
4926 var collectObservers = !hasObserve;
4927 var allObservers;
4928 Observer._allObserversCount = 0;
4929
4930 if (collectObservers) {
4931 allObservers = [];
4932 }
4933
4934 function addToAll(observer) {
4935 Observer._allObserversCount++;
4936 if (!collectObservers)
4937 return;
4938
4939 allObservers.push(observer);
4940 }
4941
4942 function removeFromAll(observer) {
4943 Observer._allObserversCount--;
4944 }
4945
4946 var runningMicrotaskCheckpoint = false;
4947
4948 global.Platform = global.Platform || {};
4949
4950 global.Platform.performMicrotaskCheckpoint = function() {
4951 if (runningMicrotaskCheckpoint)
4952 return;
4953
4954 if (!collectObservers)
4955 return;
4956
4957 runningMicrotaskCheckpoint = true;
4958
4959 var cycles = 0;
4960 var anyChanged, toCheck;
4961
4962 do {
4963 cycles++;
4964 toCheck = allObservers;
4965 allObservers = [];
4966 anyChanged = false;
4967
4968 for (var i = 0; i < toCheck.length; i++) {
4969 var observer = toCheck[i];
4970 if (observer.state_ != OPENED)
4971 continue;
4972
4973 if (observer.check_())
4974 anyChanged = true;
4975
4976 allObservers.push(observer);
4977 }
4978 if (runEOMTasks())
4979 anyChanged = true;
4980 } while (cycles < MAX_DIRTY_CHECK_CYCLES && anyChanged);
4981
4982 if (testingExposeCycleCount)
4983 global.dirtyCheckCycleCount = cycles;
4984
4985 runningMicrotaskCheckpoint = false;
4986 };
4987
4988 if (collectObservers) {
4989 global.Platform.clearObservers = function() {
4990 allObservers = [];
4991 };
4992 }
4993
4994 function ObjectObserver(object) {
4995 Observer.call(this);
4996 this.value_ = object;
4997 this.oldObject_ = undefined;
4998 }
4999
5000 ObjectObserver.prototype = createObject({
5001 __proto__: Observer.prototype,
5002
5003 arrayObserve: false,
5004
5005 connect_: function(callback, target) {
5006 if (hasObserve) {
5007 this.directObserver_ = getObservedObject(this, this.value_,
5008 this.arrayObserve);
5009 } else {
5010 this.oldObject_ = this.copyObject(this.value_);
5011 }
5012
5013 },
5014
5015 copyObject: function(object) {
5016 var copy = Array.isArray(object) ? [] : {};
5017 for (var prop in object) {
5018 copy[prop] = object[prop];
5019 };
5020 if (Array.isArray(object))
5021 copy.length = object.length;
5022 return copy;
5023 },
5024
5025 check_: function(changeRecords, skipChanges) {
5026 var diff;
5027 var oldValues;
5028 if (hasObserve) {
5029 if (!changeRecords)
5030 return false;
5031
5032 oldValues = {};
5033 diff = diffObjectFromChangeRecords(this.value_, changeRecords,
5034 oldValues);
5035 } else {
5036 oldValues = this.oldObject_;
5037 diff = diffObjectFromOldObject(this.value_, this.oldObject_);
5038 }
5039
5040 if (diffIsEmpty(diff))
5041 return false;
5042
5043 if (!hasObserve)
5044 this.oldObject_ = this.copyObject(this.value_);
5045
5046 this.report_([
5047 diff.added || {},
5048 diff.removed || {},
5049 diff.changed || {},
5050 function(property) {
5051 return oldValues[property];
5052 }
5053 ]);
5054
5055 return true;
5056 },
5057
5058 disconnect_: function() {
5059 if (hasObserve) {
5060 this.directObserver_.close();
5061 this.directObserver_ = undefined;
5062 } else {
5063 this.oldObject_ = undefined;
5064 }
5065 },
5066
5067 deliver: function() {
5068 if (this.state_ != OPENED)
5069 return;
5070
5071 if (hasObserve)
5072 this.directObserver_.deliver(false);
5073 else
5074 dirtyCheck(this);
5075 },
5076
5077 discardChanges: function() {
5078 if (this.directObserver_)
5079 this.directObserver_.deliver(true);
5080 else
5081 this.oldObject_ = this.copyObject(this.value_);
5082
5083 return this.value_;
5084 }
5085 });
5086
5087 function ArrayObserver(array) {
5088 if (!Array.isArray(array))
5089 throw Error('Provided object is not an Array');
5090 ObjectObserver.call(this, array);
5091 }
5092
5093 ArrayObserver.prototype = createObject({
5094
5095 __proto__: ObjectObserver.prototype,
5096
5097 arrayObserve: true,
5098
5099 copyObject: function(arr) {
5100 return arr.slice();
5101 },
5102
5103 check_: function(changeRecords) {
5104 var splices;
5105 if (hasObserve) {
5106 if (!changeRecords)
5107 return false;
5108 splices = projectArraySplices(this.value_, changeRecords);
5109 } else {
5110 splices = calcSplices(this.value_, 0, this.value_.length,
5111 this.oldObject_, 0, this.oldObject_.length);
5112 }
5113
5114 if (!splices || !splices.length)
5115 return false;
5116
5117 if (!hasObserve)
5118 this.oldObject_ = this.copyObject(this.value_);
5119
5120 this.report_([splices]);
5121 return true;
5122 }
5123 });
5124
5125 ArrayObserver.applySplices = function(previous, current, splices) {
5126 splices.forEach(function(splice) {
5127 var spliceArgs = [splice.index, splice.removed.length];
5128 var addIndex = splice.index;
5129 while (addIndex < splice.index + splice.addedCount) {
5130 spliceArgs.push(current[addIndex]);
5131 addIndex++;
5132 }
5133
5134 Array.prototype.splice.apply(previous, spliceArgs);
5135 });
5136 };
5137
5138 function PathObserver(object, path) {
5139 Observer.call(this);
5140
5141 this.object_ = object;
5142 this.path_ = getPath(path);
5143 this.directObserver_ = undefined;
5144 }
5145
5146 PathObserver.prototype = createObject({
5147 __proto__: Observer.prototype,
5148
5149 get path() {
5150 return this.path_;
5151 },
5152
5153 connect_: function() {
5154 if (hasObserve)
5155 this.directObserver_ = getObservedSet(this, this.object_);
5156
5157 this.check_(undefined, true);
5158 },
5159
5160 disconnect_: function() {
5161 this.value_ = undefined;
5162
5163 if (this.directObserver_) {
5164 this.directObserver_.close(this);
5165 this.directObserver_ = undefined;
5166 }
5167 },
5168
5169 iterateObjects_: function(observe) {
5170 this.path_.iterateObjects(this.object_, observe);
5171 },
5172
5173 check_: function(changeRecords, skipChanges) {
5174 var oldValue = this.value_;
5175 this.value_ = this.path_.getValueFrom(this.object_);
5176 if (skipChanges || areSameValue(this.value_, oldValue))
5177 return false;
5178
5179 this.report_([this.value_, oldValue, this]);
5180 return true;
5181 },
5182
5183 setValue: function(newValue) {
5184 if (this.path_)
5185 this.path_.setValueFrom(this.object_, newValue);
5186 }
5187 });
5188
5189 function CompoundObserver(reportChangesOnOpen) {
5190 Observer.call(this);
5191
5192 this.reportChangesOnOpen_ = reportChangesOnOpen;
5193 this.value_ = [];
5194 this.directObserver_ = undefined;
5195 this.observed_ = [];
5196 }
5197
5198 var observerSentinel = {};
5199
5200 CompoundObserver.prototype = createObject({
5201 __proto__: Observer.prototype,
5202
5203 connect_: function() {
5204 if (hasObserve) {
5205 var object;
5206 var needsDirectObserver = false;
5207 for (var i = 0; i < this.observed_.length; i += 2) {
5208 object = this.observed_[i]
5209 if (object !== observerSentinel) {
5210 needsDirectObserver = true;
5211 break;
5212 }
5213 }
5214
5215 if (needsDirectObserver)
5216 this.directObserver_ = getObservedSet(this, object);
5217 }
5218
5219 this.check_(undefined, !this.reportChangesOnOpen_);
5220 },
5221
5222 disconnect_: function() {
5223 for (var i = 0; i < this.observed_.length; i += 2) {
5224 if (this.observed_[i] === observerSentinel)
5225 this.observed_[i + 1].close();
5226 }
5227 this.observed_.length = 0;
5228 this.value_.length = 0;
5229
5230 if (this.directObserver_) {
5231 this.directObserver_.close(this);
5232 this.directObserver_ = undefined;
5233 }
5234 },
5235
5236 addPath: function(object, path) {
5237 if (this.state_ != UNOPENED && this.state_ != RESETTING)
5238 throw Error('Cannot add paths once started.');
5239
5240 var path = getPath(path);
5241 this.observed_.push(object, path);
5242 if (!this.reportChangesOnOpen_)
5243 return;
5244 var index = this.observed_.length / 2 - 1;
5245 this.value_[index] = path.getValueFrom(object);
5246 },
5247
5248 addObserver: function(observer) {
5249 if (this.state_ != UNOPENED && this.state_ != RESETTING)
5250 throw Error('Cannot add observers once started.');
5251
5252 this.observed_.push(observerSentinel, observer);
5253 if (!this.reportChangesOnOpen_)
5254 return;
5255 var index = this.observed_.length / 2 - 1;
5256 this.value_[index] = observer.open(this.deliver, this);
5257 },
5258
5259 startReset: function() {
5260 if (this.state_ != OPENED)
5261 throw Error('Can only reset while open');
5262
5263 this.state_ = RESETTING;
5264 this.disconnect_();
5265 },
5266
5267 finishReset: function() {
5268 if (this.state_ != RESETTING)
5269 throw Error('Can only finishReset after startReset');
5270 this.state_ = OPENED;
5271 this.connect_();
5272
5273 return this.value_;
5274 },
5275
5276 iterateObjects_: function(observe) {
5277 var object;
5278 for (var i = 0; i < this.observed_.length; i += 2) {
5279 object = this.observed_[i]
5280 if (object !== observerSentinel)
5281 this.observed_[i + 1].iterateObjects(object, observe)
5282 }
5283 },
5284
5285 check_: function(changeRecords, skipChanges) {
5286 var oldValues;
5287 for (var i = 0; i < this.observed_.length; i += 2) {
5288 var object = this.observed_[i];
5289 var path = this.observed_[i+1];
5290 var value;
5291 if (object === observerSentinel) {
5292 var observable = path;
5293 value = this.state_ === UNOPENED ?
5294 observable.open(this.deliver, this) :
5295 observable.discardChanges();
5296 } else {
5297 value = path.getValueFrom(object);
5298 }
5299
5300 if (skipChanges) {
5301 this.value_[i / 2] = value;
5302 continue;
5303 }
5304
5305 if (areSameValue(value, this.value_[i / 2]))
5306 continue;
5307
5308 oldValues = oldValues || [];
5309 oldValues[i / 2] = this.value_[i / 2];
5310 this.value_[i / 2] = value;
5311 }
5312
5313 if (!oldValues)
5314 return false;
5315
5316 // TODO(rafaelw): Having observed_ as the third callback arg here is
5317 // pretty lame API. Fix.
5318 this.report_([this.value_, oldValues, this.observed_]);
5319 return true;
5320 }
5321 });
5322
5323 function identFn(value) { return value; }
5324
5325 function ObserverTransform(observable, getValueFn, setValueFn,
5326 dontPassThroughSet) {
5327 this.callback_ = undefined;
5328 this.target_ = undefined;
5329 this.value_ = undefined;
5330 this.observable_ = observable;
5331 this.getValueFn_ = getValueFn || identFn;
5332 this.setValueFn_ = setValueFn || identFn;
5333 // TODO(rafaelw): This is a temporary hack. PolymerExpressions needs this
5334 // at the moment because of a bug in it's dependency tracking.
5335 this.dontPassThroughSet_ = dontPassThroughSet;
5336 }
5337
5338 ObserverTransform.prototype = {
5339 open: function(callback, target) {
5340 this.callback_ = callback;
5341 this.target_ = target;
5342 this.value_ =
5343 this.getValueFn_(this.observable_.open(this.observedCallback_, this));
5344 return this.value_;
5345 },
5346
5347 observedCallback_: function(value) {
5348 value = this.getValueFn_(value);
5349 if (areSameValue(value, this.value_))
5350 return;
5351 var oldValue = this.value_;
5352 this.value_ = value;
5353 this.callback_.call(this.target_, this.value_, oldValue);
5354 },
5355
5356 discardChanges: function() {
5357 this.value_ = this.getValueFn_(this.observable_.discardChanges());
5358 return this.value_;
5359 },
5360
5361 deliver: function() {
5362 return this.observable_.deliver();
5363 },
5364
5365 setValue: function(value) {
5366 value = this.setValueFn_(value);
5367 if (!this.dontPassThroughSet_ && this.observable_.setValue)
5368 return this.observable_.setValue(value);
5369 },
5370
5371 close: function() {
5372 if (this.observable_)
5373 this.observable_.close();
5374 this.callback_ = undefined;
5375 this.target_ = undefined;
5376 this.observable_ = undefined;
5377 this.value_ = undefined;
5378 this.getValueFn_ = undefined;
5379 this.setValueFn_ = undefined;
5380 }
5381 }
5382
5383 var expectedRecordTypes = {
5384 add: true,
5385 update: true,
5386 delete: true
5387 };
5388
5389 function diffObjectFromChangeRecords(object, changeRecords, oldValues) {
5390 var added = {};
5391 var removed = {};
5392
5393 for (var i = 0; i < changeRecords.length; i++) {
5394 var record = changeRecords[i];
5395 if (!expectedRecordTypes[record.type]) {
5396 console.error('Unknown changeRecord type: ' + record.type);
5397 console.error(record);
5398 continue;
5399 }
5400
5401 if (!(record.name in oldValues))
5402 oldValues[record.name] = record.oldValue;
5403
5404 if (record.type == 'update')
5405 continue;
5406
5407 if (record.type == 'add') {
5408 if (record.name in removed)
5409 delete removed[record.name];
5410 else
5411 added[record.name] = true;
5412
5413 continue;
5414 }
5415
5416 // type = 'delete'
5417 if (record.name in added) {
5418 delete added[record.name];
5419 delete oldValues[record.name];
5420 } else {
5421 removed[record.name] = true;
5422 }
5423 }
5424
5425 for (var prop in added)
5426 added[prop] = object[prop];
5427
5428 for (var prop in removed)
5429 removed[prop] = undefined;
5430
5431 var changed = {};
5432 for (var prop in oldValues) {
5433 if (prop in added || prop in removed)
5434 continue;
5435
5436 var newValue = object[prop];
5437 if (oldValues[prop] !== newValue)
5438 changed[prop] = newValue;
5439 }
5440
5441 return {
5442 added: added,
5443 removed: removed,
5444 changed: changed
5445 };
5446 }
5447
5448 function newSplice(index, removed, addedCount) {
5449 return {
5450 index: index,
5451 removed: removed,
5452 addedCount: addedCount
5453 };
5454 }
5455
5456 var EDIT_LEAVE = 0;
5457 var EDIT_UPDATE = 1;
5458 var EDIT_ADD = 2;
5459 var EDIT_DELETE = 3;
5460
5461 function ArraySplice() {}
5462
5463 ArraySplice.prototype = {
5464
5465 // Note: This function is *based* on the computation of the Levenshtein
5466 // "edit" distance. The one change is that "updates" are treated as two
5467 // edits - not one. With Array splices, an update is really a delete
5468 // followed by an add. By retaining this, we optimize for "keeping" the
5469 // maximum array items in the original array. For example:
5470 //
5471 // 'xxxx123' -> '123yyyy'
5472 //
5473 // With 1-edit updates, the shortest path would be just to update all seven
5474 // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
5475 // leaves the substring '123' intact.
5476 calcEditDistances: function(current, currentStart, currentEnd,
5477 old, oldStart, oldEnd) {
5478 // "Deletion" columns
5479 var rowCount = oldEnd - oldStart + 1;
5480 var columnCount = currentEnd - currentStart + 1;
5481 var distances = new Array(rowCount);
5482
5483 // "Addition" rows. Initialize null column.
5484 for (var i = 0; i < rowCount; i++) {
5485 distances[i] = new Array(columnCount);
5486 distances[i][0] = i;
5487 }
5488
5489 // Initialize null row
5490 for (var j = 0; j < columnCount; j++)
5491 distances[0][j] = j;
5492
5493 for (var i = 1; i < rowCount; i++) {
5494 for (var j = 1; j < columnCount; j++) {
5495 if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1]))
5496 distances[i][j] = distances[i - 1][j - 1];
5497 else {
5498 var north = distances[i - 1][j] + 1;
5499 var west = distances[i][j - 1] + 1;
5500 distances[i][j] = north < west ? north : west;
5501 }
5502 }
5503 }
5504
5505 return distances;
5506 },
5507
5508 // This starts at the final weight, and walks "backward" by finding
5509 // the minimum previous weight recursively until the origin of the weight
5510 // matrix.
5511 spliceOperationsFromEditDistances: function(distances) {
5512 var i = distances.length - 1;
5513 var j = distances[0].length - 1;
5514 var current = distances[i][j];
5515 var edits = [];
5516 while (i > 0 || j > 0) {
5517 if (i == 0) {
5518 edits.push(EDIT_ADD);
5519 j--;
5520 continue;
5521 }
5522 if (j == 0) {
5523 edits.push(EDIT_DELETE);
5524 i--;
5525 continue;
5526 }
5527 var northWest = distances[i - 1][j - 1];
5528 var west = distances[i - 1][j];
5529 var north = distances[i][j - 1];
5530
5531 var min;
5532 if (west < north)
5533 min = west < northWest ? west : northWest;
5534 else
5535 min = north < northWest ? north : northWest;
5536
5537 if (min == northWest) {
5538 if (northWest == current) {
5539 edits.push(EDIT_LEAVE);
5540 } else {
5541 edits.push(EDIT_UPDATE);
5542 current = northWest;
5543 }
5544 i--;
5545 j--;
5546 } else if (min == west) {
5547 edits.push(EDIT_DELETE);
5548 i--;
5549 current = west;
5550 } else {
5551 edits.push(EDIT_ADD);
5552 j--;
5553 current = north;
5554 }
5555 }
5556
5557 edits.reverse();
5558 return edits;
5559 },
5560
5561 /**
5562 * Splice Projection functions:
5563 *
5564 * A splice map is a representation of how a previous array of items
5565 * was transformed into a new array of items. Conceptually it is a list of
5566 * tuples of
5567 *
5568 * <index, removed, addedCount>
5569 *
5570 * which are kept in ascending index order of. The tuple represents that at
5571 * the |index|, |removed| sequence of items were removed, and counting forwa rd
5572 * from |index|, |addedCount| items were added.
5573 */
5574
5575 /**
5576 * Lacking individual splice mutation information, the minimal set of
5577 * splices can be synthesized given the previous state and final state of an
5578 * array. The basic approach is to calculate the edit distance matrix and
5579 * choose the shortest path through it.
5580 *
5581 * Complexity: O(l * p)
5582 * l: The length of the current array
5583 * p: The length of the old array
5584 */
5585 calcSplices: function(current, currentStart, currentEnd,
5586 old, oldStart, oldEnd) {
5587 var prefixCount = 0;
5588 var suffixCount = 0;
5589
5590 var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart);
5591 if (currentStart == 0 && oldStart == 0)
5592 prefixCount = this.sharedPrefix(current, old, minLength);
5593
5594 if (currentEnd == current.length && oldEnd == old.length)
5595 suffixCount = this.sharedSuffix(current, old, minLength - prefixCount);
5596
5597 currentStart += prefixCount;
5598 oldStart += prefixCount;
5599 currentEnd -= suffixCount;
5600 oldEnd -= suffixCount;
5601
5602 if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0)
5603 return [];
5604
5605 if (currentStart == currentEnd) {
5606 var splice = newSplice(currentStart, [], 0);
5607 while (oldStart < oldEnd)
5608 splice.removed.push(old[oldStart++]);
5609
5610 return [ splice ];
5611 } else if (oldStart == oldEnd)
5612 return [ newSplice(currentStart, [], currentEnd - currentStart) ];
5613
5614 var ops = this.spliceOperationsFromEditDistances(
5615 this.calcEditDistances(current, currentStart, currentEnd,
5616 old, oldStart, oldEnd));
5617
5618 var splice = undefined;
5619 var splices = [];
5620 var index = currentStart;
5621 var oldIndex = oldStart;
5622 for (var i = 0; i < ops.length; i++) {
5623 switch(ops[i]) {
5624 case EDIT_LEAVE:
5625 if (splice) {
5626 splices.push(splice);
5627 splice = undefined;
5628 }
5629
5630 index++;
5631 oldIndex++;
5632 break;
5633 case EDIT_UPDATE:
5634 if (!splice)
5635 splice = newSplice(index, [], 0);
5636
5637 splice.addedCount++;
5638 index++;
5639
5640 splice.removed.push(old[oldIndex]);
5641 oldIndex++;
5642 break;
5643 case EDIT_ADD:
5644 if (!splice)
5645 splice = newSplice(index, [], 0);
5646
5647 splice.addedCount++;
5648 index++;
5649 break;
5650 case EDIT_DELETE:
5651 if (!splice)
5652 splice = newSplice(index, [], 0);
5653
5654 splice.removed.push(old[oldIndex]);
5655 oldIndex++;
5656 break;
5657 }
5658 }
5659
5660 if (splice) {
5661 splices.push(splice);
5662 }
5663 return splices;
5664 },
5665
5666 sharedPrefix: function(current, old, searchLength) {
5667 for (var i = 0; i < searchLength; i++)
5668 if (!this.equals(current[i], old[i]))
5669 return i;
5670 return searchLength;
5671 },
5672
5673 sharedSuffix: function(current, old, searchLength) {
5674 var index1 = current.length;
5675 var index2 = old.length;
5676 var count = 0;
5677 while (count < searchLength && this.equals(current[--index1], old[--index2 ]))
5678 count++;
5679
5680 return count;
5681 },
5682
5683 calculateSplices: function(current, previous) {
5684 return this.calcSplices(current, 0, current.length, previous, 0,
5685 previous.length);
5686 },
5687
5688 equals: function(currentValue, previousValue) {
5689 return currentValue === previousValue;
5690 }
5691 };
5692
5693 var arraySplice = new ArraySplice();
5694
5695 function calcSplices(current, currentStart, currentEnd,
5696 old, oldStart, oldEnd) {
5697 return arraySplice.calcSplices(current, currentStart, currentEnd,
5698 old, oldStart, oldEnd);
5699 }
5700
5701 function intersect(start1, end1, start2, end2) {
5702 // Disjoint
5703 if (end1 < start2 || end2 < start1)
5704 return -1;
5705
5706 // Adjacent
5707 if (end1 == start2 || end2 == start1)
5708 return 0;
5709
5710 // Non-zero intersect, span1 first
5711 if (start1 < start2) {
5712 if (end1 < end2)
5713 return end1 - start2; // Overlap
5714 else
5715 return end2 - start2; // Contained
5716 } else {
5717 // Non-zero intersect, span2 first
5718 if (end2 < end1)
5719 return end2 - start1; // Overlap
5720 else
5721 return end1 - start1; // Contained
5722 }
5723 }
5724
5725 function mergeSplice(splices, index, removed, addedCount) {
5726
5727 var splice = newSplice(index, removed, addedCount);
5728
5729 var inserted = false;
5730 var insertionOffset = 0;
5731
5732 for (var i = 0; i < splices.length; i++) {
5733 var current = splices[i];
5734 current.index += insertionOffset;
5735
5736 if (inserted)
5737 continue;
5738
5739 var intersectCount = intersect(splice.index,
5740 splice.index + splice.removed.length,
5741 current.index,
5742 current.index + current.addedCount);
5743
5744 if (intersectCount >= 0) {
5745 // Merge the two splices
5746
5747 splices.splice(i, 1);
5748 i--;
5749
5750 insertionOffset -= current.addedCount - current.removed.length;
5751
5752 splice.addedCount += current.addedCount - intersectCount;
5753 var deleteCount = splice.removed.length +
5754 current.removed.length - intersectCount;
5755
5756 if (!splice.addedCount && !deleteCount) {
5757 // merged splice is a noop. discard.
5758 inserted = true;
5759 } else {
5760 var removed = current.removed;
5761
5762 if (splice.index < current.index) {
5763 // some prefix of splice.removed is prepended to current.removed.
5764 var prepend = splice.removed.slice(0, current.index - splice.index);
5765 Array.prototype.push.apply(prepend, removed);
5766 removed = prepend;
5767 }
5768
5769 if (splice.index + splice.removed.length > current.index + current.add edCount) {
5770 // some suffix of splice.removed is appended to current.removed.
5771 var append = splice.removed.slice(current.index + current.addedCount - splice.index);
5772 Array.prototype.push.apply(removed, append);
5773 }
5774
5775 splice.removed = removed;
5776 if (current.index < splice.index) {
5777 splice.index = current.index;
5778 }
5779 }
5780 } else if (splice.index < current.index) {
5781 // Insert splice here.
5782
5783 inserted = true;
5784
5785 splices.splice(i, 0, splice);
5786 i++;
5787
5788 var offset = splice.addedCount - splice.removed.length
5789 current.index += offset;
5790 insertionOffset += offset;
5791 }
5792 }
5793
5794 if (!inserted)
5795 splices.push(splice);
5796 }
5797
5798 function createInitialSplices(array, changeRecords) {
5799 var splices = [];
5800
5801 for (var i = 0; i < changeRecords.length; i++) {
5802 var record = changeRecords[i];
5803 switch(record.type) {
5804 case 'splice':
5805 mergeSplice(splices, record.index, record.removed.slice(), record.adde dCount);
5806 break;
5807 case 'add':
5808 case 'update':
5809 case 'delete':
5810 if (!isIndex(record.name))
5811 continue;
5812 var index = toNumber(record.name);
5813 if (index < 0)
5814 continue;
5815 mergeSplice(splices, index, [record.oldValue], 1);
5816 break;
5817 default:
5818 console.error('Unexpected record type: ' + JSON.stringify(record));
5819 break;
5820 }
5821 }
5822
5823 return splices;
5824 }
5825
5826 function projectArraySplices(array, changeRecords) {
5827 var splices = [];
5828
5829 createInitialSplices(array, changeRecords).forEach(function(splice) {
5830 if (splice.addedCount == 1 && splice.removed.length == 1) {
5831 if (splice.removed[0] !== array[splice.index])
5832 splices.push(splice);
5833
5834 return
5835 };
5836
5837 splices = splices.concat(calcSplices(array, splice.index, splice.index + s plice.addedCount,
5838 splice.removed, 0, splice.removed.len gth));
5839 });
5840
5841 return splices;
5842 }
5843
5844 // Export the observe-js object for **Node.js**, with
5845 // backwards-compatibility for the old `require()` API. If we're in
5846 // the browser, export as a global object.
5847
5848 var expose = global;
5849
5850 if (typeof exports !== 'undefined') {
5851 if (typeof module !== 'undefined' && module.exports) {
5852 expose = exports = module.exports;
5853 }
5854 expose = exports;
5855 }
5856
5857 expose.Observer = Observer;
5858 expose.Observer.runEOM_ = runEOM;
5859 expose.Observer.observerSentinel_ = observerSentinel; // for testing.
5860 expose.Observer.hasObjectObserve = hasObserve;
5861 expose.ArrayObserver = ArrayObserver;
5862 expose.ArrayObserver.calculateSplices = function(current, previous) {
5863 return arraySplice.calculateSplices(current, previous);
5864 };
5865
5866 expose.ArraySplice = ArraySplice;
5867 expose.ObjectObserver = ObjectObserver;
5868 expose.PathObserver = PathObserver;
5869 expose.CompoundObserver = CompoundObserver;
5870 expose.Path = Path;
5871 expose.ObserverTransform = ObserverTransform;
5872
5873 })(typeof global !== 'undefined' && global && typeof module !== 'undefined' && m odule ? global : this || window);
5874
5875 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
5876 // This code may only be used under the BSD style license found at http://polyme r.github.io/LICENSE.txt
5877 // The complete set of authors may be found at http://polymer.github.io/AUTHORS. txt
5878 // The complete set of contributors may be found at http://polymer.github.io/CON TRIBUTORS.txt
5879 // Code distributed by Google as part of the polymer project is also
5880 // subject to an additional IP rights grant found at http://polymer.github.io/PA TENTS.txt
5881
5882 (function(global) {
5883 'use strict';
5884
5885 var filter = Array.prototype.filter.call.bind(Array.prototype.filter);
5886
5887 function getTreeScope(node) {
5888 while (node.parentNode) {
5889 node = node.parentNode;
5890 }
5891
5892 return typeof node.getElementById === 'function' ? node : null;
5893 }
5894
5895 Node.prototype.bind = function(name, observable) {
5896 console.error('Unhandled binding to Node: ', this, name, observable);
5897 };
5898
5899 Node.prototype.bindFinished = function() {};
5900
5901 function updateBindings(node, name, binding) {
5902 var bindings = node.bindings_;
5903 if (!bindings)
5904 bindings = node.bindings_ = {};
5905
5906 if (bindings[name])
5907 binding[name].close();
5908
5909 return bindings[name] = binding;
5910 }
5911
5912 function returnBinding(node, name, binding) {
5913 return binding;
5914 }
5915
5916 function sanitizeValue(value) {
5917 return value == null ? '' : value;
5918 }
5919
5920 function updateText(node, value) {
5921 node.data = sanitizeValue(value);
5922 }
5923
5924 function textBinding(node) {
5925 return function(value) {
5926 return updateText(node, value);
5927 };
5928 }
5929
5930 var maybeUpdateBindings = returnBinding;
5931
5932 Object.defineProperty(Platform, 'enableBindingsReflection', {
5933 get: function() {
5934 return maybeUpdateBindings === updateBindings;
5935 },
5936 set: function(enable) {
5937 maybeUpdateBindings = enable ? updateBindings : returnBinding;
5938 return enable;
5939 },
5940 configurable: true
5941 });
5942
5943 Text.prototype.bind = function(name, value, oneTime) {
5944 if (name !== 'textContent')
5945 return Node.prototype.bind.call(this, name, value, oneTime);
5946
5947 if (oneTime)
5948 return updateText(this, value);
5949
5950 var observable = value;
5951 updateText(this, observable.open(textBinding(this)));
5952 return maybeUpdateBindings(this, name, observable);
5953 }
5954
5955 function updateAttribute(el, name, conditional, value) {
5956 if (conditional) {
5957 if (value)
5958 el.setAttribute(name, '');
5959 else
5960 el.removeAttribute(name);
5961 return;
5962 }
5963
5964 el.setAttribute(name, sanitizeValue(value));
5965 }
5966
5967 function attributeBinding(el, name, conditional) {
5968 return function(value) {
5969 updateAttribute(el, name, conditional, value);
5970 };
5971 }
5972
5973 Element.prototype.bind = function(name, value, oneTime) {
5974 var conditional = name[name.length - 1] == '?';
5975 if (conditional) {
5976 this.removeAttribute(name);
5977 name = name.slice(0, -1);
5978 }
5979
5980 if (oneTime)
5981 return updateAttribute(this, name, conditional, value);
5982
5983
5984 var observable = value;
5985 updateAttribute(this, name, conditional,
5986 observable.open(attributeBinding(this, name, conditional)));
5987
5988 return maybeUpdateBindings(this, name, observable);
5989 };
5990
5991 var checkboxEventType;
5992 (function() {
5993 // Attempt to feature-detect which event (change or click) is fired first
5994 // for checkboxes.
5995 var div = document.createElement('div');
5996 var checkbox = div.appendChild(document.createElement('input'));
5997 checkbox.setAttribute('type', 'checkbox');
5998 var first;
5999 var count = 0;
6000 checkbox.addEventListener('click', function(e) {
6001 count++;
6002 first = first || 'click';
6003 });
6004 checkbox.addEventListener('change', function() {
6005 count++;
6006 first = first || 'change';
6007 });
6008
6009 var event = document.createEvent('MouseEvent');
6010 event.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false,
6011 false, false, false, 0, null);
6012 checkbox.dispatchEvent(event);
6013 // WebKit/Blink don't fire the change event if the element is outside the
6014 // document, so assume 'change' for that case.
6015 checkboxEventType = count == 1 ? 'change' : first;
6016 })();
6017
6018 function getEventForInputType(element) {
6019 switch (element.type) {
6020 case 'checkbox':
6021 return checkboxEventType;
6022 case 'radio':
6023 case 'select-multiple':
6024 case 'select-one':
6025 return 'change';
6026 case 'range':
6027 if (/Trident|MSIE/.test(navigator.userAgent))
6028 return 'change';
6029 default:
6030 return 'input';
6031 }
6032 }
6033
6034 function updateInput(input, property, value, santizeFn) {
6035 input[property] = (santizeFn || sanitizeValue)(value);
6036 }
6037
6038 function inputBinding(input, property, santizeFn) {
6039 return function(value) {
6040 return updateInput(input, property, value, santizeFn);
6041 }
6042 }
6043
6044 function noop() {}
6045
6046 function bindInputEvent(input, property, observable, postEventFn) {
6047 var eventType = getEventForInputType(input);
6048
6049 function eventHandler() {
6050 observable.setValue(input[property]);
6051 observable.discardChanges();
6052 (postEventFn || noop)(input);
6053 Platform.performMicrotaskCheckpoint();
6054 }
6055 input.addEventListener(eventType, eventHandler);
6056
6057 return {
6058 close: function() {
6059 input.removeEventListener(eventType, eventHandler);
6060 observable.close();
6061 },
6062
6063 observable_: observable
6064 }
6065 }
6066
6067 function booleanSanitize(value) {
6068 return Boolean(value);
6069 }
6070
6071 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'.
6072 // Returns an array containing all radio buttons other than |element| that
6073 // have the same |name|, either in the form that |element| belongs to or,
6074 // if no form, in the document tree to which |element| belongs.
6075 //
6076 // This implementation is based upon the HTML spec definition of a
6077 // "radio button group":
6078 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state. html#radio-button-group
6079 //
6080 function getAssociatedRadioButtons(element) {
6081 if (element.form) {
6082 return filter(element.form.elements, function(el) {
6083 return el != element &&
6084 el.tagName == 'INPUT' &&
6085 el.type == 'radio' &&
6086 el.name == element.name;
6087 });
6088 } else {
6089 var treeScope = getTreeScope(element);
6090 if (!treeScope)
6091 return [];
6092 var radios = treeScope.querySelectorAll(
6093 'input[type="radio"][name="' + element.name + '"]');
6094 return filter(radios, function(el) {
6095 return el != element && !el.form;
6096 });
6097 }
6098 }
6099
6100 function checkedPostEvent(input) {
6101 // Only the radio button that is getting checked gets an event. We
6102 // therefore find all the associated radio buttons and update their
6103 // check binding manually.
6104 if (input.tagName === 'INPUT' &&
6105 input.type === 'radio') {
6106 getAssociatedRadioButtons(input).forEach(function(radio) {
6107 var checkedBinding = radio.bindings_.checked;
6108 if (checkedBinding) {
6109 // Set the value directly to avoid an infinite call stack.
6110 checkedBinding.observable_.setValue(false);
6111 }
6112 });
6113 }
6114 }
6115
6116 HTMLInputElement.prototype.bind = function(name, value, oneTime) {
6117 if (name !== 'value' && name !== 'checked')
6118 return HTMLElement.prototype.bind.call(this, name, value, oneTime);
6119
6120 this.removeAttribute(name);
6121 var sanitizeFn = name == 'checked' ? booleanSanitize : sanitizeValue;
6122 var postEventFn = name == 'checked' ? checkedPostEvent : noop;
6123
6124 if (oneTime)
6125 return updateInput(this, name, value, sanitizeFn);
6126
6127
6128 var observable = value;
6129 var binding = bindInputEvent(this, name, observable, postEventFn);
6130 updateInput(this, name,
6131 observable.open(inputBinding(this, name, sanitizeFn)),
6132 sanitizeFn);
6133
6134 // Checkboxes may need to update bindings of other checkboxes.
6135 return updateBindings(this, name, binding);
6136 }
6137
6138 HTMLTextAreaElement.prototype.bind = function(name, value, oneTime) {
6139 if (name !== 'value')
6140 return HTMLElement.prototype.bind.call(this, name, value, oneTime);
6141
6142 this.removeAttribute('value');
6143
6144 if (oneTime)
6145 return updateInput(this, 'value', value);
6146
6147 var observable = value;
6148 var binding = bindInputEvent(this, 'value', observable);
6149 updateInput(this, 'value',
6150 observable.open(inputBinding(this, 'value', sanitizeValue)));
6151 return maybeUpdateBindings(this, name, binding);
6152 }
6153
6154 function updateOption(option, value) {
6155 var parentNode = option.parentNode;;
6156 var select;
6157 var selectBinding;
6158 var oldValue;
6159 if (parentNode instanceof HTMLSelectElement &&
6160 parentNode.bindings_ &&
6161 parentNode.bindings_.value) {
6162 select = parentNode;
6163 selectBinding = select.bindings_.value;
6164 oldValue = select.value;
6165 }
6166
6167 option.value = sanitizeValue(value);
6168
6169 if (select && select.value != oldValue) {
6170 selectBinding.observable_.setValue(select.value);
6171 selectBinding.observable_.discardChanges();
6172 Platform.performMicrotaskCheckpoint();
6173 }
6174 }
6175
6176 function optionBinding(option) {
6177 return function(value) {
6178 updateOption(option, value);
6179 }
6180 }
6181
6182 HTMLOptionElement.prototype.bind = function(name, value, oneTime) {
6183 if (name !== 'value')
6184 return HTMLElement.prototype.bind.call(this, name, value, oneTime);
6185
6186 this.removeAttribute('value');
6187
6188 if (oneTime)
6189 return updateOption(this, value);
6190
6191 var observable = value;
6192 var binding = bindInputEvent(this, 'value', observable);
6193 updateOption(this, observable.open(optionBinding(this)));
6194 return maybeUpdateBindings(this, name, binding);
6195 }
6196
6197 HTMLSelectElement.prototype.bind = function(name, value, oneTime) {
6198 if (name === 'selectedindex')
6199 name = 'selectedIndex';
6200
6201 if (name !== 'selectedIndex' && name !== 'value')
6202 return HTMLElement.prototype.bind.call(this, name, value, oneTime);
6203
6204 this.removeAttribute(name);
6205
6206 if (oneTime)
6207 return updateInput(this, name, value);
6208
6209 var observable = value;
6210 var binding = bindInputEvent(this, name, observable);
6211 updateInput(this, name,
6212 observable.open(inputBinding(this, name)));
6213
6214 // Option update events may need to access select bindings.
6215 return updateBindings(this, name, binding);
6216 }
6217 })(this);
6218
6219 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
6220 // This code may only be used under the BSD style license found at http://polyme r.github.io/LICENSE.txt
6221 // The complete set of authors may be found at http://polymer.github.io/AUTHORS. txt
6222 // The complete set of contributors may be found at http://polymer.github.io/CON TRIBUTORS.txt
6223 // Code distributed by Google as part of the polymer project is also
6224 // subject to an additional IP rights grant found at http://polymer.github.io/PA TENTS.txt
6225
6226 (function(global) {
6227 'use strict';
6228
6229 function assert(v) {
6230 if (!v)
6231 throw new Error('Assertion failed');
6232 }
6233
6234 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
6235
6236 function getFragmentRoot(node) {
6237 var p;
6238 while (p = node.parentNode) {
6239 node = p;
6240 }
6241
6242 return node;
6243 }
6244
6245 function searchRefId(node, id) {
6246 if (!id)
6247 return;
6248
6249 var ref;
6250 var selector = '#' + id;
6251 while (!ref) {
6252 node = getFragmentRoot(node);
6253
6254 if (node.protoContent_)
6255 ref = node.protoContent_.querySelector(selector);
6256 else if (node.getElementById)
6257 ref = node.getElementById(id);
6258
6259 if (ref || !node.templateCreator_)
6260 break
6261
6262 node = node.templateCreator_;
6263 }
6264
6265 return ref;
6266 }
6267
6268 function getInstanceRoot(node) {
6269 while (node.parentNode) {
6270 node = node.parentNode;
6271 }
6272 return node.templateCreator_ ? node : null;
6273 }
6274
6275 var Map;
6276 if (global.Map && typeof global.Map.prototype.forEach === 'function') {
6277 Map = global.Map;
6278 } else {
6279 Map = function() {
6280 this.keys = [];
6281 this.values = [];
6282 };
6283
6284 Map.prototype = {
6285 set: function(key, value) {
6286 var index = this.keys.indexOf(key);
6287 if (index < 0) {
6288 this.keys.push(key);
6289 this.values.push(value);
6290 } else {
6291 this.values[index] = value;
6292 }
6293 },
6294
6295 get: function(key) {
6296 var index = this.keys.indexOf(key);
6297 if (index < 0)
6298 return;
6299
6300 return this.values[index];
6301 },
6302
6303 delete: function(key, value) {
6304 var index = this.keys.indexOf(key);
6305 if (index < 0)
6306 return false;
6307
6308 this.keys.splice(index, 1);
6309 this.values.splice(index, 1);
6310 return true;
6311 },
6312
6313 forEach: function(f, opt_this) {
6314 for (var i = 0; i < this.keys.length; i++)
6315 f.call(opt_this || this, this.values[i], this.keys[i], this);
6316 }
6317 };
6318 }
6319
6320 // JScript does not have __proto__. We wrap all object literals with
6321 // createObject which uses Object.create, Object.defineProperty and
6322 // Object.getOwnPropertyDescriptor to create a new object that does the exact
6323 // same thing. The main downside to this solution is that we have to extract
6324 // all those property descriptors for IE.
6325 var createObject = ('__proto__' in {}) ?
6326 function(obj) { return obj; } :
6327 function(obj) {
6328 var proto = obj.__proto__;
6329 if (!proto)
6330 return obj;
6331 var newObject = Object.create(proto);
6332 Object.getOwnPropertyNames(obj).forEach(function(name) {
6333 Object.defineProperty(newObject, name,
6334 Object.getOwnPropertyDescriptor(obj, name));
6335 });
6336 return newObject;
6337 };
6338
6339 // IE does not support have Document.prototype.contains.
6340 if (typeof document.contains != 'function') {
6341 Document.prototype.contains = function(node) {
6342 if (node === this || node.parentNode === this)
6343 return true;
6344 return this.documentElement.contains(node);
6345 }
6346 }
6347
6348 var BIND = 'bind';
6349 var REPEAT = 'repeat';
6350 var IF = 'if';
6351
6352 var templateAttributeDirectives = {
6353 'template': true,
6354 'repeat': true,
6355 'bind': true,
6356 'ref': true
6357 };
6358
6359 var semanticTemplateElements = {
6360 'THEAD': true,
6361 'TBODY': true,
6362 'TFOOT': true,
6363 'TH': true,
6364 'TR': true,
6365 'TD': true,
6366 'COLGROUP': true,
6367 'COL': true,
6368 'CAPTION': true,
6369 'OPTION': true,
6370 'OPTGROUP': true
6371 };
6372
6373 var hasTemplateElement = typeof HTMLTemplateElement !== 'undefined';
6374 if (hasTemplateElement) {
6375 // TODO(rafaelw): Remove when fix for
6376 // https://codereview.chromium.org/164803002/
6377 // makes it to Chrome release.
6378 (function() {
6379 var t = document.createElement('template');
6380 var d = t.content.ownerDocument;
6381 var html = d.appendChild(d.createElement('html'));
6382 var head = html.appendChild(d.createElement('head'));
6383 var base = d.createElement('base');
6384 base.href = document.baseURI;
6385 head.appendChild(base);
6386 })();
6387 }
6388
6389 var allTemplatesSelectors = 'template, ' +
6390 Object.keys(semanticTemplateElements).map(function(tagName) {
6391 return tagName.toLowerCase() + '[template]';
6392 }).join(', ');
6393
6394 function isSVGTemplate(el) {
6395 return el.tagName == 'template' &&
6396 el.namespaceURI == 'http://www.w3.org/2000/svg';
6397 }
6398
6399 function isHTMLTemplate(el) {
6400 return el.tagName == 'TEMPLATE' &&
6401 el.namespaceURI == 'http://www.w3.org/1999/xhtml';
6402 }
6403
6404 function isAttributeTemplate(el) {
6405 return Boolean(semanticTemplateElements[el.tagName] &&
6406 el.hasAttribute('template'));
6407 }
6408
6409 function isTemplate(el) {
6410 if (el.isTemplate_ === undefined)
6411 el.isTemplate_ = el.tagName == 'TEMPLATE' || isAttributeTemplate(el);
6412
6413 return el.isTemplate_;
6414 }
6415
6416 // FIXME: Observe templates being added/removed from documents
6417 // FIXME: Expose imperative API to decorate and observe templates in
6418 // "disconnected tress" (e.g. ShadowRoot)
6419 document.addEventListener('DOMContentLoaded', function(e) {
6420 bootstrapTemplatesRecursivelyFrom(document);
6421 // FIXME: Is this needed? Seems like it shouldn't be.
6422 Platform.performMicrotaskCheckpoint();
6423 }, false);
6424
6425 function forAllTemplatesFrom(node, fn) {
6426 var subTemplates = node.querySelectorAll(allTemplatesSelectors);
6427
6428 if (isTemplate(node))
6429 fn(node)
6430 forEach(subTemplates, fn);
6431 }
6432
6433 function bootstrapTemplatesRecursivelyFrom(node) {
6434 function bootstrap(template) {
6435 if (!HTMLTemplateElement.decorate(template))
6436 bootstrapTemplatesRecursivelyFrom(template.content);
6437 }
6438
6439 forAllTemplatesFrom(node, bootstrap);
6440 }
6441
6442 if (!hasTemplateElement) {
6443 /**
6444 * This represents a <template> element.
6445 * @constructor
6446 * @extends {HTMLElement}
6447 */
6448 global.HTMLTemplateElement = function() {
6449 throw TypeError('Illegal constructor');
6450 };
6451 }
6452
6453 var hasProto = '__proto__' in {};
6454
6455 function mixin(to, from) {
6456 Object.getOwnPropertyNames(from).forEach(function(name) {
6457 Object.defineProperty(to, name,
6458 Object.getOwnPropertyDescriptor(from, name));
6459 });
6460 }
6461
6462 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html# dfn-template-contents-owner
6463 function getOrCreateTemplateContentsOwner(template) {
6464 var doc = template.ownerDocument
6465 if (!doc.defaultView)
6466 return doc;
6467 var d = doc.templateContentsOwner_;
6468 if (!d) {
6469 // TODO(arv): This should either be a Document or HTMLDocument depending
6470 // on doc.
6471 d = doc.implementation.createHTMLDocument('');
6472 while (d.lastChild) {
6473 d.removeChild(d.lastChild);
6474 }
6475 doc.templateContentsOwner_ = d;
6476 }
6477 return d;
6478 }
6479
6480 function getTemplateStagingDocument(template) {
6481 if (!template.stagingDocument_) {
6482 var owner = template.ownerDocument;
6483 if (!owner.stagingDocument_) {
6484 owner.stagingDocument_ = owner.implementation.createHTMLDocument('');
6485 owner.stagingDocument_.isStagingDocument = true;
6486 // TODO(rafaelw): Remove when fix for
6487 // https://codereview.chromium.org/164803002/
6488 // makes it to Chrome release.
6489 var base = owner.stagingDocument_.createElement('base');
6490 base.href = document.baseURI;
6491 owner.stagingDocument_.head.appendChild(base);
6492
6493 owner.stagingDocument_.stagingDocument_ = owner.stagingDocument_;
6494 }
6495
6496 template.stagingDocument_ = owner.stagingDocument_;
6497 }
6498
6499 return template.stagingDocument_;
6500 }
6501
6502 // For non-template browsers, the parser will disallow <template> in certain
6503 // locations, so we allow "attribute templates" which combine the template
6504 // element with the top-level container node of the content, e.g.
6505 //
6506 // <tr template repeat="{{ foo }}"" class="bar"><td>Bar</td></tr>
6507 //
6508 // becomes
6509 //
6510 // <template repeat="{{ foo }}">
6511 // + #document-fragment
6512 // + <tr class="bar">
6513 // + <td>Bar</td>
6514 //
6515 function extractTemplateFromAttributeTemplate(el) {
6516 var template = el.ownerDocument.createElement('template');
6517 el.parentNode.insertBefore(template, el);
6518
6519 var attribs = el.attributes;
6520 var count = attribs.length;
6521 while (count-- > 0) {
6522 var attrib = attribs[count];
6523 if (templateAttributeDirectives[attrib.name]) {
6524 if (attrib.name !== 'template')
6525 template.setAttribute(attrib.name, attrib.value);
6526 el.removeAttribute(attrib.name);
6527 }
6528 }
6529
6530 return template;
6531 }
6532
6533 function extractTemplateFromSVGTemplate(el) {
6534 var template = el.ownerDocument.createElement('template');
6535 el.parentNode.insertBefore(template, el);
6536
6537 var attribs = el.attributes;
6538 var count = attribs.length;
6539 while (count-- > 0) {
6540 var attrib = attribs[count];
6541 template.setAttribute(attrib.name, attrib.value);
6542 el.removeAttribute(attrib.name);
6543 }
6544
6545 el.parentNode.removeChild(el);
6546 return template;
6547 }
6548
6549 function liftNonNativeTemplateChildrenIntoContent(template, el, useRoot) {
6550 var content = template.content;
6551 if (useRoot) {
6552 content.appendChild(el);
6553 return;
6554 }
6555
6556 var child;
6557 while (child = el.firstChild) {
6558 content.appendChild(child);
6559 }
6560 }
6561
6562 var templateObserver;
6563 if (typeof MutationObserver == 'function') {
6564 templateObserver = new MutationObserver(function(records) {
6565 for (var i = 0; i < records.length; i++) {
6566 records[i].target.refChanged_();
6567 }
6568 });
6569 }
6570
6571 /**
6572 * Ensures proper API and content model for template elements.
6573 * @param {HTMLTemplateElement} opt_instanceRef The template element which
6574 * |el| template element will return as the value of its ref(), and whose
6575 * content will be used as source when createInstance() is invoked.
6576 */
6577 HTMLTemplateElement.decorate = function(el, opt_instanceRef) {
6578 if (el.templateIsDecorated_)
6579 return false;
6580
6581 var templateElement = el;
6582 templateElement.templateIsDecorated_ = true;
6583
6584 var isNativeHTMLTemplate = isHTMLTemplate(templateElement) &&
6585 hasTemplateElement;
6586 var bootstrapContents = isNativeHTMLTemplate;
6587 var liftContents = !isNativeHTMLTemplate;
6588 var liftRoot = false;
6589
6590 if (!isNativeHTMLTemplate) {
6591 if (isAttributeTemplate(templateElement)) {
6592 assert(!opt_instanceRef);
6593 templateElement = extractTemplateFromAttributeTemplate(el);
6594 templateElement.templateIsDecorated_ = true;
6595 isNativeHTMLTemplate = hasTemplateElement;
6596 liftRoot = true;
6597 } else if (isSVGTemplate(templateElement)) {
6598 templateElement = extractTemplateFromSVGTemplate(el);
6599 templateElement.templateIsDecorated_ = true;
6600 isNativeHTMLTemplate = hasTemplateElement;
6601 }
6602 }
6603
6604 if (!isNativeHTMLTemplate) {
6605 fixTemplateElementPrototype(templateElement);
6606 var doc = getOrCreateTemplateContentsOwner(templateElement);
6607 templateElement.content_ = doc.createDocumentFragment();
6608 }
6609
6610 if (opt_instanceRef) {
6611 // template is contained within an instance, its direct content must be
6612 // empty
6613 templateElement.instanceRef_ = opt_instanceRef;
6614 } else if (liftContents) {
6615 liftNonNativeTemplateChildrenIntoContent(templateElement,
6616 el,
6617 liftRoot);
6618 } else if (bootstrapContents) {
6619 bootstrapTemplatesRecursivelyFrom(templateElement.content);
6620 }
6621
6622 return true;
6623 };
6624
6625 // TODO(rafaelw): This used to decorate recursively all templates from a given
6626 // node. This happens by default on 'DOMContentLoaded', but may be needed
6627 // in subtrees not descendent from document (e.g. ShadowRoot).
6628 // Review whether this is the right public API.
6629 HTMLTemplateElement.bootstrap = bootstrapTemplatesRecursivelyFrom;
6630
6631 var htmlElement = global.HTMLUnknownElement || HTMLElement;
6632
6633 var contentDescriptor = {
6634 get: function() {
6635 return this.content_;
6636 },
6637 enumerable: true,
6638 configurable: true
6639 };
6640
6641 if (!hasTemplateElement) {
6642 // Gecko is more picky with the prototype than WebKit. Make sure to use the
6643 // same prototype as created in the constructor.
6644 HTMLTemplateElement.prototype = Object.create(htmlElement.prototype);
6645
6646 Object.defineProperty(HTMLTemplateElement.prototype, 'content',
6647 contentDescriptor);
6648 }
6649
6650 function fixTemplateElementPrototype(el) {
6651 if (hasProto)
6652 el.__proto__ = HTMLTemplateElement.prototype;
6653 else
6654 mixin(el, HTMLTemplateElement.prototype);
6655 }
6656
6657 function ensureSetModelScheduled(template) {
6658 if (!template.setModelFn_) {
6659 template.setModelFn_ = function() {
6660 template.setModelFnScheduled_ = false;
6661 var map = getBindings(template,
6662 template.delegate_ && template.delegate_.prepareBinding);
6663 processBindings(template, map, template.model_);
6664 };
6665 }
6666
6667 if (!template.setModelFnScheduled_) {
6668 template.setModelFnScheduled_ = true;
6669 Observer.runEOM_(template.setModelFn_);
6670 }
6671 }
6672
6673 mixin(HTMLTemplateElement.prototype, {
6674 bind: function(name, value, oneTime) {
6675 if (name != 'ref')
6676 return Element.prototype.bind.call(this, name, value, oneTime);
6677
6678 var self = this;
6679 var ref = oneTime ? value : value.open(function(ref) {
6680 self.setAttribute('ref', ref);
6681 self.refChanged_();
6682 });
6683
6684 this.setAttribute('ref', ref);
6685 this.refChanged_();
6686 if (oneTime)
6687 return;
6688
6689 if (!this.bindings_) {
6690 this.bindings_ = { ref: value };
6691 } else {
6692 this.bindings_.ref = value;
6693 }
6694
6695 return value;
6696 },
6697
6698 processBindingDirectives_: function(directives) {
6699 if (this.iterator_)
6700 this.iterator_.closeDeps();
6701
6702 if (!directives.if && !directives.bind && !directives.repeat) {
6703 if (this.iterator_) {
6704 this.iterator_.close();
6705 this.iterator_ = undefined;
6706 }
6707
6708 return;
6709 }
6710
6711 if (!this.iterator_) {
6712 this.iterator_ = new TemplateIterator(this);
6713 }
6714
6715 this.iterator_.updateDependencies(directives, this.model_);
6716
6717 if (templateObserver) {
6718 templateObserver.observe(this, { attributes: true,
6719 attributeFilter: ['ref'] });
6720 }
6721
6722 return this.iterator_;
6723 },
6724
6725 createInstance: function(model, bindingDelegate, delegate_) {
6726 if (bindingDelegate)
6727 delegate_ = this.newDelegate_(bindingDelegate);
6728 else if (!delegate_)
6729 delegate_ = this.delegate_;
6730
6731 if (!this.refContent_)
6732 this.refContent_ = this.ref_.content;
6733 var content = this.refContent_;
6734 if (content.firstChild === null)
6735 return emptyInstance;
6736
6737 var map = getInstanceBindingMap(content, delegate_);
6738 var stagingDocument = getTemplateStagingDocument(this);
6739 var instance = stagingDocument.createDocumentFragment();
6740 instance.templateCreator_ = this;
6741 instance.protoContent_ = content;
6742 instance.bindings_ = [];
6743 instance.terminator_ = null;
6744 var instanceRecord = instance.templateInstance_ = {
6745 firstNode: null,
6746 lastNode: null,
6747 model: model
6748 };
6749
6750 var i = 0;
6751 var collectTerminator = false;
6752 for (var child = content.firstChild; child; child = child.nextSibling) {
6753 // The terminator of the instance is the clone of the last child of the
6754 // content. If the last child is an active template, it may produce
6755 // instances as a result of production, so simply collecting the last
6756 // child of the instance after it has finished producing may be wrong.
6757 if (child.nextSibling === null)
6758 collectTerminator = true;
6759
6760 var clone = cloneAndBindInstance(child, instance, stagingDocument,
6761 map.children[i++],
6762 model,
6763 delegate_,
6764 instance.bindings_);
6765 clone.templateInstance_ = instanceRecord;
6766 if (collectTerminator)
6767 instance.terminator_ = clone;
6768 }
6769
6770 instanceRecord.firstNode = instance.firstChild;
6771 instanceRecord.lastNode = instance.lastChild;
6772 instance.templateCreator_ = undefined;
6773 instance.protoContent_ = undefined;
6774 return instance;
6775 },
6776
6777 get model() {
6778 return this.model_;
6779 },
6780
6781 set model(model) {
6782 this.model_ = model;
6783 ensureSetModelScheduled(this);
6784 },
6785
6786 get bindingDelegate() {
6787 return this.delegate_ && this.delegate_.raw;
6788 },
6789
6790 refChanged_: function() {
6791 if (!this.iterator_ || this.refContent_ === this.ref_.content)
6792 return;
6793
6794 this.refContent_ = undefined;
6795 this.iterator_.valueChanged();
6796 this.iterator_.updateIteratedValue(this.iterator_.getUpdatedValue());
6797 },
6798
6799 clear: function() {
6800 this.model_ = undefined;
6801 this.delegate_ = undefined;
6802 if (this.bindings_ && this.bindings_.ref)
6803 this.bindings_.ref.close()
6804 this.refContent_ = undefined;
6805 if (!this.iterator_)
6806 return;
6807 this.iterator_.valueChanged();
6808 this.iterator_.close()
6809 this.iterator_ = undefined;
6810 },
6811
6812 setDelegate_: function(delegate) {
6813 this.delegate_ = delegate;
6814 this.bindingMap_ = undefined;
6815 if (this.iterator_) {
6816 this.iterator_.instancePositionChangedFn_ = undefined;
6817 this.iterator_.instanceModelFn_ = undefined;
6818 }
6819 },
6820
6821 newDelegate_: function(bindingDelegate) {
6822 if (!bindingDelegate)
6823 return;
6824
6825 function delegateFn(name) {
6826 var fn = bindingDelegate && bindingDelegate[name];
6827 if (typeof fn != 'function')
6828 return;
6829
6830 return function() {
6831 return fn.apply(bindingDelegate, arguments);
6832 };
6833 }
6834
6835 return {
6836 bindingMaps: {},
6837 raw: bindingDelegate,
6838 prepareBinding: delegateFn('prepareBinding'),
6839 prepareInstanceModel: delegateFn('prepareInstanceModel'),
6840 prepareInstancePositionChanged:
6841 delegateFn('prepareInstancePositionChanged')
6842 };
6843 },
6844
6845 set bindingDelegate(bindingDelegate) {
6846 if (this.delegate_) {
6847 throw Error('Template must be cleared before a new bindingDelegate ' +
6848 'can be assigned');
6849 }
6850
6851 this.setDelegate_(this.newDelegate_(bindingDelegate));
6852 },
6853
6854 get ref_() {
6855 var ref = searchRefId(this, this.getAttribute('ref'));
6856 if (!ref)
6857 ref = this.instanceRef_;
6858
6859 if (!ref)
6860 return this;
6861
6862 var nextRef = ref.ref_;
6863 return nextRef ? nextRef : ref;
6864 }
6865 });
6866
6867 // Returns
6868 // a) undefined if there are no mustaches.
6869 // b) [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least one mustache.
6870 function parseMustaches(s, name, node, prepareBindingFn) {
6871 if (!s || !s.length)
6872 return;
6873
6874 var tokens;
6875 var length = s.length;
6876 var startIndex = 0, lastIndex = 0, endIndex = 0;
6877 var onlyOneTime = true;
6878 while (lastIndex < length) {
6879 var startIndex = s.indexOf('{{', lastIndex);
6880 var oneTimeStart = s.indexOf('[[', lastIndex);
6881 var oneTime = false;
6882 var terminator = '}}';
6883
6884 if (oneTimeStart >= 0 &&
6885 (startIndex < 0 || oneTimeStart < startIndex)) {
6886 startIndex = oneTimeStart;
6887 oneTime = true;
6888 terminator = ']]';
6889 }
6890
6891 endIndex = startIndex < 0 ? -1 : s.indexOf(terminator, startIndex + 2);
6892
6893 if (endIndex < 0) {
6894 if (!tokens)
6895 return;
6896
6897 tokens.push(s.slice(lastIndex)); // TEXT
6898 break;
6899 }
6900
6901 tokens = tokens || [];
6902 tokens.push(s.slice(lastIndex, startIndex)); // TEXT
6903 var pathString = s.slice(startIndex + 2, endIndex).trim();
6904 tokens.push(oneTime); // ONE_TIME?
6905 onlyOneTime = onlyOneTime && oneTime;
6906 var delegateFn = prepareBindingFn &&
6907 prepareBindingFn(pathString, name, node);
6908 // Don't try to parse the expression if there's a prepareBinding function
6909 if (delegateFn == null) {
6910 tokens.push(Path.get(pathString)); // PATH
6911 } else {
6912 tokens.push(null);
6913 }
6914 tokens.push(delegateFn); // DELEGATE_FN
6915 lastIndex = endIndex + 2;
6916 }
6917
6918 if (lastIndex === length)
6919 tokens.push(''); // TEXT
6920
6921 tokens.hasOnePath = tokens.length === 5;
6922 tokens.isSimplePath = tokens.hasOnePath &&
6923 tokens[0] == '' &&
6924 tokens[4] == '';
6925 tokens.onlyOneTime = onlyOneTime;
6926
6927 tokens.combinator = function(values) {
6928 var newValue = tokens[0];
6929
6930 for (var i = 1; i < tokens.length; i += 4) {
6931 var value = tokens.hasOnePath ? values : values[(i - 1) / 4];
6932 if (value !== undefined)
6933 newValue += value;
6934 newValue += tokens[i + 3];
6935 }
6936
6937 return newValue;
6938 }
6939
6940 return tokens;
6941 };
6942
6943 function processOneTimeBinding(name, tokens, node, model) {
6944 if (tokens.hasOnePath) {
6945 var delegateFn = tokens[3];
6946 var value = delegateFn ? delegateFn(model, node, true) :
6947 tokens[2].getValueFrom(model);
6948 return tokens.isSimplePath ? value : tokens.combinator(value);
6949 }
6950
6951 var values = [];
6952 for (var i = 1; i < tokens.length; i += 4) {
6953 var delegateFn = tokens[i + 2];
6954 values[(i - 1) / 4] = delegateFn ? delegateFn(model, node) :
6955 tokens[i + 1].getValueFrom(model);
6956 }
6957
6958 return tokens.combinator(values);
6959 }
6960
6961 function processSinglePathBinding(name, tokens, node, model) {
6962 var delegateFn = tokens[3];
6963 var observer = delegateFn ? delegateFn(model, node, false) :
6964 new PathObserver(model, tokens[2]);
6965
6966 return tokens.isSimplePath ? observer :
6967 new ObserverTransform(observer, tokens.combinator);
6968 }
6969
6970 function processBinding(name, tokens, node, model) {
6971 if (tokens.onlyOneTime)
6972 return processOneTimeBinding(name, tokens, node, model);
6973
6974 if (tokens.hasOnePath)
6975 return processSinglePathBinding(name, tokens, node, model);
6976
6977 var observer = new CompoundObserver();
6978
6979 for (var i = 1; i < tokens.length; i += 4) {
6980 var oneTime = tokens[i];
6981 var delegateFn = tokens[i + 2];
6982
6983 if (delegateFn) {
6984 var value = delegateFn(model, node, oneTime);
6985 if (oneTime)
6986 observer.addPath(value)
6987 else
6988 observer.addObserver(value);
6989 continue;
6990 }
6991
6992 var path = tokens[i + 1];
6993 if (oneTime)
6994 observer.addPath(path.getValueFrom(model))
6995 else
6996 observer.addPath(model, path);
6997 }
6998
6999 return new ObserverTransform(observer, tokens.combinator);
7000 }
7001
7002 function processBindings(node, bindings, model, instanceBindings) {
7003 for (var i = 0; i < bindings.length; i += 2) {
7004 var name = bindings[i]
7005 var tokens = bindings[i + 1];
7006 var value = processBinding(name, tokens, node, model);
7007 var binding = node.bind(name, value, tokens.onlyOneTime);
7008 if (binding && instanceBindings)
7009 instanceBindings.push(binding);
7010 }
7011
7012 node.bindFinished();
7013 if (!bindings.isTemplate)
7014 return;
7015
7016 node.model_ = model;
7017 var iter = node.processBindingDirectives_(bindings);
7018 if (instanceBindings && iter)
7019 instanceBindings.push(iter);
7020 }
7021
7022 function parseWithDefault(el, name, prepareBindingFn) {
7023 var v = el.getAttribute(name);
7024 return parseMustaches(v == '' ? '{{}}' : v, name, el, prepareBindingFn);
7025 }
7026
7027 function parseAttributeBindings(element, prepareBindingFn) {
7028 assert(element);
7029
7030 var bindings = [];
7031 var ifFound = false;
7032 var bindFound = false;
7033
7034 for (var i = 0; i < element.attributes.length; i++) {
7035 var attr = element.attributes[i];
7036 var name = attr.name;
7037 var value = attr.value;
7038
7039 // Allow bindings expressed in attributes to be prefixed with underbars.
7040 // We do this to allow correct semantics for browsers that don't implement
7041 // <template> where certain attributes might trigger side-effects -- and
7042 // for IE which sanitizes certain attributes, disallowing mustache
7043 // replacements in their text.
7044 while (name[0] === '_') {
7045 name = name.substring(1);
7046 }
7047
7048 if (isTemplate(element) &&
7049 (name === IF || name === BIND || name === REPEAT)) {
7050 continue;
7051 }
7052
7053 var tokens = parseMustaches(value, name, element,
7054 prepareBindingFn);
7055 if (!tokens)
7056 continue;
7057
7058 bindings.push(name, tokens);
7059 }
7060
7061 if (isTemplate(element)) {
7062 bindings.isTemplate = true;
7063 bindings.if = parseWithDefault(element, IF, prepareBindingFn);
7064 bindings.bind = parseWithDefault(element, BIND, prepareBindingFn);
7065 bindings.repeat = parseWithDefault(element, REPEAT, prepareBindingFn);
7066
7067 if (bindings.if && !bindings.bind && !bindings.repeat)
7068 bindings.bind = parseMustaches('{{}}', BIND, element, prepareBindingFn);
7069 }
7070
7071 return bindings;
7072 }
7073
7074 function getBindings(node, prepareBindingFn) {
7075 if (node.nodeType === Node.ELEMENT_NODE)
7076 return parseAttributeBindings(node, prepareBindingFn);
7077
7078 if (node.nodeType === Node.TEXT_NODE) {
7079 var tokens = parseMustaches(node.data, 'textContent', node,
7080 prepareBindingFn);
7081 if (tokens)
7082 return ['textContent', tokens];
7083 }
7084
7085 return [];
7086 }
7087
7088 function cloneAndBindInstance(node, parent, stagingDocument, bindings, model,
7089 delegate,
7090 instanceBindings,
7091 instanceRecord) {
7092 var clone = parent.appendChild(stagingDocument.importNode(node, false));
7093
7094 var i = 0;
7095 for (var child = node.firstChild; child; child = child.nextSibling) {
7096 cloneAndBindInstance(child, clone, stagingDocument,
7097 bindings.children[i++],
7098 model,
7099 delegate,
7100 instanceBindings);
7101 }
7102
7103 if (bindings.isTemplate) {
7104 HTMLTemplateElement.decorate(clone, node);
7105 if (delegate)
7106 clone.setDelegate_(delegate);
7107 }
7108
7109 processBindings(clone, bindings, model, instanceBindings);
7110 return clone;
7111 }
7112
7113 function createInstanceBindingMap(node, prepareBindingFn) {
7114 var map = getBindings(node, prepareBindingFn);
7115 map.children = {};
7116 var index = 0;
7117 for (var child = node.firstChild; child; child = child.nextSibling) {
7118 map.children[index++] = createInstanceBindingMap(child, prepareBindingFn);
7119 }
7120
7121 return map;
7122 }
7123
7124 var contentUidCounter = 1;
7125
7126 // TODO(rafaelw): Setup a MutationObserver on content which clears the id
7127 // so that bindingMaps regenerate when the template.content changes.
7128 function getContentUid(content) {
7129 var id = content.id_;
7130 if (!id)
7131 id = content.id_ = contentUidCounter++;
7132 return id;
7133 }
7134
7135 // Each delegate is associated with a set of bindingMaps, one for each
7136 // content which may be used by a template. The intent is that each binding
7137 // delegate gets the opportunity to prepare the instance (via the prepare*
7138 // delegate calls) once across all uses.
7139 // TODO(rafaelw): Separate out the parse map from the binding map. In the
7140 // current implementation, if two delegates need a binding map for the same
7141 // content, the second will have to reparse.
7142 function getInstanceBindingMap(content, delegate_) {
7143 var contentId = getContentUid(content);
7144 if (delegate_) {
7145 var map = delegate_.bindingMaps[contentId];
7146 if (!map) {
7147 map = delegate_.bindingMaps[contentId] =
7148 createInstanceBindingMap(content, delegate_.prepareBinding) || [];
7149 }
7150 return map;
7151 }
7152
7153 var map = content.bindingMap_;
7154 if (!map) {
7155 map = content.bindingMap_ =
7156 createInstanceBindingMap(content, undefined) || [];
7157 }
7158 return map;
7159 }
7160
7161 Object.defineProperty(Node.prototype, 'templateInstance', {
7162 get: function() {
7163 var instance = this.templateInstance_;
7164 return instance ? instance :
7165 (this.parentNode ? this.parentNode.templateInstance : undefined);
7166 }
7167 });
7168
7169 var emptyInstance = document.createDocumentFragment();
7170 emptyInstance.bindings_ = [];
7171 emptyInstance.terminator_ = null;
7172
7173 function TemplateIterator(templateElement) {
7174 this.closed = false;
7175 this.templateElement_ = templateElement;
7176 this.instances = [];
7177 this.deps = undefined;
7178 this.iteratedValue = [];
7179 this.presentValue = undefined;
7180 this.arrayObserver = undefined;
7181 }
7182
7183 TemplateIterator.prototype = {
7184 closeDeps: function() {
7185 var deps = this.deps;
7186 if (deps) {
7187 if (deps.ifOneTime === false)
7188 deps.ifValue.close();
7189 if (deps.oneTime === false)
7190 deps.value.close();
7191 }
7192 },
7193
7194 updateDependencies: function(directives, model) {
7195 this.closeDeps();
7196
7197 var deps = this.deps = {};
7198 var template = this.templateElement_;
7199
7200 var ifValue = true;
7201 if (directives.if) {
7202 deps.hasIf = true;
7203 deps.ifOneTime = directives.if.onlyOneTime;
7204 deps.ifValue = processBinding(IF, directives.if, template, model);
7205
7206 ifValue = deps.ifValue;
7207
7208 // oneTime if & predicate is false. nothing else to do.
7209 if (deps.ifOneTime && !ifValue) {
7210 this.valueChanged();
7211 return;
7212 }
7213
7214 if (!deps.ifOneTime)
7215 ifValue = ifValue.open(this.updateIfValue, this);
7216 }
7217
7218 if (directives.repeat) {
7219 deps.repeat = true;
7220 deps.oneTime = directives.repeat.onlyOneTime;
7221 deps.value = processBinding(REPEAT, directives.repeat, template, model);
7222 } else {
7223 deps.repeat = false;
7224 deps.oneTime = directives.bind.onlyOneTime;
7225 deps.value = processBinding(BIND, directives.bind, template, model);
7226 }
7227
7228 var value = deps.value;
7229 if (!deps.oneTime)
7230 value = value.open(this.updateIteratedValue, this);
7231
7232 if (!ifValue) {
7233 this.valueChanged();
7234 return;
7235 }
7236
7237 this.updateValue(value);
7238 },
7239
7240 /**
7241 * Gets the updated value of the bind/repeat. This can potentially call
7242 * user code (if a bindingDelegate is set up) so we try to avoid it if we
7243 * already have the value in hand (from Observer.open).
7244 */
7245 getUpdatedValue: function() {
7246 var value = this.deps.value;
7247 if (!this.deps.oneTime)
7248 value = value.discardChanges();
7249 return value;
7250 },
7251
7252 updateIfValue: function(ifValue) {
7253 if (!ifValue) {
7254 this.valueChanged();
7255 return;
7256 }
7257
7258 this.updateValue(this.getUpdatedValue());
7259 },
7260
7261 updateIteratedValue: function(value) {
7262 if (this.deps.hasIf) {
7263 var ifValue = this.deps.ifValue;
7264 if (!this.deps.ifOneTime)
7265 ifValue = ifValue.discardChanges();
7266 if (!ifValue) {
7267 this.valueChanged();
7268 return;
7269 }
7270 }
7271
7272 this.updateValue(value);
7273 },
7274
7275 updateValue: function(value) {
7276 if (!this.deps.repeat)
7277 value = [value];
7278 var observe = this.deps.repeat &&
7279 !this.deps.oneTime &&
7280 Array.isArray(value);
7281 this.valueChanged(value, observe);
7282 },
7283
7284 valueChanged: function(value, observeValue) {
7285 if (!Array.isArray(value))
7286 value = [];
7287
7288 if (value === this.iteratedValue)
7289 return;
7290
7291 this.unobserve();
7292 this.presentValue = value;
7293 if (observeValue) {
7294 this.arrayObserver = new ArrayObserver(this.presentValue);
7295 this.arrayObserver.open(this.handleSplices, this);
7296 }
7297
7298 this.handleSplices(ArrayObserver.calculateSplices(this.presentValue,
7299 this.iteratedValue));
7300 },
7301
7302 getLastInstanceNode: function(index) {
7303 if (index == -1)
7304 return this.templateElement_;
7305 var instance = this.instances[index];
7306 var terminator = instance.terminator_;
7307 if (!terminator)
7308 return this.getLastInstanceNode(index - 1);
7309
7310 if (terminator.nodeType !== Node.ELEMENT_NODE ||
7311 this.templateElement_ === terminator) {
7312 return terminator;
7313 }
7314
7315 var subtemplateIterator = terminator.iterator_;
7316 if (!subtemplateIterator)
7317 return terminator;
7318
7319 return subtemplateIterator.getLastTemplateNode();
7320 },
7321
7322 getLastTemplateNode: function() {
7323 return this.getLastInstanceNode(this.instances.length - 1);
7324 },
7325
7326 insertInstanceAt: function(index, fragment) {
7327 var previousInstanceLast = this.getLastInstanceNode(index - 1);
7328 var parent = this.templateElement_.parentNode;
7329 this.instances.splice(index, 0, fragment);
7330
7331 parent.insertBefore(fragment, previousInstanceLast.nextSibling);
7332 },
7333
7334 extractInstanceAt: function(index) {
7335 var previousInstanceLast = this.getLastInstanceNode(index - 1);
7336 var lastNode = this.getLastInstanceNode(index);
7337 var parent = this.templateElement_.parentNode;
7338 var instance = this.instances.splice(index, 1)[0];
7339
7340 while (lastNode !== previousInstanceLast) {
7341 var node = previousInstanceLast.nextSibling;
7342 if (node == lastNode)
7343 lastNode = previousInstanceLast;
7344
7345 instance.appendChild(parent.removeChild(node));
7346 }
7347
7348 return instance;
7349 },
7350
7351 getDelegateFn: function(fn) {
7352 fn = fn && fn(this.templateElement_);
7353 return typeof fn === 'function' ? fn : null;
7354 },
7355
7356 handleSplices: function(splices) {
7357 if (this.closed || !splices.length)
7358 return;
7359
7360 var template = this.templateElement_;
7361
7362 if (!template.parentNode) {
7363 this.close();
7364 return;
7365 }
7366
7367 ArrayObserver.applySplices(this.iteratedValue, this.presentValue,
7368 splices);
7369
7370 var delegate = template.delegate_;
7371 if (this.instanceModelFn_ === undefined) {
7372 this.instanceModelFn_ =
7373 this.getDelegateFn(delegate && delegate.prepareInstanceModel);
7374 }
7375
7376 if (this.instancePositionChangedFn_ === undefined) {
7377 this.instancePositionChangedFn_ =
7378 this.getDelegateFn(delegate &&
7379 delegate.prepareInstancePositionChanged);
7380 }
7381
7382 // Instance Removals
7383 var instanceCache = new Map;
7384 var removeDelta = 0;
7385 for (var i = 0; i < splices.length; i++) {
7386 var splice = splices[i];
7387 var removed = splice.removed;
7388 for (var j = 0; j < removed.length; j++) {
7389 var model = removed[j];
7390 var instance = this.extractInstanceAt(splice.index + removeDelta);
7391 if (instance !== emptyInstance) {
7392 instanceCache.set(model, instance);
7393 }
7394 }
7395
7396 removeDelta -= splice.addedCount;
7397 }
7398
7399 // Instance Insertions
7400 for (var i = 0; i < splices.length; i++) {
7401 var splice = splices[i];
7402 var addIndex = splice.index;
7403 for (; addIndex < splice.index + splice.addedCount; addIndex++) {
7404 var model = this.iteratedValue[addIndex];
7405 var instance = instanceCache.get(model);
7406 if (instance) {
7407 instanceCache.delete(model);
7408 } else {
7409 if (this.instanceModelFn_) {
7410 model = this.instanceModelFn_(model);
7411 }
7412
7413 if (model === undefined) {
7414 instance = emptyInstance;
7415 } else {
7416 instance = template.createInstance(model, undefined, delegate);
7417 }
7418 }
7419
7420 this.insertInstanceAt(addIndex, instance);
7421 }
7422 }
7423
7424 instanceCache.forEach(function(instance) {
7425 this.closeInstanceBindings(instance);
7426 }, this);
7427
7428 if (this.instancePositionChangedFn_)
7429 this.reportInstancesMoved(splices);
7430 },
7431
7432 reportInstanceMoved: function(index) {
7433 var instance = this.instances[index];
7434 if (instance === emptyInstance)
7435 return;
7436
7437 this.instancePositionChangedFn_(instance.templateInstance_, index);
7438 },
7439
7440 reportInstancesMoved: function(splices) {
7441 var index = 0;
7442 var offset = 0;
7443 for (var i = 0; i < splices.length; i++) {
7444 var splice = splices[i];
7445 if (offset != 0) {
7446 while (index < splice.index) {
7447 this.reportInstanceMoved(index);
7448 index++;
7449 }
7450 } else {
7451 index = splice.index;
7452 }
7453
7454 while (index < splice.index + splice.addedCount) {
7455 this.reportInstanceMoved(index);
7456 index++;
7457 }
7458
7459 offset += splice.addedCount - splice.removed.length;
7460 }
7461
7462 if (offset == 0)
7463 return;
7464
7465 var length = this.instances.length;
7466 while (index < length) {
7467 this.reportInstanceMoved(index);
7468 index++;
7469 }
7470 },
7471
7472 closeInstanceBindings: function(instance) {
7473 var bindings = instance.bindings_;
7474 for (var i = 0; i < bindings.length; i++) {
7475 bindings[i].close();
7476 }
7477 },
7478
7479 unobserve: function() {
7480 if (!this.arrayObserver)
7481 return;
7482
7483 this.arrayObserver.close();
7484 this.arrayObserver = undefined;
7485 },
7486
7487 close: function() {
7488 if (this.closed)
7489 return;
7490 this.unobserve();
7491 for (var i = 0; i < this.instances.length; i++) {
7492 this.closeInstanceBindings(this.instances[i]);
7493 }
7494
7495 this.instances.length = 0;
7496 this.closeDeps();
7497 this.templateElement_.iterator_ = undefined;
7498 this.closed = true;
7499 }
7500 };
7501
7502 // Polyfill-specific API.
7503 HTMLTemplateElement.forAllTemplatesFrom_ = forAllTemplatesFrom;
7504 })(this);
7505
7506 (function(scope) {
7507 'use strict';
7508
7509 // feature detect for URL constructor
7510 var hasWorkingUrl = false;
7511 if (!scope.forceJURL) {
7512 try {
7513 var u = new URL('b', 'http://a');
7514 hasWorkingUrl = u.href === 'http://a/b';
7515 } catch(e) {}
7516 }
7517
7518 if (hasWorkingUrl)
7519 return;
7520
7521 var relative = Object.create(null);
7522 relative['ftp'] = 21;
7523 relative['file'] = 0;
7524 relative['gopher'] = 70;
7525 relative['http'] = 80;
7526 relative['https'] = 443;
7527 relative['ws'] = 80;
7528 relative['wss'] = 443;
7529
7530 var relativePathDotMapping = Object.create(null);
7531 relativePathDotMapping['%2e'] = '.';
7532 relativePathDotMapping['.%2e'] = '..';
7533 relativePathDotMapping['%2e.'] = '..';
7534 relativePathDotMapping['%2e%2e'] = '..';
7535
7536 function isRelativeScheme(scheme) {
7537 return relative[scheme] !== undefined;
7538 }
7539
7540 function invalid() {
7541 clear.call(this);
7542 this._isInvalid = true;
7543 }
7544
7545 function IDNAToASCII(h) {
7546 if ('' == h) {
7547 invalid.call(this)
7548 }
7549 // XXX
7550 return h.toLowerCase()
7551 }
7552
7553 function percentEscape(c) {
7554 var unicode = c.charCodeAt(0);
7555 if (unicode > 0x20 &&
7556 unicode < 0x7F &&
7557 // " # < > ? `
7558 [0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) == -1
7559 ) {
7560 return c;
7561 }
7562 return encodeURIComponent(c);
7563 }
7564
7565 function percentEscapeQuery(c) {
7566 // XXX This actually needs to encode c using encoding and then
7567 // convert the bytes one-by-one.
7568
7569 var unicode = c.charCodeAt(0);
7570 if (unicode > 0x20 &&
7571 unicode < 0x7F &&
7572 // " # < > ` (do not escape '?')
7573 [0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) == -1
7574 ) {
7575 return c;
7576 }
7577 return encodeURIComponent(c);
7578 }
7579
7580 var EOF = undefined,
7581 ALPHA = /[a-zA-Z]/,
7582 ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/;
7583
7584 function parse(input, stateOverride, base) {
7585 function err(message) {
7586 errors.push(message)
7587 }
7588
7589 var state = stateOverride || 'scheme start',
7590 cursor = 0,
7591 buffer = '',
7592 seenAt = false,
7593 seenBracket = false,
7594 errors = [];
7595
7596 loop: while ((input[cursor - 1] != EOF || cursor == 0) && !this._isInvalid) {
7597 var c = input[cursor];
7598 switch (state) {
7599 case 'scheme start':
7600 if (c && ALPHA.test(c)) {
7601 buffer += c.toLowerCase(); // ASCII-safe
7602 state = 'scheme';
7603 } else if (!stateOverride) {
7604 buffer = '';
7605 state = 'no scheme';
7606 continue;
7607 } else {
7608 err('Invalid scheme.');
7609 break loop;
7610 }
7611 break;
7612
7613 case 'scheme':
7614 if (c && ALPHANUMERIC.test(c)) {
7615 buffer += c.toLowerCase(); // ASCII-safe
7616 } else if (':' == c) {
7617 this._scheme = buffer;
7618 buffer = '';
7619 if (stateOverride) {
7620 break loop;
7621 }
7622 if (isRelativeScheme(this._scheme)) {
7623 this._isRelative = true;
7624 }
7625 if ('file' == this._scheme) {
7626 state = 'relative';
7627 } else if (this._isRelative && base && base._scheme == this._scheme) {
7628 state = 'relative or authority';
7629 } else if (this._isRelative) {
7630 state = 'authority first slash';
7631 } else {
7632 state = 'scheme data';
7633 }
7634 } else if (!stateOverride) {
7635 buffer = '';
7636 cursor = 0;
7637 state = 'no scheme';
7638 continue;
7639 } else if (EOF == c) {
7640 break loop;
7641 } else {
7642 err('Code point not allowed in scheme: ' + c)
7643 break loop;
7644 }
7645 break;
7646
7647 case 'scheme data':
7648 if ('?' == c) {
7649 query = '?';
7650 state = 'query';
7651 } else if ('#' == c) {
7652 this._fragment = '#';
7653 state = 'fragment';
7654 } else {
7655 // XXX error handling
7656 if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
7657 this._schemeData += percentEscape(c);
7658 }
7659 }
7660 break;
7661
7662 case 'no scheme':
7663 if (!base || !(isRelativeScheme(base._scheme))) {
7664 err('Missing scheme.');
7665 invalid.call(this);
7666 } else {
7667 state = 'relative';
7668 continue;
7669 }
7670 break;
7671
7672 case 'relative or authority':
7673 if ('/' == c && '/' == input[cursor+1]) {
7674 state = 'authority ignore slashes';
7675 } else {
7676 err('Expected /, got: ' + c);
7677 state = 'relative';
7678 continue
7679 }
7680 break;
7681
7682 case 'relative':
7683 this._isRelative = true;
7684 if ('file' != this._scheme)
7685 this._scheme = base._scheme;
7686 if (EOF == c) {
7687 this._host = base._host;
7688 this._port = base._port;
7689 this._path = base._path.slice();
7690 this._query = base._query;
7691 break loop;
7692 } else if ('/' == c || '\\' == c) {
7693 if ('\\' == c)
7694 err('\\ is an invalid code point.');
7695 state = 'relative slash';
7696 } else if ('?' == c) {
7697 this._host = base._host;
7698 this._port = base._port;
7699 this._path = base._path.slice();
7700 this._query = '?';
7701 state = 'query';
7702 } else if ('#' == c) {
7703 this._host = base._host;
7704 this._port = base._port;
7705 this._path = base._path.slice();
7706 this._query = base._query;
7707 this._fragment = '#';
7708 state = 'fragment';
7709 } else {
7710 var nextC = input[cursor+1]
7711 var nextNextC = input[cursor+2]
7712 if (
7713 'file' != this._scheme || !ALPHA.test(c) ||
7714 (nextC != ':' && nextC != '|') ||
7715 (EOF != nextNextC && '/' != nextNextC && '\\' != nextNextC && '?' != nextNextC && '#' != nextNextC)) {
7716 this._host = base._host;
7717 this._port = base._port;
7718 this._path = base._path.slice();
7719 this._path.pop();
7720 }
7721 state = 'relative path';
7722 continue;
7723 }
7724 break;
7725
7726 case 'relative slash':
7727 if ('/' == c || '\\' == c) {
7728 if ('\\' == c) {
7729 err('\\ is an invalid code point.');
7730 }
7731 if ('file' == this._scheme) {
7732 state = 'file host';
7733 } else {
7734 state = 'authority ignore slashes';
7735 }
7736 } else {
7737 if ('file' != this._scheme) {
7738 this._host = base._host;
7739 this._port = base._port;
7740 }
7741 state = 'relative path';
7742 continue;
7743 }
7744 break;
7745
7746 case 'authority first slash':
7747 if ('/' == c) {
7748 state = 'authority second slash';
7749 } else {
7750 err("Expected '/', got: " + c);
7751 state = 'authority ignore slashes';
7752 continue;
7753 }
7754 break;
7755
7756 case 'authority second slash':
7757 state = 'authority ignore slashes';
7758 if ('/' != c) {
7759 err("Expected '/', got: " + c);
7760 continue;
7761 }
7762 break;
7763
7764 case 'authority ignore slashes':
7765 if ('/' != c && '\\' != c) {
7766 state = 'authority';
7767 continue;
7768 } else {
7769 err('Expected authority, got: ' + c);
7770 }
7771 break;
7772
7773 case 'authority':
7774 if ('@' == c) {
7775 if (seenAt) {
7776 err('@ already seen.');
7777 buffer += '%40';
7778 }
7779 seenAt = true;
7780 for (var i = 0; i < buffer.length; i++) {
7781 var cp = buffer[i];
7782 if ('\t' == cp || '\n' == cp || '\r' == cp) {
7783 err('Invalid whitespace in authority.');
7784 continue;
7785 }
7786 // XXX check URL code points
7787 if (':' == cp && null === this._password) {
7788 this._password = '';
7789 continue;
7790 }
7791 var tempC = percentEscape(cp);
7792 (null !== this._password) ? this._password += tempC : this._userna me += tempC;
7793 }
7794 buffer = '';
7795 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
7796 cursor -= buffer.length;
7797 buffer = '';
7798 state = 'host';
7799 continue;
7800 } else {
7801 buffer += c;
7802 }
7803 break;
7804
7805 case 'file host':
7806 if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
7807 if (buffer.length == 2 && ALPHA.test(buffer[0]) && (buffer[1] == ':' || buffer[1] == '|')) {
7808 state = 'relative path';
7809 } else if (buffer.length == 0) {
7810 state = 'relative path start';
7811 } else {
7812 this._host = IDNAToASCII.call(this, buffer);
7813 buffer = '';
7814 state = 'relative path start';
7815 }
7816 continue;
7817 } else if ('\t' == c || '\n' == c || '\r' == c) {
7818 err('Invalid whitespace in file host.');
7819 } else {
7820 buffer += c;
7821 }
7822 break;
7823
7824 case 'host':
7825 case 'hostname':
7826 if (':' == c && !seenBracket) {
7827 // XXX host parsing
7828 this._host = IDNAToASCII.call(this, buffer);
7829 buffer = '';
7830 state = 'port';
7831 if ('hostname' == stateOverride) {
7832 break loop;
7833 }
7834 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
7835 this._host = IDNAToASCII.call(this, buffer);
7836 buffer = '';
7837 state = 'relative path start';
7838 if (stateOverride) {
7839 break loop;
7840 }
7841 continue;
7842 } else if ('\t' != c && '\n' != c && '\r' != c) {
7843 if ('[' == c) {
7844 seenBracket = true;
7845 } else if (']' == c) {
7846 seenBracket = false;
7847 }
7848 buffer += c;
7849 } else {
7850 err('Invalid code point in host/hostname: ' + c);
7851 }
7852 break;
7853
7854 case 'port':
7855 if (/[0-9]/.test(c)) {
7856 buffer += c;
7857 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c | | stateOverride) {
7858 if ('' != buffer) {
7859 var temp = parseInt(buffer, 10);
7860 if (temp != relative[this._scheme]) {
7861 this._port = temp + '';
7862 }
7863 buffer = '';
7864 }
7865 if (stateOverride) {
7866 break loop;
7867 }
7868 state = 'relative path start';
7869 continue;
7870 } else if ('\t' == c || '\n' == c || '\r' == c) {
7871 err('Invalid code point in port: ' + c);
7872 } else {
7873 invalid.call(this);
7874 }
7875 break;
7876
7877 case 'relative path start':
7878 if ('\\' == c)
7879 err("'\\' not allowed in path.");
7880 state = 'relative path';
7881 if ('/' != c && '\\' != c) {
7882 continue;
7883 }
7884 break;
7885
7886 case 'relative path':
7887 if (EOF == c || '/' == c || '\\' == c || (!stateOverride && ('?' == c || '#' == c))) {
7888 if ('\\' == c) {
7889 err('\\ not allowed in relative path.');
7890 }
7891 var tmp;
7892 if (tmp = relativePathDotMapping[buffer.toLowerCase()]) {
7893 buffer = tmp;
7894 }
7895 if ('..' == buffer) {
7896 this._path.pop();
7897 if ('/' != c && '\\' != c) {
7898 this._path.push('');
7899 }
7900 } else if ('.' == buffer && '/' != c && '\\' != c) {
7901 this._path.push('');
7902 } else if ('.' != buffer) {
7903 if ('file' == this._scheme && this._path.length == 0 && buffer.len gth == 2 && ALPHA.test(buffer[0]) && buffer[1] == '|') {
7904 buffer = buffer[0] + ':';
7905 }
7906 this._path.push(buffer);
7907 }
7908 buffer = '';
7909 if ('?' == c) {
7910 this._query = '?';
7911 state = 'query';
7912 } else if ('#' == c) {
7913 this._fragment = '#';
7914 state = 'fragment';
7915 }
7916 } else if ('\t' != c && '\n' != c && '\r' != c) {
7917 buffer += percentEscape(c);
7918 }
7919 break;
7920
7921 case 'query':
7922 if (!stateOverride && '#' == c) {
7923 this._fragment = '#';
7924 state = 'fragment';
7925 } else if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
7926 this._query += percentEscapeQuery(c);
7927 }
7928 break;
7929
7930 case 'fragment':
7931 if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
7932 this._fragment += c;
7933 }
7934 break;
7935 }
7936
7937 cursor++;
7938 }
7939 }
7940
7941 function clear() {
7942 this._scheme = '';
7943 this._schemeData = '';
7944 this._username = '';
7945 this._password = null;
7946 this._host = '';
7947 this._port = '';
7948 this._path = [];
7949 this._query = '';
7950 this._fragment = '';
7951 this._isInvalid = false;
7952 this._isRelative = false;
7953 }
7954
7955 // Does not process domain names or IP addresses.
7956 // Does not handle encoding for the query parameter.
7957 function jURL(url, base /* , encoding */) {
7958 if (base !== undefined && !(base instanceof jURL))
7959 base = new jURL(String(base));
7960
7961 this._url = url;
7962 clear.call(this);
7963
7964 var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, '');
7965 // encoding = encoding || 'utf-8'
7966
7967 parse.call(this, input, null, base);
7968 }
7969
7970 jURL.prototype = {
7971 get href() {
7972 if (this._isInvalid)
7973 return this._url;
7974
7975 var authority = '';
7976 if ('' != this._username || null != this._password) {
7977 authority = this._username +
7978 (null != this._password ? ':' + this._password : '') + '@';
7979 }
7980
7981 return this.protocol +
7982 (this._isRelative ? '//' + authority + this.host : '') +
7983 this.pathname + this._query + this._fragment;
7984 },
7985 set href(href) {
7986 clear.call(this);
7987 parse.call(this, href);
7988 },
7989
7990 get protocol() {
7991 return this._scheme + ':';
7992 },
7993 set protocol(protocol) {
7994 if (this._isInvalid)
7995 return;
7996 parse.call(this, protocol + ':', 'scheme start');
7997 },
7998
7999 get host() {
8000 return this._isInvalid ? '' : this._port ?
8001 this._host + ':' + this._port : this._host;
8002 },
8003 set host(host) {
8004 if (this._isInvalid || !this._isRelative)
8005 return;
8006 parse.call(this, host, 'host');
8007 },
8008
8009 get hostname() {
8010 return this._host;
8011 },
8012 set hostname(hostname) {
8013 if (this._isInvalid || !this._isRelative)
8014 return;
8015 parse.call(this, hostname, 'hostname');
8016 },
8017
8018 get port() {
8019 return this._port;
8020 },
8021 set port(port) {
8022 if (this._isInvalid || !this._isRelative)
8023 return;
8024 parse.call(this, port, 'port');
8025 },
8026
8027 get pathname() {
8028 return this._isInvalid ? '' : this._isRelative ?
8029 '/' + this._path.join('/') : this._schemeData;
8030 },
8031 set pathname(pathname) {
8032 if (this._isInvalid || !this._isRelative)
8033 return;
8034 this._path = [];
8035 parse.call(this, pathname, 'relative path start');
8036 },
8037
8038 get search() {
8039 return this._isInvalid || !this._query || '?' == this._query ?
8040 '' : this._query;
8041 },
8042 set search(search) {
8043 if (this._isInvalid || !this._isRelative)
8044 return;
8045 this._query = '?';
8046 if ('?' == search[0])
8047 search = search.slice(1);
8048 parse.call(this, search, 'query');
8049 },
8050
8051 get hash() {
8052 return this._isInvalid || !this._fragment || '#' == this._fragment ?
8053 '' : this._fragment;
8054 },
8055 set hash(hash) {
8056 if (this._isInvalid)
8057 return;
8058 this._fragment = '#';
8059 if ('#' == hash[0])
8060 hash = hash.slice(1);
8061 parse.call(this, hash, 'fragment');
8062 },
8063
8064 get origin() {
8065 var host;
8066 if (this._isInvalid || !this._scheme) {
8067 return '';
8068 }
8069 // javascript: Gecko returns String(""), WebKit/Blink String("null")
8070 // Gecko throws error for "data://"
8071 // data: Gecko returns "", Blink returns "data://", WebKit returns "null"
8072 // Gecko returns String("") for file: mailto:
8073 // WebKit/Blink returns String("SCHEME://") for file: mailto:
8074 switch (this._scheme) {
8075 case 'data':
8076 case 'file':
8077 case 'javascript':
8078 case 'mailto':
8079 return 'null';
8080 }
8081 host = this.host;
8082 if (!host) {
8083 return '';
8084 }
8085 return this._scheme + '://' + host;
8086 }
8087 };
8088
8089 // Copy over the static methods
8090 var OriginalURL = scope.URL;
8091 if (OriginalURL) {
8092 jURL.createObjectURL = function(blob) {
8093 // IE extension allows a second optional options argument.
8094 // http://msdn.microsoft.com/en-us/library/ie/hh772302(v=vs.85).aspx
8095 return OriginalURL.createObjectURL.apply(OriginalURL, arguments);
8096 };
8097 jURL.revokeObjectURL = function(url) {
8098 OriginalURL.revokeObjectURL(url);
8099 };
8100 }
8101
8102 scope.URL = jURL;
8103
8104 })(this);
8105
8106 (function(scope) {
8107
8108 var iterations = 0;
8109 var callbacks = [];
8110 var twiddle = document.createTextNode('');
8111
8112 function endOfMicrotask(callback) {
8113 twiddle.textContent = iterations++;
8114 callbacks.push(callback);
8115 }
8116
8117 function atEndOfMicrotask() {
8118 while (callbacks.length) {
8119 callbacks.shift()();
8120 }
8121 }
8122
8123 new (window.MutationObserver || JsMutationObserver)(atEndOfMicrotask)
8124 .observe(twiddle, {characterData: true})
8125 ;
8126
8127 // exports
8128 scope.endOfMicrotask = endOfMicrotask;
8129 // bc
8130 Platform.endOfMicrotask = endOfMicrotask;
8131
8132 })(Polymer);
8133
8134
8135 (function(scope) {
8136
8137 /**
8138 * @class Polymer
8139 */
8140
8141 // imports
8142 var endOfMicrotask = scope.endOfMicrotask;
8143
8144 // logging
8145 var log = window.WebComponents ? WebComponents.flags.log : {};
8146
8147 // inject style sheet
8148 var style = document.createElement('style');
8149 style.textContent = 'template {display: none !important;} /* injected by platfor m.js */';
8150 var head = document.querySelector('head');
8151 head.insertBefore(style, head.firstChild);
8152
8153
8154 /**
8155 * Force any pending data changes to be observed before
8156 * the next task. Data changes are processed asynchronously but are guaranteed
8157 * to be processed, for example, before painting. This method should rarely be
8158 * needed. It does nothing when Object.observe is available;
8159 * when Object.observe is not available, Polymer automatically flushes data
8160 * changes approximately every 1/10 second.
8161 * Therefore, `flush` should only be used when a data mutation should be
8162 * observed sooner than this.
8163 *
8164 * @method flush
8165 */
8166 // flush (with logging)
8167 var flushing;
8168 function flush() {
8169 if (!flushing) {
8170 flushing = true;
8171 endOfMicrotask(function() {
8172 flushing = false;
8173 log.data && console.group('flush');
8174 Platform.performMicrotaskCheckpoint();
8175 log.data && console.groupEnd();
8176 });
8177 }
8178 };
8179
8180 // polling dirty checker
8181 // flush periodically if platform does not have object observe.
8182 if (!Observer.hasObjectObserve) {
8183 var FLUSH_POLL_INTERVAL = 125;
8184 window.addEventListener('WebComponentsReady', function() {
8185 flush();
8186 // watch document visiblity to toggle dirty-checking
8187 var visibilityHandler = function() {
8188 // only flush if the page is visibile
8189 if (document.visibilityState === 'hidden') {
8190 if (scope.flushPoll) {
8191 clearInterval(scope.flushPoll);
8192 }
8193 } else {
8194 scope.flushPoll = setInterval(flush, FLUSH_POLL_INTERVAL);
8195 }
8196 };
8197 if (typeof document.visibilityState === 'string') {
8198 document.addEventListener('visibilitychange', visibilityHandler);
8199 }
8200 visibilityHandler();
8201 });
8202 } else {
8203 // make flush a no-op when we have Object.observe
8204 flush = function() {};
8205 }
8206
8207 if (window.CustomElements && !CustomElements.useNative) {
8208 var originalImportNode = Document.prototype.importNode;
8209 Document.prototype.importNode = function(node, deep) {
8210 var imported = originalImportNode.call(this, node, deep);
8211 CustomElements.upgradeAll(imported);
8212 return imported;
8213 };
8214 }
8215
8216 // exports
8217 scope.flush = flush;
8218 // bc
8219 Platform.flush = flush;
8220
8221 })(window.Polymer);
8222
8223
8224 (function(scope) {
8225
8226 var urlResolver = {
8227 resolveDom: function(root, url) {
8228 url = url || baseUrl(root);
8229 this.resolveAttributes(root, url);
8230 this.resolveStyles(root, url);
8231 // handle template.content
8232 var templates = root.querySelectorAll('template');
8233 if (templates) {
8234 for (var i = 0, l = templates.length, t; (i < l) && (t = templates[i]); i+ +) {
8235 if (t.content) {
8236 this.resolveDom(t.content, url);
8237 }
8238 }
8239 }
8240 },
8241 resolveTemplate: function(template) {
8242 this.resolveDom(template.content, baseUrl(template));
8243 },
8244 resolveStyles: function(root, url) {
8245 var styles = root.querySelectorAll('style');
8246 if (styles) {
8247 for (var i = 0, l = styles.length, s; (i < l) && (s = styles[i]); i++) {
8248 this.resolveStyle(s, url);
8249 }
8250 }
8251 },
8252 resolveStyle: function(style, url) {
8253 url = url || baseUrl(style);
8254 style.textContent = this.resolveCssText(style.textContent, url);
8255 },
8256 resolveCssText: function(cssText, baseUrl, keepAbsolute) {
8257 cssText = replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_URL_REGEX P);
8258 return replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_IMPORT_REGEX P);
8259 },
8260 resolveAttributes: function(root, url) {
8261 if (root.hasAttributes && root.hasAttributes()) {
8262 this.resolveElementAttributes(root, url);
8263 }
8264 // search for attributes that host urls
8265 var nodes = root && root.querySelectorAll(URL_ATTRS_SELECTOR);
8266 if (nodes) {
8267 for (var i = 0, l = nodes.length, n; (i < l) && (n = nodes[i]); i++) {
8268 this.resolveElementAttributes(n, url);
8269 }
8270 }
8271 },
8272 resolveElementAttributes: function(node, url) {
8273 url = url || baseUrl(node);
8274 URL_ATTRS.forEach(function(v) {
8275 var attr = node.attributes[v];
8276 var value = attr && attr.value;
8277 var replacement;
8278 if (value && value.search(URL_TEMPLATE_SEARCH) < 0) {
8279 if (v === 'style') {
8280 replacement = replaceUrlsInCssText(value, url, false, CSS_URL_REGEXP);
8281 } else {
8282 replacement = resolveRelativeUrl(url, value);
8283 }
8284 attr.value = replacement;
8285 }
8286 });
8287 }
8288 };
8289
8290 var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g;
8291 var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g;
8292 var URL_ATTRS = ['href', 'src', 'action', 'style', 'url'];
8293 var URL_ATTRS_SELECTOR = '[' + URL_ATTRS.join('],[') + ']';
8294 var URL_TEMPLATE_SEARCH = '{{.*}}';
8295 var URL_HASH = '#';
8296
8297 function baseUrl(node) {
8298 var u = new URL(node.ownerDocument.baseURI);
8299 u.search = '';
8300 u.hash = '';
8301 return u;
8302 }
8303
8304 function replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, regexp) {
8305 return cssText.replace(regexp, function(m, pre, url, post) {
8306 var urlPath = url.replace(/["']/g, '');
8307 urlPath = resolveRelativeUrl(baseUrl, urlPath, keepAbsolute);
8308 return pre + '\'' + urlPath + '\'' + post;
8309 });
8310 }
8311
8312 function resolveRelativeUrl(baseUrl, url, keepAbsolute) {
8313 // do not resolve '/' absolute urls
8314 if (url && url[0] === '/') {
8315 return url;
8316 }
8317 var u = new URL(url, baseUrl);
8318 return keepAbsolute ? u.href : makeDocumentRelPath(u.href);
8319 }
8320
8321 function makeDocumentRelPath(url) {
8322 var root = baseUrl(document.documentElement);
8323 var u = new URL(url, root);
8324 if (u.host === root.host && u.port === root.port &&
8325 u.protocol === root.protocol) {
8326 return makeRelPath(root, u);
8327 } else {
8328 return url;
8329 }
8330 }
8331
8332 // make a relative path from source to target
8333 function makeRelPath(sourceUrl, targetUrl) {
8334 var source = sourceUrl.pathname;
8335 var target = targetUrl.pathname;
8336 var s = source.split('/');
8337 var t = target.split('/');
8338 while (s.length && s[0] === t[0]){
8339 s.shift();
8340 t.shift();
8341 }
8342 for (var i = 0, l = s.length - 1; i < l; i++) {
8343 t.unshift('..');
8344 }
8345 // empty '#' is discarded but we need to preserve it.
8346 var hash = (targetUrl.href.slice(-1) === URL_HASH) ? URL_HASH : targetUrl.hash ;
8347 return t.join('/') + targetUrl.search + hash;
8348 }
8349
8350 // exports
8351 scope.urlResolver = urlResolver;
8352
8353 })(Polymer);
8354
8355 (function(scope) {
8356 var endOfMicrotask = Polymer.endOfMicrotask;
8357
8358 // Generic url loader
8359 function Loader(regex) {
8360 this.cache = Object.create(null);
8361 this.map = Object.create(null);
8362 this.requests = 0;
8363 this.regex = regex;
8364 }
8365 Loader.prototype = {
8366
8367 // TODO(dfreedm): there may be a better factoring here
8368 // extract absolute urls from the text (full of relative urls)
8369 extractUrls: function(text, base) {
8370 var matches = [];
8371 var matched, u;
8372 while ((matched = this.regex.exec(text))) {
8373 u = new URL(matched[1], base);
8374 matches.push({matched: matched[0], url: u.href});
8375 }
8376 return matches;
8377 },
8378 // take a text blob, a root url, and a callback and load all the urls found within the text
8379 // returns a map of absolute url to text
8380 process: function(text, root, callback) {
8381 var matches = this.extractUrls(text, root);
8382
8383 // every call to process returns all the text this loader has ever receive d
8384 var done = callback.bind(null, this.map);
8385 this.fetch(matches, done);
8386 },
8387 // build a mapping of url -> text from matches
8388 fetch: function(matches, callback) {
8389 var inflight = matches.length;
8390
8391 // return early if there is no fetching to be done
8392 if (!inflight) {
8393 return callback();
8394 }
8395
8396 // wait for all subrequests to return
8397 var done = function() {
8398 if (--inflight === 0) {
8399 callback();
8400 }
8401 };
8402
8403 // start fetching all subrequests
8404 var m, req, url;
8405 for (var i = 0; i < inflight; i++) {
8406 m = matches[i];
8407 url = m.url;
8408 req = this.cache[url];
8409 // if this url has already been requested, skip requesting it again
8410 if (!req) {
8411 req = this.xhr(url);
8412 req.match = m;
8413 this.cache[url] = req;
8414 }
8415 // wait for the request to process its subrequests
8416 req.wait(done);
8417 }
8418 },
8419 handleXhr: function(request) {
8420 var match = request.match;
8421 var url = match.url;
8422
8423 // handle errors with an empty string
8424 var response = request.response || request.responseText || '';
8425 this.map[url] = response;
8426 this.fetch(this.extractUrls(response, url), request.resolve);
8427 },
8428 xhr: function(url) {
8429 this.requests++;
8430 var request = new XMLHttpRequest();
8431 request.open('GET', url, true);
8432 request.send();
8433 request.onerror = request.onload = this.handleXhr.bind(this, request);
8434
8435 // queue of tasks to run after XHR returns
8436 request.pending = [];
8437 request.resolve = function() {
8438 var pending = request.pending;
8439 for(var i = 0; i < pending.length; i++) {
8440 pending[i]();
8441 }
8442 request.pending = null;
8443 };
8444
8445 // if we have already resolved, pending is null, async call the callback
8446 request.wait = function(fn) {
8447 if (request.pending) {
8448 request.pending.push(fn);
8449 } else {
8450 endOfMicrotask(fn);
8451 }
8452 };
8453
8454 return request;
8455 }
8456 };
8457
8458 scope.Loader = Loader;
8459 })(Polymer);
8460
8461 (function(scope) {
8462
8463 var urlResolver = scope.urlResolver;
8464 var Loader = scope.Loader;
8465
8466 function StyleResolver() {
8467 this.loader = new Loader(this.regex);
8468 }
8469 StyleResolver.prototype = {
8470 regex: /@import\s+(?:url)?["'\(]*([^'"\)]*)['"\)]*;/g,
8471 // Recursively replace @imports with the text at that url
8472 resolve: function(text, url, callback) {
8473 var done = function(map) {
8474 callback(this.flatten(text, url, map));
8475 }.bind(this);
8476 this.loader.process(text, url, done);
8477 },
8478 // resolve the textContent of a style node
8479 resolveNode: function(style, url, callback) {
8480 var text = style.textContent;
8481 var done = function(text) {
8482 style.textContent = text;
8483 callback(style);
8484 };
8485 this.resolve(text, url, done);
8486 },
8487 // flatten all the @imports to text
8488 flatten: function(text, base, map) {
8489 var matches = this.loader.extractUrls(text, base);
8490 var match, url, intermediate;
8491 for (var i = 0; i < matches.length; i++) {
8492 match = matches[i];
8493 url = match.url;
8494 // resolve any css text to be relative to the importer, keep absolute url
8495 intermediate = urlResolver.resolveCssText(map[url], url, true);
8496 // flatten intermediate @imports
8497 intermediate = this.flatten(intermediate, base, map);
8498 text = text.replace(match.matched, intermediate);
8499 }
8500 return text;
8501 },
8502 loadStyles: function(styles, base, callback) {
8503 var loaded=0, l = styles.length;
8504 // called in the context of the style
8505 function loadedStyle(style) {
8506 loaded++;
8507 if (loaded === l && callback) {
8508 callback();
8509 }
8510 }
8511 for (var i=0, s; (i<l) && (s=styles[i]); i++) {
8512 this.resolveNode(s, base, loadedStyle);
8513 }
8514 }
8515 };
8516
8517 var styleResolver = new StyleResolver();
8518
8519 // exports
8520 scope.styleResolver = styleResolver;
8521
8522 })(Polymer);
8523
8524 (function(scope) {
8525
8526 // copy own properties from 'api' to 'prototype, with name hinting for 'super'
8527 function extend(prototype, api) {
8528 if (prototype && api) {
8529 // use only own properties of 'api'
8530 Object.getOwnPropertyNames(api).forEach(function(n) {
8531 // acquire property descriptor
8532 var pd = Object.getOwnPropertyDescriptor(api, n);
8533 if (pd) {
8534 // clone property via descriptor
8535 Object.defineProperty(prototype, n, pd);
8536 // cache name-of-method for 'super' engine
8537 if (typeof pd.value == 'function') {
8538 // hint the 'super' engine
8539 pd.value.nom = n;
8540 }
8541 }
8542 });
8543 }
8544 return prototype;
8545 }
8546
8547
8548 // mixin
8549
8550 // copy all properties from inProps (et al) to inObj
8551 function mixin(inObj/*, inProps, inMoreProps, ...*/) {
8552 var obj = inObj || {};
8553 for (var i = 1; i < arguments.length; i++) {
8554 var p = arguments[i];
8555 try {
8556 for (var n in p) {
8557 copyProperty(n, p, obj);
8558 }
8559 } catch(x) {
8560 }
8561 }
8562 return obj;
8563 }
8564
8565 // copy property inName from inSource object to inTarget object
8566 function copyProperty(inName, inSource, inTarget) {
8567 var pd = getPropertyDescriptor(inSource, inName);
8568 Object.defineProperty(inTarget, inName, pd);
8569 }
8570
8571 // get property descriptor for inName on inObject, even if
8572 // inName exists on some link in inObject's prototype chain
8573 function getPropertyDescriptor(inObject, inName) {
8574 if (inObject) {
8575 var pd = Object.getOwnPropertyDescriptor(inObject, inName);
8576 return pd || getPropertyDescriptor(Object.getPrototypeOf(inObject), inName );
8577 }
8578 }
8579
8580 // exports
8581
8582 scope.extend = extend;
8583 scope.mixin = mixin;
8584
8585 // for bc
8586 Platform.mixin = mixin;
8587
8588 })(Polymer);
8589
8590 (function(scope) {
8591
8592 // usage
8593
8594 // invoke cb.call(this) in 100ms, unless the job is re-registered,
8595 // which resets the timer
8596 //
8597 // this.myJob = this.job(this.myJob, cb, 100)
8598 //
8599 // returns a job handle which can be used to re-register a job
8600
8601 var Job = function(inContext) {
8602 this.context = inContext;
8603 this.boundComplete = this.complete.bind(this)
8604 };
8605 Job.prototype = {
8606 go: function(callback, wait) {
8607 this.callback = callback;
8608 var h;
8609 if (!wait) {
8610 h = requestAnimationFrame(this.boundComplete);
8611 this.handle = function() {
8612 cancelAnimationFrame(h);
8613 }
8614 } else {
8615 h = setTimeout(this.boundComplete, wait);
8616 this.handle = function() {
8617 clearTimeout(h);
8618 }
8619 }
8620 },
8621 stop: function() {
8622 if (this.handle) {
8623 this.handle();
8624 this.handle = null;
8625 }
8626 },
8627 complete: function() {
8628 if (this.handle) {
8629 this.stop();
8630 this.callback.call(this.context);
8631 }
8632 }
8633 };
8634
8635 function job(job, callback, wait) {
8636 if (job) {
8637 job.stop();
8638 } else {
8639 job = new Job(this);
8640 }
8641 job.go(callback, wait);
8642 return job;
8643 }
8644
8645 // exports
8646
8647 scope.job = job;
8648
8649 })(Polymer);
8650
8651 (function(scope) {
8652
8653 // dom polyfill, additions, and utility methods
8654
8655 var registry = {};
8656
8657 HTMLElement.register = function(tag, prototype) {
8658 registry[tag] = prototype;
8659 };
8660
8661 // get prototype mapped to node <tag>
8662 HTMLElement.getPrototypeForTag = function(tag) {
8663 var prototype = !tag ? HTMLElement.prototype : registry[tag];
8664 // TODO(sjmiles): creating <tag> is likely to have wasteful side-effects
8665 return prototype || Object.getPrototypeOf(document.createElement(tag));
8666 };
8667
8668 // we have to flag propagation stoppage for the event dispatcher
8669 var originalStopPropagation = Event.prototype.stopPropagation;
8670 Event.prototype.stopPropagation = function() {
8671 this.cancelBubble = true;
8672 originalStopPropagation.apply(this, arguments);
8673 };
8674
8675
8676 // polyfill DOMTokenList
8677 // * add/remove: allow these methods to take multiple classNames
8678 // * toggle: add a 2nd argument which forces the given state rather
8679 // than toggling.
8680
8681 var add = DOMTokenList.prototype.add;
8682 var remove = DOMTokenList.prototype.remove;
8683 DOMTokenList.prototype.add = function() {
8684 for (var i = 0; i < arguments.length; i++) {
8685 add.call(this, arguments[i]);
8686 }
8687 };
8688 DOMTokenList.prototype.remove = function() {
8689 for (var i = 0; i < arguments.length; i++) {
8690 remove.call(this, arguments[i]);
8691 }
8692 };
8693 DOMTokenList.prototype.toggle = function(name, bool) {
8694 if (arguments.length == 1) {
8695 bool = !this.contains(name);
8696 }
8697 bool ? this.add(name) : this.remove(name);
8698 };
8699 DOMTokenList.prototype.switch = function(oldName, newName) {
8700 oldName && this.remove(oldName);
8701 newName && this.add(newName);
8702 };
8703
8704 // add array() to NodeList, NamedNodeMap, HTMLCollection
8705
8706 var ArraySlice = function() {
8707 return Array.prototype.slice.call(this);
8708 };
8709
8710 var namedNodeMap = (window.NamedNodeMap || window.MozNamedAttrMap || {});
8711
8712 NodeList.prototype.array = ArraySlice;
8713 namedNodeMap.prototype.array = ArraySlice;
8714 HTMLCollection.prototype.array = ArraySlice;
8715
8716 // utility
8717
8718 function createDOM(inTagOrNode, inHTML, inAttrs) {
8719 var dom = typeof inTagOrNode == 'string' ?
8720 document.createElement(inTagOrNode) : inTagOrNode.cloneNode(true);
8721 dom.innerHTML = inHTML;
8722 if (inAttrs) {
8723 for (var n in inAttrs) {
8724 dom.setAttribute(n, inAttrs[n]);
8725 }
8726 }
8727 return dom;
8728 }
8729
8730 // exports
8731
8732 scope.createDOM = createDOM;
8733
8734 })(Polymer);
8735
8736 (function(scope) {
8737 // super
8738
8739 // `arrayOfArgs` is an optional array of args like one might pass
8740 // to `Function.apply`
8741
8742 // TODO(sjmiles):
8743 // $super must be installed on an instance or prototype chain
8744 // as `super`, and invoked via `this`, e.g.
8745 // `this.super();`
8746
8747 // will not work if function objects are not unique, for example,
8748 // when using mixins.
8749 // The memoization strategy assumes each function exists on only one
8750 // prototype chain i.e. we use the function object for memoizing)
8751 // perhaps we can bookkeep on the prototype itself instead
8752 function $super(arrayOfArgs) {
8753 // since we are thunking a method call, performance is important here:
8754 // memoize all lookups, once memoized the fast path calls no other
8755 // functions
8756 //
8757 // find the caller (cannot be `strict` because of 'caller')
8758 var caller = $super.caller;
8759 // memoized 'name of method'
8760 var nom = caller.nom;
8761 // memoized next implementation prototype
8762 var _super = caller._super;
8763 if (!_super) {
8764 if (!nom) {
8765 nom = caller.nom = nameInThis.call(this, caller);
8766 }
8767 if (!nom) {
8768 console.warn('called super() on a method not installed declaratively ( has no .nom property)');
8769 }
8770 // super prototype is either cached or we have to find it
8771 // by searching __proto__ (at the 'top')
8772 // invariant: because we cache _super on fn below, we never reach
8773 // here from inside a series of calls to super(), so it's ok to
8774 // start searching from the prototype of 'this' (at the 'top')
8775 // we must never memoize a null super for this reason
8776 _super = memoizeSuper(caller, nom, getPrototypeOf(this));
8777 }
8778 // our super function
8779 var fn = _super[nom];
8780 if (fn) {
8781 // memoize information so 'fn' can call 'super'
8782 if (!fn._super) {
8783 // must not memoize null, or we lose our invariant above
8784 memoizeSuper(fn, nom, _super);
8785 }
8786 // invoke the inherited method
8787 // if 'fn' is not function valued, this will throw
8788 return fn.apply(this, arrayOfArgs || []);
8789 }
8790 }
8791
8792 function nameInThis(value) {
8793 var p = this.__proto__;
8794 while (p && p !== HTMLElement.prototype) {
8795 // TODO(sjmiles): getOwnPropertyNames is absurdly expensive
8796 var n$ = Object.getOwnPropertyNames(p);
8797 for (var i=0, l=n$.length, n; i<l && (n=n$[i]); i++) {
8798 var d = Object.getOwnPropertyDescriptor(p, n);
8799 if (typeof d.value === 'function' && d.value === value) {
8800 return n;
8801 }
8802 }
8803 p = p.__proto__;
8804 }
8805 }
8806
8807 function memoizeSuper(method, name, proto) {
8808 // find and cache next prototype containing `name`
8809 // we need the prototype so we can do another lookup
8810 // from here
8811 var s = nextSuper(proto, name, method);
8812 if (s[name]) {
8813 // `s` is a prototype, the actual method is `s[name]`
8814 // tag super method with it's name for quicker lookups
8815 s[name].nom = name;
8816 }
8817 return method._super = s;
8818 }
8819
8820 function nextSuper(proto, name, caller) {
8821 // look for an inherited prototype that implements name
8822 while (proto) {
8823 if ((proto[name] !== caller) && proto[name]) {
8824 return proto;
8825 }
8826 proto = getPrototypeOf(proto);
8827 }
8828 // must not return null, or we lose our invariant above
8829 // in this case, a super() call was invoked where no superclass
8830 // method exists
8831 // TODO(sjmiles): thow an exception?
8832 return Object;
8833 }
8834
8835 // NOTE: In some platforms (IE10) the prototype chain is faked via
8836 // __proto__. Therefore, always get prototype via __proto__ instead of
8837 // the more standard Object.getPrototypeOf.
8838 function getPrototypeOf(prototype) {
8839 return prototype.__proto__;
8840 }
8841
8842 // utility function to precompute name tags for functions
8843 // in a (unchained) prototype
8844 function hintSuper(prototype) {
8845 // tag functions with their prototype name to optimize
8846 // super call invocations
8847 for (var n in prototype) {
8848 var pd = Object.getOwnPropertyDescriptor(prototype, n);
8849 if (pd && typeof pd.value === 'function') {
8850 pd.value.nom = n;
8851 }
8852 }
8853 }
8854
8855 // exports
8856
8857 scope.super = $super;
8858
8859 })(Polymer);
8860
8861 (function(scope) {
8862
8863 function noopHandler(value) {
8864 return value;
8865 }
8866
8867 // helper for deserializing properties of various types to strings
8868 var typeHandlers = {
8869 string: noopHandler,
8870 'undefined': noopHandler,
8871 date: function(value) {
8872 return new Date(Date.parse(value) || Date.now());
8873 },
8874 boolean: function(value) {
8875 if (value === '') {
8876 return true;
8877 }
8878 return value === 'false' ? false : !!value;
8879 },
8880 number: function(value) {
8881 var n = parseFloat(value);
8882 // hex values like "0xFFFF" parseFloat as 0
8883 if (n === 0) {
8884 n = parseInt(value);
8885 }
8886 return isNaN(n) ? value : n;
8887 // this code disabled because encoded values (like "0xFFFF")
8888 // do not round trip to their original format
8889 //return (String(floatVal) === value) ? floatVal : value;
8890 },
8891 object: function(value, currentValue) {
8892 if (currentValue === null) {
8893 return value;
8894 }
8895 try {
8896 // If the string is an object, we can parse is with the JSON library.
8897 // include convenience replace for single-quotes. If the author omits
8898 // quotes altogether, parse will fail.
8899 return JSON.parse(value.replace(/'/g, '"'));
8900 } catch(e) {
8901 // The object isn't valid JSON, return the raw value
8902 return value;
8903 }
8904 },
8905 // avoid deserialization of functions
8906 'function': function(value, currentValue) {
8907 return currentValue;
8908 }
8909 };
8910
8911 function deserializeValue(value, currentValue) {
8912 // attempt to infer type from default value
8913 var inferredType = typeof currentValue;
8914 // invent 'date' type value for Date
8915 if (currentValue instanceof Date) {
8916 inferredType = 'date';
8917 }
8918 // delegate deserialization via type string
8919 return typeHandlers[inferredType](value, currentValue);
8920 }
8921
8922 // exports
8923
8924 scope.deserializeValue = deserializeValue;
8925
8926 })(Polymer);
8927
8928 (function(scope) {
8929
8930 // imports
8931
8932 var extend = scope.extend;
8933
8934 // module
8935
8936 var api = {};
8937
8938 api.declaration = {};
8939 api.instance = {};
8940
8941 api.publish = function(apis, prototype) {
8942 for (var n in apis) {
8943 extend(prototype, apis[n]);
8944 }
8945 };
8946
8947 // exports
8948
8949 scope.api = api;
8950
8951 })(Polymer);
8952
8953 (function(scope) {
8954
8955 /**
8956 * @class polymer-base
8957 */
8958
8959 var utils = {
8960
8961 /**
8962 * Invokes a function asynchronously. The context of the callback
8963 * function is bound to 'this' automatically. Returns a handle which may
8964 * be passed to <a href="#cancelAsync">cancelAsync</a> to cancel the
8965 * asynchronous call.
8966 *
8967 * @method async
8968 * @param {Function|String} method
8969 * @param {any|Array} args
8970 * @param {number} timeout
8971 */
8972 async: function(method, args, timeout) {
8973 // when polyfilling Object.observe, ensure changes
8974 // propagate before executing the async method
8975 Polymer.flush();
8976 // second argument to `apply` must be an array
8977 args = (args && args.length) ? args : [args];
8978 // function to invoke
8979 var fn = function() {
8980 (this[method] || method).apply(this, args);
8981 }.bind(this);
8982 // execute `fn` sooner or later
8983 var handle = timeout ? setTimeout(fn, timeout) :
8984 requestAnimationFrame(fn);
8985 // NOTE: switch on inverting handle to determine which time is used.
8986 return timeout ? handle : ~handle;
8987 },
8988
8989 /**
8990 * Cancels a pending callback that was scheduled via
8991 * <a href="#async">async</a>.
8992 *
8993 * @method cancelAsync
8994 * @param {handle} handle Handle of the `async` to cancel.
8995 */
8996 cancelAsync: function(handle) {
8997 if (handle < 0) {
8998 cancelAnimationFrame(~handle);
8999 } else {
9000 clearTimeout(handle);
9001 }
9002 },
9003
9004 /**
9005 * Fire an event.
9006 *
9007 * @method fire
9008 * @returns {Object} event
9009 * @param {string} type An event name.
9010 * @param {any} detail
9011 * @param {Node} onNode Target node.
9012 * @param {Boolean} bubbles Set false to prevent bubbling, defaults to true
9013 * @param {Boolean} cancelable Set false to prevent cancellation, defaults to true
9014 */
9015 fire: function(type, detail, onNode, bubbles, cancelable) {
9016 var node = onNode || this;
9017 var detail = detail === null || detail === undefined ? {} : detail;
9018 var event = new CustomEvent(type, {
9019 bubbles: bubbles !== undefined ? bubbles : true,
9020 cancelable: cancelable !== undefined ? cancelable : true,
9021 detail: detail
9022 });
9023 node.dispatchEvent(event);
9024 return event;
9025 },
9026
9027 /**
9028 * Fire an event asynchronously.
9029 *
9030 * @method asyncFire
9031 * @param {string} type An event name.
9032 * @param detail
9033 * @param {Node} toNode Target node.
9034 */
9035 asyncFire: function(/*inType, inDetail*/) {
9036 this.async("fire", arguments);
9037 },
9038
9039 /**
9040 * Remove class from old, add class to anew, if they exist.
9041 *
9042 * @param classFollows
9043 * @param anew A node.
9044 * @param old A node
9045 * @param className
9046 */
9047 classFollows: function(anew, old, className) {
9048 if (old) {
9049 old.classList.remove(className);
9050 }
9051 if (anew) {
9052 anew.classList.add(className);
9053 }
9054 },
9055
9056 /**
9057 * Inject HTML which contains markup bound to this element into
9058 * a target element (replacing target element content).
9059 *
9060 * @param String html to inject
9061 * @param Element target element
9062 */
9063 injectBoundHTML: function(html, element) {
9064 var template = document.createElement('template');
9065 template.innerHTML = html;
9066 var fragment = this.instanceTemplate(template);
9067 if (element) {
9068 element.textContent = '';
9069 element.appendChild(fragment);
9070 }
9071 return fragment;
9072 }
9073 };
9074
9075 // no-operation function for handy stubs
9076 var nop = function() {};
9077
9078 // null-object for handy stubs
9079 var nob = {};
9080
9081 // deprecated
9082
9083 utils.asyncMethod = utils.async;
9084
9085 // exports
9086
9087 scope.api.instance.utils = utils;
9088 scope.nop = nop;
9089 scope.nob = nob;
9090
9091 })(Polymer);
9092
9093 (function(scope) {
9094
9095 // imports
9096
9097 var log = window.WebComponents ? WebComponents.flags.log : {};
9098 var EVENT_PREFIX = 'on-';
9099
9100 // instance events api
9101 var events = {
9102 // read-only
9103 EVENT_PREFIX: EVENT_PREFIX,
9104 // event listeners on host
9105 addHostListeners: function() {
9106 var events = this.eventDelegates;
9107 log.events && (Object.keys(events).length > 0) && console.log('[%s] addHos tListeners:', this.localName, events);
9108 // NOTE: host events look like bindings but really are not;
9109 // (1) we don't want the attribute to be set and (2) we want to support
9110 // multiple event listeners ('host' and 'instance') and Node.bind
9111 // by default supports 1 thing being bound.
9112 for (var type in events) {
9113 var methodName = events[type];
9114 PolymerGestures.addEventListener(this, type, this.element.getEventHandle r(this, this, methodName));
9115 }
9116 },
9117 // call 'method' or function method on 'obj' with 'args', if the method exis ts
9118 dispatchMethod: function(obj, method, args) {
9119 if (obj) {
9120 log.events && console.group('[%s] dispatch [%s]', obj.localName, method) ;
9121 var fn = typeof method === 'function' ? method : obj[method];
9122 if (fn) {
9123 fn[args ? 'apply' : 'call'](obj, args);
9124 }
9125 log.events && console.groupEnd();
9126 // NOTE: dirty check right after calling method to ensure
9127 // changes apply quickly; in a very complicated app using high
9128 // frequency events, this can be a perf concern; in this case,
9129 // imperative handlers can be used to avoid flushing.
9130 Polymer.flush();
9131 }
9132 }
9133 };
9134
9135 // exports
9136
9137 scope.api.instance.events = events;
9138
9139 /**
9140 * @class Polymer
9141 */
9142
9143 /**
9144 * Add a gesture aware event handler to the given `node`. Can be used
9145 * in place of `element.addEventListener` and ensures gestures will function
9146 * as expected on mobile platforms. Please note that Polymer's declarative
9147 * event handlers include this functionality by default.
9148 *
9149 * @method addEventListener
9150 * @param {Node} node node on which to listen
9151 * @param {String} eventType name of the event
9152 * @param {Function} handlerFn event handler function
9153 * @param {Boolean} capture set to true to invoke event capturing
9154 * @type Function
9155 */
9156 // alias PolymerGestures event listener logic
9157 scope.addEventListener = function(node, eventType, handlerFn, capture) {
9158 PolymerGestures.addEventListener(wrap(node), eventType, handlerFn, capture);
9159 };
9160
9161 /**
9162 * Remove a gesture aware event handler on the given `node`. To remove an
9163 * event listener, the exact same arguments are required that were passed
9164 * to `Polymer.addEventListener`.
9165 *
9166 * @method removeEventListener
9167 * @param {Node} node node on which to listen
9168 * @param {String} eventType name of the event
9169 * @param {Function} handlerFn event handler function
9170 * @param {Boolean} capture set to true to invoke event capturing
9171 * @type Function
9172 */
9173 scope.removeEventListener = function(node, eventType, handlerFn, capture) {
9174 PolymerGestures.removeEventListener(wrap(node), eventType, handlerFn, captur e);
9175 };
9176
9177 })(Polymer);
9178
9179 (function(scope) {
9180
9181 // instance api for attributes
9182
9183 var attributes = {
9184 // copy attributes defined in the element declaration to the instance
9185 // e.g. <polymer-element name="x-foo" tabIndex="0"> tabIndex is copied
9186 // to the element instance here.
9187 copyInstanceAttributes: function () {
9188 var a$ = this._instanceAttributes;
9189 for (var k in a$) {
9190 if (!this.hasAttribute(k)) {
9191 this.setAttribute(k, a$[k]);
9192 }
9193 }
9194 },
9195 // for each attribute on this, deserialize value to property as needed
9196 takeAttributes: function() {
9197 // if we have no publish lookup table, we have no attributes to take
9198 // TODO(sjmiles): ad hoc
9199 if (this._publishLC) {
9200 for (var i=0, a$=this.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++) {
9201 this.attributeToProperty(a.name, a.value);
9202 }
9203 }
9204 },
9205 // if attribute 'name' is mapped to a property, deserialize
9206 // 'value' into that property
9207 attributeToProperty: function(name, value) {
9208 // try to match this attribute to a property (attributes are
9209 // all lower-case, so this is case-insensitive search)
9210 var name = this.propertyForAttribute(name);
9211 if (name) {
9212 // filter out 'mustached' values, these are to be
9213 // replaced with bound-data and are not yet values
9214 // themselves
9215 if (value && value.search(scope.bindPattern) >= 0) {
9216 return;
9217 }
9218 // get original value
9219 var currentValue = this[name];
9220 // deserialize Boolean or Number values from attribute
9221 var value = this.deserializeValue(value, currentValue);
9222 // only act if the value has changed
9223 if (value !== currentValue) {
9224 // install new value (has side-effects)
9225 this[name] = value;
9226 }
9227 }
9228 },
9229 // return the published property matching name, or undefined
9230 propertyForAttribute: function(name) {
9231 var match = this._publishLC && this._publishLC[name];
9232 return match;
9233 },
9234 // convert representation of `stringValue` based on type of `currentValue`
9235 deserializeValue: function(stringValue, currentValue) {
9236 return scope.deserializeValue(stringValue, currentValue);
9237 },
9238 // convert to a string value based on the type of `inferredType`
9239 serializeValue: function(value, inferredType) {
9240 if (inferredType === 'boolean') {
9241 return value ? '' : undefined;
9242 } else if (inferredType !== 'object' && inferredType !== 'function'
9243 && value !== undefined) {
9244 return value;
9245 }
9246 },
9247 // serializes `name` property value and updates the corresponding attribute
9248 // note that reflection is opt-in.
9249 reflectPropertyToAttribute: function(name) {
9250 var inferredType = typeof this[name];
9251 // try to intelligently serialize property value
9252 var serializedValue = this.serializeValue(this[name], inferredType);
9253 // boolean properties must reflect as boolean attributes
9254 if (serializedValue !== undefined) {
9255 this.setAttribute(name, serializedValue);
9256 // TODO(sorvell): we should remove attr for all properties
9257 // that have undefined serialization; however, we will need to
9258 // refine the attr reflection system to achieve this; pica, for example,
9259 // relies on having inferredType object properties not removed as
9260 // attrs.
9261 } else if (inferredType === 'boolean') {
9262 this.removeAttribute(name);
9263 }
9264 }
9265 };
9266
9267 // exports
9268
9269 scope.api.instance.attributes = attributes;
9270
9271 })(Polymer);
9272
9273 (function(scope) {
9274
9275 /**
9276 * @class polymer-base
9277 */
9278
9279 // imports
9280
9281 var log = window.WebComponents ? WebComponents.flags.log : {};
9282
9283 // magic words
9284
9285 var OBSERVE_SUFFIX = 'Changed';
9286
9287 // element api
9288
9289 var empty = [];
9290
9291 var updateRecord = {
9292 object: undefined,
9293 type: 'update',
9294 name: undefined,
9295 oldValue: undefined
9296 };
9297
9298 var numberIsNaN = Number.isNaN || function(value) {
9299 return typeof value === 'number' && isNaN(value);
9300 };
9301
9302 function areSameValue(left, right) {
9303 if (left === right)
9304 return left !== 0 || 1 / left === 1 / right;
9305 if (numberIsNaN(left) && numberIsNaN(right))
9306 return true;
9307 return left !== left && right !== right;
9308 }
9309
9310 // capture A's value if B's value is null or undefined,
9311 // otherwise use B's value
9312 function resolveBindingValue(oldValue, value) {
9313 if (value === undefined && oldValue === null) {
9314 return value;
9315 }
9316 return (value === null || value === undefined) ? oldValue : value;
9317 }
9318
9319 var properties = {
9320
9321 // creates a CompoundObserver to observe property changes
9322 // NOTE, this is only done there are any properties in the `observe` object
9323 createPropertyObserver: function() {
9324 var n$ = this._observeNames;
9325 if (n$ && n$.length) {
9326 var o = this._propertyObserver = new CompoundObserver(true);
9327 this.registerObserver(o);
9328 // TODO(sorvell): may not be kosher to access the value here (this[n]);
9329 // previously we looked at the descriptor on the prototype
9330 // this doesn't work for inheritance and not for accessors without
9331 // a value property
9332 for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) {
9333 o.addPath(this, n);
9334 this.observeArrayValue(n, this[n], null);
9335 }
9336 }
9337 },
9338
9339 // start observing property changes
9340 openPropertyObserver: function() {
9341 if (this._propertyObserver) {
9342 this._propertyObserver.open(this.notifyPropertyChanges, this);
9343 }
9344 },
9345
9346 // handler for property changes; routes changes to observing methods
9347 // note: array valued properties are observed for array splices
9348 notifyPropertyChanges: function(newValues, oldValues, paths) {
9349 var name, method, called = {};
9350 for (var i in oldValues) {
9351 // note: paths is of form [object, path, object, path]
9352 name = paths[2 * i + 1];
9353 method = this.observe[name];
9354 if (method) {
9355 var ov = oldValues[i], nv = newValues[i];
9356 // observes the value if it is an array
9357 this.observeArrayValue(name, nv, ov);
9358 if (!called[method]) {
9359 // only invoke change method if one of ov or nv is not (undefined | null)
9360 if ((ov !== undefined && ov !== null) || (nv !== undefined && nv !== null)) {
9361 called[method] = true;
9362 // TODO(sorvell): call method with the set of values it's expectin g;
9363 // e.g. 'foo bar': 'invalidate' expects the new and old values for
9364 // foo and bar. Currently we give only one of these and then
9365 // deliver all the arguments.
9366 this.invokeMethod(method, [ov, nv, arguments]);
9367 }
9368 }
9369 }
9370 }
9371 },
9372
9373 // call method iff it exists.
9374 invokeMethod: function(method, args) {
9375 var fn = this[method] || method;
9376 if (typeof fn === 'function') {
9377 fn.apply(this, args);
9378 }
9379 },
9380
9381 /**
9382 * Force any pending property changes to synchronously deliver to
9383 * handlers specified in the `observe` object.
9384 * Note, normally changes are processed at microtask time.
9385 *
9386 * @method deliverChanges
9387 */
9388 deliverChanges: function() {
9389 if (this._propertyObserver) {
9390 this._propertyObserver.deliver();
9391 }
9392 },
9393
9394 observeArrayValue: function(name, value, old) {
9395 // we only care if there are registered side-effects
9396 var callbackName = this.observe[name];
9397 if (callbackName) {
9398 // if we are observing the previous value, stop
9399 if (Array.isArray(old)) {
9400 log.observe && console.log('[%s] observeArrayValue: unregister observe r [%s]', this.localName, name);
9401 this.closeNamedObserver(name + '__array');
9402 }
9403 // if the new value is an array, being observing it
9404 if (Array.isArray(value)) {
9405 log.observe && console.log('[%s] observeArrayValue: register observer [%s]', this.localName, name, value);
9406 var observer = new ArrayObserver(value);
9407 observer.open(function(splices) {
9408 this.invokeMethod(callbackName, [splices]);
9409 }, this);
9410 this.registerNamedObserver(name + '__array', observer);
9411 }
9412 }
9413 },
9414
9415 emitPropertyChangeRecord: function(name, value, oldValue) {
9416 var object = this;
9417 if (areSameValue(value, oldValue)) {
9418 return;
9419 }
9420 // invoke property change side effects
9421 this._propertyChanged(name, value, oldValue);
9422 // emit change record
9423 if (!Observer.hasObjectObserve) {
9424 return;
9425 }
9426 var notifier = this._objectNotifier;
9427 if (!notifier) {
9428 notifier = this._objectNotifier = Object.getNotifier(this);
9429 }
9430 updateRecord.object = this;
9431 updateRecord.name = name;
9432 updateRecord.oldValue = oldValue;
9433 notifier.notify(updateRecord);
9434 },
9435
9436 _propertyChanged: function(name, value, oldValue) {
9437 if (this.reflect[name]) {
9438 this.reflectPropertyToAttribute(name);
9439 }
9440 },
9441
9442 // creates a property binding (called via bind) to a published property.
9443 bindProperty: function(property, observable, oneTime) {
9444 if (oneTime) {
9445 this[property] = observable;
9446 return;
9447 }
9448 var computed = this.element.prototype.computed;
9449 // Binding an "out-only" value to a computed property. Note that
9450 // since this observer isn't opened, it doesn't need to be closed on
9451 // cleanup.
9452 if (computed && computed[property]) {
9453 var privateComputedBoundValue = property + 'ComputedBoundObservable_';
9454 this[privateComputedBoundValue] = observable;
9455 return;
9456 }
9457 return this.bindToAccessor(property, observable, resolveBindingValue);
9458 },
9459
9460 // NOTE property `name` must be published. This makes it an accessor.
9461 bindToAccessor: function(name, observable, resolveFn) {
9462 var privateName = name + '_';
9463 var privateObservable = name + 'Observable_';
9464 // Present for properties which are computed and published and have a
9465 // bound value.
9466 var privateComputedBoundValue = name + 'ComputedBoundObservable_';
9467 this[privateObservable] = observable;
9468 var oldValue = this[privateName];
9469 // observable callback
9470 var self = this;
9471 function updateValue(value, oldValue) {
9472 self[privateName] = value;
9473 var setObserveable = self[privateComputedBoundValue];
9474 if (setObserveable && typeof setObserveable.setValue == 'function') {
9475 setObserveable.setValue(value);
9476 }
9477 self.emitPropertyChangeRecord(name, value, oldValue);
9478 }
9479 // resolve initial value
9480 var value = observable.open(updateValue);
9481 if (resolveFn && !areSameValue(oldValue, value)) {
9482 var resolvedValue = resolveFn(oldValue, value);
9483 if (!areSameValue(value, resolvedValue)) {
9484 value = resolvedValue;
9485 if (observable.setValue) {
9486 observable.setValue(value);
9487 }
9488 }
9489 }
9490 updateValue(value, oldValue);
9491 // register and return observable
9492 var observer = {
9493 close: function() {
9494 observable.close();
9495 self[privateObservable] = undefined;
9496 self[privateComputedBoundValue] = undefined;
9497 }
9498 };
9499 this.registerObserver(observer);
9500 return observer;
9501 },
9502
9503 createComputedProperties: function() {
9504 if (!this._computedNames) {
9505 return;
9506 }
9507 for (var i = 0; i < this._computedNames.length; i++) {
9508 var name = this._computedNames[i];
9509 var expressionText = this.computed[name];
9510 try {
9511 var expression = PolymerExpressions.getExpression(expressionText);
9512 var observable = expression.getBinding(this, this.element.syntax);
9513 this.bindToAccessor(name, observable);
9514 } catch (ex) {
9515 console.error('Failed to create computed property', ex);
9516 }
9517 }
9518 },
9519
9520 // property bookkeeping
9521 registerObserver: function(observer) {
9522 if (!this._observers) {
9523 this._observers = [observer];
9524 return;
9525 }
9526 this._observers.push(observer);
9527 },
9528
9529 closeObservers: function() {
9530 if (!this._observers) {
9531 return;
9532 }
9533 // observer array items are arrays of observers.
9534 var observers = this._observers;
9535 for (var i = 0; i < observers.length; i++) {
9536 var observer = observers[i];
9537 if (observer && typeof observer.close == 'function') {
9538 observer.close();
9539 }
9540 }
9541 this._observers = [];
9542 },
9543
9544 // bookkeeping observers for memory management
9545 registerNamedObserver: function(name, observer) {
9546 var o$ = this._namedObservers || (this._namedObservers = {});
9547 o$[name] = observer;
9548 },
9549
9550 closeNamedObserver: function(name) {
9551 var o$ = this._namedObservers;
9552 if (o$ && o$[name]) {
9553 o$[name].close();
9554 o$[name] = null;
9555 return true;
9556 }
9557 },
9558
9559 closeNamedObservers: function() {
9560 if (this._namedObservers) {
9561 for (var i in this._namedObservers) {
9562 this.closeNamedObserver(i);
9563 }
9564 this._namedObservers = {};
9565 }
9566 }
9567
9568 };
9569
9570 // logging
9571 var LOG_OBSERVE = '[%s] watching [%s]';
9572 var LOG_OBSERVED = '[%s#%s] watch: [%s] now [%s] was [%s]';
9573 var LOG_CHANGED = '[%s#%s] propertyChanged: [%s] now [%s] was [%s]';
9574
9575 // exports
9576
9577 scope.api.instance.properties = properties;
9578
9579 })(Polymer);
9580
9581 (function(scope) {
9582
9583 /**
9584 * @class polymer-base
9585 */
9586
9587 // imports
9588
9589 var log = window.WebComponents ? WebComponents.flags.log : {};
9590
9591 // element api supporting mdv
9592 var mdv = {
9593
9594 /**
9595 * Creates dom cloned from the given template, instantiating bindings
9596 * with this element as the template model and `PolymerExpressions` as the
9597 * binding delegate.
9598 *
9599 * @method instanceTemplate
9600 * @param {Template} template source template from which to create dom.
9601 */
9602 instanceTemplate: function(template) {
9603 // ensure template is decorated (lets' things like <tr template ...> work)
9604 HTMLTemplateElement.decorate(template);
9605 // ensure a default bindingDelegate
9606 var syntax = this.syntax || (!template.bindingDelegate &&
9607 this.element.syntax);
9608 var dom = template.createInstance(this, syntax);
9609 var observers = dom.bindings_;
9610 for (var i = 0; i < observers.length; i++) {
9611 this.registerObserver(observers[i]);
9612 }
9613 return dom;
9614 },
9615
9616 // Called by TemplateBinding/NodeBind to setup a binding to the given
9617 // property. It's overridden here to support property bindings
9618 // in addition to attribute bindings that are supported by default.
9619 bind: function(name, observable, oneTime) {
9620 var property = this.propertyForAttribute(name);
9621 if (!property) {
9622 // TODO(sjmiles): this mixin method must use the special form
9623 // of `super` installed by `mixinMethod` in declaration/prototype.js
9624 return this.mixinSuper(arguments);
9625 } else {
9626 // use n-way Polymer binding
9627 var observer = this.bindProperty(property, observable, oneTime);
9628 // NOTE: reflecting binding information is typically required only for
9629 // tooling. It has a performance cost so it's opt-in in Node.bind.
9630 if (Platform.enableBindingsReflection && observer) {
9631 observer.path = observable.path_;
9632 this._recordBinding(property, observer);
9633 }
9634 if (this.reflect[property]) {
9635 this.reflectPropertyToAttribute(property);
9636 }
9637 return observer;
9638 }
9639 },
9640
9641 _recordBinding: function(name, observer) {
9642 this.bindings_ = this.bindings_ || {};
9643 this.bindings_[name] = observer;
9644 },
9645
9646 // Called by TemplateBinding when all bindings on an element have been
9647 // executed. This signals that all element inputs have been gathered
9648 // and it's safe to ready the element, create shadow-root and start
9649 // data-observation.
9650 bindFinished: function() {
9651 this.makeElementReady();
9652 },
9653
9654 // called at detached time to signal that an element's bindings should be
9655 // cleaned up. This is done asynchronously so that users have the chance
9656 // to call `cancelUnbindAll` to prevent unbinding.
9657 asyncUnbindAll: function() {
9658 if (!this._unbound) {
9659 log.unbind && console.log('[%s] asyncUnbindAll', this.localName);
9660 this._unbindAllJob = this.job(this._unbindAllJob, this.unbindAll, 0);
9661 }
9662 },
9663
9664 /**
9665 * This method should rarely be used and only if
9666 * <a href="#cancelUnbindAll">`cancelUnbindAll`</a> has been called to
9667 * prevent element unbinding. In this case, the element's bindings will
9668 * not be automatically cleaned up and it cannot be garbage collected
9669 * by the system. If memory pressure is a concern or a
9670 * large amount of elements need to be managed in this way, `unbindAll`
9671 * can be called to deactivate the element's bindings and allow its
9672 * memory to be reclaimed.
9673 *
9674 * @method unbindAll
9675 */
9676 unbindAll: function() {
9677 if (!this._unbound) {
9678 this.closeObservers();
9679 this.closeNamedObservers();
9680 this._unbound = true;
9681 }
9682 },
9683
9684 /**
9685 * Call in `detached` to prevent the element from unbinding when it is
9686 * detached from the dom. The element is unbound as a cleanup step that
9687 * allows its memory to be reclaimed.
9688 * If `cancelUnbindAll` is used, consider calling
9689 * <a href="#unbindAll">`unbindAll`</a> when the element is no longer
9690 * needed. This will allow its memory to be reclaimed.
9691 *
9692 * @method cancelUnbindAll
9693 */
9694 cancelUnbindAll: function() {
9695 if (this._unbound) {
9696 log.unbind && console.warn('[%s] already unbound, cannot cancel unbindAl l', this.localName);
9697 return;
9698 }
9699 log.unbind && console.log('[%s] cancelUnbindAll', this.localName);
9700 if (this._unbindAllJob) {
9701 this._unbindAllJob = this._unbindAllJob.stop();
9702 }
9703 }
9704
9705 };
9706
9707 function unbindNodeTree(node) {
9708 forNodeTree(node, _nodeUnbindAll);
9709 }
9710
9711 function _nodeUnbindAll(node) {
9712 node.unbindAll();
9713 }
9714
9715 function forNodeTree(node, callback) {
9716 if (node) {
9717 callback(node);
9718 for (var child = node.firstChild; child; child = child.nextSibling) {
9719 forNodeTree(child, callback);
9720 }
9721 }
9722 }
9723
9724 var mustachePattern = /\{\{([^{}]*)}}/;
9725
9726 // exports
9727
9728 scope.bindPattern = mustachePattern;
9729 scope.api.instance.mdv = mdv;
9730
9731 })(Polymer);
9732
9733 (function(scope) {
9734
9735 /**
9736 * Common prototype for all Polymer Elements.
9737 *
9738 * @class polymer-base
9739 * @homepage polymer.github.io
9740 */
9741 var base = {
9742 /**
9743 * Tags this object as the canonical Base prototype.
9744 *
9745 * @property PolymerBase
9746 * @type boolean
9747 * @default true
9748 */
9749 PolymerBase: true,
9750
9751 /**
9752 * Debounce signals.
9753 *
9754 * Call `job` to defer a named signal, and all subsequent matching signals,
9755 * until a wait time has elapsed with no new signal.
9756 *
9757 * debouncedClickAction: function(e) {
9758 * // processClick only when it's been 100ms since the last click
9759 * this.job('click', function() {
9760 * this.processClick;
9761 * }, 100);
9762 * }
9763 *
9764 * @method job
9765 * @param String {String} job A string identifier for the job to debounce.
9766 * @param Function {Function} callback A function that is called (with `this ` context) when the wait time elapses.
9767 * @param Number {Number} wait Time in milliseconds (ms) after the last sign al that must elapse before invoking `callback`
9768 * @type Handle
9769 */
9770 job: function(job, callback, wait) {
9771 if (typeof job === 'string') {
9772 var n = '___' + job;
9773 this[n] = Polymer.job.call(this, this[n], callback, wait);
9774 } else {
9775 // TODO(sjmiles): suggest we deprecate this call signature
9776 return Polymer.job.call(this, job, callback, wait);
9777 }
9778 },
9779
9780 /**
9781 * Invoke a superclass method.
9782 *
9783 * Use `super()` to invoke the most recently overridden call to the
9784 * currently executing function.
9785 *
9786 * To pass arguments through, use the literal `arguments` as the parameter
9787 * to `super()`.
9788 *
9789 * nextPageAction: function(e) {
9790 * // invoke the superclass version of `nextPageAction`
9791 * this.super(arguments);
9792 * }
9793 *
9794 * To pass custom arguments, arrange them in an array.
9795 *
9796 * appendSerialNo: function(value, serial) {
9797 * // prefix the superclass serial number with our lot # before
9798 * // invoking the superlcass
9799 * return this.super([value, this.lotNo + serial])
9800 * }
9801 *
9802 * @method super
9803 * @type Any
9804 * @param {args) An array of arguments to use when calling the superclass me thod, or null.
9805 */
9806 super: Polymer.super,
9807
9808 /**
9809 * Lifecycle method called when the element is instantiated.
9810 *
9811 * Override `created` to perform custom create-time tasks. No need to call
9812 * super-class `created` unless you are extending another Polymer element.
9813 * Created is called before the element creates `shadowRoot` or prepares
9814 * data-observation.
9815 *
9816 * @method created
9817 * @type void
9818 */
9819 created: function() {
9820 },
9821
9822 /**
9823 * Lifecycle method called when the element has populated it's `shadowRoot`,
9824 * prepared data-observation, and made itself ready for API interaction.
9825 *
9826 * @method ready
9827 * @type void
9828 */
9829 ready: function() {
9830 },
9831
9832 /**
9833 * Low-level lifecycle method called as part of standard Custom Elements
9834 * operation. Polymer implements this method to provide basic default
9835 * functionality. For custom create-time tasks, implement `created`
9836 * instead, which is called immediately after `createdCallback`.
9837 *
9838 * @method createdCallback
9839 */
9840 createdCallback: function() {
9841 if (this.templateInstance && this.templateInstance.model) {
9842 console.warn('Attributes on ' + this.localName + ' were data bound ' +
9843 'prior to Polymer upgrading the element. This may result in ' +
9844 'incorrect binding types.');
9845 }
9846 this.created();
9847 this.prepareElement();
9848 if (!this.ownerDocument.isStagingDocument) {
9849 this.makeElementReady();
9850 }
9851 },
9852
9853 // system entry point, do not override
9854 prepareElement: function() {
9855 if (this._elementPrepared) {
9856 console.warn('Element already prepared', this.localName);
9857 return;
9858 }
9859 this._elementPrepared = true;
9860 // storage for shadowRoots info
9861 this.shadowRoots = {};
9862 // install property observers
9863 this.createPropertyObserver();
9864 this.openPropertyObserver();
9865 // install boilerplate attributes
9866 this.copyInstanceAttributes();
9867 // process input attributes
9868 this.takeAttributes();
9869 // add event listeners
9870 this.addHostListeners();
9871 },
9872
9873 // system entry point, do not override
9874 makeElementReady: function() {
9875 if (this._readied) {
9876 return;
9877 }
9878 this._readied = true;
9879 this.createComputedProperties();
9880 this.parseDeclarations(this.__proto__);
9881 // NOTE: Support use of the `unresolved` attribute to help polyfill
9882 // custom elements' `:unresolved` feature.
9883 this.removeAttribute('unresolved');
9884 // user entry point
9885 this.ready();
9886 },
9887
9888 /**
9889 * Low-level lifecycle method called as part of standard Custom Elements
9890 * operation. Polymer implements this method to provide basic default
9891 * functionality. For custom tasks in your element, implement `attributeChan ged`
9892 * instead, which is called immediately after `attributeChangedCallback`.
9893 *
9894 * @method attributeChangedCallback
9895 */
9896 attributeChangedCallback: function(name, oldValue) {
9897 // TODO(sjmiles): adhoc filter
9898 if (name !== 'class' && name !== 'style') {
9899 this.attributeToProperty(name, this.getAttribute(name));
9900 }
9901 if (this.attributeChanged) {
9902 this.attributeChanged.apply(this, arguments);
9903 }
9904 },
9905
9906 /**
9907 * Low-level lifecycle method called as part of standard Custom Elements
9908 * operation. Polymer implements this method to provide basic default
9909 * functionality. For custom create-time tasks, implement `attached`
9910 * instead, which is called immediately after `attachedCallback`.
9911 *
9912 * @method attachedCallback
9913 */
9914 attachedCallback: function() {
9915 // when the element is attached, prevent it from unbinding.
9916 this.cancelUnbindAll();
9917 // invoke user action
9918 if (this.attached) {
9919 this.attached();
9920 }
9921 if (!this.hasBeenAttached) {
9922 this.hasBeenAttached = true;
9923 if (this.domReady) {
9924 this.async('domReady');
9925 }
9926 }
9927 },
9928
9929 /**
9930 * Implement to access custom elements in dom descendants, ancestors,
9931 * or siblings. Because custom elements upgrade in document order,
9932 * elements accessed in `ready` or `attached` may not be upgraded. When
9933 * `domReady` is called, all registered custom elements are guaranteed
9934 * to have been upgraded.
9935 *
9936 * @method domReady
9937 */
9938
9939 /**
9940 * Low-level lifecycle method called as part of standard Custom Elements
9941 * operation. Polymer implements this method to provide basic default
9942 * functionality. For custom create-time tasks, implement `detached`
9943 * instead, which is called immediately after `detachedCallback`.
9944 *
9945 * @method detachedCallback
9946 */
9947 detachedCallback: function() {
9948 if (!this.preventDispose) {
9949 this.asyncUnbindAll();
9950 }
9951 // invoke user action
9952 if (this.detached) {
9953 this.detached();
9954 }
9955 // TODO(sorvell): bc
9956 if (this.leftView) {
9957 this.leftView();
9958 }
9959 },
9960
9961 /**
9962 * Walks the prototype-chain of this element and allows specific
9963 * classes a chance to process static declarations.
9964 *
9965 * In particular, each polymer-element has it's own `template`.
9966 * `parseDeclarations` is used to accumulate all element `template`s
9967 * from an inheritance chain.
9968 *
9969 * `parseDeclaration` static methods implemented in the chain are called
9970 * recursively, oldest first, with the `<polymer-element>` associated
9971 * with the current prototype passed as an argument.
9972 *
9973 * An element may override this method to customize shadow-root generation.
9974 *
9975 * @method parseDeclarations
9976 */
9977 parseDeclarations: function(p) {
9978 if (p && p.element) {
9979 this.parseDeclarations(p.__proto__);
9980 p.parseDeclaration.call(this, p.element);
9981 }
9982 },
9983
9984 /**
9985 * Perform init-time actions based on static information in the
9986 * `<polymer-element>` instance argument.
9987 *
9988 * For example, the standard implementation locates the template associated
9989 * with the given `<polymer-element>` and stamps it into a shadow-root to
9990 * implement shadow inheritance.
9991 *
9992 * An element may override this method for custom behavior.
9993 *
9994 * @method parseDeclaration
9995 */
9996 parseDeclaration: function(elementElement) {
9997 var template = this.fetchTemplate(elementElement);
9998 if (template) {
9999 var root = this.shadowFromTemplate(template);
10000 this.shadowRoots[elementElement.name] = root;
10001 }
10002 },
10003
10004 /**
10005 * Given a `<polymer-element>`, find an associated template (if any) to be
10006 * used for shadow-root generation.
10007 *
10008 * An element may override this method for custom behavior.
10009 *
10010 * @method fetchTemplate
10011 */
10012 fetchTemplate: function(elementElement) {
10013 return elementElement.querySelector('template');
10014 },
10015
10016 /**
10017 * Create a shadow-root in this host and stamp `template` as it's
10018 * content.
10019 *
10020 * An element may override this method for custom behavior.
10021 *
10022 * @method shadowFromTemplate
10023 */
10024 shadowFromTemplate: function(template) {
10025 if (template) {
10026 // make a shadow root
10027 var root = this.createShadowRoot();
10028 // stamp template
10029 // which includes parsing and applying MDV bindings before being
10030 // inserted (to avoid {{}} in attribute values)
10031 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404.
10032 var dom = this.instanceTemplate(template);
10033 // append to shadow dom
10034 root.appendChild(dom);
10035 // perform post-construction initialization tasks on shadow root
10036 this.shadowRootReady(root, template);
10037 // return the created shadow root
10038 return root;
10039 }
10040 },
10041
10042 // utility function that stamps a <template> into light-dom
10043 lightFromTemplate: function(template, refNode) {
10044 if (template) {
10045 // TODO(sorvell): mark this element as an eventController so that
10046 // event listeners on bound nodes inside it will be called on it.
10047 // Note, the expectation here is that events on all descendants
10048 // should be handled by this element.
10049 this.eventController = this;
10050 // stamp template
10051 // which includes parsing and applying MDV bindings before being
10052 // inserted (to avoid {{}} in attribute values)
10053 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404.
10054 var dom = this.instanceTemplate(template);
10055 // append to shadow dom
10056 if (refNode) {
10057 this.insertBefore(dom, refNode);
10058 } else {
10059 this.appendChild(dom);
10060 }
10061 // perform post-construction initialization tasks on ahem, light root
10062 this.shadowRootReady(this);
10063 // return the created shadow root
10064 return dom;
10065 }
10066 },
10067
10068 shadowRootReady: function(root) {
10069 // locate nodes with id and store references to them in this.$ hash
10070 this.marshalNodeReferences(root);
10071 },
10072
10073 // locate nodes with id and store references to them in this.$ hash
10074 marshalNodeReferences: function(root) {
10075 // establish $ instance variable
10076 var $ = this.$ = this.$ || {};
10077 // populate $ from nodes with ID from the LOCAL tree
10078 if (root) {
10079 var n$ = root.querySelectorAll("[id]");
10080 for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) {
10081 $[n.id] = n;
10082 };
10083 }
10084 },
10085
10086 /**
10087 * Register a one-time callback when a child-list or sub-tree mutation
10088 * occurs on node.
10089 *
10090 * For persistent callbacks, call onMutation from your listener.
10091 *
10092 * @method onMutation
10093 * @param Node {Node} node Node to watch for mutations.
10094 * @param Function {Function} listener Function to call on mutation. The fun ction is invoked as `listener.call(this, observer, mutations);` where `observer` is the MutationObserver that triggered the notification, and `mutations` is the native mutation list.
10095 */
10096 onMutation: function(node, listener) {
10097 var observer = new MutationObserver(function(mutations) {
10098 listener.call(this, observer, mutations);
10099 observer.disconnect();
10100 }.bind(this));
10101 observer.observe(node, {childList: true, subtree: true});
10102 }
10103 };
10104
10105 /**
10106 * @class Polymer
10107 */
10108
10109 /**
10110 * Returns true if the object includes <a href="#polymer-base">polymer-base</a > in it's prototype chain.
10111 *
10112 * @method isBase
10113 * @param Object {Object} object Object to test.
10114 * @type Boolean
10115 */
10116 function isBase(object) {
10117 return object.hasOwnProperty('PolymerBase')
10118 }
10119
10120 // name a base constructor for dev tools
10121
10122 /**
10123 * The Polymer base-class constructor.
10124 *
10125 * @property Base
10126 * @type Function
10127 */
10128 function PolymerBase() {};
10129 PolymerBase.prototype = base;
10130 base.constructor = PolymerBase;
10131
10132 // exports
10133
10134 scope.Base = PolymerBase;
10135 scope.isBase = isBase;
10136 scope.api.instance.base = base;
10137
10138 })(Polymer);
10139
10140 (function(scope) {
10141
10142 // imports
10143
10144 var log = window.WebComponents ? WebComponents.flags.log : {};
10145 var hasShadowDOMPolyfill = window.ShadowDOMPolyfill;
10146
10147 // magic words
10148
10149 var STYLE_SCOPE_ATTRIBUTE = 'element';
10150 var STYLE_CONTROLLER_SCOPE = 'controller';
10151
10152 var styles = {
10153 STYLE_SCOPE_ATTRIBUTE: STYLE_SCOPE_ATTRIBUTE,
10154 /**
10155 * Installs external stylesheets and <style> elements with the attribute
10156 * polymer-scope='controller' into the scope of element. This is intended
10157 * to be a called during custom element construction.
10158 */
10159 installControllerStyles: function() {
10160 // apply controller styles, but only if they are not yet applied
10161 var scope = this.findStyleScope();
10162 if (scope && !this.scopeHasNamedStyle(scope, this.localName)) {
10163 // allow inherited controller styles
10164 var proto = getPrototypeOf(this), cssText = '';
10165 while (proto && proto.element) {
10166 cssText += proto.element.cssTextForScope(STYLE_CONTROLLER_SCOPE);
10167 proto = getPrototypeOf(proto);
10168 }
10169 if (cssText) {
10170 this.installScopeCssText(cssText, scope);
10171 }
10172 }
10173 },
10174 installScopeStyle: function(style, name, scope) {
10175 var scope = scope || this.findStyleScope(), name = name || '';
10176 if (scope && !this.scopeHasNamedStyle(scope, this.localName + name)) {
10177 var cssText = '';
10178 if (style instanceof Array) {
10179 for (var i=0, l=style.length, s; (i<l) && (s=style[i]); i++) {
10180 cssText += s.textContent + '\n\n';
10181 }
10182 } else {
10183 cssText = style.textContent;
10184 }
10185 this.installScopeCssText(cssText, scope, name);
10186 }
10187 },
10188 installScopeCssText: function(cssText, scope, name) {
10189 scope = scope || this.findStyleScope();
10190 name = name || '';
10191 if (!scope) {
10192 return;
10193 }
10194 if (hasShadowDOMPolyfill) {
10195 cssText = shimCssText(cssText, scope.host);
10196 }
10197 var style = this.element.cssTextToScopeStyle(cssText,
10198 STYLE_CONTROLLER_SCOPE);
10199 Polymer.applyStyleToScope(style, scope);
10200 // cache that this style has been applied
10201 this.styleCacheForScope(scope)[this.localName + name] = true;
10202 },
10203 findStyleScope: function(node) {
10204 // find the shadow root that contains this element
10205 var n = node || this;
10206 while (n.parentNode) {
10207 n = n.parentNode;
10208 }
10209 return n;
10210 },
10211 scopeHasNamedStyle: function(scope, name) {
10212 var cache = this.styleCacheForScope(scope);
10213 return cache[name];
10214 },
10215 styleCacheForScope: function(scope) {
10216 if (hasShadowDOMPolyfill) {
10217 var scopeName = scope.host ? scope.host.localName : scope.localName;
10218 return polyfillScopeStyleCache[scopeName] || (polyfillScopeStyleCache[sc opeName] = {});
10219 } else {
10220 return scope._scopeStyles = (scope._scopeStyles || {});
10221 }
10222 }
10223 };
10224
10225 var polyfillScopeStyleCache = {};
10226
10227 // NOTE: use raw prototype traversal so that we ensure correct traversal
10228 // on platforms where the protoype chain is simulated via __proto__ (IE10)
10229 function getPrototypeOf(prototype) {
10230 return prototype.__proto__;
10231 }
10232
10233 function shimCssText(cssText, host) {
10234 var name = '', is = false;
10235 if (host) {
10236 name = host.localName;
10237 is = host.hasAttribute('is');
10238 }
10239 var selector = WebComponents.ShadowCSS.makeScopeSelector(name, is);
10240 return WebComponents.ShadowCSS.shimCssText(cssText, selector);
10241 }
10242
10243 // exports
10244
10245 scope.api.instance.styles = styles;
10246
10247 })(Polymer);
10248
10249 (function(scope) {
10250
10251 // imports
10252
10253 var extend = scope.extend;
10254 var api = scope.api;
10255
10256 // imperative implementation: Polymer()
10257
10258 // specify an 'own' prototype for tag `name`
10259 function element(name, prototype) {
10260 if (typeof name !== 'string') {
10261 var script = prototype || document._currentScript;
10262 prototype = name;
10263 name = script && script.parentNode && script.parentNode.getAttribute ?
10264 script.parentNode.getAttribute('name') : '';
10265 if (!name) {
10266 throw 'Element name could not be inferred.';
10267 }
10268 }
10269 if (getRegisteredPrototype(name)) {
10270 throw 'Already registered (Polymer) prototype for element ' + name;
10271 }
10272 // cache the prototype
10273 registerPrototype(name, prototype);
10274 // notify the registrar waiting for 'name', if any
10275 notifyPrototype(name);
10276 }
10277
10278 // async prototype source
10279
10280 function waitingForPrototype(name, client) {
10281 waitPrototype[name] = client;
10282 }
10283
10284 var waitPrototype = {};
10285
10286 function notifyPrototype(name) {
10287 if (waitPrototype[name]) {
10288 waitPrototype[name].registerWhenReady();
10289 delete waitPrototype[name];
10290 }
10291 }
10292
10293 // utility and bookkeeping
10294
10295 // maps tag names to prototypes, as registered with
10296 // Polymer. Prototypes associated with a tag name
10297 // using document.registerElement are available from
10298 // HTMLElement.getPrototypeForTag().
10299 // If an element was fully registered by Polymer, then
10300 // Polymer.getRegisteredPrototype(name) ===
10301 // HTMLElement.getPrototypeForTag(name)
10302
10303 var prototypesByName = {};
10304
10305 function registerPrototype(name, prototype) {
10306 return prototypesByName[name] = prototype || {};
10307 }
10308
10309 function getRegisteredPrototype(name) {
10310 return prototypesByName[name];
10311 }
10312
10313 function instanceOfType(element, type) {
10314 if (typeof type !== 'string') {
10315 return false;
10316 }
10317 var proto = HTMLElement.getPrototypeForTag(type);
10318 var ctor = proto && proto.constructor;
10319 if (!ctor) {
10320 return false;
10321 }
10322 if (CustomElements.instanceof) {
10323 return CustomElements.instanceof(element, ctor);
10324 }
10325 return element instanceof ctor;
10326 }
10327
10328 // exports
10329
10330 scope.getRegisteredPrototype = getRegisteredPrototype;
10331 scope.waitingForPrototype = waitingForPrototype;
10332 scope.instanceOfType = instanceOfType;
10333
10334 // namespace shenanigans so we can expose our scope on the registration
10335 // function
10336
10337 // make window.Polymer reference `element()`
10338
10339 window.Polymer = element;
10340
10341 // TODO(sjmiles): find a way to do this that is less terrible
10342 // copy window.Polymer properties onto `element()`
10343
10344 extend(Polymer, scope);
10345
10346 // Under the HTMLImports polyfill, scripts in the main document
10347 // do not block on imports; we want to allow calls to Polymer in the main
10348 // document. WebComponents collects those calls until we can process them, whi ch
10349 // we do here.
10350
10351 if (WebComponents.consumeDeclarations) {
10352 WebComponents.consumeDeclarations(function(declarations) {
10353 if (declarations) {
10354 for (var i=0, l=declarations.length, d; (i<l) && (d=declarations[i]); i+ +) {
10355 element.apply(null, d);
10356 }
10357 }
10358 });
10359 }
10360
10361 })(Polymer);
10362
10363 (function(scope) {
10364
10365 /**
10366 * @class polymer-base
10367 */
10368
10369 /**
10370 * Resolve a url path to be relative to a `base` url. If unspecified, `base`
10371 * defaults to the element's ownerDocument url. Can be used to resolve
10372 * paths from element's in templates loaded in HTMLImports to be relative
10373 * to the document containing the element. Polymer automatically does this for
10374 * url attributes in element templates; however, if a url, for
10375 * example, contains a binding, then `resolvePath` can be used to ensure it is
10376 * relative to the element document. For example, in an element's template,
10377 *
10378 * <a href="{{resolvePath(path)}}">Resolved</a>
10379 *
10380 * @method resolvePath
10381 * @param {String} url Url path to resolve.
10382 * @param {String} base Optional base url against which to resolve, defaults
10383 * to the element's ownerDocument url.
10384 * returns {String} resolved url.
10385 */
10386
10387 var path = {
10388 resolveElementPaths: function(node) {
10389 Polymer.urlResolver.resolveDom(node);
10390 },
10391 addResolvePathApi: function() {
10392 // let assetpath attribute modify the resolve path
10393 var assetPath = this.getAttribute('assetpath') || '';
10394 var root = new URL(assetPath, this.ownerDocument.baseURI);
10395 this.prototype.resolvePath = function(urlPath, base) {
10396 var u = new URL(urlPath, base || root);
10397 return u.href;
10398 };
10399 }
10400 };
10401
10402 // exports
10403 scope.api.declaration.path = path;
10404
10405 })(Polymer);
10406
10407 (function(scope) {
10408
10409 // imports
10410
10411 var log = window.WebComponents ? WebComponents.flags.log : {};
10412 var api = scope.api.instance.styles;
10413 var STYLE_SCOPE_ATTRIBUTE = api.STYLE_SCOPE_ATTRIBUTE;
10414
10415 var hasShadowDOMPolyfill = window.ShadowDOMPolyfill;
10416
10417 // magic words
10418
10419 var STYLE_SELECTOR = 'style';
10420 var STYLE_LOADABLE_MATCH = '@import';
10421 var SHEET_SELECTOR = 'link[rel=stylesheet]';
10422 var STYLE_GLOBAL_SCOPE = 'global';
10423 var SCOPE_ATTR = 'polymer-scope';
10424
10425 var styles = {
10426 // returns true if resources are loading
10427 loadStyles: function(callback) {
10428 var template = this.fetchTemplate();
10429 var content = template && this.templateContent();
10430 if (content) {
10431 this.convertSheetsToStyles(content);
10432 var styles = this.findLoadableStyles(content);
10433 if (styles.length) {
10434 var templateUrl = template.ownerDocument.baseURI;
10435 return Polymer.styleResolver.loadStyles(styles, templateUrl, callback) ;
10436 }
10437 }
10438 if (callback) {
10439 callback();
10440 }
10441 },
10442 convertSheetsToStyles: function(root) {
10443 var s$ = root.querySelectorAll(SHEET_SELECTOR);
10444 for (var i=0, l=s$.length, s, c; (i<l) && (s=s$[i]); i++) {
10445 c = createStyleElement(importRuleForSheet(s, this.ownerDocument.baseURI) ,
10446 this.ownerDocument);
10447 this.copySheetAttributes(c, s);
10448 s.parentNode.replaceChild(c, s);
10449 }
10450 },
10451 copySheetAttributes: function(style, link) {
10452 for (var i=0, a$=link.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++) {
10453 if (a.name !== 'rel' && a.name !== 'href') {
10454 style.setAttribute(a.name, a.value);
10455 }
10456 }
10457 },
10458 findLoadableStyles: function(root) {
10459 var loadables = [];
10460 if (root) {
10461 var s$ = root.querySelectorAll(STYLE_SELECTOR);
10462 for (var i=0, l=s$.length, s; (i<l) && (s=s$[i]); i++) {
10463 if (s.textContent.match(STYLE_LOADABLE_MATCH)) {
10464 loadables.push(s);
10465 }
10466 }
10467 }
10468 return loadables;
10469 },
10470 /**
10471 * Install external stylesheets loaded in <polymer-element> elements into th e
10472 * element's template.
10473 * @param elementElement The <element> element to style.
10474 */
10475 installSheets: function() {
10476 this.cacheSheets();
10477 this.cacheStyles();
10478 this.installLocalSheets();
10479 this.installGlobalStyles();
10480 },
10481 /**
10482 * Remove all sheets from element and store for later use.
10483 */
10484 cacheSheets: function() {
10485 this.sheets = this.findNodes(SHEET_SELECTOR);
10486 this.sheets.forEach(function(s) {
10487 if (s.parentNode) {
10488 s.parentNode.removeChild(s);
10489 }
10490 });
10491 },
10492 cacheStyles: function() {
10493 this.styles = this.findNodes(STYLE_SELECTOR + '[' + SCOPE_ATTR + ']');
10494 this.styles.forEach(function(s) {
10495 if (s.parentNode) {
10496 s.parentNode.removeChild(s);
10497 }
10498 });
10499 },
10500 /**
10501 * Takes external stylesheets loaded in an <element> element and moves
10502 * their content into a <style> element inside the <element>'s template.
10503 * The sheet is then removed from the <element>. This is done only so
10504 * that if the element is loaded in the main document, the sheet does
10505 * not become active.
10506 * Note, ignores sheets with the attribute 'polymer-scope'.
10507 * @param elementElement The <element> element to style.
10508 */
10509 installLocalSheets: function () {
10510 var sheets = this.sheets.filter(function(s) {
10511 return !s.hasAttribute(SCOPE_ATTR);
10512 });
10513 var content = this.templateContent();
10514 if (content) {
10515 var cssText = '';
10516 sheets.forEach(function(sheet) {
10517 cssText += cssTextFromSheet(sheet) + '\n';
10518 });
10519 if (cssText) {
10520 var style = createStyleElement(cssText, this.ownerDocument);
10521 content.insertBefore(style, content.firstChild);
10522 }
10523 }
10524 },
10525 findNodes: function(selector, matcher) {
10526 var nodes = this.querySelectorAll(selector).array();
10527 var content = this.templateContent();
10528 if (content) {
10529 var templateNodes = content.querySelectorAll(selector).array();
10530 nodes = nodes.concat(templateNodes);
10531 }
10532 return matcher ? nodes.filter(matcher) : nodes;
10533 },
10534 /**
10535 * Promotes external stylesheets and <style> elements with the attribute
10536 * polymer-scope='global' into global scope.
10537 * This is particularly useful for defining @keyframe rules which
10538 * currently do not function in scoped or shadow style elements.
10539 * (See wkb.ug/72462)
10540 * @param elementElement The <element> element to style.
10541 */
10542 // TODO(sorvell): remove when wkb.ug/72462 is addressed.
10543 installGlobalStyles: function() {
10544 var style = this.styleForScope(STYLE_GLOBAL_SCOPE);
10545 applyStyleToScope(style, document.head);
10546 },
10547 cssTextForScope: function(scopeDescriptor) {
10548 var cssText = '';
10549 // handle stylesheets
10550 var selector = '[' + SCOPE_ATTR + '=' + scopeDescriptor + ']';
10551 var matcher = function(s) {
10552 return matchesSelector(s, selector);
10553 };
10554 var sheets = this.sheets.filter(matcher);
10555 sheets.forEach(function(sheet) {
10556 cssText += cssTextFromSheet(sheet) + '\n\n';
10557 });
10558 // handle cached style elements
10559 var styles = this.styles.filter(matcher);
10560 styles.forEach(function(style) {
10561 cssText += style.textContent + '\n\n';
10562 });
10563 return cssText;
10564 },
10565 styleForScope: function(scopeDescriptor) {
10566 var cssText = this.cssTextForScope(scopeDescriptor);
10567 return this.cssTextToScopeStyle(cssText, scopeDescriptor);
10568 },
10569 cssTextToScopeStyle: function(cssText, scopeDescriptor) {
10570 if (cssText) {
10571 var style = createStyleElement(cssText);
10572 style.setAttribute(STYLE_SCOPE_ATTRIBUTE, this.getAttribute('name') +
10573 '-' + scopeDescriptor);
10574 return style;
10575 }
10576 }
10577 };
10578
10579 function importRuleForSheet(sheet, baseUrl) {
10580 var href = new URL(sheet.getAttribute('href'), baseUrl).href;
10581 return '@import \'' + href + '\';';
10582 }
10583
10584 function applyStyleToScope(style, scope) {
10585 if (style) {
10586 if (scope === document) {
10587 scope = document.head;
10588 }
10589 if (hasShadowDOMPolyfill) {
10590 scope = document.head;
10591 }
10592 // TODO(sorvell): necessary for IE
10593 // see https://connect.microsoft.com/IE/feedback/details/790212/
10594 // cloning-a-style-element-and-adding-to-document-produces
10595 // -unexpected-result#details
10596 // var clone = style.cloneNode(true);
10597 var clone = createStyleElement(style.textContent);
10598 var attr = style.getAttribute(STYLE_SCOPE_ATTRIBUTE);
10599 if (attr) {
10600 clone.setAttribute(STYLE_SCOPE_ATTRIBUTE, attr);
10601 }
10602 // TODO(sorvell): probably too brittle; try to figure out
10603 // where to put the element.
10604 var refNode = scope.firstElementChild;
10605 if (scope === document.head) {
10606 var selector = 'style[' + STYLE_SCOPE_ATTRIBUTE + ']';
10607 var s$ = document.head.querySelectorAll(selector);
10608 if (s$.length) {
10609 refNode = s$[s$.length-1].nextElementSibling;
10610 }
10611 }
10612 scope.insertBefore(clone, refNode);
10613 }
10614 }
10615
10616 function createStyleElement(cssText, scope) {
10617 scope = scope || document;
10618 scope = scope.createElement ? scope : scope.ownerDocument;
10619 var style = scope.createElement('style');
10620 style.textContent = cssText;
10621 return style;
10622 }
10623
10624 function cssTextFromSheet(sheet) {
10625 return (sheet && sheet.__resource) || '';
10626 }
10627
10628 function matchesSelector(node, inSelector) {
10629 if (matches) {
10630 return matches.call(node, inSelector);
10631 }
10632 }
10633 var p = HTMLElement.prototype;
10634 var matches = p.matches || p.matchesSelector || p.webkitMatchesSelector
10635 || p.mozMatchesSelector;
10636
10637 // exports
10638
10639 scope.api.declaration.styles = styles;
10640 scope.applyStyleToScope = applyStyleToScope;
10641
10642 })(Polymer);
10643
10644 (function(scope) {
10645
10646 // imports
10647
10648 var log = window.WebComponents ? WebComponents.flags.log : {};
10649 var api = scope.api.instance.events;
10650 var EVENT_PREFIX = api.EVENT_PREFIX;
10651
10652 var mixedCaseEventTypes = {};
10653 [
10654 'webkitAnimationStart',
10655 'webkitAnimationEnd',
10656 'webkitTransitionEnd',
10657 'DOMFocusOut',
10658 'DOMFocusIn',
10659 'DOMMouseScroll'
10660 ].forEach(function(e) {
10661 mixedCaseEventTypes[e.toLowerCase()] = e;
10662 });
10663
10664 // polymer-element declarative api: events feature
10665 var events = {
10666 parseHostEvents: function() {
10667 // our delegates map
10668 var delegates = this.prototype.eventDelegates;
10669 // extract data from attributes into delegates
10670 this.addAttributeDelegates(delegates);
10671 },
10672 addAttributeDelegates: function(delegates) {
10673 // for each attribute
10674 for (var i=0, a; a=this.attributes[i]; i++) {
10675 // does it have magic marker identifying it as an event delegate?
10676 if (this.hasEventPrefix(a.name)) {
10677 // if so, add the info to delegates
10678 delegates[this.removeEventPrefix(a.name)] = a.value.replace('{{', '')
10679 .replace('}}', '').trim();
10680 }
10681 }
10682 },
10683 // starts with 'on-'
10684 hasEventPrefix: function (n) {
10685 return n && (n[0] === 'o') && (n[1] === 'n') && (n[2] === '-');
10686 },
10687 removeEventPrefix: function(n) {
10688 return n.slice(prefixLength);
10689 },
10690 findController: function(node) {
10691 while (node.parentNode) {
10692 if (node.eventController) {
10693 return node.eventController;
10694 }
10695 node = node.parentNode;
10696 }
10697 return node.host;
10698 },
10699 getEventHandler: function(controller, target, method) {
10700 var events = this;
10701 return function(e) {
10702 if (!controller || !controller.PolymerBase) {
10703 controller = events.findController(target);
10704 }
10705
10706 var args = [e, e.detail, e.currentTarget];
10707 controller.dispatchMethod(controller, method, args);
10708 };
10709 },
10710 prepareEventBinding: function(pathString, name, node) {
10711 if (!this.hasEventPrefix(name))
10712 return;
10713
10714 var eventType = this.removeEventPrefix(name);
10715 eventType = mixedCaseEventTypes[eventType] || eventType;
10716
10717 var events = this;
10718
10719 return function(model, node, oneTime) {
10720 var handler = events.getEventHandler(undefined, node, pathString);
10721 PolymerGestures.addEventListener(node, eventType, handler);
10722
10723 if (oneTime)
10724 return;
10725
10726 // TODO(rafaelw): This is really pointless work. Aside from the cost
10727 // of these allocations, NodeBind is going to setAttribute back to its
10728 // current value. Fixing this would mean changing the TemplateBinding
10729 // binding delegate API.
10730 function bindingValue() {
10731 return '{{ ' + pathString + ' }}';
10732 }
10733
10734 return {
10735 open: bindingValue,
10736 discardChanges: bindingValue,
10737 close: function() {
10738 PolymerGestures.removeEventListener(node, eventType, handler);
10739 }
10740 };
10741 };
10742 }
10743 };
10744
10745 var prefixLength = EVENT_PREFIX.length;
10746
10747 // exports
10748 scope.api.declaration.events = events;
10749
10750 })(Polymer);
10751
10752 (function(scope) {
10753
10754 // element api
10755
10756 var observationBlacklist = ['attribute'];
10757
10758 var properties = {
10759 inferObservers: function(prototype) {
10760 // called before prototype.observe is chained to inherited object
10761 var observe = prototype.observe, property;
10762 for (var n in prototype) {
10763 if (n.slice(-7) === 'Changed') {
10764 property = n.slice(0, -7);
10765 if (this.canObserveProperty(property)) {
10766 if (!observe) {
10767 observe = (prototype.observe = {});
10768 }
10769 observe[property] = observe[property] || n;
10770 }
10771 }
10772 }
10773 },
10774 canObserveProperty: function(property) {
10775 return (observationBlacklist.indexOf(property) < 0);
10776 },
10777 explodeObservers: function(prototype) {
10778 // called before prototype.observe is chained to inherited object
10779 var o = prototype.observe;
10780 if (o) {
10781 var exploded = {};
10782 for (var n in o) {
10783 var names = n.split(' ');
10784 for (var i=0, ni; ni=names[i]; i++) {
10785 exploded[ni] = o[n];
10786 }
10787 }
10788 prototype.observe = exploded;
10789 }
10790 },
10791 optimizePropertyMaps: function(prototype) {
10792 if (prototype.observe) {
10793 // construct name list
10794 var a = prototype._observeNames = [];
10795 for (var n in prototype.observe) {
10796 var names = n.split(' ');
10797 for (var i=0, ni; ni=names[i]; i++) {
10798 a.push(ni);
10799 }
10800 }
10801 }
10802 if (prototype.publish) {
10803 // construct name list
10804 var a = prototype._publishNames = [];
10805 for (var n in prototype.publish) {
10806 a.push(n);
10807 }
10808 }
10809 if (prototype.computed) {
10810 // construct name list
10811 var a = prototype._computedNames = [];
10812 for (var n in prototype.computed) {
10813 a.push(n);
10814 }
10815 }
10816 },
10817 publishProperties: function(prototype, base) {
10818 // if we have any properties to publish
10819 var publish = prototype.publish;
10820 if (publish) {
10821 // transcribe `publish` entries onto own prototype
10822 this.requireProperties(publish, prototype, base);
10823 // warn and remove accessor names that are broken on some browsers
10824 this.filterInvalidAccessorNames(publish);
10825 // construct map of lower-cased property names
10826 prototype._publishLC = this.lowerCaseMap(publish);
10827 }
10828 var computed = prototype.computed;
10829 if (computed) {
10830 // warn and remove accessor names that are broken on some browsers
10831 this.filterInvalidAccessorNames(computed);
10832 }
10833 },
10834 // Publishing/computing a property where the name might conflict with a
10835 // browser property is not currently supported to help users of Polymer
10836 // avoid browser bugs:
10837 //
10838 // https://code.google.com/p/chromium/issues/detail?id=43394
10839 // https://bugs.webkit.org/show_bug.cgi?id=49739
10840 //
10841 // We can lift this restriction when those bugs are fixed.
10842 filterInvalidAccessorNames: function(propertyNames) {
10843 for (var name in propertyNames) {
10844 // Check if the name is in our blacklist.
10845 if (this.propertyNameBlacklist[name]) {
10846 console.warn('Cannot define property "' + name + '" for element "' +
10847 this.name + '" because it has the same name as an HTMLElement ' +
10848 'property, and not all browsers support overriding that. ' +
10849 'Consider giving it a different name.');
10850 // Remove the invalid accessor from the list.
10851 delete propertyNames[name];
10852 }
10853 }
10854 },
10855 //
10856 // `name: value` entries in the `publish` object may need to generate
10857 // matching properties on the prototype.
10858 //
10859 // Values that are objects may have a `reflect` property, which
10860 // signals that the value describes property control metadata.
10861 // In metadata objects, the prototype default value (if any)
10862 // is encoded in the `value` property.
10863 //
10864 // publish: {
10865 // foo: 5,
10866 // bar: {value: true, reflect: true},
10867 // zot: {}
10868 // }
10869 //
10870 // `reflect` metadata property controls whether changes to the property
10871 // are reflected back to the attribute (default false).
10872 //
10873 // A value is stored on the prototype unless it's === `undefined`,
10874 // in which case the base chain is checked for a value.
10875 // If the basal value is also undefined, `null` is stored on the prototype.
10876 //
10877 // The reflection data is stored on another prototype object, `reflect`
10878 // which also can be specified directly.
10879 //
10880 // reflect: {
10881 // foo: true
10882 // }
10883 //
10884 requireProperties: function(propertyInfos, prototype, base) {
10885 // per-prototype storage for reflected properties
10886 prototype.reflect = prototype.reflect || {};
10887 // ensure a prototype value for each property
10888 // and update the property's reflect to attribute status
10889 for (var n in propertyInfos) {
10890 var value = propertyInfos[n];
10891 // value has metadata if it has a `reflect` property
10892 if (value && value.reflect !== undefined) {
10893 prototype.reflect[n] = Boolean(value.reflect);
10894 value = value.value;
10895 }
10896 // only set a value if one is specified
10897 if (value !== undefined) {
10898 prototype[n] = value;
10899 }
10900 }
10901 },
10902 lowerCaseMap: function(properties) {
10903 var map = {};
10904 for (var n in properties) {
10905 map[n.toLowerCase()] = n;
10906 }
10907 return map;
10908 },
10909 createPropertyAccessor: function(name, ignoreWrites) {
10910 var proto = this.prototype;
10911
10912 var privateName = name + '_';
10913 var privateObservable = name + 'Observable_';
10914 proto[privateName] = proto[name];
10915
10916 Object.defineProperty(proto, name, {
10917 get: function() {
10918 var observable = this[privateObservable];
10919 if (observable)
10920 observable.deliver();
10921
10922 return this[privateName];
10923 },
10924 set: function(value) {
10925 if (ignoreWrites) {
10926 return this[privateName];
10927 }
10928
10929 var observable = this[privateObservable];
10930 if (observable) {
10931 observable.setValue(value);
10932 return;
10933 }
10934
10935 var oldValue = this[privateName];
10936 this[privateName] = value;
10937 this.emitPropertyChangeRecord(name, value, oldValue);
10938
10939 return value;
10940 },
10941 configurable: true
10942 });
10943 },
10944 createPropertyAccessors: function(prototype) {
10945 var n$ = prototype._computedNames;
10946 if (n$ && n$.length) {
10947 for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) {
10948 this.createPropertyAccessor(n, true);
10949 }
10950 }
10951 var n$ = prototype._publishNames;
10952 if (n$ && n$.length) {
10953 for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) {
10954 // If the property is computed and published, the accessor is created
10955 // above.
10956 if (!prototype.computed || !prototype.computed[n]) {
10957 this.createPropertyAccessor(n);
10958 }
10959 }
10960 }
10961 },
10962 // This list contains some property names that people commonly want to use,
10963 // but won't work because of Chrome/Safari bugs. It isn't an exhaustive
10964 // list. In particular it doesn't contain any property names found on
10965 // subtypes of HTMLElement (e.g. name, value). Rather it attempts to catch
10966 // some common cases.
10967 propertyNameBlacklist: {
10968 children: 1,
10969 'class': 1,
10970 id: 1,
10971 hidden: 1,
10972 style: 1,
10973 title: 1,
10974 }
10975 };
10976
10977 // exports
10978
10979 scope.api.declaration.properties = properties;
10980
10981 })(Polymer);
10982
10983 (function(scope) {
10984
10985 // magic words
10986
10987 var ATTRIBUTES_ATTRIBUTE = 'attributes';
10988 var ATTRIBUTES_REGEX = /\s|,/;
10989
10990 // attributes api
10991
10992 var attributes = {
10993
10994 inheritAttributesObjects: function(prototype) {
10995 // chain our lower-cased publish map to the inherited version
10996 this.inheritObject(prototype, 'publishLC');
10997 // chain our instance attributes map to the inherited version
10998 this.inheritObject(prototype, '_instanceAttributes');
10999 },
11000
11001 publishAttributes: function(prototype, base) {
11002 // merge names from 'attributes' attribute into the 'publish' object
11003 var attributes = this.getAttribute(ATTRIBUTES_ATTRIBUTE);
11004 if (attributes) {
11005 // create a `publish` object if needed.
11006 // the `publish` object is only relevant to this prototype, the
11007 // publishing logic in `declaration/properties.js` is responsible for
11008 // managing property values on the prototype chain.
11009 // TODO(sjmiles): the `publish` object is later chained to it's
11010 // ancestor object, presumably this is only for
11011 // reflection or other non-library uses.
11012 var publish = prototype.publish || (prototype.publish = {});
11013 // names='a b c' or names='a,b,c'
11014 var names = attributes.split(ATTRIBUTES_REGEX);
11015 // record each name for publishing
11016 for (var i=0, l=names.length, n; i<l; i++) {
11017 // remove excess ws
11018 n = names[i].trim();
11019 // looks weird, but causes n to exist on `publish` if it does not;
11020 // a more careful test would need expensive `in` operator
11021 if (n && publish[n] === undefined) {
11022 publish[n] = undefined;
11023 }
11024 }
11025 }
11026 },
11027
11028 // record clonable attributes from <element>
11029 accumulateInstanceAttributes: function() {
11030 // inherit instance attributes
11031 var clonable = this.prototype._instanceAttributes;
11032 // merge attributes from element
11033 var a$ = this.attributes;
11034 for (var i=0, l=a$.length, a; (i<l) && (a=a$[i]); i++) {
11035 if (this.isInstanceAttribute(a.name)) {
11036 clonable[a.name] = a.value;
11037 }
11038 }
11039 },
11040
11041 isInstanceAttribute: function(name) {
11042 return !this.blackList[name] && name.slice(0,3) !== 'on-';
11043 },
11044
11045 // do not clone these attributes onto instances
11046 blackList: {
11047 name: 1,
11048 'extends': 1,
11049 constructor: 1,
11050 noscript: 1,
11051 assetpath: 1,
11052 'cache-csstext': 1
11053 }
11054
11055 };
11056
11057 // add ATTRIBUTES_ATTRIBUTE to the blacklist
11058 attributes.blackList[ATTRIBUTES_ATTRIBUTE] = 1;
11059
11060 // exports
11061
11062 scope.api.declaration.attributes = attributes;
11063
11064 })(Polymer);
11065
11066 (function(scope) {
11067
11068 // imports
11069 var events = scope.api.declaration.events;
11070
11071 var syntax = new PolymerExpressions();
11072 var prepareBinding = syntax.prepareBinding;
11073
11074 // Polymer takes a first crack at the binding to see if it's a declarative
11075 // event handler.
11076 syntax.prepareBinding = function(pathString, name, node) {
11077 return events.prepareEventBinding(pathString, name, node) ||
11078 prepareBinding.call(syntax, pathString, name, node);
11079 };
11080
11081 // declaration api supporting mdv
11082 var mdv = {
11083 syntax: syntax,
11084 fetchTemplate: function() {
11085 return this.querySelector('template');
11086 },
11087 templateContent: function() {
11088 var template = this.fetchTemplate();
11089 return template && template.content;
11090 },
11091 installBindingDelegate: function(template) {
11092 if (template) {
11093 template.bindingDelegate = this.syntax;
11094 }
11095 }
11096 };
11097
11098 // exports
11099 scope.api.declaration.mdv = mdv;
11100
11101 })(Polymer);
11102
11103 (function(scope) {
11104
11105 // imports
11106
11107 var api = scope.api;
11108 var isBase = scope.isBase;
11109 var extend = scope.extend;
11110
11111 var hasShadowDOMPolyfill = window.ShadowDOMPolyfill;
11112
11113 // prototype api
11114
11115 var prototype = {
11116
11117 register: function(name, extendeeName) {
11118 // build prototype combining extendee, Polymer base, and named api
11119 this.buildPrototype(name, extendeeName);
11120 // register our custom element with the platform
11121 this.registerPrototype(name, extendeeName);
11122 // reference constructor in a global named by 'constructor' attribute
11123 this.publishConstructor();
11124 },
11125
11126 buildPrototype: function(name, extendeeName) {
11127 // get our custom prototype (before chaining)
11128 var extension = scope.getRegisteredPrototype(name);
11129 // get basal prototype
11130 var base = this.generateBasePrototype(extendeeName);
11131 // implement declarative features
11132 this.desugarBeforeChaining(extension, base);
11133 // join prototypes
11134 this.prototype = this.chainPrototypes(extension, base);
11135 // more declarative features
11136 this.desugarAfterChaining(name, extendeeName);
11137 },
11138
11139 desugarBeforeChaining: function(prototype, base) {
11140 // back reference declaration element
11141 // TODO(sjmiles): replace `element` with `elementElement` or `declaration`
11142 prototype.element = this;
11143 // transcribe `attributes` declarations onto own prototype's `publish`
11144 this.publishAttributes(prototype, base);
11145 // `publish` properties to the prototype and to attribute watch
11146 this.publishProperties(prototype, base);
11147 // infer observers for `observe` list based on method names
11148 this.inferObservers(prototype);
11149 // desugar compound observer syntax, e.g. 'a b c'
11150 this.explodeObservers(prototype);
11151 },
11152
11153 chainPrototypes: function(prototype, base) {
11154 // chain various meta-data objects to inherited versions
11155 this.inheritMetaData(prototype, base);
11156 // chain custom api to inherited
11157 var chained = this.chainObject(prototype, base);
11158 // x-platform fixup
11159 ensurePrototypeTraversal(chained);
11160 return chained;
11161 },
11162
11163 inheritMetaData: function(prototype, base) {
11164 // chain observe object to inherited
11165 this.inheritObject('observe', prototype, base);
11166 // chain publish object to inherited
11167 this.inheritObject('publish', prototype, base);
11168 // chain reflect object to inherited
11169 this.inheritObject('reflect', prototype, base);
11170 // chain our lower-cased publish map to the inherited version
11171 this.inheritObject('_publishLC', prototype, base);
11172 // chain our instance attributes map to the inherited version
11173 this.inheritObject('_instanceAttributes', prototype, base);
11174 // chain our event delegates map to the inherited version
11175 this.inheritObject('eventDelegates', prototype, base);
11176 },
11177
11178 // implement various declarative features
11179 desugarAfterChaining: function(name, extendee) {
11180 // build side-chained lists to optimize iterations
11181 this.optimizePropertyMaps(this.prototype);
11182 this.createPropertyAccessors(this.prototype);
11183 // install mdv delegate on template
11184 this.installBindingDelegate(this.fetchTemplate());
11185 // install external stylesheets as if they are inline
11186 this.installSheets();
11187 // adjust any paths in dom from imports
11188 this.resolveElementPaths(this);
11189 // compile list of attributes to copy to instances
11190 this.accumulateInstanceAttributes();
11191 // parse on-* delegates declared on `this` element
11192 this.parseHostEvents();
11193 //
11194 // install a helper method this.resolvePath to aid in
11195 // setting resource urls. e.g.
11196 // this.$.image.src = this.resolvePath('images/foo.png')
11197 this.addResolvePathApi();
11198 // under ShadowDOMPolyfill, transforms to approximate missing CSS features
11199 if (hasShadowDOMPolyfill) {
11200 WebComponents.ShadowCSS.shimStyling(this.templateContent(), name,
11201 extendee);
11202 }
11203 // allow custom element access to the declarative context
11204 if (this.prototype.registerCallback) {
11205 this.prototype.registerCallback(this);
11206 }
11207 },
11208
11209 // if a named constructor is requested in element, map a reference
11210 // to the constructor to the given symbol
11211 publishConstructor: function() {
11212 var symbol = this.getAttribute('constructor');
11213 if (symbol) {
11214 window[symbol] = this.ctor;
11215 }
11216 },
11217
11218 // build prototype combining extendee, Polymer base, and named api
11219 generateBasePrototype: function(extnds) {
11220 var prototype = this.findBasePrototype(extnds);
11221 if (!prototype) {
11222 // create a prototype based on tag-name extension
11223 var prototype = HTMLElement.getPrototypeForTag(extnds);
11224 // insert base api in inheritance chain (if needed)
11225 prototype = this.ensureBaseApi(prototype);
11226 // memoize this base
11227 memoizedBases[extnds] = prototype;
11228 }
11229 return prototype;
11230 },
11231
11232 findBasePrototype: function(name) {
11233 return memoizedBases[name];
11234 },
11235
11236 // install Polymer instance api into prototype chain, as needed
11237 ensureBaseApi: function(prototype) {
11238 if (prototype.PolymerBase) {
11239 return prototype;
11240 }
11241 var extended = Object.create(prototype);
11242 // we need a unique copy of base api for each base prototype
11243 // therefore we 'extend' here instead of simply chaining
11244 api.publish(api.instance, extended);
11245 // TODO(sjmiles): sharing methods across prototype chains is
11246 // not supported by 'super' implementation which optimizes
11247 // by memoizing prototype relationships.
11248 // Probably we should have a version of 'extend' that is
11249 // share-aware: it could study the text of each function,
11250 // look for usage of 'super', and wrap those functions in
11251 // closures.
11252 // As of now, there is only one problematic method, so
11253 // we just patch it manually.
11254 // To avoid re-entrancy problems, the special super method
11255 // installed is called `mixinSuper` and the mixin method
11256 // must use this method instead of the default `super`.
11257 this.mixinMethod(extended, prototype, api.instance.mdv, 'bind');
11258 // return buffed-up prototype
11259 return extended;
11260 },
11261
11262 mixinMethod: function(extended, prototype, api, name) {
11263 var $super = function(args) {
11264 return prototype[name].apply(this, args);
11265 };
11266 extended[name] = function() {
11267 this.mixinSuper = $super;
11268 return api[name].apply(this, arguments);
11269 }
11270 },
11271
11272 // ensure prototype[name] inherits from a prototype.prototype[name]
11273 inheritObject: function(name, prototype, base) {
11274 // require an object
11275 var source = prototype[name] || {};
11276 // chain inherited properties onto a new object
11277 prototype[name] = this.chainObject(source, base[name]);
11278 },
11279
11280 // register 'prototype' to custom element 'name', store constructor
11281 registerPrototype: function(name, extendee) {
11282 var info = {
11283 prototype: this.prototype
11284 }
11285 // native element must be specified in extends
11286 var typeExtension = this.findTypeExtension(extendee);
11287 if (typeExtension) {
11288 info.extends = typeExtension;
11289 }
11290 // register the prototype with HTMLElement for name lookup
11291 HTMLElement.register(name, this.prototype);
11292 // register the custom type
11293 this.ctor = document.registerElement(name, info);
11294 },
11295
11296 findTypeExtension: function(name) {
11297 if (name && name.indexOf('-') < 0) {
11298 return name;
11299 } else {
11300 var p = this.findBasePrototype(name);
11301 if (p.element) {
11302 return this.findTypeExtension(p.element.extends);
11303 }
11304 }
11305 }
11306
11307 };
11308
11309 // memoize base prototypes
11310 var memoizedBases = {};
11311
11312 // implementation of 'chainObject' depends on support for __proto__
11313 if (Object.__proto__) {
11314 prototype.chainObject = function(object, inherited) {
11315 if (object && inherited && object !== inherited) {
11316 object.__proto__ = inherited;
11317 }
11318 return object;
11319 }
11320 } else {
11321 prototype.chainObject = function(object, inherited) {
11322 if (object && inherited && object !== inherited) {
11323 var chained = Object.create(inherited);
11324 object = extend(chained, object);
11325 }
11326 return object;
11327 }
11328 }
11329
11330 // On platforms that do not support __proto__ (versions of IE), the prototype
11331 // chain of a custom element is simulated via installation of __proto__.
11332 // Although custom elements manages this, we install it here so it's
11333 // available during desugaring.
11334 function ensurePrototypeTraversal(prototype) {
11335 if (!Object.__proto__) {
11336 var ancestor = Object.getPrototypeOf(prototype);
11337 prototype.__proto__ = ancestor;
11338 if (isBase(ancestor)) {
11339 ancestor.__proto__ = Object.getPrototypeOf(ancestor);
11340 }
11341 }
11342 }
11343
11344 // exports
11345
11346 api.declaration.prototype = prototype;
11347
11348 })(Polymer);
11349
11350 (function(scope) {
11351
11352 /*
11353
11354 Elements are added to a registration queue so that they register in
11355 the proper order at the appropriate time. We do this for a few reasons:
11356
11357 * to enable elements to load resources (like stylesheets)
11358 asynchronously. We need to do this until the platform provides an efficient
11359 alternative. One issue is that remote @import stylesheets are
11360 re-fetched whenever stamped into a shadowRoot.
11361
11362 * to ensure elements loaded 'at the same time' (e.g. via some set of
11363 imports) are registered as a batch. This allows elements to be enured from
11364 upgrade ordering as long as they query the dom tree 1 task after
11365 upgrade (aka domReady). This is a performance tradeoff. On the one hand,
11366 elements that could register while imports are loading are prevented from
11367 doing so. On the other, grouping upgrades into a single task means less
11368 incremental work (for example style recalcs), Also, we can ensure the
11369 document is in a known state at the single quantum of time when
11370 elements upgrade.
11371
11372 */
11373 var queue = {
11374
11375 // tell the queue to wait for an element to be ready
11376 wait: function(element) {
11377 if (!element.__queue) {
11378 element.__queue = {};
11379 elements.push(element);
11380 }
11381 },
11382
11383 // enqueue an element to the next spot in the queue.
11384 enqueue: function(element, check, go) {
11385 var shouldAdd = element.__queue && !element.__queue.check;
11386 if (shouldAdd) {
11387 queueForElement(element).push(element);
11388 element.__queue.check = check;
11389 element.__queue.go = go;
11390 }
11391 return (this.indexOf(element) !== 0);
11392 },
11393
11394 indexOf: function(element) {
11395 var i = queueForElement(element).indexOf(element);
11396 if (i >= 0 && document.contains(element)) {
11397 i += (HTMLImports.useNative || HTMLImports.ready) ?
11398 importQueue.length : 1e9;
11399 }
11400 return i;
11401 },
11402
11403 // tell the queue an element is ready to be registered
11404 go: function(element) {
11405 var readied = this.remove(element);
11406 if (readied) {
11407 element.__queue.flushable = true;
11408 this.addToFlushQueue(readied);
11409 this.check();
11410 }
11411 },
11412
11413 remove: function(element) {
11414 var i = this.indexOf(element);
11415 if (i !== 0) {
11416 //console.warn('queue order wrong', i);
11417 return;
11418 }
11419 return queueForElement(element).shift();
11420 },
11421
11422 check: function() {
11423 // next
11424 var element = this.nextElement();
11425 if (element) {
11426 element.__queue.check.call(element);
11427 }
11428 if (this.canReady()) {
11429 this.ready();
11430 return true;
11431 }
11432 },
11433
11434 nextElement: function() {
11435 return nextQueued();
11436 },
11437
11438 canReady: function() {
11439 return !this.waitToReady && this.isEmpty();
11440 },
11441
11442 isEmpty: function() {
11443 for (var i=0, l=elements.length, e; (i<l) &&
11444 (e=elements[i]); i++) {
11445 if (e.__queue && !e.__queue.flushable) {
11446 return;
11447 }
11448 }
11449 return true;
11450 },
11451
11452 addToFlushQueue: function(element) {
11453 flushQueue.push(element);
11454 },
11455
11456 flush: function() {
11457 // prevent re-entrance
11458 if (this.flushing) {
11459 return;
11460 }
11461 this.flushing = true;
11462 var element;
11463 while (flushQueue.length) {
11464 element = flushQueue.shift();
11465 element.__queue.go.call(element);
11466 element.__queue = null;
11467 }
11468 this.flushing = false;
11469 },
11470
11471 ready: function() {
11472 // TODO(sorvell): As an optimization, turn off CE polyfill upgrading
11473 // while registering. This way we avoid having to upgrade each document
11474 // piecemeal per registration and can instead register all elements
11475 // and upgrade once in a batch. Without this optimization, upgrade time
11476 // degrades significantly when SD polyfill is used. This is mainly because
11477 // querying the document tree for elements is slow under the SD polyfill.
11478 var polyfillWasReady = CustomElements.ready;
11479 CustomElements.ready = false;
11480 this.flush();
11481 if (!CustomElements.useNative) {
11482 CustomElements.upgradeDocumentTree(document);
11483 }
11484 CustomElements.ready = polyfillWasReady;
11485 Polymer.flush();
11486 requestAnimationFrame(this.flushReadyCallbacks);
11487 },
11488
11489 addReadyCallback: function(callback) {
11490 if (callback) {
11491 readyCallbacks.push(callback);
11492 }
11493 },
11494
11495 flushReadyCallbacks: function() {
11496 if (readyCallbacks) {
11497 var fn;
11498 while (readyCallbacks.length) {
11499 fn = readyCallbacks.shift();
11500 fn();
11501 }
11502 }
11503 },
11504
11505 /**
11506 Returns a list of elements that have had polymer-elements created but
11507 are not yet ready to register. The list is an array of element definitions.
11508 */
11509 waitingFor: function() {
11510 var e$ = [];
11511 for (var i=0, l=elements.length, e; (i<l) &&
11512 (e=elements[i]); i++) {
11513 if (e.__queue && !e.__queue.flushable) {
11514 e$.push(e);
11515 }
11516 }
11517 return e$;
11518 },
11519
11520 waitToReady: true
11521
11522 };
11523
11524 var elements = [];
11525 var flushQueue = [];
11526 var importQueue = [];
11527 var mainQueue = [];
11528 var readyCallbacks = [];
11529
11530 function queueForElement(element) {
11531 return document.contains(element) ? mainQueue : importQueue;
11532 }
11533
11534 function nextQueued() {
11535 return importQueue.length ? importQueue[0] : mainQueue[0];
11536 }
11537
11538 function whenReady(callback) {
11539 queue.waitToReady = true;
11540 Polymer.endOfMicrotask(function() {
11541 HTMLImports.whenReady(function() {
11542 queue.addReadyCallback(callback);
11543 queue.waitToReady = false;
11544 queue.check();
11545 });
11546 });
11547 }
11548
11549 /**
11550 Forces polymer to register any pending elements. Can be used to abort
11551 waiting for elements that are partially defined.
11552 @param timeout {Integer} Optional timeout in milliseconds
11553 */
11554 function forceReady(timeout) {
11555 if (timeout === undefined) {
11556 queue.ready();
11557 return;
11558 }
11559 var handle = setTimeout(function() {
11560 queue.ready();
11561 }, timeout);
11562 Polymer.whenReady(function() {
11563 clearTimeout(handle);
11564 });
11565 }
11566
11567 // exports
11568 scope.elements = elements;
11569 scope.waitingFor = queue.waitingFor.bind(queue);
11570 scope.forceReady = forceReady;
11571 scope.queue = queue;
11572 scope.whenReady = scope.whenPolymerReady = whenReady;
11573 })(Polymer);
11574
11575 (function(scope) {
11576
11577 // imports
11578
11579 var extend = scope.extend;
11580 var api = scope.api;
11581 var queue = scope.queue;
11582 var whenReady = scope.whenReady;
11583 var getRegisteredPrototype = scope.getRegisteredPrototype;
11584 var waitingForPrototype = scope.waitingForPrototype;
11585
11586 // declarative implementation: <polymer-element>
11587
11588 var prototype = extend(Object.create(HTMLElement.prototype), {
11589
11590 createdCallback: function() {
11591 if (this.getAttribute('name')) {
11592 this.init();
11593 }
11594 },
11595
11596 init: function() {
11597 // fetch declared values
11598 this.name = this.getAttribute('name');
11599 this.extends = this.getAttribute('extends');
11600 queue.wait(this);
11601 // initiate any async resource fetches
11602 this.loadResources();
11603 // register when all constraints are met
11604 this.registerWhenReady();
11605 },
11606
11607 // TODO(sorvell): we currently queue in the order the prototypes are
11608 // registered, but we should queue in the order that polymer-elements
11609 // are registered. We are currently blocked from doing this based on
11610 // crbug.com/395686.
11611 registerWhenReady: function() {
11612 if (this.registered
11613 || this.waitingForPrototype(this.name)
11614 || this.waitingForQueue()
11615 || this.waitingForResources()) {
11616 return;
11617 }
11618 queue.go(this);
11619 },
11620
11621 _register: function() {
11622 //console.log('registering', this.name);
11623 // warn if extending from a custom element not registered via Polymer
11624 if (isCustomTag(this.extends) && !isRegistered(this.extends)) {
11625 console.warn('%s is attempting to extend %s, an unregistered element ' +
11626 'or one that was not registered with Polymer.', this.name,
11627 this.extends);
11628 }
11629 this.register(this.name, this.extends);
11630 this.registered = true;
11631 },
11632
11633 waitingForPrototype: function(name) {
11634 if (!getRegisteredPrototype(name)) {
11635 // then wait for a prototype
11636 waitingForPrototype(name, this);
11637 // emulate script if user is not supplying one
11638 this.handleNoScript(name);
11639 // prototype not ready yet
11640 return true;
11641 }
11642 },
11643
11644 handleNoScript: function(name) {
11645 // if explicitly marked as 'noscript'
11646 if (this.hasAttribute('noscript') && !this.noscript) {
11647 this.noscript = true;
11648 // imperative element registration
11649 Polymer(name);
11650 }
11651 },
11652
11653 waitingForResources: function() {
11654 return this._needsResources;
11655 },
11656
11657 // NOTE: Elements must be queued in proper order for inheritance/composition
11658 // dependency resolution. Previously this was enforced for inheritance,
11659 // and by rule for composition. It's now entirely by rule.
11660 waitingForQueue: function() {
11661 return queue.enqueue(this, this.registerWhenReady, this._register);
11662 },
11663
11664 loadResources: function() {
11665 this._needsResources = true;
11666 this.loadStyles(function() {
11667 this._needsResources = false;
11668 this.registerWhenReady();
11669 }.bind(this));
11670 }
11671
11672 });
11673
11674 // semi-pluggable APIs
11675
11676 // TODO(sjmiles): should be fully pluggable (aka decoupled, currently
11677 // the various plugins are allowed to depend on each other directly)
11678 api.publish(api.declaration, prototype);
11679
11680 // utility and bookkeeping
11681
11682 function isRegistered(name) {
11683 return Boolean(HTMLElement.getPrototypeForTag(name));
11684 }
11685
11686 function isCustomTag(name) {
11687 return (name && name.indexOf('-') >= 0);
11688 }
11689
11690 // boot tasks
11691
11692 whenReady(function() {
11693 document.body.removeAttribute('unresolved');
11694 document.dispatchEvent(
11695 new CustomEvent('polymer-ready', {bubbles: true})
11696 );
11697 });
11698
11699 // register polymer-element with document
11700
11701 document.registerElement('polymer-element', {prototype: prototype});
11702
11703 })(Polymer);
11704
11705 (function(scope) {
11706
11707 /**
11708 * @class Polymer
11709 */
11710
11711 var whenReady = scope.whenReady;
11712
11713 /**
11714 * Loads the set of HTMLImports contained in `node`. Notifies when all
11715 * the imports have loaded by calling the `callback` function argument.
11716 * This method can be used to lazily load imports. For example, given a
11717 * template:
11718 *
11719 * <template>
11720 * <link rel="import" href="my-import1.html">
11721 * <link rel="import" href="my-import2.html">
11722 * </template>
11723 *
11724 * Polymer.importElements(template.content, function() {
11725 * console.log('imports lazily loaded');
11726 * });
11727 *
11728 * @method importElements
11729 * @param {Node} node Node containing the HTMLImports to load.
11730 * @param {Function} callback Callback called when all imports have loaded.
11731 */
11732 function importElements(node, callback) {
11733 if (node) {
11734 document.head.appendChild(node);
11735 whenReady(callback);
11736 } else if (callback) {
11737 callback();
11738 }
11739 }
11740
11741 /**
11742 * Loads an HTMLImport for each url specified in the `urls` array.
11743 * Notifies when all the imports have loaded by calling the `callback`
11744 * function argument. This method can be used to lazily load imports.
11745 * For example,
11746 *
11747 * Polymer.import(['my-import1.html', 'my-import2.html'], function() {
11748 * console.log('imports lazily loaded');
11749 * });
11750 *
11751 * @method import
11752 * @param {Array} urls Array of urls to load as HTMLImports.
11753 * @param {Function} callback Callback called when all imports have loaded.
11754 */
11755 function _import(urls, callback) {
11756 if (urls && urls.length) {
11757 var frag = document.createDocumentFragment();
11758 for (var i=0, l=urls.length, url, link; (i<l) && (url=urls[i]); i++) {
11759 link = document.createElement('link');
11760 link.rel = 'import';
11761 link.href = url;
11762 frag.appendChild(link);
11763 }
11764 importElements(frag, callback);
11765 } else if (callback) {
11766 callback();
11767 }
11768 }
11769
11770 // exports
11771 scope.import = _import;
11772 scope.importElements = importElements;
11773
11774 })(Polymer);
11775
11776 /**
11777 * The `auto-binding` element extends the template element. It provides a quick
11778 * and easy way to do data binding without the need to setup a model.
11779 * The `auto-binding` element itself serves as the model and controller for the
11780 * elements it contains. Both data and event handlers can be bound.
11781 *
11782 * The `auto-binding` element acts just like a template that is bound to
11783 * a model. It stamps its content in the dom adjacent to itself. When the
11784 * content is stamped, the `template-bound` event is fired.
11785 *
11786 * Example:
11787 *
11788 * <template is="auto-binding">
11789 * <div>Say something: <input value="{{value}}"></div>
11790 * <div>You said: {{value}}</div>
11791 * <button on-tap="{{buttonTap}}">Tap me!</button>
11792 * </template>
11793 * <script>
11794 * var template = document.querySelector('template');
11795 * template.value = 'something';
11796 * template.buttonTap = function() {
11797 * console.log('tap!');
11798 * };
11799 * </script>
11800 *
11801 * @module Polymer
11802 * @status stable
11803 */
11804
11805 (function() {
11806
11807 var element = document.createElement('polymer-element');
11808 element.setAttribute('name', 'auto-binding');
11809 element.setAttribute('extends', 'template');
11810 element.init();
11811
11812 Polymer('auto-binding', {
11813
11814 createdCallback: function() {
11815 this.syntax = this.bindingDelegate = this.makeSyntax();
11816 // delay stamping until polymer-ready so that auto-binding is not
11817 // required to load last.
11818 Polymer.whenPolymerReady(function() {
11819 this.model = this;
11820 this.setAttribute('bind', '');
11821 // we don't bother with an explicit signal here, we could ust a MO
11822 // if necessary
11823 this.async(function() {
11824 // note: this will marshall *all* the elements in the parentNode
11825 // rather than just stamped ones. We'd need to use createInstance
11826 // to fix this or something else fancier.
11827 this.marshalNodeReferences(this.parentNode);
11828 // template stamping is asynchronous so stamping isn't complete
11829 // by polymer-ready; fire an event so users can use stamped elements
11830 this.fire('template-bound');
11831 });
11832 }.bind(this));
11833 },
11834
11835 makeSyntax: function() {
11836 var events = Object.create(Polymer.api.declaration.events);
11837 var self = this;
11838 events.findController = function() { return self.model; };
11839
11840 var syntax = new PolymerExpressions();
11841 var prepareBinding = syntax.prepareBinding;
11842 syntax.prepareBinding = function(pathString, name, node) {
11843 return events.prepareEventBinding(pathString, name, node) ||
11844 prepareBinding.call(syntax, pathString, name, node);
11845 };
11846 return syntax;
11847 }
11848
11849 });
11850
11851 })();
OLDNEW
« no previous file with comments | « polymer_0.5.0/bower_components/polymer/polymer.html ('k') | polymer_0.5.0/bower_components/polymer/polymer.min.js » ('j') | no next file with comments »

Powered by Google App Engine