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

Side by Side Diff: appengine/swarming/elements/polymer05/stats-app-build.js

Issue 2408743002: Move elements/ to ui/ (Closed)
Patch Set: rebase again Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
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.4
11 window.PolymerGestures = {};
12
13 (function(scope) {
14 var hasFullPath = false;
15
16 // test for full event path support
17 var pathTest = document.createElement('meta');
18 if (pathTest.createShadowRoot) {
19 var sr = pathTest.createShadowRoot();
20 var s = document.createElement('span');
21 sr.appendChild(s);
22 pathTest.addEventListener('testpath', function(ev) {
23 if (ev.path) {
24 // if the span is in the event path, then path[0] is the real source for all events
25 hasFullPath = ev.path[0] === s;
26 }
27 ev.stopPropagation();
28 });
29 var ev = new CustomEvent('testpath', {bubbles: true});
30 // must add node to DOM to trigger event listener
31 document.head.appendChild(pathTest);
32 s.dispatchEvent(ev);
33 pathTest.parentNode.removeChild(pathTest);
34 sr = s = null;
35 }
36 pathTest = null;
37
38 var target = {
39 shadow: function(inEl) {
40 if (inEl) {
41 return inEl.shadowRoot || inEl.webkitShadowRoot;
42 }
43 },
44 canTarget: function(shadow) {
45 return shadow && Boolean(shadow.elementFromPoint);
46 },
47 targetingShadow: function(inEl) {
48 var s = this.shadow(inEl);
49 if (this.canTarget(s)) {
50 return s;
51 }
52 },
53 olderShadow: function(shadow) {
54 var os = shadow.olderShadowRoot;
55 if (!os) {
56 var se = shadow.querySelector('shadow');
57 if (se) {
58 os = se.olderShadowRoot;
59 }
60 }
61 return os;
62 },
63 allShadows: function(element) {
64 var shadows = [], s = this.shadow(element);
65 while(s) {
66 shadows.push(s);
67 s = this.olderShadow(s);
68 }
69 return shadows;
70 },
71 searchRoot: function(inRoot, x, y) {
72 var t, st, sr, os;
73 if (inRoot) {
74 t = inRoot.elementFromPoint(x, y);
75 if (t) {
76 // found element, check if it has a ShadowRoot
77 sr = this.targetingShadow(t);
78 } else if (inRoot !== document) {
79 // check for sibling roots
80 sr = this.olderShadow(inRoot);
81 }
82 // search other roots, fall back to light dom element
83 return this.searchRoot(sr, x, y) || t;
84 }
85 },
86 owner: function(element) {
87 if (!element) {
88 return document;
89 }
90 var s = element;
91 // walk up until you hit the shadow root or document
92 while (s.parentNode) {
93 s = s.parentNode;
94 }
95 // the owner element is expected to be a Document or ShadowRoot
96 if (s.nodeType != Node.DOCUMENT_NODE && s.nodeType != Node.DOCUMENT_FRAGME NT_NODE) {
97 s = document;
98 }
99 return s;
100 },
101 findTarget: function(inEvent) {
102 if (hasFullPath && inEvent.path && inEvent.path.length) {
103 return inEvent.path[0];
104 }
105 var x = inEvent.clientX, y = inEvent.clientY;
106 // if the listener is in the shadow root, it is much faster to start there
107 var s = this.owner(inEvent.target);
108 // if x, y is not in this root, fall back to document search
109 if (!s.elementFromPoint(x, y)) {
110 s = document;
111 }
112 return this.searchRoot(s, x, y);
113 },
114 findTouchAction: function(inEvent) {
115 var n;
116 if (hasFullPath && inEvent.path && inEvent.path.length) {
117 var path = inEvent.path;
118 for (var i = 0; i < path.length; i++) {
119 n = path[i];
120 if (n.nodeType === Node.ELEMENT_NODE && n.hasAttribute('touch-action') ) {
121 return n.getAttribute('touch-action');
122 }
123 }
124 } else {
125 n = inEvent.target;
126 while(n) {
127 if (n.nodeType === Node.ELEMENT_NODE && n.hasAttribute('touch-action') ) {
128 return n.getAttribute('touch-action');
129 }
130 n = n.parentNode || n.host;
131 }
132 }
133 // auto is default
134 return "auto";
135 },
136 LCA: function(a, b) {
137 if (a === b) {
138 return a;
139 }
140 if (a && !b) {
141 return a;
142 }
143 if (b && !a) {
144 return b;
145 }
146 if (!b && !a) {
147 return document;
148 }
149 // fast case, a is a direct descendant of b or vice versa
150 if (a.contains && a.contains(b)) {
151 return a;
152 }
153 if (b.contains && b.contains(a)) {
154 return b;
155 }
156 var adepth = this.depth(a);
157 var bdepth = this.depth(b);
158 var d = adepth - bdepth;
159 if (d >= 0) {
160 a = this.walk(a, d);
161 } else {
162 b = this.walk(b, -d);
163 }
164 while (a && b && a !== b) {
165 a = a.parentNode || a.host;
166 b = b.parentNode || b.host;
167 }
168 return a;
169 },
170 walk: function(n, u) {
171 for (var i = 0; n && (i < u); i++) {
172 n = n.parentNode || n.host;
173 }
174 return n;
175 },
176 depth: function(n) {
177 var d = 0;
178 while(n) {
179 d++;
180 n = n.parentNode || n.host;
181 }
182 return d;
183 },
184 deepContains: function(a, b) {
185 var common = this.LCA(a, b);
186 // if a is the common ancestor, it must "deeply" contain b
187 return common === a;
188 },
189 insideNode: function(node, x, y) {
190 var rect = node.getBoundingClientRect();
191 return (rect.left <= x) && (x <= rect.right) && (rect.top <= y) && (y <= r ect.bottom);
192 },
193 path: function(event) {
194 var p;
195 if (hasFullPath && event.path && event.path.length) {
196 p = event.path;
197 } else {
198 p = [];
199 var n = this.findTarget(event);
200 while (n) {
201 p.push(n);
202 n = n.parentNode || n.host;
203 }
204 }
205 return p;
206 }
207 };
208 scope.targetFinding = target;
209 /**
210 * Given an event, finds the "deepest" node that could have been the original target before ShadowDOM retargetting
211 *
212 * @param {Event} Event An event object with clientX and clientY properties
213 * @return {Element} The probable event origninator
214 */
215 scope.findTarget = target.findTarget.bind(target);
216 /**
217 * Determines if the "container" node deeply contains the "containee" node, 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 currentButtons = 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 currentButtons |= bit;
1015 } else if (type === 'mouseup') {
1016 currentButtons &= ~bit;
1017 }
1018 e.buttons = currentButtons;
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 currentButtons = 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.4'
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 'if': true
6358 };
6359
6360 var semanticTemplateElements = {
6361 'THEAD': true,
6362 'TBODY': true,
6363 'TFOOT': true,
6364 'TH': true,
6365 'TR': true,
6366 'TD': true,
6367 'COLGROUP': true,
6368 'COL': true,
6369 'CAPTION': true,
6370 'OPTION': true,
6371 'OPTGROUP': true
6372 };
6373
6374 var hasTemplateElement = typeof HTMLTemplateElement !== 'undefined';
6375 if (hasTemplateElement) {
6376 // TODO(rafaelw): Remove when fix for
6377 // https://codereview.chromium.org/164803002/
6378 // makes it to Chrome release.
6379 (function() {
6380 var t = document.createElement('template');
6381 var d = t.content.ownerDocument;
6382 var html = d.appendChild(d.createElement('html'));
6383 var head = html.appendChild(d.createElement('head'));
6384 var base = d.createElement('base');
6385 base.href = document.baseURI;
6386 head.appendChild(base);
6387 })();
6388 }
6389
6390 var allTemplatesSelectors = 'template, ' +
6391 Object.keys(semanticTemplateElements).map(function(tagName) {
6392 return tagName.toLowerCase() + '[template]';
6393 }).join(', ');
6394
6395 function isSVGTemplate(el) {
6396 return el.tagName == 'template' &&
6397 el.namespaceURI == 'http://www.w3.org/2000/svg';
6398 }
6399
6400 function isHTMLTemplate(el) {
6401 return el.tagName == 'TEMPLATE' &&
6402 el.namespaceURI == 'http://www.w3.org/1999/xhtml';
6403 }
6404
6405 function isAttributeTemplate(el) {
6406 return Boolean(semanticTemplateElements[el.tagName] &&
6407 el.hasAttribute('template'));
6408 }
6409
6410 function isTemplate(el) {
6411 if (el.isTemplate_ === undefined)
6412 el.isTemplate_ = el.tagName == 'TEMPLATE' || isAttributeTemplate(el);
6413
6414 return el.isTemplate_;
6415 }
6416
6417 // FIXME: Observe templates being added/removed from documents
6418 // FIXME: Expose imperative API to decorate and observe templates in
6419 // "disconnected tress" (e.g. ShadowRoot)
6420 document.addEventListener('DOMContentLoaded', function(e) {
6421 bootstrapTemplatesRecursivelyFrom(document);
6422 // FIXME: Is this needed? Seems like it shouldn't be.
6423 Platform.performMicrotaskCheckpoint();
6424 }, false);
6425
6426 function forAllTemplatesFrom(node, fn) {
6427 var subTemplates = node.querySelectorAll(allTemplatesSelectors);
6428
6429 if (isTemplate(node))
6430 fn(node)
6431 forEach(subTemplates, fn);
6432 }
6433
6434 function bootstrapTemplatesRecursivelyFrom(node) {
6435 function bootstrap(template) {
6436 if (!HTMLTemplateElement.decorate(template))
6437 bootstrapTemplatesRecursivelyFrom(template.content);
6438 }
6439
6440 forAllTemplatesFrom(node, bootstrap);
6441 }
6442
6443 if (!hasTemplateElement) {
6444 /**
6445 * This represents a <template> element.
6446 * @constructor
6447 * @extends {HTMLElement}
6448 */
6449 global.HTMLTemplateElement = function() {
6450 throw TypeError('Illegal constructor');
6451 };
6452 }
6453
6454 var hasProto = '__proto__' in {};
6455
6456 function mixin(to, from) {
6457 Object.getOwnPropertyNames(from).forEach(function(name) {
6458 Object.defineProperty(to, name,
6459 Object.getOwnPropertyDescriptor(from, name));
6460 });
6461 }
6462
6463 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html# dfn-template-contents-owner
6464 function getOrCreateTemplateContentsOwner(template) {
6465 var doc = template.ownerDocument
6466 if (!doc.defaultView)
6467 return doc;
6468 var d = doc.templateContentsOwner_;
6469 if (!d) {
6470 // TODO(arv): This should either be a Document or HTMLDocument depending
6471 // on doc.
6472 d = doc.implementation.createHTMLDocument('');
6473 while (d.lastChild) {
6474 d.removeChild(d.lastChild);
6475 }
6476 doc.templateContentsOwner_ = d;
6477 }
6478 return d;
6479 }
6480
6481 function getTemplateStagingDocument(template) {
6482 if (!template.stagingDocument_) {
6483 var owner = template.ownerDocument;
6484 if (!owner.stagingDocument_) {
6485 owner.stagingDocument_ = owner.implementation.createHTMLDocument('');
6486 owner.stagingDocument_.isStagingDocument = true;
6487 // TODO(rafaelw): Remove when fix for
6488 // https://codereview.chromium.org/164803002/
6489 // makes it to Chrome release.
6490 var base = owner.stagingDocument_.createElement('base');
6491 base.href = document.baseURI;
6492 owner.stagingDocument_.head.appendChild(base);
6493
6494 owner.stagingDocument_.stagingDocument_ = owner.stagingDocument_;
6495 }
6496
6497 template.stagingDocument_ = owner.stagingDocument_;
6498 }
6499
6500 return template.stagingDocument_;
6501 }
6502
6503 // For non-template browsers, the parser will disallow <template> in certain
6504 // locations, so we allow "attribute templates" which combine the template
6505 // element with the top-level container node of the content, e.g.
6506 //
6507 // <tr template repeat="{{ foo }}"" class="bar"><td>Bar</td></tr>
6508 //
6509 // becomes
6510 //
6511 // <template repeat="{{ foo }}">
6512 // + #document-fragment
6513 // + <tr class="bar">
6514 // + <td>Bar</td>
6515 //
6516 function extractTemplateFromAttributeTemplate(el) {
6517 var template = el.ownerDocument.createElement('template');
6518 el.parentNode.insertBefore(template, el);
6519
6520 var attribs = el.attributes;
6521 var count = attribs.length;
6522 while (count-- > 0) {
6523 var attrib = attribs[count];
6524 if (templateAttributeDirectives[attrib.name]) {
6525 if (attrib.name !== 'template')
6526 template.setAttribute(attrib.name, attrib.value);
6527 el.removeAttribute(attrib.name);
6528 }
6529 }
6530
6531 return template;
6532 }
6533
6534 function extractTemplateFromSVGTemplate(el) {
6535 var template = el.ownerDocument.createElement('template');
6536 el.parentNode.insertBefore(template, el);
6537
6538 var attribs = el.attributes;
6539 var count = attribs.length;
6540 while (count-- > 0) {
6541 var attrib = attribs[count];
6542 template.setAttribute(attrib.name, attrib.value);
6543 el.removeAttribute(attrib.name);
6544 }
6545
6546 el.parentNode.removeChild(el);
6547 return template;
6548 }
6549
6550 function liftNonNativeTemplateChildrenIntoContent(template, el, useRoot) {
6551 var content = template.content;
6552 if (useRoot) {
6553 content.appendChild(el);
6554 return;
6555 }
6556
6557 var child;
6558 while (child = el.firstChild) {
6559 content.appendChild(child);
6560 }
6561 }
6562
6563 var templateObserver;
6564 if (typeof MutationObserver == 'function') {
6565 templateObserver = new MutationObserver(function(records) {
6566 for (var i = 0; i < records.length; i++) {
6567 records[i].target.refChanged_();
6568 }
6569 });
6570 }
6571
6572 /**
6573 * Ensures proper API and content model for template elements.
6574 * @param {HTMLTemplateElement} opt_instanceRef The template element which
6575 * |el| template element will return as the value of its ref(), and whose
6576 * content will be used as source when createInstance() is invoked.
6577 */
6578 HTMLTemplateElement.decorate = function(el, opt_instanceRef) {
6579 if (el.templateIsDecorated_)
6580 return false;
6581
6582 var templateElement = el;
6583 templateElement.templateIsDecorated_ = true;
6584
6585 var isNativeHTMLTemplate = isHTMLTemplate(templateElement) &&
6586 hasTemplateElement;
6587 var bootstrapContents = isNativeHTMLTemplate;
6588 var liftContents = !isNativeHTMLTemplate;
6589 var liftRoot = false;
6590
6591 if (!isNativeHTMLTemplate) {
6592 if (isAttributeTemplate(templateElement)) {
6593 assert(!opt_instanceRef);
6594 templateElement = extractTemplateFromAttributeTemplate(el);
6595 templateElement.templateIsDecorated_ = true;
6596 isNativeHTMLTemplate = hasTemplateElement;
6597 liftRoot = true;
6598 } else if (isSVGTemplate(templateElement)) {
6599 templateElement = extractTemplateFromSVGTemplate(el);
6600 templateElement.templateIsDecorated_ = true;
6601 isNativeHTMLTemplate = hasTemplateElement;
6602 }
6603 }
6604
6605 if (!isNativeHTMLTemplate) {
6606 fixTemplateElementPrototype(templateElement);
6607 var doc = getOrCreateTemplateContentsOwner(templateElement);
6608 templateElement.content_ = doc.createDocumentFragment();
6609 }
6610
6611 if (opt_instanceRef) {
6612 // template is contained within an instance, its direct content must be
6613 // empty
6614 templateElement.instanceRef_ = opt_instanceRef;
6615 } else if (liftContents) {
6616 liftNonNativeTemplateChildrenIntoContent(templateElement,
6617 el,
6618 liftRoot);
6619 } else if (bootstrapContents) {
6620 bootstrapTemplatesRecursivelyFrom(templateElement.content);
6621 }
6622
6623 return true;
6624 };
6625
6626 // TODO(rafaelw): This used to decorate recursively all templates from a given
6627 // node. This happens by default on 'DOMContentLoaded', but may be needed
6628 // in subtrees not descendent from document (e.g. ShadowRoot).
6629 // Review whether this is the right public API.
6630 HTMLTemplateElement.bootstrap = bootstrapTemplatesRecursivelyFrom;
6631
6632 var htmlElement = global.HTMLUnknownElement || HTMLElement;
6633
6634 var contentDescriptor = {
6635 get: function() {
6636 return this.content_;
6637 },
6638 enumerable: true,
6639 configurable: true
6640 };
6641
6642 if (!hasTemplateElement) {
6643 // Gecko is more picky with the prototype than WebKit. Make sure to use the
6644 // same prototype as created in the constructor.
6645 HTMLTemplateElement.prototype = Object.create(htmlElement.prototype);
6646
6647 Object.defineProperty(HTMLTemplateElement.prototype, 'content',
6648 contentDescriptor);
6649 }
6650
6651 function fixTemplateElementPrototype(el) {
6652 if (hasProto)
6653 el.__proto__ = HTMLTemplateElement.prototype;
6654 else
6655 mixin(el, HTMLTemplateElement.prototype);
6656 }
6657
6658 function ensureSetModelScheduled(template) {
6659 if (!template.setModelFn_) {
6660 template.setModelFn_ = function() {
6661 template.setModelFnScheduled_ = false;
6662 var map = getBindings(template,
6663 template.delegate_ && template.delegate_.prepareBinding);
6664 processBindings(template, map, template.model_);
6665 };
6666 }
6667
6668 if (!template.setModelFnScheduled_) {
6669 template.setModelFnScheduled_ = true;
6670 Observer.runEOM_(template.setModelFn_);
6671 }
6672 }
6673
6674 mixin(HTMLTemplateElement.prototype, {
6675 bind: function(name, value, oneTime) {
6676 if (name != 'ref')
6677 return Element.prototype.bind.call(this, name, value, oneTime);
6678
6679 var self = this;
6680 var ref = oneTime ? value : value.open(function(ref) {
6681 self.setAttribute('ref', ref);
6682 self.refChanged_();
6683 });
6684
6685 this.setAttribute('ref', ref);
6686 this.refChanged_();
6687 if (oneTime)
6688 return;
6689
6690 if (!this.bindings_) {
6691 this.bindings_ = { ref: value };
6692 } else {
6693 this.bindings_.ref = value;
6694 }
6695
6696 return value;
6697 },
6698
6699 processBindingDirectives_: function(directives) {
6700 if (this.iterator_)
6701 this.iterator_.closeDeps();
6702
6703 if (!directives.if && !directives.bind && !directives.repeat) {
6704 if (this.iterator_) {
6705 this.iterator_.close();
6706 this.iterator_ = undefined;
6707 }
6708
6709 return;
6710 }
6711
6712 if (!this.iterator_) {
6713 this.iterator_ = new TemplateIterator(this);
6714 }
6715
6716 this.iterator_.updateDependencies(directives, this.model_);
6717
6718 if (templateObserver) {
6719 templateObserver.observe(this, { attributes: true,
6720 attributeFilter: ['ref'] });
6721 }
6722
6723 return this.iterator_;
6724 },
6725
6726 createInstance: function(model, bindingDelegate, delegate_) {
6727 if (bindingDelegate)
6728 delegate_ = this.newDelegate_(bindingDelegate);
6729 else if (!delegate_)
6730 delegate_ = this.delegate_;
6731
6732 if (!this.refContent_)
6733 this.refContent_ = this.ref_.content;
6734 var content = this.refContent_;
6735 if (content.firstChild === null)
6736 return emptyInstance;
6737
6738 var map = getInstanceBindingMap(content, delegate_);
6739 var stagingDocument = getTemplateStagingDocument(this);
6740 var instance = stagingDocument.createDocumentFragment();
6741 instance.templateCreator_ = this;
6742 instance.protoContent_ = content;
6743 instance.bindings_ = [];
6744 instance.terminator_ = null;
6745 var instanceRecord = instance.templateInstance_ = {
6746 firstNode: null,
6747 lastNode: null,
6748 model: model
6749 };
6750
6751 var i = 0;
6752 var collectTerminator = false;
6753 for (var child = content.firstChild; child; child = child.nextSibling) {
6754 // The terminator of the instance is the clone of the last child of the
6755 // content. If the last child is an active template, it may produce
6756 // instances as a result of production, so simply collecting the last
6757 // child of the instance after it has finished producing may be wrong.
6758 if (child.nextSibling === null)
6759 collectTerminator = true;
6760
6761 var clone = cloneAndBindInstance(child, instance, stagingDocument,
6762 map.children[i++],
6763 model,
6764 delegate_,
6765 instance.bindings_);
6766 clone.templateInstance_ = instanceRecord;
6767 if (collectTerminator)
6768 instance.terminator_ = clone;
6769 }
6770
6771 instanceRecord.firstNode = instance.firstChild;
6772 instanceRecord.lastNode = instance.lastChild;
6773 instance.templateCreator_ = undefined;
6774 instance.protoContent_ = undefined;
6775 return instance;
6776 },
6777
6778 get model() {
6779 return this.model_;
6780 },
6781
6782 set model(model) {
6783 this.model_ = model;
6784 ensureSetModelScheduled(this);
6785 },
6786
6787 get bindingDelegate() {
6788 return this.delegate_ && this.delegate_.raw;
6789 },
6790
6791 refChanged_: function() {
6792 if (!this.iterator_ || this.refContent_ === this.ref_.content)
6793 return;
6794
6795 this.refContent_ = undefined;
6796 this.iterator_.valueChanged();
6797 this.iterator_.updateIteratedValue(this.iterator_.getUpdatedValue());
6798 },
6799
6800 clear: function() {
6801 this.model_ = undefined;
6802 this.delegate_ = undefined;
6803 if (this.bindings_ && this.bindings_.ref)
6804 this.bindings_.ref.close()
6805 this.refContent_ = undefined;
6806 if (!this.iterator_)
6807 return;
6808 this.iterator_.valueChanged();
6809 this.iterator_.close()
6810 this.iterator_ = undefined;
6811 },
6812
6813 setDelegate_: function(delegate) {
6814 this.delegate_ = delegate;
6815 this.bindingMap_ = undefined;
6816 if (this.iterator_) {
6817 this.iterator_.instancePositionChangedFn_ = undefined;
6818 this.iterator_.instanceModelFn_ = undefined;
6819 }
6820 },
6821
6822 newDelegate_: function(bindingDelegate) {
6823 if (!bindingDelegate)
6824 return;
6825
6826 function delegateFn(name) {
6827 var fn = bindingDelegate && bindingDelegate[name];
6828 if (typeof fn != 'function')
6829 return;
6830
6831 return function() {
6832 return fn.apply(bindingDelegate, arguments);
6833 };
6834 }
6835
6836 return {
6837 bindingMaps: {},
6838 raw: bindingDelegate,
6839 prepareBinding: delegateFn('prepareBinding'),
6840 prepareInstanceModel: delegateFn('prepareInstanceModel'),
6841 prepareInstancePositionChanged:
6842 delegateFn('prepareInstancePositionChanged')
6843 };
6844 },
6845
6846 set bindingDelegate(bindingDelegate) {
6847 if (this.delegate_) {
6848 throw Error('Template must be cleared before a new bindingDelegate ' +
6849 'can be assigned');
6850 }
6851
6852 this.setDelegate_(this.newDelegate_(bindingDelegate));
6853 },
6854
6855 get ref_() {
6856 var ref = searchRefId(this, this.getAttribute('ref'));
6857 if (!ref)
6858 ref = this.instanceRef_;
6859
6860 if (!ref)
6861 return this;
6862
6863 var nextRef = ref.ref_;
6864 return nextRef ? nextRef : ref;
6865 }
6866 });
6867
6868 // Returns
6869 // a) undefined if there are no mustaches.
6870 // b) [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least one mustache.
6871 function parseMustaches(s, name, node, prepareBindingFn) {
6872 if (!s || !s.length)
6873 return;
6874
6875 var tokens;
6876 var length = s.length;
6877 var startIndex = 0, lastIndex = 0, endIndex = 0;
6878 var onlyOneTime = true;
6879 while (lastIndex < length) {
6880 var startIndex = s.indexOf('{{', lastIndex);
6881 var oneTimeStart = s.indexOf('[[', lastIndex);
6882 var oneTime = false;
6883 var terminator = '}}';
6884
6885 if (oneTimeStart >= 0 &&
6886 (startIndex < 0 || oneTimeStart < startIndex)) {
6887 startIndex = oneTimeStart;
6888 oneTime = true;
6889 terminator = ']]';
6890 }
6891
6892 endIndex = startIndex < 0 ? -1 : s.indexOf(terminator, startIndex + 2);
6893
6894 if (endIndex < 0) {
6895 if (!tokens)
6896 return;
6897
6898 tokens.push(s.slice(lastIndex)); // TEXT
6899 break;
6900 }
6901
6902 tokens = tokens || [];
6903 tokens.push(s.slice(lastIndex, startIndex)); // TEXT
6904 var pathString = s.slice(startIndex + 2, endIndex).trim();
6905 tokens.push(oneTime); // ONE_TIME?
6906 onlyOneTime = onlyOneTime && oneTime;
6907 var delegateFn = prepareBindingFn &&
6908 prepareBindingFn(pathString, name, node);
6909 // Don't try to parse the expression if there's a prepareBinding function
6910 if (delegateFn == null) {
6911 tokens.push(Path.get(pathString)); // PATH
6912 } else {
6913 tokens.push(null);
6914 }
6915 tokens.push(delegateFn); // DELEGATE_FN
6916 lastIndex = endIndex + 2;
6917 }
6918
6919 if (lastIndex === length)
6920 tokens.push(''); // TEXT
6921
6922 tokens.hasOnePath = tokens.length === 5;
6923 tokens.isSimplePath = tokens.hasOnePath &&
6924 tokens[0] == '' &&
6925 tokens[4] == '';
6926 tokens.onlyOneTime = onlyOneTime;
6927
6928 tokens.combinator = function(values) {
6929 var newValue = tokens[0];
6930
6931 for (var i = 1; i < tokens.length; i += 4) {
6932 var value = tokens.hasOnePath ? values : values[(i - 1) / 4];
6933 if (value !== undefined)
6934 newValue += value;
6935 newValue += tokens[i + 3];
6936 }
6937
6938 return newValue;
6939 }
6940
6941 return tokens;
6942 };
6943
6944 function processOneTimeBinding(name, tokens, node, model) {
6945 if (tokens.hasOnePath) {
6946 var delegateFn = tokens[3];
6947 var value = delegateFn ? delegateFn(model, node, true) :
6948 tokens[2].getValueFrom(model);
6949 return tokens.isSimplePath ? value : tokens.combinator(value);
6950 }
6951
6952 var values = [];
6953 for (var i = 1; i < tokens.length; i += 4) {
6954 var delegateFn = tokens[i + 2];
6955 values[(i - 1) / 4] = delegateFn ? delegateFn(model, node) :
6956 tokens[i + 1].getValueFrom(model);
6957 }
6958
6959 return tokens.combinator(values);
6960 }
6961
6962 function processSinglePathBinding(name, tokens, node, model) {
6963 var delegateFn = tokens[3];
6964 var observer = delegateFn ? delegateFn(model, node, false) :
6965 new PathObserver(model, tokens[2]);
6966
6967 return tokens.isSimplePath ? observer :
6968 new ObserverTransform(observer, tokens.combinator);
6969 }
6970
6971 function processBinding(name, tokens, node, model) {
6972 if (tokens.onlyOneTime)
6973 return processOneTimeBinding(name, tokens, node, model);
6974
6975 if (tokens.hasOnePath)
6976 return processSinglePathBinding(name, tokens, node, model);
6977
6978 var observer = new CompoundObserver();
6979
6980 for (var i = 1; i < tokens.length; i += 4) {
6981 var oneTime = tokens[i];
6982 var delegateFn = tokens[i + 2];
6983
6984 if (delegateFn) {
6985 var value = delegateFn(model, node, oneTime);
6986 if (oneTime)
6987 observer.addPath(value)
6988 else
6989 observer.addObserver(value);
6990 continue;
6991 }
6992
6993 var path = tokens[i + 1];
6994 if (oneTime)
6995 observer.addPath(path.getValueFrom(model))
6996 else
6997 observer.addPath(model, path);
6998 }
6999
7000 return new ObserverTransform(observer, tokens.combinator);
7001 }
7002
7003 function processBindings(node, bindings, model, instanceBindings) {
7004 for (var i = 0; i < bindings.length; i += 2) {
7005 var name = bindings[i]
7006 var tokens = bindings[i + 1];
7007 var value = processBinding(name, tokens, node, model);
7008 var binding = node.bind(name, value, tokens.onlyOneTime);
7009 if (binding && instanceBindings)
7010 instanceBindings.push(binding);
7011 }
7012
7013 node.bindFinished();
7014 if (!bindings.isTemplate)
7015 return;
7016
7017 node.model_ = model;
7018 var iter = node.processBindingDirectives_(bindings);
7019 if (instanceBindings && iter)
7020 instanceBindings.push(iter);
7021 }
7022
7023 function parseWithDefault(el, name, prepareBindingFn) {
7024 var v = el.getAttribute(name);
7025 return parseMustaches(v == '' ? '{{}}' : v, name, el, prepareBindingFn);
7026 }
7027
7028 function parseAttributeBindings(element, prepareBindingFn) {
7029 assert(element);
7030
7031 var bindings = [];
7032 var ifFound = false;
7033 var bindFound = false;
7034
7035 for (var i = 0; i < element.attributes.length; i++) {
7036 var attr = element.attributes[i];
7037 var name = attr.name;
7038 var value = attr.value;
7039
7040 // Allow bindings expressed in attributes to be prefixed with underbars.
7041 // We do this to allow correct semantics for browsers that don't implement
7042 // <template> where certain attributes might trigger side-effects -- and
7043 // for IE which sanitizes certain attributes, disallowing mustache
7044 // replacements in their text.
7045 while (name[0] === '_') {
7046 name = name.substring(1);
7047 }
7048
7049 if (isTemplate(element) &&
7050 (name === IF || name === BIND || name === REPEAT)) {
7051 continue;
7052 }
7053
7054 var tokens = parseMustaches(value, name, element,
7055 prepareBindingFn);
7056 if (!tokens)
7057 continue;
7058
7059 bindings.push(name, tokens);
7060 }
7061
7062 if (isTemplate(element)) {
7063 bindings.isTemplate = true;
7064 bindings.if = parseWithDefault(element, IF, prepareBindingFn);
7065 bindings.bind = parseWithDefault(element, BIND, prepareBindingFn);
7066 bindings.repeat = parseWithDefault(element, REPEAT, prepareBindingFn);
7067
7068 if (bindings.if && !bindings.bind && !bindings.repeat)
7069 bindings.bind = parseMustaches('{{}}', BIND, element, prepareBindingFn);
7070 }
7071
7072 return bindings;
7073 }
7074
7075 function getBindings(node, prepareBindingFn) {
7076 if (node.nodeType === Node.ELEMENT_NODE)
7077 return parseAttributeBindings(node, prepareBindingFn);
7078
7079 if (node.nodeType === Node.TEXT_NODE) {
7080 var tokens = parseMustaches(node.data, 'textContent', node,
7081 prepareBindingFn);
7082 if (tokens)
7083 return ['textContent', tokens];
7084 }
7085
7086 return [];
7087 }
7088
7089 function cloneAndBindInstance(node, parent, stagingDocument, bindings, model,
7090 delegate,
7091 instanceBindings,
7092 instanceRecord) {
7093 var clone = parent.appendChild(stagingDocument.importNode(node, false));
7094
7095 var i = 0;
7096 for (var child = node.firstChild; child; child = child.nextSibling) {
7097 cloneAndBindInstance(child, clone, stagingDocument,
7098 bindings.children[i++],
7099 model,
7100 delegate,
7101 instanceBindings);
7102 }
7103
7104 if (bindings.isTemplate) {
7105 HTMLTemplateElement.decorate(clone, node);
7106 if (delegate)
7107 clone.setDelegate_(delegate);
7108 }
7109
7110 processBindings(clone, bindings, model, instanceBindings);
7111 return clone;
7112 }
7113
7114 function createInstanceBindingMap(node, prepareBindingFn) {
7115 var map = getBindings(node, prepareBindingFn);
7116 map.children = {};
7117 var index = 0;
7118 for (var child = node.firstChild; child; child = child.nextSibling) {
7119 map.children[index++] = createInstanceBindingMap(child, prepareBindingFn);
7120 }
7121
7122 return map;
7123 }
7124
7125 var contentUidCounter = 1;
7126
7127 // TODO(rafaelw): Setup a MutationObserver on content which clears the id
7128 // so that bindingMaps regenerate when the template.content changes.
7129 function getContentUid(content) {
7130 var id = content.id_;
7131 if (!id)
7132 id = content.id_ = contentUidCounter++;
7133 return id;
7134 }
7135
7136 // Each delegate is associated with a set of bindingMaps, one for each
7137 // content which may be used by a template. The intent is that each binding
7138 // delegate gets the opportunity to prepare the instance (via the prepare*
7139 // delegate calls) once across all uses.
7140 // TODO(rafaelw): Separate out the parse map from the binding map. In the
7141 // current implementation, if two delegates need a binding map for the same
7142 // content, the second will have to reparse.
7143 function getInstanceBindingMap(content, delegate_) {
7144 var contentId = getContentUid(content);
7145 if (delegate_) {
7146 var map = delegate_.bindingMaps[contentId];
7147 if (!map) {
7148 map = delegate_.bindingMaps[contentId] =
7149 createInstanceBindingMap(content, delegate_.prepareBinding) || [];
7150 }
7151 return map;
7152 }
7153
7154 var map = content.bindingMap_;
7155 if (!map) {
7156 map = content.bindingMap_ =
7157 createInstanceBindingMap(content, undefined) || [];
7158 }
7159 return map;
7160 }
7161
7162 Object.defineProperty(Node.prototype, 'templateInstance', {
7163 get: function() {
7164 var instance = this.templateInstance_;
7165 return instance ? instance :
7166 (this.parentNode ? this.parentNode.templateInstance : undefined);
7167 }
7168 });
7169
7170 var emptyInstance = document.createDocumentFragment();
7171 emptyInstance.bindings_ = [];
7172 emptyInstance.terminator_ = null;
7173
7174 function TemplateIterator(templateElement) {
7175 this.closed = false;
7176 this.templateElement_ = templateElement;
7177 this.instances = [];
7178 this.deps = undefined;
7179 this.iteratedValue = [];
7180 this.presentValue = undefined;
7181 this.arrayObserver = undefined;
7182 }
7183
7184 TemplateIterator.prototype = {
7185 closeDeps: function() {
7186 var deps = this.deps;
7187 if (deps) {
7188 if (deps.ifOneTime === false)
7189 deps.ifValue.close();
7190 if (deps.oneTime === false)
7191 deps.value.close();
7192 }
7193 },
7194
7195 updateDependencies: function(directives, model) {
7196 this.closeDeps();
7197
7198 var deps = this.deps = {};
7199 var template = this.templateElement_;
7200
7201 var ifValue = true;
7202 if (directives.if) {
7203 deps.hasIf = true;
7204 deps.ifOneTime = directives.if.onlyOneTime;
7205 deps.ifValue = processBinding(IF, directives.if, template, model);
7206
7207 ifValue = deps.ifValue;
7208
7209 // oneTime if & predicate is false. nothing else to do.
7210 if (deps.ifOneTime && !ifValue) {
7211 this.valueChanged();
7212 return;
7213 }
7214
7215 if (!deps.ifOneTime)
7216 ifValue = ifValue.open(this.updateIfValue, this);
7217 }
7218
7219 if (directives.repeat) {
7220 deps.repeat = true;
7221 deps.oneTime = directives.repeat.onlyOneTime;
7222 deps.value = processBinding(REPEAT, directives.repeat, template, model);
7223 } else {
7224 deps.repeat = false;
7225 deps.oneTime = directives.bind.onlyOneTime;
7226 deps.value = processBinding(BIND, directives.bind, template, model);
7227 }
7228
7229 var value = deps.value;
7230 if (!deps.oneTime)
7231 value = value.open(this.updateIteratedValue, this);
7232
7233 if (!ifValue) {
7234 this.valueChanged();
7235 return;
7236 }
7237
7238 this.updateValue(value);
7239 },
7240
7241 /**
7242 * Gets the updated value of the bind/repeat. This can potentially call
7243 * user code (if a bindingDelegate is set up) so we try to avoid it if we
7244 * already have the value in hand (from Observer.open).
7245 */
7246 getUpdatedValue: function() {
7247 var value = this.deps.value;
7248 if (!this.deps.oneTime)
7249 value = value.discardChanges();
7250 return value;
7251 },
7252
7253 updateIfValue: function(ifValue) {
7254 if (!ifValue) {
7255 this.valueChanged();
7256 return;
7257 }
7258
7259 this.updateValue(this.getUpdatedValue());
7260 },
7261
7262 updateIteratedValue: function(value) {
7263 if (this.deps.hasIf) {
7264 var ifValue = this.deps.ifValue;
7265 if (!this.deps.ifOneTime)
7266 ifValue = ifValue.discardChanges();
7267 if (!ifValue) {
7268 this.valueChanged();
7269 return;
7270 }
7271 }
7272
7273 this.updateValue(value);
7274 },
7275
7276 updateValue: function(value) {
7277 if (!this.deps.repeat)
7278 value = [value];
7279 var observe = this.deps.repeat &&
7280 !this.deps.oneTime &&
7281 Array.isArray(value);
7282 this.valueChanged(value, observe);
7283 },
7284
7285 valueChanged: function(value, observeValue) {
7286 if (!Array.isArray(value))
7287 value = [];
7288
7289 if (value === this.iteratedValue)
7290 return;
7291
7292 this.unobserve();
7293 this.presentValue = value;
7294 if (observeValue) {
7295 this.arrayObserver = new ArrayObserver(this.presentValue);
7296 this.arrayObserver.open(this.handleSplices, this);
7297 }
7298
7299 this.handleSplices(ArrayObserver.calculateSplices(this.presentValue,
7300 this.iteratedValue));
7301 },
7302
7303 getLastInstanceNode: function(index) {
7304 if (index == -1)
7305 return this.templateElement_;
7306 var instance = this.instances[index];
7307 var terminator = instance.terminator_;
7308 if (!terminator)
7309 return this.getLastInstanceNode(index - 1);
7310
7311 if (terminator.nodeType !== Node.ELEMENT_NODE ||
7312 this.templateElement_ === terminator) {
7313 return terminator;
7314 }
7315
7316 var subtemplateIterator = terminator.iterator_;
7317 if (!subtemplateIterator)
7318 return terminator;
7319
7320 return subtemplateIterator.getLastTemplateNode();
7321 },
7322
7323 getLastTemplateNode: function() {
7324 return this.getLastInstanceNode(this.instances.length - 1);
7325 },
7326
7327 insertInstanceAt: function(index, fragment) {
7328 var previousInstanceLast = this.getLastInstanceNode(index - 1);
7329 var parent = this.templateElement_.parentNode;
7330 this.instances.splice(index, 0, fragment);
7331
7332 parent.insertBefore(fragment, previousInstanceLast.nextSibling);
7333 },
7334
7335 extractInstanceAt: function(index) {
7336 var previousInstanceLast = this.getLastInstanceNode(index - 1);
7337 var lastNode = this.getLastInstanceNode(index);
7338 var parent = this.templateElement_.parentNode;
7339 var instance = this.instances.splice(index, 1)[0];
7340
7341 while (lastNode !== previousInstanceLast) {
7342 var node = previousInstanceLast.nextSibling;
7343 if (node == lastNode)
7344 lastNode = previousInstanceLast;
7345
7346 instance.appendChild(parent.removeChild(node));
7347 }
7348
7349 return instance;
7350 },
7351
7352 getDelegateFn: function(fn) {
7353 fn = fn && fn(this.templateElement_);
7354 return typeof fn === 'function' ? fn : null;
7355 },
7356
7357 handleSplices: function(splices) {
7358 if (this.closed || !splices.length)
7359 return;
7360
7361 var template = this.templateElement_;
7362
7363 if (!template.parentNode) {
7364 this.close();
7365 return;
7366 }
7367
7368 ArrayObserver.applySplices(this.iteratedValue, this.presentValue,
7369 splices);
7370
7371 var delegate = template.delegate_;
7372 if (this.instanceModelFn_ === undefined) {
7373 this.instanceModelFn_ =
7374 this.getDelegateFn(delegate && delegate.prepareInstanceModel);
7375 }
7376
7377 if (this.instancePositionChangedFn_ === undefined) {
7378 this.instancePositionChangedFn_ =
7379 this.getDelegateFn(delegate &&
7380 delegate.prepareInstancePositionChanged);
7381 }
7382
7383 // Instance Removals
7384 var instanceCache = new Map;
7385 var removeDelta = 0;
7386 for (var i = 0; i < splices.length; i++) {
7387 var splice = splices[i];
7388 var removed = splice.removed;
7389 for (var j = 0; j < removed.length; j++) {
7390 var model = removed[j];
7391 var instance = this.extractInstanceAt(splice.index + removeDelta);
7392 if (instance !== emptyInstance) {
7393 instanceCache.set(model, instance);
7394 }
7395 }
7396
7397 removeDelta -= splice.addedCount;
7398 }
7399
7400 // Instance Insertions
7401 for (var i = 0; i < splices.length; i++) {
7402 var splice = splices[i];
7403 var addIndex = splice.index;
7404 for (; addIndex < splice.index + splice.addedCount; addIndex++) {
7405 var model = this.iteratedValue[addIndex];
7406 var instance = instanceCache.get(model);
7407 if (instance) {
7408 instanceCache.delete(model);
7409 } else {
7410 if (this.instanceModelFn_) {
7411 model = this.instanceModelFn_(model);
7412 }
7413
7414 if (model === undefined) {
7415 instance = emptyInstance;
7416 } else {
7417 instance = template.createInstance(model, undefined, delegate);
7418 }
7419 }
7420
7421 this.insertInstanceAt(addIndex, instance);
7422 }
7423 }
7424
7425 instanceCache.forEach(function(instance) {
7426 this.closeInstanceBindings(instance);
7427 }, this);
7428
7429 if (this.instancePositionChangedFn_)
7430 this.reportInstancesMoved(splices);
7431 },
7432
7433 reportInstanceMoved: function(index) {
7434 var instance = this.instances[index];
7435 if (instance === emptyInstance)
7436 return;
7437
7438 this.instancePositionChangedFn_(instance.templateInstance_, index);
7439 },
7440
7441 reportInstancesMoved: function(splices) {
7442 var index = 0;
7443 var offset = 0;
7444 for (var i = 0; i < splices.length; i++) {
7445 var splice = splices[i];
7446 if (offset != 0) {
7447 while (index < splice.index) {
7448 this.reportInstanceMoved(index);
7449 index++;
7450 }
7451 } else {
7452 index = splice.index;
7453 }
7454
7455 while (index < splice.index + splice.addedCount) {
7456 this.reportInstanceMoved(index);
7457 index++;
7458 }
7459
7460 offset += splice.addedCount - splice.removed.length;
7461 }
7462
7463 if (offset == 0)
7464 return;
7465
7466 var length = this.instances.length;
7467 while (index < length) {
7468 this.reportInstanceMoved(index);
7469 index++;
7470 }
7471 },
7472
7473 closeInstanceBindings: function(instance) {
7474 var bindings = instance.bindings_;
7475 for (var i = 0; i < bindings.length; i++) {
7476 bindings[i].close();
7477 }
7478 },
7479
7480 unobserve: function() {
7481 if (!this.arrayObserver)
7482 return;
7483
7484 this.arrayObserver.close();
7485 this.arrayObserver = undefined;
7486 },
7487
7488 close: function() {
7489 if (this.closed)
7490 return;
7491 this.unobserve();
7492 for (var i = 0; i < this.instances.length; i++) {
7493 this.closeInstanceBindings(this.instances[i]);
7494 }
7495
7496 this.instances.length = 0;
7497 this.closeDeps();
7498 this.templateElement_.iterator_ = undefined;
7499 this.closed = true;
7500 }
7501 };
7502
7503 // Polyfill-specific API.
7504 HTMLTemplateElement.forAllTemplatesFrom_ = forAllTemplatesFrom;
7505 })(this);
7506
7507 (function(scope) {
7508 'use strict';
7509
7510 // feature detect for URL constructor
7511 var hasWorkingUrl = false;
7512 if (!scope.forceJURL) {
7513 try {
7514 var u = new URL('b', 'http://a');
7515 u.pathname = 'c%20d';
7516 hasWorkingUrl = u.href === 'http://a/c%20d';
7517 } catch(e) {}
7518 }
7519
7520 if (hasWorkingUrl)
7521 return;
7522
7523 var relative = Object.create(null);
7524 relative['ftp'] = 21;
7525 relative['file'] = 0;
7526 relative['gopher'] = 70;
7527 relative['http'] = 80;
7528 relative['https'] = 443;
7529 relative['ws'] = 80;
7530 relative['wss'] = 443;
7531
7532 var relativePathDotMapping = Object.create(null);
7533 relativePathDotMapping['%2e'] = '.';
7534 relativePathDotMapping['.%2e'] = '..';
7535 relativePathDotMapping['%2e.'] = '..';
7536 relativePathDotMapping['%2e%2e'] = '..';
7537
7538 function isRelativeScheme(scheme) {
7539 return relative[scheme] !== undefined;
7540 }
7541
7542 function invalid() {
7543 clear.call(this);
7544 this._isInvalid = true;
7545 }
7546
7547 function IDNAToASCII(h) {
7548 if ('' == h) {
7549 invalid.call(this)
7550 }
7551 // XXX
7552 return h.toLowerCase()
7553 }
7554
7555 function percentEscape(c) {
7556 var unicode = c.charCodeAt(0);
7557 if (unicode > 0x20 &&
7558 unicode < 0x7F &&
7559 // " # < > ? `
7560 [0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) == -1
7561 ) {
7562 return c;
7563 }
7564 return encodeURIComponent(c);
7565 }
7566
7567 function percentEscapeQuery(c) {
7568 // XXX This actually needs to encode c using encoding and then
7569 // convert the bytes one-by-one.
7570
7571 var unicode = c.charCodeAt(0);
7572 if (unicode > 0x20 &&
7573 unicode < 0x7F &&
7574 // " # < > ` (do not escape '?')
7575 [0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) == -1
7576 ) {
7577 return c;
7578 }
7579 return encodeURIComponent(c);
7580 }
7581
7582 var EOF = undefined,
7583 ALPHA = /[a-zA-Z]/,
7584 ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/;
7585
7586 function parse(input, stateOverride, base) {
7587 function err(message) {
7588 errors.push(message)
7589 }
7590
7591 var state = stateOverride || 'scheme start',
7592 cursor = 0,
7593 buffer = '',
7594 seenAt = false,
7595 seenBracket = false,
7596 errors = [];
7597
7598 loop: while ((input[cursor - 1] != EOF || cursor == 0) && !this._isInvalid) {
7599 var c = input[cursor];
7600 switch (state) {
7601 case 'scheme start':
7602 if (c && ALPHA.test(c)) {
7603 buffer += c.toLowerCase(); // ASCII-safe
7604 state = 'scheme';
7605 } else if (!stateOverride) {
7606 buffer = '';
7607 state = 'no scheme';
7608 continue;
7609 } else {
7610 err('Invalid scheme.');
7611 break loop;
7612 }
7613 break;
7614
7615 case 'scheme':
7616 if (c && ALPHANUMERIC.test(c)) {
7617 buffer += c.toLowerCase(); // ASCII-safe
7618 } else if (':' == c) {
7619 this._scheme = buffer;
7620 buffer = '';
7621 if (stateOverride) {
7622 break loop;
7623 }
7624 if (isRelativeScheme(this._scheme)) {
7625 this._isRelative = true;
7626 }
7627 if ('file' == this._scheme) {
7628 state = 'relative';
7629 } else if (this._isRelative && base && base._scheme == this._scheme) {
7630 state = 'relative or authority';
7631 } else if (this._isRelative) {
7632 state = 'authority first slash';
7633 } else {
7634 state = 'scheme data';
7635 }
7636 } else if (!stateOverride) {
7637 buffer = '';
7638 cursor = 0;
7639 state = 'no scheme';
7640 continue;
7641 } else if (EOF == c) {
7642 break loop;
7643 } else {
7644 err('Code point not allowed in scheme: ' + c)
7645 break loop;
7646 }
7647 break;
7648
7649 case 'scheme data':
7650 if ('?' == c) {
7651 query = '?';
7652 state = 'query';
7653 } else if ('#' == c) {
7654 this._fragment = '#';
7655 state = 'fragment';
7656 } else {
7657 // XXX error handling
7658 if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
7659 this._schemeData += percentEscape(c);
7660 }
7661 }
7662 break;
7663
7664 case 'no scheme':
7665 if (!base || !(isRelativeScheme(base._scheme))) {
7666 err('Missing scheme.');
7667 invalid.call(this);
7668 } else {
7669 state = 'relative';
7670 continue;
7671 }
7672 break;
7673
7674 case 'relative or authority':
7675 if ('/' == c && '/' == input[cursor+1]) {
7676 state = 'authority ignore slashes';
7677 } else {
7678 err('Expected /, got: ' + c);
7679 state = 'relative';
7680 continue
7681 }
7682 break;
7683
7684 case 'relative':
7685 this._isRelative = true;
7686 if ('file' != this._scheme)
7687 this._scheme = base._scheme;
7688 if (EOF == c) {
7689 this._host = base._host;
7690 this._port = base._port;
7691 this._path = base._path.slice();
7692 this._query = base._query;
7693 break loop;
7694 } else if ('/' == c || '\\' == c) {
7695 if ('\\' == c)
7696 err('\\ is an invalid code point.');
7697 state = 'relative slash';
7698 } else if ('?' == c) {
7699 this._host = base._host;
7700 this._port = base._port;
7701 this._path = base._path.slice();
7702 this._query = '?';
7703 state = 'query';
7704 } else if ('#' == c) {
7705 this._host = base._host;
7706 this._port = base._port;
7707 this._path = base._path.slice();
7708 this._query = base._query;
7709 this._fragment = '#';
7710 state = 'fragment';
7711 } else {
7712 var nextC = input[cursor+1]
7713 var nextNextC = input[cursor+2]
7714 if (
7715 'file' != this._scheme || !ALPHA.test(c) ||
7716 (nextC != ':' && nextC != '|') ||
7717 (EOF != nextNextC && '/' != nextNextC && '\\' != nextNextC && '?' != nextNextC && '#' != nextNextC)) {
7718 this._host = base._host;
7719 this._port = base._port;
7720 this._path = base._path.slice();
7721 this._path.pop();
7722 }
7723 state = 'relative path';
7724 continue;
7725 }
7726 break;
7727
7728 case 'relative slash':
7729 if ('/' == c || '\\' == c) {
7730 if ('\\' == c) {
7731 err('\\ is an invalid code point.');
7732 }
7733 if ('file' == this._scheme) {
7734 state = 'file host';
7735 } else {
7736 state = 'authority ignore slashes';
7737 }
7738 } else {
7739 if ('file' != this._scheme) {
7740 this._host = base._host;
7741 this._port = base._port;
7742 }
7743 state = 'relative path';
7744 continue;
7745 }
7746 break;
7747
7748 case 'authority first slash':
7749 if ('/' == c) {
7750 state = 'authority second slash';
7751 } else {
7752 err("Expected '/', got: " + c);
7753 state = 'authority ignore slashes';
7754 continue;
7755 }
7756 break;
7757
7758 case 'authority second slash':
7759 state = 'authority ignore slashes';
7760 if ('/' != c) {
7761 err("Expected '/', got: " + c);
7762 continue;
7763 }
7764 break;
7765
7766 case 'authority ignore slashes':
7767 if ('/' != c && '\\' != c) {
7768 state = 'authority';
7769 continue;
7770 } else {
7771 err('Expected authority, got: ' + c);
7772 }
7773 break;
7774
7775 case 'authority':
7776 if ('@' == c) {
7777 if (seenAt) {
7778 err('@ already seen.');
7779 buffer += '%40';
7780 }
7781 seenAt = true;
7782 for (var i = 0; i < buffer.length; i++) {
7783 var cp = buffer[i];
7784 if ('\t' == cp || '\n' == cp || '\r' == cp) {
7785 err('Invalid whitespace in authority.');
7786 continue;
7787 }
7788 // XXX check URL code points
7789 if (':' == cp && null === this._password) {
7790 this._password = '';
7791 continue;
7792 }
7793 var tempC = percentEscape(cp);
7794 (null !== this._password) ? this._password += tempC : this._userna me += tempC;
7795 }
7796 buffer = '';
7797 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
7798 cursor -= buffer.length;
7799 buffer = '';
7800 state = 'host';
7801 continue;
7802 } else {
7803 buffer += c;
7804 }
7805 break;
7806
7807 case 'file host':
7808 if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
7809 if (buffer.length == 2 && ALPHA.test(buffer[0]) && (buffer[1] == ':' || buffer[1] == '|')) {
7810 state = 'relative path';
7811 } else if (buffer.length == 0) {
7812 state = 'relative path start';
7813 } else {
7814 this._host = IDNAToASCII.call(this, buffer);
7815 buffer = '';
7816 state = 'relative path start';
7817 }
7818 continue;
7819 } else if ('\t' == c || '\n' == c || '\r' == c) {
7820 err('Invalid whitespace in file host.');
7821 } else {
7822 buffer += c;
7823 }
7824 break;
7825
7826 case 'host':
7827 case 'hostname':
7828 if (':' == c && !seenBracket) {
7829 // XXX host parsing
7830 this._host = IDNAToASCII.call(this, buffer);
7831 buffer = '';
7832 state = 'port';
7833 if ('hostname' == stateOverride) {
7834 break loop;
7835 }
7836 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
7837 this._host = IDNAToASCII.call(this, buffer);
7838 buffer = '';
7839 state = 'relative path start';
7840 if (stateOverride) {
7841 break loop;
7842 }
7843 continue;
7844 } else if ('\t' != c && '\n' != c && '\r' != c) {
7845 if ('[' == c) {
7846 seenBracket = true;
7847 } else if (']' == c) {
7848 seenBracket = false;
7849 }
7850 buffer += c;
7851 } else {
7852 err('Invalid code point in host/hostname: ' + c);
7853 }
7854 break;
7855
7856 case 'port':
7857 if (/[0-9]/.test(c)) {
7858 buffer += c;
7859 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c | | stateOverride) {
7860 if ('' != buffer) {
7861 var temp = parseInt(buffer, 10);
7862 if (temp != relative[this._scheme]) {
7863 this._port = temp + '';
7864 }
7865 buffer = '';
7866 }
7867 if (stateOverride) {
7868 break loop;
7869 }
7870 state = 'relative path start';
7871 continue;
7872 } else if ('\t' == c || '\n' == c || '\r' == c) {
7873 err('Invalid code point in port: ' + c);
7874 } else {
7875 invalid.call(this);
7876 }
7877 break;
7878
7879 case 'relative path start':
7880 if ('\\' == c)
7881 err("'\\' not allowed in path.");
7882 state = 'relative path';
7883 if ('/' != c && '\\' != c) {
7884 continue;
7885 }
7886 break;
7887
7888 case 'relative path':
7889 if (EOF == c || '/' == c || '\\' == c || (!stateOverride && ('?' == c || '#' == c))) {
7890 if ('\\' == c) {
7891 err('\\ not allowed in relative path.');
7892 }
7893 var tmp;
7894 if (tmp = relativePathDotMapping[buffer.toLowerCase()]) {
7895 buffer = tmp;
7896 }
7897 if ('..' == buffer) {
7898 this._path.pop();
7899 if ('/' != c && '\\' != c) {
7900 this._path.push('');
7901 }
7902 } else if ('.' == buffer && '/' != c && '\\' != c) {
7903 this._path.push('');
7904 } else if ('.' != buffer) {
7905 if ('file' == this._scheme && this._path.length == 0 && buffer.len gth == 2 && ALPHA.test(buffer[0]) && buffer[1] == '|') {
7906 buffer = buffer[0] + ':';
7907 }
7908 this._path.push(buffer);
7909 }
7910 buffer = '';
7911 if ('?' == c) {
7912 this._query = '?';
7913 state = 'query';
7914 } else if ('#' == c) {
7915 this._fragment = '#';
7916 state = 'fragment';
7917 }
7918 } else if ('\t' != c && '\n' != c && '\r' != c) {
7919 buffer += percentEscape(c);
7920 }
7921 break;
7922
7923 case 'query':
7924 if (!stateOverride && '#' == c) {
7925 this._fragment = '#';
7926 state = 'fragment';
7927 } else if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
7928 this._query += percentEscapeQuery(c);
7929 }
7930 break;
7931
7932 case 'fragment':
7933 if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
7934 this._fragment += c;
7935 }
7936 break;
7937 }
7938
7939 cursor++;
7940 }
7941 }
7942
7943 function clear() {
7944 this._scheme = '';
7945 this._schemeData = '';
7946 this._username = '';
7947 this._password = null;
7948 this._host = '';
7949 this._port = '';
7950 this._path = [];
7951 this._query = '';
7952 this._fragment = '';
7953 this._isInvalid = false;
7954 this._isRelative = false;
7955 }
7956
7957 // Does not process domain names or IP addresses.
7958 // Does not handle encoding for the query parameter.
7959 function jURL(url, base /* , encoding */) {
7960 if (base !== undefined && !(base instanceof jURL))
7961 base = new jURL(String(base));
7962
7963 this._url = url;
7964 clear.call(this);
7965
7966 var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, '');
7967 // encoding = encoding || 'utf-8'
7968
7969 parse.call(this, input, null, base);
7970 }
7971
7972 jURL.prototype = {
7973 get href() {
7974 if (this._isInvalid)
7975 return this._url;
7976
7977 var authority = '';
7978 if ('' != this._username || null != this._password) {
7979 authority = this._username +
7980 (null != this._password ? ':' + this._password : '') + '@';
7981 }
7982
7983 return this.protocol +
7984 (this._isRelative ? '//' + authority + this.host : '') +
7985 this.pathname + this._query + this._fragment;
7986 },
7987 set href(href) {
7988 clear.call(this);
7989 parse.call(this, href);
7990 },
7991
7992 get protocol() {
7993 return this._scheme + ':';
7994 },
7995 set protocol(protocol) {
7996 if (this._isInvalid)
7997 return;
7998 parse.call(this, protocol + ':', 'scheme start');
7999 },
8000
8001 get host() {
8002 return this._isInvalid ? '' : this._port ?
8003 this._host + ':' + this._port : this._host;
8004 },
8005 set host(host) {
8006 if (this._isInvalid || !this._isRelative)
8007 return;
8008 parse.call(this, host, 'host');
8009 },
8010
8011 get hostname() {
8012 return this._host;
8013 },
8014 set hostname(hostname) {
8015 if (this._isInvalid || !this._isRelative)
8016 return;
8017 parse.call(this, hostname, 'hostname');
8018 },
8019
8020 get port() {
8021 return this._port;
8022 },
8023 set port(port) {
8024 if (this._isInvalid || !this._isRelative)
8025 return;
8026 parse.call(this, port, 'port');
8027 },
8028
8029 get pathname() {
8030 return this._isInvalid ? '' : this._isRelative ?
8031 '/' + this._path.join('/') : this._schemeData;
8032 },
8033 set pathname(pathname) {
8034 if (this._isInvalid || !this._isRelative)
8035 return;
8036 this._path = [];
8037 parse.call(this, pathname, 'relative path start');
8038 },
8039
8040 get search() {
8041 return this._isInvalid || !this._query || '?' == this._query ?
8042 '' : this._query;
8043 },
8044 set search(search) {
8045 if (this._isInvalid || !this._isRelative)
8046 return;
8047 this._query = '?';
8048 if ('?' == search[0])
8049 search = search.slice(1);
8050 parse.call(this, search, 'query');
8051 },
8052
8053 get hash() {
8054 return this._isInvalid || !this._fragment || '#' == this._fragment ?
8055 '' : this._fragment;
8056 },
8057 set hash(hash) {
8058 if (this._isInvalid)
8059 return;
8060 this._fragment = '#';
8061 if ('#' == hash[0])
8062 hash = hash.slice(1);
8063 parse.call(this, hash, 'fragment');
8064 },
8065
8066 get origin() {
8067 var host;
8068 if (this._isInvalid || !this._scheme) {
8069 return '';
8070 }
8071 // javascript: Gecko returns String(""), WebKit/Blink String("null")
8072 // Gecko throws error for "data://"
8073 // data: Gecko returns "", Blink returns "data://", WebKit returns "null"
8074 // Gecko returns String("") for file: mailto:
8075 // WebKit/Blink returns String("SCHEME://") for file: mailto:
8076 switch (this._scheme) {
8077 case 'data':
8078 case 'file':
8079 case 'javascript':
8080 case 'mailto':
8081 return 'null';
8082 }
8083 host = this.host;
8084 if (!host) {
8085 return '';
8086 }
8087 return this._scheme + '://' + host;
8088 }
8089 };
8090
8091 // Copy over the static methods
8092 var OriginalURL = scope.URL;
8093 if (OriginalURL) {
8094 jURL.createObjectURL = function(blob) {
8095 // IE extension allows a second optional options argument.
8096 // http://msdn.microsoft.com/en-us/library/ie/hh772302(v=vs.85).aspx
8097 return OriginalURL.createObjectURL.apply(OriginalURL, arguments);
8098 };
8099 jURL.revokeObjectURL = function(url) {
8100 OriginalURL.revokeObjectURL(url);
8101 };
8102 }
8103
8104 scope.URL = jURL;
8105
8106 })(this);
8107
8108 (function(scope) {
8109
8110 var iterations = 0;
8111 var callbacks = [];
8112 var twiddle = document.createTextNode('');
8113
8114 function endOfMicrotask(callback) {
8115 twiddle.textContent = iterations++;
8116 callbacks.push(callback);
8117 }
8118
8119 function atEndOfMicrotask() {
8120 while (callbacks.length) {
8121 callbacks.shift()();
8122 }
8123 }
8124
8125 new (window.MutationObserver || JsMutationObserver)(atEndOfMicrotask)
8126 .observe(twiddle, {characterData: true})
8127 ;
8128
8129 // exports
8130 scope.endOfMicrotask = endOfMicrotask;
8131 // bc
8132 Platform.endOfMicrotask = endOfMicrotask;
8133
8134 })(Polymer);
8135
8136
8137 (function(scope) {
8138
8139 /**
8140 * @class Polymer
8141 */
8142
8143 // imports
8144 var endOfMicrotask = scope.endOfMicrotask;
8145
8146 // logging
8147 var log = window.WebComponents ? WebComponents.flags.log : {};
8148
8149 // inject style sheet
8150 var style = document.createElement('style');
8151 style.textContent = 'template {display: none !important;} /* injected by platfor m.js */';
8152 var head = document.querySelector('head');
8153 head.insertBefore(style, head.firstChild);
8154
8155
8156 /**
8157 * Force any pending data changes to be observed before
8158 * the next task. Data changes are processed asynchronously but are guaranteed
8159 * to be processed, for example, before painting. This method should rarely be
8160 * needed. It does nothing when Object.observe is available;
8161 * when Object.observe is not available, Polymer automatically flushes data
8162 * changes approximately every 1/10 second.
8163 * Therefore, `flush` should only be used when a data mutation should be
8164 * observed sooner than this.
8165 *
8166 * @method flush
8167 */
8168 // flush (with logging)
8169 var flushing;
8170 function flush() {
8171 if (!flushing) {
8172 flushing = true;
8173 endOfMicrotask(function() {
8174 flushing = false;
8175 log.data && console.group('flush');
8176 Platform.performMicrotaskCheckpoint();
8177 log.data && console.groupEnd();
8178 });
8179 }
8180 };
8181
8182 // polling dirty checker
8183 // flush periodically if platform does not have object observe.
8184 if (!Observer.hasObjectObserve) {
8185 var FLUSH_POLL_INTERVAL = 125;
8186 window.addEventListener('WebComponentsReady', function() {
8187 flush();
8188 // watch document visiblity to toggle dirty-checking
8189 var visibilityHandler = function() {
8190 // only flush if the page is visibile
8191 if (document.visibilityState === 'hidden') {
8192 if (scope.flushPoll) {
8193 clearInterval(scope.flushPoll);
8194 }
8195 } else {
8196 scope.flushPoll = setInterval(flush, FLUSH_POLL_INTERVAL);
8197 }
8198 };
8199 if (typeof document.visibilityState === 'string') {
8200 document.addEventListener('visibilitychange', visibilityHandler);
8201 }
8202 visibilityHandler();
8203 });
8204 } else {
8205 // make flush a no-op when we have Object.observe
8206 flush = function() {};
8207 }
8208
8209 if (window.CustomElements && !CustomElements.useNative) {
8210 var originalImportNode = Document.prototype.importNode;
8211 Document.prototype.importNode = function(node, deep) {
8212 var imported = originalImportNode.call(this, node, deep);
8213 CustomElements.upgradeAll(imported);
8214 return imported;
8215 };
8216 }
8217
8218 // exports
8219 scope.flush = flush;
8220 // bc
8221 Platform.flush = flush;
8222
8223 })(window.Polymer);
8224
8225
8226 (function(scope) {
8227
8228 var urlResolver = {
8229 resolveDom: function(root, url) {
8230 url = url || baseUrl(root);
8231 this.resolveAttributes(root, url);
8232 this.resolveStyles(root, url);
8233 // handle template.content
8234 var templates = root.querySelectorAll('template');
8235 if (templates) {
8236 for (var i = 0, l = templates.length, t; (i < l) && (t = templates[i]); i+ +) {
8237 if (t.content) {
8238 this.resolveDom(t.content, url);
8239 }
8240 }
8241 }
8242 },
8243 resolveTemplate: function(template) {
8244 this.resolveDom(template.content, baseUrl(template));
8245 },
8246 resolveStyles: function(root, url) {
8247 var styles = root.querySelectorAll('style');
8248 if (styles) {
8249 for (var i = 0, l = styles.length, s; (i < l) && (s = styles[i]); i++) {
8250 this.resolveStyle(s, url);
8251 }
8252 }
8253 },
8254 resolveStyle: function(style, url) {
8255 url = url || baseUrl(style);
8256 style.textContent = this.resolveCssText(style.textContent, url);
8257 },
8258 resolveCssText: function(cssText, baseUrl, keepAbsolute) {
8259 cssText = replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_URL_REGEX P);
8260 return replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_IMPORT_REGEX P);
8261 },
8262 resolveAttributes: function(root, url) {
8263 if (root.hasAttributes && root.hasAttributes()) {
8264 this.resolveElementAttributes(root, url);
8265 }
8266 // search for attributes that host urls
8267 var nodes = root && root.querySelectorAll(URL_ATTRS_SELECTOR);
8268 if (nodes) {
8269 for (var i = 0, l = nodes.length, n; (i < l) && (n = nodes[i]); i++) {
8270 this.resolveElementAttributes(n, url);
8271 }
8272 }
8273 },
8274 resolveElementAttributes: function(node, url) {
8275 url = url || baseUrl(node);
8276 URL_ATTRS.forEach(function(v) {
8277 var attr = node.attributes[v];
8278 var value = attr && attr.value;
8279 var replacement;
8280 if (value && value.search(URL_TEMPLATE_SEARCH) < 0) {
8281 if (v === 'style') {
8282 replacement = replaceUrlsInCssText(value, url, false, CSS_URL_REGEXP);
8283 } else {
8284 replacement = resolveRelativeUrl(url, value);
8285 }
8286 attr.value = replacement;
8287 }
8288 });
8289 }
8290 };
8291
8292 var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g;
8293 var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g;
8294 var URL_ATTRS = ['href', 'src', 'action', 'style', 'url'];
8295 var URL_ATTRS_SELECTOR = '[' + URL_ATTRS.join('],[') + ']';
8296 var URL_TEMPLATE_SEARCH = '{{.*}}';
8297 var URL_HASH = '#';
8298
8299 function baseUrl(node) {
8300 var u = new URL(node.ownerDocument.baseURI);
8301 u.search = '';
8302 u.hash = '';
8303 return u;
8304 }
8305
8306 function replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, regexp) {
8307 return cssText.replace(regexp, function(m, pre, url, post) {
8308 var urlPath = url.replace(/["']/g, '');
8309 urlPath = resolveRelativeUrl(baseUrl, urlPath, keepAbsolute);
8310 return pre + '\'' + urlPath + '\'' + post;
8311 });
8312 }
8313
8314 function resolveRelativeUrl(baseUrl, url, keepAbsolute) {
8315 // do not resolve '/' absolute urls
8316 if (url && url[0] === '/') {
8317 return url;
8318 }
8319 // do not resolve '#' links, they are used for routing
8320 if (url && url[0] === '#') {
8321 return url;
8322 }
8323 var u = new URL(url, baseUrl);
8324 return keepAbsolute ? u.href : makeDocumentRelPath(u.href);
8325 }
8326
8327 function makeDocumentRelPath(url) {
8328 var root = baseUrl(document.documentElement);
8329 var u = new URL(url, root);
8330 if (u.host === root.host && u.port === root.port &&
8331 u.protocol === root.protocol) {
8332 return makeRelPath(root, u);
8333 } else {
8334 return url;
8335 }
8336 }
8337
8338 // make a relative path from source to target
8339 function makeRelPath(sourceUrl, targetUrl) {
8340 var source = sourceUrl.pathname;
8341 var target = targetUrl.pathname;
8342 var s = source.split('/');
8343 var t = target.split('/');
8344 while (s.length && s[0] === t[0]){
8345 s.shift();
8346 t.shift();
8347 }
8348 for (var i = 0, l = s.length - 1; i < l; i++) {
8349 t.unshift('..');
8350 }
8351 // empty '#' is discarded but we need to preserve it.
8352 var hash = (targetUrl.href.slice(-1) === URL_HASH) ? URL_HASH : targetUrl.hash ;
8353 return t.join('/') + targetUrl.search + hash;
8354 }
8355
8356 // exports
8357 scope.urlResolver = urlResolver;
8358
8359 })(Polymer);
8360
8361 (function(scope) {
8362 var endOfMicrotask = Polymer.endOfMicrotask;
8363
8364 // Generic url loader
8365 function Loader(regex) {
8366 this.cache = Object.create(null);
8367 this.map = Object.create(null);
8368 this.requests = 0;
8369 this.regex = regex;
8370 }
8371 Loader.prototype = {
8372
8373 // TODO(dfreedm): there may be a better factoring here
8374 // extract absolute urls from the text (full of relative urls)
8375 extractUrls: function(text, base) {
8376 var matches = [];
8377 var matched, u;
8378 while ((matched = this.regex.exec(text))) {
8379 u = new URL(matched[1], base);
8380 matches.push({matched: matched[0], url: u.href});
8381 }
8382 return matches;
8383 },
8384 // take a text blob, a root url, and a callback and load all the urls found within the text
8385 // returns a map of absolute url to text
8386 process: function(text, root, callback) {
8387 var matches = this.extractUrls(text, root);
8388
8389 // every call to process returns all the text this loader has ever receive d
8390 var done = callback.bind(null, this.map);
8391 this.fetch(matches, done);
8392 },
8393 // build a mapping of url -> text from matches
8394 fetch: function(matches, callback) {
8395 var inflight = matches.length;
8396
8397 // return early if there is no fetching to be done
8398 if (!inflight) {
8399 return callback();
8400 }
8401
8402 // wait for all subrequests to return
8403 var done = function() {
8404 if (--inflight === 0) {
8405 callback();
8406 }
8407 };
8408
8409 // start fetching all subrequests
8410 var m, req, url;
8411 for (var i = 0; i < inflight; i++) {
8412 m = matches[i];
8413 url = m.url;
8414 req = this.cache[url];
8415 // if this url has already been requested, skip requesting it again
8416 if (!req) {
8417 req = this.xhr(url);
8418 req.match = m;
8419 this.cache[url] = req;
8420 }
8421 // wait for the request to process its subrequests
8422 req.wait(done);
8423 }
8424 },
8425 handleXhr: function(request) {
8426 var match = request.match;
8427 var url = match.url;
8428
8429 // handle errors with an empty string
8430 var response = request.response || request.responseText || '';
8431 this.map[url] = response;
8432 this.fetch(this.extractUrls(response, url), request.resolve);
8433 },
8434 xhr: function(url) {
8435 this.requests++;
8436 var request = new XMLHttpRequest();
8437 request.open('GET', url, true);
8438 request.send();
8439 request.onerror = request.onload = this.handleXhr.bind(this, request);
8440
8441 // queue of tasks to run after XHR returns
8442 request.pending = [];
8443 request.resolve = function() {
8444 var pending = request.pending;
8445 for(var i = 0; i < pending.length; i++) {
8446 pending[i]();
8447 }
8448 request.pending = null;
8449 };
8450
8451 // if we have already resolved, pending is null, async call the callback
8452 request.wait = function(fn) {
8453 if (request.pending) {
8454 request.pending.push(fn);
8455 } else {
8456 endOfMicrotask(fn);
8457 }
8458 };
8459
8460 return request;
8461 }
8462 };
8463
8464 scope.Loader = Loader;
8465 })(Polymer);
8466
8467 (function(scope) {
8468
8469 var urlResolver = scope.urlResolver;
8470 var Loader = scope.Loader;
8471
8472 function StyleResolver() {
8473 this.loader = new Loader(this.regex);
8474 }
8475 StyleResolver.prototype = {
8476 regex: /@import\s+(?:url)?["'\(]*([^'"\)]*)['"\)]*;/g,
8477 // Recursively replace @imports with the text at that url
8478 resolve: function(text, url, callback) {
8479 var done = function(map) {
8480 callback(this.flatten(text, url, map));
8481 }.bind(this);
8482 this.loader.process(text, url, done);
8483 },
8484 // resolve the textContent of a style node
8485 resolveNode: function(style, url, callback) {
8486 var text = style.textContent;
8487 var done = function(text) {
8488 style.textContent = text;
8489 callback(style);
8490 };
8491 this.resolve(text, url, done);
8492 },
8493 // flatten all the @imports to text
8494 flatten: function(text, base, map) {
8495 var matches = this.loader.extractUrls(text, base);
8496 var match, url, intermediate;
8497 for (var i = 0; i < matches.length; i++) {
8498 match = matches[i];
8499 url = match.url;
8500 // resolve any css text to be relative to the importer, keep absolute url
8501 intermediate = urlResolver.resolveCssText(map[url], url, true);
8502 // flatten intermediate @imports
8503 intermediate = this.flatten(intermediate, base, map);
8504 text = text.replace(match.matched, intermediate);
8505 }
8506 return text;
8507 },
8508 loadStyles: function(styles, base, callback) {
8509 var loaded=0, l = styles.length;
8510 // called in the context of the style
8511 function loadedStyle(style) {
8512 loaded++;
8513 if (loaded === l && callback) {
8514 callback();
8515 }
8516 }
8517 for (var i=0, s; (i<l) && (s=styles[i]); i++) {
8518 this.resolveNode(s, base, loadedStyle);
8519 }
8520 }
8521 };
8522
8523 var styleResolver = new StyleResolver();
8524
8525 // exports
8526 scope.styleResolver = styleResolver;
8527
8528 })(Polymer);
8529
8530 (function(scope) {
8531
8532 // copy own properties from 'api' to 'prototype, with name hinting for 'super'
8533 function extend(prototype, api) {
8534 if (prototype && api) {
8535 // use only own properties of 'api'
8536 Object.getOwnPropertyNames(api).forEach(function(n) {
8537 // acquire property descriptor
8538 var pd = Object.getOwnPropertyDescriptor(api, n);
8539 if (pd) {
8540 // clone property via descriptor
8541 Object.defineProperty(prototype, n, pd);
8542 // cache name-of-method for 'super' engine
8543 if (typeof pd.value == 'function') {
8544 // hint the 'super' engine
8545 pd.value.nom = n;
8546 }
8547 }
8548 });
8549 }
8550 return prototype;
8551 }
8552
8553
8554 // mixin
8555
8556 // copy all properties from inProps (et al) to inObj
8557 function mixin(inObj/*, inProps, inMoreProps, ...*/) {
8558 var obj = inObj || {};
8559 for (var i = 1; i < arguments.length; i++) {
8560 var p = arguments[i];
8561 try {
8562 for (var n in p) {
8563 copyProperty(n, p, obj);
8564 }
8565 } catch(x) {
8566 }
8567 }
8568 return obj;
8569 }
8570
8571 // copy property inName from inSource object to inTarget object
8572 function copyProperty(inName, inSource, inTarget) {
8573 var pd = getPropertyDescriptor(inSource, inName);
8574 Object.defineProperty(inTarget, inName, pd);
8575 }
8576
8577 // get property descriptor for inName on inObject, even if
8578 // inName exists on some link in inObject's prototype chain
8579 function getPropertyDescriptor(inObject, inName) {
8580 if (inObject) {
8581 var pd = Object.getOwnPropertyDescriptor(inObject, inName);
8582 return pd || getPropertyDescriptor(Object.getPrototypeOf(inObject), inName );
8583 }
8584 }
8585
8586 // exports
8587
8588 scope.extend = extend;
8589 scope.mixin = mixin;
8590
8591 // for bc
8592 Platform.mixin = mixin;
8593
8594 })(Polymer);
8595
8596 (function(scope) {
8597
8598 // usage
8599
8600 // invoke cb.call(this) in 100ms, unless the job is re-registered,
8601 // which resets the timer
8602 //
8603 // this.myJob = this.job(this.myJob, cb, 100)
8604 //
8605 // returns a job handle which can be used to re-register a job
8606
8607 var Job = function(inContext) {
8608 this.context = inContext;
8609 this.boundComplete = this.complete.bind(this)
8610 };
8611 Job.prototype = {
8612 go: function(callback, wait) {
8613 this.callback = callback;
8614 var h;
8615 if (!wait) {
8616 h = requestAnimationFrame(this.boundComplete);
8617 this.handle = function() {
8618 cancelAnimationFrame(h);
8619 }
8620 } else {
8621 h = setTimeout(this.boundComplete, wait);
8622 this.handle = function() {
8623 clearTimeout(h);
8624 }
8625 }
8626 },
8627 stop: function() {
8628 if (this.handle) {
8629 this.handle();
8630 this.handle = null;
8631 }
8632 },
8633 complete: function() {
8634 if (this.handle) {
8635 this.stop();
8636 this.callback.call(this.context);
8637 }
8638 }
8639 };
8640
8641 function job(job, callback, wait) {
8642 if (job) {
8643 job.stop();
8644 } else {
8645 job = new Job(this);
8646 }
8647 job.go(callback, wait);
8648 return job;
8649 }
8650
8651 // exports
8652
8653 scope.job = job;
8654
8655 })(Polymer);
8656
8657 (function(scope) {
8658
8659 // dom polyfill, additions, and utility methods
8660
8661 var registry = {};
8662
8663 HTMLElement.register = function(tag, prototype) {
8664 registry[tag] = prototype;
8665 };
8666
8667 // get prototype mapped to node <tag>
8668 HTMLElement.getPrototypeForTag = function(tag) {
8669 var prototype = !tag ? HTMLElement.prototype : registry[tag];
8670 // TODO(sjmiles): creating <tag> is likely to have wasteful side-effects
8671 return prototype || Object.getPrototypeOf(document.createElement(tag));
8672 };
8673
8674 // we have to flag propagation stoppage for the event dispatcher
8675 var originalStopPropagation = Event.prototype.stopPropagation;
8676 Event.prototype.stopPropagation = function() {
8677 this.cancelBubble = true;
8678 originalStopPropagation.apply(this, arguments);
8679 };
8680
8681
8682 // polyfill DOMTokenList
8683 // * add/remove: allow these methods to take multiple classNames
8684 // * toggle: add a 2nd argument which forces the given state rather
8685 // than toggling.
8686
8687 var add = DOMTokenList.prototype.add;
8688 var remove = DOMTokenList.prototype.remove;
8689 DOMTokenList.prototype.add = function() {
8690 for (var i = 0; i < arguments.length; i++) {
8691 add.call(this, arguments[i]);
8692 }
8693 };
8694 DOMTokenList.prototype.remove = function() {
8695 for (var i = 0; i < arguments.length; i++) {
8696 remove.call(this, arguments[i]);
8697 }
8698 };
8699 DOMTokenList.prototype.toggle = function(name, bool) {
8700 if (arguments.length == 1) {
8701 bool = !this.contains(name);
8702 }
8703 bool ? this.add(name) : this.remove(name);
8704 };
8705 DOMTokenList.prototype.switch = function(oldName, newName) {
8706 oldName && this.remove(oldName);
8707 newName && this.add(newName);
8708 };
8709
8710 // add array() to NodeList, NamedNodeMap, HTMLCollection
8711
8712 var ArraySlice = function() {
8713 return Array.prototype.slice.call(this);
8714 };
8715
8716 var namedNodeMap = (window.NamedNodeMap || window.MozNamedAttrMap || {});
8717
8718 NodeList.prototype.array = ArraySlice;
8719 namedNodeMap.prototype.array = ArraySlice;
8720 HTMLCollection.prototype.array = ArraySlice;
8721
8722 // utility
8723
8724 function createDOM(inTagOrNode, inHTML, inAttrs) {
8725 var dom = typeof inTagOrNode == 'string' ?
8726 document.createElement(inTagOrNode) : inTagOrNode.cloneNode(true);
8727 dom.innerHTML = inHTML;
8728 if (inAttrs) {
8729 for (var n in inAttrs) {
8730 dom.setAttribute(n, inAttrs[n]);
8731 }
8732 }
8733 return dom;
8734 }
8735
8736 // exports
8737
8738 scope.createDOM = createDOM;
8739
8740 })(Polymer);
8741
8742 (function(scope) {
8743 // super
8744
8745 // `arrayOfArgs` is an optional array of args like one might pass
8746 // to `Function.apply`
8747
8748 // TODO(sjmiles):
8749 // $super must be installed on an instance or prototype chain
8750 // as `super`, and invoked via `this`, e.g.
8751 // `this.super();`
8752
8753 // will not work if function objects are not unique, for example,
8754 // when using mixins.
8755 // The memoization strategy assumes each function exists on only one
8756 // prototype chain i.e. we use the function object for memoizing)
8757 // perhaps we can bookkeep on the prototype itself instead
8758 function $super(arrayOfArgs) {
8759 // since we are thunking a method call, performance is important here:
8760 // memoize all lookups, once memoized the fast path calls no other
8761 // functions
8762 //
8763 // find the caller (cannot be `strict` because of 'caller')
8764 var caller = $super.caller;
8765 // memoized 'name of method'
8766 var nom = caller.nom;
8767 // memoized next implementation prototype
8768 var _super = caller._super;
8769 if (!_super) {
8770 if (!nom) {
8771 nom = caller.nom = nameInThis.call(this, caller);
8772 }
8773 if (!nom) {
8774 console.warn('called super() on a method not installed declaratively ( has no .nom property)');
8775 }
8776 // super prototype is either cached or we have to find it
8777 // by searching __proto__ (at the 'top')
8778 // invariant: because we cache _super on fn below, we never reach
8779 // here from inside a series of calls to super(), so it's ok to
8780 // start searching from the prototype of 'this' (at the 'top')
8781 // we must never memoize a null super for this reason
8782 _super = memoizeSuper(caller, nom, getPrototypeOf(this));
8783 }
8784 // our super function
8785 var fn = _super[nom];
8786 if (fn) {
8787 // memoize information so 'fn' can call 'super'
8788 if (!fn._super) {
8789 // must not memoize null, or we lose our invariant above
8790 memoizeSuper(fn, nom, _super);
8791 }
8792 // invoke the inherited method
8793 // if 'fn' is not function valued, this will throw
8794 return fn.apply(this, arrayOfArgs || []);
8795 }
8796 }
8797
8798 function nameInThis(value) {
8799 var p = this.__proto__;
8800 while (p && p !== HTMLElement.prototype) {
8801 // TODO(sjmiles): getOwnPropertyNames is absurdly expensive
8802 var n$ = Object.getOwnPropertyNames(p);
8803 for (var i=0, l=n$.length, n; i<l && (n=n$[i]); i++) {
8804 var d = Object.getOwnPropertyDescriptor(p, n);
8805 if (typeof d.value === 'function' && d.value === value) {
8806 return n;
8807 }
8808 }
8809 p = p.__proto__;
8810 }
8811 }
8812
8813 function memoizeSuper(method, name, proto) {
8814 // find and cache next prototype containing `name`
8815 // we need the prototype so we can do another lookup
8816 // from here
8817 var s = nextSuper(proto, name, method);
8818 if (s[name]) {
8819 // `s` is a prototype, the actual method is `s[name]`
8820 // tag super method with it's name for quicker lookups
8821 s[name].nom = name;
8822 }
8823 return method._super = s;
8824 }
8825
8826 function nextSuper(proto, name, caller) {
8827 // look for an inherited prototype that implements name
8828 while (proto) {
8829 if ((proto[name] !== caller) && proto[name]) {
8830 return proto;
8831 }
8832 proto = getPrototypeOf(proto);
8833 }
8834 // must not return null, or we lose our invariant above
8835 // in this case, a super() call was invoked where no superclass
8836 // method exists
8837 // TODO(sjmiles): thow an exception?
8838 return Object;
8839 }
8840
8841 // NOTE: In some platforms (IE10) the prototype chain is faked via
8842 // __proto__. Therefore, always get prototype via __proto__ instead of
8843 // the more standard Object.getPrototypeOf.
8844 function getPrototypeOf(prototype) {
8845 return prototype.__proto__;
8846 }
8847
8848 // utility function to precompute name tags for functions
8849 // in a (unchained) prototype
8850 function hintSuper(prototype) {
8851 // tag functions with their prototype name to optimize
8852 // super call invocations
8853 for (var n in prototype) {
8854 var pd = Object.getOwnPropertyDescriptor(prototype, n);
8855 if (pd && typeof pd.value === 'function') {
8856 pd.value.nom = n;
8857 }
8858 }
8859 }
8860
8861 // exports
8862
8863 scope.super = $super;
8864
8865 })(Polymer);
8866
8867 (function(scope) {
8868
8869 function noopHandler(value) {
8870 return value;
8871 }
8872
8873 // helper for deserializing properties of various types to strings
8874 var typeHandlers = {
8875 string: noopHandler,
8876 'undefined': noopHandler,
8877 date: function(value) {
8878 return new Date(Date.parse(value) || Date.now());
8879 },
8880 boolean: function(value) {
8881 if (value === '') {
8882 return true;
8883 }
8884 return value === 'false' ? false : !!value;
8885 },
8886 number: function(value) {
8887 var n = parseFloat(value);
8888 // hex values like "0xFFFF" parseFloat as 0
8889 if (n === 0) {
8890 n = parseInt(value);
8891 }
8892 return isNaN(n) ? value : n;
8893 // this code disabled because encoded values (like "0xFFFF")
8894 // do not round trip to their original format
8895 //return (String(floatVal) === value) ? floatVal : value;
8896 },
8897 object: function(value, currentValue) {
8898 if (currentValue === null) {
8899 return value;
8900 }
8901 try {
8902 // If the string is an object, we can parse is with the JSON library.
8903 // include convenience replace for single-quotes. If the author omits
8904 // quotes altogether, parse will fail.
8905 return JSON.parse(value.replace(/'/g, '"'));
8906 } catch(e) {
8907 // The object isn't valid JSON, return the raw value
8908 return value;
8909 }
8910 },
8911 // avoid deserialization of functions
8912 'function': function(value, currentValue) {
8913 return currentValue;
8914 }
8915 };
8916
8917 function deserializeValue(value, currentValue) {
8918 // attempt to infer type from default value
8919 var inferredType = typeof currentValue;
8920 // invent 'date' type value for Date
8921 if (currentValue instanceof Date) {
8922 inferredType = 'date';
8923 }
8924 // delegate deserialization via type string
8925 return typeHandlers[inferredType](value, currentValue);
8926 }
8927
8928 // exports
8929
8930 scope.deserializeValue = deserializeValue;
8931
8932 })(Polymer);
8933
8934 (function(scope) {
8935
8936 // imports
8937
8938 var extend = scope.extend;
8939
8940 // module
8941
8942 var api = {};
8943
8944 api.declaration = {};
8945 api.instance = {};
8946
8947 api.publish = function(apis, prototype) {
8948 for (var n in apis) {
8949 extend(prototype, apis[n]);
8950 }
8951 };
8952
8953 // exports
8954
8955 scope.api = api;
8956
8957 })(Polymer);
8958
8959 (function(scope) {
8960
8961 /**
8962 * @class polymer-base
8963 */
8964
8965 var utils = {
8966
8967 /**
8968 * Invokes a function asynchronously. The context of the callback
8969 * function is bound to 'this' automatically. Returns a handle which may
8970 * be passed to <a href="#cancelAsync">cancelAsync</a> to cancel the
8971 * asynchronous call.
8972 *
8973 * @method async
8974 * @param {Function|String} method
8975 * @param {any|Array} args
8976 * @param {number} timeout
8977 */
8978 async: function(method, args, timeout) {
8979 // when polyfilling Object.observe, ensure changes
8980 // propagate before executing the async method
8981 Polymer.flush();
8982 // second argument to `apply` must be an array
8983 args = (args && args.length) ? args : [args];
8984 // function to invoke
8985 var fn = function() {
8986 (this[method] || method).apply(this, args);
8987 }.bind(this);
8988 // execute `fn` sooner or later
8989 var handle = timeout ? setTimeout(fn, timeout) :
8990 requestAnimationFrame(fn);
8991 // NOTE: switch on inverting handle to determine which time is used.
8992 return timeout ? handle : ~handle;
8993 },
8994
8995 /**
8996 * Cancels a pending callback that was scheduled via
8997 * <a href="#async">async</a>.
8998 *
8999 * @method cancelAsync
9000 * @param {handle} handle Handle of the `async` to cancel.
9001 */
9002 cancelAsync: function(handle) {
9003 if (handle < 0) {
9004 cancelAnimationFrame(~handle);
9005 } else {
9006 clearTimeout(handle);
9007 }
9008 },
9009
9010 /**
9011 * Fire an event.
9012 *
9013 * @method fire
9014 * @returns {Object} event
9015 * @param {string} type An event name.
9016 * @param {any} detail
9017 * @param {Node} onNode Target node.
9018 * @param {Boolean} bubbles Set false to prevent bubbling, defaults to true
9019 * @param {Boolean} cancelable Set false to prevent cancellation, defaults to true
9020 */
9021 fire: function(type, detail, onNode, bubbles, cancelable) {
9022 var node = onNode || this;
9023 var detail = detail === null || detail === undefined ? {} : detail;
9024 var event = new CustomEvent(type, {
9025 bubbles: bubbles !== undefined ? bubbles : true,
9026 cancelable: cancelable !== undefined ? cancelable : true,
9027 detail: detail
9028 });
9029 node.dispatchEvent(event);
9030 return event;
9031 },
9032
9033 /**
9034 * Fire an event asynchronously.
9035 *
9036 * @method asyncFire
9037 * @param {string} type An event name.
9038 * @param detail
9039 * @param {Node} toNode Target node.
9040 */
9041 asyncFire: function(/*inType, inDetail*/) {
9042 this.async("fire", arguments);
9043 },
9044
9045 /**
9046 * Remove class from old, add class to anew, if they exist.
9047 *
9048 * @param classFollows
9049 * @param anew A node.
9050 * @param old A node
9051 * @param className
9052 */
9053 classFollows: function(anew, old, className) {
9054 if (old) {
9055 old.classList.remove(className);
9056 }
9057 if (anew) {
9058 anew.classList.add(className);
9059 }
9060 },
9061
9062 /**
9063 * Inject HTML which contains markup bound to this element into
9064 * a target element (replacing target element content).
9065 *
9066 * @param String html to inject
9067 * @param Element target element
9068 */
9069 injectBoundHTML: function(html, element) {
9070 var template = document.createElement('template');
9071 template.innerHTML = html;
9072 var fragment = this.instanceTemplate(template);
9073 if (element) {
9074 element.textContent = '';
9075 element.appendChild(fragment);
9076 }
9077 return fragment;
9078 }
9079 };
9080
9081 // no-operation function for handy stubs
9082 var nop = function() {};
9083
9084 // null-object for handy stubs
9085 var nob = {};
9086
9087 // deprecated
9088
9089 utils.asyncMethod = utils.async;
9090
9091 // exports
9092
9093 scope.api.instance.utils = utils;
9094 scope.nop = nop;
9095 scope.nob = nob;
9096
9097 })(Polymer);
9098
9099 (function(scope) {
9100
9101 // imports
9102
9103 var log = window.WebComponents ? WebComponents.flags.log : {};
9104 var EVENT_PREFIX = 'on-';
9105
9106 // instance events api
9107 var events = {
9108 // read-only
9109 EVENT_PREFIX: EVENT_PREFIX,
9110 // event listeners on host
9111 addHostListeners: function() {
9112 var events = this.eventDelegates;
9113 log.events && (Object.keys(events).length > 0) && console.log('[%s] addHos tListeners:', this.localName, events);
9114 // NOTE: host events look like bindings but really are not;
9115 // (1) we don't want the attribute to be set and (2) we want to support
9116 // multiple event listeners ('host' and 'instance') and Node.bind
9117 // by default supports 1 thing being bound.
9118 for (var type in events) {
9119 var methodName = events[type];
9120 PolymerGestures.addEventListener(this, type, this.element.getEventHandle r(this, this, methodName));
9121 }
9122 },
9123 // call 'method' or function method on 'obj' with 'args', if the method exis ts
9124 dispatchMethod: function(obj, method, args) {
9125 if (obj) {
9126 log.events && console.group('[%s] dispatch [%s]', obj.localName, method) ;
9127 var fn = typeof method === 'function' ? method : obj[method];
9128 if (fn) {
9129 fn[args ? 'apply' : 'call'](obj, args);
9130 }
9131 log.events && console.groupEnd();
9132 // NOTE: dirty check right after calling method to ensure
9133 // changes apply quickly; in a very complicated app using high
9134 // frequency events, this can be a perf concern; in this case,
9135 // imperative handlers can be used to avoid flushing.
9136 Polymer.flush();
9137 }
9138 }
9139 };
9140
9141 // exports
9142
9143 scope.api.instance.events = events;
9144
9145 /**
9146 * @class Polymer
9147 */
9148
9149 /**
9150 * Add a gesture aware event handler to the given `node`. Can be used
9151 * in place of `element.addEventListener` and ensures gestures will function
9152 * as expected on mobile platforms. Please note that Polymer's declarative
9153 * event handlers include this functionality by default.
9154 *
9155 * @method addEventListener
9156 * @param {Node} node node on which to listen
9157 * @param {String} eventType name of the event
9158 * @param {Function} handlerFn event handler function
9159 * @param {Boolean} capture set to true to invoke event capturing
9160 * @type Function
9161 */
9162 // alias PolymerGestures event listener logic
9163 scope.addEventListener = function(node, eventType, handlerFn, capture) {
9164 PolymerGestures.addEventListener(wrap(node), eventType, handlerFn, capture);
9165 };
9166
9167 /**
9168 * Remove a gesture aware event handler on the given `node`. To remove an
9169 * event listener, the exact same arguments are required that were passed
9170 * to `Polymer.addEventListener`.
9171 *
9172 * @method removeEventListener
9173 * @param {Node} node node on which to listen
9174 * @param {String} eventType name of the event
9175 * @param {Function} handlerFn event handler function
9176 * @param {Boolean} capture set to true to invoke event capturing
9177 * @type Function
9178 */
9179 scope.removeEventListener = function(node, eventType, handlerFn, capture) {
9180 PolymerGestures.removeEventListener(wrap(node), eventType, handlerFn, captur e);
9181 };
9182
9183 })(Polymer);
9184
9185 (function(scope) {
9186
9187 // instance api for attributes
9188
9189 var attributes = {
9190 // copy attributes defined in the element declaration to the instance
9191 // e.g. <polymer-element name="x-foo" tabIndex="0"> tabIndex is copied
9192 // to the element instance here.
9193 copyInstanceAttributes: function () {
9194 var a$ = this._instanceAttributes;
9195 for (var k in a$) {
9196 if (!this.hasAttribute(k)) {
9197 this.setAttribute(k, a$[k]);
9198 }
9199 }
9200 },
9201 // for each attribute on this, deserialize value to property as needed
9202 takeAttributes: function() {
9203 // if we have no publish lookup table, we have no attributes to take
9204 // TODO(sjmiles): ad hoc
9205 if (this._publishLC) {
9206 for (var i=0, a$=this.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++) {
9207 this.attributeToProperty(a.name, a.value);
9208 }
9209 }
9210 },
9211 // if attribute 'name' is mapped to a property, deserialize
9212 // 'value' into that property
9213 attributeToProperty: function(name, value) {
9214 // try to match this attribute to a property (attributes are
9215 // all lower-case, so this is case-insensitive search)
9216 var name = this.propertyForAttribute(name);
9217 if (name) {
9218 // filter out 'mustached' values, these are to be
9219 // replaced with bound-data and are not yet values
9220 // themselves
9221 if (value && value.search(scope.bindPattern) >= 0) {
9222 return;
9223 }
9224 // get original value
9225 var currentValue = this[name];
9226 // deserialize Boolean or Number values from attribute
9227 var value = this.deserializeValue(value, currentValue);
9228 // only act if the value has changed
9229 if (value !== currentValue) {
9230 // install new value (has side-effects)
9231 this[name] = value;
9232 }
9233 }
9234 },
9235 // return the published property matching name, or undefined
9236 propertyForAttribute: function(name) {
9237 var match = this._publishLC && this._publishLC[name];
9238 return match;
9239 },
9240 // convert representation of `stringValue` based on type of `currentValue`
9241 deserializeValue: function(stringValue, currentValue) {
9242 return scope.deserializeValue(stringValue, currentValue);
9243 },
9244 // convert to a string value based on the type of `inferredType`
9245 serializeValue: function(value, inferredType) {
9246 if (inferredType === 'boolean') {
9247 return value ? '' : undefined;
9248 } else if (inferredType !== 'object' && inferredType !== 'function'
9249 && value !== undefined) {
9250 return value;
9251 }
9252 },
9253 // serializes `name` property value and updates the corresponding attribute
9254 // note that reflection is opt-in.
9255 reflectPropertyToAttribute: function(name) {
9256 var inferredType = typeof this[name];
9257 // try to intelligently serialize property value
9258 var serializedValue = this.serializeValue(this[name], inferredType);
9259 // boolean properties must reflect as boolean attributes
9260 if (serializedValue !== undefined) {
9261 this.setAttribute(name, serializedValue);
9262 // TODO(sorvell): we should remove attr for all properties
9263 // that have undefined serialization; however, we will need to
9264 // refine the attr reflection system to achieve this; pica, for example,
9265 // relies on having inferredType object properties not removed as
9266 // attrs.
9267 } else if (inferredType === 'boolean') {
9268 this.removeAttribute(name);
9269 }
9270 }
9271 };
9272
9273 // exports
9274
9275 scope.api.instance.attributes = attributes;
9276
9277 })(Polymer);
9278
9279 (function(scope) {
9280
9281 /**
9282 * @class polymer-base
9283 */
9284
9285 // imports
9286
9287 var log = window.WebComponents ? WebComponents.flags.log : {};
9288
9289 // magic words
9290
9291 var OBSERVE_SUFFIX = 'Changed';
9292
9293 // element api
9294
9295 var empty = [];
9296
9297 var updateRecord = {
9298 object: undefined,
9299 type: 'update',
9300 name: undefined,
9301 oldValue: undefined
9302 };
9303
9304 var numberIsNaN = Number.isNaN || function(value) {
9305 return typeof value === 'number' && isNaN(value);
9306 };
9307
9308 function areSameValue(left, right) {
9309 if (left === right)
9310 return left !== 0 || 1 / left === 1 / right;
9311 if (numberIsNaN(left) && numberIsNaN(right))
9312 return true;
9313 return left !== left && right !== right;
9314 }
9315
9316 // capture A's value if B's value is null or undefined,
9317 // otherwise use B's value
9318 function resolveBindingValue(oldValue, value) {
9319 if (value === undefined && oldValue === null) {
9320 return value;
9321 }
9322 return (value === null || value === undefined) ? oldValue : value;
9323 }
9324
9325 var properties = {
9326
9327 // creates a CompoundObserver to observe property changes
9328 // NOTE, this is only done there are any properties in the `observe` object
9329 createPropertyObserver: function() {
9330 var n$ = this._observeNames;
9331 if (n$ && n$.length) {
9332 var o = this._propertyObserver = new CompoundObserver(true);
9333 this.registerObserver(o);
9334 // TODO(sorvell): may not be kosher to access the value here (this[n]);
9335 // previously we looked at the descriptor on the prototype
9336 // this doesn't work for inheritance and not for accessors without
9337 // a value property
9338 for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) {
9339 o.addPath(this, n);
9340 this.observeArrayValue(n, this[n], null);
9341 }
9342 }
9343 },
9344
9345 // start observing property changes
9346 openPropertyObserver: function() {
9347 if (this._propertyObserver) {
9348 this._propertyObserver.open(this.notifyPropertyChanges, this);
9349 }
9350 },
9351
9352 // handler for property changes; routes changes to observing methods
9353 // note: array valued properties are observed for array splices
9354 notifyPropertyChanges: function(newValues, oldValues, paths) {
9355 var name, method, called = {};
9356 for (var i in oldValues) {
9357 // note: paths is of form [object, path, object, path]
9358 name = paths[2 * i + 1];
9359 method = this.observe[name];
9360 if (method) {
9361 var ov = oldValues[i], nv = newValues[i];
9362 // observes the value if it is an array
9363 this.observeArrayValue(name, nv, ov);
9364 if (!called[method]) {
9365 // only invoke change method if one of ov or nv is not (undefined | null)
9366 if ((ov !== undefined && ov !== null) || (nv !== undefined && nv !== null)) {
9367 called[method] = true;
9368 // TODO(sorvell): call method with the set of values it's expectin g;
9369 // e.g. 'foo bar': 'invalidate' expects the new and old values for
9370 // foo and bar. Currently we give only one of these and then
9371 // deliver all the arguments.
9372 this.invokeMethod(method, [ov, nv, arguments]);
9373 }
9374 }
9375 }
9376 }
9377 },
9378
9379 // call method iff it exists.
9380 invokeMethod: function(method, args) {
9381 var fn = this[method] || method;
9382 if (typeof fn === 'function') {
9383 fn.apply(this, args);
9384 }
9385 },
9386
9387 /**
9388 * Force any pending property changes to synchronously deliver to
9389 * handlers specified in the `observe` object.
9390 * Note, normally changes are processed at microtask time.
9391 *
9392 * @method deliverChanges
9393 */
9394 deliverChanges: function() {
9395 if (this._propertyObserver) {
9396 this._propertyObserver.deliver();
9397 }
9398 },
9399
9400 observeArrayValue: function(name, value, old) {
9401 // we only care if there are registered side-effects
9402 var callbackName = this.observe[name];
9403 if (callbackName) {
9404 // if we are observing the previous value, stop
9405 if (Array.isArray(old)) {
9406 log.observe && console.log('[%s] observeArrayValue: unregister observe r [%s]', this.localName, name);
9407 this.closeNamedObserver(name + '__array');
9408 }
9409 // if the new value is an array, being observing it
9410 if (Array.isArray(value)) {
9411 log.observe && console.log('[%s] observeArrayValue: register observer [%s]', this.localName, name, value);
9412 var observer = new ArrayObserver(value);
9413 observer.open(function(splices) {
9414 this.invokeMethod(callbackName, [splices]);
9415 }, this);
9416 this.registerNamedObserver(name + '__array', observer);
9417 }
9418 }
9419 },
9420
9421 emitPropertyChangeRecord: function(name, value, oldValue) {
9422 var object = this;
9423 if (areSameValue(value, oldValue)) {
9424 return;
9425 }
9426 // invoke property change side effects
9427 this._propertyChanged(name, value, oldValue);
9428 // emit change record
9429 if (!Observer.hasObjectObserve) {
9430 return;
9431 }
9432 var notifier = this._objectNotifier;
9433 if (!notifier) {
9434 notifier = this._objectNotifier = Object.getNotifier(this);
9435 }
9436 updateRecord.object = this;
9437 updateRecord.name = name;
9438 updateRecord.oldValue = oldValue;
9439 notifier.notify(updateRecord);
9440 },
9441
9442 _propertyChanged: function(name, value, oldValue) {
9443 if (this.reflect[name]) {
9444 this.reflectPropertyToAttribute(name);
9445 }
9446 },
9447
9448 // creates a property binding (called via bind) to a published property.
9449 bindProperty: function(property, observable, oneTime) {
9450 if (oneTime) {
9451 this[property] = observable;
9452 return;
9453 }
9454 var computed = this.element.prototype.computed;
9455 // Binding an "out-only" value to a computed property. Note that
9456 // since this observer isn't opened, it doesn't need to be closed on
9457 // cleanup.
9458 if (computed && computed[property]) {
9459 var privateComputedBoundValue = property + 'ComputedBoundObservable_';
9460 this[privateComputedBoundValue] = observable;
9461 return;
9462 }
9463 return this.bindToAccessor(property, observable, resolveBindingValue);
9464 },
9465
9466 // NOTE property `name` must be published. This makes it an accessor.
9467 bindToAccessor: function(name, observable, resolveFn) {
9468 var privateName = name + '_';
9469 var privateObservable = name + 'Observable_';
9470 // Present for properties which are computed and published and have a
9471 // bound value.
9472 var privateComputedBoundValue = name + 'ComputedBoundObservable_';
9473 this[privateObservable] = observable;
9474 var oldValue = this[privateName];
9475 // observable callback
9476 var self = this;
9477 function updateValue(value, oldValue) {
9478 self[privateName] = value;
9479 var setObserveable = self[privateComputedBoundValue];
9480 if (setObserveable && typeof setObserveable.setValue == 'function') {
9481 setObserveable.setValue(value);
9482 }
9483 self.emitPropertyChangeRecord(name, value, oldValue);
9484 }
9485 // resolve initial value
9486 var value = observable.open(updateValue);
9487 if (resolveFn && !areSameValue(oldValue, value)) {
9488 var resolvedValue = resolveFn(oldValue, value);
9489 if (!areSameValue(value, resolvedValue)) {
9490 value = resolvedValue;
9491 if (observable.setValue) {
9492 observable.setValue(value);
9493 }
9494 }
9495 }
9496 updateValue(value, oldValue);
9497 // register and return observable
9498 var observer = {
9499 close: function() {
9500 observable.close();
9501 self[privateObservable] = undefined;
9502 self[privateComputedBoundValue] = undefined;
9503 }
9504 };
9505 this.registerObserver(observer);
9506 return observer;
9507 },
9508
9509 createComputedProperties: function() {
9510 if (!this._computedNames) {
9511 return;
9512 }
9513 for (var i = 0; i < this._computedNames.length; i++) {
9514 var name = this._computedNames[i];
9515 var expressionText = this.computed[name];
9516 try {
9517 var expression = PolymerExpressions.getExpression(expressionText);
9518 var observable = expression.getBinding(this, this.element.syntax);
9519 this.bindToAccessor(name, observable);
9520 } catch (ex) {
9521 console.error('Failed to create computed property', ex);
9522 }
9523 }
9524 },
9525
9526 // property bookkeeping
9527 registerObserver: function(observer) {
9528 if (!this._observers) {
9529 this._observers = [observer];
9530 return;
9531 }
9532 this._observers.push(observer);
9533 },
9534
9535 closeObservers: function() {
9536 if (!this._observers) {
9537 return;
9538 }
9539 // observer array items are arrays of observers.
9540 var observers = this._observers;
9541 for (var i = 0; i < observers.length; i++) {
9542 var observer = observers[i];
9543 if (observer && typeof observer.close == 'function') {
9544 observer.close();
9545 }
9546 }
9547 this._observers = [];
9548 },
9549
9550 // bookkeeping observers for memory management
9551 registerNamedObserver: function(name, observer) {
9552 var o$ = this._namedObservers || (this._namedObservers = {});
9553 o$[name] = observer;
9554 },
9555
9556 closeNamedObserver: function(name) {
9557 var o$ = this._namedObservers;
9558 if (o$ && o$[name]) {
9559 o$[name].close();
9560 o$[name] = null;
9561 return true;
9562 }
9563 },
9564
9565 closeNamedObservers: function() {
9566 if (this._namedObservers) {
9567 for (var i in this._namedObservers) {
9568 this.closeNamedObserver(i);
9569 }
9570 this._namedObservers = {};
9571 }
9572 }
9573
9574 };
9575
9576 // logging
9577 var LOG_OBSERVE = '[%s] watching [%s]';
9578 var LOG_OBSERVED = '[%s#%s] watch: [%s] now [%s] was [%s]';
9579 var LOG_CHANGED = '[%s#%s] propertyChanged: [%s] now [%s] was [%s]';
9580
9581 // exports
9582
9583 scope.api.instance.properties = properties;
9584
9585 })(Polymer);
9586
9587 (function(scope) {
9588
9589 /**
9590 * @class polymer-base
9591 */
9592
9593 // imports
9594
9595 var log = window.WebComponents ? WebComponents.flags.log : {};
9596
9597 // element api supporting mdv
9598 var mdv = {
9599
9600 /**
9601 * Creates dom cloned from the given template, instantiating bindings
9602 * with this element as the template model and `PolymerExpressions` as the
9603 * binding delegate.
9604 *
9605 * @method instanceTemplate
9606 * @param {Template} template source template from which to create dom.
9607 */
9608 instanceTemplate: function(template) {
9609 // ensure template is decorated (lets' things like <tr template ...> work)
9610 HTMLTemplateElement.decorate(template);
9611 // ensure a default bindingDelegate
9612 var syntax = this.syntax || (!template.bindingDelegate &&
9613 this.element.syntax);
9614 var dom = template.createInstance(this, syntax);
9615 var observers = dom.bindings_;
9616 for (var i = 0; i < observers.length; i++) {
9617 this.registerObserver(observers[i]);
9618 }
9619 return dom;
9620 },
9621
9622 // Called by TemplateBinding/NodeBind to setup a binding to the given
9623 // property. It's overridden here to support property bindings
9624 // in addition to attribute bindings that are supported by default.
9625 bind: function(name, observable, oneTime) {
9626 var property = this.propertyForAttribute(name);
9627 if (!property) {
9628 // TODO(sjmiles): this mixin method must use the special form
9629 // of `super` installed by `mixinMethod` in declaration/prototype.js
9630 return this.mixinSuper(arguments);
9631 } else {
9632 // use n-way Polymer binding
9633 var observer = this.bindProperty(property, observable, oneTime);
9634 // NOTE: reflecting binding information is typically required only for
9635 // tooling. It has a performance cost so it's opt-in in Node.bind.
9636 if (Platform.enableBindingsReflection && observer) {
9637 observer.path = observable.path_;
9638 this._recordBinding(property, observer);
9639 }
9640 if (this.reflect[property]) {
9641 this.reflectPropertyToAttribute(property);
9642 }
9643 return observer;
9644 }
9645 },
9646
9647 _recordBinding: function(name, observer) {
9648 this.bindings_ = this.bindings_ || {};
9649 this.bindings_[name] = observer;
9650 },
9651
9652 // Called by TemplateBinding when all bindings on an element have been
9653 // executed. This signals that all element inputs have been gathered
9654 // and it's safe to ready the element, create shadow-root and start
9655 // data-observation.
9656 bindFinished: function() {
9657 this.makeElementReady();
9658 },
9659
9660 // called at detached time to signal that an element's bindings should be
9661 // cleaned up. This is done asynchronously so that users have the chance
9662 // to call `cancelUnbindAll` to prevent unbinding.
9663 asyncUnbindAll: function() {
9664 if (!this._unbound) {
9665 log.unbind && console.log('[%s] asyncUnbindAll', this.localName);
9666 this._unbindAllJob = this.job(this._unbindAllJob, this.unbindAll, 0);
9667 }
9668 },
9669
9670 /**
9671 * This method should rarely be used and only if
9672 * <a href="#cancelUnbindAll">`cancelUnbindAll`</a> has been called to
9673 * prevent element unbinding. In this case, the element's bindings will
9674 * not be automatically cleaned up and it cannot be garbage collected
9675 * by the system. If memory pressure is a concern or a
9676 * large amount of elements need to be managed in this way, `unbindAll`
9677 * can be called to deactivate the element's bindings and allow its
9678 * memory to be reclaimed.
9679 *
9680 * @method unbindAll
9681 */
9682 unbindAll: function() {
9683 if (!this._unbound) {
9684 this.closeObservers();
9685 this.closeNamedObservers();
9686 this._unbound = true;
9687 }
9688 },
9689
9690 /**
9691 * Call in `detached` to prevent the element from unbinding when it is
9692 * detached from the dom. The element is unbound as a cleanup step that
9693 * allows its memory to be reclaimed.
9694 * If `cancelUnbindAll` is used, consider calling
9695 * <a href="#unbindAll">`unbindAll`</a> when the element is no longer
9696 * needed. This will allow its memory to be reclaimed.
9697 *
9698 * @method cancelUnbindAll
9699 */
9700 cancelUnbindAll: function() {
9701 if (this._unbound) {
9702 log.unbind && console.warn('[%s] already unbound, cannot cancel unbindAl l', this.localName);
9703 return;
9704 }
9705 log.unbind && console.log('[%s] cancelUnbindAll', this.localName);
9706 if (this._unbindAllJob) {
9707 this._unbindAllJob = this._unbindAllJob.stop();
9708 }
9709 }
9710
9711 };
9712
9713 function unbindNodeTree(node) {
9714 forNodeTree(node, _nodeUnbindAll);
9715 }
9716
9717 function _nodeUnbindAll(node) {
9718 node.unbindAll();
9719 }
9720
9721 function forNodeTree(node, callback) {
9722 if (node) {
9723 callback(node);
9724 for (var child = node.firstChild; child; child = child.nextSibling) {
9725 forNodeTree(child, callback);
9726 }
9727 }
9728 }
9729
9730 var mustachePattern = /\{\{([^{}]*)}}/;
9731
9732 // exports
9733
9734 scope.bindPattern = mustachePattern;
9735 scope.api.instance.mdv = mdv;
9736
9737 })(Polymer);
9738
9739 (function(scope) {
9740
9741 /**
9742 * Common prototype for all Polymer Elements.
9743 *
9744 * @class polymer-base
9745 * @homepage polymer.github.io
9746 */
9747 var base = {
9748 /**
9749 * Tags this object as the canonical Base prototype.
9750 *
9751 * @property PolymerBase
9752 * @type boolean
9753 * @default true
9754 */
9755 PolymerBase: true,
9756
9757 /**
9758 * Debounce signals.
9759 *
9760 * Call `job` to defer a named signal, and all subsequent matching signals,
9761 * until a wait time has elapsed with no new signal.
9762 *
9763 * debouncedClickAction: function(e) {
9764 * // processClick only when it's been 100ms since the last click
9765 * this.job('click', function() {
9766 * this.processClick;
9767 * }, 100);
9768 * }
9769 *
9770 * @method job
9771 * @param String {String} job A string identifier for the job to debounce.
9772 * @param Function {Function} callback A function that is called (with `this ` context) when the wait time elapses.
9773 * @param Number {Number} wait Time in milliseconds (ms) after the last sign al that must elapse before invoking `callback`
9774 * @type Handle
9775 */
9776 job: function(job, callback, wait) {
9777 if (typeof job === 'string') {
9778 var n = '___' + job;
9779 this[n] = Polymer.job.call(this, this[n], callback, wait);
9780 } else {
9781 // TODO(sjmiles): suggest we deprecate this call signature
9782 return Polymer.job.call(this, job, callback, wait);
9783 }
9784 },
9785
9786 /**
9787 * Invoke a superclass method.
9788 *
9789 * Use `super()` to invoke the most recently overridden call to the
9790 * currently executing function.
9791 *
9792 * To pass arguments through, use the literal `arguments` as the parameter
9793 * to `super()`.
9794 *
9795 * nextPageAction: function(e) {
9796 * // invoke the superclass version of `nextPageAction`
9797 * this.super(arguments);
9798 * }
9799 *
9800 * To pass custom arguments, arrange them in an array.
9801 *
9802 * appendSerialNo: function(value, serial) {
9803 * // prefix the superclass serial number with our lot # before
9804 * // invoking the superlcass
9805 * return this.super([value, this.lotNo + serial])
9806 * }
9807 *
9808 * @method super
9809 * @type Any
9810 * @param {args) An array of arguments to use when calling the superclass me thod, or null.
9811 */
9812 super: Polymer.super,
9813
9814 /**
9815 * Lifecycle method called when the element is instantiated.
9816 *
9817 * Override `created` to perform custom create-time tasks. No need to call
9818 * super-class `created` unless you are extending another Polymer element.
9819 * Created is called before the element creates `shadowRoot` or prepares
9820 * data-observation.
9821 *
9822 * @method created
9823 * @type void
9824 */
9825 created: function() {
9826 },
9827
9828 /**
9829 * Lifecycle method called when the element has populated it's `shadowRoot`,
9830 * prepared data-observation, and made itself ready for API interaction.
9831 *
9832 * @method ready
9833 * @type void
9834 */
9835 ready: function() {
9836 },
9837
9838 /**
9839 * Low-level lifecycle method called as part of standard Custom Elements
9840 * operation. Polymer implements this method to provide basic default
9841 * functionality. For custom create-time tasks, implement `created`
9842 * instead, which is called immediately after `createdCallback`.
9843 *
9844 * @method createdCallback
9845 */
9846 createdCallback: function() {
9847 if (this.templateInstance && this.templateInstance.model) {
9848 console.warn('Attributes on ' + this.localName + ' were data bound ' +
9849 'prior to Polymer upgrading the element. This may result in ' +
9850 'incorrect binding types.');
9851 }
9852 this.created();
9853 this.prepareElement();
9854 if (!this.ownerDocument.isStagingDocument) {
9855 this.makeElementReady();
9856 }
9857 },
9858
9859 // system entry point, do not override
9860 prepareElement: function() {
9861 if (this._elementPrepared) {
9862 console.warn('Element already prepared', this.localName);
9863 return;
9864 }
9865 this._elementPrepared = true;
9866 // storage for shadowRoots info
9867 this.shadowRoots = {};
9868 // install property observers
9869 this.createPropertyObserver();
9870 this.openPropertyObserver();
9871 // install boilerplate attributes
9872 this.copyInstanceAttributes();
9873 // process input attributes
9874 this.takeAttributes();
9875 // add event listeners
9876 this.addHostListeners();
9877 },
9878
9879 // system entry point, do not override
9880 makeElementReady: function() {
9881 if (this._readied) {
9882 return;
9883 }
9884 this._readied = true;
9885 this.createComputedProperties();
9886 this.parseDeclarations(this.__proto__);
9887 // NOTE: Support use of the `unresolved` attribute to help polyfill
9888 // custom elements' `:unresolved` feature.
9889 this.removeAttribute('unresolved');
9890 // user entry point
9891 this.ready();
9892 },
9893
9894 /**
9895 * Low-level lifecycle method called as part of standard Custom Elements
9896 * operation. Polymer implements this method to provide basic default
9897 * functionality. For custom tasks in your element, implement `attributeChan ged`
9898 * instead, which is called immediately after `attributeChangedCallback`.
9899 *
9900 * @method attributeChangedCallback
9901 */
9902 attributeChangedCallback: function(name, oldValue) {
9903 // TODO(sjmiles): adhoc filter
9904 if (name !== 'class' && name !== 'style') {
9905 this.attributeToProperty(name, this.getAttribute(name));
9906 }
9907 if (this.attributeChanged) {
9908 this.attributeChanged.apply(this, arguments);
9909 }
9910 },
9911
9912 /**
9913 * Low-level lifecycle method called as part of standard Custom Elements
9914 * operation. Polymer implements this method to provide basic default
9915 * functionality. For custom create-time tasks, implement `attached`
9916 * instead, which is called immediately after `attachedCallback`.
9917 *
9918 * @method attachedCallback
9919 */
9920 attachedCallback: function() {
9921 // when the element is attached, prevent it from unbinding.
9922 this.cancelUnbindAll();
9923 // invoke user action
9924 if (this.attached) {
9925 this.attached();
9926 }
9927 if (!this.hasBeenAttached) {
9928 this.hasBeenAttached = true;
9929 if (this.domReady) {
9930 this.async('domReady');
9931 }
9932 }
9933 },
9934
9935 /**
9936 * Implement to access custom elements in dom descendants, ancestors,
9937 * or siblings. Because custom elements upgrade in document order,
9938 * elements accessed in `ready` or `attached` may not be upgraded. When
9939 * `domReady` is called, all registered custom elements are guaranteed
9940 * to have been upgraded.
9941 *
9942 * @method domReady
9943 */
9944
9945 /**
9946 * Low-level lifecycle method called as part of standard Custom Elements
9947 * operation. Polymer implements this method to provide basic default
9948 * functionality. For custom create-time tasks, implement `detached`
9949 * instead, which is called immediately after `detachedCallback`.
9950 *
9951 * @method detachedCallback
9952 */
9953 detachedCallback: function() {
9954 if (!this.preventDispose) {
9955 this.asyncUnbindAll();
9956 }
9957 // invoke user action
9958 if (this.detached) {
9959 this.detached();
9960 }
9961 // TODO(sorvell): bc
9962 if (this.leftView) {
9963 this.leftView();
9964 }
9965 },
9966
9967 /**
9968 * Walks the prototype-chain of this element and allows specific
9969 * classes a chance to process static declarations.
9970 *
9971 * In particular, each polymer-element has it's own `template`.
9972 * `parseDeclarations` is used to accumulate all element `template`s
9973 * from an inheritance chain.
9974 *
9975 * `parseDeclaration` static methods implemented in the chain are called
9976 * recursively, oldest first, with the `<polymer-element>` associated
9977 * with the current prototype passed as an argument.
9978 *
9979 * An element may override this method to customize shadow-root generation.
9980 *
9981 * @method parseDeclarations
9982 */
9983 parseDeclarations: function(p) {
9984 if (p && p.element) {
9985 this.parseDeclarations(p.__proto__);
9986 p.parseDeclaration.call(this, p.element);
9987 }
9988 },
9989
9990 /**
9991 * Perform init-time actions based on static information in the
9992 * `<polymer-element>` instance argument.
9993 *
9994 * For example, the standard implementation locates the template associated
9995 * with the given `<polymer-element>` and stamps it into a shadow-root to
9996 * implement shadow inheritance.
9997 *
9998 * An element may override this method for custom behavior.
9999 *
10000 * @method parseDeclaration
10001 */
10002 parseDeclaration: function(elementElement) {
10003 var template = this.fetchTemplate(elementElement);
10004 if (template) {
10005 var root = this.shadowFromTemplate(template);
10006 this.shadowRoots[elementElement.name] = root;
10007 }
10008 },
10009
10010 /**
10011 * Given a `<polymer-element>`, find an associated template (if any) to be
10012 * used for shadow-root generation.
10013 *
10014 * An element may override this method for custom behavior.
10015 *
10016 * @method fetchTemplate
10017 */
10018 fetchTemplate: function(elementElement) {
10019 return elementElement.querySelector('template');
10020 },
10021
10022 /**
10023 * Create a shadow-root in this host and stamp `template` as it's
10024 * content.
10025 *
10026 * An element may override this method for custom behavior.
10027 *
10028 * @method shadowFromTemplate
10029 */
10030 shadowFromTemplate: function(template) {
10031 if (template) {
10032 // make a shadow root
10033 var root = this.createShadowRoot();
10034 // stamp template
10035 // which includes parsing and applying MDV bindings before being
10036 // inserted (to avoid {{}} in attribute values)
10037 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404.
10038 var dom = this.instanceTemplate(template);
10039 // append to shadow dom
10040 root.appendChild(dom);
10041 // perform post-construction initialization tasks on shadow root
10042 this.shadowRootReady(root, template);
10043 // return the created shadow root
10044 return root;
10045 }
10046 },
10047
10048 // utility function that stamps a <template> into light-dom
10049 lightFromTemplate: function(template, refNode) {
10050 if (template) {
10051 // TODO(sorvell): mark this element as an eventController so that
10052 // event listeners on bound nodes inside it will be called on it.
10053 // Note, the expectation here is that events on all descendants
10054 // should be handled by this element.
10055 this.eventController = this;
10056 // stamp template
10057 // which includes parsing and applying MDV bindings before being
10058 // inserted (to avoid {{}} in attribute values)
10059 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404.
10060 var dom = this.instanceTemplate(template);
10061 // append to shadow dom
10062 if (refNode) {
10063 this.insertBefore(dom, refNode);
10064 } else {
10065 this.appendChild(dom);
10066 }
10067 // perform post-construction initialization tasks on ahem, light root
10068 this.shadowRootReady(this);
10069 // return the created shadow root
10070 return dom;
10071 }
10072 },
10073
10074 shadowRootReady: function(root) {
10075 // locate nodes with id and store references to them in this.$ hash
10076 this.marshalNodeReferences(root);
10077 },
10078
10079 // locate nodes with id and store references to them in this.$ hash
10080 marshalNodeReferences: function(root) {
10081 // establish $ instance variable
10082 var $ = this.$ = this.$ || {};
10083 // populate $ from nodes with ID from the LOCAL tree
10084 if (root) {
10085 var n$ = root.querySelectorAll("[id]");
10086 for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) {
10087 $[n.id] = n;
10088 };
10089 }
10090 },
10091
10092 /**
10093 * Register a one-time callback when a child-list or sub-tree mutation
10094 * occurs on node.
10095 *
10096 * For persistent callbacks, call onMutation from your listener.
10097 *
10098 * @method onMutation
10099 * @param Node {Node} node Node to watch for mutations.
10100 * @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.
10101 */
10102 onMutation: function(node, listener) {
10103 var observer = new MutationObserver(function(mutations) {
10104 listener.call(this, observer, mutations);
10105 observer.disconnect();
10106 }.bind(this));
10107 observer.observe(node, {childList: true, subtree: true});
10108 }
10109 };
10110
10111 /**
10112 * @class Polymer
10113 */
10114
10115 /**
10116 * Returns true if the object includes <a href="#polymer-base">polymer-base</a > in it's prototype chain.
10117 *
10118 * @method isBase
10119 * @param Object {Object} object Object to test.
10120 * @type Boolean
10121 */
10122 function isBase(object) {
10123 return object.hasOwnProperty('PolymerBase')
10124 }
10125
10126 // name a base constructor for dev tools
10127
10128 /**
10129 * The Polymer base-class constructor.
10130 *
10131 * @property Base
10132 * @type Function
10133 */
10134 function PolymerBase() {};
10135 PolymerBase.prototype = base;
10136 base.constructor = PolymerBase;
10137
10138 // exports
10139
10140 scope.Base = PolymerBase;
10141 scope.isBase = isBase;
10142 scope.api.instance.base = base;
10143
10144 })(Polymer);
10145
10146 (function(scope) {
10147
10148 // imports
10149
10150 var log = window.WebComponents ? WebComponents.flags.log : {};
10151 var hasShadowDOMPolyfill = window.ShadowDOMPolyfill;
10152
10153 // magic words
10154
10155 var STYLE_SCOPE_ATTRIBUTE = 'element';
10156 var STYLE_CONTROLLER_SCOPE = 'controller';
10157
10158 var styles = {
10159 STYLE_SCOPE_ATTRIBUTE: STYLE_SCOPE_ATTRIBUTE,
10160 /**
10161 * Installs external stylesheets and <style> elements with the attribute
10162 * polymer-scope='controller' into the scope of element. This is intended
10163 * to be a called during custom element construction.
10164 */
10165 installControllerStyles: function() {
10166 // apply controller styles, but only if they are not yet applied
10167 var scope = this.findStyleScope();
10168 if (scope && !this.scopeHasNamedStyle(scope, this.localName)) {
10169 // allow inherited controller styles
10170 var proto = getPrototypeOf(this), cssText = '';
10171 while (proto && proto.element) {
10172 cssText += proto.element.cssTextForScope(STYLE_CONTROLLER_SCOPE);
10173 proto = getPrototypeOf(proto);
10174 }
10175 if (cssText) {
10176 this.installScopeCssText(cssText, scope);
10177 }
10178 }
10179 },
10180 installScopeStyle: function(style, name, scope) {
10181 var scope = scope || this.findStyleScope(), name = name || '';
10182 if (scope && !this.scopeHasNamedStyle(scope, this.localName + name)) {
10183 var cssText = '';
10184 if (style instanceof Array) {
10185 for (var i=0, l=style.length, s; (i<l) && (s=style[i]); i++) {
10186 cssText += s.textContent + '\n\n';
10187 }
10188 } else {
10189 cssText = style.textContent;
10190 }
10191 this.installScopeCssText(cssText, scope, name);
10192 }
10193 },
10194 installScopeCssText: function(cssText, scope, name) {
10195 scope = scope || this.findStyleScope();
10196 name = name || '';
10197 if (!scope) {
10198 return;
10199 }
10200 if (hasShadowDOMPolyfill) {
10201 cssText = shimCssText(cssText, scope.host);
10202 }
10203 var style = this.element.cssTextToScopeStyle(cssText,
10204 STYLE_CONTROLLER_SCOPE);
10205 Polymer.applyStyleToScope(style, scope);
10206 // cache that this style has been applied
10207 this.styleCacheForScope(scope)[this.localName + name] = true;
10208 },
10209 findStyleScope: function(node) {
10210 // find the shadow root that contains this element
10211 var n = node || this;
10212 while (n.parentNode) {
10213 n = n.parentNode;
10214 }
10215 return n;
10216 },
10217 scopeHasNamedStyle: function(scope, name) {
10218 var cache = this.styleCacheForScope(scope);
10219 return cache[name];
10220 },
10221 styleCacheForScope: function(scope) {
10222 if (hasShadowDOMPolyfill) {
10223 var scopeName = scope.host ? scope.host.localName : scope.localName;
10224 return polyfillScopeStyleCache[scopeName] || (polyfillScopeStyleCache[sc opeName] = {});
10225 } else {
10226 return scope._scopeStyles = (scope._scopeStyles || {});
10227 }
10228 }
10229 };
10230
10231 var polyfillScopeStyleCache = {};
10232
10233 // NOTE: use raw prototype traversal so that we ensure correct traversal
10234 // on platforms where the protoype chain is simulated via __proto__ (IE10)
10235 function getPrototypeOf(prototype) {
10236 return prototype.__proto__;
10237 }
10238
10239 function shimCssText(cssText, host) {
10240 var name = '', is = false;
10241 if (host) {
10242 name = host.localName;
10243 is = host.hasAttribute('is');
10244 }
10245 var selector = WebComponents.ShadowCSS.makeScopeSelector(name, is);
10246 return WebComponents.ShadowCSS.shimCssText(cssText, selector);
10247 }
10248
10249 // exports
10250
10251 scope.api.instance.styles = styles;
10252
10253 })(Polymer);
10254
10255 (function(scope) {
10256
10257 // imports
10258
10259 var extend = scope.extend;
10260 var api = scope.api;
10261
10262 // imperative implementation: Polymer()
10263
10264 // specify an 'own' prototype for tag `name`
10265 function element(name, prototype) {
10266 if (typeof name !== 'string') {
10267 var script = prototype || document._currentScript;
10268 prototype = name;
10269 name = script && script.parentNode && script.parentNode.getAttribute ?
10270 script.parentNode.getAttribute('name') : '';
10271 if (!name) {
10272 throw 'Element name could not be inferred.';
10273 }
10274 }
10275 if (getRegisteredPrototype(name)) {
10276 throw 'Already registered (Polymer) prototype for element ' + name;
10277 }
10278 // cache the prototype
10279 registerPrototype(name, prototype);
10280 // notify the registrar waiting for 'name', if any
10281 notifyPrototype(name);
10282 }
10283
10284 // async prototype source
10285
10286 function waitingForPrototype(name, client) {
10287 waitPrototype[name] = client;
10288 }
10289
10290 var waitPrototype = {};
10291
10292 function notifyPrototype(name) {
10293 if (waitPrototype[name]) {
10294 waitPrototype[name].registerWhenReady();
10295 delete waitPrototype[name];
10296 }
10297 }
10298
10299 // utility and bookkeeping
10300
10301 // maps tag names to prototypes, as registered with
10302 // Polymer. Prototypes associated with a tag name
10303 // using document.registerElement are available from
10304 // HTMLElement.getPrototypeForTag().
10305 // If an element was fully registered by Polymer, then
10306 // Polymer.getRegisteredPrototype(name) ===
10307 // HTMLElement.getPrototypeForTag(name)
10308
10309 var prototypesByName = {};
10310
10311 function registerPrototype(name, prototype) {
10312 return prototypesByName[name] = prototype || {};
10313 }
10314
10315 function getRegisteredPrototype(name) {
10316 return prototypesByName[name];
10317 }
10318
10319 function instanceOfType(element, type) {
10320 if (typeof type !== 'string') {
10321 return false;
10322 }
10323 var proto = HTMLElement.getPrototypeForTag(type);
10324 var ctor = proto && proto.constructor;
10325 if (!ctor) {
10326 return false;
10327 }
10328 if (CustomElements.instanceof) {
10329 return CustomElements.instanceof(element, ctor);
10330 }
10331 return element instanceof ctor;
10332 }
10333
10334 // exports
10335
10336 scope.getRegisteredPrototype = getRegisteredPrototype;
10337 scope.waitingForPrototype = waitingForPrototype;
10338 scope.instanceOfType = instanceOfType;
10339
10340 // namespace shenanigans so we can expose our scope on the registration
10341 // function
10342
10343 // make window.Polymer reference `element()`
10344
10345 window.Polymer = element;
10346
10347 // TODO(sjmiles): find a way to do this that is less terrible
10348 // copy window.Polymer properties onto `element()`
10349
10350 extend(Polymer, scope);
10351
10352 // Under the HTMLImports polyfill, scripts in the main document
10353 // do not block on imports; we want to allow calls to Polymer in the main
10354 // document. WebComponents collects those calls until we can process them, whi ch
10355 // we do here.
10356
10357 if (WebComponents.consumeDeclarations) {
10358 WebComponents.consumeDeclarations(function(declarations) {
10359 if (declarations) {
10360 for (var i=0, l=declarations.length, d; (i<l) && (d=declarations[i]); i+ +) {
10361 element.apply(null, d);
10362 }
10363 }
10364 });
10365 }
10366
10367 })(Polymer);
10368
10369 (function(scope) {
10370
10371 /**
10372 * @class polymer-base
10373 */
10374
10375 /**
10376 * Resolve a url path to be relative to a `base` url. If unspecified, `base`
10377 * defaults to the element's ownerDocument url. Can be used to resolve
10378 * paths from element's in templates loaded in HTMLImports to be relative
10379 * to the document containing the element. Polymer automatically does this for
10380 * url attributes in element templates; however, if a url, for
10381 * example, contains a binding, then `resolvePath` can be used to ensure it is
10382 * relative to the element document. For example, in an element's template,
10383 *
10384 * <a href="{{resolvePath(path)}}">Resolved</a>
10385 *
10386 * @method resolvePath
10387 * @param {String} url Url path to resolve.
10388 * @param {String} base Optional base url against which to resolve, defaults
10389 * to the element's ownerDocument url.
10390 * returns {String} resolved url.
10391 */
10392
10393 var path = {
10394 resolveElementPaths: function(node) {
10395 Polymer.urlResolver.resolveDom(node);
10396 },
10397 addResolvePathApi: function() {
10398 // let assetpath attribute modify the resolve path
10399 var assetPath = this.getAttribute('assetpath') || '';
10400 var root = new URL(assetPath, this.ownerDocument.baseURI);
10401 this.prototype.resolvePath = function(urlPath, base) {
10402 var u = new URL(urlPath, base || root);
10403 return u.href;
10404 };
10405 }
10406 };
10407
10408 // exports
10409 scope.api.declaration.path = path;
10410
10411 })(Polymer);
10412
10413 (function(scope) {
10414
10415 // imports
10416
10417 var log = window.WebComponents ? WebComponents.flags.log : {};
10418 var api = scope.api.instance.styles;
10419 var STYLE_SCOPE_ATTRIBUTE = api.STYLE_SCOPE_ATTRIBUTE;
10420
10421 var hasShadowDOMPolyfill = window.ShadowDOMPolyfill;
10422
10423 // magic words
10424
10425 var STYLE_SELECTOR = 'style';
10426 var STYLE_LOADABLE_MATCH = '@import';
10427 var SHEET_SELECTOR = 'link[rel=stylesheet]';
10428 var STYLE_GLOBAL_SCOPE = 'global';
10429 var SCOPE_ATTR = 'polymer-scope';
10430
10431 var styles = {
10432 // returns true if resources are loading
10433 loadStyles: function(callback) {
10434 var template = this.fetchTemplate();
10435 var content = template && this.templateContent();
10436 if (content) {
10437 this.convertSheetsToStyles(content);
10438 var styles = this.findLoadableStyles(content);
10439 if (styles.length) {
10440 var templateUrl = template.ownerDocument.baseURI;
10441 return Polymer.styleResolver.loadStyles(styles, templateUrl, callback) ;
10442 }
10443 }
10444 if (callback) {
10445 callback();
10446 }
10447 },
10448 convertSheetsToStyles: function(root) {
10449 var s$ = root.querySelectorAll(SHEET_SELECTOR);
10450 for (var i=0, l=s$.length, s, c; (i<l) && (s=s$[i]); i++) {
10451 c = createStyleElement(importRuleForSheet(s, this.ownerDocument.baseURI) ,
10452 this.ownerDocument);
10453 this.copySheetAttributes(c, s);
10454 s.parentNode.replaceChild(c, s);
10455 }
10456 },
10457 copySheetAttributes: function(style, link) {
10458 for (var i=0, a$=link.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++) {
10459 if (a.name !== 'rel' && a.name !== 'href') {
10460 style.setAttribute(a.name, a.value);
10461 }
10462 }
10463 },
10464 findLoadableStyles: function(root) {
10465 var loadables = [];
10466 if (root) {
10467 var s$ = root.querySelectorAll(STYLE_SELECTOR);
10468 for (var i=0, l=s$.length, s; (i<l) && (s=s$[i]); i++) {
10469 if (s.textContent.match(STYLE_LOADABLE_MATCH)) {
10470 loadables.push(s);
10471 }
10472 }
10473 }
10474 return loadables;
10475 },
10476 /**
10477 * Install external stylesheets loaded in <polymer-element> elements into th e
10478 * element's template.
10479 * @param elementElement The <element> element to style.
10480 */
10481 installSheets: function() {
10482 this.cacheSheets();
10483 this.cacheStyles();
10484 this.installLocalSheets();
10485 this.installGlobalStyles();
10486 },
10487 /**
10488 * Remove all sheets from element and store for later use.
10489 */
10490 cacheSheets: function() {
10491 this.sheets = this.findNodes(SHEET_SELECTOR);
10492 this.sheets.forEach(function(s) {
10493 if (s.parentNode) {
10494 s.parentNode.removeChild(s);
10495 }
10496 });
10497 },
10498 cacheStyles: function() {
10499 this.styles = this.findNodes(STYLE_SELECTOR + '[' + SCOPE_ATTR + ']');
10500 this.styles.forEach(function(s) {
10501 if (s.parentNode) {
10502 s.parentNode.removeChild(s);
10503 }
10504 });
10505 },
10506 /**
10507 * Takes external stylesheets loaded in an <element> element and moves
10508 * their content into a <style> element inside the <element>'s template.
10509 * The sheet is then removed from the <element>. This is done only so
10510 * that if the element is loaded in the main document, the sheet does
10511 * not become active.
10512 * Note, ignores sheets with the attribute 'polymer-scope'.
10513 * @param elementElement The <element> element to style.
10514 */
10515 installLocalSheets: function () {
10516 var sheets = this.sheets.filter(function(s) {
10517 return !s.hasAttribute(SCOPE_ATTR);
10518 });
10519 var content = this.templateContent();
10520 if (content) {
10521 var cssText = '';
10522 sheets.forEach(function(sheet) {
10523 cssText += cssTextFromSheet(sheet) + '\n';
10524 });
10525 if (cssText) {
10526 var style = createStyleElement(cssText, this.ownerDocument);
10527 content.insertBefore(style, content.firstChild);
10528 }
10529 }
10530 },
10531 findNodes: function(selector, matcher) {
10532 var nodes = this.querySelectorAll(selector).array();
10533 var content = this.templateContent();
10534 if (content) {
10535 var templateNodes = content.querySelectorAll(selector).array();
10536 nodes = nodes.concat(templateNodes);
10537 }
10538 return matcher ? nodes.filter(matcher) : nodes;
10539 },
10540 /**
10541 * Promotes external stylesheets and <style> elements with the attribute
10542 * polymer-scope='global' into global scope.
10543 * This is particularly useful for defining @keyframe rules which
10544 * currently do not function in scoped or shadow style elements.
10545 * (See wkb.ug/72462)
10546 * @param elementElement The <element> element to style.
10547 */
10548 // TODO(sorvell): remove when wkb.ug/72462 is addressed.
10549 installGlobalStyles: function() {
10550 var style = this.styleForScope(STYLE_GLOBAL_SCOPE);
10551 applyStyleToScope(style, document.head);
10552 },
10553 cssTextForScope: function(scopeDescriptor) {
10554 var cssText = '';
10555 // handle stylesheets
10556 var selector = '[' + SCOPE_ATTR + '=' + scopeDescriptor + ']';
10557 var matcher = function(s) {
10558 return matchesSelector(s, selector);
10559 };
10560 var sheets = this.sheets.filter(matcher);
10561 sheets.forEach(function(sheet) {
10562 cssText += cssTextFromSheet(sheet) + '\n\n';
10563 });
10564 // handle cached style elements
10565 var styles = this.styles.filter(matcher);
10566 styles.forEach(function(style) {
10567 cssText += style.textContent + '\n\n';
10568 });
10569 return cssText;
10570 },
10571 styleForScope: function(scopeDescriptor) {
10572 var cssText = this.cssTextForScope(scopeDescriptor);
10573 return this.cssTextToScopeStyle(cssText, scopeDescriptor);
10574 },
10575 cssTextToScopeStyle: function(cssText, scopeDescriptor) {
10576 if (cssText) {
10577 var style = createStyleElement(cssText);
10578 style.setAttribute(STYLE_SCOPE_ATTRIBUTE, this.getAttribute('name') +
10579 '-' + scopeDescriptor);
10580 return style;
10581 }
10582 }
10583 };
10584
10585 function importRuleForSheet(sheet, baseUrl) {
10586 var href = new URL(sheet.getAttribute('href'), baseUrl).href;
10587 return '@import \'' + href + '\';';
10588 }
10589
10590 function applyStyleToScope(style, scope) {
10591 if (style) {
10592 if (scope === document) {
10593 scope = document.head;
10594 }
10595 if (hasShadowDOMPolyfill) {
10596 scope = document.head;
10597 }
10598 // TODO(sorvell): necessary for IE
10599 // see https://connect.microsoft.com/IE/feedback/details/790212/
10600 // cloning-a-style-element-and-adding-to-document-produces
10601 // -unexpected-result#details
10602 // var clone = style.cloneNode(true);
10603 var clone = createStyleElement(style.textContent);
10604 var attr = style.getAttribute(STYLE_SCOPE_ATTRIBUTE);
10605 if (attr) {
10606 clone.setAttribute(STYLE_SCOPE_ATTRIBUTE, attr);
10607 }
10608 // TODO(sorvell): probably too brittle; try to figure out
10609 // where to put the element.
10610 var refNode = scope.firstElementChild;
10611 if (scope === document.head) {
10612 var selector = 'style[' + STYLE_SCOPE_ATTRIBUTE + ']';
10613 var s$ = document.head.querySelectorAll(selector);
10614 if (s$.length) {
10615 refNode = s$[s$.length-1].nextElementSibling;
10616 }
10617 }
10618 scope.insertBefore(clone, refNode);
10619 }
10620 }
10621
10622 function createStyleElement(cssText, scope) {
10623 scope = scope || document;
10624 scope = scope.createElement ? scope : scope.ownerDocument;
10625 var style = scope.createElement('style');
10626 style.textContent = cssText;
10627 return style;
10628 }
10629
10630 function cssTextFromSheet(sheet) {
10631 return (sheet && sheet.__resource) || '';
10632 }
10633
10634 function matchesSelector(node, inSelector) {
10635 if (matches) {
10636 return matches.call(node, inSelector);
10637 }
10638 }
10639 var p = HTMLElement.prototype;
10640 var matches = p.matches || p.matchesSelector || p.webkitMatchesSelector
10641 || p.mozMatchesSelector;
10642
10643 // exports
10644
10645 scope.api.declaration.styles = styles;
10646 scope.applyStyleToScope = applyStyleToScope;
10647
10648 })(Polymer);
10649
10650 (function(scope) {
10651
10652 // imports
10653
10654 var log = window.WebComponents ? WebComponents.flags.log : {};
10655 var api = scope.api.instance.events;
10656 var EVENT_PREFIX = api.EVENT_PREFIX;
10657
10658 var mixedCaseEventTypes = {};
10659 [
10660 'webkitAnimationStart',
10661 'webkitAnimationEnd',
10662 'webkitTransitionEnd',
10663 'DOMFocusOut',
10664 'DOMFocusIn',
10665 'DOMMouseScroll'
10666 ].forEach(function(e) {
10667 mixedCaseEventTypes[e.toLowerCase()] = e;
10668 });
10669
10670 // polymer-element declarative api: events feature
10671 var events = {
10672 parseHostEvents: function() {
10673 // our delegates map
10674 var delegates = this.prototype.eventDelegates;
10675 // extract data from attributes into delegates
10676 this.addAttributeDelegates(delegates);
10677 },
10678 addAttributeDelegates: function(delegates) {
10679 // for each attribute
10680 for (var i=0, a; a=this.attributes[i]; i++) {
10681 // does it have magic marker identifying it as an event delegate?
10682 if (this.hasEventPrefix(a.name)) {
10683 // if so, add the info to delegates
10684 delegates[this.removeEventPrefix(a.name)] = a.value.replace('{{', '')
10685 .replace('}}', '').trim();
10686 }
10687 }
10688 },
10689 // starts with 'on-'
10690 hasEventPrefix: function (n) {
10691 return n && (n[0] === 'o') && (n[1] === 'n') && (n[2] === '-');
10692 },
10693 removeEventPrefix: function(n) {
10694 return n.slice(prefixLength);
10695 },
10696 findController: function(node) {
10697 while (node.parentNode) {
10698 if (node.eventController) {
10699 return node.eventController;
10700 }
10701 node = node.parentNode;
10702 }
10703 return node.host;
10704 },
10705 getEventHandler: function(controller, target, method) {
10706 var events = this;
10707 return function(e) {
10708 if (!controller || !controller.PolymerBase) {
10709 controller = events.findController(target);
10710 }
10711
10712 var args = [e, e.detail, e.currentTarget];
10713 controller.dispatchMethod(controller, method, args);
10714 };
10715 },
10716 prepareEventBinding: function(pathString, name, node) {
10717 if (!this.hasEventPrefix(name))
10718 return;
10719
10720 var eventType = this.removeEventPrefix(name);
10721 eventType = mixedCaseEventTypes[eventType] || eventType;
10722
10723 var events = this;
10724
10725 return function(model, node, oneTime) {
10726 var handler = events.getEventHandler(undefined, node, pathString);
10727 PolymerGestures.addEventListener(node, eventType, handler);
10728
10729 if (oneTime)
10730 return;
10731
10732 // TODO(rafaelw): This is really pointless work. Aside from the cost
10733 // of these allocations, NodeBind is going to setAttribute back to its
10734 // current value. Fixing this would mean changing the TemplateBinding
10735 // binding delegate API.
10736 function bindingValue() {
10737 return '{{ ' + pathString + ' }}';
10738 }
10739
10740 return {
10741 open: bindingValue,
10742 discardChanges: bindingValue,
10743 close: function() {
10744 PolymerGestures.removeEventListener(node, eventType, handler);
10745 }
10746 };
10747 };
10748 }
10749 };
10750
10751 var prefixLength = EVENT_PREFIX.length;
10752
10753 // exports
10754 scope.api.declaration.events = events;
10755
10756 })(Polymer);
10757
10758 (function(scope) {
10759
10760 // element api
10761
10762 var observationBlacklist = ['attribute'];
10763
10764 var properties = {
10765 inferObservers: function(prototype) {
10766 // called before prototype.observe is chained to inherited object
10767 var observe = prototype.observe, property;
10768 for (var n in prototype) {
10769 if (n.slice(-7) === 'Changed') {
10770 property = n.slice(0, -7);
10771 if (this.canObserveProperty(property)) {
10772 if (!observe) {
10773 observe = (prototype.observe = {});
10774 }
10775 observe[property] = observe[property] || n;
10776 }
10777 }
10778 }
10779 },
10780 canObserveProperty: function(property) {
10781 return (observationBlacklist.indexOf(property) < 0);
10782 },
10783 explodeObservers: function(prototype) {
10784 // called before prototype.observe is chained to inherited object
10785 var o = prototype.observe;
10786 if (o) {
10787 var exploded = {};
10788 for (var n in o) {
10789 var names = n.split(' ');
10790 for (var i=0, ni; ni=names[i]; i++) {
10791 exploded[ni] = o[n];
10792 }
10793 }
10794 prototype.observe = exploded;
10795 }
10796 },
10797 optimizePropertyMaps: function(prototype) {
10798 if (prototype.observe) {
10799 // construct name list
10800 var a = prototype._observeNames = [];
10801 for (var n in prototype.observe) {
10802 var names = n.split(' ');
10803 for (var i=0, ni; ni=names[i]; i++) {
10804 a.push(ni);
10805 }
10806 }
10807 }
10808 if (prototype.publish) {
10809 // construct name list
10810 var a = prototype._publishNames = [];
10811 for (var n in prototype.publish) {
10812 a.push(n);
10813 }
10814 }
10815 if (prototype.computed) {
10816 // construct name list
10817 var a = prototype._computedNames = [];
10818 for (var n in prototype.computed) {
10819 a.push(n);
10820 }
10821 }
10822 },
10823 publishProperties: function(prototype, base) {
10824 // if we have any properties to publish
10825 var publish = prototype.publish;
10826 if (publish) {
10827 // transcribe `publish` entries onto own prototype
10828 this.requireProperties(publish, prototype, base);
10829 // warn and remove accessor names that are broken on some browsers
10830 this.filterInvalidAccessorNames(publish);
10831 // construct map of lower-cased property names
10832 prototype._publishLC = this.lowerCaseMap(publish);
10833 }
10834 var computed = prototype.computed;
10835 if (computed) {
10836 // warn and remove accessor names that are broken on some browsers
10837 this.filterInvalidAccessorNames(computed);
10838 }
10839 },
10840 // Publishing/computing a property where the name might conflict with a
10841 // browser property is not currently supported to help users of Polymer
10842 // avoid browser bugs:
10843 //
10844 // https://code.google.com/p/chromium/issues/detail?id=43394
10845 // https://bugs.webkit.org/show_bug.cgi?id=49739
10846 //
10847 // We can lift this restriction when those bugs are fixed.
10848 filterInvalidAccessorNames: function(propertyNames) {
10849 for (var name in propertyNames) {
10850 // Check if the name is in our blacklist.
10851 if (this.propertyNameBlacklist[name]) {
10852 console.warn('Cannot define property "' + name + '" for element "' +
10853 this.name + '" because it has the same name as an HTMLElement ' +
10854 'property, and not all browsers support overriding that. ' +
10855 'Consider giving it a different name.');
10856 // Remove the invalid accessor from the list.
10857 delete propertyNames[name];
10858 }
10859 }
10860 },
10861 //
10862 // `name: value` entries in the `publish` object may need to generate
10863 // matching properties on the prototype.
10864 //
10865 // Values that are objects may have a `reflect` property, which
10866 // signals that the value describes property control metadata.
10867 // In metadata objects, the prototype default value (if any)
10868 // is encoded in the `value` property.
10869 //
10870 // publish: {
10871 // foo: 5,
10872 // bar: {value: true, reflect: true},
10873 // zot: {}
10874 // }
10875 //
10876 // `reflect` metadata property controls whether changes to the property
10877 // are reflected back to the attribute (default false).
10878 //
10879 // A value is stored on the prototype unless it's === `undefined`,
10880 // in which case the base chain is checked for a value.
10881 // If the basal value is also undefined, `null` is stored on the prototype.
10882 //
10883 // The reflection data is stored on another prototype object, `reflect`
10884 // which also can be specified directly.
10885 //
10886 // reflect: {
10887 // foo: true
10888 // }
10889 //
10890 requireProperties: function(propertyInfos, prototype, base) {
10891 // per-prototype storage for reflected properties
10892 prototype.reflect = prototype.reflect || {};
10893 // ensure a prototype value for each property
10894 // and update the property's reflect to attribute status
10895 for (var n in propertyInfos) {
10896 var value = propertyInfos[n];
10897 // value has metadata if it has a `reflect` property
10898 if (value && value.reflect !== undefined) {
10899 prototype.reflect[n] = Boolean(value.reflect);
10900 value = value.value;
10901 }
10902 // only set a value if one is specified
10903 if (value !== undefined) {
10904 prototype[n] = value;
10905 }
10906 }
10907 },
10908 lowerCaseMap: function(properties) {
10909 var map = {};
10910 for (var n in properties) {
10911 map[n.toLowerCase()] = n;
10912 }
10913 return map;
10914 },
10915 createPropertyAccessor: function(name, ignoreWrites) {
10916 var proto = this.prototype;
10917
10918 var privateName = name + '_';
10919 var privateObservable = name + 'Observable_';
10920 proto[privateName] = proto[name];
10921
10922 Object.defineProperty(proto, name, {
10923 get: function() {
10924 var observable = this[privateObservable];
10925 if (observable)
10926 observable.deliver();
10927
10928 return this[privateName];
10929 },
10930 set: function(value) {
10931 if (ignoreWrites) {
10932 return this[privateName];
10933 }
10934
10935 var observable = this[privateObservable];
10936 if (observable) {
10937 observable.setValue(value);
10938 return;
10939 }
10940
10941 var oldValue = this[privateName];
10942 this[privateName] = value;
10943 this.emitPropertyChangeRecord(name, value, oldValue);
10944
10945 return value;
10946 },
10947 configurable: true
10948 });
10949 },
10950 createPropertyAccessors: function(prototype) {
10951 var n$ = prototype._computedNames;
10952 if (n$ && n$.length) {
10953 for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) {
10954 this.createPropertyAccessor(n, true);
10955 }
10956 }
10957 var n$ = prototype._publishNames;
10958 if (n$ && n$.length) {
10959 for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) {
10960 // If the property is computed and published, the accessor is created
10961 // above.
10962 if (!prototype.computed || !prototype.computed[n]) {
10963 this.createPropertyAccessor(n);
10964 }
10965 }
10966 }
10967 },
10968 // This list contains some property names that people commonly want to use,
10969 // but won't work because of Chrome/Safari bugs. It isn't an exhaustive
10970 // list. In particular it doesn't contain any property names found on
10971 // subtypes of HTMLElement (e.g. name, value). Rather it attempts to catch
10972 // some common cases.
10973 propertyNameBlacklist: {
10974 children: 1,
10975 'class': 1,
10976 id: 1,
10977 hidden: 1,
10978 style: 1,
10979 title: 1,
10980 }
10981 };
10982
10983 // exports
10984
10985 scope.api.declaration.properties = properties;
10986
10987 })(Polymer);
10988
10989 (function(scope) {
10990
10991 // magic words
10992
10993 var ATTRIBUTES_ATTRIBUTE = 'attributes';
10994 var ATTRIBUTES_REGEX = /\s|,/;
10995
10996 // attributes api
10997
10998 var attributes = {
10999
11000 inheritAttributesObjects: function(prototype) {
11001 // chain our lower-cased publish map to the inherited version
11002 this.inheritObject(prototype, 'publishLC');
11003 // chain our instance attributes map to the inherited version
11004 this.inheritObject(prototype, '_instanceAttributes');
11005 },
11006
11007 publishAttributes: function(prototype, base) {
11008 // merge names from 'attributes' attribute into the 'publish' object
11009 var attributes = this.getAttribute(ATTRIBUTES_ATTRIBUTE);
11010 if (attributes) {
11011 // create a `publish` object if needed.
11012 // the `publish` object is only relevant to this prototype, the
11013 // publishing logic in `declaration/properties.js` is responsible for
11014 // managing property values on the prototype chain.
11015 // TODO(sjmiles): the `publish` object is later chained to it's
11016 // ancestor object, presumably this is only for
11017 // reflection or other non-library uses.
11018 var publish = prototype.publish || (prototype.publish = {});
11019 // names='a b c' or names='a,b,c'
11020 var names = attributes.split(ATTRIBUTES_REGEX);
11021 // record each name for publishing
11022 for (var i=0, l=names.length, n; i<l; i++) {
11023 // remove excess ws
11024 n = names[i].trim();
11025 // looks weird, but causes n to exist on `publish` if it does not;
11026 // a more careful test would need expensive `in` operator
11027 if (n && publish[n] === undefined) {
11028 publish[n] = undefined;
11029 }
11030 }
11031 }
11032 },
11033
11034 // record clonable attributes from <element>
11035 accumulateInstanceAttributes: function() {
11036 // inherit instance attributes
11037 var clonable = this.prototype._instanceAttributes;
11038 // merge attributes from element
11039 var a$ = this.attributes;
11040 for (var i=0, l=a$.length, a; (i<l) && (a=a$[i]); i++) {
11041 if (this.isInstanceAttribute(a.name)) {
11042 clonable[a.name] = a.value;
11043 }
11044 }
11045 },
11046
11047 isInstanceAttribute: function(name) {
11048 return !this.blackList[name] && name.slice(0,3) !== 'on-';
11049 },
11050
11051 // do not clone these attributes onto instances
11052 blackList: {
11053 name: 1,
11054 'extends': 1,
11055 constructor: 1,
11056 noscript: 1,
11057 assetpath: 1,
11058 'cache-csstext': 1
11059 }
11060
11061 };
11062
11063 // add ATTRIBUTES_ATTRIBUTE to the blacklist
11064 attributes.blackList[ATTRIBUTES_ATTRIBUTE] = 1;
11065
11066 // exports
11067
11068 scope.api.declaration.attributes = attributes;
11069
11070 })(Polymer);
11071
11072 (function(scope) {
11073
11074 // imports
11075 var events = scope.api.declaration.events;
11076
11077 var syntax = new PolymerExpressions();
11078 var prepareBinding = syntax.prepareBinding;
11079
11080 // Polymer takes a first crack at the binding to see if it's a declarative
11081 // event handler.
11082 syntax.prepareBinding = function(pathString, name, node) {
11083 return events.prepareEventBinding(pathString, name, node) ||
11084 prepareBinding.call(syntax, pathString, name, node);
11085 };
11086
11087 // declaration api supporting mdv
11088 var mdv = {
11089 syntax: syntax,
11090 fetchTemplate: function() {
11091 return this.querySelector('template');
11092 },
11093 templateContent: function() {
11094 var template = this.fetchTemplate();
11095 return template && template.content;
11096 },
11097 installBindingDelegate: function(template) {
11098 if (template) {
11099 template.bindingDelegate = this.syntax;
11100 }
11101 }
11102 };
11103
11104 // exports
11105 scope.api.declaration.mdv = mdv;
11106
11107 })(Polymer);
11108
11109 (function(scope) {
11110
11111 // imports
11112
11113 var api = scope.api;
11114 var isBase = scope.isBase;
11115 var extend = scope.extend;
11116
11117 var hasShadowDOMPolyfill = window.ShadowDOMPolyfill;
11118
11119 // prototype api
11120
11121 var prototype = {
11122
11123 register: function(name, extendeeName) {
11124 // build prototype combining extendee, Polymer base, and named api
11125 this.buildPrototype(name, extendeeName);
11126 // register our custom element with the platform
11127 this.registerPrototype(name, extendeeName);
11128 // reference constructor in a global named by 'constructor' attribute
11129 this.publishConstructor();
11130 },
11131
11132 buildPrototype: function(name, extendeeName) {
11133 // get our custom prototype (before chaining)
11134 var extension = scope.getRegisteredPrototype(name);
11135 // get basal prototype
11136 var base = this.generateBasePrototype(extendeeName);
11137 // implement declarative features
11138 this.desugarBeforeChaining(extension, base);
11139 // join prototypes
11140 this.prototype = this.chainPrototypes(extension, base);
11141 // more declarative features
11142 this.desugarAfterChaining(name, extendeeName);
11143 },
11144
11145 desugarBeforeChaining: function(prototype, base) {
11146 // back reference declaration element
11147 // TODO(sjmiles): replace `element` with `elementElement` or `declaration`
11148 prototype.element = this;
11149 // transcribe `attributes` declarations onto own prototype's `publish`
11150 this.publishAttributes(prototype, base);
11151 // `publish` properties to the prototype and to attribute watch
11152 this.publishProperties(prototype, base);
11153 // infer observers for `observe` list based on method names
11154 this.inferObservers(prototype);
11155 // desugar compound observer syntax, e.g. 'a b c'
11156 this.explodeObservers(prototype);
11157 },
11158
11159 chainPrototypes: function(prototype, base) {
11160 // chain various meta-data objects to inherited versions
11161 this.inheritMetaData(prototype, base);
11162 // chain custom api to inherited
11163 var chained = this.chainObject(prototype, base);
11164 // x-platform fixup
11165 ensurePrototypeTraversal(chained);
11166 return chained;
11167 },
11168
11169 inheritMetaData: function(prototype, base) {
11170 // chain observe object to inherited
11171 this.inheritObject('observe', prototype, base);
11172 // chain publish object to inherited
11173 this.inheritObject('publish', prototype, base);
11174 // chain reflect object to inherited
11175 this.inheritObject('reflect', prototype, base);
11176 // chain our lower-cased publish map to the inherited version
11177 this.inheritObject('_publishLC', prototype, base);
11178 // chain our instance attributes map to the inherited version
11179 this.inheritObject('_instanceAttributes', prototype, base);
11180 // chain our event delegates map to the inherited version
11181 this.inheritObject('eventDelegates', prototype, base);
11182 },
11183
11184 // implement various declarative features
11185 desugarAfterChaining: function(name, extendee) {
11186 // build side-chained lists to optimize iterations
11187 this.optimizePropertyMaps(this.prototype);
11188 this.createPropertyAccessors(this.prototype);
11189 // install mdv delegate on template
11190 this.installBindingDelegate(this.fetchTemplate());
11191 // install external stylesheets as if they are inline
11192 this.installSheets();
11193 // adjust any paths in dom from imports
11194 this.resolveElementPaths(this);
11195 // compile list of attributes to copy to instances
11196 this.accumulateInstanceAttributes();
11197 // parse on-* delegates declared on `this` element
11198 this.parseHostEvents();
11199 //
11200 // install a helper method this.resolvePath to aid in
11201 // setting resource urls. e.g.
11202 // this.$.image.src = this.resolvePath('images/foo.png')
11203 this.addResolvePathApi();
11204 // under ShadowDOMPolyfill, transforms to approximate missing CSS features
11205 if (hasShadowDOMPolyfill) {
11206 WebComponents.ShadowCSS.shimStyling(this.templateContent(), name,
11207 extendee);
11208 }
11209 // allow custom element access to the declarative context
11210 if (this.prototype.registerCallback) {
11211 this.prototype.registerCallback(this);
11212 }
11213 },
11214
11215 // if a named constructor is requested in element, map a reference
11216 // to the constructor to the given symbol
11217 publishConstructor: function() {
11218 var symbol = this.getAttribute('constructor');
11219 if (symbol) {
11220 window[symbol] = this.ctor;
11221 }
11222 },
11223
11224 // build prototype combining extendee, Polymer base, and named api
11225 generateBasePrototype: function(extnds) {
11226 var prototype = this.findBasePrototype(extnds);
11227 if (!prototype) {
11228 // create a prototype based on tag-name extension
11229 var prototype = HTMLElement.getPrototypeForTag(extnds);
11230 // insert base api in inheritance chain (if needed)
11231 prototype = this.ensureBaseApi(prototype);
11232 // memoize this base
11233 memoizedBases[extnds] = prototype;
11234 }
11235 return prototype;
11236 },
11237
11238 findBasePrototype: function(name) {
11239 return memoizedBases[name];
11240 },
11241
11242 // install Polymer instance api into prototype chain, as needed
11243 ensureBaseApi: function(prototype) {
11244 if (prototype.PolymerBase) {
11245 return prototype;
11246 }
11247 var extended = Object.create(prototype);
11248 // we need a unique copy of base api for each base prototype
11249 // therefore we 'extend' here instead of simply chaining
11250 api.publish(api.instance, extended);
11251 // TODO(sjmiles): sharing methods across prototype chains is
11252 // not supported by 'super' implementation which optimizes
11253 // by memoizing prototype relationships.
11254 // Probably we should have a version of 'extend' that is
11255 // share-aware: it could study the text of each function,
11256 // look for usage of 'super', and wrap those functions in
11257 // closures.
11258 // As of now, there is only one problematic method, so
11259 // we just patch it manually.
11260 // To avoid re-entrancy problems, the special super method
11261 // installed is called `mixinSuper` and the mixin method
11262 // must use this method instead of the default `super`.
11263 this.mixinMethod(extended, prototype, api.instance.mdv, 'bind');
11264 // return buffed-up prototype
11265 return extended;
11266 },
11267
11268 mixinMethod: function(extended, prototype, api, name) {
11269 var $super = function(args) {
11270 return prototype[name].apply(this, args);
11271 };
11272 extended[name] = function() {
11273 this.mixinSuper = $super;
11274 return api[name].apply(this, arguments);
11275 }
11276 },
11277
11278 // ensure prototype[name] inherits from a prototype.prototype[name]
11279 inheritObject: function(name, prototype, base) {
11280 // require an object
11281 var source = prototype[name] || {};
11282 // chain inherited properties onto a new object
11283 prototype[name] = this.chainObject(source, base[name]);
11284 },
11285
11286 // register 'prototype' to custom element 'name', store constructor
11287 registerPrototype: function(name, extendee) {
11288 var info = {
11289 prototype: this.prototype
11290 }
11291 // native element must be specified in extends
11292 var typeExtension = this.findTypeExtension(extendee);
11293 if (typeExtension) {
11294 info.extends = typeExtension;
11295 }
11296 // register the prototype with HTMLElement for name lookup
11297 HTMLElement.register(name, this.prototype);
11298 // register the custom type
11299 this.ctor = document.registerElement(name, info);
11300 },
11301
11302 findTypeExtension: function(name) {
11303 if (name && name.indexOf('-') < 0) {
11304 return name;
11305 } else {
11306 var p = this.findBasePrototype(name);
11307 if (p.element) {
11308 return this.findTypeExtension(p.element.extends);
11309 }
11310 }
11311 }
11312
11313 };
11314
11315 // memoize base prototypes
11316 var memoizedBases = {};
11317
11318 // implementation of 'chainObject' depends on support for __proto__
11319 if (Object.__proto__) {
11320 prototype.chainObject = function(object, inherited) {
11321 if (object && inherited && object !== inherited) {
11322 object.__proto__ = inherited;
11323 }
11324 return object;
11325 }
11326 } else {
11327 prototype.chainObject = function(object, inherited) {
11328 if (object && inherited && object !== inherited) {
11329 var chained = Object.create(inherited);
11330 object = extend(chained, object);
11331 }
11332 return object;
11333 }
11334 }
11335
11336 // On platforms that do not support __proto__ (versions of IE), the prototype
11337 // chain of a custom element is simulated via installation of __proto__.
11338 // Although custom elements manages this, we install it here so it's
11339 // available during desugaring.
11340 function ensurePrototypeTraversal(prototype) {
11341 if (!Object.__proto__) {
11342 var ancestor = Object.getPrototypeOf(prototype);
11343 prototype.__proto__ = ancestor;
11344 if (isBase(ancestor)) {
11345 ancestor.__proto__ = Object.getPrototypeOf(ancestor);
11346 }
11347 }
11348 }
11349
11350 // exports
11351
11352 api.declaration.prototype = prototype;
11353
11354 })(Polymer);
11355
11356 (function(scope) {
11357
11358 /*
11359
11360 Elements are added to a registration queue so that they register in
11361 the proper order at the appropriate time. We do this for a few reasons:
11362
11363 * to enable elements to load resources (like stylesheets)
11364 asynchronously. We need to do this until the platform provides an efficient
11365 alternative. One issue is that remote @import stylesheets are
11366 re-fetched whenever stamped into a shadowRoot.
11367
11368 * to ensure elements loaded 'at the same time' (e.g. via some set of
11369 imports) are registered as a batch. This allows elements to be enured from
11370 upgrade ordering as long as they query the dom tree 1 task after
11371 upgrade (aka domReady). This is a performance tradeoff. On the one hand,
11372 elements that could register while imports are loading are prevented from
11373 doing so. On the other, grouping upgrades into a single task means less
11374 incremental work (for example style recalcs), Also, we can ensure the
11375 document is in a known state at the single quantum of time when
11376 elements upgrade.
11377
11378 */
11379 var queue = {
11380
11381 // tell the queue to wait for an element to be ready
11382 wait: function(element) {
11383 if (!element.__queue) {
11384 element.__queue = {};
11385 elements.push(element);
11386 }
11387 },
11388
11389 // enqueue an element to the next spot in the queue.
11390 enqueue: function(element, check, go) {
11391 var shouldAdd = element.__queue && !element.__queue.check;
11392 if (shouldAdd) {
11393 queueForElement(element).push(element);
11394 element.__queue.check = check;
11395 element.__queue.go = go;
11396 }
11397 return (this.indexOf(element) !== 0);
11398 },
11399
11400 indexOf: function(element) {
11401 var i = queueForElement(element).indexOf(element);
11402 if (i >= 0 && document.contains(element)) {
11403 i += (HTMLImports.useNative || HTMLImports.ready) ?
11404 importQueue.length : 1e9;
11405 }
11406 return i;
11407 },
11408
11409 // tell the queue an element is ready to be registered
11410 go: function(element) {
11411 var readied = this.remove(element);
11412 if (readied) {
11413 element.__queue.flushable = true;
11414 this.addToFlushQueue(readied);
11415 this.check();
11416 }
11417 },
11418
11419 remove: function(element) {
11420 var i = this.indexOf(element);
11421 if (i !== 0) {
11422 //console.warn('queue order wrong', i);
11423 return;
11424 }
11425 return queueForElement(element).shift();
11426 },
11427
11428 check: function() {
11429 // next
11430 var element = this.nextElement();
11431 if (element) {
11432 element.__queue.check.call(element);
11433 }
11434 if (this.canReady()) {
11435 this.ready();
11436 return true;
11437 }
11438 },
11439
11440 nextElement: function() {
11441 return nextQueued();
11442 },
11443
11444 canReady: function() {
11445 return !this.waitToReady && this.isEmpty();
11446 },
11447
11448 isEmpty: function() {
11449 for (var i=0, l=elements.length, e; (i<l) &&
11450 (e=elements[i]); i++) {
11451 if (e.__queue && !e.__queue.flushable) {
11452 return;
11453 }
11454 }
11455 return true;
11456 },
11457
11458 addToFlushQueue: function(element) {
11459 flushQueue.push(element);
11460 },
11461
11462 flush: function() {
11463 // prevent re-entrance
11464 if (this.flushing) {
11465 return;
11466 }
11467 this.flushing = true;
11468 var element;
11469 while (flushQueue.length) {
11470 element = flushQueue.shift();
11471 element.__queue.go.call(element);
11472 element.__queue = null;
11473 }
11474 this.flushing = false;
11475 },
11476
11477 ready: function() {
11478 // TODO(sorvell): As an optimization, turn off CE polyfill upgrading
11479 // while registering. This way we avoid having to upgrade each document
11480 // piecemeal per registration and can instead register all elements
11481 // and upgrade once in a batch. Without this optimization, upgrade time
11482 // degrades significantly when SD polyfill is used. This is mainly because
11483 // querying the document tree for elements is slow under the SD polyfill.
11484 var polyfillWasReady = CustomElements.ready;
11485 CustomElements.ready = false;
11486 this.flush();
11487 if (!CustomElements.useNative) {
11488 CustomElements.upgradeDocumentTree(document);
11489 }
11490 CustomElements.ready = polyfillWasReady;
11491 Polymer.flush();
11492 requestAnimationFrame(this.flushReadyCallbacks);
11493 },
11494
11495 addReadyCallback: function(callback) {
11496 if (callback) {
11497 readyCallbacks.push(callback);
11498 }
11499 },
11500
11501 flushReadyCallbacks: function() {
11502 if (readyCallbacks) {
11503 var fn;
11504 while (readyCallbacks.length) {
11505 fn = readyCallbacks.shift();
11506 fn();
11507 }
11508 }
11509 },
11510
11511 /**
11512 Returns a list of elements that have had polymer-elements created but
11513 are not yet ready to register. The list is an array of element definitions.
11514 */
11515 waitingFor: function() {
11516 var e$ = [];
11517 for (var i=0, l=elements.length, e; (i<l) &&
11518 (e=elements[i]); i++) {
11519 if (e.__queue && !e.__queue.flushable) {
11520 e$.push(e);
11521 }
11522 }
11523 return e$;
11524 },
11525
11526 waitToReady: true
11527
11528 };
11529
11530 var elements = [];
11531 var flushQueue = [];
11532 var importQueue = [];
11533 var mainQueue = [];
11534 var readyCallbacks = [];
11535
11536 function queueForElement(element) {
11537 return document.contains(element) ? mainQueue : importQueue;
11538 }
11539
11540 function nextQueued() {
11541 return importQueue.length ? importQueue[0] : mainQueue[0];
11542 }
11543
11544 function whenReady(callback) {
11545 queue.waitToReady = true;
11546 Polymer.endOfMicrotask(function() {
11547 HTMLImports.whenReady(function() {
11548 queue.addReadyCallback(callback);
11549 queue.waitToReady = false;
11550 queue.check();
11551 });
11552 });
11553 }
11554
11555 /**
11556 Forces polymer to register any pending elements. Can be used to abort
11557 waiting for elements that are partially defined.
11558 @param timeout {Integer} Optional timeout in milliseconds
11559 */
11560 function forceReady(timeout) {
11561 if (timeout === undefined) {
11562 queue.ready();
11563 return;
11564 }
11565 var handle = setTimeout(function() {
11566 queue.ready();
11567 }, timeout);
11568 Polymer.whenReady(function() {
11569 clearTimeout(handle);
11570 });
11571 }
11572
11573 // exports
11574 scope.elements = elements;
11575 scope.waitingFor = queue.waitingFor.bind(queue);
11576 scope.forceReady = forceReady;
11577 scope.queue = queue;
11578 scope.whenReady = scope.whenPolymerReady = whenReady;
11579 })(Polymer);
11580
11581 (function(scope) {
11582
11583 // imports
11584
11585 var extend = scope.extend;
11586 var api = scope.api;
11587 var queue = scope.queue;
11588 var whenReady = scope.whenReady;
11589 var getRegisteredPrototype = scope.getRegisteredPrototype;
11590 var waitingForPrototype = scope.waitingForPrototype;
11591
11592 // declarative implementation: <polymer-element>
11593
11594 var prototype = extend(Object.create(HTMLElement.prototype), {
11595
11596 createdCallback: function() {
11597 if (this.getAttribute('name')) {
11598 this.init();
11599 }
11600 },
11601
11602 init: function() {
11603 // fetch declared values
11604 this.name = this.getAttribute('name');
11605 this.extends = this.getAttribute('extends');
11606 queue.wait(this);
11607 // initiate any async resource fetches
11608 this.loadResources();
11609 // register when all constraints are met
11610 this.registerWhenReady();
11611 },
11612
11613 // TODO(sorvell): we currently queue in the order the prototypes are
11614 // registered, but we should queue in the order that polymer-elements
11615 // are registered. We are currently blocked from doing this based on
11616 // crbug.com/395686.
11617 registerWhenReady: function() {
11618 if (this.registered
11619 || this.waitingForPrototype(this.name)
11620 || this.waitingForQueue()
11621 || this.waitingForResources()) {
11622 return;
11623 }
11624 queue.go(this);
11625 },
11626
11627 _register: function() {
11628 //console.log('registering', this.name);
11629 // warn if extending from a custom element not registered via Polymer
11630 if (isCustomTag(this.extends) && !isRegistered(this.extends)) {
11631 console.warn('%s is attempting to extend %s, an unregistered element ' +
11632 'or one that was not registered with Polymer.', this.name,
11633 this.extends);
11634 }
11635 this.register(this.name, this.extends);
11636 this.registered = true;
11637 },
11638
11639 waitingForPrototype: function(name) {
11640 if (!getRegisteredPrototype(name)) {
11641 // then wait for a prototype
11642 waitingForPrototype(name, this);
11643 // emulate script if user is not supplying one
11644 this.handleNoScript(name);
11645 // prototype not ready yet
11646 return true;
11647 }
11648 },
11649
11650 handleNoScript: function(name) {
11651 // if explicitly marked as 'noscript'
11652 if (this.hasAttribute('noscript') && !this.noscript) {
11653 this.noscript = true;
11654 // imperative element registration
11655 Polymer(name);
11656 }
11657 },
11658
11659 waitingForResources: function() {
11660 return this._needsResources;
11661 },
11662
11663 // NOTE: Elements must be queued in proper order for inheritance/composition
11664 // dependency resolution. Previously this was enforced for inheritance,
11665 // and by rule for composition. It's now entirely by rule.
11666 waitingForQueue: function() {
11667 return queue.enqueue(this, this.registerWhenReady, this._register);
11668 },
11669
11670 loadResources: function() {
11671 this._needsResources = true;
11672 this.loadStyles(function() {
11673 this._needsResources = false;
11674 this.registerWhenReady();
11675 }.bind(this));
11676 }
11677
11678 });
11679
11680 // semi-pluggable APIs
11681
11682 // TODO(sjmiles): should be fully pluggable (aka decoupled, currently
11683 // the various plugins are allowed to depend on each other directly)
11684 api.publish(api.declaration, prototype);
11685
11686 // utility and bookkeeping
11687
11688 function isRegistered(name) {
11689 return Boolean(HTMLElement.getPrototypeForTag(name));
11690 }
11691
11692 function isCustomTag(name) {
11693 return (name && name.indexOf('-') >= 0);
11694 }
11695
11696 // boot tasks
11697
11698 whenReady(function() {
11699 document.body.removeAttribute('unresolved');
11700 document.dispatchEvent(
11701 new CustomEvent('polymer-ready', {bubbles: true})
11702 );
11703 });
11704
11705 // register polymer-element with document
11706
11707 document.registerElement('polymer-element', {prototype: prototype});
11708
11709 })(Polymer);
11710
11711 (function(scope) {
11712
11713 /**
11714 * @class Polymer
11715 */
11716
11717 var whenReady = scope.whenReady;
11718
11719 /**
11720 * Loads the set of HTMLImports contained in `node`. Notifies when all
11721 * the imports have loaded by calling the `callback` function argument.
11722 * This method can be used to lazily load imports. For example, given a
11723 * template:
11724 *
11725 * <template>
11726 * <link rel="import" href="my-import1.html">
11727 * <link rel="import" href="my-import2.html">
11728 * </template>
11729 *
11730 * Polymer.importElements(template.content, function() {
11731 * console.log('imports lazily loaded');
11732 * });
11733 *
11734 * @method importElements
11735 * @param {Node} node Node containing the HTMLImports to load.
11736 * @param {Function} callback Callback called when all imports have loaded.
11737 */
11738 function importElements(node, callback) {
11739 if (node) {
11740 document.head.appendChild(node);
11741 whenReady(callback);
11742 } else if (callback) {
11743 callback();
11744 }
11745 }
11746
11747 /**
11748 * Loads an HTMLImport for each url specified in the `urls` array.
11749 * Notifies when all the imports have loaded by calling the `callback`
11750 * function argument. This method can be used to lazily load imports.
11751 * For example,
11752 *
11753 * Polymer.import(['my-import1.html', 'my-import2.html'], function() {
11754 * console.log('imports lazily loaded');
11755 * });
11756 *
11757 * @method import
11758 * @param {Array} urls Array of urls to load as HTMLImports.
11759 * @param {Function} callback Callback called when all imports have loaded.
11760 */
11761 function _import(urls, callback) {
11762 if (urls && urls.length) {
11763 var frag = document.createDocumentFragment();
11764 for (var i=0, l=urls.length, url, link; (i<l) && (url=urls[i]); i++) {
11765 link = document.createElement('link');
11766 link.rel = 'import';
11767 link.href = url;
11768 frag.appendChild(link);
11769 }
11770 importElements(frag, callback);
11771 } else if (callback) {
11772 callback();
11773 }
11774 }
11775
11776 // exports
11777 scope.import = _import;
11778 scope.importElements = importElements;
11779
11780 })(Polymer);
11781
11782 /**
11783 * The `auto-binding` element extends the template element. It provides a quick
11784 * and easy way to do data binding without the need to setup a model.
11785 * The `auto-binding` element itself serves as the model and controller for the
11786 * elements it contains. Both data and event handlers can be bound.
11787 *
11788 * The `auto-binding` element acts just like a template that is bound to
11789 * a model. It stamps its content in the dom adjacent to itself. When the
11790 * content is stamped, the `template-bound` event is fired.
11791 *
11792 * Example:
11793 *
11794 * <template is="auto-binding">
11795 * <div>Say something: <input value="{{value}}"></div>
11796 * <div>You said: {{value}}</div>
11797 * <button on-tap="{{buttonTap}}">Tap me!</button>
11798 * </template>
11799 * <script>
11800 * var template = document.querySelector('template');
11801 * template.value = 'something';
11802 * template.buttonTap = function() {
11803 * console.log('tap!');
11804 * };
11805 * <\/script>
11806 *
11807 * @module Polymer
11808 * @status stable
11809 */
11810
11811 (function() {
11812
11813 var element = document.createElement('polymer-element');
11814 element.setAttribute('name', 'auto-binding');
11815 element.setAttribute('extends', 'template');
11816 element.init();
11817
11818 Polymer('auto-binding', {
11819
11820 createdCallback: function() {
11821 this.syntax = this.bindingDelegate = this.makeSyntax();
11822 // delay stamping until polymer-ready so that auto-binding is not
11823 // required to load last.
11824 Polymer.whenPolymerReady(function() {
11825 this.model = this;
11826 this.setAttribute('bind', '');
11827 // we don't bother with an explicit signal here, we could ust a MO
11828 // if necessary
11829 this.async(function() {
11830 // note: this will marshall *all* the elements in the parentNode
11831 // rather than just stamped ones. We'd need to use createInstance
11832 // to fix this or something else fancier.
11833 this.marshalNodeReferences(this.parentNode);
11834 // template stamping is asynchronous so stamping isn't complete
11835 // by polymer-ready; fire an event so users can use stamped elements
11836 this.fire('template-bound');
11837 });
11838 }.bind(this));
11839 },
11840
11841 makeSyntax: function() {
11842 var events = Object.create(Polymer.api.declaration.events);
11843 var self = this;
11844 events.findController = function() { return self.model; };
11845
11846 var syntax = new PolymerExpressions();
11847 var prepareBinding = syntax.prepareBinding;
11848 syntax.prepareBinding = function(pathString, name, node) {
11849 return events.prepareEventBinding(pathString, name, node) ||
11850 prepareBinding.call(syntax, pathString, name, node);
11851 };
11852 return syntax;
11853 }
11854
11855 });
11856
11857 })();
11858 ;
11859
11860
11861 (function(scope) {
11862
11863 /**
11864 `Polymer.CoreResizable` and `Polymer.CoreResizer` are a set of mixins that can be used
11865 in Polymer elements to coordinate the flow of resize events between "resizers" (elements
11866 that control the size or hidden state of their children) and "resizables" (ele ments that
11867 need to be notified when they are resized or un-hidden by their parents in ord er to take
11868 action on their new measurements).
11869
11870 Elements that perform measurement should add the `Core.Resizable` mixin to the ir
11871 Polymer prototype definition and listen for the `core-resize` event on themsel ves.
11872 This event will be fired when they become showing after having been hidden,
11873 when they are resized explicitly by a `CoreResizer`, or when the window has be en resized.
11874 Note, the `core-resize` event is non-bubbling.
11875
11876 `CoreResizable`'s must manually call the `resizableAttachedHandler` from the e lement's
11877 `attached` callback and `resizableDetachedHandler` from the element's `detache d`
11878 callback.
11879
11880 @element CoreResizable
11881 @status beta
11882 @homepage github.io
11883 */
11884
11885 scope.CoreResizable = {
11886
11887 /**
11888 * User must call from `attached` callback
11889 *
11890 * @method resizableAttachedHandler
11891 */
11892 resizableAttachedHandler: function(cb) {
11893 cb = cb || this._notifyResizeSelf;
11894 this.async(function() {
11895 var detail = {callback: cb, hasParentResizer: false};
11896 this.fire('core-request-resize', detail);
11897 if (!detail.hasParentResizer) {
11898 this._boundWindowResizeHandler = cb.bind(this);
11899 // log('adding window resize handler', null, this);
11900 window.addEventListener('resize', this._boundWindowResizeHandler);
11901 }
11902 }.bind(this));
11903 },
11904
11905 /**
11906 * User must call from `detached` callback
11907 *
11908 * @method resizableDetachedHandler
11909 */
11910 resizableDetachedHandler: function() {
11911 this.fire('core-request-resize-cancel', null, this, false);
11912 if (this._boundWindowResizeHandler) {
11913 window.removeEventListener('resize', this._boundWindowResizeHandler);
11914 }
11915 },
11916
11917 // Private: fire non-bubbling resize event to self; returns whether
11918 // preventDefault was called, indicating that children should not
11919 // be resized
11920 _notifyResizeSelf: function() {
11921 return this.fire('core-resize', null, this, false).defaultPrevented;
11922 }
11923
11924 };
11925
11926 /**
11927 `Polymer.CoreResizable` and `Polymer.CoreResizer` are a set of mixins that can be used
11928 in Polymer elements to coordinate the flow of resize events between "resizers" (elements
11929 that control the size or hidden state of their children) and "resizables" (ele ments that
11930 need to be notified when they are resized or un-hidden by their parents in ord er to take
11931 action on their new measurements).
11932
11933 Elements that cause their children to be resized (e.g. a splitter control) or hide/show
11934 their children (e.g. overlay) should add the `Core.CoreResizer` mixin to their
11935 Polymer prototype definition and then call `this.notifyResize()` any time the element
11936 resizes or un-hides its children.
11937
11938 `CoreResizer`'s must manually call the `resizerAttachedHandler` from the eleme nt's
11939 `attached` callback and `resizerDetachedHandler` from the element's `detached`
11940 callback.
11941
11942 Note: `CoreResizer` extends `CoreResizable`, and can listen for the `core-resi ze` event
11943 on itself if it needs to perform resize work on itself before notifying childr en.
11944 In this case, returning `false` from the `core-resize` event handler (or calli ng
11945 `preventDefault` on the event) will prevent notification of children if requir ed.
11946
11947 @element CoreResizer
11948 @extends CoreResizable
11949 @status beta
11950 @homepage github.io
11951 */
11952
11953 scope.CoreResizer = Polymer.mixin({
11954
11955 /**
11956 * User must call from `attached` callback
11957 *
11958 * @method resizerAttachedHandler
11959 */
11960 resizerAttachedHandler: function() {
11961 this.resizableAttachedHandler(this.notifyResize);
11962 this._boundResizeRequested = this._boundResizeRequested || this._handleRes izeRequested.bind(this);
11963 var listener;
11964 if (this.resizerIsPeer) {
11965 listener = this.parentElement || (this.parentNode && this.parentNode.hos t);
11966 listener._resizerPeers = listener._resizerPeers || [];
11967 listener._resizerPeers.push(this);
11968 } else {
11969 listener = this;
11970 }
11971 listener.addEventListener('core-request-resize', this._boundResizeRequeste d);
11972 this._resizerListener = listener;
11973 },
11974
11975 /**
11976 * User must call from `detached` callback
11977 *
11978 * @method resizerDetachedHandler
11979 */
11980 resizerDetachedHandler: function() {
11981 this.resizableDetachedHandler();
11982 this._resizerListener.removeEventListener('core-request-resize', this._bou ndResizeRequested);
11983 },
11984
11985 /**
11986 * User should call when resizing or un-hiding children
11987 *
11988 * @method notifyResize
11989 */
11990 notifyResize: function() {
11991 // Notify self
11992 if (!this._notifyResizeSelf()) {
11993 // Notify requestors if default was not prevented
11994 var r = this.resizeRequestors;
11995 if (r) {
11996 for (var i=0; i<r.length; i++) {
11997 var ri = r[i];
11998 if (!this.resizerShouldNotify || this.resizerShouldNotify(ri.target) ) {
11999 // log('notifying resize', null, ri.target, true);
12000 ri.callback.apply(ri.target);
12001 // logEnd();
12002 }
12003 }
12004 }
12005 }
12006 },
12007
12008 /**
12009 * User should implement to introduce filtering when notifying children.
12010 * Generally, children that are hidden by the CoreResizer (e.g. non-active
12011 * pages) need not be notified during resize, since they will be notified
12012 * again when becoming un-hidden.
12013 *
12014 * Return `true` if CoreResizable passed as argument should be notified of
12015 * resize.
12016 *
12017 * @method resizeerShouldNotify
12018 * @param {Element} el
12019 */
12020 // resizeerShouldNotify: function(el) { } // User to implement if needed
12021
12022 /**
12023 * Set to `true` if the resizer is actually a peer to the elements it
12024 * resizes (e.g. splitter); in this case it will listen for resize requests
12025 * events from its peers on its parent.
12026 *
12027 * @property resizerIsPeer
12028 * @type Boolean
12029 * @default false
12030 */
12031
12032 // Private: Handle requests for resize
12033 _handleResizeRequested: function(e) {
12034 var target = e.path[0];
12035 if ((target == this) ||
12036 (target == this._resizerListener) ||
12037 (this._resizerPeers && this._resizerPeers.indexOf(target) < 0)) {
12038 return;
12039 }
12040 // log('resize requested', target, this);
12041 if (!this.resizeRequestors) {
12042 this.resizeRequestors = [];
12043 }
12044 this.resizeRequestors.push({target: target, callback: e.detail.callback});
12045 target.addEventListener('core-request-resize-cancel', this._cancelResizeRe quested.bind(this));
12046 e.detail.hasParentResizer = true;
12047 e.stopPropagation();
12048 },
12049
12050 // Private: Handle cancellation requests for resize
12051 _cancelResizeRequested: function(e) {
12052 // Exit early if we're already out of the DOM (resizeRequestors will alrea dy be null)
12053 if (this.resizeRequestors) {
12054 for (var i=0; i<this.resizeRequestors.length; i++) {
12055 if (this.resizeRequestors[i].target == e.target) {
12056 // log('resizeCanceled', e.target, this);
12057 this.resizeRequestors.splice(i, 1);
12058 break;
12059 }
12060 }
12061 }
12062 }
12063
12064 }, Polymer.CoreResizable);
12065
12066 // function prettyName(el) {
12067 // return el.localName + (el.id ? '#' : '') + el.id;
12068 // }
12069
12070 // function log(what, from, to, group) {
12071 // var args = [what];
12072 // if (from) {
12073 // args.push('from ' + prettyName(from));
12074 // }
12075 // if (to) {
12076 // args.push('to ' + prettyName(to));
12077 // }
12078 // if (group) {
12079 // console.group.apply(console, args);
12080 // } else {
12081 // console.log.apply(console, args);
12082 // }
12083 // }
12084
12085 // function logEnd() {
12086 // console.groupEnd();
12087 // }
12088
12089 })(Polymer);
12090
12091 ;
12092 Polymer.mixin2 = function(prototype, mixin) {
12093
12094 // adds a single mixin to prototype
12095
12096 if (mixin.mixinPublish) {
12097 prototype.publish = prototype.publish || {};
12098 Polymer.mixin(prototype.publish, mixin.mixinPublish);
12099 }
12100
12101 if (mixin.mixinDelegates) {
12102 prototype.eventDelegates = prototype.eventDelegates || {};
12103 for (var e in mixin.mixinDelegates) {
12104 if (!prototype.eventDelegates[e]) {
12105 prototype.eventDelegates[e] = mixin.mixinDelegates[e];
12106 }
12107 }
12108 }
12109
12110 if (mixin.mixinObserve) {
12111 prototype.observe = prototype.observe || {};
12112 for (var o in mixin.mixinObserve) {
12113 if (!prototype.observe[o] && !prototype[o + 'Changed']) {
12114 prototype.observe[o] = mixin.mixinObserve[o];
12115 }
12116 }
12117 }
12118
12119 Polymer.mixin(prototype, mixin);
12120
12121 delete prototype.mixinPublish;
12122 delete prototype.mixinDelegates;
12123 delete prototype.mixinObserve;
12124
12125 return prototype;
12126 };;
12127 /**
12128 * @group Polymer Mixins
12129 *
12130 * `Polymer.CoreFocusable` is a mixin for elements that the user can interact wi th.
12131 * Elements using this mixin will receive attributes reflecting the focus, press ed
12132 * and disabled states.
12133 *
12134 * @element Polymer.CoreFocusable
12135 * @status unstable
12136 */
12137
12138 Polymer.CoreFocusable = {
12139
12140 mixinPublish: {
12141
12142 /**
12143 * If true, the element is currently active either because the
12144 * user is touching it, or the button is a toggle
12145 * and is currently in the active state.
12146 *
12147 * @attribute active
12148 * @type boolean
12149 * @default false
12150 */
12151 active: {value: false, reflect: true},
12152
12153 /**
12154 * If true, the element currently has focus due to keyboard
12155 * navigation.
12156 *
12157 * @attribute focused
12158 * @type boolean
12159 * @default false
12160 */
12161 focused: {value: false, reflect: true},
12162
12163 /**
12164 * If true, the user is currently holding down the button.
12165 *
12166 * @attribute pressed
12167 * @type boolean
12168 * @default false
12169 */
12170 pressed: {value: false, reflect: true},
12171
12172 /**
12173 * If true, the user cannot interact with this element.
12174 *
12175 * @attribute disabled
12176 * @type boolean
12177 * @default false
12178 */
12179 disabled: {value: false, reflect: true},
12180
12181 /**
12182 * If true, the button toggles the active state with each tap.
12183 * Otherwise, the button becomes active when the user is holding
12184 * it down.
12185 *
12186 * @attribute toggle
12187 * @type boolean
12188 * @default false
12189 */
12190 toggle: false
12191
12192 },
12193
12194 mixinDelegates: {
12195 contextMenu: '_contextMenuAction',
12196 down: '_downAction',
12197 up: '_upAction',
12198 focus: '_focusAction',
12199 blur: '_blurAction'
12200 },
12201
12202 mixinObserve: {
12203 disabled: '_disabledChanged'
12204 },
12205
12206 _disabledChanged: function() {
12207 if (this.disabled) {
12208 this.style.pointerEvents = 'none';
12209 this.removeAttribute('tabindex');
12210 this.setAttribute('aria-disabled', '');
12211 } else {
12212 this.style.pointerEvents = '';
12213 this.setAttribute('tabindex', 0);
12214 this.removeAttribute('aria-disabled');
12215 }
12216 },
12217
12218 _downAction: function() {
12219 this.pressed = true;
12220
12221 if (this.toggle) {
12222 this.active = !this.active;
12223 } else {
12224 this.active = true;
12225 }
12226 },
12227
12228 // Pulling up the context menu for an item should focus it; but we need to
12229 // be careful about how we deal with down/up events surrounding context
12230 // menus. The up event typically does not fire until the context menu
12231 // closes: so we focus immediately.
12232 //
12233 // This fires _after_ downAction.
12234 _contextMenuAction: function(e) {
12235 // Note that upAction may fire _again_ on the actual up event.
12236 this._upAction(e);
12237 this._focusAction();
12238 },
12239
12240 _upAction: function() {
12241 this.pressed = false;
12242
12243 if (!this.toggle) {
12244 this.active = false;
12245 }
12246 },
12247
12248 _focusAction: function() {
12249 if (!this.pressed) {
12250 // Only render the "focused" state if the element gains focus due to
12251 // keyboard navigation.
12252 this.focused = true;
12253 }
12254 },
12255
12256 _blurAction: function() {
12257 this.focused = false;
12258 }
12259
12260 }
12261 ;
12262
12263
12264 Polymer('core-xhr', {
12265
12266 /**
12267 * Sends a HTTP request to the server and returns the XHR object.
12268 *
12269 * @method request
12270 * @param {Object} inOptions
12271 * @param {String} inOptions.url The url to which the request is sent.
12272 * @param {String} inOptions.method The HTTP method to use, default is GET.
12273 * @param {boolean} inOptions.sync By default, all requests are sent as ynchronously. To send synchronous requests, set to true.
12274 * @param {Object} inOptions.params Data to be sent to the server.
12275 * @param {Object} inOptions.body The content for the request body for POST method.
12276 * @param {Object} inOptions.headers HTTP request headers.
12277 * @param {String} inOptions.responseType The response type. Default is 'text'.
12278 * @param {boolean} inOptions.withCredentials Whether or not to send cr edentials on the request. Default is false.
12279 * @param {Object} inOptions.callback Called when request is completed.
12280 * @returns {Object} XHR object.
12281 */
12282 request: function(options) {
12283 var xhr = new XMLHttpRequest();
12284 var url = options.url;
12285 var method = options.method || 'GET';
12286 var async = !options.sync;
12287 //
12288 var params = this.toQueryString(options.params);
12289 if (params && method.toUpperCase() == 'GET') {
12290 url += (url.indexOf('?') > 0 ? '&' : '?') + params;
12291 }
12292 var xhrParams = this.isBodyMethod(method) ? (options.body || params) : n ull;
12293 //
12294 xhr.open(method, url, async);
12295 if (options.responseType) {
12296 xhr.responseType = options.responseType;
12297 }
12298 if (options.withCredentials) {
12299 xhr.withCredentials = true;
12300 }
12301 this.makeReadyStateHandler(xhr, options.callback);
12302 this.setRequestHeaders(xhr, options.headers);
12303 xhr.send(xhrParams);
12304 if (!async) {
12305 xhr.onreadystatechange(xhr);
12306 }
12307 return xhr;
12308 },
12309
12310 toQueryString: function(params) {
12311 var r = [];
12312 for (var n in params) {
12313 var v = params[n];
12314 n = encodeURIComponent(n);
12315 r.push(v == null ? n : (n + '=' + encodeURIComponent(v)));
12316 }
12317 return r.join('&');
12318 },
12319
12320 isBodyMethod: function(method) {
12321 return this.bodyMethods[(method || '').toUpperCase()];
12322 },
12323
12324 bodyMethods: {
12325 POST: 1,
12326 PUT: 1,
12327 PATCH: 1,
12328 DELETE: 1
12329 },
12330
12331 makeReadyStateHandler: function(xhr, callback) {
12332 xhr.onreadystatechange = function() {
12333 if (xhr.readyState == 4) {
12334 callback && callback.call(null, xhr.response, xhr);
12335 }
12336 };
12337 },
12338
12339 setRequestHeaders: function(xhr, headers) {
12340 if (headers) {
12341 for (var name in headers) {
12342 xhr.setRequestHeader(name, headers[name]);
12343 }
12344 }
12345 }
12346
12347 });
12348
12349 ;
12350
12351
12352 Polymer('core-ajax', {
12353 /**
12354 * Fired when a response is received.
12355 *
12356 * @event core-response
12357 */
12358
12359 /**
12360 * Fired when an error is received.
12361 *
12362 * @event core-error
12363 */
12364
12365 /**
12366 * Fired whenever a response or an error is received.
12367 *
12368 * @event core-complete
12369 */
12370
12371 /**
12372 * The URL target of the request.
12373 *
12374 * @attribute url
12375 * @type string
12376 * @default ''
12377 */
12378 url: '',
12379
12380 /**
12381 * Specifies what data to store in the `response` property, and
12382 * to deliver as `event.response` in `response` events.
12383 *
12384 * One of:
12385 *
12386 * `text`: uses `XHR.responseText`.
12387 *
12388 * `xml`: uses `XHR.responseXML`.
12389 *
12390 * `json`: uses `XHR.responseText` parsed as JSON.
12391 *
12392 * `arraybuffer`: uses `XHR.response`.
12393 *
12394 * `blob`: uses `XHR.response`.
12395 *
12396 * `document`: uses `XHR.response`.
12397 *
12398 * @attribute handleAs
12399 * @type string
12400 * @default 'text'
12401 */
12402 handleAs: '',
12403
12404 /**
12405 * If true, automatically performs an Ajax request when either `url` or `par ams` changes.
12406 *
12407 * @attribute auto
12408 * @type boolean
12409 * @default false
12410 */
12411 auto: false,
12412
12413 /**
12414 * Parameters to send to the specified URL, as JSON.
12415 *
12416 * @attribute params
12417 * @type string
12418 * @default ''
12419 */
12420 params: '',
12421
12422 /**
12423 * The response for the current request, or null if it hasn't
12424 * completed yet or the request resulted in error.
12425 *
12426 * @attribute response
12427 * @type Object
12428 * @default null
12429 */
12430 response: null,
12431
12432 /**
12433 * The error for the current request, or null if it hasn't
12434 * completed yet or the request resulted in success.
12435 *
12436 * @attribute error
12437 * @type Object
12438 * @default null
12439 */
12440 error: null,
12441
12442 /**
12443 * Whether the current request is currently loading.
12444 *
12445 * @attribute loading
12446 * @type boolean
12447 * @default false
12448 */
12449 loading: false,
12450
12451 /**
12452 * The progress of the current request.
12453 *
12454 * @attribute progress
12455 * @type {loaded: number, total: number, lengthComputable: boolean}
12456 * @default {}
12457 */
12458 progress: null,
12459
12460 /**
12461 * The HTTP method to use such as 'GET', 'POST', 'PUT', or 'DELETE'.
12462 * Default is 'GET'.
12463 *
12464 * @attribute method
12465 * @type string
12466 * @default ''
12467 */
12468 method: '',
12469
12470 /**
12471 * HTTP request headers to send.
12472 *
12473 * Example:
12474 *
12475 * <core-ajax
12476 * auto
12477 * url="http://somesite.com"
12478 * headers='{"X-Requested-With": "XMLHttpRequest"}'
12479 * handleAs="json"
12480 * on-core-response="{{handleResponse}}"></core-ajax>
12481 *
12482 * @attribute headers
12483 * @type Object
12484 * @default null
12485 */
12486 headers: null,
12487
12488 /**
12489 * Optional raw body content to send when method === "POST".
12490 *
12491 * Example:
12492 *
12493 * <core-ajax method="POST" auto url="http://somesite.com"
12494 * body='{"foo":1, "bar":2}'>
12495 * </core-ajax>
12496 *
12497 * @attribute body
12498 * @type Object
12499 * @default null
12500 */
12501 body: null,
12502
12503 /**
12504 * Content type to use when sending data.
12505 *
12506 * @attribute contentType
12507 * @type string
12508 * @default 'application/x-www-form-urlencoded'
12509 */
12510 contentType: 'application/x-www-form-urlencoded',
12511
12512 /**
12513 * Set the withCredentials flag on the request.
12514 *
12515 * @attribute withCredentials
12516 * @type boolean
12517 * @default false
12518 */
12519 withCredentials: false,
12520
12521 /**
12522 * Additional properties to send to core-xhr.
12523 *
12524 * Can be set to an object containing default properties
12525 * to send as arguments to the `core-xhr.request()` method
12526 * which implements the low-level communication.
12527 *
12528 * @property xhrArgs
12529 * @type Object
12530 * @default null
12531 */
12532 xhrArgs: null,
12533
12534 created: function() {
12535 this.progress = {};
12536 },
12537
12538 ready: function() {
12539 this.xhr = document.createElement('core-xhr');
12540 },
12541
12542 receive: function(response, xhr) {
12543 if (this.isSuccess(xhr)) {
12544 this.processResponse(xhr);
12545 } else {
12546 this.processError(xhr);
12547 }
12548 this.complete(xhr);
12549 },
12550
12551 isSuccess: function(xhr) {
12552 var status = xhr.status || 0;
12553 return !status || (status >= 200 && status < 300);
12554 },
12555
12556 processResponse: function(xhr) {
12557 var response = this.evalResponse(xhr);
12558 if (xhr === this.activeRequest) {
12559 this.response = response;
12560 }
12561 this.fire('core-response', {response: response, xhr: xhr});
12562 },
12563
12564 processError: function(xhr) {
12565 var response = xhr.status + ': ' + xhr.responseText;
12566 if (xhr === this.activeRequest) {
12567 this.error = response;
12568 }
12569 this.fire('core-error', {response: response, xhr: xhr});
12570 },
12571
12572 processProgress: function(progress, xhr) {
12573 if (xhr !== this.activeRequest) {
12574 return;
12575 }
12576 // We create a proxy object here because these fields
12577 // on the progress event are readonly properties, which
12578 // causes problems in common use cases (e.g. binding to
12579 // <paper-progress> attributes).
12580 var progressProxy = {
12581 lengthComputable: progress.lengthComputable,
12582 loaded: progress.loaded,
12583 total: progress.total
12584 }
12585 this.progress = progressProxy;
12586 },
12587
12588 complete: function(xhr) {
12589 if (xhr === this.activeRequest) {
12590 this.loading = false;
12591 }
12592 this.fire('core-complete', {response: xhr.status, xhr: xhr});
12593 },
12594
12595 evalResponse: function(xhr) {
12596 return this[(this.handleAs || 'text') + 'Handler'](xhr);
12597 },
12598
12599 xmlHandler: function(xhr) {
12600 return xhr.responseXML;
12601 },
12602
12603 textHandler: function(xhr) {
12604 return xhr.responseText;
12605 },
12606
12607 jsonHandler: function(xhr) {
12608 var r = xhr.responseText;
12609 try {
12610 return JSON.parse(r);
12611 } catch (x) {
12612 console.warn('core-ajax caught an exception trying to parse response as JSON:');
12613 console.warn('url:', this.url);
12614 console.warn(x);
12615 return r;
12616 }
12617 },
12618
12619 documentHandler: function(xhr) {
12620 return xhr.response;
12621 },
12622
12623 blobHandler: function(xhr) {
12624 return xhr.response;
12625 },
12626
12627 arraybufferHandler: function(xhr) {
12628 return xhr.response;
12629 },
12630
12631 urlChanged: function() {
12632 if (!this.handleAs) {
12633 var ext = String(this.url).split('.').pop();
12634 switch (ext) {
12635 case 'json':
12636 this.handleAs = 'json';
12637 break;
12638 }
12639 }
12640 this.autoGo();
12641 },
12642
12643 paramsChanged: function() {
12644 this.autoGo();
12645 },
12646
12647 bodyChanged: function() {
12648 this.autoGo();
12649 },
12650
12651 autoChanged: function() {
12652 this.autoGo();
12653 },
12654
12655 // TODO(sorvell): multiple side-effects could call autoGo
12656 // during one micro-task, use a job to have only one action
12657 // occur
12658 autoGo: function() {
12659 if (this.auto) {
12660 this.goJob = this.job(this.goJob, this.go, 0);
12661 }
12662 },
12663
12664 getParams: function(params) {
12665 params = this.params || params;
12666 if (params && typeof(params) == 'string') {
12667 params = JSON.parse(params);
12668 }
12669 return params;
12670 },
12671
12672 /**
12673 * Performs an Ajax request to the specified URL.
12674 *
12675 * @method go
12676 */
12677 go: function() {
12678 var args = this.xhrArgs || {};
12679 // TODO(sjmiles): we may want XHR to default to POST if body is set
12680 args.body = this.body || args.body;
12681 args.params = this.getParams(args.params);
12682 args.headers = this.headers || args.headers || {};
12683 if (args.headers && typeof(args.headers) == 'string') {
12684 args.headers = JSON.parse(args.headers);
12685 }
12686 var hasContentType = Object.keys(args.headers).some(function (header) {
12687 return header.toLowerCase() === 'content-type';
12688 });
12689 // No Content-Type should be specified if sending `FormData`.
12690 // The UA must set the Content-Type w/ a calculated multipart boundary ID .
12691 if (args.body instanceof FormData) {
12692 delete args.headers['Content-Type'];
12693 }
12694 else if (!hasContentType && this.contentType) {
12695 args.headers['Content-Type'] = this.contentType;
12696 }
12697 if (this.handleAs === 'arraybuffer' || this.handleAs === 'blob' ||
12698 this.handleAs === 'document') {
12699 args.responseType = this.handleAs;
12700 }
12701 args.withCredentials = this.withCredentials;
12702 args.callback = this.receive.bind(this);
12703 args.url = this.url;
12704 args.method = this.method;
12705
12706 this.response = this.error = this.progress = null;
12707 this.activeRequest = args.url && this.xhr.request(args);
12708 if (this.activeRequest) {
12709 this.loading = true;
12710 var activeRequest = this.activeRequest;
12711 // IE < 10 doesn't support progress events.
12712 if ('onprogress' in activeRequest) {
12713 this.activeRequest.addEventListener(
12714 'progress',
12715 function(progress) {
12716 this.processProgress(progress, activeRequest);
12717 }.bind(this), false);
12718 } else {
12719 this.progress = {
12720 lengthComputable: false,
12721 }
12722 }
12723 }
12724 return this.activeRequest;
12725 },
12726
12727 /**
12728 * Aborts the current active request if there is one and resets internal
12729 * state appropriately.
12730 *
12731 * @method abort
12732 */
12733 abort: function() {
12734 if (!this.activeRequest) return;
12735 this.activeRequest.abort();
12736 this.activeRequest = null;
12737 this.progress = {};
12738 this.loading = false;
12739 }
12740
12741 });
12742
12743 ;
12744
12745 (function() {
12746
12747 var ID = 0;
12748 function generate(node) {
12749 if (!node.id) {
12750 node.id = 'core-label-' + ID++;
12751 }
12752 return node.id;
12753 }
12754
12755 Polymer('core-label', {
12756 /**
12757 * A query selector string for a "target" element not nested in the `<co re-label>`
12758 *
12759 * @attribute for
12760 * @type string
12761 * @default ''
12762 */
12763 publish: {
12764 'for': {reflect: true, value: ''}
12765 },
12766 eventDelegates: {
12767 'tap': 'tapHandler'
12768 },
12769 created: function() {
12770 generate(this);
12771 this._forElement = null;
12772 },
12773 ready: function() {
12774 if (!this.for) {
12775 this._forElement = this.querySelector('[for]');
12776 this._tie();
12777 }
12778 },
12779 tapHandler: function(ev) {
12780 if (!this._forElement) {
12781 return;
12782 }
12783 if (ev.target === this._forElement) {
12784 return;
12785 }
12786 this._forElement.focus();
12787 this._forElement.click();
12788 this.fire('tap', null, this._forElement);
12789 },
12790 _tie: function() {
12791 if (this._forElement) {
12792 this._forElement.setAttribute('aria-labelledby', this.id);
12793 }
12794 },
12795 _findScope: function() {
12796 var n = this.parentNode;
12797 while(n && n.parentNode) {
12798 n = n.parentNode;
12799 }
12800 return n;
12801 },
12802 forChanged: function(oldFor, newFor) {
12803 if (this._forElement) {
12804 this._forElement.removeAttribute('aria-labelledby');
12805 }
12806 var scope = this._findScope();
12807 if (!scope) {
12808 return;
12809 }
12810 this._forElement = scope.querySelector(newFor);
12811 if (this._forElement) {
12812 this._tie();
12813 }
12814 }
12815 });
12816 })();
12817 ;
12818
12819
12820 (function() {
12821
12822 var waveMaxRadius = 150;
12823 //
12824 // INK EQUATIONS
12825 //
12826 function waveRadiusFn(touchDownMs, touchUpMs, anim) {
12827 // Convert from ms to s
12828 var touchDown = touchDownMs / 1000;
12829 var touchUp = touchUpMs / 1000;
12830 var totalElapsed = touchDown + touchUp;
12831 var ww = anim.width, hh = anim.height;
12832 // use diagonal size of container to avoid floating point math sadness
12833 var waveRadius = Math.min(Math.sqrt(ww * ww + hh * hh), waveMaxRadius) * 1 .1 + 5;
12834 var duration = 1.1 - .2 * (waveRadius / waveMaxRadius);
12835 var tt = (totalElapsed / duration);
12836
12837 var size = waveRadius * (1 - Math.pow(80, -tt));
12838 return Math.abs(size);
12839 }
12840
12841 function waveOpacityFn(td, tu, anim) {
12842 // Convert from ms to s.
12843 var touchDown = td / 1000;
12844 var touchUp = tu / 1000;
12845 var totalElapsed = touchDown + touchUp;
12846
12847 if (tu <= 0) { // before touch up
12848 return anim.initialOpacity;
12849 }
12850 return Math.max(0, anim.initialOpacity - touchUp * anim.opacityDecayVeloci ty);
12851 }
12852
12853 function waveOuterOpacityFn(td, tu, anim) {
12854 // Convert from ms to s.
12855 var touchDown = td / 1000;
12856 var touchUp = tu / 1000;
12857
12858 // Linear increase in background opacity, capped at the opacity
12859 // of the wavefront (waveOpacity).
12860 var outerOpacity = touchDown * 0.3;
12861 var waveOpacity = waveOpacityFn(td, tu, anim);
12862 return Math.max(0, Math.min(outerOpacity, waveOpacity));
12863 }
12864
12865 // Determines whether the wave should be completely removed.
12866 function waveDidFinish(wave, radius, anim) {
12867 var waveOpacity = waveOpacityFn(wave.tDown, wave.tUp, anim);
12868
12869 // If the wave opacity is 0 and the radius exceeds the bounds
12870 // of the element, then this is finished.
12871 return waveOpacity < 0.01 && radius >= Math.min(wave.maxRadius, waveMaxRad ius);
12872 };
12873
12874 function waveAtMaximum(wave, radius, anim) {
12875 var waveOpacity = waveOpacityFn(wave.tDown, wave.tUp, anim);
12876
12877 return waveOpacity >= anim.initialOpacity && radius >= Math.min(wave.maxRa dius, waveMaxRadius);
12878 }
12879
12880 //
12881 // DRAWING
12882 //
12883 function drawRipple(ctx, x, y, radius, innerAlpha, outerAlpha) {
12884 // Only animate opacity and transform
12885 if (outerAlpha !== undefined) {
12886 ctx.bg.style.opacity = outerAlpha;
12887 }
12888 ctx.wave.style.opacity = innerAlpha;
12889
12890 var s = radius / (ctx.containerSize / 2);
12891 var dx = x - (ctx.containerWidth / 2);
12892 var dy = y - (ctx.containerHeight / 2);
12893
12894 ctx.wc.style.webkitTransform = 'translate3d(' + dx + 'px,' + dy + 'px,0)';
12895 ctx.wc.style.transform = 'translate3d(' + dx + 'px,' + dy + 'px,0)';
12896
12897 // 2d transform for safari because of border-radius and overflow:hidden cl ipping bug.
12898 // https://bugs.webkit.org/show_bug.cgi?id=98538
12899 ctx.wave.style.webkitTransform = 'scale(' + s + ',' + s + ')';
12900 ctx.wave.style.transform = 'scale3d(' + s + ',' + s + ',1)';
12901 }
12902
12903 //
12904 // SETUP
12905 //
12906 function createWave(elem) {
12907 var elementStyle = window.getComputedStyle(elem);
12908 var fgColor = elementStyle.color;
12909
12910 var inner = document.createElement('div');
12911 inner.style.backgroundColor = fgColor;
12912 inner.classList.add('wave');
12913
12914 var outer = document.createElement('div');
12915 outer.classList.add('wave-container');
12916 outer.appendChild(inner);
12917
12918 var container = elem.$.waves;
12919 container.appendChild(outer);
12920
12921 elem.$.bg.style.backgroundColor = fgColor;
12922
12923 var wave = {
12924 bg: elem.$.bg,
12925 wc: outer,
12926 wave: inner,
12927 waveColor: fgColor,
12928 maxRadius: 0,
12929 isMouseDown: false,
12930 mouseDownStart: 0.0,
12931 mouseUpStart: 0.0,
12932 tDown: 0,
12933 tUp: 0
12934 };
12935 return wave;
12936 }
12937
12938 function removeWaveFromScope(scope, wave) {
12939 if (scope.waves) {
12940 var pos = scope.waves.indexOf(wave);
12941 scope.waves.splice(pos, 1);
12942 // FIXME cache nodes
12943 wave.wc.remove();
12944 }
12945 };
12946
12947 // Shortcuts.
12948 var pow = Math.pow;
12949 var now = Date.now;
12950 if (window.performance && performance.now) {
12951 now = performance.now.bind(performance);
12952 }
12953
12954 function cssColorWithAlpha(cssColor, alpha) {
12955 var parts = cssColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
12956 if (typeof alpha == 'undefined') {
12957 alpha = 1;
12958 }
12959 if (!parts) {
12960 return 'rgba(255, 255, 255, ' + alpha + ')';
12961 }
12962 return 'rgba(' + parts[1] + ', ' + parts[2] + ', ' + parts[3] + ', ' + a lpha + ')';
12963 }
12964
12965 function dist(p1, p2) {
12966 return Math.sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2));
12967 }
12968
12969 function distanceFromPointToFurthestCorner(point, size) {
12970 var tl_d = dist(point, {x: 0, y: 0});
12971 var tr_d = dist(point, {x: size.w, y: 0});
12972 var bl_d = dist(point, {x: 0, y: size.h});
12973 var br_d = dist(point, {x: size.w, y: size.h});
12974 return Math.max(tl_d, tr_d, bl_d, br_d);
12975 }
12976
12977 Polymer('paper-ripple', {
12978
12979 /**
12980 * The initial opacity set on the wave.
12981 *
12982 * @attribute initialOpacity
12983 * @type number
12984 * @default 0.25
12985 */
12986 initialOpacity: 0.25,
12987
12988 /**
12989 * How fast (opacity per second) the wave fades out.
12990 *
12991 * @attribute opacityDecayVelocity
12992 * @type number
12993 * @default 0.8
12994 */
12995 opacityDecayVelocity: 0.8,
12996
12997 backgroundFill: true,
12998 pixelDensity: 2,
12999
13000 eventDelegates: {
13001 down: 'downAction',
13002 up: 'upAction'
13003 },
13004
13005 ready: function() {
13006 this.waves = [];
13007 },
13008
13009 downAction: function(e) {
13010 var wave = createWave(this);
13011
13012 this.cancelled = false;
13013 wave.isMouseDown = true;
13014 wave.tDown = 0.0;
13015 wave.tUp = 0.0;
13016 wave.mouseUpStart = 0.0;
13017 wave.mouseDownStart = now();
13018
13019 var rect = this.getBoundingClientRect();
13020 var width = rect.width;
13021 var height = rect.height;
13022 var touchX = e.x - rect.left;
13023 var touchY = e.y - rect.top;
13024
13025 wave.startPosition = {x:touchX, y:touchY};
13026
13027 if (this.classList.contains("recenteringTouch")) {
13028 wave.endPosition = {x: width / 2, y: height / 2};
13029 wave.slideDistance = dist(wave.startPosition, wave.endPosition);
13030 }
13031 wave.containerSize = Math.max(width, height);
13032 wave.containerWidth = width;
13033 wave.containerHeight = height;
13034 wave.maxRadius = distanceFromPointToFurthestCorner(wave.startPosition, { w: width, h: height});
13035
13036 // The wave is circular so constrain its container to 1:1
13037 wave.wc.style.top = (wave.containerHeight - wave.containerSize) / 2 + 'p x';
13038 wave.wc.style.left = (wave.containerWidth - wave.containerSize) / 2 + 'p x';
13039 wave.wc.style.width = wave.containerSize + 'px';
13040 wave.wc.style.height = wave.containerSize + 'px';
13041
13042 this.waves.push(wave);
13043
13044 if (!this._loop) {
13045 this._loop = this.animate.bind(this, {
13046 width: width,
13047 height: height
13048 });
13049 requestAnimationFrame(this._loop);
13050 }
13051 // else there is already a rAF
13052 },
13053
13054 upAction: function() {
13055 for (var i = 0; i < this.waves.length; i++) {
13056 // Declare the next wave that has mouse down to be mouse'ed up.
13057 var wave = this.waves[i];
13058 if (wave.isMouseDown) {
13059 wave.isMouseDown = false
13060 wave.mouseUpStart = now();
13061 wave.mouseDownStart = 0;
13062 wave.tUp = 0.0;
13063 break;
13064 }
13065 }
13066 this._loop && requestAnimationFrame(this._loop);
13067 },
13068
13069 cancel: function() {
13070 this.cancelled = true;
13071 },
13072
13073 animate: function(ctx) {
13074 var shouldRenderNextFrame = false;
13075
13076 var deleteTheseWaves = [];
13077 // The oldest wave's touch down duration
13078 var longestTouchDownDuration = 0;
13079 var longestTouchUpDuration = 0;
13080 // Save the last known wave color
13081 var lastWaveColor = null;
13082 // wave animation values
13083 var anim = {
13084 initialOpacity: this.initialOpacity,
13085 opacityDecayVelocity: this.opacityDecayVelocity,
13086 height: ctx.height,
13087 width: ctx.width
13088 }
13089
13090 for (var i = 0; i < this.waves.length; i++) {
13091 var wave = this.waves[i];
13092
13093 if (wave.mouseDownStart > 0) {
13094 wave.tDown = now() - wave.mouseDownStart;
13095 }
13096 if (wave.mouseUpStart > 0) {
13097 wave.tUp = now() - wave.mouseUpStart;
13098 }
13099
13100 // Determine how long the touch has been up or down.
13101 var tUp = wave.tUp;
13102 var tDown = wave.tDown;
13103 longestTouchDownDuration = Math.max(longestTouchDownDuration, tDown);
13104 longestTouchUpDuration = Math.max(longestTouchUpDuration, tUp);
13105
13106 // Obtain the instantenous size and alpha of the ripple.
13107 var radius = waveRadiusFn(tDown, tUp, anim);
13108 var waveAlpha = waveOpacityFn(tDown, tUp, anim);
13109 var waveColor = cssColorWithAlpha(wave.waveColor, waveAlpha);
13110 lastWaveColor = wave.waveColor;
13111
13112 // Position of the ripple.
13113 var x = wave.startPosition.x;
13114 var y = wave.startPosition.y;
13115
13116 // Ripple gravitational pull to the center of the canvas.
13117 if (wave.endPosition) {
13118
13119 // This translates from the origin to the center of the view based on the max dimension of
13120 var translateFraction = Math.min(1, radius / wave.containerSize * 2 / Math.sqrt(2) );
13121
13122 x += translateFraction * (wave.endPosition.x - wave.startPosition.x) ;
13123 y += translateFraction * (wave.endPosition.y - wave.startPosition.y) ;
13124 }
13125
13126 // If we do a background fill fade too, work out the correct color.
13127 var bgFillColor = null;
13128 if (this.backgroundFill) {
13129 var bgFillAlpha = waveOuterOpacityFn(tDown, tUp, anim);
13130 bgFillColor = cssColorWithAlpha(wave.waveColor, bgFillAlpha);
13131 }
13132
13133 // Draw the ripple.
13134 drawRipple(wave, x, y, radius, waveAlpha, bgFillAlpha);
13135
13136 // Determine whether there is any more rendering to be done.
13137 var maximumWave = waveAtMaximum(wave, radius, anim);
13138 var waveDissipated = waveDidFinish(wave, radius, anim);
13139 var shouldKeepWave = !waveDissipated || maximumWave;
13140 // keep rendering dissipating wave when at maximum radius on upAction
13141 var shouldRenderWaveAgain = wave.mouseUpStart ? !waveDissipated : !max imumWave;
13142 shouldRenderNextFrame = shouldRenderNextFrame || shouldRenderWaveAgain ;
13143 if (!shouldKeepWave || this.cancelled) {
13144 deleteTheseWaves.push(wave);
13145 }
13146 }
13147
13148 if (shouldRenderNextFrame) {
13149 requestAnimationFrame(this._loop);
13150 }
13151
13152 for (var i = 0; i < deleteTheseWaves.length; ++i) {
13153 var wave = deleteTheseWaves[i];
13154 removeWaveFromScope(this, wave);
13155 }
13156
13157 if (!this.waves.length && this._loop) {
13158 // clear the background color
13159 this.$.bg.style.backgroundColor = null;
13160 this._loop = null;
13161 this.fire('core-transitionend');
13162 }
13163 }
13164
13165 });
13166
13167 })();
13168
13169 ;
13170
13171 (function() {
13172 /*
13173 * Chrome uses an older version of DOM Level 3 Keyboard Events
13174 *
13175 * Most keys are labeled as text, but some are Unicode codepoints.
13176 * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-200712 21/keyset.html#KeySet-Set
13177 */
13178 var KEY_IDENTIFIER = {
13179 'U+0009': 'tab',
13180 'U+001B': 'esc',
13181 'U+0020': 'space',
13182 'U+002A': '*',
13183 'U+0030': '0',
13184 'U+0031': '1',
13185 'U+0032': '2',
13186 'U+0033': '3',
13187 'U+0034': '4',
13188 'U+0035': '5',
13189 'U+0036': '6',
13190 'U+0037': '7',
13191 'U+0038': '8',
13192 'U+0039': '9',
13193 'U+0041': 'a',
13194 'U+0042': 'b',
13195 'U+0043': 'c',
13196 'U+0044': 'd',
13197 'U+0045': 'e',
13198 'U+0046': 'f',
13199 'U+0047': 'g',
13200 'U+0048': 'h',
13201 'U+0049': 'i',
13202 'U+004A': 'j',
13203 'U+004B': 'k',
13204 'U+004C': 'l',
13205 'U+004D': 'm',
13206 'U+004E': 'n',
13207 'U+004F': 'o',
13208 'U+0050': 'p',
13209 'U+0051': 'q',
13210 'U+0052': 'r',
13211 'U+0053': 's',
13212 'U+0054': 't',
13213 'U+0055': 'u',
13214 'U+0056': 'v',
13215 'U+0057': 'w',
13216 'U+0058': 'x',
13217 'U+0059': 'y',
13218 'U+005A': 'z',
13219 'U+007F': 'del'
13220 };
13221
13222 /*
13223 * Special table for KeyboardEvent.keyCode.
13224 * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even bett er than that
13225 *
13226 * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEve nt.keyCode#Value_of_keyCode
13227 */
13228 var KEY_CODE = {
13229 9: 'tab',
13230 13: 'enter',
13231 27: 'esc',
13232 33: 'pageup',
13233 34: 'pagedown',
13234 35: 'end',
13235 36: 'home',
13236 32: 'space',
13237 37: 'left',
13238 38: 'up',
13239 39: 'right',
13240 40: 'down',
13241 46: 'del',
13242 106: '*'
13243 };
13244
13245 /*
13246 * KeyboardEvent.key is mostly represented by printable character made by th e keyboard, with unprintable keys labeled
13247 * nicely.
13248 *
13249 * However, on OS X, Alt+char can make a Unicode character that follows an A pple-specific mapping. In this case, we
13250 * fall back to .keyCode.
13251 */
13252 var KEY_CHAR = /[a-z0-9*]/;
13253
13254 function transformKey(key) {
13255 var validKey = '';
13256 if (key) {
13257 var lKey = key.toLowerCase();
13258 if (lKey.length == 1) {
13259 if (KEY_CHAR.test(lKey)) {
13260 validKey = lKey;
13261 }
13262 } else if (lKey == 'multiply') {
13263 // numpad '*' can map to Multiply on IE/Windows
13264 validKey = '*';
13265 } else {
13266 validKey = lKey;
13267 }
13268 }
13269 return validKey;
13270 }
13271
13272 var IDENT_CHAR = /U\+/;
13273 function transformKeyIdentifier(keyIdent) {
13274 var validKey = '';
13275 if (keyIdent) {
13276 if (IDENT_CHAR.test(keyIdent)) {
13277 validKey = KEY_IDENTIFIER[keyIdent];
13278 } else {
13279 validKey = keyIdent.toLowerCase();
13280 }
13281 }
13282 return validKey;
13283 }
13284
13285 function transformKeyCode(keyCode) {
13286 var validKey = '';
13287 if (Number(keyCode)) {
13288 if (keyCode >= 65 && keyCode <= 90) {
13289 // ascii a-z
13290 // lowercase is 32 offset from uppercase
13291 validKey = String.fromCharCode(32 + keyCode);
13292 } else if (keyCode >= 112 && keyCode <= 123) {
13293 // function keys f1-f12
13294 validKey = 'f' + (keyCode - 112);
13295 } else if (keyCode >= 48 && keyCode <= 57) {
13296 // top 0-9 keys
13297 validKey = String(48 - keyCode);
13298 } else if (keyCode >= 96 && keyCode <= 105) {
13299 // num pad 0-9
13300 validKey = String(96 - keyCode);
13301 } else {
13302 validKey = KEY_CODE[keyCode];
13303 }
13304 }
13305 return validKey;
13306 }
13307
13308 function keyboardEventToKey(ev) {
13309 // fall back from .key, to .keyIdentifier, to .keyCode, and then to .detai l.key to support artificial keyboard events
13310 var normalizedKey = transformKey(ev.key) || transformKeyIdentifier(ev.keyI dentifier) || transformKeyCode(ev.keyCode) || transformKey(ev.detail.key) || '';
13311 return {
13312 shift: ev.shiftKey,
13313 ctrl: ev.ctrlKey,
13314 meta: ev.metaKey,
13315 alt: ev.altKey,
13316 key: normalizedKey
13317 };
13318 }
13319
13320 /*
13321 * Input: ctrl+shift+f7 => {ctrl: true, shift: true, key: 'f7'}
13322 * ctrl/space => {ctrl: true} || {key: space}
13323 */
13324 function stringToKey(keyCombo) {
13325 var keys = keyCombo.split('+');
13326 var keyObj = Object.create(null);
13327 keys.forEach(function(key) {
13328 if (key == 'shift') {
13329 keyObj.shift = true;
13330 } else if (key == 'ctrl') {
13331 keyObj.ctrl = true;
13332 } else if (key == 'alt') {
13333 keyObj.alt = true;
13334 } else {
13335 keyObj.key = key;
13336 }
13337 });
13338 return keyObj;
13339 }
13340
13341 function keyMatches(a, b) {
13342 return Boolean(a.alt) == Boolean(b.alt) && Boolean(a.ctrl) == Boolean(b.ct rl) && Boolean(a.shift) == Boolean(b.shift) && a.key === b.key;
13343 }
13344
13345 /**
13346 * Fired when a keycombo in `keys` is pressed.
13347 *
13348 * @event keys-pressed
13349 */
13350 function processKeys(ev) {
13351 var current = keyboardEventToKey(ev);
13352 for (var i = 0, dk; i < this._desiredKeys.length; i++) {
13353 dk = this._desiredKeys[i];
13354 if (keyMatches(dk, current)) {
13355 ev.preventDefault();
13356 ev.stopPropagation();
13357 this.fire('keys-pressed', current, this, false);
13358 break;
13359 }
13360 }
13361 }
13362
13363 function listen(node, handler) {
13364 if (node && node.addEventListener) {
13365 node.addEventListener('keydown', handler);
13366 }
13367 }
13368
13369 function unlisten(node, handler) {
13370 if (node && node.removeEventListener) {
13371 node.removeEventListener('keydown', handler);
13372 }
13373 }
13374
13375 Polymer('core-a11y-keys', {
13376 created: function() {
13377 this._keyHandler = processKeys.bind(this);
13378 },
13379 attached: function() {
13380 if (!this.target) {
13381 this.target = this.parentNode;
13382 }
13383 listen(this.target, this._keyHandler);
13384 },
13385 detached: function() {
13386 unlisten(this.target, this._keyHandler);
13387 },
13388 publish: {
13389 /**
13390 * The set of key combinations that will be matched (in keys syntax).
13391 *
13392 * @attribute keys
13393 * @type string
13394 * @default ''
13395 */
13396 keys: '',
13397 /**
13398 * The node that will fire keyboard events.
13399 * Default to this element's parentNode unless one is assigned
13400 *
13401 * @attribute target
13402 * @type Node
13403 * @default this.parentNode
13404 */
13405 target: null
13406 },
13407 keysChanged: function() {
13408 // * can have multiple mappings: shift+8, * on numpad or Multiply on num pad
13409 var normalized = this.keys.replace('*', '* shift+*');
13410 this._desiredKeys = normalized.toLowerCase().split(' ').map(stringToKey) ;
13411 },
13412 targetChanged: function(oldTarget) {
13413 unlisten(oldTarget, this._keyHandler);
13414 listen(this.target, this._keyHandler);
13415 }
13416 });
13417 })();
13418 ;
13419
13420
13421 Polymer('paper-radio-button', {
13422
13423 /**
13424 * Fired when the checked state changes due to user interaction.
13425 *
13426 * @event change
13427 */
13428
13429 /**
13430 * Fired when the checked state changes.
13431 *
13432 * @event core-change
13433 */
13434
13435 publish: {
13436 /**
13437 * Gets or sets the state, `true` is checked and `false` is unchecked.
13438 *
13439 * @attribute checked
13440 * @type boolean
13441 * @default false
13442 */
13443 checked: {value: false, reflect: true},
13444
13445 /**
13446 * The label for the radio button.
13447 *
13448 * @attribute label
13449 * @type string
13450 * @default ''
13451 */
13452 label: '',
13453
13454 /**
13455 * Normally the user cannot uncheck the radio button by tapping once
13456 * checked. Setting this property to `true` makes the radio button
13457 * toggleable from checked to unchecked.
13458 *
13459 * @attribute toggles
13460 * @type boolean
13461 * @default false
13462 */
13463 toggles: false,
13464
13465 /**
13466 * If true, the user cannot interact with this element.
13467 *
13468 * @attribute disabled
13469 * @type boolean
13470 * @default false
13471 */
13472 disabled: {value: false, reflect: true}
13473 },
13474
13475 eventDelegates: {
13476 tap: 'tap'
13477 },
13478
13479 tap: function() {
13480 if (this.disabled) {
13481 return;
13482 }
13483 var old = this.checked;
13484 this.toggle();
13485 if (this.checked !== old) {
13486 this.fire('change');
13487 }
13488 },
13489
13490 toggle: function() {
13491 this.checked = !this.toggles || !this.checked;
13492 },
13493
13494 checkedChanged: function() {
13495 this.setAttribute('aria-checked', this.checked ? 'true' : 'false');
13496 this.fire('core-change');
13497 },
13498
13499 labelChanged: function() {
13500 this.setAttribute('aria-label', this.label);
13501 }
13502
13503 });
13504
13505 ;
13506
13507
13508 Polymer('paper-checkbox', {
13509
13510 /**
13511 * Fired when the checked state changes due to user interaction.
13512 *
13513 * @event change
13514 */
13515
13516 /**
13517 * Fired when the checked state changes.
13518 *
13519 * @event core-change
13520 */
13521
13522 toggles: true,
13523
13524 checkedChanged: function() {
13525 this.$.checkbox.classList.toggle('checked', this.checked);
13526 this.setAttribute('aria-checked', this.checked ? 'true': 'false');
13527 this.$.checkmark.classList.toggle('hidden', !this.checked);
13528 this.fire('core-change');
13529 },
13530
13531 checkboxAnimationEnd: function() {
13532 var cl = this.$.checkmark.classList;
13533 cl.toggle('hidden', !this.checked && cl.contains('hidden'));
13534 }
13535
13536 });
13537
13538 ;
13539
13540 Polymer('paper-shadow',{
13541
13542 publish: {
13543
13544 /**
13545 * The z-depth of this shadow, from 0-5. Setting this property
13546 * after element creation has no effect. Use `setZ()` instead.
13547 *
13548 * @attribute z
13549 * @type number
13550 * @default 1
13551 */
13552 z: 1,
13553
13554 /**
13555 * Set this to true to animate the shadow when setting a new
13556 * `z` value.
13557 *
13558 * @attribute animated
13559 * @type boolean
13560 * @default false
13561 */
13562 animated: false
13563
13564 },
13565
13566 /**
13567 * Set the z-depth of the shadow. This should be used after element
13568 * creation instead of setting the z property directly.
13569 *
13570 * @method setZ
13571 * @param {Number} newZ
13572 */
13573 setZ: function(newZ) {
13574 if (this.z !== newZ) {
13575 this.$['shadow-bottom'].classList.remove('paper-shadow-bottom-z-' + this .z);
13576 this.$['shadow-bottom'].classList.add('paper-shadow-bottom-z-' + newZ);
13577 this.$['shadow-top'].classList.remove('paper-shadow-top-z-' + this.z);
13578 this.$['shadow-top'].classList.add('paper-shadow-top-z-' + newZ);
13579 this.z = newZ;
13580 }
13581 }
13582
13583 });
13584 ;
13585
13586
13587 (function() {
13588
13589 var SKIP_ID = 'meta';
13590 var metaData = {}, metaArray = {};
13591
13592 Polymer('core-meta', {
13593
13594 /**
13595 * The type of meta-data. All meta-data with the same type with be
13596 * stored together.
13597 *
13598 * @attribute type
13599 * @type string
13600 * @default 'default'
13601 */
13602 type: 'default',
13603
13604 alwaysPrepare: true,
13605
13606 ready: function() {
13607 this.register(this.id);
13608 },
13609
13610 get metaArray() {
13611 var t = this.type;
13612 if (!metaArray[t]) {
13613 metaArray[t] = [];
13614 }
13615 return metaArray[t];
13616 },
13617
13618 get metaData() {
13619 var t = this.type;
13620 if (!metaData[t]) {
13621 metaData[t] = {};
13622 }
13623 return metaData[t];
13624 },
13625
13626 register: function(id, old) {
13627 if (id && id !== SKIP_ID) {
13628 this.unregister(this, old);
13629 this.metaData[id] = this;
13630 this.metaArray.push(this);
13631 }
13632 },
13633
13634 unregister: function(meta, id) {
13635 delete this.metaData[id || meta.id];
13636 var i = this.metaArray.indexOf(meta);
13637 if (i >= 0) {
13638 this.metaArray.splice(i, 1);
13639 }
13640 },
13641
13642 /**
13643 * Returns a list of all meta-data elements with the same type.
13644 *
13645 * @property list
13646 * @type array
13647 * @default []
13648 */
13649 get list() {
13650 return this.metaArray;
13651 },
13652
13653 /**
13654 * Retrieves meta-data by ID.
13655 *
13656 * @method byId
13657 * @param {String} id The ID of the meta-data to be returned.
13658 * @returns Returns meta-data.
13659 */
13660 byId: function(id) {
13661 return this.metaData[id];
13662 }
13663
13664 });
13665
13666 })();
13667
13668 ;
13669
13670 Polymer('core-transition', {
13671
13672 type: 'transition',
13673
13674 /**
13675 * Run the animation.
13676 *
13677 * @method go
13678 * @param {Node} node The node to apply the animation on
13679 * @param {Object} state State info
13680 */
13681 go: function(node, state) {
13682 this.complete(node);
13683 },
13684
13685 /**
13686 * Set up the animation. This may include injecting a stylesheet,
13687 * applying styles, creating a web animations object, etc.. This
13688 *
13689 * @method setup
13690 * @param {Node} node The animated node
13691 */
13692 setup: function(node) {
13693 },
13694
13695 /**
13696 * Tear down the animation.
13697 *
13698 * @method teardown
13699 * @param {Node} node The animated node
13700 */
13701 teardown: function(node) {
13702 },
13703
13704 /**
13705 * Called when the animation completes. This function also fires the
13706 * `core-transitionend` event.
13707 *
13708 * @method complete
13709 * @param {Node} node The animated node
13710 */
13711 complete: function(node) {
13712 this.fire('core-transitionend', null, node);
13713 },
13714
13715 /**
13716 * Utility function to listen to an event on a node once.
13717 *
13718 * @method listenOnce
13719 * @param {Node} node The animated node
13720 * @param {string} event Name of an event
13721 * @param {Function} fn Event handler
13722 * @param {Array} args Additional arguments to pass to `fn`
13723 */
13724 listenOnce: function(node, event, fn, args) {
13725 var self = this;
13726 var listener = function() {
13727 fn.apply(self, args);
13728 node.removeEventListener(event, listener, false);
13729 }
13730 node.addEventListener(event, listener, false);
13731 }
13732
13733 });
13734 ;
13735
13736 Polymer('core-key-helper', {
13737 ENTER_KEY: 13,
13738 ESCAPE_KEY: 27
13739 });
13740 ;
13741
13742 (function() {
13743
13744 Polymer('core-overlay-layer', {
13745 publish: {
13746 opened: false
13747 },
13748 openedChanged: function() {
13749 this.classList.toggle('core-opened', this.opened);
13750 },
13751 /**
13752 * Adds an element to the overlay layer
13753 */
13754 addElement: function(element) {
13755 if (!this.parentNode) {
13756 document.querySelector('body').appendChild(this);
13757 }
13758 if (element.parentNode !== this) {
13759 element.__contents = [];
13760 var ip$ = element.querySelectorAll('content');
13761 for (var i=0, l=ip$.length, n; (i<l) && (n = ip$[i]); i++) {
13762 this.moveInsertedElements(n);
13763 this.cacheDomLocation(n);
13764 n.parentNode.removeChild(n);
13765 element.__contents.push(n);
13766 }
13767 this.cacheDomLocation(element);
13768 this.updateEventController(element);
13769 var h = this.makeHost();
13770 h.shadowRoot.appendChild(element);
13771 element.__host = h;
13772 }
13773 },
13774 makeHost: function() {
13775 var h = document.createElement('overlay-host');
13776 h.createShadowRoot();
13777 this.appendChild(h);
13778 return h;
13779 },
13780 moveInsertedElements: function(insertionPoint) {
13781 var n$ = insertionPoint.getDistributedNodes();
13782 var parent = insertionPoint.parentNode;
13783 insertionPoint.__contents = [];
13784 for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) {
13785 this.cacheDomLocation(n);
13786 this.updateEventController(n);
13787 insertionPoint.__contents.push(n);
13788 parent.appendChild(n);
13789 }
13790 },
13791 updateEventController: function(element) {
13792 element.eventController = this.element.findController(element);
13793 },
13794 /**
13795 * Removes an element from the overlay layer
13796 */
13797 removeElement: function(element) {
13798 element.eventController = null;
13799 this.replaceElement(element);
13800 var h = element.__host;
13801 if (h) {
13802 h.parentNode.removeChild(h);
13803 }
13804 },
13805 replaceElement: function(element) {
13806 if (element.__contents) {
13807 for (var i=0, c$=element.__contents, c; (c=c$[i]); i++) {
13808 this.replaceElement(c);
13809 }
13810 element.__contents = null;
13811 }
13812 if (element.__parentNode) {
13813 var n = element.__nextElementSibling && element.__nextElementSibling
13814 === element.__parentNode ? element.__nextElementSibling : null;
13815 element.__parentNode.insertBefore(element, n);
13816 }
13817 },
13818 cacheDomLocation: function(element) {
13819 element.__nextElementSibling = element.nextElementSibling;
13820 element.__parentNode = element.parentNode;
13821 }
13822 });
13823
13824 })();
13825 ;
13826
13827 (function() {
13828
13829 Polymer('core-overlay',Polymer.mixin({
13830
13831 publish: {
13832 /**
13833 * The target element that will be shown when the overlay is
13834 * opened. If unspecified, the core-overlay itself is the target.
13835 *
13836 * @attribute target
13837 * @type Object
13838 * @default the overlay element
13839 */
13840 target: null,
13841
13842
13843 /**
13844 * A `core-overlay`'s size is guaranteed to be
13845 * constrained to the window size. To achieve this, the sizingElement
13846 * is sized with a max-height/width. By default this element is the
13847 * target element, but it can be specifically set to a specific element
13848 * inside the target if that is more appropriate. This is useful, for
13849 * example, when a region inside the overlay should scroll if needed.
13850 *
13851 * @attribute sizingTarget
13852 * @type Object
13853 * @default the target element
13854 */
13855 sizingTarget: null,
13856
13857 /**
13858 * Set opened to true to show an overlay and to false to hide it.
13859 * A `core-overlay` may be made initially opened by setting its
13860 * `opened` attribute.
13861 * @attribute opened
13862 * @type boolean
13863 * @default false
13864 */
13865 opened: false,
13866
13867 /**
13868 * If true, the overlay has a backdrop darkening the rest of the screen.
13869 * The backdrop element is attached to the document body and may be styled
13870 * with the class `core-overlay-backdrop`. When opened the `core-opened`
13871 * class is applied.
13872 *
13873 * @attribute backdrop
13874 * @type boolean
13875 * @default false
13876 */
13877 backdrop: false,
13878
13879 /**
13880 * If true, the overlay is guaranteed to display above page content.
13881 *
13882 * @attribute layered
13883 * @type boolean
13884 * @default false
13885 */
13886 layered: false,
13887
13888 /**
13889 * By default an overlay will close automatically if the user
13890 * taps outside it or presses the escape key. Disable this
13891 * behavior by setting the `autoCloseDisabled` property to true.
13892 * @attribute autoCloseDisabled
13893 * @type boolean
13894 * @default false
13895 */
13896 autoCloseDisabled: false,
13897
13898 /**
13899 * By default an overlay will focus its target or an element inside
13900 * it with the `autoFocus` attribute. Disable this
13901 * behavior by setting the `autoFocusDisabled` property to true.
13902 * @attribute autoFocusDisabled
13903 * @type boolean
13904 * @default false
13905 */
13906 autoFocusDisabled: false,
13907
13908 /**
13909 * This property specifies an attribute on elements that should
13910 * close the overlay on tap. Should not set `closeSelector` if this
13911 * is set.
13912 *
13913 * @attribute closeAttribute
13914 * @type string
13915 * @default "core-overlay-toggle"
13916 */
13917 closeAttribute: 'core-overlay-toggle',
13918
13919 /**
13920 * This property specifies a selector matching elements that should
13921 * close the overlay on tap. Should not set `closeAttribute` if this
13922 * is set.
13923 *
13924 * @attribute closeSelector
13925 * @type string
13926 * @default ""
13927 */
13928 closeSelector: '',
13929
13930 /**
13931 * The transition property specifies a string which identifies a
13932 * <a href="../core-transition/">`core-transition`</a> element that
13933 * will be used to help the overlay open and close. The default
13934 * `core-transition-fade` will cause the overlay to fade in and out.
13935 *
13936 * @attribute transition
13937 * @type string
13938 * @default 'core-transition-fade'
13939 */
13940 transition: 'core-transition-fade'
13941
13942 },
13943
13944 captureEventName: 'tap',
13945 targetListeners: {
13946 'tap': 'tapHandler',
13947 'keydown': 'keydownHandler',
13948 'core-transitionend': 'transitionend'
13949 },
13950
13951 attached: function() {
13952 this.resizerAttachedHandler();
13953 },
13954
13955 detached: function() {
13956 this.resizerDetachedHandler();
13957 },
13958
13959 resizerShouldNotify: function() {
13960 return this.opened;
13961 },
13962
13963 registerCallback: function(element) {
13964 this.layer = document.createElement('core-overlay-layer');
13965 this.keyHelper = document.createElement('core-key-helper');
13966 this.meta = document.createElement('core-transition');
13967 this.scrim = document.createElement('div');
13968 this.scrim.className = 'core-overlay-backdrop';
13969 },
13970
13971 ready: function() {
13972 this.target = this.target || this;
13973 // flush to ensure styles are installed before paint
13974 Polymer.flush();
13975 },
13976
13977 /**
13978 * Toggle the opened state of the overlay.
13979 * @method toggle
13980 */
13981 toggle: function() {
13982 this.opened = !this.opened;
13983 },
13984
13985 /**
13986 * Open the overlay. This is equivalent to setting the `opened`
13987 * property to true.
13988 * @method open
13989 */
13990 open: function() {
13991 this.opened = true;
13992 },
13993
13994 /**
13995 * Close the overlay. This is equivalent to setting the `opened`
13996 * property to false.
13997 * @method close
13998 */
13999 close: function() {
14000 this.opened = false;
14001 },
14002
14003 domReady: function() {
14004 this.ensureTargetSetup();
14005 },
14006
14007 targetChanged: function(old) {
14008 if (this.target) {
14009 // really make sure tabIndex is set
14010 if (this.target.tabIndex < 0) {
14011 this.target.tabIndex = -1;
14012 }
14013 this.addElementListenerList(this.target, this.targetListeners);
14014 this.target.style.display = 'none';
14015 this.target.__overlaySetup = false;
14016 }
14017 if (old) {
14018 this.removeElementListenerList(old, this.targetListeners);
14019 var transition = this.getTransition();
14020 if (transition) {
14021 transition.teardown(old);
14022 } else {
14023 old.style.position = '';
14024 old.style.outline = '';
14025 }
14026 old.style.display = '';
14027 }
14028 },
14029
14030 transitionChanged: function(old) {
14031 if (!this.target) {
14032 return;
14033 }
14034 if (old) {
14035 this.getTransition(old).teardown(this.target);
14036 }
14037 this.target.__overlaySetup = false;
14038 },
14039
14040 // NOTE: wait to call this until we're as sure as possible that target
14041 // is styled.
14042 ensureTargetSetup: function() {
14043 if (!this.target || this.target.__overlaySetup) {
14044 return;
14045 }
14046 if (!this.sizingTarget) {
14047 this.sizingTarget = this.target;
14048 }
14049 this.target.__overlaySetup = true;
14050 this.target.style.display = '';
14051 var transition = this.getTransition();
14052 if (transition) {
14053 transition.setup(this.target);
14054 }
14055 var style = this.target.style;
14056 var computed = getComputedStyle(this.target);
14057 if (computed.position === 'static') {
14058 style.position = 'fixed';
14059 }
14060 style.outline = 'none';
14061 style.display = 'none';
14062 },
14063
14064 openedChanged: function() {
14065 this.transitioning = true;
14066 this.ensureTargetSetup();
14067 this.prepareRenderOpened();
14068 // async here to allow overlay layer to become visible.
14069 this.async(function() {
14070 this.target.style.display = '';
14071 // force layout to ensure transitions will go
14072 this.target.offsetWidth;
14073 this.renderOpened();
14074 });
14075 this.fire('core-overlay-open', this.opened);
14076 },
14077
14078 // tasks which must occur before opening; e.g. making the element visible
14079 prepareRenderOpened: function() {
14080 if (this.opened) {
14081 addOverlay(this);
14082 }
14083 this.prepareBackdrop();
14084 // async so we don't auto-close immediately via a click.
14085 this.async(function() {
14086 if (!this.autoCloseDisabled) {
14087 this.enableElementListener(this.opened, document,
14088 this.captureEventName, 'captureHandler', true);
14089 }
14090 });
14091 this.enableElementListener(this.opened, window, 'resize',
14092 'resizeHandler');
14093
14094 if (this.opened) {
14095 // force layout so SD Polyfill renders
14096 this.target.offsetHeight;
14097 this.discoverDimensions();
14098 // if we are showing, then take care when positioning
14099 this.preparePositioning();
14100 this.positionTarget();
14101 this.updateTargetDimensions();
14102 this.finishPositioning();
14103 if (this.layered) {
14104 this.layer.addElement(this.target);
14105 this.layer.opened = this.opened;
14106 }
14107 }
14108 },
14109
14110 // tasks which cause the overlay to actually open; typically play an
14111 // animation
14112 renderOpened: function() {
14113 this.notifyResize();
14114 var transition = this.getTransition();
14115 if (transition) {
14116 transition.go(this.target, {opened: this.opened});
14117 } else {
14118 this.transitionend();
14119 }
14120 this.renderBackdropOpened();
14121 },
14122
14123 // finishing tasks; typically called via a transition
14124 transitionend: function(e) {
14125 // make sure this is our transition event.
14126 if (e && e.target !== this.target) {
14127 return;
14128 }
14129 this.transitioning = false;
14130 if (!this.opened) {
14131 this.resetTargetDimensions();
14132 this.target.style.display = 'none';
14133 this.completeBackdrop();
14134 removeOverlay(this);
14135 if (this.layered) {
14136 if (!currentOverlay()) {
14137 this.layer.opened = this.opened;
14138 }
14139 this.layer.removeElement(this.target);
14140 }
14141 }
14142 this.fire('core-overlay-' + (this.opened ? 'open' : 'close') +
14143 '-completed');
14144 this.applyFocus();
14145 },
14146
14147 prepareBackdrop: function() {
14148 if (this.backdrop && this.opened) {
14149 if (!this.scrim.parentNode) {
14150 document.body.appendChild(this.scrim);
14151 this.scrim.style.zIndex = currentOverlayZ() - 1;
14152 }
14153 trackBackdrop(this);
14154 }
14155 },
14156
14157 renderBackdropOpened: function() {
14158 if (this.backdrop && getBackdrops().length < 2) {
14159 this.scrim.classList.toggle('core-opened', this.opened);
14160 }
14161 },
14162
14163 completeBackdrop: function() {
14164 if (this.backdrop) {
14165 trackBackdrop(this);
14166 if (getBackdrops().length === 0) {
14167 this.scrim.parentNode.removeChild(this.scrim);
14168 }
14169 }
14170 },
14171
14172 preparePositioning: function() {
14173 this.target.style.transition = this.target.style.webkitTransition = 'none' ;
14174 this.target.style.transform = this.target.style.webkitTransform = 'none';
14175 this.target.style.display = '';
14176 },
14177
14178 discoverDimensions: function() {
14179 if (this.dimensions) {
14180 return;
14181 }
14182 var target = getComputedStyle(this.target);
14183 var sizer = getComputedStyle(this.sizingTarget);
14184 this.dimensions = {
14185 position: {
14186 v: target.top !== 'auto' ? 'top' : (target.bottom !== 'auto' ?
14187 'bottom' : null),
14188 h: target.left !== 'auto' ? 'left' : (target.right !== 'auto' ?
14189 'right' : null),
14190 css: target.position
14191 },
14192 size: {
14193 v: sizer.maxHeight !== 'none',
14194 h: sizer.maxWidth !== 'none'
14195 },
14196 margin: {
14197 top: parseInt(target.marginTop) || 0,
14198 right: parseInt(target.marginRight) || 0,
14199 bottom: parseInt(target.marginBottom) || 0,
14200 left: parseInt(target.marginLeft) || 0
14201 }
14202 };
14203 },
14204
14205 finishPositioning: function(target) {
14206 this.target.style.display = 'none';
14207 this.target.style.transform = this.target.style.webkitTransform = '';
14208 // force layout to avoid application of transform
14209 this.target.offsetWidth;
14210 this.target.style.transition = this.target.style.webkitTransition = '';
14211 },
14212
14213 getTransition: function(name) {
14214 return this.meta.byId(name || this.transition);
14215 },
14216
14217 getFocusNode: function() {
14218 return this.target.querySelector('[autofocus]') || this.target;
14219 },
14220
14221 applyFocus: function() {
14222 var focusNode = this.getFocusNode();
14223 if (this.opened) {
14224 if (!this.autoFocusDisabled) {
14225 focusNode.focus();
14226 }
14227 } else {
14228 focusNode.blur();
14229 if (currentOverlay() == this) {
14230 console.warn('Current core-overlay is attempting to focus itself as ne xt! (bug)');
14231 } else {
14232 focusOverlay();
14233 }
14234 }
14235 },
14236
14237 positionTarget: function() {
14238 // fire positioning event
14239 this.fire('core-overlay-position', {target: this.target,
14240 sizingTarget: this.sizingTarget, opened: this.opened});
14241 if (!this.dimensions.position.v) {
14242 this.target.style.top = '0px';
14243 }
14244 if (!this.dimensions.position.h) {
14245 this.target.style.left = '0px';
14246 }
14247 },
14248
14249 updateTargetDimensions: function() {
14250 this.sizeTarget();
14251 this.repositionTarget();
14252 },
14253
14254 sizeTarget: function() {
14255 this.sizingTarget.style.boxSizing = 'border-box';
14256 var dims = this.dimensions;
14257 var rect = this.target.getBoundingClientRect();
14258 if (!dims.size.v) {
14259 this.sizeDimension(rect, dims.position.v, 'top', 'bottom', 'Height');
14260 }
14261 if (!dims.size.h) {
14262 this.sizeDimension(rect, dims.position.h, 'left', 'right', 'Width');
14263 }
14264 },
14265
14266 sizeDimension: function(rect, positionedBy, start, end, extent) {
14267 var dims = this.dimensions;
14268 var flip = (positionedBy === end);
14269 var m = flip ? start : end;
14270 var ws = window['inner' + extent];
14271 var o = dims.margin[m] + (flip ? ws - rect[end] :
14272 rect[start]);
14273 var offset = 'offset' + extent;
14274 var o2 = this.target[offset] - this.sizingTarget[offset];
14275 this.sizingTarget.style['max' + extent] = (ws - o - o2) + 'px';
14276 },
14277
14278 // vertically and horizontally center if not positioned
14279 repositionTarget: function() {
14280 // only center if position fixed.
14281 if (this.dimensions.position.css !== 'fixed') {
14282 return;
14283 }
14284 if (!this.dimensions.position.v) {
14285 var t = (window.innerHeight - this.target.offsetHeight) / 2;
14286 t -= this.dimensions.margin.top;
14287 this.target.style.top = t + 'px';
14288 }
14289
14290 if (!this.dimensions.position.h) {
14291 var l = (window.innerWidth - this.target.offsetWidth) / 2;
14292 l -= this.dimensions.margin.left;
14293 this.target.style.left = l + 'px';
14294 }
14295 },
14296
14297 resetTargetDimensions: function() {
14298 if (!this.dimensions || !this.dimensions.size.v) {
14299 this.sizingTarget.style.maxHeight = '';
14300 this.target.style.top = '';
14301 }
14302 if (!this.dimensions || !this.dimensions.size.h) {
14303 this.sizingTarget.style.maxWidth = '';
14304 this.target.style.left = '';
14305 }
14306 this.dimensions = null;
14307 },
14308
14309 tapHandler: function(e) {
14310 // closeSelector takes precedence since closeAttribute has a default non-n ull value.
14311 if (e.target &&
14312 (this.closeSelector && e.target.matches(this.closeSelector)) ||
14313 (this.closeAttribute && e.target.hasAttribute(this.closeAttribute))) {
14314 this.toggle();
14315 } else {
14316 if (this.autoCloseJob) {
14317 this.autoCloseJob.stop();
14318 this.autoCloseJob = null;
14319 }
14320 }
14321 },
14322
14323 // We use the traditional approach of capturing events on document
14324 // to to determine if the overlay needs to close. However, due to
14325 // ShadowDOM event retargeting, the event target is not useful. Instead
14326 // of using it, we attempt to close asynchronously and prevent the close
14327 // if a tap event is immediately heard on the target.
14328 // TODO(sorvell): This approach will not work with modal. For
14329 // this we need a scrim.
14330 captureHandler: function(e) {
14331 if (!this.autoCloseDisabled && (currentOverlay() == this)) {
14332 this.autoCloseJob = this.job(this.autoCloseJob, function() {
14333 this.close();
14334 });
14335 }
14336 },
14337
14338 keydownHandler: function(e) {
14339 if (!this.autoCloseDisabled && (e.keyCode == this.keyHelper.ESCAPE_KEY)) {
14340 this.close();
14341 e.stopPropagation();
14342 }
14343 },
14344
14345 /**
14346 * Extensions of core-overlay should implement the `resizeHandler`
14347 * method to adjust the size and position of the overlay when the
14348 * browser window resizes.
14349 * @method resizeHandler
14350 */
14351 resizeHandler: function() {
14352 this.updateTargetDimensions();
14353 },
14354
14355 // TODO(sorvell): these utility methods should not be here.
14356 addElementListenerList: function(node, events) {
14357 for (var i in events) {
14358 this.addElementListener(node, i, events[i]);
14359 }
14360 },
14361
14362 removeElementListenerList: function(node, events) {
14363 for (var i in events) {
14364 this.removeElementListener(node, i, events[i]);
14365 }
14366 },
14367
14368 enableElementListener: function(enable, node, event, methodName, capture) {
14369 if (enable) {
14370 this.addElementListener(node, event, methodName, capture);
14371 } else {
14372 this.removeElementListener(node, event, methodName, capture);
14373 }
14374 },
14375
14376 addElementListener: function(node, event, methodName, capture) {
14377 var fn = this._makeBoundListener(methodName);
14378 if (node && fn) {
14379 Polymer.addEventListener(node, event, fn, capture);
14380 }
14381 },
14382
14383 removeElementListener: function(node, event, methodName, capture) {
14384 var fn = this._makeBoundListener(methodName);
14385 if (node && fn) {
14386 Polymer.removeEventListener(node, event, fn, capture);
14387 }
14388 },
14389
14390 _makeBoundListener: function(methodName) {
14391 var self = this, method = this[methodName];
14392 if (!method) {
14393 return;
14394 }
14395 var bound = '_bound' + methodName;
14396 if (!this[bound]) {
14397 this[bound] = function(e) {
14398 method.call(self, e);
14399 };
14400 }
14401 return this[bound];
14402 }
14403
14404 }, Polymer.CoreResizer));
14405
14406 // TODO(sorvell): This should be an element with private state so it can
14407 // be independent of overlay.
14408 // track overlays for z-index and focus managemant
14409 var overlays = [];
14410 function addOverlay(overlay) {
14411 var z0 = currentOverlayZ();
14412 overlays.push(overlay);
14413 var z1 = currentOverlayZ();
14414 if (z1 <= z0) {
14415 applyOverlayZ(overlay, z0);
14416 }
14417 }
14418
14419 function removeOverlay(overlay) {
14420 var i = overlays.indexOf(overlay);
14421 if (i >= 0) {
14422 overlays.splice(i, 1);
14423 setZ(overlay, '');
14424 }
14425 }
14426
14427 function applyOverlayZ(overlay, aboveZ) {
14428 setZ(overlay.target, aboveZ + 2);
14429 }
14430
14431 function setZ(element, z) {
14432 element.style.zIndex = z;
14433 }
14434
14435 function currentOverlay() {
14436 return overlays[overlays.length-1];
14437 }
14438
14439 var DEFAULT_Z = 10;
14440
14441 function currentOverlayZ() {
14442 var z;
14443 var current = currentOverlay();
14444 if (current) {
14445 var z1 = window.getComputedStyle(current.target).zIndex;
14446 if (!isNaN(z1)) {
14447 z = Number(z1);
14448 }
14449 }
14450 return z || DEFAULT_Z;
14451 }
14452
14453 function focusOverlay() {
14454 var current = currentOverlay();
14455 // We have to be careful to focus the next overlay _after_ any current
14456 // transitions are complete (due to the state being toggled prior to the
14457 // transition). Otherwise, we risk infinite recursion when a transitioning
14458 // (closed) overlay becomes the current overlay.
14459 //
14460 // NOTE: We make the assumption that any overlay that completes a transition
14461 // will call into focusOverlay to kick the process back off. Currently:
14462 // transitionend -> applyFocus -> focusOverlay.
14463 if (current && !current.transitioning) {
14464 current.applyFocus();
14465 }
14466 }
14467
14468 var backdrops = [];
14469 function trackBackdrop(element) {
14470 if (element.opened) {
14471 backdrops.push(element);
14472 } else {
14473 var i = backdrops.indexOf(element);
14474 if (i >= 0) {
14475 backdrops.splice(i, 1);
14476 }
14477 }
14478 }
14479
14480 function getBackdrops() {
14481 return backdrops;
14482 }
14483 })();
14484 ;
14485
14486
14487 Polymer('core-transition-css', {
14488
14489 /**
14490 * The class that will be applied to all animated nodes.
14491 *
14492 * @attribute baseClass
14493 * @type string
14494 * @default "core-transition"
14495 */
14496 baseClass: 'core-transition',
14497
14498 /**
14499 * The class that will be applied to nodes in the opened state.
14500 *
14501 * @attribute openedClass
14502 * @type string
14503 * @default "core-opened"
14504 */
14505 openedClass: 'core-opened',
14506
14507 /**
14508 * The class that will be applied to nodes in the closed state.
14509 *
14510 * @attribute closedClass
14511 * @type string
14512 * @default "core-closed"
14513 */
14514 closedClass: 'core-closed',
14515
14516 /**
14517 * Event to listen to for animation completion.
14518 *
14519 * @attribute completeEventName
14520 * @type string
14521 * @default "transitionEnd"
14522 */
14523 completeEventName: 'transitionend',
14524
14525 publish: {
14526 /**
14527 * A secondary configuration attribute for the animation. The class
14528 * `<baseClass>-<transitionType` is applied to the animated node during
14529 * `setup`.
14530 *
14531 * @attribute transitionType
14532 * @type string
14533 */
14534 transitionType: null
14535 },
14536
14537 registerCallback: function(element) {
14538 this.transitionStyle = element.templateContent().firstElementChild;
14539 },
14540
14541 // template is just for loading styles, we don't need a shadowRoot
14542 fetchTemplate: function() {
14543 return null;
14544 },
14545
14546 go: function(node, state) {
14547 if (state.opened !== undefined) {
14548 this.transitionOpened(node, state.opened);
14549 }
14550 },
14551
14552 setup: function(node) {
14553 if (!node._hasTransitionStyle) {
14554 if (!node.shadowRoot) {
14555 node.createShadowRoot().innerHTML = '<content></content>';
14556 }
14557 this.installScopeStyle(this.transitionStyle, 'transition',
14558 node.shadowRoot);
14559 node._hasTransitionStyle = true;
14560 }
14561 node.classList.add(this.baseClass);
14562 if (this.transitionType) {
14563 node.classList.add(this.baseClass + '-' + this.transitionType);
14564 }
14565 },
14566
14567 teardown: function(node) {
14568 node.classList.remove(this.baseClass);
14569 if (this.transitionType) {
14570 node.classList.remove(this.baseClass + '-' + this.transitionType);
14571 }
14572 },
14573
14574 transitionOpened: function(node, opened) {
14575 this.listenOnce(node, this.completeEventName, function() {
14576 if (!opened) {
14577 node.classList.remove(this.closedClass);
14578 }
14579 this.complete(node);
14580 });
14581 node.classList.toggle(this.openedClass, opened);
14582 node.classList.toggle(this.closedClass, !opened);
14583 }
14584
14585 });
14586 ;
14587
14588
14589 Polymer('paper-dialog-base',{
14590
14591 publish: {
14592
14593 /**
14594 * The title of the dialog.
14595 *
14596 * @attribute heading
14597 * @type string
14598 * @default ''
14599 */
14600 heading: '',
14601
14602 /**
14603 * @attribute transition
14604 * @type string
14605 * @default ''
14606 */
14607 transition: '',
14608
14609 /**
14610 * @attribute layered
14611 * @type boolean
14612 * @default true
14613 */
14614 layered: true
14615 },
14616
14617 ready: function() {
14618 this.super();
14619 this.sizingTarget = this.$.scroller;
14620 },
14621
14622 headingChanged: function(old) {
14623 var label = this.getAttribute('aria-label');
14624 if (!label || label === old) {
14625 this.setAttribute('aria-label', this.heading);
14626 }
14627 },
14628
14629 openAction: function() {
14630 if (this.$.scroller.scrollTop) {
14631 this.$.scroller.scrollTop = 0;
14632 }
14633 }
14634
14635 });
14636
14637 ;
14638 Polymer('paper-dialog');;
14639
14640 Polymer('core-selection', {
14641 /**
14642 * If true, multiple selections are allowed.
14643 *
14644 * @attribute multi
14645 * @type boolean
14646 * @default false
14647 */
14648 multi: false,
14649 ready: function() {
14650 this.clear();
14651 },
14652 clear: function() {
14653 this.selection = [];
14654 },
14655 /**
14656 * Retrieves the selected item(s).
14657 * @method getSelection
14658 * @returns Returns the selected item(s). If the multi property is true,
14659 * getSelection will return an array, otherwise it will return
14660 * the selected item or undefined if there is no selection.
14661 */
14662 getSelection: function() {
14663 return this.multi ? this.selection : this.selection[0];
14664 },
14665 /**
14666 * Indicates if a given item is selected.
14667 * @method isSelected
14668 * @param {any} item The item whose selection state should be checked.
14669 * @returns Returns true if `item` is selected.
14670 */
14671 isSelected: function(item) {
14672 return this.selection.indexOf(item) >= 0;
14673 },
14674 setItemSelected: function(item, isSelected) {
14675 if (item !== undefined && item !== null) {
14676 if (isSelected) {
14677 this.selection.push(item);
14678 } else {
14679 var i = this.selection.indexOf(item);
14680 if (i >= 0) {
14681 this.selection.splice(i, 1);
14682 }
14683 }
14684 this.fire("core-select", {isSelected: isSelected, item: item});
14685 }
14686 },
14687 /**
14688 * Set the selection state for a given `item`. If the multi property
14689 * is true, then the selected state of `item` will be toggled; otherwise
14690 * the `item` will be selected.
14691 * @method select
14692 * @param {any} item: The item to select.
14693 */
14694 select: function(item) {
14695 if (this.multi) {
14696 this.toggle(item);
14697 } else if (this.getSelection() !== item) {
14698 this.setItemSelected(this.getSelection(), false);
14699 this.setItemSelected(item, true);
14700 }
14701 },
14702 /**
14703 * Toggles the selection state for `item`.
14704 * @method toggle
14705 * @param {any} item: The item to toggle.
14706 */
14707 toggle: function(item) {
14708 this.setItemSelected(item, !this.isSelected(item));
14709 }
14710 });
14711 ;
14712
14713
14714 Polymer('core-selector', {
14715
14716 /**
14717 * Gets or sets the selected element. Default to use the index
14718 * of the item element.
14719 *
14720 * If you want a specific attribute value of the element to be
14721 * used instead of index, set "valueattr" to that attribute name.
14722 *
14723 * Example:
14724 *
14725 * <core-selector valueattr="label" selected="foo">
14726 * <div label="foo"></div>
14727 * <div label="bar"></div>
14728 * <div label="zot"></div>
14729 * </core-selector>
14730 *
14731 * In multi-selection this should be an array of values.
14732 *
14733 * Example:
14734 *
14735 * <core-selector id="selector" valueattr="label" multi>
14736 * <div label="foo"></div>
14737 * <div label="bar"></div>
14738 * <div label="zot"></div>
14739 * </core-selector>
14740 *
14741 * this.$.selector.selected = ['foo', 'zot'];
14742 *
14743 * @attribute selected
14744 * @type Object
14745 * @default null
14746 */
14747 selected: null,
14748
14749 /**
14750 * If true, multiple selections are allowed.
14751 *
14752 * @attribute multi
14753 * @type boolean
14754 * @default false
14755 */
14756 multi: false,
14757
14758 /**
14759 * Specifies the attribute to be used for "selected" attribute.
14760 *
14761 * @attribute valueattr
14762 * @type string
14763 * @default 'name'
14764 */
14765 valueattr: 'name',
14766
14767 /**
14768 * Specifies the CSS class to be used to add to the selected element.
14769 *
14770 * @attribute selectedClass
14771 * @type string
14772 * @default 'core-selected'
14773 */
14774 selectedClass: 'core-selected',
14775
14776 /**
14777 * Specifies the property to be used to set on the selected element
14778 * to indicate its active state.
14779 *
14780 * @attribute selectedProperty
14781 * @type string
14782 * @default ''
14783 */
14784 selectedProperty: '',
14785
14786 /**
14787 * Specifies the attribute to set on the selected element to indicate
14788 * its active state.
14789 *
14790 * @attribute selectedAttribute
14791 * @type string
14792 * @default 'active'
14793 */
14794 selectedAttribute: 'active',
14795
14796 /**
14797 * Returns the currently selected element. In multi-selection this returns
14798 * an array of selected elements.
14799 * Note that you should not use this to set the selection. Instead use
14800 * `selected`.
14801 *
14802 * @attribute selectedItem
14803 * @type Object
14804 * @default null
14805 */
14806 selectedItem: null,
14807
14808 /**
14809 * In single selection, this returns the model associated with the
14810 * selected element.
14811 * Note that you should not use this to set the selection. Instead use
14812 * `selected`.
14813 *
14814 * @attribute selectedModel
14815 * @type Object
14816 * @default null
14817 */
14818 selectedModel: null,
14819
14820 /**
14821 * In single selection, this returns the selected index.
14822 * Note that you should not use this to set the selection. Instead use
14823 * `selected`.
14824 *
14825 * @attribute selectedIndex
14826 * @type number
14827 * @default -1
14828 */
14829 selectedIndex: -1,
14830
14831 /**
14832 * Nodes with local name that are in the list will not be included
14833 * in the selection items. In the following example, `items` returns four
14834 * `core-item`'s and doesn't include `h3` and `hr`.
14835 *
14836 * <core-selector excludedLocalNames="h3 hr">
14837 * <h3>Header</h3>
14838 * <core-item>Item1</core-item>
14839 * <core-item>Item2</core-item>
14840 * <hr>
14841 * <core-item>Item3</core-item>
14842 * <core-item>Item4</core-item>
14843 * </core-selector>
14844 *
14845 * @attribute excludedLocalNames
14846 * @type string
14847 * @default ''
14848 */
14849 excludedLocalNames: '',
14850
14851 /**
14852 * The target element that contains items. If this is not set
14853 * core-selector is the container.
14854 *
14855 * @attribute target
14856 * @type Object
14857 * @default null
14858 */
14859 target: null,
14860
14861 /**
14862 * This can be used to query nodes from the target node to be used for
14863 * selection items. Note this only works if `target` is set
14864 * and is not `core-selector` itself.
14865 *
14866 * Example:
14867 *
14868 * <core-selector target="{{$.myForm}}" itemsSelector="input[type=radi o]"></core-selector>
14869 * <form id="myForm">
14870 * <label><input type="radio" name="color" value="red"> Red</label> <br>
14871 * <label><input type="radio" name="color" value="green"> Green</lab el> <br>
14872 * <label><input type="radio" name="color" value="blue"> Blue</label > <br>
14873 * <p>color = {{color}}</p>
14874 * </form>
14875 *
14876 * @attribute itemsSelector
14877 * @type string
14878 * @default ''
14879 */
14880 itemsSelector: '',
14881
14882 /**
14883 * The event that would be fired from the item element to indicate
14884 * it is being selected.
14885 *
14886 * @attribute activateEvent
14887 * @type string
14888 * @default 'tap'
14889 */
14890 activateEvent: 'tap',
14891
14892 /**
14893 * Set this to true to disallow changing the selection via the
14894 * `activateEvent`.
14895 *
14896 * @attribute notap
14897 * @type boolean
14898 * @default false
14899 */
14900 notap: false,
14901
14902 defaultExcludedLocalNames: 'template',
14903
14904 observe: {
14905 'selected multi': 'selectedChanged'
14906 },
14907
14908 ready: function() {
14909 this.activateListener = this.activateHandler.bind(this);
14910 this.itemFilter = this.filterItem.bind(this);
14911 this.excludedLocalNamesChanged();
14912 this.observer = new MutationObserver(this.updateSelected.bind(this));
14913 if (!this.target) {
14914 this.target = this;
14915 }
14916 },
14917
14918 /**
14919 * Returns an array of all items.
14920 *
14921 * @property items
14922 */
14923 get items() {
14924 if (!this.target) {
14925 return [];
14926 }
14927 var nodes = this.target !== this ? (this.itemsSelector ?
14928 this.target.querySelectorAll(this.itemsSelector) :
14929 this.target.children) : this.$.items.getDistributedNodes();
14930 return Array.prototype.filter.call(nodes, this.itemFilter);
14931 },
14932
14933 filterItem: function(node) {
14934 return !this._excludedNames[node.localName];
14935 },
14936
14937 excludedLocalNamesChanged: function() {
14938 this._excludedNames = {};
14939 var s = this.defaultExcludedLocalNames;
14940 if (this.excludedLocalNames) {
14941 s += ' ' + this.excludedLocalNames;
14942 }
14943 s.split(/\s+/g).forEach(function(n) {
14944 this._excludedNames[n] = 1;
14945 }, this);
14946 },
14947
14948 targetChanged: function(old) {
14949 if (old) {
14950 this.removeListener(old);
14951 this.observer.disconnect();
14952 this.clearSelection();
14953 }
14954 if (this.target) {
14955 this.addListener(this.target);
14956 this.observer.observe(this.target, {childList: true});
14957 this.updateSelected();
14958 }
14959 },
14960
14961 addListener: function(node) {
14962 Polymer.addEventListener(node, this.activateEvent, this.activateListener );
14963 },
14964
14965 removeListener: function(node) {
14966 Polymer.removeEventListener(node, this.activateEvent, this.activateListe ner);
14967 },
14968
14969 /**
14970 * Returns the selected item(s). If the `multi` property is true,
14971 * this will return an array, otherwise it will return
14972 * the selected item or undefined if there is no selection.
14973 */
14974 get selection() {
14975 return this.$.selection.getSelection();
14976 },
14977
14978 selectedChanged: function() {
14979 // TODO(ffu): Right now this is the only way to know that the `selected`
14980 // is an array and was mutated, as opposed to newly assigned.
14981 if (arguments.length === 1) {
14982 this.processSplices(arguments[0]);
14983 } else {
14984 this.updateSelected();
14985 }
14986 },
14987
14988 updateSelected: function() {
14989 this.validateSelected();
14990 if (this.multi) {
14991 this.clearSelection(this.selected)
14992 this.selected && this.selected.forEach(function(s) {
14993 this.setValueSelected(s, true)
14994 }, this);
14995 } else {
14996 this.valueToSelection(this.selected);
14997 }
14998 },
14999
15000 validateSelected: function() {
15001 // convert to an array for multi-selection
15002 if (this.multi && !Array.isArray(this.selected) &&
15003 this.selected != null) {
15004 this.selected = [this.selected];
15005 // use the first selected in the array for single-selection
15006 } else if (!this.multi && Array.isArray(this.selected)) {
15007 var s = this.selected[0];
15008 this.clearSelection([s]);
15009 this.selected = s;
15010 }
15011 },
15012
15013 processSplices: function(splices) {
15014 for (var i = 0, splice; splice = splices[i]; i++) {
15015 for (var j = 0; j < splice.removed.length; j++) {
15016 this.setValueSelected(splice.removed[j], false);
15017 }
15018 for (var j = 0; j < splice.addedCount; j++) {
15019 this.setValueSelected(this.selected[splice.index + j], true);
15020 }
15021 }
15022 },
15023
15024 clearSelection: function(excludes) {
15025 this.$.selection.selection.slice().forEach(function(item) {
15026 var v = this.valueForNode(item) || this.items.indexOf(item);
15027 if (!excludes || excludes.indexOf(v) < 0) {
15028 this.$.selection.setItemSelected(item, false);
15029 }
15030 }, this);
15031 },
15032
15033 valueToSelection: function(value) {
15034 var item = this.valueToItem(value);
15035 this.$.selection.select(item);
15036 },
15037
15038 setValueSelected: function(value, isSelected) {
15039 var item = this.valueToItem(value);
15040 if (isSelected ^ this.$.selection.isSelected(item)) {
15041 this.$.selection.setItemSelected(item, isSelected);
15042 }
15043 },
15044
15045 updateSelectedItem: function() {
15046 this.selectedItem = this.selection;
15047 },
15048
15049 selectedItemChanged: function() {
15050 if (this.selectedItem) {
15051 var t = this.selectedItem.templateInstance;
15052 this.selectedModel = t ? t.model : undefined;
15053 } else {
15054 this.selectedModel = null;
15055 }
15056 this.selectedIndex = this.selectedItem ?
15057 parseInt(this.valueToIndex(this.selected)) : -1;
15058 },
15059
15060 valueToItem: function(value) {
15061 return (value === null || value === undefined) ?
15062 null : this.items[this.valueToIndex(value)];
15063 },
15064
15065 valueToIndex: function(value) {
15066 // find an item with value == value and return it's index
15067 for (var i=0, items=this.items, c; (c=items[i]); i++) {
15068 if (this.valueForNode(c) == value) {
15069 return i;
15070 }
15071 }
15072 // if no item found, the value itself is probably the index
15073 return value;
15074 },
15075
15076 valueForNode: function(node) {
15077 return node[this.valueattr] || node.getAttribute(this.valueattr);
15078 },
15079
15080 // events fired from <core-selection> object
15081 selectionSelect: function(e, detail) {
15082 this.updateSelectedItem();
15083 if (detail.item) {
15084 this.applySelection(detail.item, detail.isSelected);
15085 }
15086 },
15087
15088 applySelection: function(item, isSelected) {
15089 if (this.selectedClass) {
15090 item.classList.toggle(this.selectedClass, isSelected);
15091 }
15092 if (this.selectedProperty) {
15093 item[this.selectedProperty] = isSelected;
15094 }
15095 if (this.selectedAttribute && item.setAttribute) {
15096 if (isSelected) {
15097 item.setAttribute(this.selectedAttribute, '');
15098 } else {
15099 item.removeAttribute(this.selectedAttribute);
15100 }
15101 }
15102 },
15103
15104 // event fired from host
15105 activateHandler: function(e) {
15106 if (!this.notap) {
15107 var i = this.findDistributedTarget(e.target, this.items);
15108 if (i >= 0) {
15109 var item = this.items[i];
15110 var s = this.valueForNode(item) || i;
15111 if (this.multi) {
15112 if (this.selected) {
15113 this.addRemoveSelected(s);
15114 } else {
15115 this.selected = [s];
15116 }
15117 } else {
15118 this.selected = s;
15119 }
15120 this.asyncFire('core-activate', {item: item});
15121 }
15122 }
15123 },
15124
15125 addRemoveSelected: function(value) {
15126 var i = this.selected.indexOf(value);
15127 if (i >= 0) {
15128 this.selected.splice(i, 1);
15129 } else {
15130 this.selected.push(value);
15131 }
15132 },
15133
15134 findDistributedTarget: function(target, nodes) {
15135 // find first ancestor of target (including itself) that
15136 // is in nodes, if any
15137 while (target && target != this) {
15138 var i = Array.prototype.indexOf.call(nodes, target);
15139 if (i >= 0) {
15140 return i;
15141 }
15142 target = target.parentNode;
15143 }
15144 },
15145
15146 selectIndex: function(index) {
15147 var item = this.items[index];
15148 if (item) {
15149 this.selected = this.valueForNode(item) || index;
15150 return item;
15151 }
15152 },
15153
15154 /**
15155 * Selects the previous item. This should be used in single selection only .
15156 *
15157 * @method selectPrevious
15158 * @param {boolean} wrapped if true and it is already at the first item,
15159 * wrap to the end
15160 * @returns the previous item or undefined if there is none
15161 */
15162 selectPrevious: function(wrapped) {
15163 var i = wrapped && !this.selectedIndex ?
15164 this.items.length - 1 : this.selectedIndex - 1;
15165 return this.selectIndex(i);
15166 },
15167
15168 /**
15169 * Selects the next item. This should be used in single selection only.
15170 *
15171 * @method selectNext
15172 * @param {boolean} wrapped if true and it is already at the last item,
15173 * wrap to the front
15174 * @returns the next item or undefined if there is none
15175 */
15176 selectNext: function(wrapped) {
15177 var i = wrapped && this.selectedIndex >= this.items.length - 1 ?
15178 0 : this.selectedIndex + 1;
15179 return this.selectIndex(i);
15180 }
15181
15182 });
15183 ;
15184
15185
15186 Polymer('paper-radio-group', {
15187 nextIndex: function(index) {
15188 var items = this.items;
15189 var newIndex = index;
15190 do {
15191 newIndex = (newIndex + 1) % items.length;
15192 if (newIndex === index) {
15193 break;
15194 }
15195 } while (items[newIndex].disabled);
15196 return newIndex;
15197 },
15198 previousIndex: function(index) {
15199 var items = this.items;
15200 var newIndex = index;
15201 do {
15202 newIndex = (newIndex || items.length) - 1;
15203 if (newIndex === index) {
15204 break;
15205 }
15206 } while (items[newIndex].disabled);
15207 return newIndex;
15208 },
15209 selectNext: function() {
15210 var node = this.selectIndex(this.nextIndex(this.selectedIndex));
15211 node.focus();
15212 },
15213 selectPrevious: function() {
15214 var node = this.selectIndex(this.previousIndex(this.selectedIndex));
15215 node.focus();
15216 },
15217 selectedAttribute: 'checked',
15218 activateEvent: 'change'
15219
15220 });
15221
15222 ;
15223
15224 Polymer('paper-spinner',{
15225 eventDelegates: {
15226 'animationend': 'reset',
15227 'webkitAnimationEnd': 'reset'
15228 },
15229 publish: {
15230 /**
15231 * Displays the spinner.
15232 *
15233 * @attribute active
15234 * @type boolean
15235 * @default false
15236 */
15237 active: {value: false, reflect: true},
15238
15239 /**
15240 * Alternative text content for accessibility support.
15241 * If alt is present, it will add an aria-label whose content matches al t when active.
15242 * If alt is not present, it will default to 'loading' as the alt value.
15243 * @attribute alt
15244 * @type string
15245 * @default 'loading'
15246 */
15247 alt: {value: 'loading', reflect: true}
15248 },
15249
15250 ready: function() {
15251 // Allow user-provided `aria-label` take preference to any other text al ternative.
15252 if (this.hasAttribute('aria-label')) {
15253 this.alt = this.getAttribute('aria-label');
15254 } else {
15255 this.setAttribute('aria-label', this.alt);
15256 }
15257 if (!this.active) {
15258 this.setAttribute('aria-hidden', 'true');
15259 }
15260 },
15261
15262 activeChanged: function() {
15263 if (this.active) {
15264 this.$.spinnerContainer.classList.remove('cooldown');
15265 this.$.spinnerContainer.classList.add('active');
15266 this.removeAttribute('aria-hidden');
15267 } else {
15268 this.$.spinnerContainer.classList.add('cooldown');
15269 this.setAttribute('aria-hidden', 'true');
15270 }
15271 },
15272
15273 altChanged: function() {
15274 if (this.alt === '') {
15275 this.setAttribute('aria-hidden', 'true');
15276 } else {
15277 this.removeAttribute('aria-hidden');
15278 }
15279 this.setAttribute('aria-label', this.alt);
15280 },
15281
15282 reset: function() {
15283 this.$.spinnerContainer.classList.remove('active', 'cooldown');
15284 }
15285 });
15286 ;
15287 Polymer('core-menu');;
15288
15289
15290 (function() {
15291
15292 var p = {
15293
15294 eventDelegates: {
15295 down: 'downAction',
15296 up: 'upAction'
15297 },
15298
15299 toggleBackground: function() {
15300 if (this.active) {
15301
15302 if (!this.$.bg) {
15303 var bg = document.createElement('div');
15304 bg.setAttribute('id', 'bg');
15305 bg.setAttribute('fit', '');
15306 bg.style.opacity = 0.25;
15307 this.$.bg = bg;
15308 this.shadowRoot.insertBefore(bg, this.shadowRoot.firstChild);
15309 }
15310 this.$.bg.style.backgroundColor = getComputedStyle(this).color;
15311
15312 } else {
15313
15314 if (this.$.bg) {
15315 this.$.bg.style.backgroundColor = '';
15316 }
15317 }
15318 },
15319
15320 activeChanged: function() {
15321 this.super();
15322
15323 if (this.toggle && (!this.lastEvent || this.matches(':host-context([noin k])'))) {
15324 this.toggleBackground();
15325 }
15326 },
15327
15328 pressedChanged: function() {
15329 this.super();
15330
15331 if (!this.lastEvent) {
15332 return;
15333 }
15334
15335 if (this.$.ripple && !this.hasAttribute('noink')) {
15336 if (this.pressed) {
15337 this.$.ripple.downAction(this.lastEvent);
15338 } else {
15339 this.$.ripple.upAction();
15340 }
15341 }
15342
15343 this.adjustZ();
15344 },
15345
15346 focusedChanged: function() {
15347 this.adjustZ();
15348 },
15349
15350 disabledChanged: function() {
15351 this._disabledChanged();
15352 this.adjustZ();
15353 },
15354
15355 recenteringTouchChanged: function() {
15356 if (this.$.ripple) {
15357 this.$.ripple.classList.toggle('recenteringTouch', this.recenteringTou ch);
15358 }
15359 },
15360
15361 fillChanged: function() {
15362 if (this.$.ripple) {
15363 this.$.ripple.classList.toggle('fill', this.fill);
15364 }
15365 },
15366
15367 adjustZ: function() {
15368 if (!this.$.shadow) {
15369 return;
15370 }
15371 if (this.active) {
15372 this.$.shadow.setZ(2);
15373 } else if (this.disabled) {
15374 this.$.shadow.setZ(0);
15375 } else if (this.focused) {
15376 this.$.shadow.setZ(3);
15377 } else {
15378 this.$.shadow.setZ(1);
15379 }
15380 },
15381
15382 downAction: function(e) {
15383 this._downAction();
15384
15385 if (this.hasAttribute('noink')) {
15386 return;
15387 }
15388
15389 this.lastEvent = e;
15390 if (!this.$.ripple) {
15391 var ripple = document.createElement('paper-ripple');
15392 ripple.setAttribute('id', 'ripple');
15393 ripple.setAttribute('fit', '');
15394 if (this.recenteringTouch) {
15395 ripple.classList.add('recenteringTouch');
15396 }
15397 if (!this.fill) {
15398 ripple.classList.add('circle');
15399 }
15400 this.$.ripple = ripple;
15401 this.shadowRoot.insertBefore(ripple, this.shadowRoot.firstChild);
15402 // No need to forward the event to the ripple because the ripple
15403 // is triggered in activeChanged
15404 }
15405 },
15406
15407 upAction: function() {
15408 this._upAction();
15409
15410 if (this.toggle) {
15411 this.toggleBackground();
15412 if (this.$.ripple) {
15413 this.$.ripple.cancel();
15414 }
15415 }
15416 }
15417
15418 };
15419
15420 Polymer.mixin2(p, Polymer.CoreFocusable);
15421 Polymer('paper-button-base',p);
15422
15423 })();
15424
15425 ;
15426
15427 Polymer('paper-button',{
15428
15429 publish: {
15430
15431 /**
15432 * If true, the button will be styled with a shadow.
15433 *
15434 * @attribute raised
15435 * @type boolean
15436 * @default false
15437 */
15438 raised: false,
15439
15440 /**
15441 * By default the ripple emanates from where the user touched the button .
15442 * Set this to true to always center the ripple.
15443 *
15444 * @attribute recenteringTouch
15445 * @type boolean
15446 * @default false
15447 */
15448 recenteringTouch: false,
15449
15450 /**
15451 * By default the ripple expands to fill the button. Set this to true to
15452 * constrain the ripple to a circle within the button.
15453 *
15454 * @attribute fill
15455 * @type boolean
15456 * @default true
15457 */
15458 fill: true
15459
15460 },
15461
15462 _activate: function() {
15463 this.click();
15464 this.fire('tap');
15465 if (!this.pressed) {
15466 var bcr = this.getBoundingClientRect();
15467 var x = bcr.left + (bcr.width / 2);
15468 var y = bcr.top + (bcr.height / 2);
15469 this.downAction({x: x, y: y});
15470 var fn = function fn() {
15471 this.upAction();
15472 this.removeEventListener('keyup', fn);
15473 }.bind(this);
15474 this.addEventListener('keyup', fn);
15475 }
15476 }
15477
15478 });
15479 ;
15480
15481
15482 (function() {
15483
15484 function docElem(property) {
15485 var t;
15486 return ((t = document.documentElement) || (t = document.body.parentNode)) && (typeof t[property] === 'number') ? t : document.body;
15487 }
15488
15489 // View width and height excluding any visible scrollbars
15490 // http://www.highdots.com/forums/javascript/faq-topic-how-do-i-296669.html
15491 // 1) document.client[Width|Height] always reliable when available, includi ng Safari2
15492 // 2) document.documentElement.client[Width|Height] reliable in standards m ode DOCTYPE, except for Safari2, Opera<9.5
15493 // 3) document.body.client[Width|Height] is gives correct result when #2 do es not, except for Safari2
15494 // 4) When document.documentElement.client[Width|Height] is unreliable, it will be size of <html> element either greater or less than desired view size
15495 // https://bugzilla.mozilla.org/show_bug.cgi?id=156388#c7
15496 // 5) When document.body.client[Width|Height] is unreliable, it will be siz e of <body> element less than desired view size
15497 function viewSize() {
15498 // This algorithm avoids creating test page to determine if document.documen tElement.client[Width|Height] is greater then view size,
15499 // will succeed where such test page wouldn't detect dynamic unreliability,
15500 // and will only fail in the case the right or bottom edge is within the wid th of a scrollbar from edge of the viewport that has visible scrollbar(s).
15501 var doc = docElem('clientWidth');
15502 var body = document.body;
15503 var w, h;
15504 return typeof document.clientWidth === 'number' ?
15505 {w: document.clientWidth, h: document.clientHeight} :
15506 doc === body || (w = Math.max( doc.clientWidth, body.clientWidth )) > self .innerWidth || (h = Math.max( doc.clientHeight, body.clientHeight )) > self.inne rHeight ?
15507 {w: body.clientWidth, h: body.clientHeight} : {w: w, h: h };
15508 }
15509
15510 Polymer('core-dropdown',{
15511
15512 publish: {
15513
15514 /**
15515 * The element associated with this dropdown, usually the element that tri ggers
15516 * the menu. If unset, this property will default to the target's parent n ode
15517 * or shadow host.
15518 *
15519 * @attribute relatedTarget
15520 * @type Node
15521 */
15522 relatedTarget: null,
15523
15524 /**
15525 * The horizontal alignment of the popup relative to `relatedTarget`. `lef t`
15526 * means the left edges are aligned together. `right` means the right edge s
15527 * are aligned together.
15528 *
15529 * Accepted values: 'left', 'right'
15530 *
15531 * @attribute halign
15532 * @type String
15533 * @default 'left'
15534 */
15535 halign: 'left',
15536
15537 /**
15538 * The vertical alignment of the popup relative to `relatedTarget`. `top` means
15539 * the top edges are aligned together. `bottom` means the bottom edges are
15540 * aligned together.
15541 *
15542 * Accepted values: 'top', 'bottom'
15543 *
15544 * @attribute valign
15545 * @type String
15546 * @default 'top'
15547 */
15548 valign: 'top',
15549
15550 },
15551
15552 measure: function() {
15553 var target = this.target;
15554 // remember position, because core-overlay may have set the property
15555 var pos = target.style.position;
15556
15557 // get the size of the target as if it's positioned in the top left
15558 // corner of the screen
15559 target.style.position = 'fixed';
15560 target.style.left = '0px';
15561 target.style.top = '0px';
15562
15563 var rect = target.getBoundingClientRect();
15564
15565 target.style.position = pos;
15566 target.style.left = null;
15567 target.style.top = null;
15568
15569 return rect;
15570 },
15571
15572 resetTargetDimensions: function() {
15573 var dims = this.dimensions;
15574 var style = this.target.style;
15575 if (dims.position.h_by === this.localName) {
15576 style[dims.position.h] = null;
15577 dims.position.h_by = null;
15578 }
15579 if (dims.position.v_by === this.localName) {
15580 style[dims.position.v] = null;
15581 dims.position.v_by = null;
15582 }
15583 style.width = null;
15584 style.height = null;
15585 this.super();
15586 },
15587
15588 positionTarget: function() {
15589 if (!this.relatedTarget) {
15590 this.relatedTarget = this.target.parentElement || (this.target.parentNod e && this.target.parentNode.host);
15591 if (!this.relatedTarget) {
15592 this.super();
15593 return;
15594 }
15595 }
15596
15597 // explicitly set width/height, because we don't want it constrained
15598 // to the offsetParent
15599 var target = this.sizingTarget;
15600 var rect = this.measure();
15601 target.style.width = Math.ceil(rect.width) + 'px';
15602 target.style.height = Math.ceil(rect.height) + 'px';
15603
15604 if (this.layered) {
15605 this.positionLayeredTarget();
15606 } else {
15607 this.positionNestedTarget();
15608 }
15609 },
15610
15611 positionLayeredTarget: function() {
15612 var target = this.target;
15613 var rect = this.relatedTarget.getBoundingClientRect();
15614
15615 var dims = this.dimensions;
15616 var margin = dims.margin;
15617 var vp = viewSize();
15618
15619 if (!dims.position.h) {
15620 if (this.halign === 'right') {
15621 target.style.right = vp.w - rect.right - margin.right + 'px';
15622 dims.position.h = 'right';
15623 } else {
15624 target.style.left = rect.left - margin.left + 'px';
15625 dims.position.h = 'left';
15626 }
15627 dims.position.h_by = this.localName;
15628 }
15629
15630 if (!dims.position.v) {
15631 if (this.valign === 'bottom') {
15632 target.style.bottom = vp.h - rect.bottom - margin.bottom + 'px';
15633 dims.position.v = 'bottom';
15634 } else {
15635 target.style.top = rect.top - margin.top + 'px';
15636 dims.position.v = 'top';
15637 }
15638 dims.position.v_by = this.localName;
15639 }
15640
15641 if (dims.position.h_by || dims.position.v_by) {
15642 target.style.position = 'fixed';
15643 }
15644 },
15645
15646 positionNestedTarget: function() {
15647 var target = this.target;
15648 var related = this.relatedTarget;
15649
15650 var t_op = target.offsetParent;
15651 var r_op = related.offsetParent;
15652 if (window.ShadowDOMPolyfill) {
15653 t_op = wrap(t_op);
15654 r_op = wrap(r_op);
15655 }
15656
15657 if (t_op !== r_op && t_op !== related) {
15658 console.warn('core-dropdown-overlay: dropdown\'s offsetParent must be th e relatedTarget or the relatedTarget\'s offsetParent!');
15659 }
15660
15661 // Don't use CSS to handle halign/valign so we can use
15662 // dimensions.position to detect custom positioning
15663
15664 var dims = this.dimensions;
15665 var margin = dims.margin;
15666 var inside = t_op === related;
15667
15668 if (!dims.position.h) {
15669 if (this.halign === 'right') {
15670 target.style.right = ((inside ? 0 : t_op.offsetWidth - related.offsetL eft - related.offsetWidth) - margin.right) + 'px';
15671 dims.position.h = 'right';
15672 } else {
15673 target.style.left = ((inside ? 0 : related.offsetLeft) - margin.left) + 'px';
15674 dims.position.h = 'left';
15675 }
15676 dims.position.h_by = this.localName;
15677 }
15678
15679 if (!dims.position.v) {
15680 if (this.valign === 'bottom') {
15681 target.style.bottom = ((inside ? 0 : t_op.offsetHeight - related.offse tTop - related.offsetHeight) - margin.bottom) + 'px';
15682 dims.position.v = 'bottom';
15683 } else {
15684 target.style.top = ((inside ? 0 : related.offsetTop) - margin.top) + ' px';
15685 dims.position.v = 'top';
15686 }
15687 dims.position.v_by = this.localName;
15688 }
15689 }
15690
15691 });
15692
15693 })();
15694
15695 ;
15696
15697
15698 Polymer('core-dropdown-base',{
15699
15700 publish: {
15701
15702 /**
15703 * True if the menu is open.
15704 *
15705 * @attribute opened
15706 * @type boolean
15707 * @default false
15708 */
15709 opened: false
15710
15711 },
15712
15713 eventDelegates: {
15714 'tap': 'toggleOverlay'
15715 },
15716
15717 overlayListeners: {
15718 'core-overlay-open': 'openAction'
15719 },
15720
15721 get dropdown() {
15722 if (!this._dropdown) {
15723 this._dropdown = this.querySelector('.dropdown');
15724 for (var l in this.overlayListeners) {
15725 this.addElementListener(this._dropdown, l, this.overlayListeners[l]);
15726 }
15727 }
15728 return this._dropdown;
15729 },
15730
15731 attached: function() {
15732 // find the dropdown on attach
15733 // FIXME: Support MO?
15734 this.dropdown;
15735 },
15736
15737 addElementListener: function(node, event, methodName, capture) {
15738 var fn = this._makeBoundListener(methodName);
15739 if (node && fn) {
15740 Polymer.addEventListener(node, event, fn, capture);
15741 }
15742 },
15743
15744 removeElementListener: function(node, event, methodName, capture) {
15745 var fn = this._makeBoundListener(methodName);
15746 if (node && fn) {
15747 Polymer.removeEventListener(node, event, fn, capture);
15748 }
15749 },
15750
15751 _makeBoundListener: function(methodName) {
15752 var self = this, method = this[methodName];
15753 if (!method) {
15754 return;
15755 }
15756 var bound = '_bound' + methodName;
15757 if (!this[bound]) {
15758 this[bound] = function(e) {
15759 method.call(self, e);
15760 };
15761 }
15762 return this[bound];
15763 },
15764
15765 openedChanged: function() {
15766 if (this.disabled) {
15767 return;
15768 }
15769 var dropdown = this.dropdown;
15770 if (dropdown) {
15771 dropdown.opened = this.opened;
15772 }
15773 },
15774
15775 openAction: function(e) {
15776 this.opened = !!e.detail;
15777 },
15778
15779 toggleOverlay: function() {
15780 this.opened = !this.opened;
15781 }
15782
15783 });
15784
15785 ;
15786
15787
15788 Polymer('core-iconset', {
15789
15790 /**
15791 * The URL of the iconset image.
15792 *
15793 * @attribute src
15794 * @type string
15795 * @default ''
15796 */
15797 src: '',
15798
15799 /**
15800 * The width of the iconset image. This must only be specified if the
15801 * icons are arranged into separate rows inside the image.
15802 *
15803 * @attribute width
15804 * @type number
15805 * @default 0
15806 */
15807 width: 0,
15808
15809 /**
15810 * A space separated list of names corresponding to icons in the iconset
15811 * image file. This list must be ordered the same as the icon images
15812 * in the image file.
15813 *
15814 * @attribute icons
15815 * @type string
15816 * @default ''
15817 */
15818 icons: '',
15819
15820 /**
15821 * The size of an individual icon. Note that icons must be square.
15822 *
15823 * @attribute iconSize
15824 * @type number
15825 * @default 24
15826 */
15827 iconSize: 24,
15828
15829 /**
15830 * The horizontal offset of the icon images in the inconset src image.
15831 * This is typically used if the image resource contains additional images
15832 * beside those intended for the iconset.
15833 *
15834 * @attribute offsetX
15835 * @type number
15836 * @default 0
15837 */
15838 offsetX: 0,
15839 /**
15840 * The vertical offset of the icon images in the inconset src image.
15841 * This is typically used if the image resource contains additional images
15842 * beside those intended for the iconset.
15843 *
15844 * @attribute offsetY
15845 * @type number
15846 * @default 0
15847 */
15848 offsetY: 0,
15849 type: 'iconset',
15850
15851 created: function() {
15852 this.iconMap = {};
15853 this.iconNames = [];
15854 this.themes = {};
15855 },
15856
15857 ready: function() {
15858 // TODO(sorvell): ensure iconset's src is always relative to the main
15859 // document
15860 if (this.src && (this.ownerDocument !== document)) {
15861 this.src = this.resolvePath(this.src, this.ownerDocument.baseURI);
15862 }
15863 this.super();
15864 this.updateThemes();
15865 },
15866
15867 iconsChanged: function() {
15868 var ox = this.offsetX;
15869 var oy = this.offsetY;
15870 this.icons && this.icons.split(/\s+/g).forEach(function(name, i) {
15871 this.iconNames.push(name);
15872 this.iconMap[name] = {
15873 offsetX: ox,
15874 offsetY: oy
15875 }
15876 if (ox + this.iconSize < this.width) {
15877 ox += this.iconSize;
15878 } else {
15879 ox = this.offsetX;
15880 oy += this.iconSize;
15881 }
15882 }, this);
15883 },
15884
15885 updateThemes: function() {
15886 var ts = this.querySelectorAll('property[theme]');
15887 ts && ts.array().forEach(function(t) {
15888 this.themes[t.getAttribute('theme')] = {
15889 offsetX: parseInt(t.getAttribute('offsetX')) || 0,
15890 offsetY: parseInt(t.getAttribute('offsetY')) || 0
15891 };
15892 }, this);
15893 },
15894
15895 // TODO(ffu): support retrived by index e.g. getOffset(10);
15896 /**
15897 * Returns an object containing `offsetX` and `offsetY` properties which
15898 * specify the pixel locaion in the iconset's src file for the given
15899 * `icon` and `theme`. It's uncommon to call this method. It is useful,
15900 * for example, to manually position a css backgroundImage to the proper
15901 * offset. It's more common to use the `applyIcon` method.
15902 *
15903 * @method getOffset
15904 * @param {String|Number} icon The name of the icon or the index of the
15905 * icon within in the icon image.
15906 * @param {String} theme The name of the theme.
15907 * @returns {Object} An object specifying the offset of the given icon
15908 * within the icon resource file; `offsetX` is the horizontal offset and
15909 * `offsetY` is the vertical offset. Both values are in pixel units.
15910 */
15911 getOffset: function(icon, theme) {
15912 var i = this.iconMap[icon];
15913 if (!i) {
15914 var n = this.iconNames[Number(icon)];
15915 i = this.iconMap[n];
15916 }
15917 var t = this.themes[theme];
15918 if (i && t) {
15919 return {
15920 offsetX: i.offsetX + t.offsetX,
15921 offsetY: i.offsetY + t.offsetY
15922 }
15923 }
15924 return i;
15925 },
15926
15927 /**
15928 * Applies an icon to the given element as a css background image. This
15929 * method does not size the element, and it's often necessary to set
15930 * the element's height and width so that the background image is visible.
15931 *
15932 * @method applyIcon
15933 * @param {Element} element The element to which the background is
15934 * applied.
15935 * @param {String|Number} icon The name or index of the icon to apply.
15936 * @param {Number} scale (optional, defaults to 1) A scaling factor
15937 * with which the icon can be magnified.
15938 * @return {Element} The icon element.
15939 */
15940 applyIcon: function(element, icon, scale) {
15941 var offset = this.getOffset(icon);
15942 scale = scale || 1;
15943 if (element && offset) {
15944 var icon = element._icon || document.createElement('div');
15945 var style = icon.style;
15946 style.backgroundImage = 'url(' + this.src + ')';
15947 style.backgroundPosition = (-offset.offsetX * scale + 'px') +
15948 ' ' + (-offset.offsetY * scale + 'px');
15949 style.backgroundSize = scale === 1 ? 'auto' :
15950 this.width * scale + 'px';
15951 if (icon.parentNode !== element) {
15952 element.appendChild(icon);
15953 }
15954 return icon;
15955 }
15956 }
15957
15958 });
15959
15960 ;
15961
15962 (function() {
15963
15964 // mono-state
15965 var meta;
15966
15967 Polymer('core-icon', {
15968
15969 /**
15970 * The URL of an image for the icon. If the src property is specified,
15971 * the icon property should not be.
15972 *
15973 * @attribute src
15974 * @type string
15975 * @default ''
15976 */
15977 src: '',
15978
15979 /**
15980 * Specifies the icon name or index in the set of icons available in
15981 * the icon's icon set. If the icon property is specified,
15982 * the src property should not be.
15983 *
15984 * @attribute icon
15985 * @type string
15986 * @default ''
15987 */
15988 icon: '',
15989
15990 /**
15991 * Alternative text content for accessibility support.
15992 * If alt is present and not empty, it will set the element's role to img an d add an aria-label whose content matches alt.
15993 * If alt is present and is an empty string, '', it will hide the element fr om the accessibility layer
15994 * If alt is not present, it will set the element's role to img and the elem ent will fallback to using the icon attribute for its aria-label.
15995 *
15996 * @attribute alt
15997 * @type string
15998 * @default ''
15999 */
16000 alt: null,
16001
16002 observe: {
16003 'icon': 'updateIcon',
16004 'alt': 'updateAlt'
16005 },
16006
16007 defaultIconset: 'icons',
16008
16009 ready: function() {
16010 if (!meta) {
16011 meta = document.createElement('core-iconset');
16012 }
16013
16014 // Allow user-provided `aria-label` in preference to any other text altern ative.
16015 if (this.hasAttribute('aria-label')) {
16016 // Set `role` if it has not been overridden.
16017 if (!this.hasAttribute('role')) {
16018 this.setAttribute('role', 'img');
16019 }
16020 return;
16021 }
16022 this.updateAlt();
16023 },
16024
16025 srcChanged: function() {
16026 var icon = this._icon || document.createElement('div');
16027 icon.textContent = '';
16028 icon.setAttribute('fit', '');
16029 icon.style.backgroundImage = 'url(' + this.src + ')';
16030 icon.style.backgroundPosition = 'center';
16031 icon.style.backgroundSize = '100%';
16032 if (!icon.parentNode) {
16033 this.appendChild(icon);
16034 }
16035 this._icon = icon;
16036 },
16037
16038 getIconset: function(name) {
16039 return meta.byId(name || this.defaultIconset);
16040 },
16041
16042 updateIcon: function(oldVal, newVal) {
16043 if (!this.icon) {
16044 this.updateAlt();
16045 return;
16046 }
16047 var parts = String(this.icon).split(':');
16048 var icon = parts.pop();
16049 if (icon) {
16050 var set = this.getIconset(parts.pop());
16051 if (set) {
16052 this._icon = set.applyIcon(this, icon);
16053 if (this._icon) {
16054 this._icon.setAttribute('fit', '');
16055 }
16056 }
16057 }
16058 // Check to see if we're using the old icon's name for our a11y fallback
16059 if (oldVal) {
16060 if (oldVal.split(':').pop() == this.getAttribute('aria-label')) {
16061 this.updateAlt();
16062 }
16063 }
16064 },
16065
16066 updateAlt: function() {
16067 // Respect the user's decision to remove this element from
16068 // the a11y tree
16069 if (this.getAttribute('aria-hidden')) {
16070 return;
16071 }
16072
16073 // Remove element from a11y tree if `alt` is empty, otherwise
16074 // use `alt` as `aria-label`.
16075 if (this.alt === '') {
16076 this.setAttribute('aria-hidden', 'true');
16077 if (this.hasAttribute('role')) {
16078 this.removeAttribute('role');
16079 }
16080 if (this.hasAttribute('aria-label')) {
16081 this.removeAttribute('aria-label');
16082 }
16083 } else {
16084 this.setAttribute('aria-label', this.alt ||
16085 this.icon.split(':').pop());
16086 if (!this.hasAttribute('role')) {
16087 this.setAttribute('role', 'img');
16088 }
16089 if (this.hasAttribute('aria-hidden')) {
16090 this.removeAttribute('aria-hidden');
16091 }
16092 }
16093 }
16094
16095 });
16096
16097 })();
16098 ;
16099
16100
16101 Polymer('core-iconset-svg', {
16102
16103
16104 /**
16105 * The size of an individual icon. Note that icons must be square.
16106 *
16107 * @attribute iconSize
16108 * @type number
16109 * @default 24
16110 */
16111 iconSize: 24,
16112 type: 'iconset',
16113
16114 created: function() {
16115 this._icons = {};
16116 },
16117
16118 ready: function() {
16119 this.super();
16120 this.updateIcons();
16121 },
16122
16123 iconById: function(id) {
16124 return this._icons[id] || (this._icons[id] = this.querySelector('[id="' + id +'"]'));
16125 },
16126
16127 cloneIcon: function(id) {
16128 var icon = this.iconById(id);
16129 if (icon) {
16130 var content = icon.cloneNode(true);
16131 content.removeAttribute('id');
16132 var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg' );
16133 svg.setAttribute('viewBox', '0 0 ' + this.iconSize + ' ' +
16134 this.iconSize);
16135 // NOTE(dfreedm): work around https://crbug.com/370136
16136 svg.style.pointerEvents = 'none';
16137 svg.appendChild(content);
16138 return svg;
16139 }
16140 },
16141
16142 get iconNames() {
16143 if (!this._iconNames) {
16144 this._iconNames = this.findIconNames();
16145 }
16146 return this._iconNames;
16147 },
16148
16149 findIconNames: function() {
16150 var icons = this.querySelectorAll('[id]').array();
16151 if (icons.length) {
16152 return icons.map(function(n){ return n.id });
16153 }
16154 },
16155
16156 /**
16157 * Applies an icon to the given element. The svg icon is added to the
16158 * element's shadowRoot if one exists or directly to itself.
16159 *
16160 * @method applyIcon
16161 * @param {Element} element The element to which the icon is
16162 * applied.
16163 * @param {String|Number} icon The name the icon to apply.
16164 * @return {Element} The icon element
16165 */
16166 applyIcon: function(element, icon) {
16167 var root = element;
16168 // remove old
16169 var old = root.querySelector('svg');
16170 if (old) {
16171 old.remove();
16172 }
16173 // install new
16174 var svg = this.cloneIcon(icon);
16175 if (!svg) {
16176 return;
16177 }
16178 svg.setAttribute('height', '100%');
16179 svg.setAttribute('width', '100%');
16180 svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
16181 svg.style.display = 'block';
16182 root.insertBefore(svg, root.firstElementChild);
16183 return svg;
16184 },
16185
16186 /**
16187 * Tell users of the iconset, that the set has loaded.
16188 * This finds all elements matching the selector argument and calls
16189 * the method argument on them.
16190 * @method updateIcons
16191 * @param selector {string} css selector to identify iconset users,
16192 * defaults to '[icon]'
16193 * @param method {string} method to call on found elements,
16194 * defaults to 'updateIcon'
16195 */
16196 updateIcons: function(selector, method) {
16197 selector = selector || '[icon]';
16198 method = method || 'updateIcon';
16199 var deep = window.ShadowDOMPolyfill ? '' : 'html /deep/ ';
16200 var i$ = document.querySelectorAll(deep + selector);
16201 for (var i=0, e; e=i$[i]; i++) {
16202 if (e[method]) {
16203 e[method].call(e);
16204 }
16205 }
16206 }
16207
16208
16209 });
16210
16211 ;
16212
16213
16214 (function() {
16215
16216 var p = {
16217
16218 publish: {
16219
16220 /**
16221 * A label for the control. The label is displayed if no item is selected.
16222 *
16223 * @attribute label
16224 * @type string
16225 * @default 'Select an item'
16226 */
16227 label: 'Select an item',
16228
16229 /**
16230 * The icon to display when the drop-down is opened.
16231 *
16232 * @attribute openedIcon
16233 * @type string
16234 * @default 'arrow-drop-up'
16235 */
16236 openedIcon: 'arrow-drop-up',
16237
16238 /**
16239 * The icon to display when the drop-down is closed.
16240 *
16241 * @attribute closedIcon
16242 * @type string
16243 * @default 'arrow-drop-down'
16244 */
16245 closedIcon: 'arrow-drop-down'
16246
16247 },
16248
16249 selectedItemLabel: '',
16250
16251 overlayListeners: {
16252 'core-overlay-open': 'openAction',
16253 'core-activate': 'activateAction',
16254 'core-select': 'selectAction'
16255 },
16256
16257 activateAction: function(e) {
16258 this.opened = false;
16259 },
16260
16261 selectAction: function(e) {
16262 var detail = e.detail;
16263 if (detail.isSelected) {
16264 this.selectedItemLabel = detail.item.label || detail.item.textContent;
16265 } else {
16266 this.selectedItemLabel = '';
16267 }
16268 }
16269
16270 };
16271
16272 Polymer.mixin2(p, Polymer.CoreFocusable);
16273 Polymer('core-dropdown-menu',p);
16274
16275 })();
16276
16277 ;
16278
16279
16280 Polymer('core-item', {
16281
16282 /**
16283 * The URL of an image for the icon.
16284 *
16285 * @attribute src
16286 * @type string
16287 * @default ''
16288 */
16289
16290 /**
16291 * Specifies the icon from the Polymer icon set.
16292 *
16293 * @attribute icon
16294 * @type string
16295 * @default ''
16296 */
16297
16298 /**
16299 * Specifies the label for the menu item.
16300 *
16301 * @attribute label
16302 * @type string
16303 * @default ''
16304 */
16305
16306 });
16307
16308 ;
16309
16310 Polymer('stats-dimension-filter', {
16311 count: 0,
16312 dimensions: [],
16313 filters: {},
16314 props: [],
16315 value: null,
16316 values: {},
16317
16318 stringify: function(d) {
16319 return JSON.stringify(d);
16320 },
16321
16322 // Update this.props and this.values
16323 dimensionsChanged: function() {
16324 var ds = this.dimensions;
16325 var attrs = {}, values = {};
16326 var props = [];
16327 for (var i = 0; i < ds.length; ++i) {
16328 var d = ds[i];
16329 if (typeof d === "object") {
16330 for (var attr in d) {
16331 if (!attrs[attr]) {
16332 attrs[attr] = {};
16333 }
16334 attrs[attr][d[attr]] = true;
16335 }
16336 } else {
16337 console.error('dimension should be an object');
16338 }
16339 }
16340 for (var attr in attrs) {
16341 props.push(attr);
16342 if (!values[attr]) {
16343 values[attr] = [];
16344 }
16345 for (var i in attrs[attr]) {
16346 values[attr].push(i);
16347 }
16348 }
16349 this.props = props;
16350 this.values = values;
16351 },
16352
16353 dimensionFilter: function(dimensions, filters, count) {
16354 var hasFilter = false;
16355 var arr = dimensions.filter(function(d) {
16356 for (var prop in filters) {
16357 hasFilter = true;
16358 if (d[prop] !== filters[prop]) {
16359 return false;
16360 }
16361 }
16362 return true;
16363 });
16364 if (hasFilter) {
16365 this.value = JSON.stringify(arr[0]);
16366 }
16367 return arr;
16368 },
16369
16370 propPickerFilter: function(props, filters, count) {
16371 return props.filter(function(prop) {
16372 for (var p in filters) {
16373 if (prop === p) {
16374 return false;
16375 }
16376 }
16377 return true;
16378 });
16379 },
16380
16381 filtersArray: function(filters, count) {
16382 var arr = [];
16383 for (var prop in filters) {
16384 arr.push({
16385 prop: prop,
16386 value: filters[prop]
16387 });
16388 }
16389 return arr;
16390 },
16391
16392 // HACK: this is to trigger the dimensionFilter due to a bug in Polymer
16393 // https://github.com/Polymer/polymer/issues/1082
16394 filtersChanged: function() {
16395 this.count++;
16396 },
16397
16398 propSelected: function(event, detail, sender) {
16399 var prop = sender.label;
16400 var v = sender.selectedItemLabel;
16401 if (v) {
16402 this.filters[prop] = v;
16403 }
16404 // TODO: use observer pattern
16405 this.filtersChanged();
16406 },
16407
16408 propValueFilter: function(values, dimensions, filters, prop) {
16409 var ds = this.dimensionFilter(dimensions, filters);
16410 var selection = {};
16411 for (var i = 0; i < ds.length; ++i) {
16412 if (ds[i].hasOwnProperty(prop)) {
16413 selection[ds[i][prop]] = true;
16414 }
16415 }
16416 var ret = [];
16417 for (var i in selection) {
16418 ret.push(i);
16419 }
16420 return ret;
16421 },
16422
16423 onFilterUnchecked: function(event, detail, sender) {
16424 delete this.filters[sender.getAttribute('prop')];
16425 // TODO: use observer pattern
16426 this.filtersChanged();
16427 },
16428
16429 selectDimension: function(event, detail, sender) {
16430 this.value = sender.innerHTML;
16431 },
16432
16433 onClearDimension: function(event, detail, sender) {
16434 this.value = null;
16435 }
16436 });
16437 ;
16438
16439 (function() {
16440
16441 Polymer('core-shared-lib',{
16442
16443 notifyEvent: 'core-shared-lib-load',
16444
16445 ready: function() {
16446 if (!this.url && this.defaultUrl) {
16447 this.url = this.defaultUrl;
16448 }
16449 },
16450
16451 urlChanged: function() {
16452 require(this.url, this, this.callbackName);
16453 },
16454
16455 provide: function() {
16456 this.async('notify');
16457 },
16458
16459 notify: function() {
16460 this.fire(this.notifyEvent, arguments);
16461 }
16462
16463 });
16464
16465 var apiMap = {};
16466
16467 function require(url, notifiee, callbackName) {
16468 // make hashable string form url
16469 var name = nameFromUrl(url);
16470 // lookup existing loader instance
16471 var loader = apiMap[name];
16472 // create a loader as needed
16473 if (!loader) {
16474 loader = apiMap[name] = new Loader(name, url, callbackName);
16475 }
16476 loader.requestNotify(notifiee);
16477 }
16478
16479 function nameFromUrl(url) {
16480 return url.replace(/[\:\/\%\?\&\.\=\-\,]/g, '_') + '_api';
16481 }
16482
16483 var Loader = function(name, url, callbackName) {
16484 this.instances = [];
16485 this.callbackName = callbackName;
16486 if (this.callbackName) {
16487 window[this.callbackName] = this.success.bind(this);
16488 } else {
16489 if (url.indexOf(this.callbackMacro) >= 0) {
16490 this.callbackName = name + '_loaded';
16491 window[this.callbackName] = this.success.bind(this);
16492 url = url.replace(this.callbackMacro, this.callbackName);
16493 } else {
16494 // TODO(sjmiles): we should probably fallback to listening to script.loa d
16495 throw 'core-shared-api: a %%callback%% parameter is required in the API url';
16496 }
16497 }
16498 //
16499 this.addScript(url);
16500 };
16501
16502 Loader.prototype = {
16503
16504 callbackMacro: '%%callback%%',
16505 loaded: false,
16506
16507 addScript: function(src) {
16508 var script = document.createElement('script');
16509 script.src = src;
16510 script.onerror = this.error.bind(this);
16511 var s = document.querySelector('script');
16512 s.parentNode.insertBefore(script, s);
16513 this.script = script;
16514 },
16515
16516 removeScript: function() {
16517 if (this.script.parentNode) {
16518 this.script.parentNode.removeChild(this.script);
16519 }
16520 this.script = null;
16521 },
16522
16523 error: function() {
16524 this.cleanup();
16525 },
16526
16527 success: function() {
16528 this.loaded = true;
16529 this.cleanup();
16530 this.result = Array.prototype.slice.call(arguments);
16531 this.instances.forEach(this.provide, this);
16532 this.instances = null;
16533 },
16534
16535 cleanup: function() {
16536 delete window[this.callbackName];
16537 },
16538
16539 provide: function(instance) {
16540 instance.notify(instance, this.result);
16541 },
16542
16543 requestNotify: function(instance) {
16544 if (this.loaded) {
16545 this.provide(instance);
16546 } else {
16547 this.instances.push(instance);
16548 }
16549 }
16550
16551 };
16552
16553 })();
16554 ;
16555
16556 Polymer('google-jsapi',{
16557 defaultUrl: 'https://www.google.com/jsapi?callback=%%callback%%',
16558
16559 /**
16560 * Fired when the API library is loaded and available.
16561 * @event api-load
16562 */
16563 notifyEvent: 'api-load',
16564
16565 /**
16566 * Wrapper for `google` API namespace.
16567 * @property api
16568 */
16569 get api() {
16570 return google;
16571 }
16572 });
16573 ;
16574
16575 (function() {
16576 'use strict';
16577
16578 Polymer('google-chart',{
16579 /**
16580 * Fired when the graph is displayed.
16581 *
16582 * @event google-chart-render
16583 */
16584
16585
16586 /**
16587 * Sets the type of the chart.
16588 *
16589 * Should be one of:
16590 * - `area`, `bar`, `bubble`, `candlestick`, `column`, `combo`, `geo`,
16591 * `histogram`, `line`, `pie`, `scatter`, `stepped-area`
16592 *
16593 * See <a href="https://google-developers.appspot.com/chart/interactive/ docs/gallery">Google Visualization API reference (Chart Gallery)</a> for details .
16594 *
16595 * @attribute type
16596 * @type string
16597 */
16598 type: 'column',
16599
16600 /**
16601 * Sets the options for the chart.
16602 *
16603 * Example:
16604 * <pre>{
16605 * title: "Chart title goes here",
16606 * hAxis: {title: "Categories"},
16607 * vAxis: {title: "Values", minValue: 0, maxValue: 2},
16608 * legend: "none"
16609 * };</pre>
16610 * See <a href="https://google-developers.appspot.com/chart/interactive/ docs/gallery">Google Visualization API reference (Chart Gallery)</a>
16611 * for the options available to each chart type.
16612 *
16613 * @attribute options
16614 * @type object
16615 */
16616 options: null,
16617
16618 /**
16619 * Sets the data columns for this object.
16620 *
16621 * When specifying data with `cols` you must also specify `rows`, and
16622 * not specify `data`.
16623 *
16624 * Example:
16625 * <pre>[{label: "Categories", type: "string"},
16626 * {label: "Value", type: "number"}]</pre>
16627 * See <a href="https://google-developers.appspot.com/chart/interactive/ docs/reference#DataTable_addColumn">Google Visualization API reference (addColum n)</a>
16628 * for column definition format.
16629 *
16630 * @attribute cols
16631 * @type array
16632 */
16633 cols: null,
16634
16635 /**
16636 * Sets the data rows for this object.
16637 *
16638 * When specifying data with `rows` you must also specify `cols`, and
16639 * not specify `data`.
16640 *
16641 * Example:
16642 * <pre>[["Category 1", 1.0],
16643 * ["Category 2", 1.1]]</pre>
16644 * See <a href="https://google-developers.appspot.com/chart/interactive/ docs/reference#addrow">Google Visualization API reference (addRow)</a>
16645 * for row format.
16646 *
16647 * @attribute rows
16648 * @type array
16649 */
16650 rows: null,
16651
16652 /**
16653 * Sets the entire dataset for this object.
16654 * Can be used to provide the data directly, or to provide a URL from
16655 * which to request the data.
16656 *
16657 * The data format can be a two-dimensional array or the DataTable forma t
16658 * expected by Google Charts.
16659 * See <a href="https://google-developers.appspot.com/chart/interactive/ docs/reference#DataTable">Google Visualization API reference (DataTable construc tor)</a>
16660 * for data table format details.
16661 *
16662 * When specifying data with `data` you must not specify `cols` or `rows `.
16663 *
16664 * Example:
16665 * <pre>[["Categories", "Value"],
16666 * ["Category 1", 1.0],
16667 * ["Category 2", 1.1]]</pre>
16668 *
16669 * @attribute data
16670 * @type array, object, or string
16671 */
16672 data: null,
16673
16674 chartTypes: null,
16675
16676 chartObject: null,
16677
16678 isReady: false,
16679
16680 canDraw: false,
16681
16682 dataTable: null,
16683
16684 created: function() {
16685 this.chartTypes = {};
16686 this.cols = [];
16687 this.data = [];
16688 this.options = {};
16689 this.rows = [];
16690 this.dataTable = null;
16691 },
16692
16693 readyForAction: function(e, detail, sender) {
16694 google.load("visualization", "1", {
16695 packages: ['corechart'],
16696 callback: function() {
16697 this.isReady = true;
16698 this.loadChartTypes();
16699 this.loadData();
16700 }.bind(this)
16701 });
16702 },
16703
16704 typeChanged: function() {
16705 // Invalidate current chart object.
16706 this.chartObject = null;
16707 this.loadData();
16708 },
16709
16710 observe: {
16711 rows: 'loadData',
16712 cols: 'loadData',
16713 data: 'loadData'
16714 },
16715
16716 /**
16717 * Draws the chart.
16718 *
16719 * Called automatically on first load and whenever one of the attributes
16720 * changes. Can be called manually to handle e.g. page resizes.
16721 *
16722 * @method drawChart
16723 * @return {Object} Returns null.
16724 */
16725 drawChart: function() {
16726 if (this.canDraw) {
16727 if (!this.options) {
16728 this.options = {};
16729 }
16730 if (!this.chartObject) {
16731 var chartClass = this.chartTypes[this.type];
16732 if (chartClass) {
16733 this.chartObject = new chartClass(this.$.chartdiv);
16734 }
16735 }
16736 if (this.chartObject) {
16737 google.visualization.events.addOneTimeListener(this.chartObject,
16738 'ready', function() {
16739 this.fire('google-chart-render');
16740 }.bind(this));
16741 this.chartObject.draw(this.dataTable, this.options);
16742 } else {
16743 this.$.chartdiv.innerHTML = 'Undefined chart type';
16744 }
16745 }
16746 return null;
16747 },
16748
16749 loadChartTypes: function() {
16750 this.chartTypes = {
16751 'area': google.visualization.AreaChart,
16752 'bar': google.visualization.BarChart,
16753 'bubble': google.visualization.BubbleChart,
16754 'candlestick': google.visualization.CandlestickChart,
16755 'column': google.visualization.ColumnChart,
16756 'combo': google.visualization.ComboChart,
16757 'geo': google.visualization.GeoChart,
16758 'histogram': google.visualization.Histogram,
16759 'line': google.visualization.LineChart,
16760 'pie': google.visualization.PieChart,
16761 'scatter': google.visualization.ScatterChart,
16762 'stepped-area': google.visualization.SteppedAreaChart
16763 };
16764 },
16765
16766 loadData: function() {
16767 this.canDraw = false;
16768 if (this.isReady) {
16769 if (typeof this.data == 'string' || this.data instanceof String) {
16770 // Load data asynchronously, from external URL.
16771 this.$.ajax.go();
16772 } else {
16773 var dataTable = this.createDataTable();
16774 this.canDraw = true;
16775 if (dataTable) {
16776 this.dataTable = dataTable;
16777 this.drawChart();
16778 }
16779 }
16780 }
16781 },
16782
16783 externalDataLoaded: function(e, detail, sender) {
16784 var dataTable = this.createDataTable(this.$.ajax.response);
16785 this.canDraw = true;
16786 this.dataTable = dataTable;
16787 this.drawChart();
16788 },
16789
16790 createDataTable: function(data) {
16791 var dataTable = null;
16792
16793 // If a data object was not passed to this function, default to the
16794 // chart's data attribute. Passing a data object is necessary for
16795 // cases when the data attribute is a URL pointing to an external
16796 // data source.
16797 if (!data) {
16798 data = this.data;
16799 }
16800
16801 if (this.rows && this.rows.length > 0 && this.cols &&
16802 this.cols.length > 0) {
16803 // Create the data table from cols and rows.
16804 dataTable = new google.visualization.DataTable();
16805 dataTable.cols = this.cols;
16806
16807 for (var i = 0; i < this.cols.length; i++) {
16808 dataTable.addColumn(this.cols[i]);
16809 }
16810
16811 dataTable.addRows(this.rows);
16812 } else {
16813 // Create dataTable from the passed data or the data attribute.
16814 // Data can be in the form of raw DataTable data or a two dimensiona l
16815 // array.
16816 if (data.rows && data.cols) {
16817 dataTable = new google.visualization.DataTable(data);
16818 } else if (data.length > 0) {
16819 dataTable = google.visualization.arrayToDataTable(data);
16820 }
16821 }
16822
16823 return dataTable;
16824 }
16825 });
16826 })();
16827 ;
16828
16829
16830 (function() {
16831 function formatToIsoUnit(val) {
16832 for (var n = 0; val >= 1000; n++) {
16833 val /= 1000;
16834 }
16835 // Enforce 2 decimals.
16836 if (n > 0) {
16837 val = val.toFixed(2);
16838 }
16839 return val + ISO_SUFFIXES[n];
16840 }
16841
16842 var p = {
16843 dataTable: null,
16844 isReady: false,
16845 resolution: 'hours',
16846 titleText: '',
16847
16848 observe: {
16849 'data': 'loadData'
16850 },
16851
16852 ready: function() {
16853 // FIXME: I tried option='{"title": "{{titleText}}"" }''
16854 this.$.chart.options.title = this.titleText;
16855 },
16856
16857 attachView: function(view) {
16858 this.$.chart.setAttribute('data', view.toDataTable().toJSON());
16859 },
16860
16861 getKeyFormatter: function() {
16862 if (this.resolution == 'days') {
16863 return new google.visualization.DateFormat({pattern: 'yyyy/MM/dd'});
16864 } else {
16865 return new google.visualization.DateFormat({pattern: 'MM/dd HH:mm'});
16866 }
16867 },
16868
16869 formatDataColumnToIsoUnit: function(column) {
16870 for (var i = 0; i < this.dataTable.getNumberOfRows(); i++) {
16871 this.dataTable.setFormattedValue(
16872 i, column, formatToIsoUnit(this.dataTable.getValue(i, column)));
16873 }
16874 },
16875
16876 populate: function() {
16877 // override by subclass
16878 },
16879
16880 readyForAction: function(e, detail, sender) {
16881 google.load("visualization", "1", {
16882 packages: ['corechart'],
16883 callback: function() {
16884 this.isReady = true;
16885 this.loadData();
16886 }.bind(this)
16887 });
16888 },
16889
16890 // Makes sure ALL custom formatting is removed.
16891 resetFormattedData: function() {
16892 for (var i = 0; i < this.dataTable.getNumberOfColumns(); i++) {
16893 this.resetFormattedDataColumn(i);
16894 }
16895 },
16896
16897 // Makes sure custom formatting is removed for a specific column.
16898 resetFormattedDataColumn: function(column) {
16899 for (var i = 0; i < this.dataTable.getNumberOfRows(); i++) {
16900 this.dataTable.setFormattedValue(i, column, null);
16901 }
16902 },
16903
16904 loadData: function() {
16905 if (this.isReady && this.data) {
16906 this.dataTable = new google.visualization.DataTable(this.data);
16907 this.populate();
16908 }
16909 }
16910 };
16911
16912 Polymer('stats-chart-base', p);
16913 })();
16914
16915 ;
16916
16917 Polymer('stats-request-chart', {
16918 titleText: 'Requests',
16919
16920 populate: function() {
16921 if (this.hidden) {
16922 return;
16923 }
16924 this.resetFormattedData();
16925
16926 // These indexes are relative to stats_gviz._Summary.ORDER.
16927 this.getKeyFormatter().format(this.dataTable, 0);
16928
16929 var view = new google.visualization.DataView(this.dataTable);
16930 view.setColumns([0, 1, 2]);
16931 this.attachView(view);
16932 }
16933 });
16934 ;
16935
16936 Polymer('stats-work-chart', {
16937 isDimension: false,
16938 titleText: 'Shards Activity',
16939
16940 populate: function() {
16941 this.resetFormattedData();
16942
16943 // These indexes are relative to stats_gviz._Summary.ORDER.
16944 this.getKeyFormatter().format(this.dataTable, 0);
16945
16946 var view = new google.visualization.DataView(this.dataTable);
16947 if (this.isDimension) {
16948 view.setColumns([0, 1, 2, 9, 10]);
16949 } else {
16950 view.setColumns([0, 3, 4, 11, 12]);
16951 }
16952
16953 this.attachView(view);
16954 }
16955 });
16956 ;
16957
16958 Polymer('stats-table-chart', {
16959 observe: {
16960 'data': 'loadData'
16961 },
16962
16963 readyForAction: function() {
16964 google.load("visualization", "1", {
16965 packages: ['table'],
16966 callback: function() {
16967 this.isReady = true;
16968 this.loadData();
16969 }.bind(this)
16970 });
16971 },
16972
16973 loadData: function() {
16974 if (this.data && this.isReady) {
16975 if (!this.table) {
16976 this.table = new google.visualization.Table(this.$.chart);
16977 }
16978 this.table.draw(new google.visualization.DataTable(this.data));
16979 }
16980 }
16981 });
16982 ;
16983
16984 Polymer('stats-time-chart', {
16985 isDimension: false,
16986 titleText: 'Times (s)',
16987
16988 populate: function() {
16989 this.resetFormattedData();
16990
16991 // These indexes are relative to stats_gviz._Summary.ORDER.
16992 this.getKeyFormatter().format(this.dataTable, 0);
16993
16994 var round3 = new google.visualization.NumberFormat(
16995 {decimalSymbol:'.', fractionDigits:3});
16996 // These indexes are relative to stats_gviz._GVIZ_COLUMNS_ORDER.
16997 if (this.isDimension) {
16998 round3.format(this.dataTable, 6);
16999 round3.format(this.dataTable, 7);
17000 round3.format(this.dataTable, 8);
17001 } else {
17002 round3.format(this.dataTable, 8);
17003 round3.format(this.dataTable, 9);
17004 round3.format(this.dataTable, 10);
17005 }
17006
17007 var view = new google.visualization.DataView(this.dataTable);
17008 if (this.dimension) {
17009 view.setColumns([0, 6, 7, 8]);
17010 } else {
17011 view.setColumns([0, 8, 9, 10]);
17012 }
17013
17014 this.attachView(view);
17015 }
17016 });
17017 ;
17018
17019 Polymer('stats-app', {
17020 observe: {
17021 'dimension': 'paramsChanged',
17022 'duration': 'paramsChanged',
17023 'resolution': 'paramsChanged'
17024 },
17025
17026 ready: function() {
17027 var self = this;
17028 window.onpopstate = function(event) {
17029 self.restoringState = true;
17030 self.dimension = event.state.dimension;
17031 self.duration = event.state.duration;
17032 self.resolution = event.state.resolution;
17033 };
17034 this.restoreState();
17035 },
17036
17037 restoreState: function() {
17038 try {
17039 var stateObj = JSON.parse(
17040 decodeURIComponent(window.location.search.substr(1)));
17041 this.dimension = stateObj.dimension;
17042 this.duration = stateObj.duration || 120;
17043 this.resolution = stateObj.resolution || 'hours';
17044 this.restoringState = true;
17045 var stateObj = this.getState();
17046 var url = '/stats?' + JSON.stringify(stateObj);
17047 window.history.replaceState(stateObj, '', url);
17048 } catch (e) {
17049 this.restoringState = false;
17050 this.resolution = 'hours';
17051 this.duration = 120;
17052 }
17053 },
17054
17055 getState: function() {
17056 return {
17057 dimension: this.dimension,
17058 duration: this.duration,
17059 resolution: this.resolution,
17060 };
17061 },
17062
17063 pushState: function() {
17064 var stateObj = this.getState();
17065 var url = '/stats?' + JSON.stringify(stateObj);
17066 window.history.pushState(stateObj, '', url);
17067 },
17068
17069 paramsChanged: function(oldValue, newValue) {
17070 if (this.dimension) {
17071 this.$.get_stats_dimension.go();
17072 } else {
17073 this.$.get_stats_summary.go();
17074 }
17075 if (!this.restoringState) {
17076 this.pushState();
17077 } else {
17078 this.restoringState = false;
17079 }
17080 },
17081
17082 onGetStatsSummarySuccess: function(event, detail, sender) {
17083 this.dataTable = detail.response.table;
17084 },
17085
17086 ajaxLoadingChanged: function(oldValue, newValue) {
17087 if (newValue) {
17088 this.$.loading.active = true;
17089 } else {
17090 this.$.loading.active = false;
17091 }
17092 }
17093 });
17094
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698