Index: pkg/polymer/lib/src/js/polymer/polymer.concat.js |
diff --git a/pkg/polymer/lib/src/js/polymer/polymer.concat.js b/pkg/polymer/lib/src/js/polymer/polymer.concat.js |
index 65d22b94d5be066be10de506f2182b3ef454c6a4..0d97f534bc59706470301d6e993dc36f9d6a5dee 100644 |
--- a/pkg/polymer/lib/src/js/polymer/polymer.concat.js |
+++ b/pkg/polymer/lib/src/js/polymer/polymer.concat.js |
@@ -591,6 +591,9 @@ window.PolymerGestures = {}; |
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 |
@@ -605,6 +608,7 @@ window.PolymerGestures = {}; |
*/ |
var dispatcher = { |
pointermap: new scope.PointerMap(), |
+ requiredGestures: new scope.PointerMap(), |
eventMap: Object.create(null), |
// Scope objects for native events. |
// This exists for ease of testing. |
@@ -665,6 +669,7 @@ window.PolymerGestures = {}; |
}, |
// EVENTS |
down: function(inEvent) { |
+ this.requiredGestures.set(inEvent.pointerId, currentGestures); |
this.fireEvent('down', inEvent); |
}, |
move: function(inEvent) { |
@@ -674,10 +679,12 @@ window.PolymerGestures = {}; |
}, |
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); |
}, |
// LISTENER LOGIC |
eventHandler: function(inEvent) { |
@@ -685,12 +692,34 @@ window.PolymerGestures = {}; |
// platform events. This can happen when two elements in different scopes |
// are set up to create pointer events, which is relevant to Shadow DOM. |
- // TODO(dfreedm): make this check more granular, allow for minimal event generation |
- // e.g inEvent._handledByPG['tap'] and inEvent._handledByPG['track'], etc |
+ 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 = {}; |
+ } |
+ // map gesture names to ordered set of recognizers |
+ var gesturesWanted = inEvent.currentTarget._pgEvents; |
+ if (gesturesWanted) { |
+ 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; |
+ } |
+ } |
+ } |
+ } |
+ |
if (inEvent._handledByPG) { |
return; |
} |
- var type = inEvent.type; |
var fn = this.eventMap && this.eventMap[type]; |
if (fn) { |
fn(inEvent); |
@@ -782,13 +811,17 @@ window.PolymerGestures = {}; |
}, |
gestureTrigger: function() { |
// process the gesture queue |
- for (var i = 0, e; i < this.gestureQueue.length; i++) { |
+ for (var i = 0, e, rg; i < this.gestureQueue.length; i++) { |
e = this.gestureQueue[i]; |
+ rg = e._requiredGestures; |
for (var j = 0, g, fn; j < this.gestures.length; j++) { |
- g = this.gestures[j]; |
- fn = g[e.type]; |
- if (g.enabled && fn) { |
- fn.call(g, e); |
+ // 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); |
+ } |
} |
} |
} |
@@ -799,6 +832,7 @@ window.PolymerGestures = {}; |
if (!this.gestureQueue.length) { |
requestAnimationFrame(this.boundGestureTrigger); |
} |
+ ev._requiredGestures = this.requiredGestures.get(ev.pointerId); |
this.gestureQueue.push(ev); |
} |
}; |
@@ -820,12 +854,6 @@ window.PolymerGestures = {}; |
var dep = dispatcher.dependencyMap[g]; |
if (dep) { |
var recognizer = dispatcher.gestures[dep.index]; |
- if (dep.listeners === 0) { |
- if (recognizer) { |
- recognizer.enabled = true; |
- } |
- } |
- dep.listeners++; |
if (!node._pgListeners) { |
dispatcher.register(node); |
node._pgListeners = 0; |
@@ -849,6 +877,10 @@ window.PolymerGestures = {}; |
actionNode.setAttribute('touch-action', touchAction); |
} |
} |
+ if (!node._pgEvents) { |
+ node._pgEvents = {}; |
+ } |
+ node._pgEvents[g] = (node._pgEvents[g] || 0) + 1; |
node._pgListeners++; |
} |
return Boolean(dep); |
@@ -883,21 +915,19 @@ window.PolymerGestures = {}; |
var g = gesture.toLowerCase(); |
var dep = dispatcher.dependencyMap[g]; |
if (dep) { |
- if (dep.listeners > 0) { |
- dep.listeners--; |
- } |
- if (dep.listeners === 0) { |
- var recognizer = dispatcher.gestures[dep.index]; |
- if (recognizer) { |
- recognizer.enabled = false; |
- } |
- } |
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); |
}; |
@@ -1365,9 +1395,6 @@ window.PolymerGestures = {}; |
'MSPointerCancel', |
], |
register: function(target) { |
- if (target !== document) { |
- return; |
- } |
dispatcher.listen(target, this.events); |
}, |
unregister: function(target) { |
@@ -1399,9 +1426,12 @@ window.PolymerGestures = {}; |
dispatcher.down(e); |
}, |
MSPointerMove: function(inEvent) { |
- var e = this.prepareEvent(inEvent); |
- e.target = pointermap.get(e.pointerId); |
- dispatcher.move(e); |
+ 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); |
@@ -1447,9 +1477,6 @@ window.PolymerGestures = {}; |
return e; |
}, |
register: function(target) { |
- if (target !== document) { |
- return; |
- } |
dispatcher.listen(target, this.events); |
}, |
unregister: function(target) { |
@@ -1465,9 +1492,12 @@ window.PolymerGestures = {}; |
dispatcher.down(e); |
}, |
pointermove: function(inEvent) { |
- var e = this.prepareEvent(inEvent); |
- e.target = pointermap.get(e.pointerId); |
- dispatcher.move(e); |
+ 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); |
@@ -3668,7 +3698,7 @@ window.PolymerGestures = {}; |
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt |
*/ |
Polymer = { |
- version: '0.3.5-5d00e4b' |
+ version: '0.4.0-d66a86e' |
}; |
/* |
@@ -3697,143 +3727,46 @@ if (typeof window.Polymer === 'function') { |
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt |
*/ |
-(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; |
- } |
- |
- // exports |
- |
- scope.extend = extend; |
- |
-})(Polymer); |
- |
-/* |
- * 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 |
+ /* |
+ On supported platforms, platform.js is not needed. To retain compatibility |
+ with the polyfills, we stub out minimal functionality. |
*/ |
+if (!window.Platform) { |
+ logFlags = window.logFlags || {}; |
-(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) |
+ Platform = { |
+ flush: function() {} |
}; |
- 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); |
- } |
+ |
+ CustomElements = { |
+ useNative: true, |
+ ready: true, |
+ takeRecords: function() {}, |
+ instanceof: function(obj, base) { |
+ return obj instanceof base; |
} |
}; |
- function job(job, callback, wait) { |
- if (job) { |
- job.stop(); |
- } else { |
- job = new Job(this); |
- } |
- job.go(callback, wait); |
- return job; |
- } |
- |
- // exports |
+ HTMLImports = { |
+ useNative: true |
+ }; |
- scope.job = job; |
-})(Polymer); |
- |
-/* |
- * 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(scope) { |
- |
- var registry = {}; |
+ addEventListener('HTMLImportsLoaded', function() { |
+ document.dispatchEvent( |
+ new CustomEvent('WebComponentsReady', {bubbles: true}) |
+ ); |
+ }); |
- 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)); |
+ // ShadowDOM |
+ ShadowDOMPolyfill = null; |
+ wrap = unwrap = function(n){ |
+ return n; |
}; |
- // 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); |
- }; |
- |
- // TODO(sorvell): remove when we're sure imports does not need |
- // to load stylesheets |
- /* |
- HTMLImports.importer.preloadSelectors += |
- ', polymer-element link[rel=stylesheet]'; |
- */ |
-})(Polymer); |
+} |
/* |
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved. |
@@ -3844,108 +3777,4402 @@ if (typeof window.Polymer === 'function') { |
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt |
*/ |
- (function(scope) { |
- // super |
+(function(scope) { |
- // `arrayOfArgs` is an optional array of args like one might pass |
- // to `Function.apply` |
+var hasNative = ('import' in document.createElement('link')); |
+var useNative = hasNative; |
- // TODO(sjmiles): |
- // $super must be installed on an instance or prototype chain |
- // as `super`, and invoked via `this`, e.g. |
- // `this.super();` |
+isIE = /Trident/.test(navigator.userAgent); |
- // 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)); |
+// TODO(sorvell): SD polyfill intrusion |
+var hasShadowDOMPolyfill = Boolean(window.ShadowDOMPolyfill); |
+var wrap = function(node) { |
+ return hasShadowDOMPolyfill ? ShadowDOMPolyfill.wrapIfNeeded(node) : node; |
+}; |
+var mainDoc = wrap(document); |
+ |
+// 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. |
+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(mainDoc, '_currentScript', currentScriptDescriptor); |
+ |
+// call a callback when all HTMLImports in the document at call (or at least |
+// document ready) time 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 whenImportsReady(callback, doc) { |
+ doc = doc || mainDoc; |
+ // 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); |
} |
- // 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 || []); |
+ }; |
+ 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(); |
+ } |
+} |
- 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__; |
+// NOTE: test for native imports loading is based on explicitly watching |
+// all imports (see below). |
+function isImportLoaded(link) { |
+ return useNative ? link.__loaded : 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 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; |
+ function handleImports(nodes) { |
+ for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) { |
+ if (isImport(n)) { |
+ handleImport(n); |
} |
- 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); |
+ 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); |
} |
- // 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. |
+} |
+ |
+// 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. |
+whenImportsReady(function() { |
+ HTMLImports.ready = true; |
+ HTMLImports.readyTime = new Date().getTime(); |
+ mainDoc.dispatchEvent( |
+ new CustomEvent('HTMLImportsLoaded', {bubbles: true}) |
+ ); |
+}); |
+ |
+// exports |
+scope.useNative = useNative; |
+scope.isImportLoaded = isImportLoaded; |
+scope.whenReady = whenImportsReady; |
+scope.isIE = isIE; |
+ |
+// deprecated |
+scope.whenImportsReady = whenImportsReady; |
+ |
+})(window.HTMLImports); |
+/* |
+ * 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(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); |
+ |
+/* |
+ * 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(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); |
+ |
+// Copyright 2012 Google Inc. |
+// |
+// Licensed under the Apache License, Version 2.0 (the "License"); |
+// you may not use this file except in compliance with the License. |
+// You may obtain a copy of the License at |
+// |
+// http://www.apache.org/licenses/LICENSE-2.0 |
+// |
+// Unless required by applicable law or agreed to in writing, software |
+// distributed under the License is distributed on an "AS IS" BASIS, |
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+// See the License for the specific language governing permissions and |
+// limitations under the License. |
+ |
+(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 (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; |
+ } |
+ |
+ 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[0]); |
+ } |
+ }, |
+ |
+ 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(){ |
+ var eomObj = { pingPong: true }; |
+ var eomRunScheduled = false; |
+ |
+ Object.observe(eomObj, function() { |
+ runEOMTasks(); |
+ eomRunScheduled = false; |
+ }); |
+ |
+ return function(fn) { |
+ eomTasks.push(fn); |
+ if (!eomRunScheduled) { |
+ eomRunScheduled = true; |
+ eomObj.pingPong = !eomObj.pingPong; |
+ } |
+ }; |
+ })() : |
+ (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 = { |
+ object: undefined, |
+ objects: objects, |
+ open: function(obs, object) { |
+ if (!rootObj) { |
+ rootObj = object; |
+ rootObjProps = {}; |
+ } |
+ |
+ 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); |
+ } |
+ }; |
+ |
+ return record; |
+ } |
+ |
+ var lastObservedSet; |
+ |
+ function getObservedSet(observer, obj) { |
+ if (!lastObservedSet || lastObservedSet.object !== obj) { |
+ lastObservedSet = observedSetCache.pop() || newObservedSet(); |
+ lastObservedSet.object = 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; |
+ |
+ var hasDebugForceFullDelivery = hasObserve && hasEval && (function() { |
+ try { |
+ eval('%RunMicrotasks()'); |
+ return true; |
+ } catch (ex) { |
+ return false; |
+ } |
+ })(); |
+ |
+ global.Platform = global.Platform || {}; |
+ |
+ global.Platform.performMicrotaskCheckpoint = function() { |
+ if (runningMicrotaskCheckpoint) |
+ return; |
+ |
+ if (hasDebugForceFullDelivery) { |
+ eval('%RunMicrotasks()'); |
+ 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; |
+ } |
+ |
+ global.Observer = Observer; |
+ global.Observer.runEOM_ = runEOM; |
+ global.Observer.observerSentinel_ = observerSentinel; // for testing. |
+ global.Observer.hasObjectObserve = hasObserve; |
+ global.ArrayObserver = ArrayObserver; |
+ global.ArrayObserver.calculateSplices = function(current, previous) { |
+ return arraySplice.calculateSplices(current, previous); |
+ }; |
+ |
+ global.ArraySplice = ArraySplice; |
+ global.ObjectObserver = ObjectObserver; |
+ global.PathObserver = PathObserver; |
+ global.CompoundObserver = CompoundObserver; |
+ global.Path = Path; |
+ global.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); |
+ |
+/* |
+ * 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(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; |
+ |
+})(Platform); |
+ |
+ |
+/* |
+ * 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(scope) { |
+ |
+// 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); |
+ |
+// flush (with logging) |
+var flushing; |
+function flush() { |
+ if (!flushing) { |
+ flushing = true; |
+ scope.endOfMicrotask(function() { |
+ flushing = false; |
+ logFlags.data && console.group('Platform.flush()'); |
+ scope.performMicrotaskCheckpoint(); |
+ logFlags.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(); |
+ scope.flushPoll = setInterval(flush, FLUSH_POLL_INTERVAL); |
+ }); |
+} 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; |
+ |
+})(window.Platform); |
+ |
+ |
+/* |
+ * 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(scope) { |
+ |
+var urlResolver = { |
+ resolveDom: function(root, url) { |
+ url = url || root.ownerDocument.baseURI; |
+ 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, template.ownerDocument.baseURI); |
+ }, |
+ 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 || style.ownerDocument.baseURI; |
+ 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 || node.ownerDocument.baseURI; |
+ 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 = '{{.*}}'; |
+ |
+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 = new URL(document.baseURI); |
+ 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('..'); |
+ } |
+ return t.join('/') + targetUrl.search + targetUrl.hash; |
+} |
+ |
+// exports |
+scope.urlResolver = urlResolver; |
+ |
+})(Polymer); |
+ |
+/* |
+ * 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(scope) { |
+ var endOfMicrotask = Platform.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); |
+ |
+/* |
+ * 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(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); |
+ |
+/* |
+ * 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(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); |
+ |
+/* |
+ * 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(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); |
+ |
+/* |
+ * 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(scope) { |
+ |
+ 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); |
+ |
+/* |
+ * 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(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__; |
} |
@@ -4477,8 +8704,8 @@ if (typeof window.Polymer === 'function') { |
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(value, old) { |
- this.invokeMethod(callbackName, [old]); |
+ observer.open(function(splices) { |
+ this.invokeMethod(callbackName, [splices]); |
}, this); |
this.registerNamedObserver(name + '__array', observer); |
} |
@@ -4644,6 +8871,8 @@ if (typeof window.Polymer === 'function') { |
// element api supporting mdv |
var mdv = { |
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); |
@@ -5116,9 +9345,9 @@ if (typeof window.Polymer === 'function') { |
// specify an 'own' prototype for tag `name` |
function element(name, prototype) { |
- if (arguments.length === 1 && typeof arguments[0] !== 'string') { |
+ if (typeof name !== 'string') { |
+ var script = prototype || document._currentScript; |
prototype = name; |
- var script = document._currentScript; |
name = script && script.parentNode && script.parentNode.getAttribute ? |
script.parentNode.getAttribute('name') : ''; |
if (!name) { |
@@ -5191,11 +9420,14 @@ if (typeof window.Polymer === 'function') { |
// document. Platform collects those calls until we can process them, which |
// we do here. |
- var declarations = Platform.deliverDeclarations(); |
- if (declarations) { |
- for (var i=0, l=declarations.length, d; (i<l) && (d=declarations[i]); i++) { |
- element.apply(null, d); |
- } |
+ if (Platform.consumeDeclarations) { |
+ Platform.consumeDeclarations(function(declarations) {; |
+ if (declarations) { |
+ for (var i=0, l=declarations.length, d; (i<l) && (d=declarations[i]); i++) { |
+ element.apply(null, d); |
+ } |
+ } |
+ }); |
} |
})(Polymer); |
@@ -5213,7 +9445,7 @@ if (typeof window.Polymer === 'function') { |
var path = { |
resolveElementPaths: function(node) { |
- Platform.urlResolver.resolveDom(node); |
+ Polymer.urlResolver.resolveDom(node); |
}, |
addResolvePathApi: function() { |
// let assetpath attribute modify the resolve path |
@@ -5268,7 +9500,7 @@ scope.api.declaration.path = path; |
var styles = this.findLoadableStyles(content); |
if (styles.length) { |
var templateUrl = template.ownerDocument.baseURI; |
- return Platform.styleResolver.loadStyles(styles, templateUrl, callback); |
+ return Polymer.styleResolver.loadStyles(styles, templateUrl, callback); |
} |
} |
if (callback) { |
@@ -5901,7 +10133,7 @@ scope.api.declaration.path = path; |
}, |
templateContent: function() { |
var template = this.fetchTemplate(); |
- return template && Platform.templateContent(template); |
+ return template && template.content; |
}, |
installBindingDelegate: function(template) { |
if (template) { |
@@ -6301,17 +10533,19 @@ scope.api.declaration.path = path; |
}, |
ready: function() { |
- this.flush(); |
// 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. |
- if (CustomElements.ready === false) { |
+ var polyfillWasReady = CustomElements.ready; |
+ CustomElements.ready = false; |
+ this.flush(); |
+ if (!CustomElements.useNative) { |
CustomElements.upgradeDocumentTree(document); |
- CustomElements.ready = true; |
} |
+ CustomElements.ready = polyfillWasReady; |
Platform.flush(); |
requestAnimationFrame(this.flushReadyCallbacks); |
}, |
@@ -6350,15 +10584,8 @@ scope.api.declaration.path = path; |
return importQueue.length ? importQueue[0] : mainQueue[0]; |
} |
- var polymerReadied = false; |
- |
- document.addEventListener('WebComponentsReady', function() { |
- CustomElements.ready = false; |
- }); |
- |
- function whenPolymerReady(callback) { |
+ function whenReady(callback) { |
queue.waitToReady = true; |
- CustomElements.ready = false; |
HTMLImports.whenImportsReady(function() { |
queue.addReadyCallback(callback); |
queue.waitToReady = false; |
@@ -6369,50 +10596,7 @@ scope.api.declaration.path = path; |
// exports |
scope.elements = elements; |
scope.queue = queue; |
- scope.whenReady = scope.whenPolymerReady = whenPolymerReady; |
-})(Polymer); |
- |
-/* |
- * 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(scope) { |
- |
- var whenPolymerReady = scope.whenPolymerReady; |
- |
- function importElements(elementOrFragment, callback) { |
- if (elementOrFragment) { |
- document.head.appendChild(elementOrFragment); |
- whenPolymerReady(callback); |
- } else if (callback) { |
- callback(); |
- } |
- } |
- |
- function importUrls(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 = importUrls; |
- scope.importElements = importElements; |
- |
+ scope.whenReady = scope.whenPolymerReady = whenReady; |
})(Polymer); |
/* |
@@ -6431,7 +10615,7 @@ scope.api.declaration.path = path; |
var extend = scope.extend; |
var api = scope.api; |
var queue = scope.queue; |
- var whenPolymerReady = scope.whenPolymerReady; |
+ var whenReady = scope.whenReady; |
var getRegisteredPrototype = scope.getRegisteredPrototype; |
var waitingForPrototype = scope.waitingForPrototype; |
@@ -6541,7 +10725,7 @@ scope.api.declaration.path = path; |
// boot tasks |
- whenPolymerReady(function() { |
+ whenReady(function() { |
document.body.removeAttribute('unresolved'); |
document.dispatchEvent( |
new CustomEvent('polymer-ready', {bubbles: true}) |
@@ -6563,6 +10747,49 @@ scope.api.declaration.path = path; |
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt |
*/ |
+(function(scope) { |
+ |
+ var whenPolymerReady = scope.whenPolymerReady; |
+ |
+ function importElements(elementOrFragment, callback) { |
+ if (elementOrFragment) { |
+ document.head.appendChild(elementOrFragment); |
+ whenPolymerReady(callback); |
+ } else if (callback) { |
+ callback(); |
+ } |
+ } |
+ |
+ function importUrls(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 = importUrls; |
+ scope.importElements = importElements; |
+ |
+})(Polymer); |
+ |
+/* |
+ * 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 |
+ */ |
+ |
/** |
* 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. |