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

Side by Side Diff: appengine/swarming/elements/polymer05/stats-overview-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 Polymer('core-xhr', {
11862
11863 /**
11864 * Sends a HTTP request to the server and returns the XHR object.
11865 *
11866 * @method request
11867 * @param {Object} inOptions
11868 * @param {String} inOptions.url The url to which the request is sent.
11869 * @param {String} inOptions.method The HTTP method to use, default is GET.
11870 * @param {boolean} inOptions.sync By default, all requests are sent as ynchronously. To send synchronous requests, set to true.
11871 * @param {Object} inOptions.params Data to be sent to the server.
11872 * @param {Object} inOptions.body The content for the request body for POST method.
11873 * @param {Object} inOptions.headers HTTP request headers.
11874 * @param {String} inOptions.responseType The response type. Default is 'text'.
11875 * @param {boolean} inOptions.withCredentials Whether or not to send cr edentials on the request. Default is false.
11876 * @param {Object} inOptions.callback Called when request is completed.
11877 * @returns {Object} XHR object.
11878 */
11879 request: function(options) {
11880 var xhr = new XMLHttpRequest();
11881 var url = options.url;
11882 var method = options.method || 'GET';
11883 var async = !options.sync;
11884 //
11885 var params = this.toQueryString(options.params);
11886 if (params && method.toUpperCase() == 'GET') {
11887 url += (url.indexOf('?') > 0 ? '&' : '?') + params;
11888 }
11889 var xhrParams = this.isBodyMethod(method) ? (options.body || params) : n ull;
11890 //
11891 xhr.open(method, url, async);
11892 if (options.responseType) {
11893 xhr.responseType = options.responseType;
11894 }
11895 if (options.withCredentials) {
11896 xhr.withCredentials = true;
11897 }
11898 this.makeReadyStateHandler(xhr, options.callback);
11899 this.setRequestHeaders(xhr, options.headers);
11900 xhr.send(xhrParams);
11901 if (!async) {
11902 xhr.onreadystatechange(xhr);
11903 }
11904 return xhr;
11905 },
11906
11907 toQueryString: function(params) {
11908 var r = [];
11909 for (var n in params) {
11910 var v = params[n];
11911 n = encodeURIComponent(n);
11912 r.push(v == null ? n : (n + '=' + encodeURIComponent(v)));
11913 }
11914 return r.join('&');
11915 },
11916
11917 isBodyMethod: function(method) {
11918 return this.bodyMethods[(method || '').toUpperCase()];
11919 },
11920
11921 bodyMethods: {
11922 POST: 1,
11923 PUT: 1,
11924 PATCH: 1,
11925 DELETE: 1
11926 },
11927
11928 makeReadyStateHandler: function(xhr, callback) {
11929 xhr.onreadystatechange = function() {
11930 if (xhr.readyState == 4) {
11931 callback && callback.call(null, xhr.response, xhr);
11932 }
11933 };
11934 },
11935
11936 setRequestHeaders: function(xhr, headers) {
11937 if (headers) {
11938 for (var name in headers) {
11939 xhr.setRequestHeader(name, headers[name]);
11940 }
11941 }
11942 }
11943
11944 });
11945
11946 ;
11947
11948
11949 Polymer('core-ajax', {
11950 /**
11951 * Fired when a response is received.
11952 *
11953 * @event core-response
11954 */
11955
11956 /**
11957 * Fired when an error is received.
11958 *
11959 * @event core-error
11960 */
11961
11962 /**
11963 * Fired whenever a response or an error is received.
11964 *
11965 * @event core-complete
11966 */
11967
11968 /**
11969 * The URL target of the request.
11970 *
11971 * @attribute url
11972 * @type string
11973 * @default ''
11974 */
11975 url: '',
11976
11977 /**
11978 * Specifies what data to store in the `response` property, and
11979 * to deliver as `event.response` in `response` events.
11980 *
11981 * One of:
11982 *
11983 * `text`: uses `XHR.responseText`.
11984 *
11985 * `xml`: uses `XHR.responseXML`.
11986 *
11987 * `json`: uses `XHR.responseText` parsed as JSON.
11988 *
11989 * `arraybuffer`: uses `XHR.response`.
11990 *
11991 * `blob`: uses `XHR.response`.
11992 *
11993 * `document`: uses `XHR.response`.
11994 *
11995 * @attribute handleAs
11996 * @type string
11997 * @default 'text'
11998 */
11999 handleAs: '',
12000
12001 /**
12002 * If true, automatically performs an Ajax request when either `url` or `par ams` changes.
12003 *
12004 * @attribute auto
12005 * @type boolean
12006 * @default false
12007 */
12008 auto: false,
12009
12010 /**
12011 * Parameters to send to the specified URL, as JSON.
12012 *
12013 * @attribute params
12014 * @type string
12015 * @default ''
12016 */
12017 params: '',
12018
12019 /**
12020 * The response for the current request, or null if it hasn't
12021 * completed yet or the request resulted in error.
12022 *
12023 * @attribute response
12024 * @type Object
12025 * @default null
12026 */
12027 response: null,
12028
12029 /**
12030 * The error for the current request, or null if it hasn't
12031 * completed yet or the request resulted in success.
12032 *
12033 * @attribute error
12034 * @type Object
12035 * @default null
12036 */
12037 error: null,
12038
12039 /**
12040 * Whether the current request is currently loading.
12041 *
12042 * @attribute loading
12043 * @type boolean
12044 * @default false
12045 */
12046 loading: false,
12047
12048 /**
12049 * The progress of the current request.
12050 *
12051 * @attribute progress
12052 * @type {loaded: number, total: number, lengthComputable: boolean}
12053 * @default {}
12054 */
12055 progress: null,
12056
12057 /**
12058 * The HTTP method to use such as 'GET', 'POST', 'PUT', or 'DELETE'.
12059 * Default is 'GET'.
12060 *
12061 * @attribute method
12062 * @type string
12063 * @default ''
12064 */
12065 method: '',
12066
12067 /**
12068 * HTTP request headers to send.
12069 *
12070 * Example:
12071 *
12072 * <core-ajax
12073 * auto
12074 * url="http://somesite.com"
12075 * headers='{"X-Requested-With": "XMLHttpRequest"}'
12076 * handleAs="json"
12077 * on-core-response="{{handleResponse}}"></core-ajax>
12078 *
12079 * @attribute headers
12080 * @type Object
12081 * @default null
12082 */
12083 headers: null,
12084
12085 /**
12086 * Optional raw body content to send when method === "POST".
12087 *
12088 * Example:
12089 *
12090 * <core-ajax method="POST" auto url="http://somesite.com"
12091 * body='{"foo":1, "bar":2}'>
12092 * </core-ajax>
12093 *
12094 * @attribute body
12095 * @type Object
12096 * @default null
12097 */
12098 body: null,
12099
12100 /**
12101 * Content type to use when sending data.
12102 *
12103 * @attribute contentType
12104 * @type string
12105 * @default 'application/x-www-form-urlencoded'
12106 */
12107 contentType: 'application/x-www-form-urlencoded',
12108
12109 /**
12110 * Set the withCredentials flag on the request.
12111 *
12112 * @attribute withCredentials
12113 * @type boolean
12114 * @default false
12115 */
12116 withCredentials: false,
12117
12118 /**
12119 * Additional properties to send to core-xhr.
12120 *
12121 * Can be set to an object containing default properties
12122 * to send as arguments to the `core-xhr.request()` method
12123 * which implements the low-level communication.
12124 *
12125 * @property xhrArgs
12126 * @type Object
12127 * @default null
12128 */
12129 xhrArgs: null,
12130
12131 created: function() {
12132 this.progress = {};
12133 },
12134
12135 ready: function() {
12136 this.xhr = document.createElement('core-xhr');
12137 },
12138
12139 receive: function(response, xhr) {
12140 if (this.isSuccess(xhr)) {
12141 this.processResponse(xhr);
12142 } else {
12143 this.processError(xhr);
12144 }
12145 this.complete(xhr);
12146 },
12147
12148 isSuccess: function(xhr) {
12149 var status = xhr.status || 0;
12150 return !status || (status >= 200 && status < 300);
12151 },
12152
12153 processResponse: function(xhr) {
12154 var response = this.evalResponse(xhr);
12155 if (xhr === this.activeRequest) {
12156 this.response = response;
12157 }
12158 this.fire('core-response', {response: response, xhr: xhr});
12159 },
12160
12161 processError: function(xhr) {
12162 var response = xhr.status + ': ' + xhr.responseText;
12163 if (xhr === this.activeRequest) {
12164 this.error = response;
12165 }
12166 this.fire('core-error', {response: response, xhr: xhr});
12167 },
12168
12169 processProgress: function(progress, xhr) {
12170 if (xhr !== this.activeRequest) {
12171 return;
12172 }
12173 // We create a proxy object here because these fields
12174 // on the progress event are readonly properties, which
12175 // causes problems in common use cases (e.g. binding to
12176 // <paper-progress> attributes).
12177 var progressProxy = {
12178 lengthComputable: progress.lengthComputable,
12179 loaded: progress.loaded,
12180 total: progress.total
12181 }
12182 this.progress = progressProxy;
12183 },
12184
12185 complete: function(xhr) {
12186 if (xhr === this.activeRequest) {
12187 this.loading = false;
12188 }
12189 this.fire('core-complete', {response: xhr.status, xhr: xhr});
12190 },
12191
12192 evalResponse: function(xhr) {
12193 return this[(this.handleAs || 'text') + 'Handler'](xhr);
12194 },
12195
12196 xmlHandler: function(xhr) {
12197 return xhr.responseXML;
12198 },
12199
12200 textHandler: function(xhr) {
12201 return xhr.responseText;
12202 },
12203
12204 jsonHandler: function(xhr) {
12205 var r = xhr.responseText;
12206 try {
12207 return JSON.parse(r);
12208 } catch (x) {
12209 console.warn('core-ajax caught an exception trying to parse response as JSON:');
12210 console.warn('url:', this.url);
12211 console.warn(x);
12212 return r;
12213 }
12214 },
12215
12216 documentHandler: function(xhr) {
12217 return xhr.response;
12218 },
12219
12220 blobHandler: function(xhr) {
12221 return xhr.response;
12222 },
12223
12224 arraybufferHandler: function(xhr) {
12225 return xhr.response;
12226 },
12227
12228 urlChanged: function() {
12229 if (!this.handleAs) {
12230 var ext = String(this.url).split('.').pop();
12231 switch (ext) {
12232 case 'json':
12233 this.handleAs = 'json';
12234 break;
12235 }
12236 }
12237 this.autoGo();
12238 },
12239
12240 paramsChanged: function() {
12241 this.autoGo();
12242 },
12243
12244 bodyChanged: function() {
12245 this.autoGo();
12246 },
12247
12248 autoChanged: function() {
12249 this.autoGo();
12250 },
12251
12252 // TODO(sorvell): multiple side-effects could call autoGo
12253 // during one micro-task, use a job to have only one action
12254 // occur
12255 autoGo: function() {
12256 if (this.auto) {
12257 this.goJob = this.job(this.goJob, this.go, 0);
12258 }
12259 },
12260
12261 getParams: function(params) {
12262 params = this.params || params;
12263 if (params && typeof(params) == 'string') {
12264 params = JSON.parse(params);
12265 }
12266 return params;
12267 },
12268
12269 /**
12270 * Performs an Ajax request to the specified URL.
12271 *
12272 * @method go
12273 */
12274 go: function() {
12275 var args = this.xhrArgs || {};
12276 // TODO(sjmiles): we may want XHR to default to POST if body is set
12277 args.body = this.body || args.body;
12278 args.params = this.getParams(args.params);
12279 args.headers = this.headers || args.headers || {};
12280 if (args.headers && typeof(args.headers) == 'string') {
12281 args.headers = JSON.parse(args.headers);
12282 }
12283 var hasContentType = Object.keys(args.headers).some(function (header) {
12284 return header.toLowerCase() === 'content-type';
12285 });
12286 // No Content-Type should be specified if sending `FormData`.
12287 // The UA must set the Content-Type w/ a calculated multipart boundary ID .
12288 if (args.body instanceof FormData) {
12289 delete args.headers['Content-Type'];
12290 }
12291 else if (!hasContentType && this.contentType) {
12292 args.headers['Content-Type'] = this.contentType;
12293 }
12294 if (this.handleAs === 'arraybuffer' || this.handleAs === 'blob' ||
12295 this.handleAs === 'document') {
12296 args.responseType = this.handleAs;
12297 }
12298 args.withCredentials = this.withCredentials;
12299 args.callback = this.receive.bind(this);
12300 args.url = this.url;
12301 args.method = this.method;
12302
12303 this.response = this.error = this.progress = null;
12304 this.activeRequest = args.url && this.xhr.request(args);
12305 if (this.activeRequest) {
12306 this.loading = true;
12307 var activeRequest = this.activeRequest;
12308 // IE < 10 doesn't support progress events.
12309 if ('onprogress' in activeRequest) {
12310 this.activeRequest.addEventListener(
12311 'progress',
12312 function(progress) {
12313 this.processProgress(progress, activeRequest);
12314 }.bind(this), false);
12315 } else {
12316 this.progress = {
12317 lengthComputable: false,
12318 }
12319 }
12320 }
12321 return this.activeRequest;
12322 },
12323
12324 /**
12325 * Aborts the current active request if there is one and resets internal
12326 * state appropriately.
12327 *
12328 * @method abort
12329 */
12330 abort: function() {
12331 if (!this.activeRequest) return;
12332 this.activeRequest.abort();
12333 this.activeRequest = null;
12334 this.progress = {};
12335 this.loading = false;
12336 }
12337
12338 });
12339
12340 ;
12341
12342 (function() {
12343
12344 Polymer('core-shared-lib',{
12345
12346 notifyEvent: 'core-shared-lib-load',
12347
12348 ready: function() {
12349 if (!this.url && this.defaultUrl) {
12350 this.url = this.defaultUrl;
12351 }
12352 },
12353
12354 urlChanged: function() {
12355 require(this.url, this, this.callbackName);
12356 },
12357
12358 provide: function() {
12359 this.async('notify');
12360 },
12361
12362 notify: function() {
12363 this.fire(this.notifyEvent, arguments);
12364 }
12365
12366 });
12367
12368 var apiMap = {};
12369
12370 function require(url, notifiee, callbackName) {
12371 // make hashable string form url
12372 var name = nameFromUrl(url);
12373 // lookup existing loader instance
12374 var loader = apiMap[name];
12375 // create a loader as needed
12376 if (!loader) {
12377 loader = apiMap[name] = new Loader(name, url, callbackName);
12378 }
12379 loader.requestNotify(notifiee);
12380 }
12381
12382 function nameFromUrl(url) {
12383 return url.replace(/[\:\/\%\?\&\.\=\-\,]/g, '_') + '_api';
12384 }
12385
12386 var Loader = function(name, url, callbackName) {
12387 this.instances = [];
12388 this.callbackName = callbackName;
12389 if (this.callbackName) {
12390 window[this.callbackName] = this.success.bind(this);
12391 } else {
12392 if (url.indexOf(this.callbackMacro) >= 0) {
12393 this.callbackName = name + '_loaded';
12394 window[this.callbackName] = this.success.bind(this);
12395 url = url.replace(this.callbackMacro, this.callbackName);
12396 } else {
12397 // TODO(sjmiles): we should probably fallback to listening to script.loa d
12398 throw 'core-shared-api: a %%callback%% parameter is required in the API url';
12399 }
12400 }
12401 //
12402 this.addScript(url);
12403 };
12404
12405 Loader.prototype = {
12406
12407 callbackMacro: '%%callback%%',
12408 loaded: false,
12409
12410 addScript: function(src) {
12411 var script = document.createElement('script');
12412 script.src = src;
12413 script.onerror = this.error.bind(this);
12414 var s = document.querySelector('script');
12415 s.parentNode.insertBefore(script, s);
12416 this.script = script;
12417 },
12418
12419 removeScript: function() {
12420 if (this.script.parentNode) {
12421 this.script.parentNode.removeChild(this.script);
12422 }
12423 this.script = null;
12424 },
12425
12426 error: function() {
12427 this.cleanup();
12428 },
12429
12430 success: function() {
12431 this.loaded = true;
12432 this.cleanup();
12433 this.result = Array.prototype.slice.call(arguments);
12434 this.instances.forEach(this.provide, this);
12435 this.instances = null;
12436 },
12437
12438 cleanup: function() {
12439 delete window[this.callbackName];
12440 },
12441
12442 provide: function(instance) {
12443 instance.notify(instance, this.result);
12444 },
12445
12446 requestNotify: function(instance) {
12447 if (this.loaded) {
12448 this.provide(instance);
12449 } else {
12450 this.instances.push(instance);
12451 }
12452 }
12453
12454 };
12455
12456 })();
12457 ;
12458
12459 Polymer('google-jsapi',{
12460 defaultUrl: 'https://www.google.com/jsapi?callback=%%callback%%',
12461
12462 /**
12463 * Fired when the API library is loaded and available.
12464 * @event api-load
12465 */
12466 notifyEvent: 'api-load',
12467
12468 /**
12469 * Wrapper for `google` API namespace.
12470 * @property api
12471 */
12472 get api() {
12473 return google;
12474 }
12475 });
12476 ;
12477
12478 (function() {
12479 'use strict';
12480
12481 Polymer('google-chart',{
12482 /**
12483 * Fired when the graph is displayed.
12484 *
12485 * @event google-chart-render
12486 */
12487
12488
12489 /**
12490 * Sets the type of the chart.
12491 *
12492 * Should be one of:
12493 * - `area`, `bar`, `bubble`, `candlestick`, `column`, `combo`, `geo`,
12494 * `histogram`, `line`, `pie`, `scatter`, `stepped-area`
12495 *
12496 * See <a href="https://google-developers.appspot.com/chart/interactive/ docs/gallery">Google Visualization API reference (Chart Gallery)</a> for details .
12497 *
12498 * @attribute type
12499 * @type string
12500 */
12501 type: 'column',
12502
12503 /**
12504 * Sets the options for the chart.
12505 *
12506 * Example:
12507 * <pre>{
12508 * title: "Chart title goes here",
12509 * hAxis: {title: "Categories"},
12510 * vAxis: {title: "Values", minValue: 0, maxValue: 2},
12511 * legend: "none"
12512 * };</pre>
12513 * See <a href="https://google-developers.appspot.com/chart/interactive/ docs/gallery">Google Visualization API reference (Chart Gallery)</a>
12514 * for the options available to each chart type.
12515 *
12516 * @attribute options
12517 * @type object
12518 */
12519 options: null,
12520
12521 /**
12522 * Sets the data columns for this object.
12523 *
12524 * When specifying data with `cols` you must also specify `rows`, and
12525 * not specify `data`.
12526 *
12527 * Example:
12528 * <pre>[{label: "Categories", type: "string"},
12529 * {label: "Value", type: "number"}]</pre>
12530 * See <a href="https://google-developers.appspot.com/chart/interactive/ docs/reference#DataTable_addColumn">Google Visualization API reference (addColum n)</a>
12531 * for column definition format.
12532 *
12533 * @attribute cols
12534 * @type array
12535 */
12536 cols: null,
12537
12538 /**
12539 * Sets the data rows for this object.
12540 *
12541 * When specifying data with `rows` you must also specify `cols`, and
12542 * not specify `data`.
12543 *
12544 * Example:
12545 * <pre>[["Category 1", 1.0],
12546 * ["Category 2", 1.1]]</pre>
12547 * See <a href="https://google-developers.appspot.com/chart/interactive/ docs/reference#addrow">Google Visualization API reference (addRow)</a>
12548 * for row format.
12549 *
12550 * @attribute rows
12551 * @type array
12552 */
12553 rows: null,
12554
12555 /**
12556 * Sets the entire dataset for this object.
12557 * Can be used to provide the data directly, or to provide a URL from
12558 * which to request the data.
12559 *
12560 * The data format can be a two-dimensional array or the DataTable forma t
12561 * expected by Google Charts.
12562 * See <a href="https://google-developers.appspot.com/chart/interactive/ docs/reference#DataTable">Google Visualization API reference (DataTable construc tor)</a>
12563 * for data table format details.
12564 *
12565 * When specifying data with `data` you must not specify `cols` or `rows `.
12566 *
12567 * Example:
12568 * <pre>[["Categories", "Value"],
12569 * ["Category 1", 1.0],
12570 * ["Category 2", 1.1]]</pre>
12571 *
12572 * @attribute data
12573 * @type array, object, or string
12574 */
12575 data: null,
12576
12577 chartTypes: null,
12578
12579 chartObject: null,
12580
12581 isReady: false,
12582
12583 canDraw: false,
12584
12585 dataTable: null,
12586
12587 created: function() {
12588 this.chartTypes = {};
12589 this.cols = [];
12590 this.data = [];
12591 this.options = {};
12592 this.rows = [];
12593 this.dataTable = null;
12594 },
12595
12596 readyForAction: function(e, detail, sender) {
12597 google.load("visualization", "1", {
12598 packages: ['corechart'],
12599 callback: function() {
12600 this.isReady = true;
12601 this.loadChartTypes();
12602 this.loadData();
12603 }.bind(this)
12604 });
12605 },
12606
12607 typeChanged: function() {
12608 // Invalidate current chart object.
12609 this.chartObject = null;
12610 this.loadData();
12611 },
12612
12613 observe: {
12614 rows: 'loadData',
12615 cols: 'loadData',
12616 data: 'loadData'
12617 },
12618
12619 /**
12620 * Draws the chart.
12621 *
12622 * Called automatically on first load and whenever one of the attributes
12623 * changes. Can be called manually to handle e.g. page resizes.
12624 *
12625 * @method drawChart
12626 * @return {Object} Returns null.
12627 */
12628 drawChart: function() {
12629 if (this.canDraw) {
12630 if (!this.options) {
12631 this.options = {};
12632 }
12633 if (!this.chartObject) {
12634 var chartClass = this.chartTypes[this.type];
12635 if (chartClass) {
12636 this.chartObject = new chartClass(this.$.chartdiv);
12637 }
12638 }
12639 if (this.chartObject) {
12640 google.visualization.events.addOneTimeListener(this.chartObject,
12641 'ready', function() {
12642 this.fire('google-chart-render');
12643 }.bind(this));
12644 this.chartObject.draw(this.dataTable, this.options);
12645 } else {
12646 this.$.chartdiv.innerHTML = 'Undefined chart type';
12647 }
12648 }
12649 return null;
12650 },
12651
12652 loadChartTypes: function() {
12653 this.chartTypes = {
12654 'area': google.visualization.AreaChart,
12655 'bar': google.visualization.BarChart,
12656 'bubble': google.visualization.BubbleChart,
12657 'candlestick': google.visualization.CandlestickChart,
12658 'column': google.visualization.ColumnChart,
12659 'combo': google.visualization.ComboChart,
12660 'geo': google.visualization.GeoChart,
12661 'histogram': google.visualization.Histogram,
12662 'line': google.visualization.LineChart,
12663 'pie': google.visualization.PieChart,
12664 'scatter': google.visualization.ScatterChart,
12665 'stepped-area': google.visualization.SteppedAreaChart
12666 };
12667 },
12668
12669 loadData: function() {
12670 this.canDraw = false;
12671 if (this.isReady) {
12672 if (typeof this.data == 'string' || this.data instanceof String) {
12673 // Load data asynchronously, from external URL.
12674 this.$.ajax.go();
12675 } else {
12676 var dataTable = this.createDataTable();
12677 this.canDraw = true;
12678 if (dataTable) {
12679 this.dataTable = dataTable;
12680 this.drawChart();
12681 }
12682 }
12683 }
12684 },
12685
12686 externalDataLoaded: function(e, detail, sender) {
12687 var dataTable = this.createDataTable(this.$.ajax.response);
12688 this.canDraw = true;
12689 this.dataTable = dataTable;
12690 this.drawChart();
12691 },
12692
12693 createDataTable: function(data) {
12694 var dataTable = null;
12695
12696 // If a data object was not passed to this function, default to the
12697 // chart's data attribute. Passing a data object is necessary for
12698 // cases when the data attribute is a URL pointing to an external
12699 // data source.
12700 if (!data) {
12701 data = this.data;
12702 }
12703
12704 if (this.rows && this.rows.length > 0 && this.cols &&
12705 this.cols.length > 0) {
12706 // Create the data table from cols and rows.
12707 dataTable = new google.visualization.DataTable();
12708 dataTable.cols = this.cols;
12709
12710 for (var i = 0; i < this.cols.length; i++) {
12711 dataTable.addColumn(this.cols[i]);
12712 }
12713
12714 dataTable.addRows(this.rows);
12715 } else {
12716 // Create dataTable from the passed data or the data attribute.
12717 // Data can be in the form of raw DataTable data or a two dimensiona l
12718 // array.
12719 if (data.rows && data.cols) {
12720 dataTable = new google.visualization.DataTable(data);
12721 } else if (data.length > 0) {
12722 dataTable = google.visualization.arrayToDataTable(data);
12723 }
12724 }
12725
12726 return dataTable;
12727 }
12728 });
12729 })();
12730 ;
12731
12732
12733 (function() {
12734 function formatToIsoUnit(val) {
12735 for (var n = 0; val >= 1000; n++) {
12736 val /= 1000;
12737 }
12738 // Enforce 2 decimals.
12739 if (n > 0) {
12740 val = val.toFixed(2);
12741 }
12742 return val + ISO_SUFFIXES[n];
12743 }
12744
12745 var p = {
12746 dataTable: null,
12747 isReady: false,
12748 resolution: 'hours',
12749 titleText: '',
12750
12751 observe: {
12752 'data': 'loadData'
12753 },
12754
12755 ready: function() {
12756 // FIXME: I tried option='{"title": "{{titleText}}"" }''
12757 this.$.chart.options.title = this.titleText;
12758 },
12759
12760 attachView: function(view) {
12761 this.$.chart.setAttribute('data', view.toDataTable().toJSON());
12762 },
12763
12764 getKeyFormatter: function() {
12765 if (this.resolution == 'days') {
12766 return new google.visualization.DateFormat({pattern: 'yyyy/MM/dd'});
12767 } else {
12768 return new google.visualization.DateFormat({pattern: 'MM/dd HH:mm'});
12769 }
12770 },
12771
12772 formatDataColumnToIsoUnit: function(column) {
12773 for (var i = 0; i < this.dataTable.getNumberOfRows(); i++) {
12774 this.dataTable.setFormattedValue(
12775 i, column, formatToIsoUnit(this.dataTable.getValue(i, column)));
12776 }
12777 },
12778
12779 populate: function() {
12780 // override by subclass
12781 },
12782
12783 readyForAction: function(e, detail, sender) {
12784 google.load("visualization", "1", {
12785 packages: ['corechart'],
12786 callback: function() {
12787 this.isReady = true;
12788 this.loadData();
12789 }.bind(this)
12790 });
12791 },
12792
12793 // Makes sure ALL custom formatting is removed.
12794 resetFormattedData: function() {
12795 for (var i = 0; i < this.dataTable.getNumberOfColumns(); i++) {
12796 this.resetFormattedDataColumn(i);
12797 }
12798 },
12799
12800 // Makes sure custom formatting is removed for a specific column.
12801 resetFormattedDataColumn: function(column) {
12802 for (var i = 0; i < this.dataTable.getNumberOfRows(); i++) {
12803 this.dataTable.setFormattedValue(i, column, null);
12804 }
12805 },
12806
12807 loadData: function() {
12808 if (this.isReady && this.data) {
12809 this.dataTable = new google.visualization.DataTable(this.data);
12810 this.populate();
12811 }
12812 }
12813 };
12814
12815 Polymer('stats-chart-base', p);
12816 })();
12817
12818 ;
12819
12820 Polymer('stats-request-chart', {
12821 titleText: 'Requests',
12822
12823 populate: function() {
12824 if (this.hidden) {
12825 return;
12826 }
12827 this.resetFormattedData();
12828
12829 // These indexes are relative to stats_gviz._Summary.ORDER.
12830 this.getKeyFormatter().format(this.dataTable, 0);
12831
12832 var view = new google.visualization.DataView(this.dataTable);
12833 view.setColumns([0, 1, 2]);
12834 this.attachView(view);
12835 }
12836 });
12837 ;
12838
12839 Polymer('stats-work-chart', {
12840 isDimension: false,
12841 titleText: 'Shards Activity',
12842
12843 populate: function() {
12844 this.resetFormattedData();
12845
12846 // These indexes are relative to stats_gviz._Summary.ORDER.
12847 this.getKeyFormatter().format(this.dataTable, 0);
12848
12849 var view = new google.visualization.DataView(this.dataTable);
12850 if (this.isDimension) {
12851 view.setColumns([0, 1, 2, 9, 10]);
12852 } else {
12853 view.setColumns([0, 3, 4, 11, 12]);
12854 }
12855
12856 this.attachView(view);
12857 }
12858 });
12859 ;
12860
12861 Polymer('stats-time-chart', {
12862 isDimension: false,
12863 titleText: 'Times (s)',
12864
12865 populate: function() {
12866 this.resetFormattedData();
12867
12868 // These indexes are relative to stats_gviz._Summary.ORDER.
12869 this.getKeyFormatter().format(this.dataTable, 0);
12870
12871 var round3 = new google.visualization.NumberFormat(
12872 {decimalSymbol:'.', fractionDigits:3});
12873 // These indexes are relative to stats_gviz._GVIZ_COLUMNS_ORDER.
12874 if (this.isDimension) {
12875 round3.format(this.dataTable, 6);
12876 round3.format(this.dataTable, 7);
12877 round3.format(this.dataTable, 8);
12878 } else {
12879 round3.format(this.dataTable, 8);
12880 round3.format(this.dataTable, 9);
12881 round3.format(this.dataTable, 10);
12882 }
12883
12884 var view = new google.visualization.DataView(this.dataTable);
12885 if (this.dimension) {
12886 view.setColumns([0, 6, 7, 8]);
12887 } else {
12888 view.setColumns([0, 8, 9, 10]);
12889 }
12890
12891 this.attachView(view);
12892 }
12893 });
12894 ;
12895
12896 Polymer('stats-overview', {
12897 ready: function() {
12898 this.$.get_stats_summary.go();
12899 },
12900
12901 onGetStatsSummarySuccess: function(event, detail, sender) {
12902 this.dataTable = detail.response.table;
12903 }
12904 });
12905
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698