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

Side by Side Diff: packages/polymer_interop/lib/src/js/polymer.js

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

Powered by Google App Engine
This is Rietveld 408576698