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

Unified Diff: runtime/bin/vmservice/observatory/deployed/web/packages/polymer/src/js/polymer/polymer.js

Issue 839543002: Revert "Build Observatory with runtime" (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 5 years, 11 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 side-by-side diff with in-line comments
Download patch
Index: runtime/bin/vmservice/observatory/deployed/web/packages/polymer/src/js/polymer/polymer.js
diff --git a/runtime/bin/vmservice/observatory/deployed/web/packages/polymer/src/js/polymer/polymer.js b/runtime/bin/vmservice/observatory/deployed/web/packages/polymer/src/js/polymer/polymer.js
new file mode 100644
index 0000000000000000000000000000000000000000..b8b62b0a6dcf83d08e443c9f857d048172eff9d1
--- /dev/null
+++ b/runtime/bin/vmservice/observatory/deployed/web/packages/polymer/src/js/polymer/polymer.js
@@ -0,0 +1,11829 @@
+/**
+ * @license
+ * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
+ * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
+ * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
+ * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
+ * Code distributed by Google as part of the polymer project is also
+ * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
+ */
+// @version 0.5.1
+window.PolymerGestures = {};
+
+(function(scope) {
+ var HAS_FULL_PATH = false;
+
+ // test for full event path support
+ var pathTest = document.createElement('meta');
+ if (pathTest.createShadowRoot) {
+ var sr = pathTest.createShadowRoot();
+ var s = document.createElement('span');
+ sr.appendChild(s);
+ pathTest.addEventListener('testpath', function(ev) {
+ if (ev.path) {
+ // if the span is in the event path, then path[0] is the real source for all events
+ HAS_FULL_PATH = ev.path[0] === s;
+ }
+ ev.stopPropagation();
+ });
+ var ev = new CustomEvent('testpath', {bubbles: true});
+ // must add node to DOM to trigger event listener
+ document.head.appendChild(pathTest);
+ s.dispatchEvent(ev);
+ pathTest.parentNode.removeChild(pathTest);
+ sr = s = null;
+ }
+ pathTest = null;
+
+ var target = {
+ shadow: function(inEl) {
+ if (inEl) {
+ return inEl.shadowRoot || inEl.webkitShadowRoot;
+ }
+ },
+ canTarget: function(shadow) {
+ return shadow && Boolean(shadow.elementFromPoint);
+ },
+ targetingShadow: function(inEl) {
+ var s = this.shadow(inEl);
+ if (this.canTarget(s)) {
+ return s;
+ }
+ },
+ olderShadow: function(shadow) {
+ var os = shadow.olderShadowRoot;
+ if (!os) {
+ var se = shadow.querySelector('shadow');
+ if (se) {
+ os = se.olderShadowRoot;
+ }
+ }
+ return os;
+ },
+ allShadows: function(element) {
+ var shadows = [], s = this.shadow(element);
+ while(s) {
+ shadows.push(s);
+ s = this.olderShadow(s);
+ }
+ return shadows;
+ },
+ searchRoot: function(inRoot, x, y) {
+ var t, st, sr, os;
+ if (inRoot) {
+ t = inRoot.elementFromPoint(x, y);
+ if (t) {
+ // found element, check if it has a ShadowRoot
+ sr = this.targetingShadow(t);
+ } else if (inRoot !== document) {
+ // check for sibling roots
+ sr = this.olderShadow(inRoot);
+ }
+ // search other roots, fall back to light dom element
+ return this.searchRoot(sr, x, y) || t;
+ }
+ },
+ owner: function(element) {
+ if (!element) {
+ return document;
+ }
+ var s = element;
+ // walk up until you hit the shadow root or document
+ while (s.parentNode) {
+ s = s.parentNode;
+ }
+ // the owner element is expected to be a Document or ShadowRoot
+ if (s.nodeType != Node.DOCUMENT_NODE && s.nodeType != Node.DOCUMENT_FRAGMENT_NODE) {
+ s = document;
+ }
+ return s;
+ },
+ findTarget: function(inEvent) {
+ if (HAS_FULL_PATH && inEvent.path && inEvent.path.length) {
+ return inEvent.path[0];
+ }
+ var x = inEvent.clientX, y = inEvent.clientY;
+ // if the listener is in the shadow root, it is much faster to start there
+ var s = this.owner(inEvent.target);
+ // if x, y is not in this root, fall back to document search
+ if (!s.elementFromPoint(x, y)) {
+ s = document;
+ }
+ return this.searchRoot(s, x, y);
+ },
+ findTouchAction: function(inEvent) {
+ var n;
+ if (HAS_FULL_PATH && inEvent.path && inEvent.path.length) {
+ var path = inEvent.path;
+ for (var i = 0; i < path.length; i++) {
+ n = path[i];
+ if (n.nodeType === Node.ELEMENT_NODE && n.hasAttribute('touch-action')) {
+ return n.getAttribute('touch-action');
+ }
+ }
+ } else {
+ n = inEvent.target;
+ while(n) {
+ if (n.nodeType === Node.ELEMENT_NODE && n.hasAttribute('touch-action')) {
+ return n.getAttribute('touch-action');
+ }
+ n = n.parentNode || n.host;
+ }
+ }
+ // auto is default
+ return "auto";
+ },
+ LCA: function(a, b) {
+ if (a === b) {
+ return a;
+ }
+ if (a && !b) {
+ return a;
+ }
+ if (b && !a) {
+ return b;
+ }
+ if (!b && !a) {
+ return document;
+ }
+ // fast case, a is a direct descendant of b or vice versa
+ if (a.contains && a.contains(b)) {
+ return a;
+ }
+ if (b.contains && b.contains(a)) {
+ return b;
+ }
+ var adepth = this.depth(a);
+ var bdepth = this.depth(b);
+ var d = adepth - bdepth;
+ if (d >= 0) {
+ a = this.walk(a, d);
+ } else {
+ b = this.walk(b, -d);
+ }
+ while (a && b && a !== b) {
+ a = a.parentNode || a.host;
+ b = b.parentNode || b.host;
+ }
+ return a;
+ },
+ walk: function(n, u) {
+ for (var i = 0; n && (i < u); i++) {
+ n = n.parentNode || n.host;
+ }
+ return n;
+ },
+ depth: function(n) {
+ var d = 0;
+ while(n) {
+ d++;
+ n = n.parentNode || n.host;
+ }
+ return d;
+ },
+ deepContains: function(a, b) {
+ var common = this.LCA(a, b);
+ // if a is the common ancestor, it must "deeply" contain b
+ return common === a;
+ },
+ insideNode: function(node, x, y) {
+ var rect = node.getBoundingClientRect();
+ return (rect.left <= x) && (x <= rect.right) && (rect.top <= y) && (y <= rect.bottom);
+ },
+ path: function(event) {
+ var p;
+ if (HAS_FULL_PATH && event.path && event.path.length) {
+ p = event.path;
+ } else {
+ p = [];
+ var n = this.findTarget(event);
+ while (n) {
+ p.push(n);
+ n = n.parentNode || n.host;
+ }
+ }
+ return p;
+ }
+ };
+ scope.targetFinding = target;
+ /**
+ * Given an event, finds the "deepest" node that could have been the original target before ShadowDOM retargetting
+ *
+ * @param {Event} Event An event object with clientX and clientY properties
+ * @return {Element} The probable event origninator
+ */
+ scope.findTarget = target.findTarget.bind(target);
+ /**
+ * Determines if the "container" node deeply contains the "containee" node, including situations where the "containee" is contained by one or more ShadowDOM
+ * roots.
+ *
+ * @param {Node} container
+ * @param {Node} containee
+ * @return {Boolean}
+ */
+ scope.deepContains = target.deepContains.bind(target);
+
+ /**
+ * Determines if the x/y position is inside the given node.
+ *
+ * Example:
+ *
+ * function upHandler(event) {
+ * var innode = PolymerGestures.insideNode(event.target, event.clientX, event.clientY);
+ * if (innode) {
+ * // wait for tap?
+ * } else {
+ * // tap will never happen
+ * }
+ * }
+ *
+ * @param {Node} node
+ * @param {Number} x Screen X position
+ * @param {Number} y screen Y position
+ * @return {Boolean}
+ */
+ scope.insideNode = target.insideNode;
+
+})(window.PolymerGestures);
+
+(function() {
+ function shadowSelector(v) {
+ return 'html /deep/ ' + selector(v);
+ }
+ function selector(v) {
+ return '[touch-action="' + v + '"]';
+ }
+ function rule(v) {
+ return '{ -ms-touch-action: ' + v + '; touch-action: ' + v + ';}';
+ }
+ var attrib2css = [
+ 'none',
+ 'auto',
+ 'pan-x',
+ 'pan-y',
+ {
+ rule: 'pan-x pan-y',
+ selectors: [
+ 'pan-x pan-y',
+ 'pan-y pan-x'
+ ]
+ },
+ 'manipulation'
+ ];
+ var styles = '';
+ // only install stylesheet if the browser has touch action support
+ var hasTouchAction = typeof document.head.style.touchAction === 'string';
+ // only add shadow selectors if shadowdom is supported
+ var hasShadowRoot = !window.ShadowDOMPolyfill && document.head.createShadowRoot;
+
+ if (hasTouchAction) {
+ attrib2css.forEach(function(r) {
+ if (String(r) === r) {
+ styles += selector(r) + rule(r) + '\n';
+ if (hasShadowRoot) {
+ styles += shadowSelector(r) + rule(r) + '\n';
+ }
+ } else {
+ styles += r.selectors.map(selector) + rule(r.rule) + '\n';
+ if (hasShadowRoot) {
+ styles += r.selectors.map(shadowSelector) + rule(r.rule) + '\n';
+ }
+ }
+ });
+
+ var el = document.createElement('style');
+ el.textContent = styles;
+ document.head.appendChild(el);
+ }
+})();
+
+/**
+ * This is the constructor for new PointerEvents.
+ *
+ * New Pointer Events must be given a type, and an optional dictionary of
+ * initialization properties.
+ *
+ * Due to certain platform requirements, events returned from the constructor
+ * identify as MouseEvents.
+ *
+ * @constructor
+ * @param {String} inType The type of the event to create.
+ * @param {Object} [inDict] An optional dictionary of initial event properties.
+ * @return {Event} A new PointerEvent of type `inType` and initialized with properties from `inDict`.
+ */
+(function(scope) {
+
+ var MOUSE_PROPS = [
+ 'bubbles',
+ 'cancelable',
+ 'view',
+ 'detail',
+ 'screenX',
+ 'screenY',
+ 'clientX',
+ 'clientY',
+ 'ctrlKey',
+ 'altKey',
+ 'shiftKey',
+ 'metaKey',
+ 'button',
+ 'relatedTarget',
+ 'pageX',
+ 'pageY'
+ ];
+
+ var MOUSE_DEFAULTS = [
+ false,
+ false,
+ null,
+ null,
+ 0,
+ 0,
+ 0,
+ 0,
+ false,
+ false,
+ false,
+ false,
+ 0,
+ null,
+ 0,
+ 0
+ ];
+
+ var NOP_FACTORY = function(){ return function(){}; };
+
+ var eventFactory = {
+ // TODO(dfreedm): this is overridden by tap recognizer, needs review
+ preventTap: NOP_FACTORY,
+ makeBaseEvent: function(inType, inDict) {
+ var e = document.createEvent('Event');
+ e.initEvent(inType, inDict.bubbles || false, inDict.cancelable || false);
+ e.preventTap = eventFactory.preventTap(e);
+ return e;
+ },
+ makeGestureEvent: function(inType, inDict) {
+ inDict = inDict || Object.create(null);
+
+ var e = this.makeBaseEvent(inType, inDict);
+ for (var i = 0, keys = Object.keys(inDict), k; i < keys.length; i++) {
+ k = keys[i];
+ e[k] = inDict[k];
+ }
+ return e;
+ },
+ makePointerEvent: function(inType, inDict) {
+ inDict = inDict || Object.create(null);
+
+ var e = this.makeBaseEvent(inType, inDict);
+ // define inherited MouseEvent properties
+ for(var i = 0, p; i < MOUSE_PROPS.length; i++) {
+ p = MOUSE_PROPS[i];
+ e[p] = inDict[p] || MOUSE_DEFAULTS[i];
+ }
+ e.buttons = inDict.buttons || 0;
+
+ // Spec requires that pointers without pressure specified use 0.5 for down
+ // state and 0 for up state.
+ var pressure = 0;
+ if (inDict.pressure) {
+ pressure = inDict.pressure;
+ } else {
+ pressure = e.buttons ? 0.5 : 0;
+ }
+
+ // add x/y properties aliased to clientX/Y
+ e.x = e.clientX;
+ e.y = e.clientY;
+
+ // define the properties of the PointerEvent interface
+ e.pointerId = inDict.pointerId || 0;
+ e.width = inDict.width || 0;
+ e.height = inDict.height || 0;
+ e.pressure = pressure;
+ e.tiltX = inDict.tiltX || 0;
+ e.tiltY = inDict.tiltY || 0;
+ e.pointerType = inDict.pointerType || '';
+ e.hwTimestamp = inDict.hwTimestamp || 0;
+ e.isPrimary = inDict.isPrimary || false;
+ e._source = inDict._source || '';
+ return e;
+ }
+ };
+
+ scope.eventFactory = eventFactory;
+})(window.PolymerGestures);
+
+/**
+ * This module implements an map of pointer states
+ */
+(function(scope) {
+ var USE_MAP = window.Map && window.Map.prototype.forEach;
+ var POINTERS_FN = function(){ return this.size; };
+ function PointerMap() {
+ if (USE_MAP) {
+ var m = new Map();
+ m.pointers = POINTERS_FN;
+ return m;
+ } else {
+ this.keys = [];
+ this.values = [];
+ }
+ }
+
+ PointerMap.prototype = {
+ set: function(inId, inEvent) {
+ var i = this.keys.indexOf(inId);
+ if (i > -1) {
+ this.values[i] = inEvent;
+ } else {
+ this.keys.push(inId);
+ this.values.push(inEvent);
+ }
+ },
+ has: function(inId) {
+ return this.keys.indexOf(inId) > -1;
+ },
+ 'delete': function(inId) {
+ var i = this.keys.indexOf(inId);
+ if (i > -1) {
+ this.keys.splice(i, 1);
+ this.values.splice(i, 1);
+ }
+ },
+ get: function(inId) {
+ var i = this.keys.indexOf(inId);
+ return this.values[i];
+ },
+ clear: function() {
+ this.keys.length = 0;
+ this.values.length = 0;
+ },
+ // return value, key, map
+ forEach: function(callback, thisArg) {
+ this.values.forEach(function(v, i) {
+ callback.call(thisArg, v, this.keys[i], this);
+ }, this);
+ },
+ pointers: function() {
+ return this.keys.length;
+ }
+ };
+
+ scope.PointerMap = PointerMap;
+})(window.PolymerGestures);
+
+(function(scope) {
+ var CLONE_PROPS = [
+ // MouseEvent
+ 'bubbles',
+ 'cancelable',
+ 'view',
+ 'detail',
+ 'screenX',
+ 'screenY',
+ 'clientX',
+ 'clientY',
+ 'ctrlKey',
+ 'altKey',
+ 'shiftKey',
+ 'metaKey',
+ 'button',
+ 'relatedTarget',
+ // DOM Level 3
+ 'buttons',
+ // PointerEvent
+ 'pointerId',
+ 'width',
+ 'height',
+ 'pressure',
+ 'tiltX',
+ 'tiltY',
+ 'pointerType',
+ 'hwTimestamp',
+ 'isPrimary',
+ // event instance
+ 'type',
+ 'target',
+ 'currentTarget',
+ 'which',
+ 'pageX',
+ 'pageY',
+ 'timeStamp',
+ // gesture addons
+ 'preventTap',
+ 'tapPrevented',
+ '_source'
+ ];
+
+ var CLONE_DEFAULTS = [
+ // MouseEvent
+ false,
+ false,
+ null,
+ null,
+ 0,
+ 0,
+ 0,
+ 0,
+ false,
+ false,
+ false,
+ false,
+ 0,
+ null,
+ // DOM Level 3
+ 0,
+ // PointerEvent
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ '',
+ 0,
+ false,
+ // event instance
+ '',
+ null,
+ null,
+ 0,
+ 0,
+ 0,
+ 0,
+ function(){},
+ false
+ ];
+
+ var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined');
+
+ var eventFactory = scope.eventFactory;
+
+ // set of recognizers to run for the currently handled event
+ var currentGestures;
+
+ /**
+ * This module is for normalizing events. Mouse and Touch events will be
+ * collected here, and fire PointerEvents that have the same semantics, no
+ * matter the source.
+ * Events fired:
+ * - pointerdown: a pointing is added
+ * - pointerup: a pointer is removed
+ * - pointermove: a pointer is moved
+ * - pointerover: a pointer crosses into an element
+ * - pointerout: a pointer leaves an element
+ * - pointercancel: a pointer will no longer generate events
+ */
+ var dispatcher = {
+ IS_IOS: false,
+ pointermap: new scope.PointerMap(),
+ requiredGestures: new scope.PointerMap(),
+ eventMap: Object.create(null),
+ // Scope objects for native events.
+ // This exists for ease of testing.
+ eventSources: Object.create(null),
+ eventSourceList: [],
+ gestures: [],
+ // map gesture event -> {listeners: int, index: gestures[int]}
+ dependencyMap: {
+ // make sure down and up are in the map to trigger "register"
+ down: {listeners: 0, index: -1},
+ up: {listeners: 0, index: -1}
+ },
+ gestureQueue: [],
+ /**
+ * Add a new event source that will generate pointer events.
+ *
+ * `inSource` must contain an array of event names named `events`, and
+ * functions with the names specified in the `events` array.
+ * @param {string} name A name for the event source
+ * @param {Object} source A new source of platform events.
+ */
+ registerSource: function(name, source) {
+ var s = source;
+ var newEvents = s.events;
+ if (newEvents) {
+ newEvents.forEach(function(e) {
+ if (s[e]) {
+ this.eventMap[e] = s[e].bind(s);
+ }
+ }, this);
+ this.eventSources[name] = s;
+ this.eventSourceList.push(s);
+ }
+ },
+ registerGesture: function(name, source) {
+ var obj = Object.create(null);
+ obj.listeners = 0;
+ obj.index = this.gestures.length;
+ for (var i = 0, g; i < source.exposes.length; i++) {
+ g = source.exposes[i].toLowerCase();
+ this.dependencyMap[g] = obj;
+ }
+ this.gestures.push(source);
+ },
+ register: function(element, initial) {
+ var l = this.eventSourceList.length;
+ for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) {
+ // call eventsource register
+ es.register.call(es, element, initial);
+ }
+ },
+ unregister: function(element) {
+ var l = this.eventSourceList.length;
+ for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) {
+ // call eventsource register
+ es.unregister.call(es, element);
+ }
+ },
+ // EVENTS
+ down: function(inEvent) {
+ this.requiredGestures.set(inEvent.pointerId, currentGestures);
+ this.fireEvent('down', inEvent);
+ },
+ move: function(inEvent) {
+ // pipe move events into gesture queue directly
+ inEvent.type = 'move';
+ this.fillGestureQueue(inEvent);
+ },
+ up: function(inEvent) {
+ this.fireEvent('up', inEvent);
+ this.requiredGestures.delete(inEvent.pointerId);
+ },
+ cancel: function(inEvent) {
+ inEvent.tapPrevented = true;
+ this.fireEvent('up', inEvent);
+ this.requiredGestures.delete(inEvent.pointerId);
+ },
+ addGestureDependency: function(node, currentGestures) {
+ var gesturesWanted = node._pgEvents;
+ if (gesturesWanted && currentGestures) {
+ var gk = Object.keys(gesturesWanted);
+ for (var i = 0, r, ri, g; i < gk.length; i++) {
+ // gesture
+ g = gk[i];
+ if (gesturesWanted[g] > 0) {
+ // lookup gesture recognizer
+ r = this.dependencyMap[g];
+ // recognizer index
+ ri = r ? r.index : -1;
+ currentGestures[ri] = true;
+ }
+ }
+ }
+ },
+ // LISTENER LOGIC
+ eventHandler: function(inEvent) {
+ // This is used to prevent multiple dispatch of events from
+ // platform events. This can happen when two elements in different scopes
+ // are set up to create pointer events, which is relevant to Shadow DOM.
+
+ var type = inEvent.type;
+
+ // only generate the list of desired events on "down"
+ if (type === 'touchstart' || type === 'mousedown' || type === 'pointerdown' || type === 'MSPointerDown') {
+ if (!inEvent._handledByPG) {
+ currentGestures = {};
+ }
+
+ // in IOS mode, there is only a listener on the document, so this is not re-entrant
+ if (this.IS_IOS) {
+ var ev = inEvent;
+ if (type === 'touchstart') {
+ var ct = inEvent.changedTouches[0];
+ // set up a fake event to give to the path builder
+ ev = {target: inEvent.target, clientX: ct.clientX, clientY: ct.clientY, path: inEvent.path};
+ }
+ // use event path if available, otherwise build a path from target finding
+ var nodes = inEvent.path || scope.targetFinding.path(ev);
+ for (var i = 0, n; i < nodes.length; i++) {
+ n = nodes[i];
+ this.addGestureDependency(n, currentGestures);
+ }
+ } else {
+ this.addGestureDependency(inEvent.currentTarget, currentGestures);
+ }
+ }
+
+ if (inEvent._handledByPG) {
+ return;
+ }
+ var fn = this.eventMap && this.eventMap[type];
+ if (fn) {
+ fn(inEvent);
+ }
+ inEvent._handledByPG = true;
+ },
+ // set up event listeners
+ listen: function(target, events) {
+ for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) {
+ this.addEvent(target, e);
+ }
+ },
+ // remove event listeners
+ unlisten: function(target, events) {
+ for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) {
+ this.removeEvent(target, e);
+ }
+ },
+ addEvent: function(target, eventName) {
+ target.addEventListener(eventName, this.boundHandler);
+ },
+ removeEvent: function(target, eventName) {
+ target.removeEventListener(eventName, this.boundHandler);
+ },
+ // EVENT CREATION AND TRACKING
+ /**
+ * Creates a new Event of type `inType`, based on the information in
+ * `inEvent`.
+ *
+ * @param {string} inType A string representing the type of event to create
+ * @param {Event} inEvent A platform event with a target
+ * @return {Event} A PointerEvent of type `inType`
+ */
+ makeEvent: function(inType, inEvent) {
+ var e = eventFactory.makePointerEvent(inType, inEvent);
+ e.preventDefault = inEvent.preventDefault;
+ e.tapPrevented = inEvent.tapPrevented;
+ e._target = e._target || inEvent.target;
+ return e;
+ },
+ // make and dispatch an event in one call
+ fireEvent: function(inType, inEvent) {
+ var e = this.makeEvent(inType, inEvent);
+ return this.dispatchEvent(e);
+ },
+ /**
+ * Returns a snapshot of inEvent, with writable properties.
+ *
+ * @param {Event} inEvent An event that contains properties to copy.
+ * @return {Object} An object containing shallow copies of `inEvent`'s
+ * properties.
+ */
+ cloneEvent: function(inEvent) {
+ var eventCopy = Object.create(null), p;
+ for (var i = 0; i < CLONE_PROPS.length; i++) {
+ p = CLONE_PROPS[i];
+ eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i];
+ // Work around SVGInstanceElement shadow tree
+ // Return the <use> element that is represented by the instance for Safari, Chrome, IE.
+ // This is the behavior implemented by Firefox.
+ if (p === 'target' || p === 'relatedTarget') {
+ if (HAS_SVG_INSTANCE && eventCopy[p] instanceof SVGElementInstance) {
+ eventCopy[p] = eventCopy[p].correspondingUseElement;
+ }
+ }
+ }
+ // keep the semantics of preventDefault
+ eventCopy.preventDefault = function() {
+ inEvent.preventDefault();
+ };
+ return eventCopy;
+ },
+ /**
+ * Dispatches the event to its target.
+ *
+ * @param {Event} inEvent The event to be dispatched.
+ * @return {Boolean} True if an event handler returns true, false otherwise.
+ */
+ dispatchEvent: function(inEvent) {
+ var t = inEvent._target;
+ if (t) {
+ t.dispatchEvent(inEvent);
+ // clone the event for the gesture system to process
+ // clone after dispatch to pick up gesture prevention code
+ var clone = this.cloneEvent(inEvent);
+ clone.target = t;
+ this.fillGestureQueue(clone);
+ }
+ },
+ gestureTrigger: function() {
+ // process the gesture queue
+ for (var i = 0, e, rg; i < this.gestureQueue.length; i++) {
+ e = this.gestureQueue[i];
+ rg = e._requiredGestures;
+ if (rg) {
+ for (var j = 0, g, fn; j < this.gestures.length; j++) {
+ // only run recognizer if an element in the source event's path is listening for those gestures
+ if (rg[j]) {
+ g = this.gestures[j];
+ fn = g[e.type];
+ if (fn) {
+ fn.call(g, e);
+ }
+ }
+ }
+ }
+ }
+ this.gestureQueue.length = 0;
+ },
+ fillGestureQueue: function(ev) {
+ // only trigger the gesture queue once
+ if (!this.gestureQueue.length) {
+ requestAnimationFrame(this.boundGestureTrigger);
+ }
+ ev._requiredGestures = this.requiredGestures.get(ev.pointerId);
+ this.gestureQueue.push(ev);
+ }
+ };
+ dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher);
+ dispatcher.boundGestureTrigger = dispatcher.gestureTrigger.bind(dispatcher);
+ scope.dispatcher = dispatcher;
+
+ /**
+ * Listen for `gesture` on `node` with the `handler` function
+ *
+ * If `handler` is the first listener for `gesture`, the underlying gesture recognizer is then enabled.
+ *
+ * @param {Element} node
+ * @param {string} gesture
+ * @return Boolean `gesture` is a valid gesture
+ */
+ scope.activateGesture = function(node, gesture) {
+ var g = gesture.toLowerCase();
+ var dep = dispatcher.dependencyMap[g];
+ if (dep) {
+ var recognizer = dispatcher.gestures[dep.index];
+ if (!node._pgListeners) {
+ dispatcher.register(node);
+ node._pgListeners = 0;
+ }
+ // TODO(dfreedm): re-evaluate bookkeeping to avoid using attributes
+ if (recognizer) {
+ var touchAction = recognizer.defaultActions && recognizer.defaultActions[g];
+ var actionNode;
+ switch(node.nodeType) {
+ case Node.ELEMENT_NODE:
+ actionNode = node;
+ break;
+ case Node.DOCUMENT_FRAGMENT_NODE:
+ actionNode = node.host;
+ break;
+ default:
+ actionNode = null;
+ break;
+ }
+ if (touchAction && actionNode && !actionNode.hasAttribute('touch-action')) {
+ actionNode.setAttribute('touch-action', touchAction);
+ }
+ }
+ if (!node._pgEvents) {
+ node._pgEvents = {};
+ }
+ node._pgEvents[g] = (node._pgEvents[g] || 0) + 1;
+ node._pgListeners++;
+ }
+ return Boolean(dep);
+ };
+
+ /**
+ *
+ * Listen for `gesture` from `node` with `handler` function.
+ *
+ * @param {Element} node
+ * @param {string} gesture
+ * @param {Function} handler
+ * @param {Boolean} capture
+ */
+ scope.addEventListener = function(node, gesture, handler, capture) {
+ if (handler) {
+ scope.activateGesture(node, gesture);
+ node.addEventListener(gesture, handler, capture);
+ }
+ };
+
+ /**
+ * Tears down the gesture configuration for `node`
+ *
+ * If `handler` is the last listener for `gesture`, the underlying gesture recognizer is disabled.
+ *
+ * @param {Element} node
+ * @param {string} gesture
+ * @return Boolean `gesture` is a valid gesture
+ */
+ scope.deactivateGesture = function(node, gesture) {
+ var g = gesture.toLowerCase();
+ var dep = dispatcher.dependencyMap[g];
+ if (dep) {
+ if (node._pgListeners > 0) {
+ node._pgListeners--;
+ }
+ if (node._pgListeners === 0) {
+ dispatcher.unregister(node);
+ }
+ if (node._pgEvents) {
+ if (node._pgEvents[g] > 0) {
+ node._pgEvents[g]--;
+ } else {
+ node._pgEvents[g] = 0;
+ }
+ }
+ }
+ return Boolean(dep);
+ };
+
+ /**
+ * Stop listening for `gesture` from `node` with `handler` function.
+ *
+ * @param {Element} node
+ * @param {string} gesture
+ * @param {Function} handler
+ * @param {Boolean} capture
+ */
+ scope.removeEventListener = function(node, gesture, handler, capture) {
+ if (handler) {
+ scope.deactivateGesture(node, gesture);
+ node.removeEventListener(gesture, handler, capture);
+ }
+ };
+})(window.PolymerGestures);
+
+(function (scope) {
+ var dispatcher = scope.dispatcher;
+ var pointermap = dispatcher.pointermap;
+ // radius around touchend that swallows mouse events
+ var DEDUP_DIST = 25;
+
+ var WHICH_TO_BUTTONS = [0, 1, 4, 2];
+
+ var CURRENT_BUTTONS = 0;
+ var HAS_BUTTONS = false;
+ try {
+ HAS_BUTTONS = new MouseEvent('test', {buttons: 1}).buttons === 1;
+ } catch (e) {}
+
+ // handler block for native mouse events
+ var mouseEvents = {
+ POINTER_ID: 1,
+ POINTER_TYPE: 'mouse',
+ events: [
+ 'mousedown',
+ 'mousemove',
+ 'mouseup'
+ ],
+ exposes: [
+ 'down',
+ 'up',
+ 'move'
+ ],
+ register: function(target) {
+ dispatcher.listen(target, this.events);
+ },
+ unregister: function(target) {
+ if (target === document) {
+ return;
+ }
+ dispatcher.unlisten(target, this.events);
+ },
+ lastTouches: [],
+ // collide with the global mouse listener
+ isEventSimulatedFromTouch: function(inEvent) {
+ var lts = this.lastTouches;
+ var x = inEvent.clientX, y = inEvent.clientY;
+ for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) {
+ // simulated mouse events will be swallowed near a primary touchend
+ var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
+ if (dx <= DEDUP_DIST && dy <= DEDUP_DIST) {
+ return true;
+ }
+ }
+ },
+ prepareEvent: function(inEvent) {
+ var e = dispatcher.cloneEvent(inEvent);
+ e.pointerId = this.POINTER_ID;
+ e.isPrimary = true;
+ e.pointerType = this.POINTER_TYPE;
+ e._source = 'mouse';
+ if (!HAS_BUTTONS) {
+ var type = inEvent.type;
+ var bit = WHICH_TO_BUTTONS[inEvent.which] || 0;
+ if (type === 'mousedown') {
+ CURRENT_BUTTONS |= bit;
+ } else if (type === 'mouseup') {
+ CURRENT_BUTTONS &= ~bit;
+ }
+ e.buttons = CURRENT_BUTTONS;
+ }
+ return e;
+ },
+ mousedown: function(inEvent) {
+ if (!this.isEventSimulatedFromTouch(inEvent)) {
+ var p = pointermap.has(this.POINTER_ID);
+ var e = this.prepareEvent(inEvent);
+ e.target = scope.findTarget(inEvent);
+ pointermap.set(this.POINTER_ID, e.target);
+ dispatcher.down(e);
+ }
+ },
+ mousemove: function(inEvent) {
+ if (!this.isEventSimulatedFromTouch(inEvent)) {
+ var target = pointermap.get(this.POINTER_ID);
+ if (target) {
+ var e = this.prepareEvent(inEvent);
+ e.target = target;
+ // handle case where we missed a mouseup
+ if ((HAS_BUTTONS ? e.buttons : e.which) === 0) {
+ if (!HAS_BUTTONS) {
+ CURRENT_BUTTONS = e.buttons = 0;
+ }
+ dispatcher.cancel(e);
+ this.cleanupMouse(e.buttons);
+ } else {
+ dispatcher.move(e);
+ }
+ }
+ }
+ },
+ mouseup: function(inEvent) {
+ if (!this.isEventSimulatedFromTouch(inEvent)) {
+ var e = this.prepareEvent(inEvent);
+ e.relatedTarget = scope.findTarget(inEvent);
+ e.target = pointermap.get(this.POINTER_ID);
+ dispatcher.up(e);
+ this.cleanupMouse(e.buttons);
+ }
+ },
+ cleanupMouse: function(buttons) {
+ if (buttons === 0) {
+ pointermap.delete(this.POINTER_ID);
+ }
+ }
+ };
+
+ scope.mouseEvents = mouseEvents;
+})(window.PolymerGestures);
+
+(function(scope) {
+ var dispatcher = scope.dispatcher;
+ var allShadows = scope.targetFinding.allShadows.bind(scope.targetFinding);
+ var pointermap = dispatcher.pointermap;
+ var touchMap = Array.prototype.map.call.bind(Array.prototype.map);
+ // This should be long enough to ignore compat mouse events made by touch
+ var DEDUP_TIMEOUT = 2500;
+ var DEDUP_DIST = 25;
+ var CLICK_COUNT_TIMEOUT = 200;
+ var HYSTERESIS = 20;
+ var ATTRIB = 'touch-action';
+ // TODO(dfreedm): disable until http://crbug.com/399765 is resolved
+ // var HAS_TOUCH_ACTION = ATTRIB in document.head.style;
+ var HAS_TOUCH_ACTION = false;
+
+ // handler block for native touch events
+ var touchEvents = {
+ IS_IOS: false,
+ events: [
+ 'touchstart',
+ 'touchmove',
+ 'touchend',
+ 'touchcancel'
+ ],
+ exposes: [
+ 'down',
+ 'up',
+ 'move'
+ ],
+ register: function(target, initial) {
+ if (this.IS_IOS ? initial : !initial) {
+ dispatcher.listen(target, this.events);
+ }
+ },
+ unregister: function(target) {
+ if (!this.IS_IOS) {
+ dispatcher.unlisten(target, this.events);
+ }
+ },
+ scrollTypes: {
+ EMITTER: 'none',
+ XSCROLLER: 'pan-x',
+ YSCROLLER: 'pan-y',
+ },
+ touchActionToScrollType: function(touchAction) {
+ var t = touchAction;
+ var st = this.scrollTypes;
+ if (t === st.EMITTER) {
+ return 'none';
+ } else if (t === st.XSCROLLER) {
+ return 'X';
+ } else if (t === st.YSCROLLER) {
+ return 'Y';
+ } else {
+ return 'XY';
+ }
+ },
+ POINTER_TYPE: 'touch',
+ firstTouch: null,
+ isPrimaryTouch: function(inTouch) {
+ return this.firstTouch === inTouch.identifier;
+ },
+ setPrimaryTouch: function(inTouch) {
+ // set primary touch if there no pointers, or the only pointer is the mouse
+ if (pointermap.pointers() === 0 || (pointermap.pointers() === 1 && pointermap.has(1))) {
+ this.firstTouch = inTouch.identifier;
+ this.firstXY = {X: inTouch.clientX, Y: inTouch.clientY};
+ this.firstTarget = inTouch.target;
+ this.scrolling = null;
+ this.cancelResetClickCount();
+ }
+ },
+ removePrimaryPointer: function(inPointer) {
+ if (inPointer.isPrimary) {
+ this.firstTouch = null;
+ this.firstXY = null;
+ this.resetClickCount();
+ }
+ },
+ clickCount: 0,
+ resetId: null,
+ resetClickCount: function() {
+ var fn = function() {
+ this.clickCount = 0;
+ this.resetId = null;
+ }.bind(this);
+ this.resetId = setTimeout(fn, CLICK_COUNT_TIMEOUT);
+ },
+ cancelResetClickCount: function() {
+ if (this.resetId) {
+ clearTimeout(this.resetId);
+ }
+ },
+ typeToButtons: function(type) {
+ var ret = 0;
+ if (type === 'touchstart' || type === 'touchmove') {
+ ret = 1;
+ }
+ return ret;
+ },
+ findTarget: function(touch, id) {
+ if (this.currentTouchEvent.type === 'touchstart') {
+ if (this.isPrimaryTouch(touch)) {
+ var fastPath = {
+ clientX: touch.clientX,
+ clientY: touch.clientY,
+ path: this.currentTouchEvent.path,
+ target: this.currentTouchEvent.target
+ };
+ return scope.findTarget(fastPath);
+ } else {
+ return scope.findTarget(touch);
+ }
+ }
+ // reuse target we found in touchstart
+ return pointermap.get(id);
+ },
+ touchToPointer: function(inTouch) {
+ var cte = this.currentTouchEvent;
+ var e = dispatcher.cloneEvent(inTouch);
+ // Spec specifies that pointerId 1 is reserved for Mouse.
+ // Touch identifiers can start at 0.
+ // Add 2 to the touch identifier for compatibility.
+ var id = e.pointerId = inTouch.identifier + 2;
+ e.target = this.findTarget(inTouch, id);
+ e.bubbles = true;
+ e.cancelable = true;
+ e.detail = this.clickCount;
+ e.buttons = this.typeToButtons(cte.type);
+ e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0;
+ e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0;
+ e.pressure = inTouch.webkitForce || inTouch.force || 0.5;
+ e.isPrimary = this.isPrimaryTouch(inTouch);
+ e.pointerType = this.POINTER_TYPE;
+ e._source = 'touch';
+ // forward touch preventDefaults
+ var self = this;
+ e.preventDefault = function() {
+ self.scrolling = false;
+ self.firstXY = null;
+ cte.preventDefault();
+ };
+ return e;
+ },
+ processTouches: function(inEvent, inFunction) {
+ var tl = inEvent.changedTouches;
+ this.currentTouchEvent = inEvent;
+ for (var i = 0, t, p; i < tl.length; i++) {
+ t = tl[i];
+ p = this.touchToPointer(t);
+ if (inEvent.type === 'touchstart') {
+ pointermap.set(p.pointerId, p.target);
+ }
+ if (pointermap.has(p.pointerId)) {
+ inFunction.call(this, p);
+ }
+ if (inEvent.type === 'touchend' || inEvent._cancel) {
+ this.cleanUpPointer(p);
+ }
+ }
+ },
+ // For single axis scrollers, determines whether the element should emit
+ // pointer events or behave as a scroller
+ shouldScroll: function(inEvent) {
+ if (this.firstXY) {
+ var ret;
+ var touchAction = scope.targetFinding.findTouchAction(inEvent);
+ var scrollAxis = this.touchActionToScrollType(touchAction);
+ if (scrollAxis === 'none') {
+ // this element is a touch-action: none, should never scroll
+ ret = false;
+ } else if (scrollAxis === 'XY') {
+ // this element should always scroll
+ ret = true;
+ } else {
+ var t = inEvent.changedTouches[0];
+ // check the intended scroll axis, and other axis
+ var a = scrollAxis;
+ var oa = scrollAxis === 'Y' ? 'X' : 'Y';
+ var da = Math.abs(t['client' + a] - this.firstXY[a]);
+ var doa = Math.abs(t['client' + oa] - this.firstXY[oa]);
+ // if delta in the scroll axis > delta other axis, scroll instead of
+ // making events
+ ret = da >= doa;
+ }
+ return ret;
+ }
+ },
+ findTouch: function(inTL, inId) {
+ for (var i = 0, l = inTL.length, t; i < l && (t = inTL[i]); i++) {
+ if (t.identifier === inId) {
+ return true;
+ }
+ }
+ },
+ // In some instances, a touchstart can happen without a touchend. This
+ // leaves the pointermap in a broken state.
+ // Therefore, on every touchstart, we remove the touches that did not fire a
+ // touchend event.
+ // To keep state globally consistent, we fire a
+ // pointercancel for this "abandoned" touch
+ vacuumTouches: function(inEvent) {
+ var tl = inEvent.touches;
+ // pointermap.pointers() should be < tl.length here, as the touchstart has not
+ // been processed yet.
+ if (pointermap.pointers() >= tl.length) {
+ var d = [];
+ pointermap.forEach(function(value, key) {
+ // Never remove pointerId == 1, which is mouse.
+ // Touch identifiers are 2 smaller than their pointerId, which is the
+ // index in pointermap.
+ if (key !== 1 && !this.findTouch(tl, key - 2)) {
+ var p = value;
+ d.push(p);
+ }
+ }, this);
+ d.forEach(function(p) {
+ this.cancel(p);
+ pointermap.delete(p.pointerId);
+ });
+ }
+ },
+ touchstart: function(inEvent) {
+ this.vacuumTouches(inEvent);
+ this.setPrimaryTouch(inEvent.changedTouches[0]);
+ this.dedupSynthMouse(inEvent);
+ if (!this.scrolling) {
+ this.clickCount++;
+ this.processTouches(inEvent, this.down);
+ }
+ },
+ down: function(inPointer) {
+ dispatcher.down(inPointer);
+ },
+ touchmove: function(inEvent) {
+ if (HAS_TOUCH_ACTION) {
+ // touchevent.cancelable == false is sent when the page is scrolling under native Touch Action in Chrome 36
+ // https://groups.google.com/a/chromium.org/d/msg/input-dev/wHnyukcYBcA/b9kmtwM1jJQJ
+ if (inEvent.cancelable) {
+ this.processTouches(inEvent, this.move);
+ }
+ } else {
+ if (!this.scrolling) {
+ if (this.scrolling === null && this.shouldScroll(inEvent)) {
+ this.scrolling = true;
+ } else {
+ this.scrolling = false;
+ inEvent.preventDefault();
+ this.processTouches(inEvent, this.move);
+ }
+ } else if (this.firstXY) {
+ var t = inEvent.changedTouches[0];
+ var dx = t.clientX - this.firstXY.X;
+ var dy = t.clientY - this.firstXY.Y;
+ var dd = Math.sqrt(dx * dx + dy * dy);
+ if (dd >= HYSTERESIS) {
+ this.touchcancel(inEvent);
+ this.scrolling = true;
+ this.firstXY = null;
+ }
+ }
+ }
+ },
+ move: function(inPointer) {
+ dispatcher.move(inPointer);
+ },
+ touchend: function(inEvent) {
+ this.dedupSynthMouse(inEvent);
+ this.processTouches(inEvent, this.up);
+ },
+ up: function(inPointer) {
+ inPointer.relatedTarget = scope.findTarget(inPointer);
+ dispatcher.up(inPointer);
+ },
+ cancel: function(inPointer) {
+ dispatcher.cancel(inPointer);
+ },
+ touchcancel: function(inEvent) {
+ inEvent._cancel = true;
+ this.processTouches(inEvent, this.cancel);
+ },
+ cleanUpPointer: function(inPointer) {
+ pointermap['delete'](inPointer.pointerId);
+ this.removePrimaryPointer(inPointer);
+ },
+ // prevent synth mouse events from creating pointer events
+ dedupSynthMouse: function(inEvent) {
+ var lts = scope.mouseEvents.lastTouches;
+ var t = inEvent.changedTouches[0];
+ // only the primary finger will synth mouse events
+ if (this.isPrimaryTouch(t)) {
+ // remember x/y of last touch
+ var lt = {x: t.clientX, y: t.clientY};
+ lts.push(lt);
+ var fn = (function(lts, lt){
+ var i = lts.indexOf(lt);
+ if (i > -1) {
+ lts.splice(i, 1);
+ }
+ }).bind(null, lts, lt);
+ setTimeout(fn, DEDUP_TIMEOUT);
+ }
+ }
+ };
+
+ // prevent "ghost clicks" that come from elements that were removed in a touch handler
+ var STOP_PROP_FN = Event.prototype.stopImmediatePropagation || Event.prototype.stopPropagation;
+ document.addEventListener('click', function(ev) {
+ var x = ev.clientX, y = ev.clientY;
+ // check if a click is within DEDUP_DIST px radius of the touchstart
+ var closeTo = function(touch) {
+ var dx = Math.abs(x - touch.x), dy = Math.abs(y - touch.y);
+ return (dx <= DEDUP_DIST && dy <= DEDUP_DIST);
+ };
+ // if click coordinates are close to touch coordinates, assume the click came from a touch
+ var wasTouched = scope.mouseEvents.lastTouches.some(closeTo);
+ // if the click came from touch, and the touchstart target is not in the path of the click event,
+ // then the touchstart target was probably removed, and the click should be "busted"
+ var path = scope.targetFinding.path(ev);
+ if (wasTouched) {
+ for (var i = 0; i < path.length; i++) {
+ if (path[i] === touchEvents.firstTarget) {
+ return;
+ }
+ }
+ ev.preventDefault();
+ STOP_PROP_FN.call(ev);
+ }
+ }, true);
+
+ scope.touchEvents = touchEvents;
+})(window.PolymerGestures);
+
+(function(scope) {
+ var dispatcher = scope.dispatcher;
+ var pointermap = dispatcher.pointermap;
+ var HAS_BITMAP_TYPE = window.MSPointerEvent && typeof window.MSPointerEvent.MSPOINTER_TYPE_MOUSE === 'number';
+ var msEvents = {
+ events: [
+ 'MSPointerDown',
+ 'MSPointerMove',
+ 'MSPointerUp',
+ 'MSPointerCancel',
+ ],
+ register: function(target) {
+ dispatcher.listen(target, this.events);
+ },
+ unregister: function(target) {
+ if (target === document) {
+ return;
+ }
+ dispatcher.unlisten(target, this.events);
+ },
+ POINTER_TYPES: [
+ '',
+ 'unavailable',
+ 'touch',
+ 'pen',
+ 'mouse'
+ ],
+ prepareEvent: function(inEvent) {
+ var e = inEvent;
+ e = dispatcher.cloneEvent(inEvent);
+ if (HAS_BITMAP_TYPE) {
+ e.pointerType = this.POINTER_TYPES[inEvent.pointerType];
+ }
+ e._source = 'ms';
+ return e;
+ },
+ cleanup: function(id) {
+ pointermap['delete'](id);
+ },
+ MSPointerDown: function(inEvent) {
+ var e = this.prepareEvent(inEvent);
+ e.target = scope.findTarget(inEvent);
+ pointermap.set(inEvent.pointerId, e.target);
+ dispatcher.down(e);
+ },
+ MSPointerMove: function(inEvent) {
+ var target = pointermap.get(inEvent.pointerId);
+ if (target) {
+ var e = this.prepareEvent(inEvent);
+ e.target = target;
+ dispatcher.move(e);
+ }
+ },
+ MSPointerUp: function(inEvent) {
+ var e = this.prepareEvent(inEvent);
+ e.relatedTarget = scope.findTarget(inEvent);
+ e.target = pointermap.get(e.pointerId);
+ dispatcher.up(e);
+ this.cleanup(inEvent.pointerId);
+ },
+ MSPointerCancel: function(inEvent) {
+ var e = this.prepareEvent(inEvent);
+ e.relatedTarget = scope.findTarget(inEvent);
+ e.target = pointermap.get(e.pointerId);
+ dispatcher.cancel(e);
+ this.cleanup(inEvent.pointerId);
+ }
+ };
+
+ scope.msEvents = msEvents;
+})(window.PolymerGestures);
+
+(function(scope) {
+ var dispatcher = scope.dispatcher;
+ var pointermap = dispatcher.pointermap;
+ var pointerEvents = {
+ events: [
+ 'pointerdown',
+ 'pointermove',
+ 'pointerup',
+ 'pointercancel'
+ ],
+ prepareEvent: function(inEvent) {
+ var e = dispatcher.cloneEvent(inEvent);
+ e._source = 'pointer';
+ return e;
+ },
+ register: function(target) {
+ dispatcher.listen(target, this.events);
+ },
+ unregister: function(target) {
+ if (target === document) {
+ return;
+ }
+ dispatcher.unlisten(target, this.events);
+ },
+ cleanup: function(id) {
+ pointermap['delete'](id);
+ },
+ pointerdown: function(inEvent) {
+ var e = this.prepareEvent(inEvent);
+ e.target = scope.findTarget(inEvent);
+ pointermap.set(e.pointerId, e.target);
+ dispatcher.down(e);
+ },
+ pointermove: function(inEvent) {
+ var target = pointermap.get(inEvent.pointerId);
+ if (target) {
+ var e = this.prepareEvent(inEvent);
+ e.target = target;
+ dispatcher.move(e);
+ }
+ },
+ pointerup: function(inEvent) {
+ var e = this.prepareEvent(inEvent);
+ e.relatedTarget = scope.findTarget(inEvent);
+ e.target = pointermap.get(e.pointerId);
+ dispatcher.up(e);
+ this.cleanup(inEvent.pointerId);
+ },
+ pointercancel: function(inEvent) {
+ var e = this.prepareEvent(inEvent);
+ e.relatedTarget = scope.findTarget(inEvent);
+ e.target = pointermap.get(e.pointerId);
+ dispatcher.cancel(e);
+ this.cleanup(inEvent.pointerId);
+ }
+ };
+
+ scope.pointerEvents = pointerEvents;
+})(window.PolymerGestures);
+
+/**
+ * This module contains the handlers for native platform events.
+ * From here, the dispatcher is called to create unified pointer events.
+ * Included are touch events (v1), mouse events, and MSPointerEvents.
+ */
+(function(scope) {
+
+ var dispatcher = scope.dispatcher;
+ var nav = window.navigator;
+
+ if (window.PointerEvent) {
+ dispatcher.registerSource('pointer', scope.pointerEvents);
+ } else if (nav.msPointerEnabled) {
+ dispatcher.registerSource('ms', scope.msEvents);
+ } else {
+ dispatcher.registerSource('mouse', scope.mouseEvents);
+ if (window.ontouchstart !== undefined) {
+ dispatcher.registerSource('touch', scope.touchEvents);
+ }
+ }
+
+ // Work around iOS bugs https://bugs.webkit.org/show_bug.cgi?id=135628 and https://bugs.webkit.org/show_bug.cgi?id=136506
+ var ua = navigator.userAgent;
+ var IS_IOS = ua.match(/iPad|iPhone|iPod/) && 'ontouchstart' in window;
+
+ dispatcher.IS_IOS = IS_IOS;
+ scope.touchEvents.IS_IOS = IS_IOS;
+
+ dispatcher.register(document, true);
+})(window.PolymerGestures);
+
+/**
+ * This event denotes the beginning of a series of tracking events.
+ *
+ * @module PointerGestures
+ * @submodule Events
+ * @class trackstart
+ */
+/**
+ * Pixels moved in the x direction since trackstart.
+ * @type Number
+ * @property dx
+ */
+/**
+ * Pixes moved in the y direction since trackstart.
+ * @type Number
+ * @property dy
+ */
+/**
+ * Pixels moved in the x direction since the last track.
+ * @type Number
+ * @property ddx
+ */
+/**
+ * Pixles moved in the y direction since the last track.
+ * @type Number
+ * @property ddy
+ */
+/**
+ * The clientX position of the track gesture.
+ * @type Number
+ * @property clientX
+ */
+/**
+ * The clientY position of the track gesture.
+ * @type Number
+ * @property clientY
+ */
+/**
+ * The pageX position of the track gesture.
+ * @type Number
+ * @property pageX
+ */
+/**
+ * The pageY position of the track gesture.
+ * @type Number
+ * @property pageY
+ */
+/**
+ * The screenX position of the track gesture.
+ * @type Number
+ * @property screenX
+ */
+/**
+ * The screenY position of the track gesture.
+ * @type Number
+ * @property screenY
+ */
+/**
+ * The last x axis direction of the pointer.
+ * @type Number
+ * @property xDirection
+ */
+/**
+ * The last y axis direction of the pointer.
+ * @type Number
+ * @property yDirection
+ */
+/**
+ * A shared object between all tracking events.
+ * @type Object
+ * @property trackInfo
+ */
+/**
+ * The element currently under the pointer.
+ * @type Element
+ * @property relatedTarget
+ */
+/**
+ * The type of pointer that make the track gesture.
+ * @type String
+ * @property pointerType
+ */
+/**
+ *
+ * This event fires for all pointer movement being tracked.
+ *
+ * @class track
+ * @extends trackstart
+ */
+/**
+ * This event fires when the pointer is no longer being tracked.
+ *
+ * @class trackend
+ * @extends trackstart
+ */
+
+ (function(scope) {
+ var dispatcher = scope.dispatcher;
+ var eventFactory = scope.eventFactory;
+ var pointermap = new scope.PointerMap();
+ var track = {
+ events: [
+ 'down',
+ 'move',
+ 'up',
+ ],
+ exposes: [
+ 'trackstart',
+ 'track',
+ 'trackx',
+ 'tracky',
+ 'trackend'
+ ],
+ defaultActions: {
+ 'track': 'none',
+ 'trackx': 'pan-y',
+ 'tracky': 'pan-x'
+ },
+ WIGGLE_THRESHOLD: 4,
+ clampDir: function(inDelta) {
+ return inDelta > 0 ? 1 : -1;
+ },
+ calcPositionDelta: function(inA, inB) {
+ var x = 0, y = 0;
+ if (inA && inB) {
+ x = inB.pageX - inA.pageX;
+ y = inB.pageY - inA.pageY;
+ }
+ return {x: x, y: y};
+ },
+ fireTrack: function(inType, inEvent, inTrackingData) {
+ var t = inTrackingData;
+ var d = this.calcPositionDelta(t.downEvent, inEvent);
+ var dd = this.calcPositionDelta(t.lastMoveEvent, inEvent);
+ if (dd.x) {
+ t.xDirection = this.clampDir(dd.x);
+ } else if (inType === 'trackx') {
+ return;
+ }
+ if (dd.y) {
+ t.yDirection = this.clampDir(dd.y);
+ } else if (inType === 'tracky') {
+ return;
+ }
+ var gestureProto = {
+ bubbles: true,
+ cancelable: true,
+ trackInfo: t.trackInfo,
+ relatedTarget: inEvent.relatedTarget,
+ pointerType: inEvent.pointerType,
+ pointerId: inEvent.pointerId,
+ _source: 'track'
+ };
+ if (inType !== 'tracky') {
+ gestureProto.x = inEvent.x;
+ gestureProto.dx = d.x;
+ gestureProto.ddx = dd.x;
+ gestureProto.clientX = inEvent.clientX;
+ gestureProto.pageX = inEvent.pageX;
+ gestureProto.screenX = inEvent.screenX;
+ gestureProto.xDirection = t.xDirection;
+ }
+ if (inType !== 'trackx') {
+ gestureProto.dy = d.y;
+ gestureProto.ddy = dd.y;
+ gestureProto.y = inEvent.y;
+ gestureProto.clientY = inEvent.clientY;
+ gestureProto.pageY = inEvent.pageY;
+ gestureProto.screenY = inEvent.screenY;
+ gestureProto.yDirection = t.yDirection;
+ }
+ var e = eventFactory.makeGestureEvent(inType, gestureProto);
+ t.downTarget.dispatchEvent(e);
+ },
+ down: function(inEvent) {
+ if (inEvent.isPrimary && (inEvent.pointerType === 'mouse' ? inEvent.buttons === 1 : true)) {
+ var p = {
+ downEvent: inEvent,
+ downTarget: inEvent.target,
+ trackInfo: {},
+ lastMoveEvent: null,
+ xDirection: 0,
+ yDirection: 0,
+ tracking: false
+ };
+ pointermap.set(inEvent.pointerId, p);
+ }
+ },
+ move: function(inEvent) {
+ var p = pointermap.get(inEvent.pointerId);
+ if (p) {
+ if (!p.tracking) {
+ var d = this.calcPositionDelta(p.downEvent, inEvent);
+ var move = d.x * d.x + d.y * d.y;
+ // start tracking only if finger moves more than WIGGLE_THRESHOLD
+ if (move > this.WIGGLE_THRESHOLD) {
+ p.tracking = true;
+ p.lastMoveEvent = p.downEvent;
+ this.fireTrack('trackstart', inEvent, p);
+ }
+ }
+ if (p.tracking) {
+ this.fireTrack('track', inEvent, p);
+ this.fireTrack('trackx', inEvent, p);
+ this.fireTrack('tracky', inEvent, p);
+ }
+ p.lastMoveEvent = inEvent;
+ }
+ },
+ up: function(inEvent) {
+ var p = pointermap.get(inEvent.pointerId);
+ if (p) {
+ if (p.tracking) {
+ this.fireTrack('trackend', inEvent, p);
+ }
+ pointermap.delete(inEvent.pointerId);
+ }
+ }
+ };
+ dispatcher.registerGesture('track', track);
+ })(window.PolymerGestures);
+
+/**
+ * This event is fired when a pointer is held down for 200ms.
+ *
+ * @module PointerGestures
+ * @submodule Events
+ * @class hold
+ */
+/**
+ * Type of pointer that made the holding event.
+ * @type String
+ * @property pointerType
+ */
+/**
+ * Screen X axis position of the held pointer
+ * @type Number
+ * @property clientX
+ */
+/**
+ * Screen Y axis position of the held pointer
+ * @type Number
+ * @property clientY
+ */
+/**
+ * Type of pointer that made the holding event.
+ * @type String
+ * @property pointerType
+ */
+/**
+ * This event is fired every 200ms while a pointer is held down.
+ *
+ * @class holdpulse
+ * @extends hold
+ */
+/**
+ * Milliseconds pointer has been held down.
+ * @type Number
+ * @property holdTime
+ */
+/**
+ * This event is fired when a held pointer is released or moved.
+ *
+ * @class release
+ */
+
+(function(scope) {
+ var dispatcher = scope.dispatcher;
+ var eventFactory = scope.eventFactory;
+ var hold = {
+ // wait at least HOLD_DELAY ms between hold and pulse events
+ HOLD_DELAY: 200,
+ // pointer can move WIGGLE_THRESHOLD pixels before not counting as a hold
+ WIGGLE_THRESHOLD: 16,
+ events: [
+ 'down',
+ 'move',
+ 'up',
+ ],
+ exposes: [
+ 'hold',
+ 'holdpulse',
+ 'release'
+ ],
+ heldPointer: null,
+ holdJob: null,
+ pulse: function() {
+ var hold = Date.now() - this.heldPointer.timeStamp;
+ var type = this.held ? 'holdpulse' : 'hold';
+ this.fireHold(type, hold);
+ this.held = true;
+ },
+ cancel: function() {
+ clearInterval(this.holdJob);
+ if (this.held) {
+ this.fireHold('release');
+ }
+ this.held = false;
+ this.heldPointer = null;
+ this.target = null;
+ this.holdJob = null;
+ },
+ down: function(inEvent) {
+ if (inEvent.isPrimary && !this.heldPointer) {
+ this.heldPointer = inEvent;
+ this.target = inEvent.target;
+ this.holdJob = setInterval(this.pulse.bind(this), this.HOLD_DELAY);
+ }
+ },
+ up: function(inEvent) {
+ if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId) {
+ this.cancel();
+ }
+ },
+ move: function(inEvent) {
+ if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId) {
+ var x = inEvent.clientX - this.heldPointer.clientX;
+ var y = inEvent.clientY - this.heldPointer.clientY;
+ if ((x * x + y * y) > this.WIGGLE_THRESHOLD) {
+ this.cancel();
+ }
+ }
+ },
+ fireHold: function(inType, inHoldTime) {
+ var p = {
+ bubbles: true,
+ cancelable: true,
+ pointerType: this.heldPointer.pointerType,
+ pointerId: this.heldPointer.pointerId,
+ x: this.heldPointer.clientX,
+ y: this.heldPointer.clientY,
+ _source: 'hold'
+ };
+ if (inHoldTime) {
+ p.holdTime = inHoldTime;
+ }
+ var e = eventFactory.makeGestureEvent(inType, p);
+ this.target.dispatchEvent(e);
+ }
+ };
+ dispatcher.registerGesture('hold', hold);
+})(window.PolymerGestures);
+
+/**
+ * This event is fired when a pointer quickly goes down and up, and is used to
+ * denote activation.
+ *
+ * Any gesture event can prevent the tap event from being created by calling
+ * `event.preventTap`.
+ *
+ * Any pointer event can prevent the tap by setting the `tapPrevented` property
+ * on itself.
+ *
+ * @module PointerGestures
+ * @submodule Events
+ * @class tap
+ */
+/**
+ * X axis position of the tap.
+ * @property x
+ * @type Number
+ */
+/**
+ * Y axis position of the tap.
+ * @property y
+ * @type Number
+ */
+/**
+ * Type of the pointer that made the tap.
+ * @property pointerType
+ * @type String
+ */
+(function(scope) {
+ var dispatcher = scope.dispatcher;
+ var eventFactory = scope.eventFactory;
+ var pointermap = new scope.PointerMap();
+ var tap = {
+ events: [
+ 'down',
+ 'up'
+ ],
+ exposes: [
+ 'tap'
+ ],
+ down: function(inEvent) {
+ if (inEvent.isPrimary && !inEvent.tapPrevented) {
+ pointermap.set(inEvent.pointerId, {
+ target: inEvent.target,
+ buttons: inEvent.buttons,
+ x: inEvent.clientX,
+ y: inEvent.clientY
+ });
+ }
+ },
+ shouldTap: function(e, downState) {
+ var tap = true;
+ if (e.pointerType === 'mouse') {
+ // only allow left click to tap for mouse
+ tap = (e.buttons ^ 1) && (downState.buttons & 1);
+ }
+ return tap && !e.tapPrevented;
+ },
+ up: function(inEvent) {
+ var start = pointermap.get(inEvent.pointerId);
+ if (start && this.shouldTap(inEvent, start)) {
+ // up.relatedTarget is target currently under finger
+ var t = scope.targetFinding.LCA(start.target, inEvent.relatedTarget);
+ if (t) {
+ var e = eventFactory.makeGestureEvent('tap', {
+ bubbles: true,
+ cancelable: true,
+ x: inEvent.clientX,
+ y: inEvent.clientY,
+ detail: inEvent.detail,
+ pointerType: inEvent.pointerType,
+ pointerId: inEvent.pointerId,
+ altKey: inEvent.altKey,
+ ctrlKey: inEvent.ctrlKey,
+ metaKey: inEvent.metaKey,
+ shiftKey: inEvent.shiftKey,
+ _source: 'tap'
+ });
+ t.dispatchEvent(e);
+ }
+ }
+ pointermap.delete(inEvent.pointerId);
+ }
+ };
+ // patch eventFactory to remove id from tap's pointermap for preventTap calls
+ eventFactory.preventTap = function(e) {
+ return function() {
+ e.tapPrevented = true;
+ pointermap.delete(e.pointerId);
+ };
+ };
+ dispatcher.registerGesture('tap', tap);
+})(window.PolymerGestures);
+
+/*
+ * Basic strategy: find the farthest apart points, use as diameter of circle
+ * react to size change and rotation of the chord
+ */
+
+/**
+ * @module pointer-gestures
+ * @submodule Events
+ * @class pinch
+ */
+/**
+ * Scale of the pinch zoom gesture
+ * @property scale
+ * @type Number
+ */
+/**
+ * Center X position of pointers causing pinch
+ * @property centerX
+ * @type Number
+ */
+/**
+ * Center Y position of pointers causing pinch
+ * @property centerY
+ * @type Number
+ */
+
+/**
+ * @module pointer-gestures
+ * @submodule Events
+ * @class rotate
+ */
+/**
+ * Angle (in degrees) of rotation. Measured from starting positions of pointers.
+ * @property angle
+ * @type Number
+ */
+/**
+ * Center X position of pointers causing rotation
+ * @property centerX
+ * @type Number
+ */
+/**
+ * Center Y position of pointers causing rotation
+ * @property centerY
+ * @type Number
+ */
+(function(scope) {
+ var dispatcher = scope.dispatcher;
+ var eventFactory = scope.eventFactory;
+ var pointermap = new scope.PointerMap();
+ var RAD_TO_DEG = 180 / Math.PI;
+ var pinch = {
+ events: [
+ 'down',
+ 'up',
+ 'move',
+ 'cancel'
+ ],
+ exposes: [
+ 'pinch',
+ 'rotate'
+ ],
+ defaultActions: {
+ 'pinch': 'none',
+ 'rotate': 'none'
+ },
+ reference: {},
+ down: function(inEvent) {
+ pointermap.set(inEvent.pointerId, inEvent);
+ if (pointermap.pointers() == 2) {
+ var points = this.calcChord();
+ var angle = this.calcAngle(points);
+ this.reference = {
+ angle: angle,
+ diameter: points.diameter,
+ target: scope.targetFinding.LCA(points.a.target, points.b.target)
+ };
+ }
+ },
+ up: function(inEvent) {
+ var p = pointermap.get(inEvent.pointerId);
+ if (p) {
+ pointermap.delete(inEvent.pointerId);
+ }
+ },
+ move: function(inEvent) {
+ if (pointermap.has(inEvent.pointerId)) {
+ pointermap.set(inEvent.pointerId, inEvent);
+ if (pointermap.pointers() > 1) {
+ this.calcPinchRotate();
+ }
+ }
+ },
+ cancel: function(inEvent) {
+ this.up(inEvent);
+ },
+ firePinch: function(diameter, points) {
+ var zoom = diameter / this.reference.diameter;
+ var e = eventFactory.makeGestureEvent('pinch', {
+ bubbles: true,
+ cancelable: true,
+ scale: zoom,
+ centerX: points.center.x,
+ centerY: points.center.y,
+ _source: 'pinch'
+ });
+ this.reference.target.dispatchEvent(e);
+ },
+ fireRotate: function(angle, points) {
+ var diff = Math.round((angle - this.reference.angle) % 360);
+ var e = eventFactory.makeGestureEvent('rotate', {
+ bubbles: true,
+ cancelable: true,
+ angle: diff,
+ centerX: points.center.x,
+ centerY: points.center.y,
+ _source: 'pinch'
+ });
+ this.reference.target.dispatchEvent(e);
+ },
+ calcPinchRotate: function() {
+ var points = this.calcChord();
+ var diameter = points.diameter;
+ var angle = this.calcAngle(points);
+ if (diameter != this.reference.diameter) {
+ this.firePinch(diameter, points);
+ }
+ if (angle != this.reference.angle) {
+ this.fireRotate(angle, points);
+ }
+ },
+ calcChord: function() {
+ var pointers = [];
+ pointermap.forEach(function(p) {
+ pointers.push(p);
+ });
+ var dist = 0;
+ // start with at least two pointers
+ var points = {a: pointers[0], b: pointers[1]};
+ var x, y, d;
+ for (var i = 0; i < pointers.length; i++) {
+ var a = pointers[i];
+ for (var j = i + 1; j < pointers.length; j++) {
+ var b = pointers[j];
+ x = Math.abs(a.clientX - b.clientX);
+ y = Math.abs(a.clientY - b.clientY);
+ d = x + y;
+ if (d > dist) {
+ dist = d;
+ points = {a: a, b: b};
+ }
+ }
+ }
+ x = Math.abs(points.a.clientX + points.b.clientX) / 2;
+ y = Math.abs(points.a.clientY + points.b.clientY) / 2;
+ points.center = { x: x, y: y };
+ points.diameter = dist;
+ return points;
+ },
+ calcAngle: function(points) {
+ var x = points.a.clientX - points.b.clientX;
+ var y = points.a.clientY - points.b.clientY;
+ return (360 + Math.atan2(y, x) * RAD_TO_DEG) % 360;
+ }
+ };
+ dispatcher.registerGesture('pinch', pinch);
+})(window.PolymerGestures);
+
+(function (global) {
+ 'use strict';
+
+ var Token,
+ TokenName,
+ Syntax,
+ Messages,
+ source,
+ index,
+ length,
+ delegate,
+ lookahead,
+ state;
+
+ Token = {
+ BooleanLiteral: 1,
+ EOF: 2,
+ Identifier: 3,
+ Keyword: 4,
+ NullLiteral: 5,
+ NumericLiteral: 6,
+ Punctuator: 7,
+ StringLiteral: 8
+ };
+
+ TokenName = {};
+ TokenName[Token.BooleanLiteral] = 'Boolean';
+ TokenName[Token.EOF] = '<end>';
+ TokenName[Token.Identifier] = 'Identifier';
+ TokenName[Token.Keyword] = 'Keyword';
+ TokenName[Token.NullLiteral] = 'Null';
+ TokenName[Token.NumericLiteral] = 'Numeric';
+ TokenName[Token.Punctuator] = 'Punctuator';
+ TokenName[Token.StringLiteral] = 'String';
+
+ Syntax = {
+ ArrayExpression: 'ArrayExpression',
+ BinaryExpression: 'BinaryExpression',
+ CallExpression: 'CallExpression',
+ ConditionalExpression: 'ConditionalExpression',
+ EmptyStatement: 'EmptyStatement',
+ ExpressionStatement: 'ExpressionStatement',
+ Identifier: 'Identifier',
+ Literal: 'Literal',
+ LabeledStatement: 'LabeledStatement',
+ LogicalExpression: 'LogicalExpression',
+ MemberExpression: 'MemberExpression',
+ ObjectExpression: 'ObjectExpression',
+ Program: 'Program',
+ Property: 'Property',
+ ThisExpression: 'ThisExpression',
+ UnaryExpression: 'UnaryExpression'
+ };
+
+ // Error messages should be identical to V8.
+ Messages = {
+ UnexpectedToken: 'Unexpected token %0',
+ UnknownLabel: 'Undefined label \'%0\'',
+ Redeclaration: '%0 \'%1\' has already been declared'
+ };
+
+ // Ensure the condition is true, otherwise throw an error.
+ // This is only to have a better contract semantic, i.e. another safety net
+ // to catch a logic error. The condition shall be fulfilled in normal case.
+ // Do NOT use this to enforce a certain condition on any user input.
+
+ function assert(condition, message) {
+ if (!condition) {
+ throw new Error('ASSERT: ' + message);
+ }
+ }
+
+ function isDecimalDigit(ch) {
+ return (ch >= 48 && ch <= 57); // 0..9
+ }
+
+
+ // 7.2 White Space
+
+ function isWhiteSpace(ch) {
+ return (ch === 32) || // space
+ (ch === 9) || // tab
+ (ch === 0xB) ||
+ (ch === 0xC) ||
+ (ch === 0xA0) ||
+ (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(String.fromCharCode(ch)) > 0);
+ }
+
+ // 7.3 Line Terminators
+
+ function isLineTerminator(ch) {
+ return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029);
+ }
+
+ // 7.6 Identifier Names and Identifiers
+
+ function isIdentifierStart(ch) {
+ return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore)
+ (ch >= 65 && ch <= 90) || // A..Z
+ (ch >= 97 && ch <= 122); // a..z
+ }
+
+ function isIdentifierPart(ch) {
+ return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore)
+ (ch >= 65 && ch <= 90) || // A..Z
+ (ch >= 97 && ch <= 122) || // a..z
+ (ch >= 48 && ch <= 57); // 0..9
+ }
+
+ // 7.6.1.1 Keywords
+
+ function isKeyword(id) {
+ return (id === 'this')
+ }
+
+ // 7.4 Comments
+
+ function skipWhitespace() {
+ while (index < length && isWhiteSpace(source.charCodeAt(index))) {
+ ++index;
+ }
+ }
+
+ function getIdentifier() {
+ var start, ch;
+
+ start = index++;
+ while (index < length) {
+ ch = source.charCodeAt(index);
+ if (isIdentifierPart(ch)) {
+ ++index;
+ } else {
+ break;
+ }
+ }
+
+ return source.slice(start, index);
+ }
+
+ function scanIdentifier() {
+ var start, id, type;
+
+ start = index;
+
+ id = getIdentifier();
+
+ // There is no keyword or literal with only one character.
+ // Thus, it must be an identifier.
+ if (id.length === 1) {
+ type = Token.Identifier;
+ } else if (isKeyword(id)) {
+ type = Token.Keyword;
+ } else if (id === 'null') {
+ type = Token.NullLiteral;
+ } else if (id === 'true' || id === 'false') {
+ type = Token.BooleanLiteral;
+ } else {
+ type = Token.Identifier;
+ }
+
+ return {
+ type: type,
+ value: id,
+ range: [start, index]
+ };
+ }
+
+
+ // 7.7 Punctuators
+
+ function scanPunctuator() {
+ var start = index,
+ code = source.charCodeAt(index),
+ code2,
+ ch1 = source[index],
+ ch2;
+
+ switch (code) {
+
+ // Check for most common single-character punctuators.
+ case 46: // . dot
+ case 40: // ( open bracket
+ case 41: // ) close bracket
+ case 59: // ; semicolon
+ case 44: // , comma
+ case 123: // { open curly brace
+ case 125: // } close curly brace
+ case 91: // [
+ case 93: // ]
+ case 58: // :
+ case 63: // ?
+ ++index;
+ return {
+ type: Token.Punctuator,
+ value: String.fromCharCode(code),
+ range: [start, index]
+ };
+
+ default:
+ code2 = source.charCodeAt(index + 1);
+
+ // '=' (char #61) marks an assignment or comparison operator.
+ if (code2 === 61) {
+ switch (code) {
+ case 37: // %
+ case 38: // &
+ case 42: // *:
+ case 43: // +
+ case 45: // -
+ case 47: // /
+ case 60: // <
+ case 62: // >
+ case 124: // |
+ index += 2;
+ return {
+ type: Token.Punctuator,
+ value: String.fromCharCode(code) + String.fromCharCode(code2),
+ range: [start, index]
+ };
+
+ case 33: // !
+ case 61: // =
+ index += 2;
+
+ // !== and ===
+ if (source.charCodeAt(index) === 61) {
+ ++index;
+ }
+ return {
+ type: Token.Punctuator,
+ value: source.slice(start, index),
+ range: [start, index]
+ };
+ default:
+ break;
+ }
+ }
+ break;
+ }
+
+ // Peek more characters.
+
+ ch2 = source[index + 1];
+
+ // Other 2-character punctuators: && ||
+
+ if (ch1 === ch2 && ('&|'.indexOf(ch1) >= 0)) {
+ index += 2;
+ return {
+ type: Token.Punctuator,
+ value: ch1 + ch2,
+ range: [start, index]
+ };
+ }
+
+ if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) {
+ ++index;
+ return {
+ type: Token.Punctuator,
+ value: ch1,
+ range: [start, index]
+ };
+ }
+
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ // 7.8.3 Numeric Literals
+ function scanNumericLiteral() {
+ var number, start, ch;
+
+ ch = source[index];
+ assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'),
+ 'Numeric literal must start with a decimal digit or a decimal point');
+
+ start = index;
+ number = '';
+ if (ch !== '.') {
+ number = source[index++];
+ ch = source[index];
+
+ // Hex number starts with '0x'.
+ // Octal number starts with '0'.
+ if (number === '0') {
+ // decimal number starts with '0' such as '09' is illegal.
+ if (ch && isDecimalDigit(ch.charCodeAt(0))) {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+ }
+
+ while (isDecimalDigit(source.charCodeAt(index))) {
+ number += source[index++];
+ }
+ ch = source[index];
+ }
+
+ if (ch === '.') {
+ number += source[index++];
+ while (isDecimalDigit(source.charCodeAt(index))) {
+ number += source[index++];
+ }
+ ch = source[index];
+ }
+
+ if (ch === 'e' || ch === 'E') {
+ number += source[index++];
+
+ ch = source[index];
+ if (ch === '+' || ch === '-') {
+ number += source[index++];
+ }
+ if (isDecimalDigit(source.charCodeAt(index))) {
+ while (isDecimalDigit(source.charCodeAt(index))) {
+ number += source[index++];
+ }
+ } else {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+ }
+
+ if (isIdentifierStart(source.charCodeAt(index))) {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ return {
+ type: Token.NumericLiteral,
+ value: parseFloat(number),
+ range: [start, index]
+ };
+ }
+
+ // 7.8.4 String Literals
+
+ function scanStringLiteral() {
+ var str = '', quote, start, ch, octal = false;
+
+ quote = source[index];
+ assert((quote === '\'' || quote === '"'),
+ 'String literal must starts with a quote');
+
+ start = index;
+ ++index;
+
+ while (index < length) {
+ ch = source[index++];
+
+ if (ch === quote) {
+ quote = '';
+ break;
+ } else if (ch === '\\') {
+ ch = source[index++];
+ if (!ch || !isLineTerminator(ch.charCodeAt(0))) {
+ switch (ch) {
+ case 'n':
+ str += '\n';
+ break;
+ case 'r':
+ str += '\r';
+ break;
+ case 't':
+ str += '\t';
+ break;
+ case 'b':
+ str += '\b';
+ break;
+ case 'f':
+ str += '\f';
+ break;
+ case 'v':
+ str += '\x0B';
+ break;
+
+ default:
+ str += ch;
+ break;
+ }
+ } else {
+ if (ch === '\r' && source[index] === '\n') {
+ ++index;
+ }
+ }
+ } else if (isLineTerminator(ch.charCodeAt(0))) {
+ break;
+ } else {
+ str += ch;
+ }
+ }
+
+ if (quote !== '') {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ return {
+ type: Token.StringLiteral,
+ value: str,
+ octal: octal,
+ range: [start, index]
+ };
+ }
+
+ function isIdentifierName(token) {
+ return token.type === Token.Identifier ||
+ token.type === Token.Keyword ||
+ token.type === Token.BooleanLiteral ||
+ token.type === Token.NullLiteral;
+ }
+
+ function advance() {
+ var ch;
+
+ skipWhitespace();
+
+ if (index >= length) {
+ return {
+ type: Token.EOF,
+ range: [index, index]
+ };
+ }
+
+ ch = source.charCodeAt(index);
+
+ // Very common: ( and ) and ;
+ if (ch === 40 || ch === 41 || ch === 58) {
+ return scanPunctuator();
+ }
+
+ // String literal starts with single quote (#39) or double quote (#34).
+ if (ch === 39 || ch === 34) {
+ return scanStringLiteral();
+ }
+
+ if (isIdentifierStart(ch)) {
+ return scanIdentifier();
+ }
+
+ // Dot (.) char #46 can also start a floating-point number, hence the need
+ // to check the next character.
+ if (ch === 46) {
+ if (isDecimalDigit(source.charCodeAt(index + 1))) {
+ return scanNumericLiteral();
+ }
+ return scanPunctuator();
+ }
+
+ if (isDecimalDigit(ch)) {
+ return scanNumericLiteral();
+ }
+
+ return scanPunctuator();
+ }
+
+ function lex() {
+ var token;
+
+ token = lookahead;
+ index = token.range[1];
+
+ lookahead = advance();
+
+ index = token.range[1];
+
+ return token;
+ }
+
+ function peek() {
+ var pos;
+
+ pos = index;
+ lookahead = advance();
+ index = pos;
+ }
+
+ // Throw an exception
+
+ function throwError(token, messageFormat) {
+ var error,
+ args = Array.prototype.slice.call(arguments, 2),
+ msg = messageFormat.replace(
+ /%(\d)/g,
+ function (whole, index) {
+ assert(index < args.length, 'Message reference must be in range');
+ return args[index];
+ }
+ );
+
+ error = new Error(msg);
+ error.index = index;
+ error.description = msg;
+ throw error;
+ }
+
+ // Throw an exception because of the token.
+
+ function throwUnexpected(token) {
+ throwError(token, Messages.UnexpectedToken, token.value);
+ }
+
+ // Expect the next token to match the specified punctuator.
+ // If not, an exception will be thrown.
+
+ function expect(value) {
+ var token = lex();
+ if (token.type !== Token.Punctuator || token.value !== value) {
+ throwUnexpected(token);
+ }
+ }
+
+ // Return true if the next token matches the specified punctuator.
+
+ function match(value) {
+ return lookahead.type === Token.Punctuator && lookahead.value === value;
+ }
+
+ // Return true if the next token matches the specified keyword
+
+ function matchKeyword(keyword) {
+ return lookahead.type === Token.Keyword && lookahead.value === keyword;
+ }
+
+ function consumeSemicolon() {
+ // Catch the very common case first: immediately a semicolon (char #59).
+ if (source.charCodeAt(index) === 59) {
+ lex();
+ return;
+ }
+
+ skipWhitespace();
+
+ if (match(';')) {
+ lex();
+ return;
+ }
+
+ if (lookahead.type !== Token.EOF && !match('}')) {
+ throwUnexpected(lookahead);
+ }
+ }
+
+ // 11.1.4 Array Initialiser
+
+ function parseArrayInitialiser() {
+ var elements = [];
+
+ expect('[');
+
+ while (!match(']')) {
+ if (match(',')) {
+ lex();
+ elements.push(null);
+ } else {
+ elements.push(parseExpression());
+
+ if (!match(']')) {
+ expect(',');
+ }
+ }
+ }
+
+ expect(']');
+
+ return delegate.createArrayExpression(elements);
+ }
+
+ // 11.1.5 Object Initialiser
+
+ function parseObjectPropertyKey() {
+ var token;
+
+ skipWhitespace();
+ token = lex();
+
+ // Note: This function is called only from parseObjectProperty(), where
+ // EOF and Punctuator tokens are already filtered out.
+ if (token.type === Token.StringLiteral || token.type === Token.NumericLiteral) {
+ return delegate.createLiteral(token);
+ }
+
+ return delegate.createIdentifier(token.value);
+ }
+
+ function parseObjectProperty() {
+ var token, key;
+
+ token = lookahead;
+ skipWhitespace();
+
+ if (token.type === Token.EOF || token.type === Token.Punctuator) {
+ throwUnexpected(token);
+ }
+
+ key = parseObjectPropertyKey();
+ expect(':');
+ return delegate.createProperty('init', key, parseExpression());
+ }
+
+ function parseObjectInitialiser() {
+ var properties = [];
+
+ expect('{');
+
+ while (!match('}')) {
+ properties.push(parseObjectProperty());
+
+ if (!match('}')) {
+ expect(',');
+ }
+ }
+
+ expect('}');
+
+ return delegate.createObjectExpression(properties);
+ }
+
+ // 11.1.6 The Grouping Operator
+
+ function parseGroupExpression() {
+ var expr;
+
+ expect('(');
+
+ expr = parseExpression();
+
+ expect(')');
+
+ return expr;
+ }
+
+
+ // 11.1 Primary Expressions
+
+ function parsePrimaryExpression() {
+ var type, token, expr;
+
+ if (match('(')) {
+ return parseGroupExpression();
+ }
+
+ type = lookahead.type;
+
+ if (type === Token.Identifier) {
+ expr = delegate.createIdentifier(lex().value);
+ } else if (type === Token.StringLiteral || type === Token.NumericLiteral) {
+ expr = delegate.createLiteral(lex());
+ } else if (type === Token.Keyword) {
+ if (matchKeyword('this')) {
+ lex();
+ expr = delegate.createThisExpression();
+ }
+ } else if (type === Token.BooleanLiteral) {
+ token = lex();
+ token.value = (token.value === 'true');
+ expr = delegate.createLiteral(token);
+ } else if (type === Token.NullLiteral) {
+ token = lex();
+ token.value = null;
+ expr = delegate.createLiteral(token);
+ } else if (match('[')) {
+ expr = parseArrayInitialiser();
+ } else if (match('{')) {
+ expr = parseObjectInitialiser();
+ }
+
+ if (expr) {
+ return expr;
+ }
+
+ throwUnexpected(lex());
+ }
+
+ // 11.2 Left-Hand-Side Expressions
+
+ function parseArguments() {
+ var args = [];
+
+ expect('(');
+
+ if (!match(')')) {
+ while (index < length) {
+ args.push(parseExpression());
+ if (match(')')) {
+ break;
+ }
+ expect(',');
+ }
+ }
+
+ expect(')');
+
+ return args;
+ }
+
+ function parseNonComputedProperty() {
+ var token;
+
+ token = lex();
+
+ if (!isIdentifierName(token)) {
+ throwUnexpected(token);
+ }
+
+ return delegate.createIdentifier(token.value);
+ }
+
+ function parseNonComputedMember() {
+ expect('.');
+
+ return parseNonComputedProperty();
+ }
+
+ function parseComputedMember() {
+ var expr;
+
+ expect('[');
+
+ expr = parseExpression();
+
+ expect(']');
+
+ return expr;
+ }
+
+ function parseLeftHandSideExpression() {
+ var expr, args, property;
+
+ expr = parsePrimaryExpression();
+
+ while (true) {
+ if (match('[')) {
+ property = parseComputedMember();
+ expr = delegate.createMemberExpression('[', expr, property);
+ } else if (match('.')) {
+ property = parseNonComputedMember();
+ expr = delegate.createMemberExpression('.', expr, property);
+ } else if (match('(')) {
+ args = parseArguments();
+ expr = delegate.createCallExpression(expr, args);
+ } else {
+ break;
+ }
+ }
+
+ return expr;
+ }
+
+ // 11.3 Postfix Expressions
+
+ var parsePostfixExpression = parseLeftHandSideExpression;
+
+ // 11.4 Unary Operators
+
+ function parseUnaryExpression() {
+ var token, expr;
+
+ if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyword) {
+ expr = parsePostfixExpression();
+ } else if (match('+') || match('-') || match('!')) {
+ token = lex();
+ expr = parseUnaryExpression();
+ expr = delegate.createUnaryExpression(token.value, expr);
+ } else if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) {
+ throwError({}, Messages.UnexpectedToken);
+ } else {
+ expr = parsePostfixExpression();
+ }
+
+ return expr;
+ }
+
+ function binaryPrecedence(token) {
+ var prec = 0;
+
+ if (token.type !== Token.Punctuator && token.type !== Token.Keyword) {
+ return 0;
+ }
+
+ switch (token.value) {
+ case '||':
+ prec = 1;
+ break;
+
+ case '&&':
+ prec = 2;
+ break;
+
+ case '==':
+ case '!=':
+ case '===':
+ case '!==':
+ prec = 6;
+ break;
+
+ case '<':
+ case '>':
+ case '<=':
+ case '>=':
+ case 'instanceof':
+ prec = 7;
+ break;
+
+ case 'in':
+ prec = 7;
+ break;
+
+ case '+':
+ case '-':
+ prec = 9;
+ break;
+
+ case '*':
+ case '/':
+ case '%':
+ prec = 11;
+ break;
+
+ default:
+ break;
+ }
+
+ return prec;
+ }
+
+ // 11.5 Multiplicative Operators
+ // 11.6 Additive Operators
+ // 11.7 Bitwise Shift Operators
+ // 11.8 Relational Operators
+ // 11.9 Equality Operators
+ // 11.10 Binary Bitwise Operators
+ // 11.11 Binary Logical Operators
+
+ function parseBinaryExpression() {
+ var expr, token, prec, stack, right, operator, left, i;
+
+ left = parseUnaryExpression();
+
+ token = lookahead;
+ prec = binaryPrecedence(token);
+ if (prec === 0) {
+ return left;
+ }
+ token.prec = prec;
+ lex();
+
+ right = parseUnaryExpression();
+
+ stack = [left, token, right];
+
+ while ((prec = binaryPrecedence(lookahead)) > 0) {
+
+ // Reduce: make a binary expression from the three topmost entries.
+ while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) {
+ right = stack.pop();
+ operator = stack.pop().value;
+ left = stack.pop();
+ expr = delegate.createBinaryExpression(operator, left, right);
+ stack.push(expr);
+ }
+
+ // Shift.
+ token = lex();
+ token.prec = prec;
+ stack.push(token);
+ expr = parseUnaryExpression();
+ stack.push(expr);
+ }
+
+ // Final reduce to clean-up the stack.
+ i = stack.length - 1;
+ expr = stack[i];
+ while (i > 1) {
+ expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i - 2], expr);
+ i -= 2;
+ }
+
+ return expr;
+ }
+
+
+ // 11.12 Conditional Operator
+
+ function parseConditionalExpression() {
+ var expr, consequent, alternate;
+
+ expr = parseBinaryExpression();
+
+ if (match('?')) {
+ lex();
+ consequent = parseConditionalExpression();
+ expect(':');
+ alternate = parseConditionalExpression();
+
+ expr = delegate.createConditionalExpression(expr, consequent, alternate);
+ }
+
+ return expr;
+ }
+
+ // Simplification since we do not support AssignmentExpression.
+ var parseExpression = parseConditionalExpression;
+
+ // Polymer Syntax extensions
+
+ // Filter ::
+ // Identifier
+ // Identifier "(" ")"
+ // Identifier "(" FilterArguments ")"
+
+ function parseFilter() {
+ var identifier, args;
+
+ identifier = lex();
+
+ if (identifier.type !== Token.Identifier) {
+ throwUnexpected(identifier);
+ }
+
+ args = match('(') ? parseArguments() : [];
+
+ return delegate.createFilter(identifier.value, args);
+ }
+
+ // Filters ::
+ // "|" Filter
+ // Filters "|" Filter
+
+ function parseFilters() {
+ while (match('|')) {
+ lex();
+ parseFilter();
+ }
+ }
+
+ // TopLevel ::
+ // LabelledExpressions
+ // AsExpression
+ // InExpression
+ // FilterExpression
+
+ // AsExpression ::
+ // FilterExpression as Identifier
+
+ // InExpression ::
+ // Identifier, Identifier in FilterExpression
+ // Identifier in FilterExpression
+
+ // FilterExpression ::
+ // Expression
+ // Expression Filters
+
+ function parseTopLevel() {
+ skipWhitespace();
+ peek();
+
+ var expr = parseExpression();
+ if (expr) {
+ if (lookahead.value === ',' || lookahead.value == 'in' &&
+ expr.type === Syntax.Identifier) {
+ parseInExpression(expr);
+ } else {
+ parseFilters();
+ if (lookahead.value === 'as') {
+ parseAsExpression(expr);
+ } else {
+ delegate.createTopLevel(expr);
+ }
+ }
+ }
+
+ if (lookahead.type !== Token.EOF) {
+ throwUnexpected(lookahead);
+ }
+ }
+
+ function parseAsExpression(expr) {
+ lex(); // as
+ var identifier = lex().value;
+ delegate.createAsExpression(expr, identifier);
+ }
+
+ function parseInExpression(identifier) {
+ var indexName;
+ if (lookahead.value === ',') {
+ lex();
+ if (lookahead.type !== Token.Identifier)
+ throwUnexpected(lookahead);
+ indexName = lex().value;
+ }
+
+ lex(); // in
+ var expr = parseExpression();
+ parseFilters();
+ delegate.createInExpression(identifier.name, indexName, expr);
+ }
+
+ function parse(code, inDelegate) {
+ delegate = inDelegate;
+ source = code;
+ index = 0;
+ length = source.length;
+ lookahead = null;
+ state = {
+ labelSet: {}
+ };
+
+ return parseTopLevel();
+ }
+
+ global.esprima = {
+ parse: parse
+ };
+})(this);
+
+// Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
+// This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
+// The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
+// The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
+// Code distributed by Google as part of the polymer project is also
+// subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
+
+(function (global) {
+ 'use strict';
+
+ function prepareBinding(expressionText, name, node, filterRegistry) {
+ var expression;
+ try {
+ expression = getExpression(expressionText);
+ if (expression.scopeIdent &&
+ (node.nodeType !== Node.ELEMENT_NODE ||
+ node.tagName !== 'TEMPLATE' ||
+ (name !== 'bind' && name !== 'repeat'))) {
+ throw Error('as and in can only be used within <template bind/repeat>');
+ }
+ } catch (ex) {
+ console.error('Invalid expression syntax: ' + expressionText, ex);
+ return;
+ }
+
+ return function(model, node, oneTime) {
+ var binding = expression.getBinding(model, filterRegistry, oneTime);
+ if (expression.scopeIdent && binding) {
+ node.polymerExpressionScopeIdent_ = expression.scopeIdent;
+ if (expression.indexIdent)
+ node.polymerExpressionIndexIdent_ = expression.indexIdent;
+ }
+
+ return binding;
+ }
+ }
+
+ // TODO(rafaelw): Implement simple LRU.
+ var expressionParseCache = Object.create(null);
+
+ function getExpression(expressionText) {
+ var expression = expressionParseCache[expressionText];
+ if (!expression) {
+ var delegate = new ASTDelegate();
+ esprima.parse(expressionText, delegate);
+ expression = new Expression(delegate);
+ expressionParseCache[expressionText] = expression;
+ }
+ return expression;
+ }
+
+ function Literal(value) {
+ this.value = value;
+ this.valueFn_ = undefined;
+ }
+
+ Literal.prototype = {
+ valueFn: function() {
+ if (!this.valueFn_) {
+ var value = this.value;
+ this.valueFn_ = function() {
+ return value;
+ }
+ }
+
+ return this.valueFn_;
+ }
+ }
+
+ function IdentPath(name) {
+ this.name = name;
+ this.path = Path.get(name);
+ }
+
+ IdentPath.prototype = {
+ valueFn: function() {
+ if (!this.valueFn_) {
+ var name = this.name;
+ var path = this.path;
+ this.valueFn_ = function(model, observer) {
+ if (observer)
+ observer.addPath(model, path);
+
+ return path.getValueFrom(model);
+ }
+ }
+
+ return this.valueFn_;
+ },
+
+ setValue: function(model, newValue) {
+ if (this.path.length == 1);
+ model = findScope(model, this.path[0]);
+
+ return this.path.setValueFrom(model, newValue);
+ }
+ };
+
+ function MemberExpression(object, property, accessor) {
+ this.computed = accessor == '[';
+
+ this.dynamicDeps = typeof object == 'function' ||
+ object.dynamicDeps ||
+ (this.computed && !(property instanceof Literal));
+
+ this.simplePath =
+ !this.dynamicDeps &&
+ (property instanceof IdentPath || property instanceof Literal) &&
+ (object instanceof MemberExpression || object instanceof IdentPath);
+
+ this.object = this.simplePath ? object : getFn(object);
+ this.property = !this.computed || this.simplePath ?
+ property : getFn(property);
+ }
+
+ MemberExpression.prototype = {
+ get fullPath() {
+ if (!this.fullPath_) {
+
+ var parts = this.object instanceof MemberExpression ?
+ this.object.fullPath.slice() : [this.object.name];
+ parts.push(this.property instanceof IdentPath ?
+ this.property.name : this.property.value);
+ this.fullPath_ = Path.get(parts);
+ }
+
+ return this.fullPath_;
+ },
+
+ valueFn: function() {
+ if (!this.valueFn_) {
+ var object = this.object;
+
+ if (this.simplePath) {
+ var path = this.fullPath;
+
+ this.valueFn_ = function(model, observer) {
+ if (observer)
+ observer.addPath(model, path);
+
+ return path.getValueFrom(model);
+ };
+ } else if (!this.computed) {
+ var path = Path.get(this.property.name);
+
+ this.valueFn_ = function(model, observer, filterRegistry) {
+ var context = object(model, observer, filterRegistry);
+
+ if (observer)
+ observer.addPath(context, path);
+
+ return path.getValueFrom(context);
+ }
+ } else {
+ // Computed property.
+ var property = this.property;
+
+ this.valueFn_ = function(model, observer, filterRegistry) {
+ var context = object(model, observer, filterRegistry);
+ var propName = property(model, observer, filterRegistry);
+ if (observer)
+ observer.addPath(context, [propName]);
+
+ return context ? context[propName] : undefined;
+ };
+ }
+ }
+ return this.valueFn_;
+ },
+
+ setValue: function(model, newValue) {
+ if (this.simplePath) {
+ this.fullPath.setValueFrom(model, newValue);
+ return newValue;
+ }
+
+ var object = this.object(model);
+ var propName = this.property instanceof IdentPath ? this.property.name :
+ this.property(model);
+ return object[propName] = newValue;
+ }
+ };
+
+ function Filter(name, args) {
+ this.name = name;
+ this.args = [];
+ for (var i = 0; i < args.length; i++) {
+ this.args[i] = getFn(args[i]);
+ }
+ }
+
+ Filter.prototype = {
+ transform: function(model, observer, filterRegistry, toModelDirection,
+ initialArgs) {
+ var fn = filterRegistry[this.name];
+ var context = model;
+ if (fn) {
+ context = undefined;
+ } else {
+ fn = context[this.name];
+ if (!fn) {
+ console.error('Cannot find function or filter: ' + this.name);
+ return;
+ }
+ }
+
+ // If toModelDirection is falsey, then the "normal" (dom-bound) direction
+ // is used. Otherwise, it looks for a 'toModel' property function on the
+ // object.
+ if (toModelDirection) {
+ fn = fn.toModel;
+ } else if (typeof fn.toDOM == 'function') {
+ fn = fn.toDOM;
+ }
+
+ if (typeof fn != 'function') {
+ console.error('Cannot find function or filter: ' + this.name);
+ return;
+ }
+
+ var args = initialArgs || [];
+ for (var i = 0; i < this.args.length; i++) {
+ args.push(getFn(this.args[i])(model, observer, filterRegistry));
+ }
+
+ return fn.apply(context, args);
+ }
+ };
+
+ function notImplemented() { throw Error('Not Implemented'); }
+
+ var unaryOperators = {
+ '+': function(v) { return +v; },
+ '-': function(v) { return -v; },
+ '!': function(v) { return !v; }
+ };
+
+ var binaryOperators = {
+ '+': function(l, r) { return l+r; },
+ '-': function(l, r) { return l-r; },
+ '*': function(l, r) { return l*r; },
+ '/': function(l, r) { return l/r; },
+ '%': function(l, r) { return l%r; },
+ '<': function(l, r) { return l<r; },
+ '>': function(l, r) { return l>r; },
+ '<=': function(l, r) { return l<=r; },
+ '>=': function(l, r) { return l>=r; },
+ '==': function(l, r) { return l==r; },
+ '!=': function(l, r) { return l!=r; },
+ '===': function(l, r) { return l===r; },
+ '!==': function(l, r) { return l!==r; },
+ '&&': function(l, r) { return l&&r; },
+ '||': function(l, r) { return l||r; },
+ };
+
+ function getFn(arg) {
+ return typeof arg == 'function' ? arg : arg.valueFn();
+ }
+
+ function ASTDelegate() {
+ this.expression = null;
+ this.filters = [];
+ this.deps = {};
+ this.currentPath = undefined;
+ this.scopeIdent = undefined;
+ this.indexIdent = undefined;
+ this.dynamicDeps = false;
+ }
+
+ ASTDelegate.prototype = {
+ createUnaryExpression: function(op, argument) {
+ if (!unaryOperators[op])
+ throw Error('Disallowed operator: ' + op);
+
+ argument = getFn(argument);
+
+ return function(model, observer, filterRegistry) {
+ return unaryOperators[op](argument(model, observer, filterRegistry));
+ };
+ },
+
+ createBinaryExpression: function(op, left, right) {
+ if (!binaryOperators[op])
+ throw Error('Disallowed operator: ' + op);
+
+ left = getFn(left);
+ right = getFn(right);
+
+ switch (op) {
+ case '||':
+ this.dynamicDeps = true;
+ return function(model, observer, filterRegistry) {
+ return left(model, observer, filterRegistry) ||
+ right(model, observer, filterRegistry);
+ };
+ case '&&':
+ this.dynamicDeps = true;
+ return function(model, observer, filterRegistry) {
+ return left(model, observer, filterRegistry) &&
+ right(model, observer, filterRegistry);
+ };
+ }
+
+ return function(model, observer, filterRegistry) {
+ return binaryOperators[op](left(model, observer, filterRegistry),
+ right(model, observer, filterRegistry));
+ };
+ },
+
+ createConditionalExpression: function(test, consequent, alternate) {
+ test = getFn(test);
+ consequent = getFn(consequent);
+ alternate = getFn(alternate);
+
+ this.dynamicDeps = true;
+
+ return function(model, observer, filterRegistry) {
+ return test(model, observer, filterRegistry) ?
+ consequent(model, observer, filterRegistry) :
+ alternate(model, observer, filterRegistry);
+ }
+ },
+
+ createIdentifier: function(name) {
+ var ident = new IdentPath(name);
+ ident.type = 'Identifier';
+ return ident;
+ },
+
+ createMemberExpression: function(accessor, object, property) {
+ var ex = new MemberExpression(object, property, accessor);
+ if (ex.dynamicDeps)
+ this.dynamicDeps = true;
+ return ex;
+ },
+
+ createCallExpression: function(expression, args) {
+ if (!(expression instanceof IdentPath))
+ throw Error('Only identifier function invocations are allowed');
+
+ var filter = new Filter(expression.name, args);
+
+ return function(model, observer, filterRegistry) {
+ return filter.transform(model, observer, filterRegistry, false);
+ };
+ },
+
+ createLiteral: function(token) {
+ return new Literal(token.value);
+ },
+
+ createArrayExpression: function(elements) {
+ for (var i = 0; i < elements.length; i++)
+ elements[i] = getFn(elements[i]);
+
+ return function(model, observer, filterRegistry) {
+ var arr = []
+ for (var i = 0; i < elements.length; i++)
+ arr.push(elements[i](model, observer, filterRegistry));
+ return arr;
+ }
+ },
+
+ createProperty: function(kind, key, value) {
+ return {
+ key: key instanceof IdentPath ? key.name : key.value,
+ value: value
+ };
+ },
+
+ createObjectExpression: function(properties) {
+ for (var i = 0; i < properties.length; i++)
+ properties[i].value = getFn(properties[i].value);
+
+ return function(model, observer, filterRegistry) {
+ var obj = {};
+ for (var i = 0; i < properties.length; i++)
+ obj[properties[i].key] =
+ properties[i].value(model, observer, filterRegistry);
+ return obj;
+ }
+ },
+
+ createFilter: function(name, args) {
+ this.filters.push(new Filter(name, args));
+ },
+
+ createAsExpression: function(expression, scopeIdent) {
+ this.expression = expression;
+ this.scopeIdent = scopeIdent;
+ },
+
+ createInExpression: function(scopeIdent, indexIdent, expression) {
+ this.expression = expression;
+ this.scopeIdent = scopeIdent;
+ this.indexIdent = indexIdent;
+ },
+
+ createTopLevel: function(expression) {
+ this.expression = expression;
+ },
+
+ createThisExpression: notImplemented
+ }
+
+ function ConstantObservable(value) {
+ this.value_ = value;
+ }
+
+ ConstantObservable.prototype = {
+ open: function() { return this.value_; },
+ discardChanges: function() { return this.value_; },
+ deliver: function() {},
+ close: function() {},
+ }
+
+ function Expression(delegate) {
+ this.scopeIdent = delegate.scopeIdent;
+ this.indexIdent = delegate.indexIdent;
+
+ if (!delegate.expression)
+ throw Error('No expression found.');
+
+ this.expression = delegate.expression;
+ getFn(this.expression); // forces enumeration of path dependencies
+
+ this.filters = delegate.filters;
+ this.dynamicDeps = delegate.dynamicDeps;
+ }
+
+ Expression.prototype = {
+ getBinding: function(model, filterRegistry, oneTime) {
+ if (oneTime)
+ return this.getValue(model, undefined, filterRegistry);
+
+ var observer = new CompoundObserver();
+ // captures deps.
+ var firstValue = this.getValue(model, observer, filterRegistry);
+ var firstTime = true;
+ var self = this;
+
+ function valueFn() {
+ // deps cannot have changed on first value retrieval.
+ if (firstTime) {
+ firstTime = false;
+ return firstValue;
+ }
+
+ if (self.dynamicDeps)
+ observer.startReset();
+
+ var value = self.getValue(model,
+ self.dynamicDeps ? observer : undefined,
+ filterRegistry);
+ if (self.dynamicDeps)
+ observer.finishReset();
+
+ return value;
+ }
+
+ function setValueFn(newValue) {
+ self.setValue(model, newValue, filterRegistry);
+ return newValue;
+ }
+
+ return new ObserverTransform(observer, valueFn, setValueFn, true);
+ },
+
+ getValue: function(model, observer, filterRegistry) {
+ var value = getFn(this.expression)(model, observer, filterRegistry);
+ for (var i = 0; i < this.filters.length; i++) {
+ value = this.filters[i].transform(model, observer, filterRegistry,
+ false, [value]);
+ }
+
+ return value;
+ },
+
+ setValue: function(model, newValue, filterRegistry) {
+ var count = this.filters ? this.filters.length : 0;
+ while (count-- > 0) {
+ newValue = this.filters[count].transform(model, undefined,
+ filterRegistry, true, [newValue]);
+ }
+
+ if (this.expression.setValue)
+ return this.expression.setValue(model, newValue);
+ }
+ }
+
+ /**
+ * Converts a style property name to a css property name. For example:
+ * "WebkitUserSelect" to "-webkit-user-select"
+ */
+ function convertStylePropertyName(name) {
+ return String(name).replace(/[A-Z]/g, function(c) {
+ return '-' + c.toLowerCase();
+ });
+ }
+
+ var parentScopeName = '@' + Math.random().toString(36).slice(2);
+
+ // Single ident paths must bind directly to the appropriate scope object.
+ // I.e. Pushed values in two-bindings need to be assigned to the actual model
+ // object.
+ function findScope(model, prop) {
+ while (model[parentScopeName] &&
+ !Object.prototype.hasOwnProperty.call(model, prop)) {
+ model = model[parentScopeName];
+ }
+
+ return model;
+ }
+
+ function isLiteralExpression(pathString) {
+ switch (pathString) {
+ case '':
+ return false;
+
+ case 'false':
+ case 'null':
+ case 'true':
+ return true;
+ }
+
+ if (!isNaN(Number(pathString)))
+ return true;
+
+ return false;
+ };
+
+ function PolymerExpressions() {}
+
+ PolymerExpressions.prototype = {
+ // "built-in" filters
+ styleObject: function(value) {
+ var parts = [];
+ for (var key in value) {
+ parts.push(convertStylePropertyName(key) + ': ' + value[key]);
+ }
+ return parts.join('; ');
+ },
+
+ tokenList: function(value) {
+ var tokens = [];
+ for (var key in value) {
+ if (value[key])
+ tokens.push(key);
+ }
+ return tokens.join(' ');
+ },
+
+ // binding delegate API
+ prepareInstancePositionChanged: function(template) {
+ var indexIdent = template.polymerExpressionIndexIdent_;
+ if (!indexIdent)
+ return;
+
+ return function(templateInstance, index) {
+ templateInstance.model[indexIdent] = index;
+ };
+ },
+
+ prepareBinding: function(pathString, name, node) {
+ var path = Path.get(pathString);
+
+ if (!isLiteralExpression(pathString) && path.valid) {
+ if (path.length == 1) {
+ return function(model, node, oneTime) {
+ if (oneTime)
+ return path.getValueFrom(model);
+
+ var scope = findScope(model, path[0]);
+ return new PathObserver(scope, path);
+ };
+ }
+ return; // bail out early if pathString is simple path.
+ }
+
+ return prepareBinding(pathString, name, node, this);
+ },
+
+ prepareInstanceModel: function(template) {
+ var scopeName = template.polymerExpressionScopeIdent_;
+ if (!scopeName)
+ return;
+
+ var parentScope = template.templateInstance ?
+ template.templateInstance.model :
+ template.model;
+
+ var indexName = template.polymerExpressionIndexIdent_;
+
+ return function(model) {
+ return createScopeObject(parentScope, model, scopeName, indexName);
+ };
+ }
+ };
+
+ var createScopeObject = ('__proto__' in {}) ?
+ function(parentScope, model, scopeName, indexName) {
+ var scope = {};
+ scope[scopeName] = model;
+ scope[indexName] = undefined;
+ scope[parentScopeName] = parentScope;
+ scope.__proto__ = parentScope;
+ return scope;
+ } :
+ function(parentScope, model, scopeName, indexName) {
+ var scope = Object.create(parentScope);
+ Object.defineProperty(scope, scopeName,
+ { value: model, configurable: true, writable: true });
+ Object.defineProperty(scope, indexName,
+ { value: undefined, configurable: true, writable: true });
+ Object.defineProperty(scope, parentScopeName,
+ { value: parentScope, configurable: true, writable: true });
+ return scope;
+ };
+
+ global.PolymerExpressions = PolymerExpressions;
+ PolymerExpressions.getExpression = getExpression;
+})(this);
+
+Polymer = {
+ version: '0.5.1'
+};
+
+// TODO(sorvell): this ensures Polymer is an object and not a function
+// Platform is currently defining it as a function to allow for async loading
+// of polymer; once we refine the loading process this likely goes away.
+if (typeof window.Polymer === 'function') {
+ Polymer = {};
+}
+
+
+(function(scope) {
+
+ function withDependencies(task, depends) {
+ depends = depends || [];
+ if (!depends.map) {
+ depends = [depends];
+ }
+ return task.apply(this, depends.map(marshal));
+ }
+
+ function module(name, dependsOrFactory, moduleFactory) {
+ var module;
+ switch (arguments.length) {
+ case 0:
+ return;
+ case 1:
+ module = null;
+ break;
+ case 2:
+ // dependsOrFactory is `factory` in this case
+ module = dependsOrFactory.apply(this);
+ break;
+ default:
+ // dependsOrFactory is `depends` in this case
+ module = withDependencies(moduleFactory, dependsOrFactory);
+ break;
+ }
+ modules[name] = module;
+ };
+
+ function marshal(name) {
+ return modules[name];
+ }
+
+ var modules = {};
+
+ function using(depends, task) {
+ HTMLImports.whenImportsReady(function() {
+ withDependencies(task, depends);
+ });
+ };
+
+ // exports
+
+ scope.marshal = marshal;
+ // `module` confuses commonjs detectors
+ scope.modularize = module;
+ scope.using = using;
+
+})(window);
+
+/*
+ Build only script.
+
+ Ensures scripts needed for basic x-platform compatibility
+ will be run when platform.js is not loaded.
+ */
+if (!window.WebComponents) {
+
+/*
+ On supported platforms, platform.js is not needed. To retain compatibility
+ with the polyfills, we stub out minimal functionality.
+ */
+if (!window.WebComponents) {
+
+ WebComponents = {
+ flush: function() {},
+ flags: {log: {}}
+ };
+
+ Platform = WebComponents;
+
+ CustomElements = {
+ useNative: true,
+ ready: true,
+ takeRecords: function() {},
+ instanceof: function(obj, base) {
+ return obj instanceof base;
+ }
+ };
+
+ HTMLImports = {
+ useNative: true
+ };
+
+
+ addEventListener('HTMLImportsLoaded', function() {
+ document.dispatchEvent(
+ new CustomEvent('WebComponentsReady', {bubbles: true})
+ );
+ });
+
+
+ // ShadowDOM
+ ShadowDOMPolyfill = null;
+ wrap = unwrap = function(n){
+ return n;
+ };
+
+}
+
+/*
+ Create polyfill scope and feature detect native support.
+*/
+window.HTMLImports = window.HTMLImports || {flags:{}};
+
+(function(scope) {
+
+/**
+ Basic setup and simple module executer. We collect modules and then execute
+ the code later, only if it's necessary for polyfilling.
+*/
+var IMPORT_LINK_TYPE = 'import';
+var useNative = Boolean(IMPORT_LINK_TYPE in document.createElement('link'));
+
+/**
+ Support `currentScript` on all browsers as `document._currentScript.`
+
+ NOTE: We cannot polyfill `document.currentScript` because it's not possible
+ both to override and maintain the ability to capture the native value.
+ Therefore we choose to expose `_currentScript` both when native imports
+ and the polyfill are in use.
+*/
+// NOTE: ShadowDOMPolyfill intrusion.
+var hasShadowDOMPolyfill = Boolean(window.ShadowDOMPolyfill);
+var wrap = function(node) {
+ return hasShadowDOMPolyfill ? ShadowDOMPolyfill.wrapIfNeeded(node) : node;
+};
+var rootDocument = wrap(document);
+
+var currentScriptDescriptor = {
+ get: function() {
+ var script = HTMLImports.currentScript || document.currentScript ||
+ // NOTE: only works when called in synchronously executing code.
+ // readyState should check if `loading` but IE10 is
+ // interactive when scripts run so we cheat.
+ (document.readyState !== 'complete' ?
+ document.scripts[document.scripts.length - 1] : null);
+ return wrap(script);
+ },
+ configurable: true
+};
+
+Object.defineProperty(document, '_currentScript', currentScriptDescriptor);
+Object.defineProperty(rootDocument, '_currentScript', currentScriptDescriptor);
+
+/**
+ Add support for the `HTMLImportsLoaded` event and the `HTMLImports.whenReady`
+ method. This api is necessary because unlike the native implementation,
+ script elements do not force imports to resolve. Instead, users should wrap
+ code in either an `HTMLImportsLoaded` hander or after load time in an
+ `HTMLImports.whenReady(callback)` call.
+
+ NOTE: This module also supports these apis under the native implementation.
+ Therefore, if this file is loaded, the same code can be used under both
+ the polyfill and native implementation.
+ */
+
+var isIE = /Trident/.test(navigator.userAgent);
+
+// call a callback when all HTMLImports in the document at call time
+// (or at least document ready) have loaded.
+// 1. ensure the document is in a ready state (has dom), then
+// 2. watch for loading of imports and call callback when done
+function whenReady(callback, doc) {
+ doc = doc || rootDocument;
+ // if document is loading, wait and try again
+ whenDocumentReady(function() {
+ watchImportsLoad(callback, doc);
+ }, doc);
+}
+
+// call the callback when the document is in a ready state (has dom)
+var requiredReadyState = isIE ? 'complete' : 'interactive';
+var READY_EVENT = 'readystatechange';
+function isDocumentReady(doc) {
+ return (doc.readyState === 'complete' ||
+ doc.readyState === requiredReadyState);
+}
+
+// call <callback> when we ensure the document is in a ready state
+function whenDocumentReady(callback, doc) {
+ if (!isDocumentReady(doc)) {
+ var checkReady = function() {
+ if (doc.readyState === 'complete' ||
+ doc.readyState === requiredReadyState) {
+ doc.removeEventListener(READY_EVENT, checkReady);
+ whenDocumentReady(callback, doc);
+ }
+ };
+ doc.addEventListener(READY_EVENT, checkReady);
+ } else if (callback) {
+ callback();
+ }
+}
+
+function markTargetLoaded(event) {
+ event.target.__loaded = true;
+}
+
+// call <callback> when we ensure all imports have loaded
+function watchImportsLoad(callback, doc) {
+ var imports = doc.querySelectorAll('link[rel=import]');
+ var loaded = 0, l = imports.length;
+ function checkDone(d) {
+ if ((loaded == l) && callback) {
+ callback();
+ }
+ }
+ function loadedImport(e) {
+ markTargetLoaded(e);
+ loaded++;
+ checkDone();
+ }
+ if (l) {
+ for (var i=0, imp; (i<l) && (imp=imports[i]); i++) {
+ if (isImportLoaded(imp)) {
+ loadedImport.call(imp, {target: imp});
+ } else {
+ imp.addEventListener('load', loadedImport);
+ imp.addEventListener('error', loadedImport);
+ }
+ }
+ } else {
+ checkDone();
+ }
+}
+
+// NOTE: test for native imports loading is based on explicitly watching
+// all imports (see below).
+// However, we cannot rely on this entirely without watching the entire document
+// for import links. For perf reasons, currently only head is watched.
+// Instead, we fallback to checking if the import property is available
+// and the document is not itself loading.
+function isImportLoaded(link) {
+ return useNative ? link.__loaded ||
+ (link.import && link.import.readyState !== 'loading') :
+ link.__importParsed;
+}
+
+// TODO(sorvell): Workaround for
+// https://www.w3.org/Bugs/Public/show_bug.cgi?id=25007, should be removed when
+// this bug is addressed.
+// (1) Install a mutation observer to see when HTMLImports have loaded
+// (2) if this script is run during document load it will watch any existing
+// imports for loading.
+//
+// NOTE: The workaround has restricted functionality: (1) it's only compatible
+// with imports that are added to document.head since the mutation observer
+// watches only head for perf reasons, (2) it requires this script
+// to run before any imports have completed loading.
+if (useNative) {
+ new MutationObserver(function(mxns) {
+ for (var i=0, l=mxns.length, m; (i < l) && (m=mxns[i]); i++) {
+ if (m.addedNodes) {
+ handleImports(m.addedNodes);
+ }
+ }
+ }).observe(document.head, {childList: true});
+
+ function handleImports(nodes) {
+ for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) {
+ if (isImport(n)) {
+ handleImport(n);
+ }
+ }
+ }
+
+ function isImport(element) {
+ return element.localName === 'link' && element.rel === 'import';
+ }
+
+ function handleImport(element) {
+ var loaded = element.import;
+ if (loaded) {
+ markTargetLoaded({target: element});
+ } else {
+ element.addEventListener('load', markTargetLoaded);
+ element.addEventListener('error', markTargetLoaded);
+ }
+ }
+
+ // make sure to catch any imports that are in the process of loading
+ // when this script is run.
+ (function() {
+ if (document.readyState === 'loading') {
+ var imports = document.querySelectorAll('link[rel=import]');
+ for (var i=0, l=imports.length, imp; (i<l) && (imp=imports[i]); i++) {
+ handleImport(imp);
+ }
+ }
+ })();
+
+}
+
+// Fire the 'HTMLImportsLoaded' event when imports in document at load time
+// have loaded. This event is required to simulate the script blocking
+// behavior of native imports. A main document script that needs to be sure
+// imports have loaded should wait for this event.
+whenReady(function() {
+ HTMLImports.ready = true;
+ HTMLImports.readyTime = new Date().getTime();
+ rootDocument.dispatchEvent(
+ new CustomEvent('HTMLImportsLoaded', {bubbles: true})
+ );
+});
+
+// exports
+scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE;
+scope.useNative = useNative;
+scope.rootDocument = rootDocument;
+scope.whenReady = whenReady;
+scope.isIE = isIE;
+
+})(HTMLImports);
+
+(function(scope) {
+
+ // TODO(sorvell): It's desireable to provide a default stylesheet
+ // that's convenient for styling unresolved elements, but
+ // it's cumbersome to have to include this manually in every page.
+ // It would make sense to put inside some HTMLImport but
+ // the HTMLImports polyfill does not allow loading of stylesheets
+ // that block rendering. Therefore this injection is tolerated here.
+ var style = document.createElement('style');
+ style.textContent = ''
+ + 'body {'
+ + 'transition: opacity ease-in 0.2s;'
+ + ' } \n'
+ + 'body[unresolved] {'
+ + 'opacity: 0; display: block; overflow: hidden;'
+ + ' } \n'
+ ;
+ var head = document.querySelector('head');
+ head.insertBefore(style, head.firstChild);
+
+})(Platform);
+
+/*
+ Build only script.
+
+ Ensures scripts needed for basic x-platform compatibility
+ will be run when platform.js is not loaded.
+ */
+}
+(function(global) {
+ 'use strict';
+
+ var testingExposeCycleCount = global.testingExposeCycleCount;
+
+ // Detect and do basic sanity checking on Object/Array.observe.
+ function detectObjectObserve() {
+ if (typeof Object.observe !== 'function' ||
+ typeof Array.observe !== 'function') {
+ return false;
+ }
+
+ var records = [];
+
+ function callback(recs) {
+ records = recs;
+ }
+
+ var test = {};
+ var arr = [];
+ Object.observe(test, callback);
+ Array.observe(arr, callback);
+ test.id = 1;
+ test.id = 2;
+ delete test.id;
+ arr.push(1, 2);
+ arr.length = 0;
+
+ Object.deliverChangeRecords(callback);
+ if (records.length !== 5)
+ return false;
+
+ if (records[0].type != 'add' ||
+ records[1].type != 'update' ||
+ records[2].type != 'delete' ||
+ records[3].type != 'splice' ||
+ records[4].type != 'splice') {
+ return false;
+ }
+
+ Object.unobserve(test, callback);
+ Array.unobserve(arr, callback);
+
+ return true;
+ }
+
+ var hasObserve = detectObjectObserve();
+
+ function detectEval() {
+ // Don't test for eval if we're running in a Chrome App environment.
+ // We check for APIs set that only exist in a Chrome App context.
+ if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) {
+ return false;
+ }
+
+ // Firefox OS Apps do not allow eval. This feature detection is very hacky
+ // but even if some other platform adds support for this function this code
+ // will continue to work.
+ if (typeof navigator != 'undefined' && navigator.getDeviceStorage) {
+ return false;
+ }
+
+ try {
+ var f = new Function('', 'return true;');
+ return f();
+ } catch (ex) {
+ return false;
+ }
+ }
+
+ var hasEval = detectEval();
+
+ function isIndex(s) {
+ return +s === s >>> 0 && s !== '';
+ }
+
+ function toNumber(s) {
+ return +s;
+ }
+
+ function isObject(obj) {
+ return obj === Object(obj);
+ }
+
+ var numberIsNaN = global.Number.isNaN || function(value) {
+ return typeof value === 'number' && global.isNaN(value);
+ }
+
+ function areSameValue(left, right) {
+ if (left === right)
+ return left !== 0 || 1 / left === 1 / right;
+ if (numberIsNaN(left) && numberIsNaN(right))
+ return true;
+
+ return left !== left && right !== right;
+ }
+
+ var createObject = ('__proto__' in {}) ?
+ function(obj) { return obj; } :
+ function(obj) {
+ var proto = obj.__proto__;
+ if (!proto)
+ return obj;
+ var newObject = Object.create(proto);
+ Object.getOwnPropertyNames(obj).forEach(function(name) {
+ Object.defineProperty(newObject, name,
+ Object.getOwnPropertyDescriptor(obj, name));
+ });
+ return newObject;
+ };
+
+ var identStart = '[\$_a-zA-Z]';
+ var identPart = '[\$_a-zA-Z0-9]';
+ var identRegExp = new RegExp('^' + identStart + '+' + identPart + '*' + '$');
+
+ function getPathCharType(char) {
+ if (char === undefined)
+ return 'eof';
+
+ var code = char.charCodeAt(0);
+
+ switch(code) {
+ case 0x5B: // [
+ case 0x5D: // ]
+ case 0x2E: // .
+ case 0x22: // "
+ case 0x27: // '
+ case 0x30: // 0
+ return char;
+
+ case 0x5F: // _
+ case 0x24: // $
+ return 'ident';
+
+ case 0x20: // Space
+ case 0x09: // Tab
+ case 0x0A: // Newline
+ case 0x0D: // Return
+ case 0xA0: // No-break space
+ case 0xFEFF: // Byte Order Mark
+ case 0x2028: // Line Separator
+ case 0x2029: // Paragraph Separator
+ return 'ws';
+ }
+
+ // a-z, A-Z
+ if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A))
+ return 'ident';
+
+ // 1-9
+ if (0x31 <= code && code <= 0x39)
+ return 'number';
+
+ return 'else';
+ }
+
+ var pathStateMachine = {
+ 'beforePath': {
+ 'ws': ['beforePath'],
+ 'ident': ['inIdent', 'append'],
+ '[': ['beforeElement'],
+ 'eof': ['afterPath']
+ },
+
+ 'inPath': {
+ 'ws': ['inPath'],
+ '.': ['beforeIdent'],
+ '[': ['beforeElement'],
+ 'eof': ['afterPath']
+ },
+
+ 'beforeIdent': {
+ 'ws': ['beforeIdent'],
+ 'ident': ['inIdent', 'append']
+ },
+
+ 'inIdent': {
+ 'ident': ['inIdent', 'append'],
+ '0': ['inIdent', 'append'],
+ 'number': ['inIdent', 'append'],
+ 'ws': ['inPath', 'push'],
+ '.': ['beforeIdent', 'push'],
+ '[': ['beforeElement', 'push'],
+ 'eof': ['afterPath', 'push']
+ },
+
+ 'beforeElement': {
+ 'ws': ['beforeElement'],
+ '0': ['afterZero', 'append'],
+ 'number': ['inIndex', 'append'],
+ "'": ['inSingleQuote', 'append', ''],
+ '"': ['inDoubleQuote', 'append', '']
+ },
+
+ 'afterZero': {
+ 'ws': ['afterElement', 'push'],
+ ']': ['inPath', 'push']
+ },
+
+ 'inIndex': {
+ '0': ['inIndex', 'append'],
+ 'number': ['inIndex', 'append'],
+ 'ws': ['afterElement'],
+ ']': ['inPath', 'push']
+ },
+
+ 'inSingleQuote': {
+ "'": ['afterElement'],
+ 'eof': ['error'],
+ 'else': ['inSingleQuote', 'append']
+ },
+
+ 'inDoubleQuote': {
+ '"': ['afterElement'],
+ 'eof': ['error'],
+ 'else': ['inDoubleQuote', 'append']
+ },
+
+ 'afterElement': {
+ 'ws': ['afterElement'],
+ ']': ['inPath', 'push']
+ }
+ }
+
+ function noop() {}
+
+ function parsePath(path) {
+ var keys = [];
+ var index = -1;
+ var c, newChar, key, type, transition, action, typeMap, mode = 'beforePath';
+
+ var actions = {
+ push: function() {
+ if (key === undefined)
+ return;
+
+ keys.push(key);
+ key = undefined;
+ },
+
+ append: function() {
+ if (key === undefined)
+ key = newChar
+ else
+ key += newChar;
+ }
+ };
+
+ function maybeUnescapeQuote() {
+ if (index >= path.length)
+ return;
+
+ var nextChar = path[index + 1];
+ if ((mode == 'inSingleQuote' && nextChar == "'") ||
+ (mode == 'inDoubleQuote' && nextChar == '"')) {
+ index++;
+ newChar = nextChar;
+ actions.append();
+ return true;
+ }
+ }
+
+ while (mode) {
+ index++;
+ c = path[index];
+
+ if (c == '\\' && maybeUnescapeQuote(mode))
+ continue;
+
+ type = getPathCharType(c);
+ typeMap = pathStateMachine[mode];
+ transition = typeMap[type] || typeMap['else'] || 'error';
+
+ if (transition == 'error')
+ return; // parse error;
+
+ mode = transition[0];
+ action = actions[transition[1]] || noop;
+ newChar = transition[2] === undefined ? c : transition[2];
+ action();
+
+ if (mode === 'afterPath') {
+ return keys;
+ }
+ }
+
+ return; // parse error
+ }
+
+ function isIdent(s) {
+ return identRegExp.test(s);
+ }
+
+ var constructorIsPrivate = {};
+
+ function Path(parts, privateToken) {
+ if (privateToken !== constructorIsPrivate)
+ throw Error('Use Path.get to retrieve path objects');
+
+ for (var i = 0; i < parts.length; i++) {
+ this.push(String(parts[i]));
+ }
+
+ if (hasEval && this.length) {
+ this.getValueFrom = this.compiledGetValueFromFn();
+ }
+ }
+
+ // TODO(rafaelw): Make simple LRU cache
+ var pathCache = {};
+
+ function getPath(pathString) {
+ if (pathString instanceof Path)
+ return pathString;
+
+ if (pathString == null || pathString.length == 0)
+ pathString = '';
+
+ if (typeof pathString != 'string') {
+ if (isIndex(pathString.length)) {
+ // Constructed with array-like (pre-parsed) keys
+ return new Path(pathString, constructorIsPrivate);
+ }
+
+ pathString = String(pathString);
+ }
+
+ var path = pathCache[pathString];
+ if (path)
+ return path;
+
+ var parts = parsePath(pathString);
+ if (!parts)
+ return invalidPath;
+
+ var path = new Path(parts, constructorIsPrivate);
+ pathCache[pathString] = path;
+ return path;
+ }
+
+ Path.get = getPath;
+
+ function formatAccessor(key) {
+ if (isIndex(key)) {
+ return '[' + key + ']';
+ } else {
+ return '["' + key.replace(/"/g, '\\"') + '"]';
+ }
+ }
+
+ Path.prototype = createObject({
+ __proto__: [],
+ valid: true,
+
+ toString: function() {
+ var pathString = '';
+ for (var i = 0; i < this.length; i++) {
+ var key = this[i];
+ if (isIdent(key)) {
+ pathString += i ? '.' + key : key;
+ } else {
+ pathString += formatAccessor(key);
+ }
+ }
+
+ return pathString;
+ },
+
+ getValueFrom: function(obj, directObserver) {
+ for (var i = 0; i < this.length; i++) {
+ if (obj == null)
+ return;
+ obj = obj[this[i]];
+ }
+ return obj;
+ },
+
+ iterateObjects: function(obj, observe) {
+ for (var i = 0; i < this.length; i++) {
+ if (i)
+ obj = obj[this[i - 1]];
+ if (!isObject(obj))
+ return;
+ observe(obj, this[i]);
+ }
+ },
+
+ compiledGetValueFromFn: function() {
+ var str = '';
+ var pathString = 'obj';
+ str += 'if (obj != null';
+ var i = 0;
+ var key;
+ for (; i < (this.length - 1); i++) {
+ key = this[i];
+ pathString += isIdent(key) ? '.' + key : formatAccessor(key);
+ str += ' &&\n ' + pathString + ' != null';
+ }
+ str += ')\n';
+
+ var key = this[i];
+ pathString += isIdent(key) ? '.' + key : formatAccessor(key);
+
+ str += ' return ' + pathString + ';\nelse\n return undefined;';
+ return new Function('obj', str);
+ },
+
+ setValueFrom: function(obj, value) {
+ if (!this.length)
+ return false;
+
+ for (var i = 0; i < this.length - 1; i++) {
+ if (!isObject(obj))
+ return false;
+ obj = obj[this[i]];
+ }
+
+ if (!isObject(obj))
+ return false;
+
+ obj[this[i]] = value;
+ return true;
+ }
+ });
+
+ var invalidPath = new Path('', constructorIsPrivate);
+ invalidPath.valid = false;
+ invalidPath.getValueFrom = invalidPath.setValueFrom = function() {};
+
+ var MAX_DIRTY_CHECK_CYCLES = 1000;
+
+ function dirtyCheck(observer) {
+ var cycles = 0;
+ while (cycles < MAX_DIRTY_CHECK_CYCLES && observer.check_()) {
+ cycles++;
+ }
+ if (testingExposeCycleCount)
+ global.dirtyCheckCycleCount = cycles;
+
+ return cycles > 0;
+ }
+
+ function objectIsEmpty(object) {
+ for (var prop in object)
+ return false;
+ return true;
+ }
+
+ function diffIsEmpty(diff) {
+ return objectIsEmpty(diff.added) &&
+ objectIsEmpty(diff.removed) &&
+ objectIsEmpty(diff.changed);
+ }
+
+ function diffObjectFromOldObject(object, oldObject) {
+ var added = {};
+ var removed = {};
+ var changed = {};
+
+ for (var prop in oldObject) {
+ var newValue = object[prop];
+
+ if (newValue !== undefined && newValue === oldObject[prop])
+ continue;
+
+ if (!(prop in object)) {
+ removed[prop] = undefined;
+ continue;
+ }
+
+ if (newValue !== oldObject[prop])
+ changed[prop] = newValue;
+ }
+
+ for (var prop in object) {
+ if (prop in oldObject)
+ continue;
+
+ added[prop] = object[prop];
+ }
+
+ if (Array.isArray(object) && object.length !== oldObject.length)
+ changed.length = object.length;
+
+ return {
+ added: added,
+ removed: removed,
+ changed: changed
+ };
+ }
+
+ var eomTasks = [];
+ function runEOMTasks() {
+ if (!eomTasks.length)
+ return false;
+
+ for (var i = 0; i < eomTasks.length; i++) {
+ eomTasks[i]();
+ }
+ eomTasks.length = 0;
+ return true;
+ }
+
+ var runEOM = hasObserve ? (function(){
+ return function(fn) {
+ return Promise.resolve().then(fn);
+ }
+ })() :
+ (function() {
+ return function(fn) {
+ eomTasks.push(fn);
+ };
+ })();
+
+ var observedObjectCache = [];
+
+ function newObservedObject() {
+ var observer;
+ var object;
+ var discardRecords = false;
+ var first = true;
+
+ function callback(records) {
+ if (observer && observer.state_ === OPENED && !discardRecords)
+ observer.check_(records);
+ }
+
+ return {
+ open: function(obs) {
+ if (observer)
+ throw Error('ObservedObject in use');
+
+ if (!first)
+ Object.deliverChangeRecords(callback);
+
+ observer = obs;
+ first = false;
+ },
+ observe: function(obj, arrayObserve) {
+ object = obj;
+ if (arrayObserve)
+ Array.observe(object, callback);
+ else
+ Object.observe(object, callback);
+ },
+ deliver: function(discard) {
+ discardRecords = discard;
+ Object.deliverChangeRecords(callback);
+ discardRecords = false;
+ },
+ close: function() {
+ observer = undefined;
+ Object.unobserve(object, callback);
+ observedObjectCache.push(this);
+ }
+ };
+ }
+
+ /*
+ * The observedSet abstraction is a perf optimization which reduces the total
+ * number of Object.observe observations of a set of objects. The idea is that
+ * groups of Observers will have some object dependencies in common and this
+ * observed set ensures that each object in the transitive closure of
+ * dependencies is only observed once. The observedSet acts as a write barrier
+ * such that whenever any change comes through, all Observers are checked for
+ * changed values.
+ *
+ * Note that this optimization is explicitly moving work from setup-time to
+ * change-time.
+ *
+ * TODO(rafaelw): Implement "garbage collection". In order to move work off
+ * the critical path, when Observers are closed, their observed objects are
+ * not Object.unobserve(d). As a result, it's possible that if the observedSet
+ * is kept open, but some Observers have been closed, it could cause "leaks"
+ * (prevent otherwise collectable objects from being collected). At some
+ * point, we should implement incremental "gc" which keeps a list of
+ * observedSets which may need clean-up and does small amounts of cleanup on a
+ * timeout until all is clean.
+ */
+
+ function getObservedObject(observer, object, arrayObserve) {
+ var dir = observedObjectCache.pop() || newObservedObject();
+ dir.open(observer);
+ dir.observe(object, arrayObserve);
+ return dir;
+ }
+
+ var observedSetCache = [];
+
+ function newObservedSet() {
+ var observerCount = 0;
+ var observers = [];
+ var objects = [];
+ var rootObj;
+ var rootObjProps;
+
+ function observe(obj, prop) {
+ if (!obj)
+ return;
+
+ if (obj === rootObj)
+ rootObjProps[prop] = true;
+
+ if (objects.indexOf(obj) < 0) {
+ objects.push(obj);
+ Object.observe(obj, callback);
+ }
+
+ observe(Object.getPrototypeOf(obj), prop);
+ }
+
+ function allRootObjNonObservedProps(recs) {
+ for (var i = 0; i < recs.length; i++) {
+ var rec = recs[i];
+ if (rec.object !== rootObj ||
+ rootObjProps[rec.name] ||
+ rec.type === 'setPrototype') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function callback(recs) {
+ if (allRootObjNonObservedProps(recs))
+ return;
+
+ var observer;
+ for (var i = 0; i < observers.length; i++) {
+ observer = observers[i];
+ if (observer.state_ == OPENED) {
+ observer.iterateObjects_(observe);
+ }
+ }
+
+ for (var i = 0; i < observers.length; i++) {
+ observer = observers[i];
+ if (observer.state_ == OPENED) {
+ observer.check_();
+ }
+ }
+ }
+
+ var record = {
+ objects: objects,
+ get rootObject() { return rootObj; },
+ set rootObject(value) {
+ rootObj = value;
+ rootObjProps = {};
+ },
+ open: function(obs, object) {
+ observers.push(obs);
+ observerCount++;
+ obs.iterateObjects_(observe);
+ },
+ close: function(obs) {
+ observerCount--;
+ if (observerCount > 0) {
+ return;
+ }
+
+ for (var i = 0; i < objects.length; i++) {
+ Object.unobserve(objects[i], callback);
+ Observer.unobservedCount++;
+ }
+
+ observers.length = 0;
+ objects.length = 0;
+ rootObj = undefined;
+ rootObjProps = undefined;
+ observedSetCache.push(this);
+ if (lastObservedSet === this)
+ lastObservedSet = null;
+ },
+ };
+
+ return record;
+ }
+
+ var lastObservedSet;
+
+ function getObservedSet(observer, obj) {
+ if (!lastObservedSet || lastObservedSet.rootObject !== obj) {
+ lastObservedSet = observedSetCache.pop() || newObservedSet();
+ lastObservedSet.rootObject = obj;
+ }
+ lastObservedSet.open(observer, obj);
+ return lastObservedSet;
+ }
+
+ var UNOPENED = 0;
+ var OPENED = 1;
+ var CLOSED = 2;
+ var RESETTING = 3;
+
+ var nextObserverId = 1;
+
+ function Observer() {
+ this.state_ = UNOPENED;
+ this.callback_ = undefined;
+ this.target_ = undefined; // TODO(rafaelw): Should be WeakRef
+ this.directObserver_ = undefined;
+ this.value_ = undefined;
+ this.id_ = nextObserverId++;
+ }
+
+ Observer.prototype = {
+ open: function(callback, target) {
+ if (this.state_ != UNOPENED)
+ throw Error('Observer has already been opened.');
+
+ addToAll(this);
+ this.callback_ = callback;
+ this.target_ = target;
+ this.connect_();
+ this.state_ = OPENED;
+ return this.value_;
+ },
+
+ close: function() {
+ if (this.state_ != OPENED)
+ return;
+
+ removeFromAll(this);
+ this.disconnect_();
+ this.value_ = undefined;
+ this.callback_ = undefined;
+ this.target_ = undefined;
+ this.state_ = CLOSED;
+ },
+
+ deliver: function() {
+ if (this.state_ != OPENED)
+ return;
+
+ dirtyCheck(this);
+ },
+
+ report_: function(changes) {
+ try {
+ this.callback_.apply(this.target_, changes);
+ } catch (ex) {
+ Observer._errorThrownDuringCallback = true;
+ console.error('Exception caught during observer callback: ' +
+ (ex.stack || ex));
+ }
+ },
+
+ discardChanges: function() {
+ this.check_(undefined, true);
+ return this.value_;
+ }
+ }
+
+ var collectObservers = !hasObserve;
+ var allObservers;
+ Observer._allObserversCount = 0;
+
+ if (collectObservers) {
+ allObservers = [];
+ }
+
+ function addToAll(observer) {
+ Observer._allObserversCount++;
+ if (!collectObservers)
+ return;
+
+ allObservers.push(observer);
+ }
+
+ function removeFromAll(observer) {
+ Observer._allObserversCount--;
+ }
+
+ var runningMicrotaskCheckpoint = false;
+
+ global.Platform = global.Platform || {};
+
+ global.Platform.performMicrotaskCheckpoint = function() {
+ if (runningMicrotaskCheckpoint)
+ return;
+
+ if (!collectObservers)
+ return;
+
+ runningMicrotaskCheckpoint = true;
+
+ var cycles = 0;
+ var anyChanged, toCheck;
+
+ do {
+ cycles++;
+ toCheck = allObservers;
+ allObservers = [];
+ anyChanged = false;
+
+ for (var i = 0; i < toCheck.length; i++) {
+ var observer = toCheck[i];
+ if (observer.state_ != OPENED)
+ continue;
+
+ if (observer.check_())
+ anyChanged = true;
+
+ allObservers.push(observer);
+ }
+ if (runEOMTasks())
+ anyChanged = true;
+ } while (cycles < MAX_DIRTY_CHECK_CYCLES && anyChanged);
+
+ if (testingExposeCycleCount)
+ global.dirtyCheckCycleCount = cycles;
+
+ runningMicrotaskCheckpoint = false;
+ };
+
+ if (collectObservers) {
+ global.Platform.clearObservers = function() {
+ allObservers = [];
+ };
+ }
+
+ function ObjectObserver(object) {
+ Observer.call(this);
+ this.value_ = object;
+ this.oldObject_ = undefined;
+ }
+
+ ObjectObserver.prototype = createObject({
+ __proto__: Observer.prototype,
+
+ arrayObserve: false,
+
+ connect_: function(callback, target) {
+ if (hasObserve) {
+ this.directObserver_ = getObservedObject(this, this.value_,
+ this.arrayObserve);
+ } else {
+ this.oldObject_ = this.copyObject(this.value_);
+ }
+
+ },
+
+ copyObject: function(object) {
+ var copy = Array.isArray(object) ? [] : {};
+ for (var prop in object) {
+ copy[prop] = object[prop];
+ };
+ if (Array.isArray(object))
+ copy.length = object.length;
+ return copy;
+ },
+
+ check_: function(changeRecords, skipChanges) {
+ var diff;
+ var oldValues;
+ if (hasObserve) {
+ if (!changeRecords)
+ return false;
+
+ oldValues = {};
+ diff = diffObjectFromChangeRecords(this.value_, changeRecords,
+ oldValues);
+ } else {
+ oldValues = this.oldObject_;
+ diff = diffObjectFromOldObject(this.value_, this.oldObject_);
+ }
+
+ if (diffIsEmpty(diff))
+ return false;
+
+ if (!hasObserve)
+ this.oldObject_ = this.copyObject(this.value_);
+
+ this.report_([
+ diff.added || {},
+ diff.removed || {},
+ diff.changed || {},
+ function(property) {
+ return oldValues[property];
+ }
+ ]);
+
+ return true;
+ },
+
+ disconnect_: function() {
+ if (hasObserve) {
+ this.directObserver_.close();
+ this.directObserver_ = undefined;
+ } else {
+ this.oldObject_ = undefined;
+ }
+ },
+
+ deliver: function() {
+ if (this.state_ != OPENED)
+ return;
+
+ if (hasObserve)
+ this.directObserver_.deliver(false);
+ else
+ dirtyCheck(this);
+ },
+
+ discardChanges: function() {
+ if (this.directObserver_)
+ this.directObserver_.deliver(true);
+ else
+ this.oldObject_ = this.copyObject(this.value_);
+
+ return this.value_;
+ }
+ });
+
+ function ArrayObserver(array) {
+ if (!Array.isArray(array))
+ throw Error('Provided object is not an Array');
+ ObjectObserver.call(this, array);
+ }
+
+ ArrayObserver.prototype = createObject({
+
+ __proto__: ObjectObserver.prototype,
+
+ arrayObserve: true,
+
+ copyObject: function(arr) {
+ return arr.slice();
+ },
+
+ check_: function(changeRecords) {
+ var splices;
+ if (hasObserve) {
+ if (!changeRecords)
+ return false;
+ splices = projectArraySplices(this.value_, changeRecords);
+ } else {
+ splices = calcSplices(this.value_, 0, this.value_.length,
+ this.oldObject_, 0, this.oldObject_.length);
+ }
+
+ if (!splices || !splices.length)
+ return false;
+
+ if (!hasObserve)
+ this.oldObject_ = this.copyObject(this.value_);
+
+ this.report_([splices]);
+ return true;
+ }
+ });
+
+ ArrayObserver.applySplices = function(previous, current, splices) {
+ splices.forEach(function(splice) {
+ var spliceArgs = [splice.index, splice.removed.length];
+ var addIndex = splice.index;
+ while (addIndex < splice.index + splice.addedCount) {
+ spliceArgs.push(current[addIndex]);
+ addIndex++;
+ }
+
+ Array.prototype.splice.apply(previous, spliceArgs);
+ });
+ };
+
+ function PathObserver(object, path) {
+ Observer.call(this);
+
+ this.object_ = object;
+ this.path_ = getPath(path);
+ this.directObserver_ = undefined;
+ }
+
+ PathObserver.prototype = createObject({
+ __proto__: Observer.prototype,
+
+ get path() {
+ return this.path_;
+ },
+
+ connect_: function() {
+ if (hasObserve)
+ this.directObserver_ = getObservedSet(this, this.object_);
+
+ this.check_(undefined, true);
+ },
+
+ disconnect_: function() {
+ this.value_ = undefined;
+
+ if (this.directObserver_) {
+ this.directObserver_.close(this);
+ this.directObserver_ = undefined;
+ }
+ },
+
+ iterateObjects_: function(observe) {
+ this.path_.iterateObjects(this.object_, observe);
+ },
+
+ check_: function(changeRecords, skipChanges) {
+ var oldValue = this.value_;
+ this.value_ = this.path_.getValueFrom(this.object_);
+ if (skipChanges || areSameValue(this.value_, oldValue))
+ return false;
+
+ this.report_([this.value_, oldValue, this]);
+ return true;
+ },
+
+ setValue: function(newValue) {
+ if (this.path_)
+ this.path_.setValueFrom(this.object_, newValue);
+ }
+ });
+
+ function CompoundObserver(reportChangesOnOpen) {
+ Observer.call(this);
+
+ this.reportChangesOnOpen_ = reportChangesOnOpen;
+ this.value_ = [];
+ this.directObserver_ = undefined;
+ this.observed_ = [];
+ }
+
+ var observerSentinel = {};
+
+ CompoundObserver.prototype = createObject({
+ __proto__: Observer.prototype,
+
+ connect_: function() {
+ if (hasObserve) {
+ var object;
+ var needsDirectObserver = false;
+ for (var i = 0; i < this.observed_.length; i += 2) {
+ object = this.observed_[i]
+ if (object !== observerSentinel) {
+ needsDirectObserver = true;
+ break;
+ }
+ }
+
+ if (needsDirectObserver)
+ this.directObserver_ = getObservedSet(this, object);
+ }
+
+ this.check_(undefined, !this.reportChangesOnOpen_);
+ },
+
+ disconnect_: function() {
+ for (var i = 0; i < this.observed_.length; i += 2) {
+ if (this.observed_[i] === observerSentinel)
+ this.observed_[i + 1].close();
+ }
+ this.observed_.length = 0;
+ this.value_.length = 0;
+
+ if (this.directObserver_) {
+ this.directObserver_.close(this);
+ this.directObserver_ = undefined;
+ }
+ },
+
+ addPath: function(object, path) {
+ if (this.state_ != UNOPENED && this.state_ != RESETTING)
+ throw Error('Cannot add paths once started.');
+
+ var path = getPath(path);
+ this.observed_.push(object, path);
+ if (!this.reportChangesOnOpen_)
+ return;
+ var index = this.observed_.length / 2 - 1;
+ this.value_[index] = path.getValueFrom(object);
+ },
+
+ addObserver: function(observer) {
+ if (this.state_ != UNOPENED && this.state_ != RESETTING)
+ throw Error('Cannot add observers once started.');
+
+ this.observed_.push(observerSentinel, observer);
+ if (!this.reportChangesOnOpen_)
+ return;
+ var index = this.observed_.length / 2 - 1;
+ this.value_[index] = observer.open(this.deliver, this);
+ },
+
+ startReset: function() {
+ if (this.state_ != OPENED)
+ throw Error('Can only reset while open');
+
+ this.state_ = RESETTING;
+ this.disconnect_();
+ },
+
+ finishReset: function() {
+ if (this.state_ != RESETTING)
+ throw Error('Can only finishReset after startReset');
+ this.state_ = OPENED;
+ this.connect_();
+
+ return this.value_;
+ },
+
+ iterateObjects_: function(observe) {
+ var object;
+ for (var i = 0; i < this.observed_.length; i += 2) {
+ object = this.observed_[i]
+ if (object !== observerSentinel)
+ this.observed_[i + 1].iterateObjects(object, observe)
+ }
+ },
+
+ check_: function(changeRecords, skipChanges) {
+ var oldValues;
+ for (var i = 0; i < this.observed_.length; i += 2) {
+ var object = this.observed_[i];
+ var path = this.observed_[i+1];
+ var value;
+ if (object === observerSentinel) {
+ var observable = path;
+ value = this.state_ === UNOPENED ?
+ observable.open(this.deliver, this) :
+ observable.discardChanges();
+ } else {
+ value = path.getValueFrom(object);
+ }
+
+ if (skipChanges) {
+ this.value_[i / 2] = value;
+ continue;
+ }
+
+ if (areSameValue(value, this.value_[i / 2]))
+ continue;
+
+ oldValues = oldValues || [];
+ oldValues[i / 2] = this.value_[i / 2];
+ this.value_[i / 2] = value;
+ }
+
+ if (!oldValues)
+ return false;
+
+ // TODO(rafaelw): Having observed_ as the third callback arg here is
+ // pretty lame API. Fix.
+ this.report_([this.value_, oldValues, this.observed_]);
+ return true;
+ }
+ });
+
+ function identFn(value) { return value; }
+
+ function ObserverTransform(observable, getValueFn, setValueFn,
+ dontPassThroughSet) {
+ this.callback_ = undefined;
+ this.target_ = undefined;
+ this.value_ = undefined;
+ this.observable_ = observable;
+ this.getValueFn_ = getValueFn || identFn;
+ this.setValueFn_ = setValueFn || identFn;
+ // TODO(rafaelw): This is a temporary hack. PolymerExpressions needs this
+ // at the moment because of a bug in it's dependency tracking.
+ this.dontPassThroughSet_ = dontPassThroughSet;
+ }
+
+ ObserverTransform.prototype = {
+ open: function(callback, target) {
+ this.callback_ = callback;
+ this.target_ = target;
+ this.value_ =
+ this.getValueFn_(this.observable_.open(this.observedCallback_, this));
+ return this.value_;
+ },
+
+ observedCallback_: function(value) {
+ value = this.getValueFn_(value);
+ if (areSameValue(value, this.value_))
+ return;
+ var oldValue = this.value_;
+ this.value_ = value;
+ this.callback_.call(this.target_, this.value_, oldValue);
+ },
+
+ discardChanges: function() {
+ this.value_ = this.getValueFn_(this.observable_.discardChanges());
+ return this.value_;
+ },
+
+ deliver: function() {
+ return this.observable_.deliver();
+ },
+
+ setValue: function(value) {
+ value = this.setValueFn_(value);
+ if (!this.dontPassThroughSet_ && this.observable_.setValue)
+ return this.observable_.setValue(value);
+ },
+
+ close: function() {
+ if (this.observable_)
+ this.observable_.close();
+ this.callback_ = undefined;
+ this.target_ = undefined;
+ this.observable_ = undefined;
+ this.value_ = undefined;
+ this.getValueFn_ = undefined;
+ this.setValueFn_ = undefined;
+ }
+ }
+
+ var expectedRecordTypes = {
+ add: true,
+ update: true,
+ delete: true
+ };
+
+ function diffObjectFromChangeRecords(object, changeRecords, oldValues) {
+ var added = {};
+ var removed = {};
+
+ for (var i = 0; i < changeRecords.length; i++) {
+ var record = changeRecords[i];
+ if (!expectedRecordTypes[record.type]) {
+ console.error('Unknown changeRecord type: ' + record.type);
+ console.error(record);
+ continue;
+ }
+
+ if (!(record.name in oldValues))
+ oldValues[record.name] = record.oldValue;
+
+ if (record.type == 'update')
+ continue;
+
+ if (record.type == 'add') {
+ if (record.name in removed)
+ delete removed[record.name];
+ else
+ added[record.name] = true;
+
+ continue;
+ }
+
+ // type = 'delete'
+ if (record.name in added) {
+ delete added[record.name];
+ delete oldValues[record.name];
+ } else {
+ removed[record.name] = true;
+ }
+ }
+
+ for (var prop in added)
+ added[prop] = object[prop];
+
+ for (var prop in removed)
+ removed[prop] = undefined;
+
+ var changed = {};
+ for (var prop in oldValues) {
+ if (prop in added || prop in removed)
+ continue;
+
+ var newValue = object[prop];
+ if (oldValues[prop] !== newValue)
+ changed[prop] = newValue;
+ }
+
+ return {
+ added: added,
+ removed: removed,
+ changed: changed
+ };
+ }
+
+ function newSplice(index, removed, addedCount) {
+ return {
+ index: index,
+ removed: removed,
+ addedCount: addedCount
+ };
+ }
+
+ var EDIT_LEAVE = 0;
+ var EDIT_UPDATE = 1;
+ var EDIT_ADD = 2;
+ var EDIT_DELETE = 3;
+
+ function ArraySplice() {}
+
+ ArraySplice.prototype = {
+
+ // Note: This function is *based* on the computation of the Levenshtein
+ // "edit" distance. The one change is that "updates" are treated as two
+ // edits - not one. With Array splices, an update is really a delete
+ // followed by an add. By retaining this, we optimize for "keeping" the
+ // maximum array items in the original array. For example:
+ //
+ // 'xxxx123' -> '123yyyy'
+ //
+ // With 1-edit updates, the shortest path would be just to update all seven
+ // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
+ // leaves the substring '123' intact.
+ calcEditDistances: function(current, currentStart, currentEnd,
+ old, oldStart, oldEnd) {
+ // "Deletion" columns
+ var rowCount = oldEnd - oldStart + 1;
+ var columnCount = currentEnd - currentStart + 1;
+ var distances = new Array(rowCount);
+
+ // "Addition" rows. Initialize null column.
+ for (var i = 0; i < rowCount; i++) {
+ distances[i] = new Array(columnCount);
+ distances[i][0] = i;
+ }
+
+ // Initialize null row
+ for (var j = 0; j < columnCount; j++)
+ distances[0][j] = j;
+
+ for (var i = 1; i < rowCount; i++) {
+ for (var j = 1; j < columnCount; j++) {
+ if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1]))
+ distances[i][j] = distances[i - 1][j - 1];
+ else {
+ var north = distances[i - 1][j] + 1;
+ var west = distances[i][j - 1] + 1;
+ distances[i][j] = north < west ? north : west;
+ }
+ }
+ }
+
+ return distances;
+ },
+
+ // This starts at the final weight, and walks "backward" by finding
+ // the minimum previous weight recursively until the origin of the weight
+ // matrix.
+ spliceOperationsFromEditDistances: function(distances) {
+ var i = distances.length - 1;
+ var j = distances[0].length - 1;
+ var current = distances[i][j];
+ var edits = [];
+ while (i > 0 || j > 0) {
+ if (i == 0) {
+ edits.push(EDIT_ADD);
+ j--;
+ continue;
+ }
+ if (j == 0) {
+ edits.push(EDIT_DELETE);
+ i--;
+ continue;
+ }
+ var northWest = distances[i - 1][j - 1];
+ var west = distances[i - 1][j];
+ var north = distances[i][j - 1];
+
+ var min;
+ if (west < north)
+ min = west < northWest ? west : northWest;
+ else
+ min = north < northWest ? north : northWest;
+
+ if (min == northWest) {
+ if (northWest == current) {
+ edits.push(EDIT_LEAVE);
+ } else {
+ edits.push(EDIT_UPDATE);
+ current = northWest;
+ }
+ i--;
+ j--;
+ } else if (min == west) {
+ edits.push(EDIT_DELETE);
+ i--;
+ current = west;
+ } else {
+ edits.push(EDIT_ADD);
+ j--;
+ current = north;
+ }
+ }
+
+ edits.reverse();
+ return edits;
+ },
+
+ /**
+ * Splice Projection functions:
+ *
+ * A splice map is a representation of how a previous array of items
+ * was transformed into a new array of items. Conceptually it is a list of
+ * tuples of
+ *
+ * <index, removed, addedCount>
+ *
+ * which are kept in ascending index order of. The tuple represents that at
+ * the |index|, |removed| sequence of items were removed, and counting forward
+ * from |index|, |addedCount| items were added.
+ */
+
+ /**
+ * Lacking individual splice mutation information, the minimal set of
+ * splices can be synthesized given the previous state and final state of an
+ * array. The basic approach is to calculate the edit distance matrix and
+ * choose the shortest path through it.
+ *
+ * Complexity: O(l * p)
+ * l: The length of the current array
+ * p: The length of the old array
+ */
+ calcSplices: function(current, currentStart, currentEnd,
+ old, oldStart, oldEnd) {
+ var prefixCount = 0;
+ var suffixCount = 0;
+
+ var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart);
+ if (currentStart == 0 && oldStart == 0)
+ prefixCount = this.sharedPrefix(current, old, minLength);
+
+ if (currentEnd == current.length && oldEnd == old.length)
+ suffixCount = this.sharedSuffix(current, old, minLength - prefixCount);
+
+ currentStart += prefixCount;
+ oldStart += prefixCount;
+ currentEnd -= suffixCount;
+ oldEnd -= suffixCount;
+
+ if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0)
+ return [];
+
+ if (currentStart == currentEnd) {
+ var splice = newSplice(currentStart, [], 0);
+ while (oldStart < oldEnd)
+ splice.removed.push(old[oldStart++]);
+
+ return [ splice ];
+ } else if (oldStart == oldEnd)
+ return [ newSplice(currentStart, [], currentEnd - currentStart) ];
+
+ var ops = this.spliceOperationsFromEditDistances(
+ this.calcEditDistances(current, currentStart, currentEnd,
+ old, oldStart, oldEnd));
+
+ var splice = undefined;
+ var splices = [];
+ var index = currentStart;
+ var oldIndex = oldStart;
+ for (var i = 0; i < ops.length; i++) {
+ switch(ops[i]) {
+ case EDIT_LEAVE:
+ if (splice) {
+ splices.push(splice);
+ splice = undefined;
+ }
+
+ index++;
+ oldIndex++;
+ break;
+ case EDIT_UPDATE:
+ if (!splice)
+ splice = newSplice(index, [], 0);
+
+ splice.addedCount++;
+ index++;
+
+ splice.removed.push(old[oldIndex]);
+ oldIndex++;
+ break;
+ case EDIT_ADD:
+ if (!splice)
+ splice = newSplice(index, [], 0);
+
+ splice.addedCount++;
+ index++;
+ break;
+ case EDIT_DELETE:
+ if (!splice)
+ splice = newSplice(index, [], 0);
+
+ splice.removed.push(old[oldIndex]);
+ oldIndex++;
+ break;
+ }
+ }
+
+ if (splice) {
+ splices.push(splice);
+ }
+ return splices;
+ },
+
+ sharedPrefix: function(current, old, searchLength) {
+ for (var i = 0; i < searchLength; i++)
+ if (!this.equals(current[i], old[i]))
+ return i;
+ return searchLength;
+ },
+
+ sharedSuffix: function(current, old, searchLength) {
+ var index1 = current.length;
+ var index2 = old.length;
+ var count = 0;
+ while (count < searchLength && this.equals(current[--index1], old[--index2]))
+ count++;
+
+ return count;
+ },
+
+ calculateSplices: function(current, previous) {
+ return this.calcSplices(current, 0, current.length, previous, 0,
+ previous.length);
+ },
+
+ equals: function(currentValue, previousValue) {
+ return currentValue === previousValue;
+ }
+ };
+
+ var arraySplice = new ArraySplice();
+
+ function calcSplices(current, currentStart, currentEnd,
+ old, oldStart, oldEnd) {
+ return arraySplice.calcSplices(current, currentStart, currentEnd,
+ old, oldStart, oldEnd);
+ }
+
+ function intersect(start1, end1, start2, end2) {
+ // Disjoint
+ if (end1 < start2 || end2 < start1)
+ return -1;
+
+ // Adjacent
+ if (end1 == start2 || end2 == start1)
+ return 0;
+
+ // Non-zero intersect, span1 first
+ if (start1 < start2) {
+ if (end1 < end2)
+ return end1 - start2; // Overlap
+ else
+ return end2 - start2; // Contained
+ } else {
+ // Non-zero intersect, span2 first
+ if (end2 < end1)
+ return end2 - start1; // Overlap
+ else
+ return end1 - start1; // Contained
+ }
+ }
+
+ function mergeSplice(splices, index, removed, addedCount) {
+
+ var splice = newSplice(index, removed, addedCount);
+
+ var inserted = false;
+ var insertionOffset = 0;
+
+ for (var i = 0; i < splices.length; i++) {
+ var current = splices[i];
+ current.index += insertionOffset;
+
+ if (inserted)
+ continue;
+
+ var intersectCount = intersect(splice.index,
+ splice.index + splice.removed.length,
+ current.index,
+ current.index + current.addedCount);
+
+ if (intersectCount >= 0) {
+ // Merge the two splices
+
+ splices.splice(i, 1);
+ i--;
+
+ insertionOffset -= current.addedCount - current.removed.length;
+
+ splice.addedCount += current.addedCount - intersectCount;
+ var deleteCount = splice.removed.length +
+ current.removed.length - intersectCount;
+
+ if (!splice.addedCount && !deleteCount) {
+ // merged splice is a noop. discard.
+ inserted = true;
+ } else {
+ var removed = current.removed;
+
+ if (splice.index < current.index) {
+ // some prefix of splice.removed is prepended to current.removed.
+ var prepend = splice.removed.slice(0, current.index - splice.index);
+ Array.prototype.push.apply(prepend, removed);
+ removed = prepend;
+ }
+
+ if (splice.index + splice.removed.length > current.index + current.addedCount) {
+ // some suffix of splice.removed is appended to current.removed.
+ var append = splice.removed.slice(current.index + current.addedCount - splice.index);
+ Array.prototype.push.apply(removed, append);
+ }
+
+ splice.removed = removed;
+ if (current.index < splice.index) {
+ splice.index = current.index;
+ }
+ }
+ } else if (splice.index < current.index) {
+ // Insert splice here.
+
+ inserted = true;
+
+ splices.splice(i, 0, splice);
+ i++;
+
+ var offset = splice.addedCount - splice.removed.length
+ current.index += offset;
+ insertionOffset += offset;
+ }
+ }
+
+ if (!inserted)
+ splices.push(splice);
+ }
+
+ function createInitialSplices(array, changeRecords) {
+ var splices = [];
+
+ for (var i = 0; i < changeRecords.length; i++) {
+ var record = changeRecords[i];
+ switch(record.type) {
+ case 'splice':
+ mergeSplice(splices, record.index, record.removed.slice(), record.addedCount);
+ break;
+ case 'add':
+ case 'update':
+ case 'delete':
+ if (!isIndex(record.name))
+ continue;
+ var index = toNumber(record.name);
+ if (index < 0)
+ continue;
+ mergeSplice(splices, index, [record.oldValue], 1);
+ break;
+ default:
+ console.error('Unexpected record type: ' + JSON.stringify(record));
+ break;
+ }
+ }
+
+ return splices;
+ }
+
+ function projectArraySplices(array, changeRecords) {
+ var splices = [];
+
+ createInitialSplices(array, changeRecords).forEach(function(splice) {
+ if (splice.addedCount == 1 && splice.removed.length == 1) {
+ if (splice.removed[0] !== array[splice.index])
+ splices.push(splice);
+
+ return
+ };
+
+ splices = splices.concat(calcSplices(array, splice.index, splice.index + splice.addedCount,
+ splice.removed, 0, splice.removed.length));
+ });
+
+ return splices;
+ }
+
+ // Export the observe-js object for **Node.js**, with
+ // backwards-compatibility for the old `require()` API. If we're in
+ // the browser, export as a global object.
+
+ var expose = global;
+
+ if (typeof exports !== 'undefined') {
+ if (typeof module !== 'undefined' && module.exports) {
+ expose = exports = module.exports;
+ }
+ expose = exports;
+ }
+
+ expose.Observer = Observer;
+ expose.Observer.runEOM_ = runEOM;
+ expose.Observer.observerSentinel_ = observerSentinel; // for testing.
+ expose.Observer.hasObjectObserve = hasObserve;
+ expose.ArrayObserver = ArrayObserver;
+ expose.ArrayObserver.calculateSplices = function(current, previous) {
+ return arraySplice.calculateSplices(current, previous);
+ };
+
+ expose.ArraySplice = ArraySplice;
+ expose.ObjectObserver = ObjectObserver;
+ expose.PathObserver = PathObserver;
+ expose.CompoundObserver = CompoundObserver;
+ expose.Path = Path;
+ expose.ObserverTransform = ObserverTransform;
+
+})(typeof global !== 'undefined' && global && typeof module !== 'undefined' && module ? global : this || window);
+
+// Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
+// This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
+// The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
+// The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
+// Code distributed by Google as part of the polymer project is also
+// subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
+
+(function(global) {
+ 'use strict';
+
+ var filter = Array.prototype.filter.call.bind(Array.prototype.filter);
+
+ function getTreeScope(node) {
+ while (node.parentNode) {
+ node = node.parentNode;
+ }
+
+ return typeof node.getElementById === 'function' ? node : null;
+ }
+
+ Node.prototype.bind = function(name, observable) {
+ console.error('Unhandled binding to Node: ', this, name, observable);
+ };
+
+ Node.prototype.bindFinished = function() {};
+
+ function updateBindings(node, name, binding) {
+ var bindings = node.bindings_;
+ if (!bindings)
+ bindings = node.bindings_ = {};
+
+ if (bindings[name])
+ binding[name].close();
+
+ return bindings[name] = binding;
+ }
+
+ function returnBinding(node, name, binding) {
+ return binding;
+ }
+
+ function sanitizeValue(value) {
+ return value == null ? '' : value;
+ }
+
+ function updateText(node, value) {
+ node.data = sanitizeValue(value);
+ }
+
+ function textBinding(node) {
+ return function(value) {
+ return updateText(node, value);
+ };
+ }
+
+ var maybeUpdateBindings = returnBinding;
+
+ Object.defineProperty(Platform, 'enableBindingsReflection', {
+ get: function() {
+ return maybeUpdateBindings === updateBindings;
+ },
+ set: function(enable) {
+ maybeUpdateBindings = enable ? updateBindings : returnBinding;
+ return enable;
+ },
+ configurable: true
+ });
+
+ Text.prototype.bind = function(name, value, oneTime) {
+ if (name !== 'textContent')
+ return Node.prototype.bind.call(this, name, value, oneTime);
+
+ if (oneTime)
+ return updateText(this, value);
+
+ var observable = value;
+ updateText(this, observable.open(textBinding(this)));
+ return maybeUpdateBindings(this, name, observable);
+ }
+
+ function updateAttribute(el, name, conditional, value) {
+ if (conditional) {
+ if (value)
+ el.setAttribute(name, '');
+ else
+ el.removeAttribute(name);
+ return;
+ }
+
+ el.setAttribute(name, sanitizeValue(value));
+ }
+
+ function attributeBinding(el, name, conditional) {
+ return function(value) {
+ updateAttribute(el, name, conditional, value);
+ };
+ }
+
+ Element.prototype.bind = function(name, value, oneTime) {
+ var conditional = name[name.length - 1] == '?';
+ if (conditional) {
+ this.removeAttribute(name);
+ name = name.slice(0, -1);
+ }
+
+ if (oneTime)
+ return updateAttribute(this, name, conditional, value);
+
+
+ var observable = value;
+ updateAttribute(this, name, conditional,
+ observable.open(attributeBinding(this, name, conditional)));
+
+ return maybeUpdateBindings(this, name, observable);
+ };
+
+ var checkboxEventType;
+ (function() {
+ // Attempt to feature-detect which event (change or click) is fired first
+ // for checkboxes.
+ var div = document.createElement('div');
+ var checkbox = div.appendChild(document.createElement('input'));
+ checkbox.setAttribute('type', 'checkbox');
+ var first;
+ var count = 0;
+ checkbox.addEventListener('click', function(e) {
+ count++;
+ first = first || 'click';
+ });
+ checkbox.addEventListener('change', function() {
+ count++;
+ first = first || 'change';
+ });
+
+ var event = document.createEvent('MouseEvent');
+ event.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false,
+ false, false, false, 0, null);
+ checkbox.dispatchEvent(event);
+ // WebKit/Blink don't fire the change event if the element is outside the
+ // document, so assume 'change' for that case.
+ checkboxEventType = count == 1 ? 'change' : first;
+ })();
+
+ function getEventForInputType(element) {
+ switch (element.type) {
+ case 'checkbox':
+ return checkboxEventType;
+ case 'radio':
+ case 'select-multiple':
+ case 'select-one':
+ return 'change';
+ case 'range':
+ if (/Trident|MSIE/.test(navigator.userAgent))
+ return 'change';
+ default:
+ return 'input';
+ }
+ }
+
+ function updateInput(input, property, value, santizeFn) {
+ input[property] = (santizeFn || sanitizeValue)(value);
+ }
+
+ function inputBinding(input, property, santizeFn) {
+ return function(value) {
+ return updateInput(input, property, value, santizeFn);
+ }
+ }
+
+ function noop() {}
+
+ function bindInputEvent(input, property, observable, postEventFn) {
+ var eventType = getEventForInputType(input);
+
+ function eventHandler() {
+ observable.setValue(input[property]);
+ observable.discardChanges();
+ (postEventFn || noop)(input);
+ Platform.performMicrotaskCheckpoint();
+ }
+ input.addEventListener(eventType, eventHandler);
+
+ return {
+ close: function() {
+ input.removeEventListener(eventType, eventHandler);
+ observable.close();
+ },
+
+ observable_: observable
+ }
+ }
+
+ function booleanSanitize(value) {
+ return Boolean(value);
+ }
+
+ // |element| is assumed to be an HTMLInputElement with |type| == 'radio'.
+ // Returns an array containing all radio buttons other than |element| that
+ // have the same |name|, either in the form that |element| belongs to or,
+ // if no form, in the document tree to which |element| belongs.
+ //
+ // This implementation is based upon the HTML spec definition of a
+ // "radio button group":
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.html#radio-button-group
+ //
+ function getAssociatedRadioButtons(element) {
+ if (element.form) {
+ return filter(element.form.elements, function(el) {
+ return el != element &&
+ el.tagName == 'INPUT' &&
+ el.type == 'radio' &&
+ el.name == element.name;
+ });
+ } else {
+ var treeScope = getTreeScope(element);
+ if (!treeScope)
+ return [];
+ var radios = treeScope.querySelectorAll(
+ 'input[type="radio"][name="' + element.name + '"]');
+ return filter(radios, function(el) {
+ return el != element && !el.form;
+ });
+ }
+ }
+
+ function checkedPostEvent(input) {
+ // Only the radio button that is getting checked gets an event. We
+ // therefore find all the associated radio buttons and update their
+ // check binding manually.
+ if (input.tagName === 'INPUT' &&
+ input.type === 'radio') {
+ getAssociatedRadioButtons(input).forEach(function(radio) {
+ var checkedBinding = radio.bindings_.checked;
+ if (checkedBinding) {
+ // Set the value directly to avoid an infinite call stack.
+ checkedBinding.observable_.setValue(false);
+ }
+ });
+ }
+ }
+
+ HTMLInputElement.prototype.bind = function(name, value, oneTime) {
+ if (name !== 'value' && name !== 'checked')
+ return HTMLElement.prototype.bind.call(this, name, value, oneTime);
+
+ this.removeAttribute(name);
+ var sanitizeFn = name == 'checked' ? booleanSanitize : sanitizeValue;
+ var postEventFn = name == 'checked' ? checkedPostEvent : noop;
+
+ if (oneTime)
+ return updateInput(this, name, value, sanitizeFn);
+
+
+ var observable = value;
+ var binding = bindInputEvent(this, name, observable, postEventFn);
+ updateInput(this, name,
+ observable.open(inputBinding(this, name, sanitizeFn)),
+ sanitizeFn);
+
+ // Checkboxes may need to update bindings of other checkboxes.
+ return updateBindings(this, name, binding);
+ }
+
+ HTMLTextAreaElement.prototype.bind = function(name, value, oneTime) {
+ if (name !== 'value')
+ return HTMLElement.prototype.bind.call(this, name, value, oneTime);
+
+ this.removeAttribute('value');
+
+ if (oneTime)
+ return updateInput(this, 'value', value);
+
+ var observable = value;
+ var binding = bindInputEvent(this, 'value', observable);
+ updateInput(this, 'value',
+ observable.open(inputBinding(this, 'value', sanitizeValue)));
+ return maybeUpdateBindings(this, name, binding);
+ }
+
+ function updateOption(option, value) {
+ var parentNode = option.parentNode;;
+ var select;
+ var selectBinding;
+ var oldValue;
+ if (parentNode instanceof HTMLSelectElement &&
+ parentNode.bindings_ &&
+ parentNode.bindings_.value) {
+ select = parentNode;
+ selectBinding = select.bindings_.value;
+ oldValue = select.value;
+ }
+
+ option.value = sanitizeValue(value);
+
+ if (select && select.value != oldValue) {
+ selectBinding.observable_.setValue(select.value);
+ selectBinding.observable_.discardChanges();
+ Platform.performMicrotaskCheckpoint();
+ }
+ }
+
+ function optionBinding(option) {
+ return function(value) {
+ updateOption(option, value);
+ }
+ }
+
+ HTMLOptionElement.prototype.bind = function(name, value, oneTime) {
+ if (name !== 'value')
+ return HTMLElement.prototype.bind.call(this, name, value, oneTime);
+
+ this.removeAttribute('value');
+
+ if (oneTime)
+ return updateOption(this, value);
+
+ var observable = value;
+ var binding = bindInputEvent(this, 'value', observable);
+ updateOption(this, observable.open(optionBinding(this)));
+ return maybeUpdateBindings(this, name, binding);
+ }
+
+ HTMLSelectElement.prototype.bind = function(name, value, oneTime) {
+ if (name === 'selectedindex')
+ name = 'selectedIndex';
+
+ if (name !== 'selectedIndex' && name !== 'value')
+ return HTMLElement.prototype.bind.call(this, name, value, oneTime);
+
+ this.removeAttribute(name);
+
+ if (oneTime)
+ return updateInput(this, name, value);
+
+ var observable = value;
+ var binding = bindInputEvent(this, name, observable);
+ updateInput(this, name,
+ observable.open(inputBinding(this, name)));
+
+ // Option update events may need to access select bindings.
+ return updateBindings(this, name, binding);
+ }
+})(this);
+
+// Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
+// This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
+// The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
+// The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
+// Code distributed by Google as part of the polymer project is also
+// subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
+
+(function(global) {
+ 'use strict';
+
+ function assert(v) {
+ if (!v)
+ throw new Error('Assertion failed');
+ }
+
+ var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
+
+ function getFragmentRoot(node) {
+ var p;
+ while (p = node.parentNode) {
+ node = p;
+ }
+
+ return node;
+ }
+
+ function searchRefId(node, id) {
+ if (!id)
+ return;
+
+ var ref;
+ var selector = '#' + id;
+ while (!ref) {
+ node = getFragmentRoot(node);
+
+ if (node.protoContent_)
+ ref = node.protoContent_.querySelector(selector);
+ else if (node.getElementById)
+ ref = node.getElementById(id);
+
+ if (ref || !node.templateCreator_)
+ break
+
+ node = node.templateCreator_;
+ }
+
+ return ref;
+ }
+
+ function getInstanceRoot(node) {
+ while (node.parentNode) {
+ node = node.parentNode;
+ }
+ return node.templateCreator_ ? node : null;
+ }
+
+ var Map;
+ if (global.Map && typeof global.Map.prototype.forEach === 'function') {
+ Map = global.Map;
+ } else {
+ Map = function() {
+ this.keys = [];
+ this.values = [];
+ };
+
+ Map.prototype = {
+ set: function(key, value) {
+ var index = this.keys.indexOf(key);
+ if (index < 0) {
+ this.keys.push(key);
+ this.values.push(value);
+ } else {
+ this.values[index] = value;
+ }
+ },
+
+ get: function(key) {
+ var index = this.keys.indexOf(key);
+ if (index < 0)
+ return;
+
+ return this.values[index];
+ },
+
+ delete: function(key, value) {
+ var index = this.keys.indexOf(key);
+ if (index < 0)
+ return false;
+
+ this.keys.splice(index, 1);
+ this.values.splice(index, 1);
+ return true;
+ },
+
+ forEach: function(f, opt_this) {
+ for (var i = 0; i < this.keys.length; i++)
+ f.call(opt_this || this, this.values[i], this.keys[i], this);
+ }
+ };
+ }
+
+ // JScript does not have __proto__. We wrap all object literals with
+ // createObject which uses Object.create, Object.defineProperty and
+ // Object.getOwnPropertyDescriptor to create a new object that does the exact
+ // same thing. The main downside to this solution is that we have to extract
+ // all those property descriptors for IE.
+ var createObject = ('__proto__' in {}) ?
+ function(obj) { return obj; } :
+ function(obj) {
+ var proto = obj.__proto__;
+ if (!proto)
+ return obj;
+ var newObject = Object.create(proto);
+ Object.getOwnPropertyNames(obj).forEach(function(name) {
+ Object.defineProperty(newObject, name,
+ Object.getOwnPropertyDescriptor(obj, name));
+ });
+ return newObject;
+ };
+
+ // IE does not support have Document.prototype.contains.
+ if (typeof document.contains != 'function') {
+ Document.prototype.contains = function(node) {
+ if (node === this || node.parentNode === this)
+ return true;
+ return this.documentElement.contains(node);
+ }
+ }
+
+ var BIND = 'bind';
+ var REPEAT = 'repeat';
+ var IF = 'if';
+
+ var templateAttributeDirectives = {
+ 'template': true,
+ 'repeat': true,
+ 'bind': true,
+ 'ref': true
+ };
+
+ var semanticTemplateElements = {
+ 'THEAD': true,
+ 'TBODY': true,
+ 'TFOOT': true,
+ 'TH': true,
+ 'TR': true,
+ 'TD': true,
+ 'COLGROUP': true,
+ 'COL': true,
+ 'CAPTION': true,
+ 'OPTION': true,
+ 'OPTGROUP': true
+ };
+
+ var hasTemplateElement = typeof HTMLTemplateElement !== 'undefined';
+ if (hasTemplateElement) {
+ // TODO(rafaelw): Remove when fix for
+ // https://codereview.chromium.org/164803002/
+ // makes it to Chrome release.
+ (function() {
+ var t = document.createElement('template');
+ var d = t.content.ownerDocument;
+ var html = d.appendChild(d.createElement('html'));
+ var head = html.appendChild(d.createElement('head'));
+ var base = d.createElement('base');
+ base.href = document.baseURI;
+ head.appendChild(base);
+ })();
+ }
+
+ var allTemplatesSelectors = 'template, ' +
+ Object.keys(semanticTemplateElements).map(function(tagName) {
+ return tagName.toLowerCase() + '[template]';
+ }).join(', ');
+
+ function isSVGTemplate(el) {
+ return el.tagName == 'template' &&
+ el.namespaceURI == 'http://www.w3.org/2000/svg';
+ }
+
+ function isHTMLTemplate(el) {
+ return el.tagName == 'TEMPLATE' &&
+ el.namespaceURI == 'http://www.w3.org/1999/xhtml';
+ }
+
+ function isAttributeTemplate(el) {
+ return Boolean(semanticTemplateElements[el.tagName] &&
+ el.hasAttribute('template'));
+ }
+
+ function isTemplate(el) {
+ if (el.isTemplate_ === undefined)
+ el.isTemplate_ = el.tagName == 'TEMPLATE' || isAttributeTemplate(el);
+
+ return el.isTemplate_;
+ }
+
+ // FIXME: Observe templates being added/removed from documents
+ // FIXME: Expose imperative API to decorate and observe templates in
+ // "disconnected tress" (e.g. ShadowRoot)
+ document.addEventListener('DOMContentLoaded', function(e) {
+ bootstrapTemplatesRecursivelyFrom(document);
+ // FIXME: Is this needed? Seems like it shouldn't be.
+ Platform.performMicrotaskCheckpoint();
+ }, false);
+
+ function forAllTemplatesFrom(node, fn) {
+ var subTemplates = node.querySelectorAll(allTemplatesSelectors);
+
+ if (isTemplate(node))
+ fn(node)
+ forEach(subTemplates, fn);
+ }
+
+ function bootstrapTemplatesRecursivelyFrom(node) {
+ function bootstrap(template) {
+ if (!HTMLTemplateElement.decorate(template))
+ bootstrapTemplatesRecursivelyFrom(template.content);
+ }
+
+ forAllTemplatesFrom(node, bootstrap);
+ }
+
+ if (!hasTemplateElement) {
+ /**
+ * This represents a <template> element.
+ * @constructor
+ * @extends {HTMLElement}
+ */
+ global.HTMLTemplateElement = function() {
+ throw TypeError('Illegal constructor');
+ };
+ }
+
+ var hasProto = '__proto__' in {};
+
+ function mixin(to, from) {
+ Object.getOwnPropertyNames(from).forEach(function(name) {
+ Object.defineProperty(to, name,
+ Object.getOwnPropertyDescriptor(from, name));
+ });
+ }
+
+ // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#dfn-template-contents-owner
+ function getOrCreateTemplateContentsOwner(template) {
+ var doc = template.ownerDocument
+ if (!doc.defaultView)
+ return doc;
+ var d = doc.templateContentsOwner_;
+ if (!d) {
+ // TODO(arv): This should either be a Document or HTMLDocument depending
+ // on doc.
+ d = doc.implementation.createHTMLDocument('');
+ while (d.lastChild) {
+ d.removeChild(d.lastChild);
+ }
+ doc.templateContentsOwner_ = d;
+ }
+ return d;
+ }
+
+ function getTemplateStagingDocument(template) {
+ if (!template.stagingDocument_) {
+ var owner = template.ownerDocument;
+ if (!owner.stagingDocument_) {
+ owner.stagingDocument_ = owner.implementation.createHTMLDocument('');
+ owner.stagingDocument_.isStagingDocument = true;
+ // TODO(rafaelw): Remove when fix for
+ // https://codereview.chromium.org/164803002/
+ // makes it to Chrome release.
+ var base = owner.stagingDocument_.createElement('base');
+ base.href = document.baseURI;
+ owner.stagingDocument_.head.appendChild(base);
+
+ owner.stagingDocument_.stagingDocument_ = owner.stagingDocument_;
+ }
+
+ template.stagingDocument_ = owner.stagingDocument_;
+ }
+
+ return template.stagingDocument_;
+ }
+
+ // For non-template browsers, the parser will disallow <template> in certain
+ // locations, so we allow "attribute templates" which combine the template
+ // element with the top-level container node of the content, e.g.
+ //
+ // <tr template repeat="{{ foo }}"" class="bar"><td>Bar</td></tr>
+ //
+ // becomes
+ //
+ // <template repeat="{{ foo }}">
+ // + #document-fragment
+ // + <tr class="bar">
+ // + <td>Bar</td>
+ //
+ function extractTemplateFromAttributeTemplate(el) {
+ var template = el.ownerDocument.createElement('template');
+ el.parentNode.insertBefore(template, el);
+
+ var attribs = el.attributes;
+ var count = attribs.length;
+ while (count-- > 0) {
+ var attrib = attribs[count];
+ if (templateAttributeDirectives[attrib.name]) {
+ if (attrib.name !== 'template')
+ template.setAttribute(attrib.name, attrib.value);
+ el.removeAttribute(attrib.name);
+ }
+ }
+
+ return template;
+ }
+
+ function extractTemplateFromSVGTemplate(el) {
+ var template = el.ownerDocument.createElement('template');
+ el.parentNode.insertBefore(template, el);
+
+ var attribs = el.attributes;
+ var count = attribs.length;
+ while (count-- > 0) {
+ var attrib = attribs[count];
+ template.setAttribute(attrib.name, attrib.value);
+ el.removeAttribute(attrib.name);
+ }
+
+ el.parentNode.removeChild(el);
+ return template;
+ }
+
+ function liftNonNativeTemplateChildrenIntoContent(template, el, useRoot) {
+ var content = template.content;
+ if (useRoot) {
+ content.appendChild(el);
+ return;
+ }
+
+ var child;
+ while (child = el.firstChild) {
+ content.appendChild(child);
+ }
+ }
+
+ var templateObserver;
+ if (typeof MutationObserver == 'function') {
+ templateObserver = new MutationObserver(function(records) {
+ for (var i = 0; i < records.length; i++) {
+ records[i].target.refChanged_();
+ }
+ });
+ }
+
+ /**
+ * Ensures proper API and content model for template elements.
+ * @param {HTMLTemplateElement} opt_instanceRef The template element which
+ * |el| template element will return as the value of its ref(), and whose
+ * content will be used as source when createInstance() is invoked.
+ */
+ HTMLTemplateElement.decorate = function(el, opt_instanceRef) {
+ if (el.templateIsDecorated_)
+ return false;
+
+ var templateElement = el;
+ templateElement.templateIsDecorated_ = true;
+
+ var isNativeHTMLTemplate = isHTMLTemplate(templateElement) &&
+ hasTemplateElement;
+ var bootstrapContents = isNativeHTMLTemplate;
+ var liftContents = !isNativeHTMLTemplate;
+ var liftRoot = false;
+
+ if (!isNativeHTMLTemplate) {
+ if (isAttributeTemplate(templateElement)) {
+ assert(!opt_instanceRef);
+ templateElement = extractTemplateFromAttributeTemplate(el);
+ templateElement.templateIsDecorated_ = true;
+ isNativeHTMLTemplate = hasTemplateElement;
+ liftRoot = true;
+ } else if (isSVGTemplate(templateElement)) {
+ templateElement = extractTemplateFromSVGTemplate(el);
+ templateElement.templateIsDecorated_ = true;
+ isNativeHTMLTemplate = hasTemplateElement;
+ }
+ }
+
+ if (!isNativeHTMLTemplate) {
+ fixTemplateElementPrototype(templateElement);
+ var doc = getOrCreateTemplateContentsOwner(templateElement);
+ templateElement.content_ = doc.createDocumentFragment();
+ }
+
+ if (opt_instanceRef) {
+ // template is contained within an instance, its direct content must be
+ // empty
+ templateElement.instanceRef_ = opt_instanceRef;
+ } else if (liftContents) {
+ liftNonNativeTemplateChildrenIntoContent(templateElement,
+ el,
+ liftRoot);
+ } else if (bootstrapContents) {
+ bootstrapTemplatesRecursivelyFrom(templateElement.content);
+ }
+
+ return true;
+ };
+
+ // TODO(rafaelw): This used to decorate recursively all templates from a given
+ // node. This happens by default on 'DOMContentLoaded', but may be needed
+ // in subtrees not descendent from document (e.g. ShadowRoot).
+ // Review whether this is the right public API.
+ HTMLTemplateElement.bootstrap = bootstrapTemplatesRecursivelyFrom;
+
+ var htmlElement = global.HTMLUnknownElement || HTMLElement;
+
+ var contentDescriptor = {
+ get: function() {
+ return this.content_;
+ },
+ enumerable: true,
+ configurable: true
+ };
+
+ if (!hasTemplateElement) {
+ // Gecko is more picky with the prototype than WebKit. Make sure to use the
+ // same prototype as created in the constructor.
+ HTMLTemplateElement.prototype = Object.create(htmlElement.prototype);
+
+ Object.defineProperty(HTMLTemplateElement.prototype, 'content',
+ contentDescriptor);
+ }
+
+ function fixTemplateElementPrototype(el) {
+ if (hasProto)
+ el.__proto__ = HTMLTemplateElement.prototype;
+ else
+ mixin(el, HTMLTemplateElement.prototype);
+ }
+
+ function ensureSetModelScheduled(template) {
+ if (!template.setModelFn_) {
+ template.setModelFn_ = function() {
+ template.setModelFnScheduled_ = false;
+ var map = getBindings(template,
+ template.delegate_ && template.delegate_.prepareBinding);
+ processBindings(template, map, template.model_);
+ };
+ }
+
+ if (!template.setModelFnScheduled_) {
+ template.setModelFnScheduled_ = true;
+ Observer.runEOM_(template.setModelFn_);
+ }
+ }
+
+ mixin(HTMLTemplateElement.prototype, {
+ bind: function(name, value, oneTime) {
+ if (name != 'ref')
+ return Element.prototype.bind.call(this, name, value, oneTime);
+
+ var self = this;
+ var ref = oneTime ? value : value.open(function(ref) {
+ self.setAttribute('ref', ref);
+ self.refChanged_();
+ });
+
+ this.setAttribute('ref', ref);
+ this.refChanged_();
+ if (oneTime)
+ return;
+
+ if (!this.bindings_) {
+ this.bindings_ = { ref: value };
+ } else {
+ this.bindings_.ref = value;
+ }
+
+ return value;
+ },
+
+ processBindingDirectives_: function(directives) {
+ if (this.iterator_)
+ this.iterator_.closeDeps();
+
+ if (!directives.if && !directives.bind && !directives.repeat) {
+ if (this.iterator_) {
+ this.iterator_.close();
+ this.iterator_ = undefined;
+ }
+
+ return;
+ }
+
+ if (!this.iterator_) {
+ this.iterator_ = new TemplateIterator(this);
+ }
+
+ this.iterator_.updateDependencies(directives, this.model_);
+
+ if (templateObserver) {
+ templateObserver.observe(this, { attributes: true,
+ attributeFilter: ['ref'] });
+ }
+
+ return this.iterator_;
+ },
+
+ createInstance: function(model, bindingDelegate, delegate_) {
+ if (bindingDelegate)
+ delegate_ = this.newDelegate_(bindingDelegate);
+ else if (!delegate_)
+ delegate_ = this.delegate_;
+
+ if (!this.refContent_)
+ this.refContent_ = this.ref_.content;
+ var content = this.refContent_;
+ if (content.firstChild === null)
+ return emptyInstance;
+
+ var map = getInstanceBindingMap(content, delegate_);
+ var stagingDocument = getTemplateStagingDocument(this);
+ var instance = stagingDocument.createDocumentFragment();
+ instance.templateCreator_ = this;
+ instance.protoContent_ = content;
+ instance.bindings_ = [];
+ instance.terminator_ = null;
+ var instanceRecord = instance.templateInstance_ = {
+ firstNode: null,
+ lastNode: null,
+ model: model
+ };
+
+ var i = 0;
+ var collectTerminator = false;
+ for (var child = content.firstChild; child; child = child.nextSibling) {
+ // The terminator of the instance is the clone of the last child of the
+ // content. If the last child is an active template, it may produce
+ // instances as a result of production, so simply collecting the last
+ // child of the instance after it has finished producing may be wrong.
+ if (child.nextSibling === null)
+ collectTerminator = true;
+
+ var clone = cloneAndBindInstance(child, instance, stagingDocument,
+ map.children[i++],
+ model,
+ delegate_,
+ instance.bindings_);
+ clone.templateInstance_ = instanceRecord;
+ if (collectTerminator)
+ instance.terminator_ = clone;
+ }
+
+ instanceRecord.firstNode = instance.firstChild;
+ instanceRecord.lastNode = instance.lastChild;
+ instance.templateCreator_ = undefined;
+ instance.protoContent_ = undefined;
+ return instance;
+ },
+
+ get model() {
+ return this.model_;
+ },
+
+ set model(model) {
+ this.model_ = model;
+ ensureSetModelScheduled(this);
+ },
+
+ get bindingDelegate() {
+ return this.delegate_ && this.delegate_.raw;
+ },
+
+ refChanged_: function() {
+ if (!this.iterator_ || this.refContent_ === this.ref_.content)
+ return;
+
+ this.refContent_ = undefined;
+ this.iterator_.valueChanged();
+ this.iterator_.updateIteratedValue(this.iterator_.getUpdatedValue());
+ },
+
+ clear: function() {
+ this.model_ = undefined;
+ this.delegate_ = undefined;
+ if (this.bindings_ && this.bindings_.ref)
+ this.bindings_.ref.close()
+ this.refContent_ = undefined;
+ if (!this.iterator_)
+ return;
+ this.iterator_.valueChanged();
+ this.iterator_.close()
+ this.iterator_ = undefined;
+ },
+
+ setDelegate_: function(delegate) {
+ this.delegate_ = delegate;
+ this.bindingMap_ = undefined;
+ if (this.iterator_) {
+ this.iterator_.instancePositionChangedFn_ = undefined;
+ this.iterator_.instanceModelFn_ = undefined;
+ }
+ },
+
+ newDelegate_: function(bindingDelegate) {
+ if (!bindingDelegate)
+ return;
+
+ function delegateFn(name) {
+ var fn = bindingDelegate && bindingDelegate[name];
+ if (typeof fn != 'function')
+ return;
+
+ return function() {
+ return fn.apply(bindingDelegate, arguments);
+ };
+ }
+
+ return {
+ bindingMaps: {},
+ raw: bindingDelegate,
+ prepareBinding: delegateFn('prepareBinding'),
+ prepareInstanceModel: delegateFn('prepareInstanceModel'),
+ prepareInstancePositionChanged:
+ delegateFn('prepareInstancePositionChanged')
+ };
+ },
+
+ set bindingDelegate(bindingDelegate) {
+ if (this.delegate_) {
+ throw Error('Template must be cleared before a new bindingDelegate ' +
+ 'can be assigned');
+ }
+
+ this.setDelegate_(this.newDelegate_(bindingDelegate));
+ },
+
+ get ref_() {
+ var ref = searchRefId(this, this.getAttribute('ref'));
+ if (!ref)
+ ref = this.instanceRef_;
+
+ if (!ref)
+ return this;
+
+ var nextRef = ref.ref_;
+ return nextRef ? nextRef : ref;
+ }
+ });
+
+ // Returns
+ // a) undefined if there are no mustaches.
+ // b) [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least one mustache.
+ function parseMustaches(s, name, node, prepareBindingFn) {
+ if (!s || !s.length)
+ return;
+
+ var tokens;
+ var length = s.length;
+ var startIndex = 0, lastIndex = 0, endIndex = 0;
+ var onlyOneTime = true;
+ while (lastIndex < length) {
+ var startIndex = s.indexOf('{{', lastIndex);
+ var oneTimeStart = s.indexOf('[[', lastIndex);
+ var oneTime = false;
+ var terminator = '}}';
+
+ if (oneTimeStart >= 0 &&
+ (startIndex < 0 || oneTimeStart < startIndex)) {
+ startIndex = oneTimeStart;
+ oneTime = true;
+ terminator = ']]';
+ }
+
+ endIndex = startIndex < 0 ? -1 : s.indexOf(terminator, startIndex + 2);
+
+ if (endIndex < 0) {
+ if (!tokens)
+ return;
+
+ tokens.push(s.slice(lastIndex)); // TEXT
+ break;
+ }
+
+ tokens = tokens || [];
+ tokens.push(s.slice(lastIndex, startIndex)); // TEXT
+ var pathString = s.slice(startIndex + 2, endIndex).trim();
+ tokens.push(oneTime); // ONE_TIME?
+ onlyOneTime = onlyOneTime && oneTime;
+ var delegateFn = prepareBindingFn &&
+ prepareBindingFn(pathString, name, node);
+ // Don't try to parse the expression if there's a prepareBinding function
+ if (delegateFn == null) {
+ tokens.push(Path.get(pathString)); // PATH
+ } else {
+ tokens.push(null);
+ }
+ tokens.push(delegateFn); // DELEGATE_FN
+ lastIndex = endIndex + 2;
+ }
+
+ if (lastIndex === length)
+ tokens.push(''); // TEXT
+
+ tokens.hasOnePath = tokens.length === 5;
+ tokens.isSimplePath = tokens.hasOnePath &&
+ tokens[0] == '' &&
+ tokens[4] == '';
+ tokens.onlyOneTime = onlyOneTime;
+
+ tokens.combinator = function(values) {
+ var newValue = tokens[0];
+
+ for (var i = 1; i < tokens.length; i += 4) {
+ var value = tokens.hasOnePath ? values : values[(i - 1) / 4];
+ if (value !== undefined)
+ newValue += value;
+ newValue += tokens[i + 3];
+ }
+
+ return newValue;
+ }
+
+ return tokens;
+ };
+
+ function processOneTimeBinding(name, tokens, node, model) {
+ if (tokens.hasOnePath) {
+ var delegateFn = tokens[3];
+ var value = delegateFn ? delegateFn(model, node, true) :
+ tokens[2].getValueFrom(model);
+ return tokens.isSimplePath ? value : tokens.combinator(value);
+ }
+
+ var values = [];
+ for (var i = 1; i < tokens.length; i += 4) {
+ var delegateFn = tokens[i + 2];
+ values[(i - 1) / 4] = delegateFn ? delegateFn(model, node) :
+ tokens[i + 1].getValueFrom(model);
+ }
+
+ return tokens.combinator(values);
+ }
+
+ function processSinglePathBinding(name, tokens, node, model) {
+ var delegateFn = tokens[3];
+ var observer = delegateFn ? delegateFn(model, node, false) :
+ new PathObserver(model, tokens[2]);
+
+ return tokens.isSimplePath ? observer :
+ new ObserverTransform(observer, tokens.combinator);
+ }
+
+ function processBinding(name, tokens, node, model) {
+ if (tokens.onlyOneTime)
+ return processOneTimeBinding(name, tokens, node, model);
+
+ if (tokens.hasOnePath)
+ return processSinglePathBinding(name, tokens, node, model);
+
+ var observer = new CompoundObserver();
+
+ for (var i = 1; i < tokens.length; i += 4) {
+ var oneTime = tokens[i];
+ var delegateFn = tokens[i + 2];
+
+ if (delegateFn) {
+ var value = delegateFn(model, node, oneTime);
+ if (oneTime)
+ observer.addPath(value)
+ else
+ observer.addObserver(value);
+ continue;
+ }
+
+ var path = tokens[i + 1];
+ if (oneTime)
+ observer.addPath(path.getValueFrom(model))
+ else
+ observer.addPath(model, path);
+ }
+
+ return new ObserverTransform(observer, tokens.combinator);
+ }
+
+ function processBindings(node, bindings, model, instanceBindings) {
+ for (var i = 0; i < bindings.length; i += 2) {
+ var name = bindings[i]
+ var tokens = bindings[i + 1];
+ var value = processBinding(name, tokens, node, model);
+ var binding = node.bind(name, value, tokens.onlyOneTime);
+ if (binding && instanceBindings)
+ instanceBindings.push(binding);
+ }
+
+ node.bindFinished();
+ if (!bindings.isTemplate)
+ return;
+
+ node.model_ = model;
+ var iter = node.processBindingDirectives_(bindings);
+ if (instanceBindings && iter)
+ instanceBindings.push(iter);
+ }
+
+ function parseWithDefault(el, name, prepareBindingFn) {
+ var v = el.getAttribute(name);
+ return parseMustaches(v == '' ? '{{}}' : v, name, el, prepareBindingFn);
+ }
+
+ function parseAttributeBindings(element, prepareBindingFn) {
+ assert(element);
+
+ var bindings = [];
+ var ifFound = false;
+ var bindFound = false;
+
+ for (var i = 0; i < element.attributes.length; i++) {
+ var attr = element.attributes[i];
+ var name = attr.name;
+ var value = attr.value;
+
+ // Allow bindings expressed in attributes to be prefixed with underbars.
+ // We do this to allow correct semantics for browsers that don't implement
+ // <template> where certain attributes might trigger side-effects -- and
+ // for IE which sanitizes certain attributes, disallowing mustache
+ // replacements in their text.
+ while (name[0] === '_') {
+ name = name.substring(1);
+ }
+
+ if (isTemplate(element) &&
+ (name === IF || name === BIND || name === REPEAT)) {
+ continue;
+ }
+
+ var tokens = parseMustaches(value, name, element,
+ prepareBindingFn);
+ if (!tokens)
+ continue;
+
+ bindings.push(name, tokens);
+ }
+
+ if (isTemplate(element)) {
+ bindings.isTemplate = true;
+ bindings.if = parseWithDefault(element, IF, prepareBindingFn);
+ bindings.bind = parseWithDefault(element, BIND, prepareBindingFn);
+ bindings.repeat = parseWithDefault(element, REPEAT, prepareBindingFn);
+
+ if (bindings.if && !bindings.bind && !bindings.repeat)
+ bindings.bind = parseMustaches('{{}}', BIND, element, prepareBindingFn);
+ }
+
+ return bindings;
+ }
+
+ function getBindings(node, prepareBindingFn) {
+ if (node.nodeType === Node.ELEMENT_NODE)
+ return parseAttributeBindings(node, prepareBindingFn);
+
+ if (node.nodeType === Node.TEXT_NODE) {
+ var tokens = parseMustaches(node.data, 'textContent', node,
+ prepareBindingFn);
+ if (tokens)
+ return ['textContent', tokens];
+ }
+
+ return [];
+ }
+
+ function cloneAndBindInstance(node, parent, stagingDocument, bindings, model,
+ delegate,
+ instanceBindings,
+ instanceRecord) {
+ var clone = parent.appendChild(stagingDocument.importNode(node, false));
+
+ var i = 0;
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ cloneAndBindInstance(child, clone, stagingDocument,
+ bindings.children[i++],
+ model,
+ delegate,
+ instanceBindings);
+ }
+
+ if (bindings.isTemplate) {
+ HTMLTemplateElement.decorate(clone, node);
+ if (delegate)
+ clone.setDelegate_(delegate);
+ }
+
+ processBindings(clone, bindings, model, instanceBindings);
+ return clone;
+ }
+
+ function createInstanceBindingMap(node, prepareBindingFn) {
+ var map = getBindings(node, prepareBindingFn);
+ map.children = {};
+ var index = 0;
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ map.children[index++] = createInstanceBindingMap(child, prepareBindingFn);
+ }
+
+ return map;
+ }
+
+ var contentUidCounter = 1;
+
+ // TODO(rafaelw): Setup a MutationObserver on content which clears the id
+ // so that bindingMaps regenerate when the template.content changes.
+ function getContentUid(content) {
+ var id = content.id_;
+ if (!id)
+ id = content.id_ = contentUidCounter++;
+ return id;
+ }
+
+ // Each delegate is associated with a set of bindingMaps, one for each
+ // content which may be used by a template. The intent is that each binding
+ // delegate gets the opportunity to prepare the instance (via the prepare*
+ // delegate calls) once across all uses.
+ // TODO(rafaelw): Separate out the parse map from the binding map. In the
+ // current implementation, if two delegates need a binding map for the same
+ // content, the second will have to reparse.
+ function getInstanceBindingMap(content, delegate_) {
+ var contentId = getContentUid(content);
+ if (delegate_) {
+ var map = delegate_.bindingMaps[contentId];
+ if (!map) {
+ map = delegate_.bindingMaps[contentId] =
+ createInstanceBindingMap(content, delegate_.prepareBinding) || [];
+ }
+ return map;
+ }
+
+ var map = content.bindingMap_;
+ if (!map) {
+ map = content.bindingMap_ =
+ createInstanceBindingMap(content, undefined) || [];
+ }
+ return map;
+ }
+
+ Object.defineProperty(Node.prototype, 'templateInstance', {
+ get: function() {
+ var instance = this.templateInstance_;
+ return instance ? instance :
+ (this.parentNode ? this.parentNode.templateInstance : undefined);
+ }
+ });
+
+ var emptyInstance = document.createDocumentFragment();
+ emptyInstance.bindings_ = [];
+ emptyInstance.terminator_ = null;
+
+ function TemplateIterator(templateElement) {
+ this.closed = false;
+ this.templateElement_ = templateElement;
+ this.instances = [];
+ this.deps = undefined;
+ this.iteratedValue = [];
+ this.presentValue = undefined;
+ this.arrayObserver = undefined;
+ }
+
+ TemplateIterator.prototype = {
+ closeDeps: function() {
+ var deps = this.deps;
+ if (deps) {
+ if (deps.ifOneTime === false)
+ deps.ifValue.close();
+ if (deps.oneTime === false)
+ deps.value.close();
+ }
+ },
+
+ updateDependencies: function(directives, model) {
+ this.closeDeps();
+
+ var deps = this.deps = {};
+ var template = this.templateElement_;
+
+ var ifValue = true;
+ if (directives.if) {
+ deps.hasIf = true;
+ deps.ifOneTime = directives.if.onlyOneTime;
+ deps.ifValue = processBinding(IF, directives.if, template, model);
+
+ ifValue = deps.ifValue;
+
+ // oneTime if & predicate is false. nothing else to do.
+ if (deps.ifOneTime && !ifValue) {
+ this.valueChanged();
+ return;
+ }
+
+ if (!deps.ifOneTime)
+ ifValue = ifValue.open(this.updateIfValue, this);
+ }
+
+ if (directives.repeat) {
+ deps.repeat = true;
+ deps.oneTime = directives.repeat.onlyOneTime;
+ deps.value = processBinding(REPEAT, directives.repeat, template, model);
+ } else {
+ deps.repeat = false;
+ deps.oneTime = directives.bind.onlyOneTime;
+ deps.value = processBinding(BIND, directives.bind, template, model);
+ }
+
+ var value = deps.value;
+ if (!deps.oneTime)
+ value = value.open(this.updateIteratedValue, this);
+
+ if (!ifValue) {
+ this.valueChanged();
+ return;
+ }
+
+ this.updateValue(value);
+ },
+
+ /**
+ * Gets the updated value of the bind/repeat. This can potentially call
+ * user code (if a bindingDelegate is set up) so we try to avoid it if we
+ * already have the value in hand (from Observer.open).
+ */
+ getUpdatedValue: function() {
+ var value = this.deps.value;
+ if (!this.deps.oneTime)
+ value = value.discardChanges();
+ return value;
+ },
+
+ updateIfValue: function(ifValue) {
+ if (!ifValue) {
+ this.valueChanged();
+ return;
+ }
+
+ this.updateValue(this.getUpdatedValue());
+ },
+
+ updateIteratedValue: function(value) {
+ if (this.deps.hasIf) {
+ var ifValue = this.deps.ifValue;
+ if (!this.deps.ifOneTime)
+ ifValue = ifValue.discardChanges();
+ if (!ifValue) {
+ this.valueChanged();
+ return;
+ }
+ }
+
+ this.updateValue(value);
+ },
+
+ updateValue: function(value) {
+ if (!this.deps.repeat)
+ value = [value];
+ var observe = this.deps.repeat &&
+ !this.deps.oneTime &&
+ Array.isArray(value);
+ this.valueChanged(value, observe);
+ },
+
+ valueChanged: function(value, observeValue) {
+ if (!Array.isArray(value))
+ value = [];
+
+ if (value === this.iteratedValue)
+ return;
+
+ this.unobserve();
+ this.presentValue = value;
+ if (observeValue) {
+ this.arrayObserver = new ArrayObserver(this.presentValue);
+ this.arrayObserver.open(this.handleSplices, this);
+ }
+
+ this.handleSplices(ArrayObserver.calculateSplices(this.presentValue,
+ this.iteratedValue));
+ },
+
+ getLastInstanceNode: function(index) {
+ if (index == -1)
+ return this.templateElement_;
+ var instance = this.instances[index];
+ var terminator = instance.terminator_;
+ if (!terminator)
+ return this.getLastInstanceNode(index - 1);
+
+ if (terminator.nodeType !== Node.ELEMENT_NODE ||
+ this.templateElement_ === terminator) {
+ return terminator;
+ }
+
+ var subtemplateIterator = terminator.iterator_;
+ if (!subtemplateIterator)
+ return terminator;
+
+ return subtemplateIterator.getLastTemplateNode();
+ },
+
+ getLastTemplateNode: function() {
+ return this.getLastInstanceNode(this.instances.length - 1);
+ },
+
+ insertInstanceAt: function(index, fragment) {
+ var previousInstanceLast = this.getLastInstanceNode(index - 1);
+ var parent = this.templateElement_.parentNode;
+ this.instances.splice(index, 0, fragment);
+
+ parent.insertBefore(fragment, previousInstanceLast.nextSibling);
+ },
+
+ extractInstanceAt: function(index) {
+ var previousInstanceLast = this.getLastInstanceNode(index - 1);
+ var lastNode = this.getLastInstanceNode(index);
+ var parent = this.templateElement_.parentNode;
+ var instance = this.instances.splice(index, 1)[0];
+
+ while (lastNode !== previousInstanceLast) {
+ var node = previousInstanceLast.nextSibling;
+ if (node == lastNode)
+ lastNode = previousInstanceLast;
+
+ instance.appendChild(parent.removeChild(node));
+ }
+
+ return instance;
+ },
+
+ getDelegateFn: function(fn) {
+ fn = fn && fn(this.templateElement_);
+ return typeof fn === 'function' ? fn : null;
+ },
+
+ handleSplices: function(splices) {
+ if (this.closed || !splices.length)
+ return;
+
+ var template = this.templateElement_;
+
+ if (!template.parentNode) {
+ this.close();
+ return;
+ }
+
+ ArrayObserver.applySplices(this.iteratedValue, this.presentValue,
+ splices);
+
+ var delegate = template.delegate_;
+ if (this.instanceModelFn_ === undefined) {
+ this.instanceModelFn_ =
+ this.getDelegateFn(delegate && delegate.prepareInstanceModel);
+ }
+
+ if (this.instancePositionChangedFn_ === undefined) {
+ this.instancePositionChangedFn_ =
+ this.getDelegateFn(delegate &&
+ delegate.prepareInstancePositionChanged);
+ }
+
+ // Instance Removals
+ var instanceCache = new Map;
+ var removeDelta = 0;
+ for (var i = 0; i < splices.length; i++) {
+ var splice = splices[i];
+ var removed = splice.removed;
+ for (var j = 0; j < removed.length; j++) {
+ var model = removed[j];
+ var instance = this.extractInstanceAt(splice.index + removeDelta);
+ if (instance !== emptyInstance) {
+ instanceCache.set(model, instance);
+ }
+ }
+
+ removeDelta -= splice.addedCount;
+ }
+
+ // Instance Insertions
+ for (var i = 0; i < splices.length; i++) {
+ var splice = splices[i];
+ var addIndex = splice.index;
+ for (; addIndex < splice.index + splice.addedCount; addIndex++) {
+ var model = this.iteratedValue[addIndex];
+ var instance = instanceCache.get(model);
+ if (instance) {
+ instanceCache.delete(model);
+ } else {
+ if (this.instanceModelFn_) {
+ model = this.instanceModelFn_(model);
+ }
+
+ if (model === undefined) {
+ instance = emptyInstance;
+ } else {
+ instance = template.createInstance(model, undefined, delegate);
+ }
+ }
+
+ this.insertInstanceAt(addIndex, instance);
+ }
+ }
+
+ instanceCache.forEach(function(instance) {
+ this.closeInstanceBindings(instance);
+ }, this);
+
+ if (this.instancePositionChangedFn_)
+ this.reportInstancesMoved(splices);
+ },
+
+ reportInstanceMoved: function(index) {
+ var instance = this.instances[index];
+ if (instance === emptyInstance)
+ return;
+
+ this.instancePositionChangedFn_(instance.templateInstance_, index);
+ },
+
+ reportInstancesMoved: function(splices) {
+ var index = 0;
+ var offset = 0;
+ for (var i = 0; i < splices.length; i++) {
+ var splice = splices[i];
+ if (offset != 0) {
+ while (index < splice.index) {
+ this.reportInstanceMoved(index);
+ index++;
+ }
+ } else {
+ index = splice.index;
+ }
+
+ while (index < splice.index + splice.addedCount) {
+ this.reportInstanceMoved(index);
+ index++;
+ }
+
+ offset += splice.addedCount - splice.removed.length;
+ }
+
+ if (offset == 0)
+ return;
+
+ var length = this.instances.length;
+ while (index < length) {
+ this.reportInstanceMoved(index);
+ index++;
+ }
+ },
+
+ closeInstanceBindings: function(instance) {
+ var bindings = instance.bindings_;
+ for (var i = 0; i < bindings.length; i++) {
+ bindings[i].close();
+ }
+ },
+
+ unobserve: function() {
+ if (!this.arrayObserver)
+ return;
+
+ this.arrayObserver.close();
+ this.arrayObserver = undefined;
+ },
+
+ close: function() {
+ if (this.closed)
+ return;
+ this.unobserve();
+ for (var i = 0; i < this.instances.length; i++) {
+ this.closeInstanceBindings(this.instances[i]);
+ }
+
+ this.instances.length = 0;
+ this.closeDeps();
+ this.templateElement_.iterator_ = undefined;
+ this.closed = true;
+ }
+ };
+
+ // Polyfill-specific API.
+ HTMLTemplateElement.forAllTemplatesFrom_ = forAllTemplatesFrom;
+})(this);
+
+(function(scope) {
+ 'use strict';
+
+ // feature detect for URL constructor
+ var hasWorkingUrl = false;
+ if (!scope.forceJURL) {
+ try {
+ var u = new URL('b', 'http://a');
+ hasWorkingUrl = u.href === 'http://a/b';
+ } catch(e) {}
+ }
+
+ if (hasWorkingUrl)
+ return;
+
+ var relative = Object.create(null);
+ relative['ftp'] = 21;
+ relative['file'] = 0;
+ relative['gopher'] = 70;
+ relative['http'] = 80;
+ relative['https'] = 443;
+ relative['ws'] = 80;
+ relative['wss'] = 443;
+
+ var relativePathDotMapping = Object.create(null);
+ relativePathDotMapping['%2e'] = '.';
+ relativePathDotMapping['.%2e'] = '..';
+ relativePathDotMapping['%2e.'] = '..';
+ relativePathDotMapping['%2e%2e'] = '..';
+
+ function isRelativeScheme(scheme) {
+ return relative[scheme] !== undefined;
+ }
+
+ function invalid() {
+ clear.call(this);
+ this._isInvalid = true;
+ }
+
+ function IDNAToASCII(h) {
+ if ('' == h) {
+ invalid.call(this)
+ }
+ // XXX
+ return h.toLowerCase()
+ }
+
+ function percentEscape(c) {
+ var unicode = c.charCodeAt(0);
+ if (unicode > 0x20 &&
+ unicode < 0x7F &&
+ // " # < > ? `
+ [0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) == -1
+ ) {
+ return c;
+ }
+ return encodeURIComponent(c);
+ }
+
+ function percentEscapeQuery(c) {
+ // XXX This actually needs to encode c using encoding and then
+ // convert the bytes one-by-one.
+
+ var unicode = c.charCodeAt(0);
+ if (unicode > 0x20 &&
+ unicode < 0x7F &&
+ // " # < > ` (do not escape '?')
+ [0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) == -1
+ ) {
+ return c;
+ }
+ return encodeURIComponent(c);
+ }
+
+ var EOF = undefined,
+ ALPHA = /[a-zA-Z]/,
+ ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/;
+
+ function parse(input, stateOverride, base) {
+ function err(message) {
+ errors.push(message)
+ }
+
+ var state = stateOverride || 'scheme start',
+ cursor = 0,
+ buffer = '',
+ seenAt = false,
+ seenBracket = false,
+ errors = [];
+
+ loop: while ((input[cursor - 1] != EOF || cursor == 0) && !this._isInvalid) {
+ var c = input[cursor];
+ switch (state) {
+ case 'scheme start':
+ if (c && ALPHA.test(c)) {
+ buffer += c.toLowerCase(); // ASCII-safe
+ state = 'scheme';
+ } else if (!stateOverride) {
+ buffer = '';
+ state = 'no scheme';
+ continue;
+ } else {
+ err('Invalid scheme.');
+ break loop;
+ }
+ break;
+
+ case 'scheme':
+ if (c && ALPHANUMERIC.test(c)) {
+ buffer += c.toLowerCase(); // ASCII-safe
+ } else if (':' == c) {
+ this._scheme = buffer;
+ buffer = '';
+ if (stateOverride) {
+ break loop;
+ }
+ if (isRelativeScheme(this._scheme)) {
+ this._isRelative = true;
+ }
+ if ('file' == this._scheme) {
+ state = 'relative';
+ } else if (this._isRelative && base && base._scheme == this._scheme) {
+ state = 'relative or authority';
+ } else if (this._isRelative) {
+ state = 'authority first slash';
+ } else {
+ state = 'scheme data';
+ }
+ } else if (!stateOverride) {
+ buffer = '';
+ cursor = 0;
+ state = 'no scheme';
+ continue;
+ } else if (EOF == c) {
+ break loop;
+ } else {
+ err('Code point not allowed in scheme: ' + c)
+ break loop;
+ }
+ break;
+
+ case 'scheme data':
+ if ('?' == c) {
+ query = '?';
+ state = 'query';
+ } else if ('#' == c) {
+ this._fragment = '#';
+ state = 'fragment';
+ } else {
+ // XXX error handling
+ if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
+ this._schemeData += percentEscape(c);
+ }
+ }
+ break;
+
+ case 'no scheme':
+ if (!base || !(isRelativeScheme(base._scheme))) {
+ err('Missing scheme.');
+ invalid.call(this);
+ } else {
+ state = 'relative';
+ continue;
+ }
+ break;
+
+ case 'relative or authority':
+ if ('/' == c && '/' == input[cursor+1]) {
+ state = 'authority ignore slashes';
+ } else {
+ err('Expected /, got: ' + c);
+ state = 'relative';
+ continue
+ }
+ break;
+
+ case 'relative':
+ this._isRelative = true;
+ if ('file' != this._scheme)
+ this._scheme = base._scheme;
+ if (EOF == c) {
+ this._host = base._host;
+ this._port = base._port;
+ this._path = base._path.slice();
+ this._query = base._query;
+ break loop;
+ } else if ('/' == c || '\\' == c) {
+ if ('\\' == c)
+ err('\\ is an invalid code point.');
+ state = 'relative slash';
+ } else if ('?' == c) {
+ this._host = base._host;
+ this._port = base._port;
+ this._path = base._path.slice();
+ this._query = '?';
+ state = 'query';
+ } else if ('#' == c) {
+ this._host = base._host;
+ this._port = base._port;
+ this._path = base._path.slice();
+ this._query = base._query;
+ this._fragment = '#';
+ state = 'fragment';
+ } else {
+ var nextC = input[cursor+1]
+ var nextNextC = input[cursor+2]
+ if (
+ 'file' != this._scheme || !ALPHA.test(c) ||
+ (nextC != ':' && nextC != '|') ||
+ (EOF != nextNextC && '/' != nextNextC && '\\' != nextNextC && '?' != nextNextC && '#' != nextNextC)) {
+ this._host = base._host;
+ this._port = base._port;
+ this._path = base._path.slice();
+ this._path.pop();
+ }
+ state = 'relative path';
+ continue;
+ }
+ break;
+
+ case 'relative slash':
+ if ('/' == c || '\\' == c) {
+ if ('\\' == c) {
+ err('\\ is an invalid code point.');
+ }
+ if ('file' == this._scheme) {
+ state = 'file host';
+ } else {
+ state = 'authority ignore slashes';
+ }
+ } else {
+ if ('file' != this._scheme) {
+ this._host = base._host;
+ this._port = base._port;
+ }
+ state = 'relative path';
+ continue;
+ }
+ break;
+
+ case 'authority first slash':
+ if ('/' == c) {
+ state = 'authority second slash';
+ } else {
+ err("Expected '/', got: " + c);
+ state = 'authority ignore slashes';
+ continue;
+ }
+ break;
+
+ case 'authority second slash':
+ state = 'authority ignore slashes';
+ if ('/' != c) {
+ err("Expected '/', got: " + c);
+ continue;
+ }
+ break;
+
+ case 'authority ignore slashes':
+ if ('/' != c && '\\' != c) {
+ state = 'authority';
+ continue;
+ } else {
+ err('Expected authority, got: ' + c);
+ }
+ break;
+
+ case 'authority':
+ if ('@' == c) {
+ if (seenAt) {
+ err('@ already seen.');
+ buffer += '%40';
+ }
+ seenAt = true;
+ for (var i = 0; i < buffer.length; i++) {
+ var cp = buffer[i];
+ if ('\t' == cp || '\n' == cp || '\r' == cp) {
+ err('Invalid whitespace in authority.');
+ continue;
+ }
+ // XXX check URL code points
+ if (':' == cp && null === this._password) {
+ this._password = '';
+ continue;
+ }
+ var tempC = percentEscape(cp);
+ (null !== this._password) ? this._password += tempC : this._username += tempC;
+ }
+ buffer = '';
+ } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
+ cursor -= buffer.length;
+ buffer = '';
+ state = 'host';
+ continue;
+ } else {
+ buffer += c;
+ }
+ break;
+
+ case 'file host':
+ if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
+ if (buffer.length == 2 && ALPHA.test(buffer[0]) && (buffer[1] == ':' || buffer[1] == '|')) {
+ state = 'relative path';
+ } else if (buffer.length == 0) {
+ state = 'relative path start';
+ } else {
+ this._host = IDNAToASCII.call(this, buffer);
+ buffer = '';
+ state = 'relative path start';
+ }
+ continue;
+ } else if ('\t' == c || '\n' == c || '\r' == c) {
+ err('Invalid whitespace in file host.');
+ } else {
+ buffer += c;
+ }
+ break;
+
+ case 'host':
+ case 'hostname':
+ if (':' == c && !seenBracket) {
+ // XXX host parsing
+ this._host = IDNAToASCII.call(this, buffer);
+ buffer = '';
+ state = 'port';
+ if ('hostname' == stateOverride) {
+ break loop;
+ }
+ } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
+ this._host = IDNAToASCII.call(this, buffer);
+ buffer = '';
+ state = 'relative path start';
+ if (stateOverride) {
+ break loop;
+ }
+ continue;
+ } else if ('\t' != c && '\n' != c && '\r' != c) {
+ if ('[' == c) {
+ seenBracket = true;
+ } else if (']' == c) {
+ seenBracket = false;
+ }
+ buffer += c;
+ } else {
+ err('Invalid code point in host/hostname: ' + c);
+ }
+ break;
+
+ case 'port':
+ if (/[0-9]/.test(c)) {
+ buffer += c;
+ } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c || stateOverride) {
+ if ('' != buffer) {
+ var temp = parseInt(buffer, 10);
+ if (temp != relative[this._scheme]) {
+ this._port = temp + '';
+ }
+ buffer = '';
+ }
+ if (stateOverride) {
+ break loop;
+ }
+ state = 'relative path start';
+ continue;
+ } else if ('\t' == c || '\n' == c || '\r' == c) {
+ err('Invalid code point in port: ' + c);
+ } else {
+ invalid.call(this);
+ }
+ break;
+
+ case 'relative path start':
+ if ('\\' == c)
+ err("'\\' not allowed in path.");
+ state = 'relative path';
+ if ('/' != c && '\\' != c) {
+ continue;
+ }
+ break;
+
+ case 'relative path':
+ if (EOF == c || '/' == c || '\\' == c || (!stateOverride && ('?' == c || '#' == c))) {
+ if ('\\' == c) {
+ err('\\ not allowed in relative path.');
+ }
+ var tmp;
+ if (tmp = relativePathDotMapping[buffer.toLowerCase()]) {
+ buffer = tmp;
+ }
+ if ('..' == buffer) {
+ this._path.pop();
+ if ('/' != c && '\\' != c) {
+ this._path.push('');
+ }
+ } else if ('.' == buffer && '/' != c && '\\' != c) {
+ this._path.push('');
+ } else if ('.' != buffer) {
+ if ('file' == this._scheme && this._path.length == 0 && buffer.length == 2 && ALPHA.test(buffer[0]) && buffer[1] == '|') {
+ buffer = buffer[0] + ':';
+ }
+ this._path.push(buffer);
+ }
+ buffer = '';
+ if ('?' == c) {
+ this._query = '?';
+ state = 'query';
+ } else if ('#' == c) {
+ this._fragment = '#';
+ state = 'fragment';
+ }
+ } else if ('\t' != c && '\n' != c && '\r' != c) {
+ buffer += percentEscape(c);
+ }
+ break;
+
+ case 'query':
+ if (!stateOverride && '#' == c) {
+ this._fragment = '#';
+ state = 'fragment';
+ } else if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
+ this._query += percentEscapeQuery(c);
+ }
+ break;
+
+ case 'fragment':
+ if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
+ this._fragment += c;
+ }
+ break;
+ }
+
+ cursor++;
+ }
+ }
+
+ function clear() {
+ this._scheme = '';
+ this._schemeData = '';
+ this._username = '';
+ this._password = null;
+ this._host = '';
+ this._port = '';
+ this._path = [];
+ this._query = '';
+ this._fragment = '';
+ this._isInvalid = false;
+ this._isRelative = false;
+ }
+
+ // Does not process domain names or IP addresses.
+ // Does not handle encoding for the query parameter.
+ function jURL(url, base /* , encoding */) {
+ if (base !== undefined && !(base instanceof jURL))
+ base = new jURL(String(base));
+
+ this._url = url;
+ clear.call(this);
+
+ var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, '');
+ // encoding = encoding || 'utf-8'
+
+ parse.call(this, input, null, base);
+ }
+
+ jURL.prototype = {
+ get href() {
+ if (this._isInvalid)
+ return this._url;
+
+ var authority = '';
+ if ('' != this._username || null != this._password) {
+ authority = this._username +
+ (null != this._password ? ':' + this._password : '') + '@';
+ }
+
+ return this.protocol +
+ (this._isRelative ? '//' + authority + this.host : '') +
+ this.pathname + this._query + this._fragment;
+ },
+ set href(href) {
+ clear.call(this);
+ parse.call(this, href);
+ },
+
+ get protocol() {
+ return this._scheme + ':';
+ },
+ set protocol(protocol) {
+ if (this._isInvalid)
+ return;
+ parse.call(this, protocol + ':', 'scheme start');
+ },
+
+ get host() {
+ return this._isInvalid ? '' : this._port ?
+ this._host + ':' + this._port : this._host;
+ },
+ set host(host) {
+ if (this._isInvalid || !this._isRelative)
+ return;
+ parse.call(this, host, 'host');
+ },
+
+ get hostname() {
+ return this._host;
+ },
+ set hostname(hostname) {
+ if (this._isInvalid || !this._isRelative)
+ return;
+ parse.call(this, hostname, 'hostname');
+ },
+
+ get port() {
+ return this._port;
+ },
+ set port(port) {
+ if (this._isInvalid || !this._isRelative)
+ return;
+ parse.call(this, port, 'port');
+ },
+
+ get pathname() {
+ return this._isInvalid ? '' : this._isRelative ?
+ '/' + this._path.join('/') : this._schemeData;
+ },
+ set pathname(pathname) {
+ if (this._isInvalid || !this._isRelative)
+ return;
+ this._path = [];
+ parse.call(this, pathname, 'relative path start');
+ },
+
+ get search() {
+ return this._isInvalid || !this._query || '?' == this._query ?
+ '' : this._query;
+ },
+ set search(search) {
+ if (this._isInvalid || !this._isRelative)
+ return;
+ this._query = '?';
+ if ('?' == search[0])
+ search = search.slice(1);
+ parse.call(this, search, 'query');
+ },
+
+ get hash() {
+ return this._isInvalid || !this._fragment || '#' == this._fragment ?
+ '' : this._fragment;
+ },
+ set hash(hash) {
+ if (this._isInvalid)
+ return;
+ this._fragment = '#';
+ if ('#' == hash[0])
+ hash = hash.slice(1);
+ parse.call(this, hash, 'fragment');
+ },
+
+ get origin() {
+ var host;
+ if (this._isInvalid || !this._scheme) {
+ return '';
+ }
+ // javascript: Gecko returns String(""), WebKit/Blink String("null")
+ // Gecko throws error for "data://"
+ // data: Gecko returns "", Blink returns "data://", WebKit returns "null"
+ // Gecko returns String("") for file: mailto:
+ // WebKit/Blink returns String("SCHEME://") for file: mailto:
+ switch (this._scheme) {
+ case 'data':
+ case 'file':
+ case 'javascript':
+ case 'mailto':
+ return 'null';
+ }
+ host = this.host;
+ if (!host) {
+ return '';
+ }
+ return this._scheme + '://' + host;
+ }
+ };
+
+ // Copy over the static methods
+ var OriginalURL = scope.URL;
+ if (OriginalURL) {
+ jURL.createObjectURL = function(blob) {
+ // IE extension allows a second optional options argument.
+ // http://msdn.microsoft.com/en-us/library/ie/hh772302(v=vs.85).aspx
+ return OriginalURL.createObjectURL.apply(OriginalURL, arguments);
+ };
+ jURL.revokeObjectURL = function(url) {
+ OriginalURL.revokeObjectURL(url);
+ };
+ }
+
+ scope.URL = jURL;
+
+})(this);
+
+(function(scope) {
+
+var iterations = 0;
+var callbacks = [];
+var twiddle = document.createTextNode('');
+
+function endOfMicrotask(callback) {
+ twiddle.textContent = iterations++;
+ callbacks.push(callback);
+}
+
+function atEndOfMicrotask() {
+ while (callbacks.length) {
+ callbacks.shift()();
+ }
+}
+
+new (window.MutationObserver || JsMutationObserver)(atEndOfMicrotask)
+ .observe(twiddle, {characterData: true})
+ ;
+
+// exports
+scope.endOfMicrotask = endOfMicrotask;
+// bc
+Platform.endOfMicrotask = endOfMicrotask;
+
+})(Polymer);
+
+
+(function(scope) {
+
+/**
+ * @class Polymer
+ */
+
+// imports
+var endOfMicrotask = scope.endOfMicrotask;
+
+// logging
+var log = window.WebComponents ? WebComponents.flags.log : {};
+
+// inject style sheet
+var style = document.createElement('style');
+style.textContent = 'template {display: none !important;} /* injected by platform.js */';
+var head = document.querySelector('head');
+head.insertBefore(style, head.firstChild);
+
+
+/**
+ * Force any pending data changes to be observed before
+ * the next task. Data changes are processed asynchronously but are guaranteed
+ * to be processed, for example, before paintin. This method should rarely be
+ * needed. It does nothing when Object.observe is available;
+ * when Object.observe is not available, Polymer automatically flushes data
+ * changes approximately every 1/10 second.
+ * Therefore, `flush` should only be used when a data mutation should be
+ * observed sooner than this.
+ *
+ * @method flush
+ */
+// flush (with logging)
+var flushing;
+function flush() {
+ if (!flushing) {
+ flushing = true;
+ endOfMicrotask(function() {
+ flushing = false;
+ log.data && console.group('flush');
+ Platform.performMicrotaskCheckpoint();
+ log.data && console.groupEnd();
+ });
+ }
+};
+
+// polling dirty checker
+// flush periodically if platform does not have object observe.
+if (!Observer.hasObjectObserve) {
+ var FLUSH_POLL_INTERVAL = 125;
+ window.addEventListener('WebComponentsReady', function() {
+ flush();
+ // watch document visiblity to toggle dirty-checking
+ var visibilityHandler = function() {
+ // only flush if the page is visibile
+ if (document.visibilityState === 'hidden') {
+ if (scope.flushPoll) {
+ clearInterval(scope.flushPoll);
+ }
+ } else {
+ scope.flushPoll = setInterval(flush, FLUSH_POLL_INTERVAL);
+ }
+ };
+ if (typeof document.visibilityState === 'string') {
+ document.addEventListener('visibilitychange', visibilityHandler);
+ }
+ visibilityHandler();
+ });
+} else {
+ // make flush a no-op when we have Object.observe
+ flush = function() {};
+}
+
+if (window.CustomElements && !CustomElements.useNative) {
+ var originalImportNode = Document.prototype.importNode;
+ Document.prototype.importNode = function(node, deep) {
+ var imported = originalImportNode.call(this, node, deep);
+ CustomElements.upgradeAll(imported);
+ return imported;
+ };
+}
+
+// exports
+scope.flush = flush;
+// bc
+Platform.flush = flush;
+
+})(window.Polymer);
+
+
+(function(scope) {
+
+var urlResolver = {
+ resolveDom: function(root, url) {
+ url = url || baseUrl(root);
+ this.resolveAttributes(root, url);
+ this.resolveStyles(root, url);
+ // handle template.content
+ var templates = root.querySelectorAll('template');
+ if (templates) {
+ for (var i = 0, l = templates.length, t; (i < l) && (t = templates[i]); i++) {
+ if (t.content) {
+ this.resolveDom(t.content, url);
+ }
+ }
+ }
+ },
+ resolveTemplate: function(template) {
+ this.resolveDom(template.content, baseUrl(template));
+ },
+ resolveStyles: function(root, url) {
+ var styles = root.querySelectorAll('style');
+ if (styles) {
+ for (var i = 0, l = styles.length, s; (i < l) && (s = styles[i]); i++) {
+ this.resolveStyle(s, url);
+ }
+ }
+ },
+ resolveStyle: function(style, url) {
+ url = url || baseUrl(style);
+ style.textContent = this.resolveCssText(style.textContent, url);
+ },
+ resolveCssText: function(cssText, baseUrl, keepAbsolute) {
+ cssText = replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_URL_REGEXP);
+ return replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_IMPORT_REGEXP);
+ },
+ resolveAttributes: function(root, url) {
+ if (root.hasAttributes && root.hasAttributes()) {
+ this.resolveElementAttributes(root, url);
+ }
+ // search for attributes that host urls
+ var nodes = root && root.querySelectorAll(URL_ATTRS_SELECTOR);
+ if (nodes) {
+ for (var i = 0, l = nodes.length, n; (i < l) && (n = nodes[i]); i++) {
+ this.resolveElementAttributes(n, url);
+ }
+ }
+ },
+ resolveElementAttributes: function(node, url) {
+ url = url || baseUrl(node);
+ URL_ATTRS.forEach(function(v) {
+ var attr = node.attributes[v];
+ var value = attr && attr.value;
+ var replacement;
+ if (value && value.search(URL_TEMPLATE_SEARCH) < 0) {
+ if (v === 'style') {
+ replacement = replaceUrlsInCssText(value, url, false, CSS_URL_REGEXP);
+ } else {
+ replacement = resolveRelativeUrl(url, value);
+ }
+ attr.value = replacement;
+ }
+ });
+ }
+};
+
+var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g;
+var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g;
+var URL_ATTRS = ['href', 'src', 'action', 'style', 'url'];
+var URL_ATTRS_SELECTOR = '[' + URL_ATTRS.join('],[') + ']';
+var URL_TEMPLATE_SEARCH = '{{.*}}';
+var URL_HASH = '#';
+
+function baseUrl(node) {
+ var u = new URL(node.ownerDocument.baseURI);
+ u.search = '';
+ u.hash = '';
+ return u;
+}
+
+function replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, regexp) {
+ return cssText.replace(regexp, function(m, pre, url, post) {
+ var urlPath = url.replace(/["']/g, '');
+ urlPath = resolveRelativeUrl(baseUrl, urlPath, keepAbsolute);
+ return pre + '\'' + urlPath + '\'' + post;
+ });
+}
+
+function resolveRelativeUrl(baseUrl, url, keepAbsolute) {
+ // do not resolve '/' absolute urls
+ if (url && url[0] === '/') {
+ return url;
+ }
+ var u = new URL(url, baseUrl);
+ return keepAbsolute ? u.href : makeDocumentRelPath(u.href);
+}
+
+function makeDocumentRelPath(url) {
+ var root = baseUrl(document.documentElement);
+ var u = new URL(url, root);
+ if (u.host === root.host && u.port === root.port &&
+ u.protocol === root.protocol) {
+ return makeRelPath(root, u);
+ } else {
+ return url;
+ }
+}
+
+// make a relative path from source to target
+function makeRelPath(sourceUrl, targetUrl) {
+ var source = sourceUrl.pathname;
+ var target = targetUrl.pathname;
+ var s = source.split('/');
+ var t = target.split('/');
+ while (s.length && s[0] === t[0]){
+ s.shift();
+ t.shift();
+ }
+ for (var i = 0, l = s.length - 1; i < l; i++) {
+ t.unshift('..');
+ }
+ // empty '#' is discarded but we need to preserve it.
+ var hash = (targetUrl.href.slice(-1) === URL_HASH) ? URL_HASH : targetUrl.hash;
+ return t.join('/') + targetUrl.search + hash;
+}
+
+// exports
+scope.urlResolver = urlResolver;
+
+})(Polymer);
+
+(function(scope) {
+ var endOfMicrotask = Polymer.endOfMicrotask;
+
+ // Generic url loader
+ function Loader(regex) {
+ this.cache = Object.create(null);
+ this.map = Object.create(null);
+ this.requests = 0;
+ this.regex = regex;
+ }
+ Loader.prototype = {
+
+ // TODO(dfreedm): there may be a better factoring here
+ // extract absolute urls from the text (full of relative urls)
+ extractUrls: function(text, base) {
+ var matches = [];
+ var matched, u;
+ while ((matched = this.regex.exec(text))) {
+ u = new URL(matched[1], base);
+ matches.push({matched: matched[0], url: u.href});
+ }
+ return matches;
+ },
+ // take a text blob, a root url, and a callback and load all the urls found within the text
+ // returns a map of absolute url to text
+ process: function(text, root, callback) {
+ var matches = this.extractUrls(text, root);
+
+ // every call to process returns all the text this loader has ever received
+ var done = callback.bind(null, this.map);
+ this.fetch(matches, done);
+ },
+ // build a mapping of url -> text from matches
+ fetch: function(matches, callback) {
+ var inflight = matches.length;
+
+ // return early if there is no fetching to be done
+ if (!inflight) {
+ return callback();
+ }
+
+ // wait for all subrequests to return
+ var done = function() {
+ if (--inflight === 0) {
+ callback();
+ }
+ };
+
+ // start fetching all subrequests
+ var m, req, url;
+ for (var i = 0; i < inflight; i++) {
+ m = matches[i];
+ url = m.url;
+ req = this.cache[url];
+ // if this url has already been requested, skip requesting it again
+ if (!req) {
+ req = this.xhr(url);
+ req.match = m;
+ this.cache[url] = req;
+ }
+ // wait for the request to process its subrequests
+ req.wait(done);
+ }
+ },
+ handleXhr: function(request) {
+ var match = request.match;
+ var url = match.url;
+
+ // handle errors with an empty string
+ var response = request.response || request.responseText || '';
+ this.map[url] = response;
+ this.fetch(this.extractUrls(response, url), request.resolve);
+ },
+ xhr: function(url) {
+ this.requests++;
+ var request = new XMLHttpRequest();
+ request.open('GET', url, true);
+ request.send();
+ request.onerror = request.onload = this.handleXhr.bind(this, request);
+
+ // queue of tasks to run after XHR returns
+ request.pending = [];
+ request.resolve = function() {
+ var pending = request.pending;
+ for(var i = 0; i < pending.length; i++) {
+ pending[i]();
+ }
+ request.pending = null;
+ };
+
+ // if we have already resolved, pending is null, async call the callback
+ request.wait = function(fn) {
+ if (request.pending) {
+ request.pending.push(fn);
+ } else {
+ endOfMicrotask(fn);
+ }
+ };
+
+ return request;
+ }
+ };
+
+ scope.Loader = Loader;
+})(Polymer);
+
+(function(scope) {
+
+var urlResolver = scope.urlResolver;
+var Loader = scope.Loader;
+
+function StyleResolver() {
+ this.loader = new Loader(this.regex);
+}
+StyleResolver.prototype = {
+ regex: /@import\s+(?:url)?["'\(]*([^'"\)]*)['"\)]*;/g,
+ // Recursively replace @imports with the text at that url
+ resolve: function(text, url, callback) {
+ var done = function(map) {
+ callback(this.flatten(text, url, map));
+ }.bind(this);
+ this.loader.process(text, url, done);
+ },
+ // resolve the textContent of a style node
+ resolveNode: function(style, url, callback) {
+ var text = style.textContent;
+ var done = function(text) {
+ style.textContent = text;
+ callback(style);
+ };
+ this.resolve(text, url, done);
+ },
+ // flatten all the @imports to text
+ flatten: function(text, base, map) {
+ var matches = this.loader.extractUrls(text, base);
+ var match, url, intermediate;
+ for (var i = 0; i < matches.length; i++) {
+ match = matches[i];
+ url = match.url;
+ // resolve any css text to be relative to the importer, keep absolute url
+ intermediate = urlResolver.resolveCssText(map[url], url, true);
+ // flatten intermediate @imports
+ intermediate = this.flatten(intermediate, base, map);
+ text = text.replace(match.matched, intermediate);
+ }
+ return text;
+ },
+ loadStyles: function(styles, base, callback) {
+ var loaded=0, l = styles.length;
+ // called in the context of the style
+ function loadedStyle(style) {
+ loaded++;
+ if (loaded === l && callback) {
+ callback();
+ }
+ }
+ for (var i=0, s; (i<l) && (s=styles[i]); i++) {
+ this.resolveNode(s, base, loadedStyle);
+ }
+ }
+};
+
+var styleResolver = new StyleResolver();
+
+// exports
+scope.styleResolver = styleResolver;
+
+})(Polymer);
+
+(function(scope) {
+
+ // copy own properties from 'api' to 'prototype, with name hinting for 'super'
+ function extend(prototype, api) {
+ if (prototype && api) {
+ // use only own properties of 'api'
+ Object.getOwnPropertyNames(api).forEach(function(n) {
+ // acquire property descriptor
+ var pd = Object.getOwnPropertyDescriptor(api, n);
+ if (pd) {
+ // clone property via descriptor
+ Object.defineProperty(prototype, n, pd);
+ // cache name-of-method for 'super' engine
+ if (typeof pd.value == 'function') {
+ // hint the 'super' engine
+ pd.value.nom = n;
+ }
+ }
+ });
+ }
+ return prototype;
+ }
+
+
+ // mixin
+
+ // copy all properties from inProps (et al) to inObj
+ function mixin(inObj/*, inProps, inMoreProps, ...*/) {
+ var obj = inObj || {};
+ for (var i = 1; i < arguments.length; i++) {
+ var p = arguments[i];
+ try {
+ for (var n in p) {
+ copyProperty(n, p, obj);
+ }
+ } catch(x) {
+ }
+ }
+ return obj;
+ }
+
+ // copy property inName from inSource object to inTarget object
+ function copyProperty(inName, inSource, inTarget) {
+ var pd = getPropertyDescriptor(inSource, inName);
+ Object.defineProperty(inTarget, inName, pd);
+ }
+
+ // get property descriptor for inName on inObject, even if
+ // inName exists on some link in inObject's prototype chain
+ function getPropertyDescriptor(inObject, inName) {
+ if (inObject) {
+ var pd = Object.getOwnPropertyDescriptor(inObject, inName);
+ return pd || getPropertyDescriptor(Object.getPrototypeOf(inObject), inName);
+ }
+ }
+
+ // exports
+
+ scope.extend = extend;
+ scope.mixin = mixin;
+
+ // for bc
+ Platform.mixin = mixin;
+
+})(Polymer);
+
+(function(scope) {
+
+ // usage
+
+ // invoke cb.call(this) in 100ms, unless the job is re-registered,
+ // which resets the timer
+ //
+ // this.myJob = this.job(this.myJob, cb, 100)
+ //
+ // returns a job handle which can be used to re-register a job
+
+ var Job = function(inContext) {
+ this.context = inContext;
+ this.boundComplete = this.complete.bind(this)
+ };
+ Job.prototype = {
+ go: function(callback, wait) {
+ this.callback = callback;
+ var h;
+ if (!wait) {
+ h = requestAnimationFrame(this.boundComplete);
+ this.handle = function() {
+ cancelAnimationFrame(h);
+ }
+ } else {
+ h = setTimeout(this.boundComplete, wait);
+ this.handle = function() {
+ clearTimeout(h);
+ }
+ }
+ },
+ stop: function() {
+ if (this.handle) {
+ this.handle();
+ this.handle = null;
+ }
+ },
+ complete: function() {
+ if (this.handle) {
+ this.stop();
+ this.callback.call(this.context);
+ }
+ }
+ };
+
+ function job(job, callback, wait) {
+ if (job) {
+ job.stop();
+ } else {
+ job = new Job(this);
+ }
+ job.go(callback, wait);
+ return job;
+ }
+
+ // exports
+
+ scope.job = job;
+
+})(Polymer);
+
+(function(scope) {
+
+ // dom polyfill, additions, and utility methods
+
+ var registry = {};
+
+ HTMLElement.register = function(tag, prototype) {
+ registry[tag] = prototype;
+ };
+
+ // get prototype mapped to node <tag>
+ HTMLElement.getPrototypeForTag = function(tag) {
+ var prototype = !tag ? HTMLElement.prototype : registry[tag];
+ // TODO(sjmiles): creating <tag> is likely to have wasteful side-effects
+ return prototype || Object.getPrototypeOf(document.createElement(tag));
+ };
+
+ // we have to flag propagation stoppage for the event dispatcher
+ var originalStopPropagation = Event.prototype.stopPropagation;
+ Event.prototype.stopPropagation = function() {
+ this.cancelBubble = true;
+ originalStopPropagation.apply(this, arguments);
+ };
+
+
+ // polyfill DOMTokenList
+ // * add/remove: allow these methods to take multiple classNames
+ // * toggle: add a 2nd argument which forces the given state rather
+ // than toggling.
+
+ var add = DOMTokenList.prototype.add;
+ var remove = DOMTokenList.prototype.remove;
+ DOMTokenList.prototype.add = function() {
+ for (var i = 0; i < arguments.length; i++) {
+ add.call(this, arguments[i]);
+ }
+ };
+ DOMTokenList.prototype.remove = function() {
+ for (var i = 0; i < arguments.length; i++) {
+ remove.call(this, arguments[i]);
+ }
+ };
+ DOMTokenList.prototype.toggle = function(name, bool) {
+ if (arguments.length == 1) {
+ bool = !this.contains(name);
+ }
+ bool ? this.add(name) : this.remove(name);
+ };
+ DOMTokenList.prototype.switch = function(oldName, newName) {
+ oldName && this.remove(oldName);
+ newName && this.add(newName);
+ };
+
+ // add array() to NodeList, NamedNodeMap, HTMLCollection
+
+ var ArraySlice = function() {
+ return Array.prototype.slice.call(this);
+ };
+
+ var namedNodeMap = (window.NamedNodeMap || window.MozNamedAttrMap || {});
+
+ NodeList.prototype.array = ArraySlice;
+ namedNodeMap.prototype.array = ArraySlice;
+ HTMLCollection.prototype.array = ArraySlice;
+
+ // utility
+
+ function createDOM(inTagOrNode, inHTML, inAttrs) {
+ var dom = typeof inTagOrNode == 'string' ?
+ document.createElement(inTagOrNode) : inTagOrNode.cloneNode(true);
+ dom.innerHTML = inHTML;
+ if (inAttrs) {
+ for (var n in inAttrs) {
+ dom.setAttribute(n, inAttrs[n]);
+ }
+ }
+ return dom;
+ }
+
+ // exports
+
+ scope.createDOM = createDOM;
+
+})(Polymer);
+
+(function(scope) {
+ // super
+
+ // `arrayOfArgs` is an optional array of args like one might pass
+ // to `Function.apply`
+
+ // TODO(sjmiles):
+ // $super must be installed on an instance or prototype chain
+ // as `super`, and invoked via `this`, e.g.
+ // `this.super();`
+
+ // will not work if function objects are not unique, for example,
+ // when using mixins.
+ // The memoization strategy assumes each function exists on only one
+ // prototype chain i.e. we use the function object for memoizing)
+ // perhaps we can bookkeep on the prototype itself instead
+ function $super(arrayOfArgs) {
+ // since we are thunking a method call, performance is important here:
+ // memoize all lookups, once memoized the fast path calls no other
+ // functions
+ //
+ // find the caller (cannot be `strict` because of 'caller')
+ var caller = $super.caller;
+ // memoized 'name of method'
+ var nom = caller.nom;
+ // memoized next implementation prototype
+ var _super = caller._super;
+ if (!_super) {
+ if (!nom) {
+ nom = caller.nom = nameInThis.call(this, caller);
+ }
+ if (!nom) {
+ console.warn('called super() on a method not installed declaratively (has no .nom property)');
+ }
+ // super prototype is either cached or we have to find it
+ // by searching __proto__ (at the 'top')
+ // invariant: because we cache _super on fn below, we never reach
+ // here from inside a series of calls to super(), so it's ok to
+ // start searching from the prototype of 'this' (at the 'top')
+ // we must never memoize a null super for this reason
+ _super = memoizeSuper(caller, nom, getPrototypeOf(this));
+ }
+ // our super function
+ var fn = _super[nom];
+ if (fn) {
+ // memoize information so 'fn' can call 'super'
+ if (!fn._super) {
+ // must not memoize null, or we lose our invariant above
+ memoizeSuper(fn, nom, _super);
+ }
+ // invoke the inherited method
+ // if 'fn' is not function valued, this will throw
+ return fn.apply(this, arrayOfArgs || []);
+ }
+ }
+
+ function nameInThis(value) {
+ var p = this.__proto__;
+ while (p && p !== HTMLElement.prototype) {
+ // TODO(sjmiles): getOwnPropertyNames is absurdly expensive
+ var n$ = Object.getOwnPropertyNames(p);
+ for (var i=0, l=n$.length, n; i<l && (n=n$[i]); i++) {
+ var d = Object.getOwnPropertyDescriptor(p, n);
+ if (typeof d.value === 'function' && d.value === value) {
+ return n;
+ }
+ }
+ p = p.__proto__;
+ }
+ }
+
+ function memoizeSuper(method, name, proto) {
+ // find and cache next prototype containing `name`
+ // we need the prototype so we can do another lookup
+ // from here
+ var s = nextSuper(proto, name, method);
+ if (s[name]) {
+ // `s` is a prototype, the actual method is `s[name]`
+ // tag super method with it's name for quicker lookups
+ s[name].nom = name;
+ }
+ return method._super = s;
+ }
+
+ function nextSuper(proto, name, caller) {
+ // look for an inherited prototype that implements name
+ while (proto) {
+ if ((proto[name] !== caller) && proto[name]) {
+ return proto;
+ }
+ proto = getPrototypeOf(proto);
+ }
+ // must not return null, or we lose our invariant above
+ // in this case, a super() call was invoked where no superclass
+ // method exists
+ // TODO(sjmiles): thow an exception?
+ return Object;
+ }
+
+ // NOTE: In some platforms (IE10) the prototype chain is faked via
+ // __proto__. Therefore, always get prototype via __proto__ instead of
+ // the more standard Object.getPrototypeOf.
+ function getPrototypeOf(prototype) {
+ return prototype.__proto__;
+ }
+
+ // utility function to precompute name tags for functions
+ // in a (unchained) prototype
+ function hintSuper(prototype) {
+ // tag functions with their prototype name to optimize
+ // super call invocations
+ for (var n in prototype) {
+ var pd = Object.getOwnPropertyDescriptor(prototype, n);
+ if (pd && typeof pd.value === 'function') {
+ pd.value.nom = n;
+ }
+ }
+ }
+
+ // exports
+
+ scope.super = $super;
+
+})(Polymer);
+
+(function(scope) {
+
+ function noopHandler(value) {
+ return value;
+ }
+
+ // helper for deserializing properties of various types to strings
+ var typeHandlers = {
+ string: noopHandler,
+ 'undefined': noopHandler,
+ date: function(value) {
+ return new Date(Date.parse(value) || Date.now());
+ },
+ boolean: function(value) {
+ if (value === '') {
+ return true;
+ }
+ return value === 'false' ? false : !!value;
+ },
+ number: function(value) {
+ var n = parseFloat(value);
+ // hex values like "0xFFFF" parseFloat as 0
+ if (n === 0) {
+ n = parseInt(value);
+ }
+ return isNaN(n) ? value : n;
+ // this code disabled because encoded values (like "0xFFFF")
+ // do not round trip to their original format
+ //return (String(floatVal) === value) ? floatVal : value;
+ },
+ object: function(value, currentValue) {
+ if (currentValue === null) {
+ return value;
+ }
+ try {
+ // If the string is an object, we can parse is with the JSON library.
+ // include convenience replace for single-quotes. If the author omits
+ // quotes altogether, parse will fail.
+ return JSON.parse(value.replace(/'/g, '"'));
+ } catch(e) {
+ // The object isn't valid JSON, return the raw value
+ return value;
+ }
+ },
+ // avoid deserialization of functions
+ 'function': function(value, currentValue) {
+ return currentValue;
+ }
+ };
+
+ function deserializeValue(value, currentValue) {
+ // attempt to infer type from default value
+ var inferredType = typeof currentValue;
+ // invent 'date' type value for Date
+ if (currentValue instanceof Date) {
+ inferredType = 'date';
+ }
+ // delegate deserialization via type string
+ return typeHandlers[inferredType](value, currentValue);
+ }
+
+ // exports
+
+ scope.deserializeValue = deserializeValue;
+
+})(Polymer);
+
+(function(scope) {
+
+ // imports
+
+ var extend = scope.extend;
+
+ // module
+
+ var api = {};
+
+ api.declaration = {};
+ api.instance = {};
+
+ api.publish = function(apis, prototype) {
+ for (var n in apis) {
+ extend(prototype, apis[n]);
+ }
+ };
+
+ // exports
+
+ scope.api = api;
+
+})(Polymer);
+
+(function(scope) {
+
+ /**
+ * @class polymer-base
+ */
+
+ var utils = {
+
+ /**
+ * Invokes a function asynchronously. The context of the callback
+ * function is bound to 'this' automatically. Returns a handle which may
+ * be passed to <a href="#cancelAsync">cancelAsync</a> to cancel the
+ * asynchronous call.
+ *
+ * @method async
+ * @param {Function|String} method
+ * @param {any|Array} args
+ * @param {number} timeout
+ */
+ async: function(method, args, timeout) {
+ // when polyfilling Object.observe, ensure changes
+ // propagate before executing the async method
+ Polymer.flush();
+ // second argument to `apply` must be an array
+ args = (args && args.length) ? args : [args];
+ // function to invoke
+ var fn = function() {
+ (this[method] || method).apply(this, args);
+ }.bind(this);
+ // execute `fn` sooner or later
+ var handle = timeout ? setTimeout(fn, timeout) :
+ requestAnimationFrame(fn);
+ // NOTE: switch on inverting handle to determine which time is used.
+ return timeout ? handle : ~handle;
+ },
+
+ /**
+ * Cancels a pending callback that was scheduled via
+ * <a href="#async">async</a>.
+ *
+ * @method cancelAsync
+ * @param {handle} handle Handle of the `async` to cancel.
+ */
+ cancelAsync: function(handle) {
+ if (handle < 0) {
+ cancelAnimationFrame(~handle);
+ } else {
+ clearTimeout(handle);
+ }
+ },
+
+ /**
+ * Fire an event.
+ *
+ * @method fire
+ * @returns {Object} event
+ * @param {string} type An event name.
+ * @param {any} detail
+ * @param {Node} onNode Target node.
+ * @param {Boolean} bubbles Set false to prevent bubbling, defaults to true
+ * @param {Boolean} cancelable Set false to prevent cancellation, defaults to true
+ */
+ fire: function(type, detail, onNode, bubbles, cancelable) {
+ var node = onNode || this;
+ var detail = detail === null || detail === undefined ? {} : detail;
+ var event = new CustomEvent(type, {
+ bubbles: bubbles !== undefined ? bubbles : true,
+ cancelable: cancelable !== undefined ? cancelable : true,
+ detail: detail
+ });
+ node.dispatchEvent(event);
+ return event;
+ },
+
+ /**
+ * Fire an event asynchronously.
+ *
+ * @method asyncFire
+ * @param {string} type An event name.
+ * @param detail
+ * @param {Node} toNode Target node.
+ */
+ asyncFire: function(/*inType, inDetail*/) {
+ this.async("fire", arguments);
+ },
+
+ /**
+ * Remove class from old, add class to anew, if they exist.
+ *
+ * @param classFollows
+ * @param anew A node.
+ * @param old A node
+ * @param className
+ */
+ classFollows: function(anew, old, className) {
+ if (old) {
+ old.classList.remove(className);
+ }
+ if (anew) {
+ anew.classList.add(className);
+ }
+ },
+
+ /**
+ * Inject HTML which contains markup bound to this element into
+ * a target element (replacing target element content).
+ *
+ * @param String html to inject
+ * @param Element target element
+ */
+ injectBoundHTML: function(html, element) {
+ var template = document.createElement('template');
+ template.innerHTML = html;
+ var fragment = this.instanceTemplate(template);
+ if (element) {
+ element.textContent = '';
+ element.appendChild(fragment);
+ }
+ return fragment;
+ }
+ };
+
+ // no-operation function for handy stubs
+ var nop = function() {};
+
+ // null-object for handy stubs
+ var nob = {};
+
+ // deprecated
+
+ utils.asyncMethod = utils.async;
+
+ // exports
+
+ scope.api.instance.utils = utils;
+ scope.nop = nop;
+ scope.nob = nob;
+
+})(Polymer);
+
+(function(scope) {
+
+ // imports
+
+ var log = window.WebComponents ? WebComponents.flags.log : {};
+ var EVENT_PREFIX = 'on-';
+
+ // instance events api
+ var events = {
+ // read-only
+ EVENT_PREFIX: EVENT_PREFIX,
+ // event listeners on host
+ addHostListeners: function() {
+ var events = this.eventDelegates;
+ log.events && (Object.keys(events).length > 0) && console.log('[%s] addHostListeners:', this.localName, events);
+ // NOTE: host events look like bindings but really are not;
+ // (1) we don't want the attribute to be set and (2) we want to support
+ // multiple event listeners ('host' and 'instance') and Node.bind
+ // by default supports 1 thing being bound.
+ for (var type in events) {
+ var methodName = events[type];
+ PolymerGestures.addEventListener(this, type, this.element.getEventHandler(this, this, methodName));
+ }
+ },
+ // call 'method' or function method on 'obj' with 'args', if the method exists
+ dispatchMethod: function(obj, method, args) {
+ if (obj) {
+ log.events && console.group('[%s] dispatch [%s]', obj.localName, method);
+ var fn = typeof method === 'function' ? method : obj[method];
+ if (fn) {
+ fn[args ? 'apply' : 'call'](obj, args);
+ }
+ log.events && console.groupEnd();
+ // NOTE: dirty check right after calling method to ensure
+ // changes apply quickly; in a very complicated app using high
+ // frequency events, this can be a perf concern; in this case,
+ // imperative handlers can be used to avoid flushing.
+ Polymer.flush();
+ }
+ }
+ };
+
+ // exports
+
+ scope.api.instance.events = events;
+
+ /**
+ * @class Polymer
+ */
+
+ /**
+ * Add a gesture aware event handler to the given `node`. Can be used
+ * in place of `element.addEventListener` and ensures gestures will function
+ * as expected on mobile platforms. Please note that Polymer's declarative
+ * event handlers include this functionality by default.
+ *
+ * @method addEventListener
+ * @param {Node} node node on which to listen
+ * @param {String} eventType name of the event
+ * @param {Function} handlerFn event handler function
+ * @param {Boolean} capture set to true to invoke event capturing
+ * @type Function
+ */
+ // alias PolymerGestures event listener logic
+ scope.addEventListener = function(node, eventType, handlerFn, capture) {
+ PolymerGestures.addEventListener(wrap(node), eventType, handlerFn, capture);
+ };
+
+ /**
+ * Remove a gesture aware event handler on the given `node`. To remove an
+ * event listener, the exact same arguments are required that were passed
+ * to `Polymer.addEventListener`.
+ *
+ * @method removeEventListener
+ * @param {Node} node node on which to listen
+ * @param {String} eventType name of the event
+ * @param {Function} handlerFn event handler function
+ * @param {Boolean} capture set to true to invoke event capturing
+ * @type Function
+ */
+ scope.removeEventListener = function(node, eventType, handlerFn, capture) {
+ PolymerGestures.removeEventListener(wrap(node), eventType, handlerFn, capture);
+ };
+
+})(Polymer);
+
+(function(scope) {
+
+ // instance api for attributes
+
+ var attributes = {
+ // copy attributes defined in the element declaration to the instance
+ // e.g. <polymer-element name="x-foo" tabIndex="0"> tabIndex is copied
+ // to the element instance here.
+ copyInstanceAttributes: function () {
+ var a$ = this._instanceAttributes;
+ for (var k in a$) {
+ if (!this.hasAttribute(k)) {
+ this.setAttribute(k, a$[k]);
+ }
+ }
+ },
+ // for each attribute on this, deserialize value to property as needed
+ takeAttributes: function() {
+ // if we have no publish lookup table, we have no attributes to take
+ // TODO(sjmiles): ad hoc
+ if (this._publishLC) {
+ for (var i=0, a$=this.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++) {
+ this.attributeToProperty(a.name, a.value);
+ }
+ }
+ },
+ // if attribute 'name' is mapped to a property, deserialize
+ // 'value' into that property
+ attributeToProperty: function(name, value) {
+ // try to match this attribute to a property (attributes are
+ // all lower-case, so this is case-insensitive search)
+ var name = this.propertyForAttribute(name);
+ if (name) {
+ // filter out 'mustached' values, these are to be
+ // replaced with bound-data and are not yet values
+ // themselves
+ if (value && value.search(scope.bindPattern) >= 0) {
+ return;
+ }
+ // get original value
+ var currentValue = this[name];
+ // deserialize Boolean or Number values from attribute
+ var value = this.deserializeValue(value, currentValue);
+ // only act if the value has changed
+ if (value !== currentValue) {
+ // install new value (has side-effects)
+ this[name] = value;
+ }
+ }
+ },
+ // return the published property matching name, or undefined
+ propertyForAttribute: function(name) {
+ var match = this._publishLC && this._publishLC[name];
+ return match;
+ },
+ // convert representation of `stringValue` based on type of `currentValue`
+ deserializeValue: function(stringValue, currentValue) {
+ return scope.deserializeValue(stringValue, currentValue);
+ },
+ // convert to a string value based on the type of `inferredType`
+ serializeValue: function(value, inferredType) {
+ if (inferredType === 'boolean') {
+ return value ? '' : undefined;
+ } else if (inferredType !== 'object' && inferredType !== 'function'
+ && value !== undefined) {
+ return value;
+ }
+ },
+ // serializes `name` property value and updates the corresponding attribute
+ // note that reflection is opt-in.
+ reflectPropertyToAttribute: function(name) {
+ var inferredType = typeof this[name];
+ // try to intelligently serialize property value
+ var serializedValue = this.serializeValue(this[name], inferredType);
+ // boolean properties must reflect as boolean attributes
+ if (serializedValue !== undefined) {
+ this.setAttribute(name, serializedValue);
+ // TODO(sorvell): we should remove attr for all properties
+ // that have undefined serialization; however, we will need to
+ // refine the attr reflection system to achieve this; pica, for example,
+ // relies on having inferredType object properties not removed as
+ // attrs.
+ } else if (inferredType === 'boolean') {
+ this.removeAttribute(name);
+ }
+ }
+ };
+
+ // exports
+
+ scope.api.instance.attributes = attributes;
+
+})(Polymer);
+
+(function(scope) {
+
+ /**
+ * @class polymer-base
+ */
+
+ // imports
+
+ var log = window.WebComponents ? WebComponents.flags.log : {};
+
+ // magic words
+
+ var OBSERVE_SUFFIX = 'Changed';
+
+ // element api
+
+ var empty = [];
+
+ var updateRecord = {
+ object: undefined,
+ type: 'update',
+ name: undefined,
+ oldValue: undefined
+ };
+
+ var numberIsNaN = Number.isNaN || function(value) {
+ return typeof value === 'number' && isNaN(value);
+ };
+
+ function areSameValue(left, right) {
+ if (left === right)
+ return left !== 0 || 1 / left === 1 / right;
+ if (numberIsNaN(left) && numberIsNaN(right))
+ return true;
+ return left !== left && right !== right;
+ }
+
+ // capture A's value if B's value is null or undefined,
+ // otherwise use B's value
+ function resolveBindingValue(oldValue, value) {
+ if (value === undefined && oldValue === null) {
+ return value;
+ }
+ return (value === null || value === undefined) ? oldValue : value;
+ }
+
+ var properties = {
+
+ // creates a CompoundObserver to observe property changes
+ // NOTE, this is only done there are any properties in the `observe` object
+ createPropertyObserver: function() {
+ var n$ = this._observeNames;
+ if (n$ && n$.length) {
+ var o = this._propertyObserver = new CompoundObserver(true);
+ this.registerObserver(o);
+ // TODO(sorvell): may not be kosher to access the value here (this[n]);
+ // previously we looked at the descriptor on the prototype
+ // this doesn't work for inheritance and not for accessors without
+ // a value property
+ for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) {
+ o.addPath(this, n);
+ this.observeArrayValue(n, this[n], null);
+ }
+ }
+ },
+
+ // start observing property changes
+ openPropertyObserver: function() {
+ if (this._propertyObserver) {
+ this._propertyObserver.open(this.notifyPropertyChanges, this);
+ }
+ },
+
+ // handler for property changes; routes changes to observing methods
+ // note: array valued properties are observed for array splices
+ notifyPropertyChanges: function(newValues, oldValues, paths) {
+ var name, method, called = {};
+ for (var i in oldValues) {
+ // note: paths is of form [object, path, object, path]
+ name = paths[2 * i + 1];
+ method = this.observe[name];
+ if (method) {
+ var ov = oldValues[i], nv = newValues[i];
+ // observes the value if it is an array
+ this.observeArrayValue(name, nv, ov);
+ if (!called[method]) {
+ // only invoke change method if one of ov or nv is not (undefined | null)
+ if ((ov !== undefined && ov !== null) || (nv !== undefined && nv !== null)) {
+ called[method] = true;
+ // TODO(sorvell): call method with the set of values it's expecting;
+ // e.g. 'foo bar': 'invalidate' expects the new and old values for
+ // foo and bar. Currently we give only one of these and then
+ // deliver all the arguments.
+ this.invokeMethod(method, [ov, nv, arguments]);
+ }
+ }
+ }
+ }
+ },
+
+ // call method iff it exists.
+ invokeMethod: function(method, args) {
+ var fn = this[method] || method;
+ if (typeof fn === 'function') {
+ fn.apply(this, args);
+ }
+ },
+
+ /**
+ * Force any pending property changes to synchronously deliver to
+ * handlers specified in the `observe` object.
+ * Note, normally changes are processed at microtask time.
+ *
+ * @method deliverChanges
+ */
+ deliverChanges: function() {
+ if (this._propertyObserver) {
+ this._propertyObserver.deliver();
+ }
+ },
+
+ observeArrayValue: function(name, value, old) {
+ // we only care if there are registered side-effects
+ var callbackName = this.observe[name];
+ if (callbackName) {
+ // if we are observing the previous value, stop
+ if (Array.isArray(old)) {
+ log.observe && console.log('[%s] observeArrayValue: unregister observer [%s]', this.localName, name);
+ this.closeNamedObserver(name + '__array');
+ }
+ // if the new value is an array, being observing it
+ if (Array.isArray(value)) {
+ log.observe && console.log('[%s] observeArrayValue: register observer [%s]', this.localName, name, value);
+ var observer = new ArrayObserver(value);
+ observer.open(function(splices) {
+ this.invokeMethod(callbackName, [splices]);
+ }, this);
+ this.registerNamedObserver(name + '__array', observer);
+ }
+ }
+ },
+
+ emitPropertyChangeRecord: function(name, value, oldValue) {
+ var object = this;
+ if (areSameValue(value, oldValue)) {
+ return;
+ }
+ // invoke property change side effects
+ this._propertyChanged(name, value, oldValue);
+ // emit change record
+ if (!Observer.hasObjectObserve) {
+ return;
+ }
+ var notifier = this._objectNotifier;
+ if (!notifier) {
+ notifier = this._objectNotifier = Object.getNotifier(this);
+ }
+ updateRecord.object = this;
+ updateRecord.name = name;
+ updateRecord.oldValue = oldValue;
+ notifier.notify(updateRecord);
+ },
+
+ _propertyChanged: function(name, value, oldValue) {
+ if (this.reflect[name]) {
+ this.reflectPropertyToAttribute(name);
+ }
+ },
+
+ // creates a property binding (called via bind) to a published property.
+ bindProperty: function(property, observable, oneTime) {
+ if (oneTime) {
+ this[property] = observable;
+ return;
+ }
+ var computed = this.element.prototype.computed;
+ // Binding an "out-only" value to a computed property. Note that
+ // since this observer isn't opened, it doesn't need to be closed on
+ // cleanup.
+ if (computed && computed[property]) {
+ var privateComputedBoundValue = property + 'ComputedBoundObservable_';
+ this[privateComputedBoundValue] = observable;
+ return;
+ }
+ return this.bindToAccessor(property, observable, resolveBindingValue);
+ },
+
+ // NOTE property `name` must be published. This makes it an accessor.
+ bindToAccessor: function(name, observable, resolveFn) {
+ var privateName = name + '_';
+ var privateObservable = name + 'Observable_';
+ // Present for properties which are computed and published and have a
+ // bound value.
+ var privateComputedBoundValue = name + 'ComputedBoundObservable_';
+ this[privateObservable] = observable;
+ var oldValue = this[privateName];
+ // observable callback
+ var self = this;
+ function updateValue(value, oldValue) {
+ self[privateName] = value;
+ var setObserveable = self[privateComputedBoundValue];
+ if (setObserveable && typeof setObserveable.setValue == 'function') {
+ setObserveable.setValue(value);
+ }
+ self.emitPropertyChangeRecord(name, value, oldValue);
+ }
+ // resolve initial value
+ var value = observable.open(updateValue);
+ if (resolveFn && !areSameValue(oldValue, value)) {
+ var resolvedValue = resolveFn(oldValue, value);
+ if (!areSameValue(value, resolvedValue)) {
+ value = resolvedValue;
+ if (observable.setValue) {
+ observable.setValue(value);
+ }
+ }
+ }
+ updateValue(value, oldValue);
+ // register and return observable
+ var observer = {
+ close: function() {
+ observable.close();
+ self[privateObservable] = undefined;
+ self[privateComputedBoundValue] = undefined;
+ }
+ };
+ this.registerObserver(observer);
+ return observer;
+ },
+
+ createComputedProperties: function() {
+ if (!this._computedNames) {
+ return;
+ }
+ for (var i = 0; i < this._computedNames.length; i++) {
+ var name = this._computedNames[i];
+ var expressionText = this.computed[name];
+ try {
+ var expression = PolymerExpressions.getExpression(expressionText);
+ var observable = expression.getBinding(this, this.element.syntax);
+ this.bindToAccessor(name, observable);
+ } catch (ex) {
+ console.error('Failed to create computed property', ex);
+ }
+ }
+ },
+
+ // property bookkeeping
+ registerObserver: function(observer) {
+ if (!this._observers) {
+ this._observers = [observer];
+ return;
+ }
+ this._observers.push(observer);
+ },
+
+ closeObservers: function() {
+ if (!this._observers) {
+ return;
+ }
+ // observer array items are arrays of observers.
+ var observers = this._observers;
+ for (var i = 0; i < observers.length; i++) {
+ var observer = observers[i];
+ if (observer && typeof observer.close == 'function') {
+ observer.close();
+ }
+ }
+ this._observers = [];
+ },
+
+ // bookkeeping observers for memory management
+ registerNamedObserver: function(name, observer) {
+ var o$ = this._namedObservers || (this._namedObservers = {});
+ o$[name] = observer;
+ },
+
+ closeNamedObserver: function(name) {
+ var o$ = this._namedObservers;
+ if (o$ && o$[name]) {
+ o$[name].close();
+ o$[name] = null;
+ return true;
+ }
+ },
+
+ closeNamedObservers: function() {
+ if (this._namedObservers) {
+ for (var i in this._namedObservers) {
+ this.closeNamedObserver(i);
+ }
+ this._namedObservers = {};
+ }
+ }
+
+ };
+
+ // logging
+ var LOG_OBSERVE = '[%s] watching [%s]';
+ var LOG_OBSERVED = '[%s#%s] watch: [%s] now [%s] was [%s]';
+ var LOG_CHANGED = '[%s#%s] propertyChanged: [%s] now [%s] was [%s]';
+
+ // exports
+
+ scope.api.instance.properties = properties;
+
+})(Polymer);
+
+(function(scope) {
+
+ /**
+ * @class polymer-base
+ */
+
+ // imports
+
+ var log = window.WebComponents ? WebComponents.flags.log : {};
+
+ // element api supporting mdv
+ var mdv = {
+
+ /**
+ * Creates dom cloned from the given template, instantiating bindings
+ * with this element as the template model and `PolymerExpressions` as the
+ * binding delegate.
+ *
+ * @method instanceTemplate
+ * @param {Template} template source template from which to create dom.
+ */
+ instanceTemplate: function(template) {
+ // ensure template is decorated (lets' things like <tr template ...> work)
+ HTMLTemplateElement.decorate(template);
+ // ensure a default bindingDelegate
+ var syntax = this.syntax || (!template.bindingDelegate &&
+ this.element.syntax);
+ var dom = template.createInstance(this, syntax);
+ var observers = dom.bindings_;
+ for (var i = 0; i < observers.length; i++) {
+ this.registerObserver(observers[i]);
+ }
+ return dom;
+ },
+
+ // Called by TemplateBinding/NodeBind to setup a binding to the given
+ // property. It's overridden here to support property bindings
+ // in addition to attribute bindings that are supported by default.
+ bind: function(name, observable, oneTime) {
+ var property = this.propertyForAttribute(name);
+ if (!property) {
+ // TODO(sjmiles): this mixin method must use the special form
+ // of `super` installed by `mixinMethod` in declaration/prototype.js
+ return this.mixinSuper(arguments);
+ } else {
+ // use n-way Polymer binding
+ var observer = this.bindProperty(property, observable, oneTime);
+ // NOTE: reflecting binding information is typically required only for
+ // tooling. It has a performance cost so it's opt-in in Node.bind.
+ if (Platform.enableBindingsReflection && observer) {
+ observer.path = observable.path_;
+ this._recordBinding(property, observer);
+ }
+ if (this.reflect[property]) {
+ this.reflectPropertyToAttribute(property);
+ }
+ return observer;
+ }
+ },
+
+ _recordBinding: function(name, observer) {
+ this.bindings_ = this.bindings_ || {};
+ this.bindings_[name] = observer;
+ },
+
+ // Called by TemplateBinding when all bindings on an element have been
+ // executed. This signals that all element inputs have been gathered
+ // and it's safe to ready the element, create shadow-root and start
+ // data-observation.
+ bindFinished: function() {
+ this.makeElementReady();
+ },
+
+ // called at detached time to signal that an element's bindings should be
+ // cleaned up. This is done asynchronously so that users have the chance
+ // to call `cancelUnbindAll` to prevent unbinding.
+ asyncUnbindAll: function() {
+ if (!this._unbound) {
+ log.unbind && console.log('[%s] asyncUnbindAll', this.localName);
+ this._unbindAllJob = this.job(this._unbindAllJob, this.unbindAll, 0);
+ }
+ },
+
+ /**
+ * This method should rarely be used and only if
+ * <a href="#cancelUnbindAll">`cancelUnbindAll`</a> has been called to
+ * prevent element unbinding. In this case, the element's bindings will
+ * not be automatically cleaned up and it cannot be garbage collected
+ * by the system. If memory pressure is a concern or a
+ * large amount of elements need to be managed in this way, `unbindAll`
+ * can be called to deactivate the element's bindings and allow its
+ * memory to be reclaimed.
+ *
+ * @method unbindAll
+ */
+ unbindAll: function() {
+ if (!this._unbound) {
+ this.closeObservers();
+ this.closeNamedObservers();
+ this._unbound = true;
+ }
+ },
+
+ /**
+ * Call in `detached` to prevent the element from unbinding when it is
+ * detached from the dom. The element is unbound as a cleanup step that
+ * allows its memory to be reclaimed.
+ * If `cancelUnbindAll` is used, consider calling
+ * <a href="#unbindAll">`unbindAll`</a> when the element is no longer
+ * needed. This will allow its memory to be reclaimed.
+ *
+ * @method cancelUnbindAll
+ */
+ cancelUnbindAll: function() {
+ if (this._unbound) {
+ log.unbind && console.warn('[%s] already unbound, cannot cancel unbindAll', this.localName);
+ return;
+ }
+ log.unbind && console.log('[%s] cancelUnbindAll', this.localName);
+ if (this._unbindAllJob) {
+ this._unbindAllJob = this._unbindAllJob.stop();
+ }
+ }
+
+ };
+
+ function unbindNodeTree(node) {
+ forNodeTree(node, _nodeUnbindAll);
+ }
+
+ function _nodeUnbindAll(node) {
+ node.unbindAll();
+ }
+
+ function forNodeTree(node, callback) {
+ if (node) {
+ callback(node);
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ forNodeTree(child, callback);
+ }
+ }
+ }
+
+ var mustachePattern = /\{\{([^{}]*)}}/;
+
+ // exports
+
+ scope.bindPattern = mustachePattern;
+ scope.api.instance.mdv = mdv;
+
+})(Polymer);
+
+(function(scope) {
+
+ /**
+ * Common prototype for all Polymer Elements.
+ *
+ * @class polymer-base
+ * @homepage polymer.github.io
+ */
+ var base = {
+ /**
+ * Tags this object as the canonical Base prototype.
+ *
+ * @property PolymerBase
+ * @type boolean
+ * @default true
+ */
+ PolymerBase: true,
+
+ /**
+ * Debounce signals.
+ *
+ * Call `job` to defer a named signal, and all subsequent matching signals,
+ * until a wait time has elapsed with no new signal.
+ *
+ * debouncedClickAction: function(e) {
+ * // processClick only when it's been 100ms since the last click
+ * this.job('click', function() {
+ * this.processClick;
+ * }, 100);
+ * }
+ *
+ * @method job
+ * @param String {String} job A string identifier for the job to debounce.
+ * @param Function {Function} callback A function that is called (with `this` context) when the wait time elapses.
+ * @param Number {Number} wait Time in milliseconds (ms) after the last signal that must elapse before invoking `callback`
+ * @type Handle
+ */
+ job: function(job, callback, wait) {
+ if (typeof job === 'string') {
+ var n = '___' + job;
+ this[n] = Polymer.job.call(this, this[n], callback, wait);
+ } else {
+ // TODO(sjmiles): suggest we deprecate this call signature
+ return Polymer.job.call(this, job, callback, wait);
+ }
+ },
+
+ /**
+ * Invoke a superclass method.
+ *
+ * Use `super()` to invoke the most recently overridden call to the
+ * currently executing function.
+ *
+ * To pass arguments through, use the literal `arguments` as the parameter
+ * to `super()`.
+ *
+ * nextPageAction: function(e) {
+ * // invoke the superclass version of `nextPageAction`
+ * this.super(arguments);
+ * }
+ *
+ * To pass custom arguments, arrange them in an array.
+ *
+ * appendSerialNo: function(value, serial) {
+ * // prefix the superclass serial number with our lot # before
+ * // invoking the superlcass
+ * return this.super([value, this.lotNo + serial])
+ * }
+ *
+ * @method super
+ * @type Any
+ * @param {args) An array of arguments to use when calling the superclass method, or null.
+ */
+ super: Polymer.super,
+
+ /**
+ * Lifecycle method called when the element is instantiated.
+ *
+ * Override `created` to perform custom create-time tasks. No need to call
+ * super-class `created` unless you are extending another Polymer element.
+ * Created is called before the element creates `shadowRoot` or prepares
+ * data-observation.
+ *
+ * @method created
+ * @type void
+ */
+ created: function() {
+ },
+
+ /**
+ * Lifecycle method called when the element has populated it's `shadowRoot`,
+ * prepared data-observation, and made itself ready for API interaction.
+ *
+ * @method ready
+ * @type void
+ */
+ ready: function() {
+ },
+
+ /**
+ * Low-level lifecycle method called as part of standard Custom Elements
+ * operation. Polymer implements this method to provide basic default
+ * functionality. For custom create-time tasks, implement `created`
+ * instead, which is called immediately after `createdCallback`.
+ *
+ * @method createdCallback
+ */
+ createdCallback: function() {
+ if (this.templateInstance && this.templateInstance.model) {
+ console.warn('Attributes on ' + this.localName + ' were data bound ' +
+ 'prior to Polymer upgrading the element. This may result in ' +
+ 'incorrect binding types.');
+ }
+ this.created();
+ this.prepareElement();
+ if (!this.ownerDocument.isStagingDocument) {
+ this.makeElementReady();
+ }
+ },
+
+ // system entry point, do not override
+ prepareElement: function() {
+ if (this._elementPrepared) {
+ console.warn('Element already prepared', this.localName);
+ return;
+ }
+ this._elementPrepared = true;
+ // storage for shadowRoots info
+ this.shadowRoots = {};
+ // install property observers
+ this.createPropertyObserver();
+ this.openPropertyObserver();
+ // install boilerplate attributes
+ this.copyInstanceAttributes();
+ // process input attributes
+ this.takeAttributes();
+ // add event listeners
+ this.addHostListeners();
+ },
+
+ // system entry point, do not override
+ makeElementReady: function() {
+ if (this._readied) {
+ return;
+ }
+ this._readied = true;
+ this.createComputedProperties();
+ this.parseDeclarations(this.__proto__);
+ // NOTE: Support use of the `unresolved` attribute to help polyfill
+ // custom elements' `:unresolved` feature.
+ this.removeAttribute('unresolved');
+ // user entry point
+ this.ready();
+ },
+
+ /**
+ * Low-level lifecycle method called as part of standard Custom Elements
+ * operation. Polymer implements this method to provide basic default
+ * functionality. For custom tasks in your element, implement `attributeChanged`
+ * instead, which is called immediately after `attributeChangedCallback`.
+ *
+ * @method attributeChangedCallback
+ */
+ attributeChangedCallback: function(name, oldValue) {
+ // TODO(sjmiles): adhoc filter
+ if (name !== 'class' && name !== 'style') {
+ this.attributeToProperty(name, this.getAttribute(name));
+ }
+ if (this.attributeChanged) {
+ this.attributeChanged.apply(this, arguments);
+ }
+ },
+
+ /**
+ * Low-level lifecycle method called as part of standard Custom Elements
+ * operation. Polymer implements this method to provide basic default
+ * functionality. For custom create-time tasks, implement `attached`
+ * instead, which is called immediately after `attachedCallback`.
+ *
+ * @method attachedCallback
+ */
+ attachedCallback: function() {
+ // when the element is attached, prevent it from unbinding.
+ this.cancelUnbindAll();
+ // invoke user action
+ if (this.attached) {
+ this.attached();
+ }
+ if (!this.hasBeenAttached) {
+ this.hasBeenAttached = true;
+ if (this.domReady) {
+ this.async('domReady');
+ }
+ }
+ },
+
+ /**
+ * Implement to access custom elements in dom descendants, ancestors,
+ * or siblings. Because custom elements upgrade in document order,
+ * elements accessed in `ready` or `attached` may not be upgraded. When
+ * `domReady` is called, all registered custom elements are guaranteed
+ * to have been upgraded.
+ *
+ * @method domReady
+ */
+
+ /**
+ * Low-level lifecycle method called as part of standard Custom Elements
+ * operation. Polymer implements this method to provide basic default
+ * functionality. For custom create-time tasks, implement `detached`
+ * instead, which is called immediately after `detachedCallback`.
+ *
+ * @method detachedCallback
+ */
+ detachedCallback: function() {
+ if (!this.preventDispose) {
+ this.asyncUnbindAll();
+ }
+ // invoke user action
+ if (this.detached) {
+ this.detached();
+ }
+ // TODO(sorvell): bc
+ if (this.leftView) {
+ this.leftView();
+ }
+ },
+
+ /**
+ * Walks the prototype-chain of this element and allows specific
+ * classes a chance to process static declarations.
+ *
+ * In particular, each polymer-element has it's own `template`.
+ * `parseDeclarations` is used to accumulate all element `template`s
+ * from an inheritance chain.
+ *
+ * `parseDeclaration` static methods implemented in the chain are called
+ * recursively, oldest first, with the `<polymer-element>` associated
+ * with the current prototype passed as an argument.
+ *
+ * An element may override this method to customize shadow-root generation.
+ *
+ * @method parseDeclarations
+ */
+ parseDeclarations: function(p) {
+ if (p && p.element) {
+ this.parseDeclarations(p.__proto__);
+ p.parseDeclaration.call(this, p.element);
+ }
+ },
+
+ /**
+ * Perform init-time actions based on static information in the
+ * `<polymer-element>` instance argument.
+ *
+ * For example, the standard implementation locates the template associated
+ * with the given `<polymer-element>` and stamps it into a shadow-root to
+ * implement shadow inheritance.
+ *
+ * An element may override this method for custom behavior.
+ *
+ * @method parseDeclaration
+ */
+ parseDeclaration: function(elementElement) {
+ var template = this.fetchTemplate(elementElement);
+ if (template) {
+ var root = this.shadowFromTemplate(template);
+ this.shadowRoots[elementElement.name] = root;
+ }
+ },
+
+ /**
+ * Given a `<polymer-element>`, find an associated template (if any) to be
+ * used for shadow-root generation.
+ *
+ * An element may override this method for custom behavior.
+ *
+ * @method fetchTemplate
+ */
+ fetchTemplate: function(elementElement) {
+ return elementElement.querySelector('template');
+ },
+
+ /**
+ * Create a shadow-root in this host and stamp `template` as it's
+ * content.
+ *
+ * An element may override this method for custom behavior.
+ *
+ * @method shadowFromTemplate
+ */
+ shadowFromTemplate: function(template) {
+ if (template) {
+ // make a shadow root
+ var root = this.createShadowRoot();
+ // stamp template
+ // which includes parsing and applying MDV bindings before being
+ // inserted (to avoid {{}} in attribute values)
+ // e.g. to prevent <img src="images/{{icon}}"> from generating a 404.
+ var dom = this.instanceTemplate(template);
+ // append to shadow dom
+ root.appendChild(dom);
+ // perform post-construction initialization tasks on shadow root
+ this.shadowRootReady(root, template);
+ // return the created shadow root
+ return root;
+ }
+ },
+
+ // utility function that stamps a <template> into light-dom
+ lightFromTemplate: function(template, refNode) {
+ if (template) {
+ // TODO(sorvell): mark this element as an eventController so that
+ // event listeners on bound nodes inside it will be called on it.
+ // Note, the expectation here is that events on all descendants
+ // should be handled by this element.
+ this.eventController = this;
+ // stamp template
+ // which includes parsing and applying MDV bindings before being
+ // inserted (to avoid {{}} in attribute values)
+ // e.g. to prevent <img src="images/{{icon}}"> from generating a 404.
+ var dom = this.instanceTemplate(template);
+ // append to shadow dom
+ if (refNode) {
+ this.insertBefore(dom, refNode);
+ } else {
+ this.appendChild(dom);
+ }
+ // perform post-construction initialization tasks on ahem, light root
+ this.shadowRootReady(this);
+ // return the created shadow root
+ return dom;
+ }
+ },
+
+ shadowRootReady: function(root) {
+ // locate nodes with id and store references to them in this.$ hash
+ this.marshalNodeReferences(root);
+ },
+
+ // locate nodes with id and store references to them in this.$ hash
+ marshalNodeReferences: function(root) {
+ // establish $ instance variable
+ var $ = this.$ = this.$ || {};
+ // populate $ from nodes with ID from the LOCAL tree
+ if (root) {
+ var n$ = root.querySelectorAll("[id]");
+ for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) {
+ $[n.id] = n;
+ };
+ }
+ },
+
+ /**
+ * Register a one-time callback when a child-list or sub-tree mutation
+ * occurs on node.
+ *
+ * For persistent callbacks, call onMutation from your listener.
+ *
+ * @method onMutation
+ * @param Node {Node} node Node to watch for mutations.
+ * @param Function {Function} listener Function to call on mutation. The function is invoked as `listener.call(this, observer, mutations);` where `observer` is the MutationObserver that triggered the notification, and `mutations` is the native mutation list.
+ */
+ onMutation: function(node, listener) {
+ var observer = new MutationObserver(function(mutations) {
+ listener.call(this, observer, mutations);
+ observer.disconnect();
+ }.bind(this));
+ observer.observe(node, {childList: true, subtree: true});
+ }
+ };
+
+ /**
+ * @class Polymer
+ */
+
+ /**
+ * Returns true if the object includes <a href="#polymer-base">polymer-base</a> in it's prototype chain.
+ *
+ * @method isBase
+ * @param Object {Object} object Object to test.
+ * @type Boolean
+ */
+ function isBase(object) {
+ return object.hasOwnProperty('PolymerBase')
+ }
+
+ // name a base constructor for dev tools
+
+ /**
+ * The Polymer base-class constructor.
+ *
+ * @property Base
+ * @type Function
+ */
+ function PolymerBase() {};
+ PolymerBase.prototype = base;
+ base.constructor = PolymerBase;
+
+ // exports
+
+ scope.Base = PolymerBase;
+ scope.isBase = isBase;
+ scope.api.instance.base = base;
+
+})(Polymer);
+
+(function(scope) {
+
+ // imports
+
+ var log = window.WebComponents ? WebComponents.flags.log : {};
+ var hasShadowDOMPolyfill = window.ShadowDOMPolyfill;
+
+ // magic words
+
+ var STYLE_SCOPE_ATTRIBUTE = 'element';
+ var STYLE_CONTROLLER_SCOPE = 'controller';
+
+ var styles = {
+ STYLE_SCOPE_ATTRIBUTE: STYLE_SCOPE_ATTRIBUTE,
+ /**
+ * Installs external stylesheets and <style> elements with the attribute
+ * polymer-scope='controller' into the scope of element. This is intended
+ * to be a called during custom element construction.
+ */
+ installControllerStyles: function() {
+ // apply controller styles, but only if they are not yet applied
+ var scope = this.findStyleScope();
+ if (scope && !this.scopeHasNamedStyle(scope, this.localName)) {
+ // allow inherited controller styles
+ var proto = getPrototypeOf(this), cssText = '';
+ while (proto && proto.element) {
+ cssText += proto.element.cssTextForScope(STYLE_CONTROLLER_SCOPE);
+ proto = getPrototypeOf(proto);
+ }
+ if (cssText) {
+ this.installScopeCssText(cssText, scope);
+ }
+ }
+ },
+ installScopeStyle: function(style, name, scope) {
+ var scope = scope || this.findStyleScope(), name = name || '';
+ if (scope && !this.scopeHasNamedStyle(scope, this.localName + name)) {
+ var cssText = '';
+ if (style instanceof Array) {
+ for (var i=0, l=style.length, s; (i<l) && (s=style[i]); i++) {
+ cssText += s.textContent + '\n\n';
+ }
+ } else {
+ cssText = style.textContent;
+ }
+ this.installScopeCssText(cssText, scope, name);
+ }
+ },
+ installScopeCssText: function(cssText, scope, name) {
+ scope = scope || this.findStyleScope();
+ name = name || '';
+ if (!scope) {
+ return;
+ }
+ if (hasShadowDOMPolyfill) {
+ cssText = shimCssText(cssText, scope.host);
+ }
+ var style = this.element.cssTextToScopeStyle(cssText,
+ STYLE_CONTROLLER_SCOPE);
+ Polymer.applyStyleToScope(style, scope);
+ // cache that this style has been applied
+ this.styleCacheForScope(scope)[this.localName + name] = true;
+ },
+ findStyleScope: function(node) {
+ // find the shadow root that contains this element
+ var n = node || this;
+ while (n.parentNode) {
+ n = n.parentNode;
+ }
+ return n;
+ },
+ scopeHasNamedStyle: function(scope, name) {
+ var cache = this.styleCacheForScope(scope);
+ return cache[name];
+ },
+ styleCacheForScope: function(scope) {
+ if (hasShadowDOMPolyfill) {
+ var scopeName = scope.host ? scope.host.localName : scope.localName;
+ return polyfillScopeStyleCache[scopeName] || (polyfillScopeStyleCache[scopeName] = {});
+ } else {
+ return scope._scopeStyles = (scope._scopeStyles || {});
+ }
+ }
+ };
+
+ var polyfillScopeStyleCache = {};
+
+ // NOTE: use raw prototype traversal so that we ensure correct traversal
+ // on platforms where the protoype chain is simulated via __proto__ (IE10)
+ function getPrototypeOf(prototype) {
+ return prototype.__proto__;
+ }
+
+ function shimCssText(cssText, host) {
+ var name = '', is = false;
+ if (host) {
+ name = host.localName;
+ is = host.hasAttribute('is');
+ }
+ var selector = WebComponents.ShadowCSS.makeScopeSelector(name, is);
+ return WebComponents.ShadowCSS.shimCssText(cssText, selector);
+ }
+
+ // exports
+
+ scope.api.instance.styles = styles;
+
+})(Polymer);
+
+(function(scope) {
+
+ // imports
+
+ var extend = scope.extend;
+ var api = scope.api;
+
+ // imperative implementation: Polymer()
+
+ // specify an 'own' prototype for tag `name`
+ function element(name, prototype) {
+ if (typeof name !== 'string') {
+ var script = prototype || document._currentScript;
+ prototype = name;
+ name = script && script.parentNode && script.parentNode.getAttribute ?
+ script.parentNode.getAttribute('name') : '';
+ if (!name) {
+ throw 'Element name could not be inferred.';
+ }
+ }
+ if (getRegisteredPrototype(name)) {
+ throw 'Already registered (Polymer) prototype for element ' + name;
+ }
+ // cache the prototype
+ registerPrototype(name, prototype);
+ // notify the registrar waiting for 'name', if any
+ notifyPrototype(name);
+ }
+
+ // async prototype source
+
+ function waitingForPrototype(name, client) {
+ waitPrototype[name] = client;
+ }
+
+ var waitPrototype = {};
+
+ function notifyPrototype(name) {
+ if (waitPrototype[name]) {
+ waitPrototype[name].registerWhenReady();
+ delete waitPrototype[name];
+ }
+ }
+
+ // utility and bookkeeping
+
+ // maps tag names to prototypes, as registered with
+ // Polymer. Prototypes associated with a tag name
+ // using document.registerElement are available from
+ // HTMLElement.getPrototypeForTag().
+ // If an element was fully registered by Polymer, then
+ // Polymer.getRegisteredPrototype(name) ===
+ // HTMLElement.getPrototypeForTag(name)
+
+ var prototypesByName = {};
+
+ function registerPrototype(name, prototype) {
+ return prototypesByName[name] = prototype || {};
+ }
+
+ function getRegisteredPrototype(name) {
+ return prototypesByName[name];
+ }
+
+ function instanceOfType(element, type) {
+ if (typeof type !== 'string') {
+ return false;
+ }
+ var proto = HTMLElement.getPrototypeForTag(type);
+ var ctor = proto && proto.constructor;
+ if (!ctor) {
+ return false;
+ }
+ if (CustomElements.instanceof) {
+ return CustomElements.instanceof(element, ctor);
+ }
+ return element instanceof ctor;
+ }
+
+ // exports
+
+ scope.getRegisteredPrototype = getRegisteredPrototype;
+ scope.waitingForPrototype = waitingForPrototype;
+ scope.instanceOfType = instanceOfType;
+
+ // namespace shenanigans so we can expose our scope on the registration
+ // function
+
+ // make window.Polymer reference `element()`
+
+ window.Polymer = element;
+
+ // TODO(sjmiles): find a way to do this that is less terrible
+ // copy window.Polymer properties onto `element()`
+
+ extend(Polymer, scope);
+
+ // Under the HTMLImports polyfill, scripts in the main document
+ // do not block on imports; we want to allow calls to Polymer in the main
+ // document. WebComponents collects those calls until we can process them, which
+ // we do here.
+
+ if (WebComponents.consumeDeclarations) {
+ WebComponents.consumeDeclarations(function(declarations) {
+ if (declarations) {
+ for (var i=0, l=declarations.length, d; (i<l) && (d=declarations[i]); i++) {
+ element.apply(null, d);
+ }
+ }
+ });
+ }
+
+})(Polymer);
+
+(function(scope) {
+
+/**
+ * @class polymer-base
+ */
+
+ /**
+ * Resolve a url path to be relative to a `base` url. If unspecified, `base`
+ * defaults to the element's ownerDocument url. Can be used to resolve
+ * paths from element's in templates loaded in HTMLImports to be relative
+ * to the document containing the element. Polymer automatically does this for
+ * url attributes in element templates; however, if a url, for
+ * example, contains a binding, then `resolvePath` can be used to ensure it is
+ * relative to the element document. For example, in an element's template,
+ *
+ * <a href="{{resolvePath(path)}}">Resolved</a>
+ *
+ * @method resolvePath
+ * @param {String} url Url path to resolve.
+ * @param {String} base Optional base url against which to resolve, defaults
+ * to the element's ownerDocument url.
+ * returns {String} resolved url.
+ */
+
+var path = {
+ resolveElementPaths: function(node) {
+ Polymer.urlResolver.resolveDom(node);
+ },
+ addResolvePathApi: function() {
+ // let assetpath attribute modify the resolve path
+ var assetPath = this.getAttribute('assetpath') || '';
+ var root = new URL(assetPath, this.ownerDocument.baseURI);
+ this.prototype.resolvePath = function(urlPath, base) {
+ var u = new URL(urlPath, base || root);
+ return u.href;
+ };
+ }
+};
+
+// exports
+scope.api.declaration.path = path;
+
+})(Polymer);
+
+(function(scope) {
+
+ // imports
+
+ var log = window.WebComponents ? WebComponents.flags.log : {};
+ var api = scope.api.instance.styles;
+ var STYLE_SCOPE_ATTRIBUTE = api.STYLE_SCOPE_ATTRIBUTE;
+
+ var hasShadowDOMPolyfill = window.ShadowDOMPolyfill;
+
+ // magic words
+
+ var STYLE_SELECTOR = 'style';
+ var STYLE_LOADABLE_MATCH = '@import';
+ var SHEET_SELECTOR = 'link[rel=stylesheet]';
+ var STYLE_GLOBAL_SCOPE = 'global';
+ var SCOPE_ATTR = 'polymer-scope';
+
+ var styles = {
+ // returns true if resources are loading
+ loadStyles: function(callback) {
+ var template = this.fetchTemplate();
+ var content = template && this.templateContent();
+ if (content) {
+ this.convertSheetsToStyles(content);
+ var styles = this.findLoadableStyles(content);
+ if (styles.length) {
+ var templateUrl = template.ownerDocument.baseURI;
+ return Polymer.styleResolver.loadStyles(styles, templateUrl, callback);
+ }
+ }
+ if (callback) {
+ callback();
+ }
+ },
+ convertSheetsToStyles: function(root) {
+ var s$ = root.querySelectorAll(SHEET_SELECTOR);
+ for (var i=0, l=s$.length, s, c; (i<l) && (s=s$[i]); i++) {
+ c = createStyleElement(importRuleForSheet(s, this.ownerDocument.baseURI),
+ this.ownerDocument);
+ this.copySheetAttributes(c, s);
+ s.parentNode.replaceChild(c, s);
+ }
+ },
+ copySheetAttributes: function(style, link) {
+ for (var i=0, a$=link.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++) {
+ if (a.name !== 'rel' && a.name !== 'href') {
+ style.setAttribute(a.name, a.value);
+ }
+ }
+ },
+ findLoadableStyles: function(root) {
+ var loadables = [];
+ if (root) {
+ var s$ = root.querySelectorAll(STYLE_SELECTOR);
+ for (var i=0, l=s$.length, s; (i<l) && (s=s$[i]); i++) {
+ if (s.textContent.match(STYLE_LOADABLE_MATCH)) {
+ loadables.push(s);
+ }
+ }
+ }
+ return loadables;
+ },
+ /**
+ * Install external stylesheets loaded in <polymer-element> elements into the
+ * element's template.
+ * @param elementElement The <element> element to style.
+ */
+ installSheets: function() {
+ this.cacheSheets();
+ this.cacheStyles();
+ this.installLocalSheets();
+ this.installGlobalStyles();
+ },
+ /**
+ * Remove all sheets from element and store for later use.
+ */
+ cacheSheets: function() {
+ this.sheets = this.findNodes(SHEET_SELECTOR);
+ this.sheets.forEach(function(s) {
+ if (s.parentNode) {
+ s.parentNode.removeChild(s);
+ }
+ });
+ },
+ cacheStyles: function() {
+ this.styles = this.findNodes(STYLE_SELECTOR + '[' + SCOPE_ATTR + ']');
+ this.styles.forEach(function(s) {
+ if (s.parentNode) {
+ s.parentNode.removeChild(s);
+ }
+ });
+ },
+ /**
+ * Takes external stylesheets loaded in an <element> element and moves
+ * their content into a <style> element inside the <element>'s template.
+ * The sheet is then removed from the <element>. This is done only so
+ * that if the element is loaded in the main document, the sheet does
+ * not become active.
+ * Note, ignores sheets with the attribute 'polymer-scope'.
+ * @param elementElement The <element> element to style.
+ */
+ installLocalSheets: function () {
+ var sheets = this.sheets.filter(function(s) {
+ return !s.hasAttribute(SCOPE_ATTR);
+ });
+ var content = this.templateContent();
+ if (content) {
+ var cssText = '';
+ sheets.forEach(function(sheet) {
+ cssText += cssTextFromSheet(sheet) + '\n';
+ });
+ if (cssText) {
+ var style = createStyleElement(cssText, this.ownerDocument);
+ content.insertBefore(style, content.firstChild);
+ }
+ }
+ },
+ findNodes: function(selector, matcher) {
+ var nodes = this.querySelectorAll(selector).array();
+ var content = this.templateContent();
+ if (content) {
+ var templateNodes = content.querySelectorAll(selector).array();
+ nodes = nodes.concat(templateNodes);
+ }
+ return matcher ? nodes.filter(matcher) : nodes;
+ },
+ /**
+ * Promotes external stylesheets and <style> elements with the attribute
+ * polymer-scope='global' into global scope.
+ * This is particularly useful for defining @keyframe rules which
+ * currently do not function in scoped or shadow style elements.
+ * (See wkb.ug/72462)
+ * @param elementElement The <element> element to style.
+ */
+ // TODO(sorvell): remove when wkb.ug/72462 is addressed.
+ installGlobalStyles: function() {
+ var style = this.styleForScope(STYLE_GLOBAL_SCOPE);
+ applyStyleToScope(style, document.head);
+ },
+ cssTextForScope: function(scopeDescriptor) {
+ var cssText = '';
+ // handle stylesheets
+ var selector = '[' + SCOPE_ATTR + '=' + scopeDescriptor + ']';
+ var matcher = function(s) {
+ return matchesSelector(s, selector);
+ };
+ var sheets = this.sheets.filter(matcher);
+ sheets.forEach(function(sheet) {
+ cssText += cssTextFromSheet(sheet) + '\n\n';
+ });
+ // handle cached style elements
+ var styles = this.styles.filter(matcher);
+ styles.forEach(function(style) {
+ cssText += style.textContent + '\n\n';
+ });
+ return cssText;
+ },
+ styleForScope: function(scopeDescriptor) {
+ var cssText = this.cssTextForScope(scopeDescriptor);
+ return this.cssTextToScopeStyle(cssText, scopeDescriptor);
+ },
+ cssTextToScopeStyle: function(cssText, scopeDescriptor) {
+ if (cssText) {
+ var style = createStyleElement(cssText);
+ style.setAttribute(STYLE_SCOPE_ATTRIBUTE, this.getAttribute('name') +
+ '-' + scopeDescriptor);
+ return style;
+ }
+ }
+ };
+
+ function importRuleForSheet(sheet, baseUrl) {
+ var href = new URL(sheet.getAttribute('href'), baseUrl).href;
+ return '@import \'' + href + '\';';
+ }
+
+ function applyStyleToScope(style, scope) {
+ if (style) {
+ if (scope === document) {
+ scope = document.head;
+ }
+ if (hasShadowDOMPolyfill) {
+ scope = document.head;
+ }
+ // TODO(sorvell): necessary for IE
+ // see https://connect.microsoft.com/IE/feedback/details/790212/
+ // cloning-a-style-element-and-adding-to-document-produces
+ // -unexpected-result#details
+ // var clone = style.cloneNode(true);
+ var clone = createStyleElement(style.textContent);
+ var attr = style.getAttribute(STYLE_SCOPE_ATTRIBUTE);
+ if (attr) {
+ clone.setAttribute(STYLE_SCOPE_ATTRIBUTE, attr);
+ }
+ // TODO(sorvell): probably too brittle; try to figure out
+ // where to put the element.
+ var refNode = scope.firstElementChild;
+ if (scope === document.head) {
+ var selector = 'style[' + STYLE_SCOPE_ATTRIBUTE + ']';
+ var s$ = document.head.querySelectorAll(selector);
+ if (s$.length) {
+ refNode = s$[s$.length-1].nextElementSibling;
+ }
+ }
+ scope.insertBefore(clone, refNode);
+ }
+ }
+
+ function createStyleElement(cssText, scope) {
+ scope = scope || document;
+ scope = scope.createElement ? scope : scope.ownerDocument;
+ var style = scope.createElement('style');
+ style.textContent = cssText;
+ return style;
+ }
+
+ function cssTextFromSheet(sheet) {
+ return (sheet && sheet.__resource) || '';
+ }
+
+ function matchesSelector(node, inSelector) {
+ if (matches) {
+ return matches.call(node, inSelector);
+ }
+ }
+ var p = HTMLElement.prototype;
+ var matches = p.matches || p.matchesSelector || p.webkitMatchesSelector
+ || p.mozMatchesSelector;
+
+ // exports
+
+ scope.api.declaration.styles = styles;
+ scope.applyStyleToScope = applyStyleToScope;
+
+})(Polymer);
+
+(function(scope) {
+
+ // imports
+
+ var log = window.WebComponents ? WebComponents.flags.log : {};
+ var api = scope.api.instance.events;
+ var EVENT_PREFIX = api.EVENT_PREFIX;
+
+ var mixedCaseEventTypes = {};
+ [
+ 'webkitAnimationStart',
+ 'webkitAnimationEnd',
+ 'webkitTransitionEnd',
+ 'DOMFocusOut',
+ 'DOMFocusIn',
+ 'DOMMouseScroll'
+ ].forEach(function(e) {
+ mixedCaseEventTypes[e.toLowerCase()] = e;
+ });
+
+ // polymer-element declarative api: events feature
+ var events = {
+ parseHostEvents: function() {
+ // our delegates map
+ var delegates = this.prototype.eventDelegates;
+ // extract data from attributes into delegates
+ this.addAttributeDelegates(delegates);
+ },
+ addAttributeDelegates: function(delegates) {
+ // for each attribute
+ for (var i=0, a; a=this.attributes[i]; i++) {
+ // does it have magic marker identifying it as an event delegate?
+ if (this.hasEventPrefix(a.name)) {
+ // if so, add the info to delegates
+ delegates[this.removeEventPrefix(a.name)] = a.value.replace('{{', '')
+ .replace('}}', '').trim();
+ }
+ }
+ },
+ // starts with 'on-'
+ hasEventPrefix: function (n) {
+ return n && (n[0] === 'o') && (n[1] === 'n') && (n[2] === '-');
+ },
+ removeEventPrefix: function(n) {
+ return n.slice(prefixLength);
+ },
+ findController: function(node) {
+ while (node.parentNode) {
+ if (node.eventController) {
+ return node.eventController;
+ }
+ node = node.parentNode;
+ }
+ return node.host;
+ },
+ getEventHandler: function(controller, target, method) {
+ var events = this;
+ return function(e) {
+ if (!controller || !controller.PolymerBase) {
+ controller = events.findController(target);
+ }
+
+ var args = [e, e.detail, e.currentTarget];
+ controller.dispatchMethod(controller, method, args);
+ };
+ },
+ prepareEventBinding: function(pathString, name, node) {
+ if (!this.hasEventPrefix(name))
+ return;
+
+ var eventType = this.removeEventPrefix(name);
+ eventType = mixedCaseEventTypes[eventType] || eventType;
+
+ var events = this;
+
+ return function(model, node, oneTime) {
+ var handler = events.getEventHandler(undefined, node, pathString);
+ PolymerGestures.addEventListener(node, eventType, handler);
+
+ if (oneTime)
+ return;
+
+ // TODO(rafaelw): This is really pointless work. Aside from the cost
+ // of these allocations, NodeBind is going to setAttribute back to its
+ // current value. Fixing this would mean changing the TemplateBinding
+ // binding delegate API.
+ function bindingValue() {
+ return '{{ ' + pathString + ' }}';
+ }
+
+ return {
+ open: bindingValue,
+ discardChanges: bindingValue,
+ close: function() {
+ PolymerGestures.removeEventListener(node, eventType, handler);
+ }
+ };
+ };
+ }
+ };
+
+ var prefixLength = EVENT_PREFIX.length;
+
+ // exports
+ scope.api.declaration.events = events;
+
+})(Polymer);
+
+(function(scope) {
+
+ // element api
+
+ var observationBlacklist = ['attribute'];
+
+ var properties = {
+ inferObservers: function(prototype) {
+ // called before prototype.observe is chained to inherited object
+ var observe = prototype.observe, property;
+ for (var n in prototype) {
+ if (n.slice(-7) === 'Changed') {
+ property = n.slice(0, -7);
+ if (this.canObserveProperty(property)) {
+ if (!observe) {
+ observe = (prototype.observe = {});
+ }
+ observe[property] = observe[property] || n;
+ }
+ }
+ }
+ },
+ canObserveProperty: function(property) {
+ return (observationBlacklist.indexOf(property) < 0);
+ },
+ explodeObservers: function(prototype) {
+ // called before prototype.observe is chained to inherited object
+ var o = prototype.observe;
+ if (o) {
+ var exploded = {};
+ for (var n in o) {
+ var names = n.split(' ');
+ for (var i=0, ni; ni=names[i]; i++) {
+ exploded[ni] = o[n];
+ }
+ }
+ prototype.observe = exploded;
+ }
+ },
+ optimizePropertyMaps: function(prototype) {
+ if (prototype.observe) {
+ // construct name list
+ var a = prototype._observeNames = [];
+ for (var n in prototype.observe) {
+ var names = n.split(' ');
+ for (var i=0, ni; ni=names[i]; i++) {
+ a.push(ni);
+ }
+ }
+ }
+ if (prototype.publish) {
+ // construct name list
+ var a = prototype._publishNames = [];
+ for (var n in prototype.publish) {
+ a.push(n);
+ }
+ }
+ if (prototype.computed) {
+ // construct name list
+ var a = prototype._computedNames = [];
+ for (var n in prototype.computed) {
+ a.push(n);
+ }
+ }
+ },
+ publishProperties: function(prototype, base) {
+ // if we have any properties to publish
+ var publish = prototype.publish;
+ if (publish) {
+ // transcribe `publish` entries onto own prototype
+ this.requireProperties(publish, prototype, base);
+ // warn and remove accessor names that are broken on some browsers
+ this.filterInvalidAccessorNames(publish);
+ // construct map of lower-cased property names
+ prototype._publishLC = this.lowerCaseMap(publish);
+ }
+ var computed = prototype.computed;
+ if (computed) {
+ // warn and remove accessor names that are broken on some browsers
+ this.filterInvalidAccessorNames(computed);
+ }
+ },
+ // Publishing/computing a property where the name might conflict with a
+ // browser property is not currently supported to help users of Polymer
+ // avoid browser bugs:
+ //
+ // https://code.google.com/p/chromium/issues/detail?id=43394
+ // https://bugs.webkit.org/show_bug.cgi?id=49739
+ //
+ // We can lift this restriction when those bugs are fixed.
+ filterInvalidAccessorNames: function(propertyNames) {
+ for (var name in propertyNames) {
+ // Check if the name is in our blacklist.
+ if (this.propertyNameBlacklist[name]) {
+ console.warn('Cannot define property "' + name + '" for element "' +
+ this.name + '" because it has the same name as an HTMLElement ' +
+ 'property, and not all browsers support overriding that. ' +
+ 'Consider giving it a different name.');
+ // Remove the invalid accessor from the list.
+ delete propertyNames[name];
+ }
+ }
+ },
+ //
+ // `name: value` entries in the `publish` object may need to generate
+ // matching properties on the prototype.
+ //
+ // Values that are objects may have a `reflect` property, which
+ // signals that the value describes property control metadata.
+ // In metadata objects, the prototype default value (if any)
+ // is encoded in the `value` property.
+ //
+ // publish: {
+ // foo: 5,
+ // bar: {value: true, reflect: true},
+ // zot: {}
+ // }
+ //
+ // `reflect` metadata property controls whether changes to the property
+ // are reflected back to the attribute (default false).
+ //
+ // A value is stored on the prototype unless it's === `undefined`,
+ // in which case the base chain is checked for a value.
+ // If the basal value is also undefined, `null` is stored on the prototype.
+ //
+ // The reflection data is stored on another prototype object, `reflect`
+ // which also can be specified directly.
+ //
+ // reflect: {
+ // foo: true
+ // }
+ //
+ requireProperties: function(propertyInfos, prototype, base) {
+ // per-prototype storage for reflected properties
+ prototype.reflect = prototype.reflect || {};
+ // ensure a prototype value for each property
+ // and update the property's reflect to attribute status
+ for (var n in propertyInfos) {
+ var value = propertyInfos[n];
+ // value has metadata if it has a `reflect` property
+ if (value && value.reflect !== undefined) {
+ prototype.reflect[n] = Boolean(value.reflect);
+ value = value.value;
+ }
+ // only set a value if one is specified
+ if (value !== undefined) {
+ prototype[n] = value;
+ }
+ }
+ },
+ lowerCaseMap: function(properties) {
+ var map = {};
+ for (var n in properties) {
+ map[n.toLowerCase()] = n;
+ }
+ return map;
+ },
+ createPropertyAccessor: function(name, ignoreWrites) {
+ var proto = this.prototype;
+
+ var privateName = name + '_';
+ var privateObservable = name + 'Observable_';
+ proto[privateName] = proto[name];
+
+ Object.defineProperty(proto, name, {
+ get: function() {
+ var observable = this[privateObservable];
+ if (observable)
+ observable.deliver();
+
+ return this[privateName];
+ },
+ set: function(value) {
+ if (ignoreWrites) {
+ return this[privateName];
+ }
+
+ var observable = this[privateObservable];
+ if (observable) {
+ observable.setValue(value);
+ return;
+ }
+
+ var oldValue = this[privateName];
+ this[privateName] = value;
+ this.emitPropertyChangeRecord(name, value, oldValue);
+
+ return value;
+ },
+ configurable: true
+ });
+ },
+ createPropertyAccessors: function(prototype) {
+ var n$ = prototype._computedNames;
+ if (n$ && n$.length) {
+ for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) {
+ this.createPropertyAccessor(n, true);
+ }
+ }
+ var n$ = prototype._publishNames;
+ if (n$ && n$.length) {
+ for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) {
+ // If the property is computed and published, the accessor is created
+ // above.
+ if (!prototype.computed || !prototype.computed[n]) {
+ this.createPropertyAccessor(n);
+ }
+ }
+ }
+ },
+ // This list contains some property names that people commonly want to use,
+ // but won't work because of Chrome/Safari bugs. It isn't an exhaustive
+ // list. In particular it doesn't contain any property names found on
+ // subtypes of HTMLElement (e.g. name, value). Rather it attempts to catch
+ // some common cases.
+ propertyNameBlacklist: {
+ children: 1,
+ 'class': 1,
+ id: 1,
+ hidden: 1,
+ style: 1,
+ title: 1,
+ }
+ };
+
+ // exports
+
+ scope.api.declaration.properties = properties;
+
+})(Polymer);
+
+(function(scope) {
+
+ // magic words
+
+ var ATTRIBUTES_ATTRIBUTE = 'attributes';
+ var ATTRIBUTES_REGEX = /\s|,/;
+
+ // attributes api
+
+ var attributes = {
+
+ inheritAttributesObjects: function(prototype) {
+ // chain our lower-cased publish map to the inherited version
+ this.inheritObject(prototype, 'publishLC');
+ // chain our instance attributes map to the inherited version
+ this.inheritObject(prototype, '_instanceAttributes');
+ },
+
+ publishAttributes: function(prototype, base) {
+ // merge names from 'attributes' attribute into the 'publish' object
+ var attributes = this.getAttribute(ATTRIBUTES_ATTRIBUTE);
+ if (attributes) {
+ // create a `publish` object if needed.
+ // the `publish` object is only relevant to this prototype, the
+ // publishing logic in `declaration/properties.js` is responsible for
+ // managing property values on the prototype chain.
+ // TODO(sjmiles): the `publish` object is later chained to it's
+ // ancestor object, presumably this is only for
+ // reflection or other non-library uses.
+ var publish = prototype.publish || (prototype.publish = {});
+ // names='a b c' or names='a,b,c'
+ var names = attributes.split(ATTRIBUTES_REGEX);
+ // record each name for publishing
+ for (var i=0, l=names.length, n; i<l; i++) {
+ // remove excess ws
+ n = names[i].trim();
+ // looks weird, but causes n to exist on `publish` if it does not;
+ // a more careful test would need expensive `in` operator
+ if (n && publish[n] === undefined) {
+ publish[n] = undefined;
+ }
+ }
+ }
+ },
+
+ // record clonable attributes from <element>
+ accumulateInstanceAttributes: function() {
+ // inherit instance attributes
+ var clonable = this.prototype._instanceAttributes;
+ // merge attributes from element
+ var a$ = this.attributes;
+ for (var i=0, l=a$.length, a; (i<l) && (a=a$[i]); i++) {
+ if (this.isInstanceAttribute(a.name)) {
+ clonable[a.name] = a.value;
+ }
+ }
+ },
+
+ isInstanceAttribute: function(name) {
+ return !this.blackList[name] && name.slice(0,3) !== 'on-';
+ },
+
+ // do not clone these attributes onto instances
+ blackList: {
+ name: 1,
+ 'extends': 1,
+ constructor: 1,
+ noscript: 1,
+ assetpath: 1,
+ 'cache-csstext': 1
+ }
+
+ };
+
+ // add ATTRIBUTES_ATTRIBUTE to the blacklist
+ attributes.blackList[ATTRIBUTES_ATTRIBUTE] = 1;
+
+ // exports
+
+ scope.api.declaration.attributes = attributes;
+
+})(Polymer);
+
+(function(scope) {
+
+ // imports
+ var events = scope.api.declaration.events;
+
+ var syntax = new PolymerExpressions();
+ var prepareBinding = syntax.prepareBinding;
+
+ // Polymer takes a first crack at the binding to see if it's a declarative
+ // event handler.
+ syntax.prepareBinding = function(pathString, name, node) {
+ return events.prepareEventBinding(pathString, name, node) ||
+ prepareBinding.call(syntax, pathString, name, node);
+ };
+
+ // declaration api supporting mdv
+ var mdv = {
+ syntax: syntax,
+ fetchTemplate: function() {
+ return this.querySelector('template');
+ },
+ templateContent: function() {
+ var template = this.fetchTemplate();
+ return template && template.content;
+ },
+ installBindingDelegate: function(template) {
+ if (template) {
+ template.bindingDelegate = this.syntax;
+ }
+ }
+ };
+
+ // exports
+ scope.api.declaration.mdv = mdv;
+
+})(Polymer);
+
+(function(scope) {
+
+ // imports
+
+ var api = scope.api;
+ var isBase = scope.isBase;
+ var extend = scope.extend;
+
+ var hasShadowDOMPolyfill = window.ShadowDOMPolyfill;
+
+ // prototype api
+
+ var prototype = {
+
+ register: function(name, extendeeName) {
+ // build prototype combining extendee, Polymer base, and named api
+ this.buildPrototype(name, extendeeName);
+ // register our custom element with the platform
+ this.registerPrototype(name, extendeeName);
+ // reference constructor in a global named by 'constructor' attribute
+ this.publishConstructor();
+ },
+
+ buildPrototype: function(name, extendeeName) {
+ // get our custom prototype (before chaining)
+ var extension = scope.getRegisteredPrototype(name);
+ // get basal prototype
+ var base = this.generateBasePrototype(extendeeName);
+ // implement declarative features
+ this.desugarBeforeChaining(extension, base);
+ // join prototypes
+ this.prototype = this.chainPrototypes(extension, base);
+ // more declarative features
+ this.desugarAfterChaining(name, extendeeName);
+ },
+
+ desugarBeforeChaining: function(prototype, base) {
+ // back reference declaration element
+ // TODO(sjmiles): replace `element` with `elementElement` or `declaration`
+ prototype.element = this;
+ // transcribe `attributes` declarations onto own prototype's `publish`
+ this.publishAttributes(prototype, base);
+ // `publish` properties to the prototype and to attribute watch
+ this.publishProperties(prototype, base);
+ // infer observers for `observe` list based on method names
+ this.inferObservers(prototype);
+ // desugar compound observer syntax, e.g. 'a b c'
+ this.explodeObservers(prototype);
+ },
+
+ chainPrototypes: function(prototype, base) {
+ // chain various meta-data objects to inherited versions
+ this.inheritMetaData(prototype, base);
+ // chain custom api to inherited
+ var chained = this.chainObject(prototype, base);
+ // x-platform fixup
+ ensurePrototypeTraversal(chained);
+ return chained;
+ },
+
+ inheritMetaData: function(prototype, base) {
+ // chain observe object to inherited
+ this.inheritObject('observe', prototype, base);
+ // chain publish object to inherited
+ this.inheritObject('publish', prototype, base);
+ // chain reflect object to inherited
+ this.inheritObject('reflect', prototype, base);
+ // chain our lower-cased publish map to the inherited version
+ this.inheritObject('_publishLC', prototype, base);
+ // chain our instance attributes map to the inherited version
+ this.inheritObject('_instanceAttributes', prototype, base);
+ // chain our event delegates map to the inherited version
+ this.inheritObject('eventDelegates', prototype, base);
+ },
+
+ // implement various declarative features
+ desugarAfterChaining: function(name, extendee) {
+ // build side-chained lists to optimize iterations
+ this.optimizePropertyMaps(this.prototype);
+ this.createPropertyAccessors(this.prototype);
+ // install mdv delegate on template
+ this.installBindingDelegate(this.fetchTemplate());
+ // install external stylesheets as if they are inline
+ this.installSheets();
+ // adjust any paths in dom from imports
+ this.resolveElementPaths(this);
+ // compile list of attributes to copy to instances
+ this.accumulateInstanceAttributes();
+ // parse on-* delegates declared on `this` element
+ this.parseHostEvents();
+ //
+ // install a helper method this.resolvePath to aid in
+ // setting resource urls. e.g.
+ // this.$.image.src = this.resolvePath('images/foo.png')
+ this.addResolvePathApi();
+ // under ShadowDOMPolyfill, transforms to approximate missing CSS features
+ if (hasShadowDOMPolyfill) {
+ WebComponents.ShadowCSS.shimStyling(this.templateContent(), name,
+ extendee);
+ }
+ // allow custom element access to the declarative context
+ if (this.prototype.registerCallback) {
+ this.prototype.registerCallback(this);
+ }
+ },
+
+ // if a named constructor is requested in element, map a reference
+ // to the constructor to the given symbol
+ publishConstructor: function() {
+ var symbol = this.getAttribute('constructor');
+ if (symbol) {
+ window[symbol] = this.ctor;
+ }
+ },
+
+ // build prototype combining extendee, Polymer base, and named api
+ generateBasePrototype: function(extnds) {
+ var prototype = this.findBasePrototype(extnds);
+ if (!prototype) {
+ // create a prototype based on tag-name extension
+ var prototype = HTMLElement.getPrototypeForTag(extnds);
+ // insert base api in inheritance chain (if needed)
+ prototype = this.ensureBaseApi(prototype);
+ // memoize this base
+ memoizedBases[extnds] = prototype;
+ }
+ return prototype;
+ },
+
+ findBasePrototype: function(name) {
+ return memoizedBases[name];
+ },
+
+ // install Polymer instance api into prototype chain, as needed
+ ensureBaseApi: function(prototype) {
+ if (prototype.PolymerBase) {
+ return prototype;
+ }
+ var extended = Object.create(prototype);
+ // we need a unique copy of base api for each base prototype
+ // therefore we 'extend' here instead of simply chaining
+ api.publish(api.instance, extended);
+ // TODO(sjmiles): sharing methods across prototype chains is
+ // not supported by 'super' implementation which optimizes
+ // by memoizing prototype relationships.
+ // Probably we should have a version of 'extend' that is
+ // share-aware: it could study the text of each function,
+ // look for usage of 'super', and wrap those functions in
+ // closures.
+ // As of now, there is only one problematic method, so
+ // we just patch it manually.
+ // To avoid re-entrancy problems, the special super method
+ // installed is called `mixinSuper` and the mixin method
+ // must use this method instead of the default `super`.
+ this.mixinMethod(extended, prototype, api.instance.mdv, 'bind');
+ // return buffed-up prototype
+ return extended;
+ },
+
+ mixinMethod: function(extended, prototype, api, name) {
+ var $super = function(args) {
+ return prototype[name].apply(this, args);
+ };
+ extended[name] = function() {
+ this.mixinSuper = $super;
+ return api[name].apply(this, arguments);
+ }
+ },
+
+ // ensure prototype[name] inherits from a prototype.prototype[name]
+ inheritObject: function(name, prototype, base) {
+ // require an object
+ var source = prototype[name] || {};
+ // chain inherited properties onto a new object
+ prototype[name] = this.chainObject(source, base[name]);
+ },
+
+ // register 'prototype' to custom element 'name', store constructor
+ registerPrototype: function(name, extendee) {
+ var info = {
+ prototype: this.prototype
+ }
+ // native element must be specified in extends
+ var typeExtension = this.findTypeExtension(extendee);
+ if (typeExtension) {
+ info.extends = typeExtension;
+ }
+ // register the prototype with HTMLElement for name lookup
+ HTMLElement.register(name, this.prototype);
+ // register the custom type
+ this.ctor = document.registerElement(name, info);
+ },
+
+ findTypeExtension: function(name) {
+ if (name && name.indexOf('-') < 0) {
+ return name;
+ } else {
+ var p = this.findBasePrototype(name);
+ if (p.element) {
+ return this.findTypeExtension(p.element.extends);
+ }
+ }
+ }
+
+ };
+
+ // memoize base prototypes
+ var memoizedBases = {};
+
+ // implementation of 'chainObject' depends on support for __proto__
+ if (Object.__proto__) {
+ prototype.chainObject = function(object, inherited) {
+ if (object && inherited && object !== inherited) {
+ object.__proto__ = inherited;
+ }
+ return object;
+ }
+ } else {
+ prototype.chainObject = function(object, inherited) {
+ if (object && inherited && object !== inherited) {
+ var chained = Object.create(inherited);
+ object = extend(chained, object);
+ }
+ return object;
+ }
+ }
+
+ // On platforms that do not support __proto__ (versions of IE), the prototype
+ // chain of a custom element is simulated via installation of __proto__.
+ // Although custom elements manages this, we install it here so it's
+ // available during desugaring.
+ function ensurePrototypeTraversal(prototype) {
+ if (!Object.__proto__) {
+ var ancestor = Object.getPrototypeOf(prototype);
+ prototype.__proto__ = ancestor;
+ if (isBase(ancestor)) {
+ ancestor.__proto__ = Object.getPrototypeOf(ancestor);
+ }
+ }
+ }
+
+ // exports
+
+ api.declaration.prototype = prototype;
+
+})(Polymer);
+
+(function(scope) {
+
+ /*
+
+ Elements are added to a registration queue so that they register in
+ the proper order at the appropriate time. We do this for a few reasons:
+
+ * to enable elements to load resources (like stylesheets)
+ asynchronously. We need to do this until the platform provides an efficient
+ alternative. One issue is that remote @import stylesheets are
+ re-fetched whenever stamped into a shadowRoot.
+
+ * to ensure elements loaded 'at the same time' (e.g. via some set of
+ imports) are registered as a batch. This allows elements to be enured from
+ upgrade ordering as long as they query the dom tree 1 task after
+ upgrade (aka domReady). This is a performance tradeoff. On the one hand,
+ elements that could register while imports are loading are prevented from
+ doing so. On the other, grouping upgrades into a single task means less
+ incremental work (for example style recalcs), Also, we can ensure the
+ document is in a known state at the single quantum of time when
+ elements upgrade.
+
+ */
+ var queue = {
+
+ // tell the queue to wait for an element to be ready
+ wait: function(element) {
+ if (!element.__queue) {
+ element.__queue = {};
+ elements.push(element);
+ }
+ },
+
+ // enqueue an element to the next spot in the queue.
+ enqueue: function(element, check, go) {
+ var shouldAdd = element.__queue && !element.__queue.check;
+ if (shouldAdd) {
+ queueForElement(element).push(element);
+ element.__queue.check = check;
+ element.__queue.go = go;
+ }
+ return (this.indexOf(element) !== 0);
+ },
+
+ indexOf: function(element) {
+ var i = queueForElement(element).indexOf(element);
+ if (i >= 0 && document.contains(element)) {
+ i += (HTMLImports.useNative || HTMLImports.ready) ?
+ importQueue.length : 1e9;
+ }
+ return i;
+ },
+
+ // tell the queue an element is ready to be registered
+ go: function(element) {
+ var readied = this.remove(element);
+ if (readied) {
+ element.__queue.flushable = true;
+ this.addToFlushQueue(readied);
+ this.check();
+ }
+ },
+
+ remove: function(element) {
+ var i = this.indexOf(element);
+ if (i !== 0) {
+ //console.warn('queue order wrong', i);
+ return;
+ }
+ return queueForElement(element).shift();
+ },
+
+ check: function() {
+ // next
+ var element = this.nextElement();
+ if (element) {
+ element.__queue.check.call(element);
+ }
+ if (this.canReady()) {
+ this.ready();
+ return true;
+ }
+ },
+
+ nextElement: function() {
+ return nextQueued();
+ },
+
+ canReady: function() {
+ return !this.waitToReady && this.isEmpty();
+ },
+
+ isEmpty: function() {
+ for (var i=0, l=elements.length, e; (i<l) &&
+ (e=elements[i]); i++) {
+ if (e.__queue && !e.__queue.flushable) {
+ return;
+ }
+ }
+ return true;
+ },
+
+ addToFlushQueue: function(element) {
+ flushQueue.push(element);
+ },
+
+ flush: function() {
+ // prevent re-entrance
+ if (this.flushing) {
+ return;
+ }
+ this.flushing = true;
+ var element;
+ while (flushQueue.length) {
+ element = flushQueue.shift();
+ element.__queue.go.call(element);
+ element.__queue = null;
+ }
+ this.flushing = false;
+ },
+
+ ready: function() {
+ // TODO(sorvell): As an optimization, turn off CE polyfill upgrading
+ // while registering. This way we avoid having to upgrade each document
+ // piecemeal per registration and can instead register all elements
+ // and upgrade once in a batch. Without this optimization, upgrade time
+ // degrades significantly when SD polyfill is used. This is mainly because
+ // querying the document tree for elements is slow under the SD polyfill.
+ var polyfillWasReady = CustomElements.ready;
+ CustomElements.ready = false;
+ this.flush();
+ if (!CustomElements.useNative) {
+ CustomElements.upgradeDocumentTree(document);
+ }
+ CustomElements.ready = polyfillWasReady;
+ Polymer.flush();
+ requestAnimationFrame(this.flushReadyCallbacks);
+ },
+
+ addReadyCallback: function(callback) {
+ if (callback) {
+ readyCallbacks.push(callback);
+ }
+ },
+
+ flushReadyCallbacks: function() {
+ if (readyCallbacks) {
+ var fn;
+ while (readyCallbacks.length) {
+ fn = readyCallbacks.shift();
+ fn();
+ }
+ }
+ },
+
+ /**
+ Returns a list of elements that have had polymer-elements created but
+ are not yet ready to register. The list is an array of element definitions.
+ */
+ waitingFor: function() {
+ var e$ = [];
+ for (var i=0, l=elements.length, e; (i<l) &&
+ (e=elements[i]); i++) {
+ if (e.__queue && !e.__queue.flushable) {
+ e$.push(e);
+ }
+ }
+ return e$;
+ },
+
+ waitToReady: true
+
+ };
+
+ var elements = [];
+ var flushQueue = [];
+ var importQueue = [];
+ var mainQueue = [];
+ var readyCallbacks = [];
+
+ function queueForElement(element) {
+ return document.contains(element) ? mainQueue : importQueue;
+ }
+
+ function nextQueued() {
+ return importQueue.length ? importQueue[0] : mainQueue[0];
+ }
+
+ function whenReady(callback) {
+ queue.waitToReady = true;
+ Polymer.endOfMicrotask(function() {
+ HTMLImports.whenReady(function() {
+ queue.addReadyCallback(callback);
+ queue.waitToReady = false;
+ queue.check();
+ });
+ });
+ }
+
+ /**
+ Forces polymer to register any pending elements. Can be used to abort
+ waiting for elements that are partially defined.
+ @param timeout {Integer} Optional timeout in milliseconds
+ */
+ function forceReady(timeout) {
+ if (timeout === undefined) {
+ queue.ready();
+ return;
+ }
+ var handle = setTimeout(function() {
+ queue.ready();
+ }, timeout);
+ Polymer.whenReady(function() {
+ clearTimeout(handle);
+ });
+ }
+
+ // exports
+ scope.elements = elements;
+ scope.waitingFor = queue.waitingFor.bind(queue);
+ scope.forceReady = forceReady;
+ scope.queue = queue;
+ scope.whenReady = scope.whenPolymerReady = whenReady;
+})(Polymer);
+
+(function(scope) {
+
+ // imports
+
+ var extend = scope.extend;
+ var api = scope.api;
+ var queue = scope.queue;
+ var whenReady = scope.whenReady;
+ var getRegisteredPrototype = scope.getRegisteredPrototype;
+ var waitingForPrototype = scope.waitingForPrototype;
+
+ // declarative implementation: <polymer-element>
+
+ var prototype = extend(Object.create(HTMLElement.prototype), {
+
+ createdCallback: function() {
+ if (this.getAttribute('name')) {
+ this.init();
+ }
+ },
+
+ init: function() {
+ // fetch declared values
+ this.name = this.getAttribute('name');
+ this.extends = this.getAttribute('extends');
+ queue.wait(this);
+ // initiate any async resource fetches
+ this.loadResources();
+ // register when all constraints are met
+ this.registerWhenReady();
+ },
+
+ // TODO(sorvell): we currently queue in the order the prototypes are
+ // registered, but we should queue in the order that polymer-elements
+ // are registered. We are currently blocked from doing this based on
+ // crbug.com/395686.
+ registerWhenReady: function() {
+ if (this.registered
+ || this.waitingForPrototype(this.name)
+ || this.waitingForQueue()
+ || this.waitingForResources()) {
+ return;
+ }
+ queue.go(this);
+ },
+
+ _register: function() {
+ //console.log('registering', this.name);
+ // warn if extending from a custom element not registered via Polymer
+ if (isCustomTag(this.extends) && !isRegistered(this.extends)) {
+ console.warn('%s is attempting to extend %s, an unregistered element ' +
+ 'or one that was not registered with Polymer.', this.name,
+ this.extends);
+ }
+ this.register(this.name, this.extends);
+ this.registered = true;
+ },
+
+ waitingForPrototype: function(name) {
+ if (!getRegisteredPrototype(name)) {
+ // then wait for a prototype
+ waitingForPrototype(name, this);
+ // emulate script if user is not supplying one
+ this.handleNoScript(name);
+ // prototype not ready yet
+ return true;
+ }
+ },
+
+ handleNoScript: function(name) {
+ // if explicitly marked as 'noscript'
+ if (this.hasAttribute('noscript') && !this.noscript) {
+ this.noscript = true;
+ // imperative element registration
+ Polymer(name);
+ }
+ },
+
+ waitingForResources: function() {
+ return this._needsResources;
+ },
+
+ // NOTE: Elements must be queued in proper order for inheritance/composition
+ // dependency resolution. Previously this was enforced for inheritance,
+ // and by rule for composition. It's now entirely by rule.
+ waitingForQueue: function() {
+ return queue.enqueue(this, this.registerWhenReady, this._register);
+ },
+
+ loadResources: function() {
+ this._needsResources = true;
+ this.loadStyles(function() {
+ this._needsResources = false;
+ this.registerWhenReady();
+ }.bind(this));
+ }
+
+ });
+
+ // semi-pluggable APIs
+
+ // TODO(sjmiles): should be fully pluggable (aka decoupled, currently
+ // the various plugins are allowed to depend on each other directly)
+ api.publish(api.declaration, prototype);
+
+ // utility and bookkeeping
+
+ function isRegistered(name) {
+ return Boolean(HTMLElement.getPrototypeForTag(name));
+ }
+
+ function isCustomTag(name) {
+ return (name && name.indexOf('-') >= 0);
+ }
+
+ // boot tasks
+
+ whenReady(function() {
+ document.body.removeAttribute('unresolved');
+ document.dispatchEvent(
+ new CustomEvent('polymer-ready', {bubbles: true})
+ );
+ });
+
+ // register polymer-element with document
+
+ document.registerElement('polymer-element', {prototype: prototype});
+
+})(Polymer);
+
+(function(scope) {
+
+/**
+ * @class Polymer
+ */
+
+var whenReady = scope.whenReady;
+
+/**
+ * Loads the set of HTMLImports contained in `node`. Notifies when all
+ * the imports have loaded by calling the `callback` function argument.
+ * This method can be used to lazily load imports. For example, given a
+ * template:
+ *
+ * <template>
+ * <link rel="import" href="my-import1.html">
+ * <link rel="import" href="my-import2.html">
+ * </template>
+ *
+ * Polymer.importElements(template.content, function() {
+ * console.log('imports lazily loaded');
+ * });
+ *
+ * @method importElements
+ * @param {Node} node Node containing the HTMLImports to load.
+ * @param {Function} callback Callback called when all imports have loaded.
+ */
+function importElements(node, callback) {
+ if (node) {
+ document.head.appendChild(node);
+ whenReady(callback);
+ } else if (callback) {
+ callback();
+ }
+}
+
+/**
+ * Loads an HTMLImport for each url specified in the `urls` array.
+ * Notifies when all the imports have loaded by calling the `callback`
+ * function argument. This method can be used to lazily load imports.
+ * For example,
+ *
+ * Polymer.import(['my-import1.html', 'my-import2.html'], function() {
+ * console.log('imports lazily loaded');
+ * });
+ *
+ * @method import
+ * @param {Array} urls Array of urls to load as HTMLImports.
+ * @param {Function} callback Callback called when all imports have loaded.
+ */
+function _import(urls, callback) {
+ if (urls && urls.length) {
+ var frag = document.createDocumentFragment();
+ for (var i=0, l=urls.length, url, link; (i<l) && (url=urls[i]); i++) {
+ link = document.createElement('link');
+ link.rel = 'import';
+ link.href = url;
+ frag.appendChild(link);
+ }
+ importElements(frag, callback);
+ } else if (callback) {
+ callback();
+ }
+}
+
+// exports
+scope.import = _import;
+scope.importElements = importElements;
+
+})(Polymer);
+
+/**
+ * The `auto-binding` element extends the template element. It provides a quick
+ * and easy way to do data binding without the need to setup a model.
+ * The `auto-binding` element itself serves as the model and controller for the
+ * elements it contains. Both data and event handlers can be bound.
+ *
+ * The `auto-binding` element acts just like a template that is bound to
+ * a model. It stamps its content in the dom adjacent to itself. When the
+ * content is stamped, the `template-bound` event is fired.
+ *
+ * Example:
+ *
+ * <template is="auto-binding">
+ * <div>Say something: <input value="{{value}}"></div>
+ * <div>You said: {{value}}</div>
+ * <button on-tap="{{buttonTap}}">Tap me!</button>
+ * </template>
+ * <script>
+ * var template = document.querySelector('template');
+ * template.value = 'something';
+ * template.buttonTap = function() {
+ * console.log('tap!');
+ * };
+ * </script>
+ *
+ * @module Polymer
+ * @status stable
+*/
+
+(function() {
+
+ var element = document.createElement('polymer-element');
+ element.setAttribute('name', 'auto-binding');
+ element.setAttribute('extends', 'template');
+ element.init();
+
+ Polymer('auto-binding', {
+
+ createdCallback: function() {
+ this.syntax = this.bindingDelegate = this.makeSyntax();
+ // delay stamping until polymer-ready so that auto-binding is not
+ // required to load last.
+ Polymer.whenPolymerReady(function() {
+ this.model = this;
+ this.setAttribute('bind', '');
+ // we don't bother with an explicit signal here, we could ust a MO
+ // if necessary
+ this.async(function() {
+ // note: this will marshall *all* the elements in the parentNode
+ // rather than just stamped ones. We'd need to use createInstance
+ // to fix this or something else fancier.
+ this.marshalNodeReferences(this.parentNode);
+ // template stamping is asynchronous so stamping isn't complete
+ // by polymer-ready; fire an event so users can use stamped elements
+ this.fire('template-bound');
+ });
+ }.bind(this));
+ },
+
+ makeSyntax: function() {
+ var events = Object.create(Polymer.api.declaration.events);
+ var self = this;
+ events.findController = function() { return self.model; };
+
+ var syntax = new PolymerExpressions();
+ var prepareBinding = syntax.prepareBinding;
+ syntax.prepareBinding = function(pathString, name, node) {
+ return events.prepareEventBinding(pathString, name, node) ||
+ prepareBinding.call(syntax, pathString, name, node);
+ };
+ return syntax;
+ }
+
+ });
+
+})();

Powered by Google App Engine
This is Rietveld 408576698