Index: polymer_0.5.0/bower_components/polymer/polymer.js |
diff --git a/polymer_0.5.0/bower_components/polymer/polymer.js b/polymer_0.5.0/bower_components/polymer/polymer.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2d3b692ccc3bff584b2fe9aa304077232728adb8 |
--- /dev/null |
+++ b/polymer_0.5.0/bower_components/polymer/polymer.js |
@@ -0,0 +1,11851 @@ |
+/** |
+ * @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 FIREFOX_LINUX = /Linux.*Firefox\//i; |
+ |
+ var HAS_BUTTONS = (function() { |
+ // firefox on linux returns spec-incorrect values for mouseup.buttons |
+ // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent.buttons#See_also |
+ // https://codereview.chromium.org/727593003/#msg16 |
+ if (FIREFOX_LINUX.test(navigator.userAgent)) { |
+ return false; |
+ } |
+ try { |
+ return new MouseEvent('test', {buttons: 1}).buttons === 1; |
+ } catch (e) { |
+ return false; |
+ } |
+ })(); |
+ |
+ // 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); |
+ }, this); |
+ } |
+ }, |
+ 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: [ |
+ 'pinchstart', |
+ 'pinch', |
+ 'pinchend', |
+ '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) |
+ }; |
+ |
+ this.firePinch('pinchstart', points.diameter, points); |
+ } |
+ }, |
+ up: function(inEvent) { |
+ var p = pointermap.get(inEvent.pointerId); |
+ var num = pointermap.pointers(); |
+ if (p) { |
+ if (num === 2) { |
+ // fire 'pinchend' before deleting pointer |
+ var points = this.calcChord(); |
+ this.firePinch('pinchend', points.diameter, points); |
+ } |
+ 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(type, diameter, points) { |
+ var zoom = diameter / this.reference.diameter; |
+ var e = eventFactory.makeGestureEvent(type, { |
+ 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('pinch', 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 painting. 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; |
+ } |
+ |
+ }); |
+ |
+})(); |