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

Unified Diff: pkg/web_components/lib/platform.concat.js

Issue 158083002: introduce web_components pkg for consolidated polyfills (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: pkg/web_components/lib/platform.concat.js
diff --git a/pkg/web_components/lib/platform.concat.js b/pkg/web_components/lib/platform.concat.js
new file mode 100644
index 0000000000000000000000000000000000000000..b1e80668ad486024b5432cae1dfe30600cd5bd3b
--- /dev/null
+++ b/pkg/web_components/lib/platform.concat.js
@@ -0,0 +1,16914 @@
+/*
+ * Copyright 2012 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+if (typeof WeakMap === 'undefined') {
+ (function() {
+ var defineProperty = Object.defineProperty;
+ var counter = Date.now() % 1e9;
+
+ var WeakMap = function() {
+ this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__');
+ };
+
+ WeakMap.prototype = {
+ set: function(key, value) {
+ var entry = key[this.name];
+ if (entry && entry[0] === key)
+ entry[1] = value;
+ else
+ defineProperty(key, this.name, {value: [key, value], writable: true});
+ },
+ get: function(key) {
+ var entry;
+ return (entry = key[this.name]) && entry[0] === key ?
+ entry[1] : undefined;
+ },
+ delete: function(key) {
+ this.set(key, undefined);
+ }
+ };
+
+ window.WeakMap = WeakMap;
+ })();
+}
+
+// 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 PROP_ADD_TYPE = 'add';
+ var PROP_UPDATE_TYPE = 'update';
+ var PROP_RECONFIGURE_TYPE = 'reconfigure';
+ var PROP_DELETE_TYPE = 'delete';
+ var ARRAY_SPLICE_TYPE = 'splice';
+
+ // 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 = {};
+ Object.observe(test, callback);
+ test.id = 1;
+ test.id = 2;
+ delete test.id;
+ Object.deliverChangeRecords(callback);
+ if (records.length !== 3)
+ return false;
+
+ // TODO(rafaelw): Remove this when new change record type names make it to
+ // chrome release.
+ if (records[0].type == 'new' &&
+ records[1].type == 'updated' &&
+ records[2].type == 'deleted') {
+ PROP_ADD_TYPE = 'new';
+ PROP_UPDATE_TYPE = 'updated';
+ PROP_RECONFIGURE_TYPE = 'reconfigured';
+ PROP_DELETE_TYPE = 'deleted';
+ } else if (records[0].type != 'add' ||
+ records[1].type != 'update' ||
+ records[2].type != 'delete') {
+ console.error('Unexpected change record names for Object.observe. ' +
+ 'Using dirty-checking instead');
+ return false;
+ }
+ Object.unobserve(test, callback);
+
+ test = [0];
+ Array.observe(test, callback);
+ test[1] = 1;
+ test.length = 0;
+ Object.deliverChangeRecords(callback);
+ if (records.length != 2)
+ return false;
+ if (records[0].type != ARRAY_SPLICE_TYPE ||
+ records[1].type != ARRAY_SPLICE_TYPE) {
+ return false;
+ }
+ Array.unobserve(test, callback);
+
+ return true;
+ }
+
+ var hasObserve = detectObjectObserve();
+
+ function detectEval() {
+ // don't test for eval if document has CSP securityPolicy object and we can see that
+ // eval is not supported. This avoids an error message in console even when the exception
+ // is caught
+ if (global.document &&
+ 'securityPolicy' in global.document &&
+ !global.document.securityPolicy.allowsEval) {
+ 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 isNaN(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 ident = identStart + '+' + identPart + '*';
+ var elementIndex = '(?:[0-9]|[1-9]+[0-9]+)';
+ var identOrElementIndex = '(?:' + ident + '|' + elementIndex + ')';
+ var path = '(?:' + identOrElementIndex + ')(?:\\s*\\.\\s*' + identOrElementIndex + ')*';
+ var pathRegExp = new RegExp('^' + path + '$');
+
+ function isPathValid(s) {
+ if (typeof s != 'string')
+ return false;
+ s = s.trim();
+
+ if (s == '')
+ return true;
+
+ if (s[0] == '.')
+ return false;
+
+ return pathRegExp.test(s);
+ }
+
+ var constructorIsPrivate = {};
+
+ function Path(s, privateToken) {
+ if (privateToken !== constructorIsPrivate)
+ throw Error('Use Path.get to retrieve path objects');
+
+ if (s.trim() == '')
+ return this;
+
+ if (isIndex(s)) {
+ this.push(s);
+ return this;
+ }
+
+ s.split(/\s*\.\s*/).filter(function(part) {
+ return part;
+ }).forEach(function(part) {
+ this.push(part);
+ }, this);
+
+ 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 = '';
+
+ if (typeof pathString !== 'string')
+ pathString = String(pathString);
+
+ var path = pathCache[pathString];
+ if (path)
+ return path;
+ if (!isPathValid(pathString))
+ return invalidPath;
+ var path = new Path(pathString, constructorIsPrivate);
+ pathCache[pathString] = path;
+ return path;
+ }
+
+ Path.get = getPath;
+
+ Path.prototype = createObject({
+ __proto__: [],
+ valid: true,
+
+ toString: function() {
+ return this.join('.');
+ },
+
+ 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 (!obj)
+ return;
+ observe(obj);
+ }
+ },
+
+ compiledGetValueFromFn: function() {
+ var accessors = this.map(function(ident) {
+ return isIndex(ident) ? '["' + ident + '"]' : '.' + ident;
+ });
+
+ var str = '';
+ var pathString = 'obj';
+ str += 'if (obj != null';
+ var i = 0;
+ for (; i < (this.length - 1); i++) {
+ var ident = this[i];
+ pathString += accessors[i];
+ str += ' &&\n ' + pathString + ' != null';
+ }
+ str += ')\n';
+
+ pathString += accessors[i];
+
+ 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 (global.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);
+ }
+ };
+ }
+
+ function getObservedObject(observer, object, arrayObserve) {
+ var dir = observedObjectCache.pop() || newObservedObject();
+ dir.open(observer);
+ dir.observe(object, arrayObserve);
+ return dir;
+ }
+
+ var emptyArray = [];
+ var observedSetCache = [];
+
+ function newObservedSet() {
+ var observers = [];
+ var observerCount = 0;
+ var objects = [];
+ var toRemove = emptyArray;
+ var resetNeeded = false;
+ var resetScheduled = false;
+
+ function observe(obj) {
+ if (!isObject(obj))
+ return;
+
+ var index = toRemove.indexOf(obj);
+ if (index >= 0) {
+ toRemove[index] = undefined;
+ objects.push(obj);
+ } else if (objects.indexOf(obj) < 0) {
+ objects.push(obj);
+ Object.observe(obj, callback);
+ }
+
+ observe(Object.getPrototypeOf(obj));
+ }
+
+ function reset() {
+ resetScheduled = false;
+ if (!resetNeeded)
+ return;
+
+ var objs = toRemove === emptyArray ? [] : toRemove;
+ toRemove = objects;
+ objects = objs;
+
+ var observer;
+ for (var id in observers) {
+ observer = observers[id];
+ if (!observer || observer.state_ != OPENED)
+ continue;
+
+ observer.iterateObjects_(observe);
+ }
+
+ for (var i = 0; i < toRemove.length; i++) {
+ var obj = toRemove[i];
+ if (obj)
+ Object.unobserve(obj, callback);
+ }
+
+ toRemove.length = 0;
+ }
+
+ function scheduleReset() {
+ if (resetScheduled)
+ return;
+
+ resetNeeded = true;
+ resetScheduled = true;
+ runEOM(reset);
+ }
+
+ function callback() {
+ var observer;
+
+ for (var id in observers) {
+ observer = observers[id];
+ if (!observer || observer.state_ != OPENED)
+ continue;
+
+ observer.check_();
+ }
+
+ scheduleReset();
+ }
+
+ var record = {
+ object: undefined,
+ objects: objects,
+ open: function(obs) {
+ observers[obs.id_] = obs;
+ observerCount++;
+ obs.iterateObjects_(observe);
+ },
+ close: function(obs) {
+ var anyLeft = false;
+
+ observers[obs.id_] = undefined;
+ observerCount--;
+
+ if (observerCount) {
+ scheduleReset();
+ return;
+ }
+ resetNeeded = false;
+
+ for (var i = 0; i < objects.length; i++) {
+ Object.unobserve(objects[i], callback);
+ Observer.unobservedCount++;
+ }
+
+ observers.length = 0;
+ objects.length = 0;
+ observedSetCache.push(this);
+ },
+ reset: scheduleReset
+ };
+
+ return record;
+ }
+
+ var lastObservedSet;
+
+ function getObservedSet(observer, obj) {
+ if (!lastObservedSet || lastObservedSet.object !== obj) {
+ lastObservedSet = observedSetCache.pop() || newObservedSet();
+ lastObservedSet.object = obj;
+ }
+ lastObservedSet.open(observer);
+ 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.state_ = OPENED;
+ this.connect_();
+ return this.value_;
+ },
+
+ close: function() {
+ if (this.state_ != OPENED)
+ return;
+
+ removeFromAll(this);
+ this.state_ = CLOSED;
+ this.disconnect_();
+ this.value_ = undefined;
+ this.callback_ = undefined;
+ this.target_ = undefined;
+ },
+
+ 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 = typeof Object.deliverAllChangeRecords == 'function';
+
+ global.Platform = global.Platform || {};
+
+ global.Platform.performMicrotaskCheckpoint = function() {
+ if (runningMicrotaskCheckpoint)
+ return;
+
+ if (hasDebugForceFullDelivery) {
+ Object.deliverAllChangeRecords();
+ 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 (global.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_ = path instanceof Path ? path : getPath(path);
+ this.directObserver_ = undefined;
+ }
+
+ PathObserver.prototype = createObject({
+ __proto__: Observer.prototype,
+
+ 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]);
+ return true;
+ },
+
+ setValue: function(newValue) {
+ if (this.path_)
+ this.path_.setValueFrom(this.object_, newValue);
+ }
+ });
+
+ function CompoundObserver() {
+ Observer.call(this);
+
+ this.value_ = [];
+ this.directObserver_ = undefined;
+ this.observed_ = [];
+ }
+
+ var observerSentinel = {};
+
+ CompoundObserver.prototype = createObject({
+ __proto__: Observer.prototype,
+
+ connect_: function() {
+ this.check_(undefined, true);
+
+ if (!hasObserve)
+ return;
+
+ 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 (this.directObserver_) {
+ if (needsDirectObserver) {
+ this.directObserver_.reset();
+ return;
+ }
+ this.directObserver_.close();
+ this.directObserver_ = undefined;
+ return;
+ }
+
+ if (needsDirectObserver)
+ this.directObserver_ = getObservedSet(this, object);
+ },
+
+ closeObservers_: 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;
+ },
+
+ disconnect_: function() {
+ this.value_ = undefined;
+
+ if (this.directObserver_) {
+ this.directObserver_.close(this);
+ this.directObserver_ = undefined;
+ }
+
+ this.closeObservers_();
+ },
+
+ addPath: function(object, path) {
+ if (this.state_ != UNOPENED && this.state_ != RESETTING)
+ throw Error('Cannot add paths once started.');
+
+ this.observed_.push(object, path instanceof Path ? path : getPath(path));
+ },
+
+ addObserver: function(observer) {
+ if (this.state_ != UNOPENED && this.state_ != RESETTING)
+ throw Error('Cannot add observers once started.');
+
+ observer.open(this.deliver, this);
+ this.observed_.push(observerSentinel, observer);
+ },
+
+ startReset: function() {
+ if (this.state_ != OPENED)
+ throw Error('Can only reset while open');
+
+ this.state_ = RESETTING;
+ this.closeObservers_();
+ },
+
+ 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 pathOrObserver = this.observed_[i+1];
+ var object = this.observed_[i];
+ var value = object === observerSentinel ?
+ pathOrObserver.discardChanges() :
+ pathOrObserver.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 = {};
+ expectedRecordTypes[PROP_ADD_TYPE] = true;
+ expectedRecordTypes[PROP_UPDATE_TYPE] = true;
+ expectedRecordTypes[PROP_DELETE_TYPE] = true;
+
+ function notifyFunction(object, name) {
+ if (typeof Object.observe !== 'function')
+ return;
+
+ var notifier = Object.getNotifier(object);
+ return function(type, oldValue) {
+ var changeRecord = {
+ object: object,
+ type: type,
+ name: name
+ };
+ if (arguments.length === 2)
+ changeRecord.oldValue = oldValue;
+ notifier.notify(changeRecord);
+ }
+ }
+
+ Observer.defineComputedProperty = function(target, name, observable) {
+ var notify = notifyFunction(target, name);
+ var value = observable.open(function(newValue, oldValue) {
+ value = newValue;
+ if (notify)
+ notify(PROP_UPDATE_TYPE, oldValue);
+ });
+
+ Object.defineProperty(target, name, {
+ get: function() {
+ observable.deliver();
+ return value;
+ },
+ set: function(newValue) {
+ observable.setValue(newValue);
+ return newValue;
+ },
+ configurable: true
+ });
+
+ return {
+ close: function() {
+ observable.close();
+ Object.defineProperty(target, name, {
+ value: value,
+ writable: true,
+ configurable: 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 == PROP_UPDATE_TYPE)
+ continue;
+
+ if (record.type == PROP_ADD_TYPE) {
+ 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 ARRAY_SPLICE_TYPE:
+ mergeSplice(splices, record.index, record.removed.slice(), record.addedCount);
+ break;
+ case PROP_ADD_TYPE:
+ case PROP_UPDATE_TYPE:
+ case PROP_DELETE_TYPE:
+ 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.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;
+
+ // TODO(rafaelw): Only needed for testing until new change record names
+ // make it to release.
+ global.Observer.changeRecordTypes = {
+ add: PROP_ADD_TYPE,
+ update: PROP_UPDATE_TYPE,
+ reconfigure: PROP_RECONFIGURE_TYPE,
+ 'delete': PROP_DELETE_TYPE,
+ splice: ARRAY_SPLICE_TYPE
+ };
+})(typeof global !== 'undefined' && global && typeof module !== 'undefined' && module ? global : this || window);
+
+// prepoulate window.Platform.flags for default controls
+window.Platform = window.Platform || {};
+// prepopulate window.logFlags if necessary
+window.logFlags = window.logFlags || {};
+// process flags
+(function(scope){
+ // import
+ var flags = scope.flags || {};
+ // populate flags from location
+ location.search.slice(1).split('&').forEach(function(o) {
+ o = o.split('=');
+ o[0] && (flags[o[0]] = o[1] || true);
+ });
+ var entryPoint = document.currentScript || document.querySelector('script[src*="platform.js"]');
+ if (entryPoint) {
+ var a = entryPoint.attributes;
+ for (var i = 0, n; i < a.length; i++) {
+ n = a[i];
+ if (n.name !== 'src') {
+ flags[n.name] = n.value || true;
+ }
+ }
+ }
+ if (flags.log) {
+ flags.log.split(',').forEach(function(f) {
+ window.logFlags[f] = true;
+ });
+ }
+ // If any of these flags match 'native', then force native ShadowDOM; any
+ // other truthy value, or failure to detect native
+ // ShadowDOM, results in polyfill
+ flags.shadow = (flags.shadow || flags.shadowdom || flags.polyfill);
+ if (flags.shadow === 'native') {
+ flags.shadow = false;
+ } else {
+ flags.shadow = flags.shadow || !HTMLElement.prototype.createShadowRoot;
+ }
+
+ // CustomElements polyfill flag
+ if (flags.register) {
+ window.CustomElements = window.CustomElements || {flags: {}};
+ window.CustomElements.flags.register = flags.register;
+ }
+
+ if (flags.imports) {
+ window.HTMLImports = window.HTMLImports || {flags: {}};
+ window.HTMLImports.flags.imports = flags.imports;
+ }
+
+ // export
+ scope.flags = flags;
+})(Platform);
+
+// select ShadowDOM impl
+if (Platform.flags.shadow) {
+
+// Copyright 2012 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+window.ShadowDOMPolyfill = {};
+
+(function(scope) {
+ 'use strict';
+
+ var constructorTable = new WeakMap();
+ var nativePrototypeTable = new WeakMap();
+ var wrappers = Object.create(null);
+
+ // Don't test for eval if document has CSP securityPolicy object and we can
+ // see that eval is not supported. This avoids an error message in console
+ // even when the exception is caught
+ var hasEval = !('securityPolicy' in document) ||
+ document.securityPolicy.allowsEval;
+ if (hasEval) {
+ try {
+ var f = new Function('', 'return true;');
+ hasEval = f();
+ } catch (ex) {
+ hasEval = false;
+ }
+ }
+
+ function assert(b) {
+ if (!b)
+ throw new Error('Assertion failed');
+ };
+
+ var defineProperty = Object.defineProperty;
+ var getOwnPropertyNames = Object.getOwnPropertyNames;
+ var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
+
+ function mixin(to, from) {
+ getOwnPropertyNames(from).forEach(function(name) {
+ defineProperty(to, name, getOwnPropertyDescriptor(from, name));
+ });
+ return to;
+ };
+
+ function mixinStatics(to, from) {
+ getOwnPropertyNames(from).forEach(function(name) {
+ switch (name) {
+ case 'arguments':
+ case 'caller':
+ case 'length':
+ case 'name':
+ case 'prototype':
+ case 'toString':
+ return;
+ }
+ defineProperty(to, name, getOwnPropertyDescriptor(from, name));
+ });
+ return to;
+ };
+
+ function oneOf(object, propertyNames) {
+ for (var i = 0; i < propertyNames.length; i++) {
+ if (propertyNames[i] in object)
+ return propertyNames[i];
+ }
+ }
+
+ // Mozilla's old DOM bindings are bretty busted:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=855844
+ // Make sure they are create before we start modifying things.
+ getOwnPropertyNames(window);
+
+ function getWrapperConstructor(node) {
+ var nativePrototype = node.__proto__ || Object.getPrototypeOf(node);
+ var wrapperConstructor = constructorTable.get(nativePrototype);
+ if (wrapperConstructor)
+ return wrapperConstructor;
+
+ var parentWrapperConstructor = getWrapperConstructor(nativePrototype);
+
+ var GeneratedWrapper = createWrapperConstructor(parentWrapperConstructor);
+ registerInternal(nativePrototype, GeneratedWrapper, node);
+
+ return GeneratedWrapper;
+ }
+
+ function addForwardingProperties(nativePrototype, wrapperPrototype) {
+ installProperty(nativePrototype, wrapperPrototype, true);
+ }
+
+ function registerInstanceProperties(wrapperPrototype, instanceObject) {
+ installProperty(instanceObject, wrapperPrototype, false);
+ }
+
+ var isFirefox = /Firefox/.test(navigator.userAgent);
+
+ // This is used as a fallback when getting the descriptor fails in
+ // installProperty.
+ var dummyDescriptor = {
+ get: function() {},
+ set: function(v) {},
+ configurable: true,
+ enumerable: true
+ };
+
+ function isEventHandlerName(name) {
+ return /^on[a-z]+$/.test(name);
+ }
+
+ function isIdentifierName(name) {
+ return /^\w[a-zA-Z_0-9]*$/.test(name);
+ }
+
+ function getGetter(name) {
+ return hasEval && isIdentifierName(name) ?
+ new Function('return this.impl.' + name) :
+ function() { return this.impl[name]; };
+ }
+
+ function getSetter(name) {
+ return hasEval && isIdentifierName(name) ?
+ new Function('v', 'this.impl.' + name + ' = v') :
+ function(v) { this.impl[name] = v; };
+ }
+
+ function getMethod(name) {
+ return hasEval && isIdentifierName(name) ?
+ new Function('return this.impl.' + name +
+ '.apply(this.impl, arguments)') :
+ function() { return this.impl[name].apply(this.impl, arguments); };
+ }
+
+ function getDescriptor(source, name) {
+ try {
+ return Object.getOwnPropertyDescriptor(source, name);
+ } catch (ex) {
+ // JSC and V8 both use data properties instead of accessors which can
+ // cause getting the property desciptor to throw an exception.
+ // https://bugs.webkit.org/show_bug.cgi?id=49739
+ return dummyDescriptor;
+ }
+ }
+
+ function installProperty(source, target, allowMethod, opt_blacklist) {
+ var names = getOwnPropertyNames(source);
+ for (var i = 0; i < names.length; i++) {
+ var name = names[i];
+ if (name === 'polymerBlackList_')
+ continue;
+
+ if (name in target)
+ continue;
+
+ if (source.polymerBlackList_ && source.polymerBlackList_[name])
+ continue;
+
+ if (isFirefox) {
+ // Tickle Firefox's old bindings.
+ source.__lookupGetter__(name);
+ }
+ var descriptor = getDescriptor(source, name);
+ var getter, setter;
+ if (allowMethod && typeof descriptor.value === 'function') {
+ target[name] = getMethod(name);
+ continue;
+ }
+
+ var isEvent = isEventHandlerName(name);
+ if (isEvent)
+ getter = scope.getEventHandlerGetter(name);
+ else
+ getter = getGetter(name);
+
+ if (descriptor.writable || descriptor.set) {
+ if (isEvent)
+ setter = scope.getEventHandlerSetter(name);
+ else
+ setter = getSetter(name);
+ }
+
+ defineProperty(target, name, {
+ get: getter,
+ set: setter,
+ configurable: descriptor.configurable,
+ enumerable: descriptor.enumerable
+ });
+ }
+ }
+
+ /**
+ * @param {Function} nativeConstructor
+ * @param {Function} wrapperConstructor
+ * @param {Object=} opt_instance If present, this is used to extract
+ * properties from an instance object.
+ */
+ function register(nativeConstructor, wrapperConstructor, opt_instance) {
+ var nativePrototype = nativeConstructor.prototype;
+ registerInternal(nativePrototype, wrapperConstructor, opt_instance);
+ mixinStatics(wrapperConstructor, nativeConstructor);
+ }
+
+ function registerInternal(nativePrototype, wrapperConstructor, opt_instance) {
+ var wrapperPrototype = wrapperConstructor.prototype;
+ assert(constructorTable.get(nativePrototype) === undefined);
+
+ constructorTable.set(nativePrototype, wrapperConstructor);
+ nativePrototypeTable.set(wrapperPrototype, nativePrototype);
+
+ addForwardingProperties(nativePrototype, wrapperPrototype);
+ if (opt_instance)
+ registerInstanceProperties(wrapperPrototype, opt_instance);
+ defineProperty(wrapperPrototype, 'constructor', {
+ value: wrapperConstructor,
+ configurable: true,
+ enumerable: false,
+ writable: true
+ });
+ }
+
+ function isWrapperFor(wrapperConstructor, nativeConstructor) {
+ return constructorTable.get(nativeConstructor.prototype) ===
+ wrapperConstructor;
+ }
+
+ /**
+ * Creates a generic wrapper constructor based on |object| and its
+ * constructor.
+ * @param {Node} object
+ * @return {Function} The generated constructor.
+ */
+ function registerObject(object) {
+ var nativePrototype = Object.getPrototypeOf(object);
+
+ var superWrapperConstructor = getWrapperConstructor(nativePrototype);
+ var GeneratedWrapper = createWrapperConstructor(superWrapperConstructor);
+ registerInternal(nativePrototype, GeneratedWrapper, object);
+
+ return GeneratedWrapper;
+ }
+
+ function createWrapperConstructor(superWrapperConstructor) {
+ function GeneratedWrapper(node) {
+ superWrapperConstructor.call(this, node);
+ }
+ GeneratedWrapper.prototype =
+ Object.create(superWrapperConstructor.prototype);
+ GeneratedWrapper.prototype.constructor = GeneratedWrapper;
+
+ return GeneratedWrapper;
+ }
+
+ var OriginalDOMImplementation = window.DOMImplementation;
+ var OriginalEventTarget = window.EventTarget;
+ var OriginalEvent = window.Event;
+ var OriginalNode = window.Node;
+ var OriginalWindow = window.Window;
+ var OriginalRange = window.Range;
+ var OriginalCanvasRenderingContext2D = window.CanvasRenderingContext2D;
+ var OriginalWebGLRenderingContext = window.WebGLRenderingContext;
+ var OriginalSVGElementInstance = window.SVGElementInstance;
+
+ function isWrapper(object) {
+ return object instanceof wrappers.EventTarget ||
+ object instanceof wrappers.Event ||
+ object instanceof wrappers.Range ||
+ object instanceof wrappers.DOMImplementation ||
+ object instanceof wrappers.CanvasRenderingContext2D ||
+ wrappers.WebGLRenderingContext &&
+ object instanceof wrappers.WebGLRenderingContext;
+ }
+
+ function isNative(object) {
+ return OriginalEventTarget && object instanceof OriginalEventTarget ||
+ object instanceof OriginalNode ||
+ object instanceof OriginalEvent ||
+ object instanceof OriginalWindow ||
+ object instanceof OriginalRange ||
+ object instanceof OriginalDOMImplementation ||
+ object instanceof OriginalCanvasRenderingContext2D ||
+ OriginalWebGLRenderingContext &&
+ object instanceof OriginalWebGLRenderingContext ||
+ OriginalSVGElementInstance &&
+ object instanceof OriginalSVGElementInstance;
+ }
+
+ /**
+ * Wraps a node in a WrapperNode. If there already exists a wrapper for the
+ * |node| that wrapper is returned instead.
+ * @param {Node} node
+ * @return {WrapperNode}
+ */
+ function wrap(impl) {
+ if (impl === null)
+ return null;
+
+ assert(isNative(impl));
+ return impl.polymerWrapper_ ||
+ (impl.polymerWrapper_ = new (getWrapperConstructor(impl))(impl));
+ }
+
+ /**
+ * Unwraps a wrapper and returns the node it is wrapping.
+ * @param {WrapperNode} wrapper
+ * @return {Node}
+ */
+ function unwrap(wrapper) {
+ if (wrapper === null)
+ return null;
+ assert(isWrapper(wrapper));
+ return wrapper.impl;
+ }
+
+ /**
+ * Unwraps object if it is a wrapper.
+ * @param {Object} object
+ * @return {Object} The native implementation object.
+ */
+ function unwrapIfNeeded(object) {
+ return object && isWrapper(object) ? unwrap(object) : object;
+ }
+
+ /**
+ * Wraps object if it is not a wrapper.
+ * @param {Object} object
+ * @return {Object} The wrapper for object.
+ */
+ function wrapIfNeeded(object) {
+ return object && !isWrapper(object) ? wrap(object) : object;
+ }
+
+ /**
+ * Overrides the current wrapper (if any) for node.
+ * @param {Node} node
+ * @param {WrapperNode=} wrapper If left out the wrapper will be created as
+ * needed next time someone wraps the node.
+ */
+ function rewrap(node, wrapper) {
+ if (wrapper === null)
+ return;
+ assert(isNative(node));
+ assert(wrapper === undefined || isWrapper(wrapper));
+ node.polymerWrapper_ = wrapper;
+ }
+
+ function defineGetter(constructor, name, getter) {
+ defineProperty(constructor.prototype, name, {
+ get: getter,
+ configurable: true,
+ enumerable: true
+ });
+ }
+
+ function defineWrapGetter(constructor, name) {
+ defineGetter(constructor, name, function() {
+ return wrap(this.impl[name]);
+ });
+ }
+
+ /**
+ * Forwards existing methods on the native object to the wrapper methods.
+ * This does not wrap any of the arguments or the return value since the
+ * wrapper implementation already takes care of that.
+ * @param {Array.<Function>} constructors
+ * @parem {Array.<string>} names
+ */
+ function forwardMethodsToWrapper(constructors, names) {
+ constructors.forEach(function(constructor) {
+ names.forEach(function(name) {
+ constructor.prototype[name] = function() {
+ var w = wrapIfNeeded(this);
+ return w[name].apply(w, arguments);
+ };
+ });
+ });
+ }
+
+ scope.assert = assert;
+ scope.constructorTable = constructorTable;
+ scope.defineGetter = defineGetter;
+ scope.defineWrapGetter = defineWrapGetter;
+ scope.forwardMethodsToWrapper = forwardMethodsToWrapper;
+ scope.isWrapper = isWrapper;
+ scope.isWrapperFor = isWrapperFor;
+ scope.mixin = mixin;
+ scope.nativePrototypeTable = nativePrototypeTable;
+ scope.oneOf = oneOf;
+ scope.registerObject = registerObject;
+ scope.registerWrapper = register;
+ scope.rewrap = rewrap;
+ scope.unwrap = unwrap;
+ scope.unwrapIfNeeded = unwrapIfNeeded;
+ scope.wrap = wrap;
+ scope.wrapIfNeeded = wrapIfNeeded;
+ scope.wrappers = wrappers;
+
+})(window.ShadowDOMPolyfill);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is goverened by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+(function(context) {
+ 'use strict';
+
+ var OriginalMutationObserver = window.MutationObserver;
+ var callbacks = [];
+ var pending = false;
+ var timerFunc;
+
+ function handle() {
+ pending = false;
+ var copies = callbacks.slice(0);
+ callbacks = [];
+ for (var i = 0; i < copies.length; i++) {
+ (0, copies[i])();
+ }
+ }
+
+ if (OriginalMutationObserver) {
+ var counter = 1;
+ var observer = new OriginalMutationObserver(handle);
+ var textNode = document.createTextNode(counter);
+ observer.observe(textNode, {characterData: true});
+
+ timerFunc = function() {
+ counter = (counter + 1) % 2;
+ textNode.data = counter;
+ };
+
+ } else {
+ timerFunc = window.setImmediate || window.setTimeout;
+ }
+
+ function setEndOfMicrotask(func) {
+ callbacks.push(func);
+ if (pending)
+ return;
+ pending = true;
+ timerFunc(handle, 0);
+ }
+
+ context.setEndOfMicrotask = setEndOfMicrotask;
+
+})(window.ShadowDOMPolyfill);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is goverened by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+(function(scope) {
+ 'use strict';
+
+ var setEndOfMicrotask = scope.setEndOfMicrotask
+ var wrapIfNeeded = scope.wrapIfNeeded
+ var wrappers = scope.wrappers;
+
+ var registrationsTable = new WeakMap();
+ var globalMutationObservers = [];
+ var isScheduled = false;
+
+ function scheduleCallback(observer) {
+ if (isScheduled)
+ return;
+ setEndOfMicrotask(notifyObservers);
+ isScheduled = true;
+ }
+
+ // http://dom.spec.whatwg.org/#mutation-observers
+ function notifyObservers() {
+ isScheduled = false;
+
+ do {
+ var notifyList = globalMutationObservers.slice();
+ var anyNonEmpty = false;
+ for (var i = 0; i < notifyList.length; i++) {
+ var mo = notifyList[i];
+ var queue = mo.takeRecords();
+ removeTransientObserversFor(mo);
+ if (queue.length) {
+ mo.callback_(queue, mo);
+ anyNonEmpty = true;
+ }
+ }
+ } while (anyNonEmpty);
+ }
+
+ /**
+ * @param {string} type
+ * @param {Node} target
+ * @constructor
+ */
+ function MutationRecord(type, target) {
+ this.type = type;
+ this.target = target;
+ this.addedNodes = new wrappers.NodeList();
+ this.removedNodes = new wrappers.NodeList();
+ this.previousSibling = null;
+ this.nextSibling = null;
+ this.attributeName = null;
+ this.attributeNamespace = null;
+ this.oldValue = null;
+ }
+
+ /**
+ * Registers transient observers to ancestor and its ancesors for the node
+ * which was removed.
+ * @param {!Node} ancestor
+ * @param {!Node} node
+ */
+ function registerTransientObservers(ancestor, node) {
+ for (; ancestor; ancestor = ancestor.parentNode) {
+ var registrations = registrationsTable.get(ancestor);
+ if (!registrations)
+ continue;
+ for (var i = 0; i < registrations.length; i++) {
+ var registration = registrations[i];
+ if (registration.options.subtree)
+ registration.addTransientObserver(node);
+ }
+ }
+ }
+
+ function removeTransientObserversFor(observer) {
+ for (var i = 0; i < observer.nodes_.length; i++) {
+ var node = observer.nodes_[i];
+ var registrations = registrationsTable.get(node);
+ if (!registrations)
+ return;
+ for (var j = 0; j < registrations.length; j++) {
+ var registration = registrations[j];
+ if (registration.observer === observer)
+ registration.removeTransientObservers();
+ }
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#queue-a-mutation-record
+ function enqueueMutation(target, type, data) {
+ // 1.
+ var interestedObservers = Object.create(null);
+ var associatedStrings = Object.create(null);
+
+ // 2.
+ for (var node = target; node; node = node.parentNode) {
+ // 3.
+ var registrations = registrationsTable.get(node);
+ if (!registrations)
+ continue;
+ for (var j = 0; j < registrations.length; j++) {
+ var registration = registrations[j];
+ var options = registration.options;
+ // 1.
+ if (node !== target && !options.subtree)
+ continue;
+
+ // 2.
+ if (type === 'attributes' && !options.attributes)
+ continue;
+
+ // 3. If type is "attributes", options's attributeFilter is present, and
+ // either options's attributeFilter does not contain name or namespace
+ // is non-null, continue.
+ if (type === 'attributes' && options.attributeFilter &&
+ (data.namespace !== null ||
+ options.attributeFilter.indexOf(data.name) === -1)) {
+ continue;
+ }
+
+ // 4.
+ if (type === 'characterData' && !options.characterData)
+ continue;
+
+ // 5.
+ if (type === 'childList' && !options.childList)
+ continue;
+
+ // 6.
+ var observer = registration.observer;
+ interestedObservers[observer.uid_] = observer;
+
+ // 7. If either type is "attributes" and options's attributeOldValue is
+ // true, or type is "characterData" and options's characterDataOldValue
+ // is true, set the paired string of registered observer's observer in
+ // interested observers to oldValue.
+ if (type === 'attributes' && options.attributeOldValue ||
+ type === 'characterData' && options.characterDataOldValue) {
+ associatedStrings[observer.uid_] = data.oldValue;
+ }
+ }
+ }
+
+ var anyRecordsEnqueued = false;
+
+ // 4.
+ for (var uid in interestedObservers) {
+ var observer = interestedObservers[uid];
+ var record = new MutationRecord(type, target);
+
+ // 2.
+ if ('name' in data && 'namespace' in data) {
+ record.attributeName = data.name;
+ record.attributeNamespace = data.namespace;
+ }
+
+ // 3.
+ if (data.addedNodes)
+ record.addedNodes = data.addedNodes;
+
+ // 4.
+ if (data.removedNodes)
+ record.removedNodes = data.removedNodes;
+
+ // 5.
+ if (data.previousSibling)
+ record.previousSibling = data.previousSibling;
+
+ // 6.
+ if (data.nextSibling)
+ record.nextSibling = data.nextSibling;
+
+ // 7.
+ if (associatedStrings[uid] !== undefined)
+ record.oldValue = associatedStrings[uid];
+
+ // 8.
+ observer.records_.push(record);
+
+ anyRecordsEnqueued = true;
+ }
+
+ if (anyRecordsEnqueued)
+ scheduleCallback();
+ }
+
+ var slice = Array.prototype.slice;
+
+ /**
+ * @param {!Object} options
+ * @constructor
+ */
+ function MutationObserverOptions(options) {
+ this.childList = !!options.childList;
+ this.subtree = !!options.subtree;
+
+ // 1. If either options' attributeOldValue or attributeFilter is present
+ // and options' attributes is omitted, set options' attributes to true.
+ if (!('attributes' in options) &&
+ ('attributeOldValue' in options || 'attributeFilter' in options)) {
+ this.attributes = true;
+ } else {
+ this.attributes = !!options.attributes;
+ }
+
+ // 2. If options' characterDataOldValue is present and options'
+ // characterData is omitted, set options' characterData to true.
+ if ('characterDataOldValue' in options && !('characterData' in options))
+ this.characterData = true;
+ else
+ this.characterData = !!options.characterData;
+
+ // 3. & 4.
+ if (!this.attributes &&
+ (options.attributeOldValue || 'attributeFilter' in options) ||
+ // 5.
+ !this.characterData && options.characterDataOldValue) {
+ throw new TypeError();
+ }
+
+ this.characterData = !!options.characterData;
+ this.attributeOldValue = !!options.attributeOldValue;
+ this.characterDataOldValue = !!options.characterDataOldValue;
+ if ('attributeFilter' in options) {
+ if (options.attributeFilter == null ||
+ typeof options.attributeFilter !== 'object') {
+ throw new TypeError();
+ }
+ this.attributeFilter = slice.call(options.attributeFilter);
+ } else {
+ this.attributeFilter = null;
+ }
+ }
+
+ var uidCounter = 0;
+
+ /**
+ * The class that maps to the DOM MutationObserver interface.
+ * @param {Function} callback.
+ * @constructor
+ */
+ function MutationObserver(callback) {
+ this.callback_ = callback;
+ this.nodes_ = [];
+ this.records_ = [];
+ this.uid_ = ++uidCounter;
+
+ // This will leak. There is no way to implement this without WeakRefs :'(
+ globalMutationObservers.push(this);
+ }
+
+ MutationObserver.prototype = {
+ // http://dom.spec.whatwg.org/#dom-mutationobserver-observe
+ observe: function(target, options) {
+ target = wrapIfNeeded(target);
+
+ var newOptions = new MutationObserverOptions(options);
+
+ // 6.
+ var registration;
+ var registrations = registrationsTable.get(target);
+ if (!registrations)
+ registrationsTable.set(target, registrations = []);
+
+ for (var i = 0; i < registrations.length; i++) {
+ if (registrations[i].observer === this) {
+ registration = registrations[i];
+ // 6.1.
+ registration.removeTransientObservers();
+ // 6.2.
+ registration.options = newOptions;
+ }
+ }
+
+ // 7.
+ if (!registration) {
+ registration = new Registration(this, target, newOptions);
+ registrations.push(registration);
+ this.nodes_.push(target);
+ }
+ },
+
+ // http://dom.spec.whatwg.org/#dom-mutationobserver-disconnect
+ disconnect: function() {
+ this.nodes_.forEach(function(node) {
+ var registrations = registrationsTable.get(node);
+ for (var i = 0; i < registrations.length; i++) {
+ var registration = registrations[i];
+ if (registration.observer === this) {
+ registrations.splice(i, 1);
+ // Each node can only have one registered observer associated with
+ // this observer.
+ break;
+ }
+ }
+ }, this);
+ this.records_ = [];
+ },
+
+ takeRecords: function() {
+ var copyOfRecords = this.records_;
+ this.records_ = [];
+ return copyOfRecords;
+ }
+ };
+
+ /**
+ * Class used to represent a registered observer.
+ * @param {MutationObserver} observer
+ * @param {Node} target
+ * @param {MutationObserverOptions} options
+ * @constructor
+ */
+ function Registration(observer, target, options) {
+ this.observer = observer;
+ this.target = target;
+ this.options = options;
+ this.transientObservedNodes = [];
+ }
+
+ Registration.prototype = {
+ /**
+ * Adds a transient observer on node. The transient observer gets removed
+ * next time we deliver the change records.
+ * @param {Node} node
+ */
+ addTransientObserver: function(node) {
+ // Don't add transient observers on the target itself. We already have all
+ // the required listeners set up on the target.
+ if (node === this.target)
+ return;
+
+ this.transientObservedNodes.push(node);
+ var registrations = registrationsTable.get(node);
+ if (!registrations)
+ registrationsTable.set(node, registrations = []);
+
+ // We know that registrations does not contain this because we already
+ // checked if node === this.target.
+ registrations.push(this);
+ },
+
+ removeTransientObservers: function() {
+ var transientObservedNodes = this.transientObservedNodes;
+ this.transientObservedNodes = [];
+
+ for (var i = 0; i < transientObservedNodes.length; i++) {
+ var node = transientObservedNodes[i];
+ var registrations = registrationsTable.get(node);
+ for (var j = 0; j < registrations.length; j++) {
+ if (registrations[j] === this) {
+ registrations.splice(j, 1);
+ // Each node can only have one registered observer associated with
+ // this observer.
+ break;
+ }
+ }
+ }
+ }
+ };
+
+ scope.enqueueMutation = enqueueMutation;
+ scope.registerTransientObservers = registerTransientObservers;
+ scope.wrappers.MutationObserver = MutationObserver;
+ scope.wrappers.MutationRecord = MutationRecord;
+
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var forwardMethodsToWrapper = scope.forwardMethodsToWrapper;
+ var mixin = scope.mixin;
+ var registerWrapper = scope.registerWrapper;
+ var unwrap = scope.unwrap;
+ var wrap = scope.wrap;
+ var wrappers = scope.wrappers;
+
+ var wrappedFuns = new WeakMap();
+ var listenersTable = new WeakMap();
+ var handledEventsTable = new WeakMap();
+ var currentlyDispatchingEvents = new WeakMap();
+ var targetTable = new WeakMap();
+ var currentTargetTable = new WeakMap();
+ var relatedTargetTable = new WeakMap();
+ var eventPhaseTable = new WeakMap();
+ var stopPropagationTable = new WeakMap();
+ var stopImmediatePropagationTable = new WeakMap();
+ var eventHandlersTable = new WeakMap();
+ var eventPathTable = new WeakMap();
+
+ function isShadowRoot(node) {
+ return node instanceof wrappers.ShadowRoot;
+ }
+
+ function isInsertionPoint(node) {
+ var localName = node.localName;
+ return localName === 'content' || localName === 'shadow';
+ }
+
+ function isShadowHost(node) {
+ return !!node.shadowRoot;
+ }
+
+ function getEventParent(node) {
+ var dv;
+ return node.parentNode || (dv = node.defaultView) && wrap(dv) || null;
+ }
+
+ // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#dfn-adjusted-parent
+ function calculateParents(node, context, ancestors) {
+ if (ancestors.length)
+ return ancestors.shift();
+
+ // 1.
+ if (isShadowRoot(node))
+ return getInsertionParent(node) || node.host;
+
+ // 2.
+ var eventParents = scope.eventParentsTable.get(node);
+ if (eventParents) {
+ // Copy over the remaining event parents for next iteration.
+ for (var i = 1; i < eventParents.length; i++) {
+ ancestors[i - 1] = eventParents[i];
+ }
+ return eventParents[0];
+ }
+
+ // 3.
+ if (context && isInsertionPoint(node)) {
+ var parentNode = node.parentNode;
+ if (parentNode && isShadowHost(parentNode)) {
+ var trees = scope.getShadowTrees(parentNode);
+ var p = getInsertionParent(context);
+ for (var i = 0; i < trees.length; i++) {
+ if (trees[i].contains(p))
+ return p;
+ }
+ }
+ }
+
+ return getEventParent(node);
+ }
+
+ // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#event-retargeting
+ function retarget(node) {
+ var stack = []; // 1.
+ var ancestor = node; // 2.
+ var targets = [];
+ var ancestors = [];
+ while (ancestor) { // 3.
+ var context = null; // 3.2.
+ // TODO(arv): Change order of these. If the stack is empty we always end
+ // up pushing ancestor, no matter what.
+ if (isInsertionPoint(ancestor)) { // 3.1.
+ context = topMostNotInsertionPoint(stack); // 3.1.1.
+ var top = stack[stack.length - 1] || ancestor; // 3.1.2.
+ stack.push(top);
+ } else if (!stack.length) {
+ stack.push(ancestor); // 3.3.
+ }
+ var target = stack[stack.length - 1]; // 3.4.
+ targets.push({target: target, currentTarget: ancestor}); // 3.5.
+ if (isShadowRoot(ancestor)) // 3.6.
+ stack.pop(); // 3.6.1.
+
+ ancestor = calculateParents(ancestor, context, ancestors); // 3.7.
+ }
+ return targets;
+ }
+
+ function topMostNotInsertionPoint(stack) {
+ for (var i = stack.length - 1; i >= 0; i--) {
+ if (!isInsertionPoint(stack[i]))
+ return stack[i];
+ }
+ return null;
+ }
+
+ // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#dfn-adjusted-related-target
+ function adjustRelatedTarget(target, related) {
+ var ancestors = [];
+ while (target) { // 3.
+ var stack = []; // 3.1.
+ var ancestor = related; // 3.2.
+ var last = undefined; // 3.3. Needs to be reset every iteration.
+ while (ancestor) {
+ var context = null;
+ if (!stack.length) {
+ stack.push(ancestor);
+ } else {
+ if (isInsertionPoint(ancestor)) { // 3.4.3.
+ context = topMostNotInsertionPoint(stack);
+ // isDistributed is more general than checking whether last is
+ // assigned into ancestor.
+ if (isDistributed(last)) { // 3.4.3.2.
+ var head = stack[stack.length - 1];
+ stack.push(head);
+ }
+ }
+ }
+
+ if (inSameTree(ancestor, target)) // 3.4.4.
+ return stack[stack.length - 1];
+
+ if (isShadowRoot(ancestor)) // 3.4.5.
+ stack.pop();
+
+ last = ancestor; // 3.4.6.
+ ancestor = calculateParents(ancestor, context, ancestors); // 3.4.7.
+ }
+ if (isShadowRoot(target)) // 3.5.
+ target = target.host;
+ else
+ target = target.parentNode; // 3.6.
+ }
+ }
+
+ function getInsertionParent(node) {
+ return scope.insertionParentTable.get(node);
+ }
+
+ function isDistributed(node) {
+ return getInsertionParent(node);
+ }
+
+ function rootOfNode(node) {
+ var p;
+ while (p = node.parentNode) {
+ node = p;
+ }
+ return node;
+ }
+
+ function inSameTree(a, b) {
+ return rootOfNode(a) === rootOfNode(b);
+ }
+
+ function enclosedBy(a, b) {
+ if (a === b)
+ return true;
+ if (a instanceof wrappers.ShadowRoot)
+ return enclosedBy(rootOfNode(a.host), b);
+ return false;
+ }
+
+
+ function dispatchOriginalEvent(originalEvent) {
+ // Make sure this event is only dispatched once.
+ if (handledEventsTable.get(originalEvent))
+ return;
+ handledEventsTable.set(originalEvent, true);
+
+ return dispatchEvent(wrap(originalEvent), wrap(originalEvent.target));
+ }
+
+ function dispatchEvent(event, originalWrapperTarget) {
+ if (currentlyDispatchingEvents.get(event))
+ throw new Error('InvalidStateError')
+ currentlyDispatchingEvents.set(event, true);
+
+ // Render to ensure that the event path is correct.
+ scope.renderAllPending();
+ var eventPath = retarget(originalWrapperTarget);
+
+ // For window load events the load event is dispatched at the window but
+ // the target is set to the document.
+ //
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#the-end
+ //
+ // TODO(arv): Find a less hacky way to do this.
+ if (event.type === 'load' &&
+ eventPath.length === 2 &&
+ eventPath[0].target instanceof wrappers.Document) {
+ eventPath.shift();
+ }
+
+ eventPathTable.set(event, eventPath);
+
+ if (dispatchCapturing(event, eventPath)) {
+ if (dispatchAtTarget(event, eventPath)) {
+ dispatchBubbling(event, eventPath);
+ }
+ }
+
+ eventPhaseTable.set(event, Event.NONE);
+ currentTargetTable.delete(event, null);
+ currentlyDispatchingEvents.delete(event);
+
+ return event.defaultPrevented;
+ }
+
+ function dispatchCapturing(event, eventPath) {
+ var phase;
+
+ for (var i = eventPath.length - 1; i > 0; i--) {
+ var target = eventPath[i].target;
+ var currentTarget = eventPath[i].currentTarget;
+ if (target === currentTarget)
+ continue;
+
+ phase = Event.CAPTURING_PHASE;
+ if (!invoke(eventPath[i], event, phase))
+ return false;
+ }
+
+ return true;
+ }
+
+ function dispatchAtTarget(event, eventPath) {
+ var phase = Event.AT_TARGET;
+ return invoke(eventPath[0], event, phase);
+ }
+
+ function dispatchBubbling(event, eventPath) {
+ var bubbles = event.bubbles;
+ var phase;
+
+ for (var i = 1; i < eventPath.length; i++) {
+ var target = eventPath[i].target;
+ var currentTarget = eventPath[i].currentTarget;
+ if (target === currentTarget)
+ phase = Event.AT_TARGET;
+ else if (bubbles && !stopImmediatePropagationTable.get(event))
+ phase = Event.BUBBLING_PHASE;
+ else
+ continue;
+
+ if (!invoke(eventPath[i], event, phase))
+ return;
+ }
+ }
+
+ function invoke(tuple, event, phase) {
+ var target = tuple.target;
+ var currentTarget = tuple.currentTarget;
+
+ var listeners = listenersTable.get(currentTarget);
+ if (!listeners)
+ return true;
+
+ if ('relatedTarget' in event) {
+ var originalEvent = unwrap(event);
+ // X-Tag sets relatedTarget on a CustomEvent. If they do that there is no
+ // way to have relatedTarget return the adjusted target but worse is that
+ // the originalEvent might not have a relatedTarget so we hit an assert
+ // when we try to wrap it.
+ if (originalEvent.relatedTarget) {
+ var relatedTarget = wrap(originalEvent.relatedTarget);
+
+ var adjusted = adjustRelatedTarget(currentTarget, relatedTarget);
+ if (adjusted === target)
+ return true;
+
+ relatedTargetTable.set(event, adjusted);
+ }
+ }
+
+ eventPhaseTable.set(event, phase);
+ var type = event.type;
+
+ var anyRemoved = false;
+ targetTable.set(event, target);
+ currentTargetTable.set(event, currentTarget);
+
+ for (var i = 0; i < listeners.length; i++) {
+ var listener = listeners[i];
+ if (listener.removed) {
+ anyRemoved = true;
+ continue;
+ }
+
+ if (listener.type !== type ||
+ !listener.capture && phase === Event.CAPTURING_PHASE ||
+ listener.capture && phase === Event.BUBBLING_PHASE) {
+ continue;
+ }
+
+ try {
+ if (typeof listener.handler === 'function')
+ listener.handler.call(currentTarget, event);
+ else
+ listener.handler.handleEvent(event);
+
+ if (stopImmediatePropagationTable.get(event))
+ return false;
+
+ } catch (ex) {
+ if (window.onerror)
+ window.onerror(ex.message);
+ else
+ console.error(ex, ex.stack);
+ }
+ }
+
+ if (anyRemoved) {
+ var copy = listeners.slice();
+ listeners.length = 0;
+ for (var i = 0; i < copy.length; i++) {
+ if (!copy[i].removed)
+ listeners.push(copy[i]);
+ }
+ }
+
+ return !stopPropagationTable.get(event);
+ }
+
+ function Listener(type, handler, capture) {
+ this.type = type;
+ this.handler = handler;
+ this.capture = Boolean(capture);
+ }
+ Listener.prototype = {
+ equals: function(that) {
+ return this.handler === that.handler && this.type === that.type &&
+ this.capture === that.capture;
+ },
+ get removed() {
+ return this.handler === null;
+ },
+ remove: function() {
+ this.handler = null;
+ }
+ };
+
+ var OriginalEvent = window.Event;
+ OriginalEvent.prototype.polymerBlackList_ = {
+ returnValue: true,
+ // TODO(arv): keyLocation is part of KeyboardEvent but Firefox does not
+ // support constructable KeyboardEvent so we keep it here for now.
+ keyLocation: true
+ };
+
+ /**
+ * Creates a new Event wrapper or wraps an existin native Event object.
+ * @param {string|Event} type
+ * @param {Object=} options
+ * @constructor
+ */
+ function Event(type, options) {
+ if (type instanceof OriginalEvent)
+ this.impl = type;
+ else
+ return wrap(constructEvent(OriginalEvent, 'Event', type, options));
+ }
+ Event.prototype = {
+ get target() {
+ return targetTable.get(this);
+ },
+ get currentTarget() {
+ return currentTargetTable.get(this);
+ },
+ get eventPhase() {
+ return eventPhaseTable.get(this);
+ },
+ get path() {
+ var nodeList = new wrappers.NodeList();
+ var eventPath = eventPathTable.get(this);
+ if (eventPath) {
+ var index = 0;
+ var lastIndex = eventPath.length - 1;
+ var baseRoot = rootOfNode(currentTargetTable.get(this));
+
+ for (var i = 0; i <= lastIndex; i++) {
+ var currentTarget = eventPath[i].currentTarget;
+ var currentRoot = rootOfNode(currentTarget);
+ if (enclosedBy(baseRoot, currentRoot) &&
+ // Make sure we do not add Window to the path.
+ (i !== lastIndex || currentTarget instanceof wrappers.Node)) {
+ nodeList[index++] = currentTarget;
+ }
+ }
+ nodeList.length = index;
+ }
+ return nodeList;
+ },
+ stopPropagation: function() {
+ stopPropagationTable.set(this, true);
+ },
+ stopImmediatePropagation: function() {
+ stopPropagationTable.set(this, true);
+ stopImmediatePropagationTable.set(this, true);
+ }
+ };
+ registerWrapper(OriginalEvent, Event, document.createEvent('Event'));
+
+ function unwrapOptions(options) {
+ if (!options || !options.relatedTarget)
+ return options;
+ return Object.create(options, {
+ relatedTarget: {value: unwrap(options.relatedTarget)}
+ });
+ }
+
+ function registerGenericEvent(name, SuperEvent, prototype) {
+ var OriginalEvent = window[name];
+ var GenericEvent = function(type, options) {
+ if (type instanceof OriginalEvent)
+ this.impl = type;
+ else
+ return wrap(constructEvent(OriginalEvent, name, type, options));
+ };
+ GenericEvent.prototype = Object.create(SuperEvent.prototype);
+ if (prototype)
+ mixin(GenericEvent.prototype, prototype);
+ if (OriginalEvent) {
+ // - Old versions of Safari fails on new FocusEvent (and others?).
+ // - IE does not support event constructors.
+ // - createEvent('FocusEvent') throws in Firefox.
+ // => Try the best practice solution first and fallback to the old way
+ // if needed.
+ try {
+ registerWrapper(OriginalEvent, GenericEvent, new OriginalEvent('temp'));
+ } catch (ex) {
+ registerWrapper(OriginalEvent, GenericEvent,
+ document.createEvent(name));
+ }
+ }
+ return GenericEvent;
+ }
+
+ var UIEvent = registerGenericEvent('UIEvent', Event);
+ var CustomEvent = registerGenericEvent('CustomEvent', Event);
+
+ var relatedTargetProto = {
+ get relatedTarget() {
+ return relatedTargetTable.get(this) || wrap(unwrap(this).relatedTarget);
+ }
+ };
+
+ function getInitFunction(name, relatedTargetIndex) {
+ return function() {
+ arguments[relatedTargetIndex] = unwrap(arguments[relatedTargetIndex]);
+ var impl = unwrap(this);
+ impl[name].apply(impl, arguments);
+ };
+ }
+
+ var mouseEventProto = mixin({
+ initMouseEvent: getInitFunction('initMouseEvent', 14)
+ }, relatedTargetProto);
+
+ var focusEventProto = mixin({
+ initFocusEvent: getInitFunction('initFocusEvent', 5)
+ }, relatedTargetProto);
+
+ var MouseEvent = registerGenericEvent('MouseEvent', UIEvent, mouseEventProto);
+ var FocusEvent = registerGenericEvent('FocusEvent', UIEvent, focusEventProto);
+
+ // In case the browser does not support event constructors we polyfill that
+ // by calling `createEvent('Foo')` and `initFooEvent` where the arguments to
+ // `initFooEvent` are derived from the registered default event init dict.
+ var defaultInitDicts = Object.create(null);
+
+ var supportsEventConstructors = (function() {
+ try {
+ new window.FocusEvent('focus');
+ } catch (ex) {
+ return false;
+ }
+ return true;
+ })();
+
+ /**
+ * Constructs a new native event.
+ */
+ function constructEvent(OriginalEvent, name, type, options) {
+ if (supportsEventConstructors)
+ return new OriginalEvent(type, unwrapOptions(options));
+
+ // Create the arguments from the default dictionary.
+ var event = unwrap(document.createEvent(name));
+ var defaultDict = defaultInitDicts[name];
+ var args = [type];
+ Object.keys(defaultDict).forEach(function(key) {
+ var v = options != null && key in options ?
+ options[key] : defaultDict[key];
+ if (key === 'relatedTarget')
+ v = unwrap(v);
+ args.push(v);
+ });
+ event['init' + name].apply(event, args);
+ return event;
+ }
+
+ if (!supportsEventConstructors) {
+ var configureEventConstructor = function(name, initDict, superName) {
+ if (superName) {
+ var superDict = defaultInitDicts[superName];
+ initDict = mixin(mixin({}, superDict), initDict);
+ }
+
+ defaultInitDicts[name] = initDict;
+ };
+
+ // The order of the default event init dictionary keys is important, the
+ // arguments to initFooEvent is derived from that.
+ configureEventConstructor('Event', {bubbles: false, cancelable: false});
+ configureEventConstructor('CustomEvent', {detail: null}, 'Event');
+ configureEventConstructor('UIEvent', {view: null, detail: 0}, 'Event');
+ configureEventConstructor('MouseEvent', {
+ screenX: 0,
+ screenY: 0,
+ clientX: 0,
+ clientY: 0,
+ ctrlKey: false,
+ altKey: false,
+ shiftKey: false,
+ metaKey: false,
+ button: 0,
+ relatedTarget: null
+ }, 'UIEvent');
+ configureEventConstructor('FocusEvent', {relatedTarget: null}, 'UIEvent');
+ }
+
+ function BeforeUnloadEvent(impl) {
+ Event.call(this);
+ }
+ BeforeUnloadEvent.prototype = Object.create(Event.prototype);
+ mixin(BeforeUnloadEvent.prototype, {
+ get returnValue() {
+ return this.impl.returnValue;
+ },
+ set returnValue(v) {
+ this.impl.returnValue = v;
+ }
+ });
+
+ function isValidListener(fun) {
+ if (typeof fun === 'function')
+ return true;
+ return fun && fun.handleEvent;
+ }
+
+ function isMutationEvent(type) {
+ switch (type) {
+ case 'DOMAttrModified':
+ case 'DOMAttributeNameChanged':
+ case 'DOMCharacterDataModified':
+ case 'DOMElementNameChanged':
+ case 'DOMNodeInserted':
+ case 'DOMNodeInsertedIntoDocument':
+ case 'DOMNodeRemoved':
+ case 'DOMNodeRemovedFromDocument':
+ case 'DOMSubtreeModified':
+ return true;
+ }
+ return false;
+ }
+
+ var OriginalEventTarget = window.EventTarget;
+
+ /**
+ * This represents a wrapper for an EventTarget.
+ * @param {!EventTarget} impl The original event target.
+ * @constructor
+ */
+ function EventTarget(impl) {
+ this.impl = impl;
+ }
+
+ // Node and Window have different internal type checks in WebKit so we cannot
+ // use the same method as the original function.
+ var methodNames = [
+ 'addEventListener',
+ 'removeEventListener',
+ 'dispatchEvent'
+ ];
+
+ [Node, Window].forEach(function(constructor) {
+ var p = constructor.prototype;
+ methodNames.forEach(function(name) {
+ Object.defineProperty(p, name + '_', {value: p[name]});
+ });
+ });
+
+ function getTargetToListenAt(wrapper) {
+ if (wrapper instanceof wrappers.ShadowRoot)
+ wrapper = wrapper.host;
+ return unwrap(wrapper);
+ }
+
+ EventTarget.prototype = {
+ addEventListener: function(type, fun, capture) {
+ if (!isValidListener(fun) || isMutationEvent(type))
+ return;
+
+ var listener = new Listener(type, fun, capture);
+ var listeners = listenersTable.get(this);
+ if (!listeners) {
+ listeners = [];
+ listenersTable.set(this, listeners);
+ } else {
+ // Might have a duplicate.
+ for (var i = 0; i < listeners.length; i++) {
+ if (listener.equals(listeners[i]))
+ return;
+ }
+ }
+
+ listeners.push(listener);
+
+ var target = getTargetToListenAt(this);
+ target.addEventListener_(type, dispatchOriginalEvent, true);
+ },
+ removeEventListener: function(type, fun, capture) {
+ capture = Boolean(capture);
+ var listeners = listenersTable.get(this);
+ if (!listeners)
+ return;
+ var count = 0, found = false;
+ for (var i = 0; i < listeners.length; i++) {
+ if (listeners[i].type === type && listeners[i].capture === capture) {
+ count++;
+ if (listeners[i].handler === fun) {
+ found = true;
+ listeners[i].remove();
+ }
+ }
+ }
+
+ if (found && count === 1) {
+ var target = getTargetToListenAt(this);
+ target.removeEventListener_(type, dispatchOriginalEvent, true);
+ }
+ },
+ dispatchEvent: function(event) {
+ // We want to use the native dispatchEvent because it triggers the default
+ // actions (like checking a checkbox). However, if there are no listeners
+ // in the composed tree then there are no events that will trigger and
+ // listeners in the non composed tree that are part of the event path are
+ // not notified.
+ //
+ // If we find out that there are no listeners in the composed tree we add
+ // a temporary listener to the target which makes us get called back even
+ // in that case.
+
+ var nativeEvent = unwrap(event);
+ var eventType = nativeEvent.type;
+
+ // Allow dispatching the same event again. This is safe because if user
+ // code calls this during an existing dispatch of the same event the
+ // native dispatchEvent throws (that is required by the spec).
+ handledEventsTable.set(nativeEvent, false);
+
+ // Force rendering since we prefer native dispatch and that works on the
+ // composed tree.
+ scope.renderAllPending();
+
+ var tempListener;
+ if (!hasListenerInAncestors(this, eventType)) {
+ tempListener = function() {};
+ this.addEventListener(eventType, tempListener, true);
+ }
+
+ try {
+ return unwrap(this).dispatchEvent_(nativeEvent);
+ } finally {
+ if (tempListener)
+ this.removeEventListener(eventType, tempListener, true);
+ }
+ }
+ };
+
+ function hasListener(node, type) {
+ var listeners = listenersTable.get(node);
+ if (listeners) {
+ for (var i = 0; i < listeners.length; i++) {
+ if (!listeners[i].removed && listeners[i].type === type)
+ return true;
+ }
+ }
+ return false;
+ }
+
+ function hasListenerInAncestors(target, type) {
+ for (var node = unwrap(target); node; node = node.parentNode) {
+ if (hasListener(wrap(node), type))
+ return true;
+ }
+ return false;
+ }
+
+ if (OriginalEventTarget)
+ registerWrapper(OriginalEventTarget, EventTarget);
+
+ function wrapEventTargetMethods(constructors) {
+ forwardMethodsToWrapper(constructors, methodNames);
+ }
+
+ var originalElementFromPoint = document.elementFromPoint;
+
+ function elementFromPoint(self, document, x, y) {
+ scope.renderAllPending();
+
+ var element = wrap(originalElementFromPoint.call(document.impl, x, y));
+ var targets = retarget(element, this)
+ for (var i = 0; i < targets.length; i++) {
+ var target = targets[i];
+ if (target.currentTarget === self)
+ return target.target;
+ }
+ return null;
+ }
+
+ /**
+ * Returns a function that is to be used as a getter for `onfoo` properties.
+ * @param {string} name
+ * @return {Function}
+ */
+ function getEventHandlerGetter(name) {
+ return function() {
+ var inlineEventHandlers = eventHandlersTable.get(this);
+ return inlineEventHandlers && inlineEventHandlers[name] &&
+ inlineEventHandlers[name].value || null;
+ };
+ }
+
+ /**
+ * Returns a function that is to be used as a setter for `onfoo` properties.
+ * @param {string} name
+ * @return {Function}
+ */
+ function getEventHandlerSetter(name) {
+ var eventType = name.slice(2);
+ return function(value) {
+ var inlineEventHandlers = eventHandlersTable.get(this);
+ if (!inlineEventHandlers) {
+ inlineEventHandlers = Object.create(null);
+ eventHandlersTable.set(this, inlineEventHandlers);
+ }
+
+ var old = inlineEventHandlers[name];
+ if (old)
+ this.removeEventListener(eventType, old.wrapped, false);
+
+ if (typeof value === 'function') {
+ var wrapped = function(e) {
+ var rv = value.call(this, e);
+ if (rv === false)
+ e.preventDefault();
+ else if (name === 'onbeforeunload' && typeof rv === 'string')
+ e.returnValue = rv;
+ // mouseover uses true for preventDefault but preventDefault for
+ // mouseover is ignored by browsers these day.
+ };
+
+ this.addEventListener(eventType, wrapped, false);
+ inlineEventHandlers[name] = {
+ value: value,
+ wrapped: wrapped
+ };
+ }
+ };
+ }
+
+ scope.adjustRelatedTarget = adjustRelatedTarget;
+ scope.elementFromPoint = elementFromPoint;
+ scope.getEventHandlerGetter = getEventHandlerGetter;
+ scope.getEventHandlerSetter = getEventHandlerSetter;
+ scope.wrapEventTargetMethods = wrapEventTargetMethods;
+ scope.wrappers.BeforeUnloadEvent = BeforeUnloadEvent;
+ scope.wrappers.CustomEvent = CustomEvent;
+ scope.wrappers.Event = Event;
+ scope.wrappers.EventTarget = EventTarget;
+ scope.wrappers.FocusEvent = FocusEvent;
+ scope.wrappers.MouseEvent = MouseEvent;
+ scope.wrappers.UIEvent = UIEvent;
+
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2012 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var wrap = scope.wrap;
+
+ function nonEnum(obj, prop) {
+ Object.defineProperty(obj, prop, {enumerable: false});
+ }
+
+ function NodeList() {
+ this.length = 0;
+ nonEnum(this, 'length');
+ }
+ NodeList.prototype = {
+ item: function(index) {
+ return this[index];
+ }
+ };
+ nonEnum(NodeList.prototype, 'item');
+
+ function wrapNodeList(list) {
+ if (list == null)
+ return list;
+ var wrapperList = new NodeList();
+ for (var i = 0, length = list.length; i < length; i++) {
+ wrapperList[i] = wrap(list[i]);
+ }
+ wrapperList.length = length;
+ return wrapperList;
+ }
+
+ function addWrapNodeListMethod(wrapperConstructor, name) {
+ wrapperConstructor.prototype[name] = function() {
+ return wrapNodeList(this.impl[name].apply(this.impl, arguments));
+ };
+ }
+
+ scope.wrappers.NodeList = NodeList;
+ scope.addWrapNodeListMethod = addWrapNodeListMethod;
+ scope.wrapNodeList = wrapNodeList;
+
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2012 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var EventTarget = scope.wrappers.EventTarget;
+ var NodeList = scope.wrappers.NodeList;
+ var assert = scope.assert;
+ var defineWrapGetter = scope.defineWrapGetter;
+ var enqueueMutation = scope.enqueueMutation;
+ var isWrapper = scope.isWrapper;
+ var mixin = scope.mixin;
+ var registerTransientObservers = scope.registerTransientObservers;
+ var registerWrapper = scope.registerWrapper;
+ var unwrap = scope.unwrap;
+ var wrap = scope.wrap;
+ var wrapIfNeeded = scope.wrapIfNeeded;
+ var wrappers = scope.wrappers;
+
+ function assertIsNodeWrapper(node) {
+ assert(node instanceof Node);
+ }
+
+ function createOneElementNodeList(node) {
+ var nodes = new NodeList();
+ nodes[0] = node;
+ nodes.length = 1;
+ return nodes;
+ }
+
+ var surpressMutations = false;
+
+ /**
+ * Called before node is inserted into a node to enqueue its removal from its
+ * old parent.
+ * @param {!Node} node The node that is about to be removed.
+ * @param {!Node} parent The parent node that the node is being removed from.
+ * @param {!NodeList} nodes The collected nodes.
+ */
+ function enqueueRemovalForInsertedNodes(node, parent, nodes) {
+ enqueueMutation(parent, 'childList', {
+ removedNodes: nodes,
+ previousSibling: node.previousSibling,
+ nextSibling: node.nextSibling
+ });
+ }
+
+ function enqueueRemovalForInsertedDocumentFragment(df, nodes) {
+ enqueueMutation(df, 'childList', {
+ removedNodes: nodes
+ });
+ }
+
+ /**
+ * Collects nodes from a DocumentFragment or a Node for removal followed
+ * by an insertion.
+ *
+ * This updates the internal pointers for node, previousNode and nextNode.
+ */
+ function collectNodes(node, parentNode, previousNode, nextNode) {
+ if (node instanceof DocumentFragment) {
+ var nodes = collectNodesForDocumentFragment(node);
+
+ // The extra loop is to work around bugs with DocumentFragments in IE.
+ surpressMutations = true;
+ for (var i = nodes.length - 1; i >= 0; i--) {
+ node.removeChild(nodes[i]);
+ nodes[i].parentNode_ = parentNode;
+ }
+ surpressMutations = false;
+
+ for (var i = 0; i < nodes.length; i++) {
+ nodes[i].previousSibling_ = nodes[i - 1] || previousNode;
+ nodes[i].nextSibling_ = nodes[i + 1] || nextNode;
+ }
+
+ if (previousNode)
+ previousNode.nextSibling_ = nodes[0];
+ if (nextNode)
+ nextNode.previousSibling_ = nodes[nodes.length - 1];
+
+ return nodes;
+ }
+
+ var nodes = createOneElementNodeList(node);
+ var oldParent = node.parentNode;
+ if (oldParent) {
+ // This will enqueue the mutation record for the removal as needed.
+ oldParent.removeChild(node);
+ }
+
+ node.parentNode_ = parentNode;
+ node.previousSibling_ = previousNode;
+ node.nextSibling_ = nextNode;
+ if (previousNode)
+ previousNode.nextSibling_ = node;
+ if (nextNode)
+ nextNode.previousSibling_ = node;
+
+ return nodes;
+ }
+
+ function collectNodesNative(node) {
+ if (node instanceof DocumentFragment)
+ return collectNodesForDocumentFragment(node);
+
+ var nodes = createOneElementNodeList(node);
+ var oldParent = node.parentNode;
+ if (oldParent)
+ enqueueRemovalForInsertedNodes(node, oldParent, nodes);
+ return nodes;
+ }
+
+ function collectNodesForDocumentFragment(node) {
+ var nodes = new NodeList();
+ var i = 0;
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ nodes[i++] = child;
+ }
+ nodes.length = i;
+ enqueueRemovalForInsertedDocumentFragment(node, nodes);
+ return nodes;
+ }
+
+ function snapshotNodeList(nodeList) {
+ // NodeLists are not live at the moment so just return the same object.
+ return nodeList;
+ }
+
+ // http://dom.spec.whatwg.org/#node-is-inserted
+ function nodeWasAdded(node) {
+ node.nodeIsInserted_();
+ }
+
+ function nodesWereAdded(nodes) {
+ for (var i = 0; i < nodes.length; i++) {
+ nodeWasAdded(nodes[i]);
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#node-is-removed
+ function nodeWasRemoved(node) {
+ // Nothing at this point in time.
+ }
+
+ function nodesWereRemoved(nodes) {
+ // Nothing at this point in time.
+ }
+
+ function ensureSameOwnerDocument(parent, child) {
+ var ownerDoc = parent.nodeType === Node.DOCUMENT_NODE ?
+ parent : parent.ownerDocument;
+ if (ownerDoc !== child.ownerDocument)
+ ownerDoc.adoptNode(child);
+ }
+
+ function adoptNodesIfNeeded(owner, nodes) {
+ if (!nodes.length)
+ return;
+
+ var ownerDoc = owner.ownerDocument;
+
+ // All nodes have the same ownerDocument when we get here.
+ if (ownerDoc === nodes[0].ownerDocument)
+ return;
+
+ for (var i = 0; i < nodes.length; i++) {
+ scope.adoptNodeNoRemove(nodes[i], ownerDoc);
+ }
+ }
+
+ function unwrapNodesForInsertion(owner, nodes) {
+ adoptNodesIfNeeded(owner, nodes);
+ var length = nodes.length;
+
+ if (length === 1)
+ return unwrap(nodes[0]);
+
+ var df = unwrap(owner.ownerDocument.createDocumentFragment());
+ for (var i = 0; i < length; i++) {
+ df.appendChild(unwrap(nodes[i]));
+ }
+ return df;
+ }
+
+ function clearChildNodes(wrapper) {
+ if (wrapper.firstChild_ !== undefined) {
+ var child = wrapper.firstChild_;
+ while (child) {
+ var tmp = child;
+ child = child.nextSibling_;
+ tmp.parentNode_ = tmp.previousSibling_ = tmp.nextSibling_ = undefined;
+ }
+ }
+ wrapper.firstChild_ = wrapper.lastChild_ = undefined;
+ }
+
+ function removeAllChildNodes(wrapper) {
+ if (wrapper.invalidateShadowRenderer()) {
+ var childWrapper = wrapper.firstChild;
+ while (childWrapper) {
+ assert(childWrapper.parentNode === wrapper);
+ var nextSibling = childWrapper.nextSibling;
+ var childNode = unwrap(childWrapper);
+ var parentNode = childNode.parentNode;
+ if (parentNode)
+ originalRemoveChild.call(parentNode, childNode);
+ childWrapper.previousSibling_ = childWrapper.nextSibling_ =
+ childWrapper.parentNode_ = null;
+ childWrapper = nextSibling;
+ }
+ wrapper.firstChild_ = wrapper.lastChild_ = null;
+ } else {
+ var node = unwrap(wrapper);
+ var child = node.firstChild;
+ var nextSibling;
+ while (child) {
+ nextSibling = child.nextSibling;
+ originalRemoveChild.call(node, child);
+ child = nextSibling;
+ }
+ }
+ }
+
+ function invalidateParent(node) {
+ var p = node.parentNode;
+ return p && p.invalidateShadowRenderer();
+ }
+
+ function cleanupNodes(nodes) {
+ for (var i = 0, n; i < nodes.length; i++) {
+ n = nodes[i];
+ n.parentNode.removeChild(n);
+ }
+ }
+
+ var originalImportNode = document.importNode;
+ var originalCloneNode = window.Node.prototype.cloneNode;
+
+ function cloneNode(node, deep, opt_doc) {
+ var clone;
+ if (opt_doc)
+ clone = wrap(originalImportNode.call(opt_doc, node.impl, false));
+ else
+ clone = wrap(originalCloneNode.call(node.impl, false));
+
+ if (deep) {
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ clone.appendChild(cloneNode(child, true, opt_doc));
+ }
+
+ if (node instanceof wrappers.HTMLTemplateElement) {
+ var cloneContent = clone.content;
+ for (var child = node.content.firstChild;
+ child;
+ child = child.nextSibling) {
+ cloneContent.appendChild(cloneNode(child, true, opt_doc));
+ }
+ }
+ }
+ // TODO(arv): Some HTML elements also clone other data like value.
+ return clone;
+ }
+
+ var OriginalNode = window.Node;
+
+ /**
+ * This represents a wrapper of a native DOM node.
+ * @param {!Node} original The original DOM node, aka, the visual DOM node.
+ * @constructor
+ * @extends {EventTarget}
+ */
+ function Node(original) {
+ assert(original instanceof OriginalNode);
+
+ EventTarget.call(this, original);
+
+ // These properties are used to override the visual references with the
+ // logical ones. If the value is undefined it means that the logical is the
+ // same as the visual.
+
+ /**
+ * @type {Node|undefined}
+ * @private
+ */
+ this.parentNode_ = undefined;
+
+ /**
+ * @type {Node|undefined}
+ * @private
+ */
+ this.firstChild_ = undefined;
+
+ /**
+ * @type {Node|undefined}
+ * @private
+ */
+ this.lastChild_ = undefined;
+
+ /**
+ * @type {Node|undefined}
+ * @private
+ */
+ this.nextSibling_ = undefined;
+
+ /**
+ * @type {Node|undefined}
+ * @private
+ */
+ this.previousSibling_ = undefined;
+ }
+
+ var OriginalDocumentFragment = window.DocumentFragment;
+ var originalAppendChild = OriginalNode.prototype.appendChild;
+ var originalCompareDocumentPosition =
+ OriginalNode.prototype.compareDocumentPosition;
+ var originalInsertBefore = OriginalNode.prototype.insertBefore;
+ var originalRemoveChild = OriginalNode.prototype.removeChild;
+ var originalReplaceChild = OriginalNode.prototype.replaceChild;
+
+ var isIe = /Trident/.test(navigator.userAgent);
+
+ var removeChildOriginalHelper = isIe ?
+ function(parent, child) {
+ try {
+ originalRemoveChild.call(parent, child);
+ } catch (ex) {
+ if (!(parent instanceof OriginalDocumentFragment))
+ throw ex;
+ }
+ } :
+ function(parent, child) {
+ originalRemoveChild.call(parent, child);
+ };
+
+ Node.prototype = Object.create(EventTarget.prototype);
+ mixin(Node.prototype, {
+ appendChild: function(childWrapper) {
+ return this.insertBefore(childWrapper, null);
+ },
+
+ insertBefore: function(childWrapper, refWrapper) {
+ assertIsNodeWrapper(childWrapper);
+
+ var refNode;
+ if (refWrapper) {
+ if (isWrapper(refWrapper)) {
+ refNode = unwrap(refWrapper);
+ } else {
+ refNode = refWrapper;
+ refWrapper = wrap(refNode);
+ }
+ } else {
+ refWrapper = null;
+ refNode = null;
+ }
+
+ refWrapper && assert(refWrapper.parentNode === this);
+
+ var nodes;
+ var previousNode =
+ refWrapper ? refWrapper.previousSibling : this.lastChild;
+
+ var useNative = !this.invalidateShadowRenderer() &&
+ !invalidateParent(childWrapper);
+
+ if (useNative)
+ nodes = collectNodesNative(childWrapper);
+ else
+ nodes = collectNodes(childWrapper, this, previousNode, refWrapper);
+
+ if (useNative) {
+ ensureSameOwnerDocument(this, childWrapper);
+ clearChildNodes(this);
+ originalInsertBefore.call(this.impl, unwrap(childWrapper), refNode);
+ } else {
+ if (!previousNode)
+ this.firstChild_ = nodes[0];
+ if (!refWrapper)
+ this.lastChild_ = nodes[nodes.length - 1];
+
+ var parentNode = refNode ? refNode.parentNode : this.impl;
+
+ // insertBefore refWrapper no matter what the parent is?
+ if (parentNode) {
+ originalInsertBefore.call(parentNode,
+ unwrapNodesForInsertion(this, nodes), refNode);
+ } else {
+ adoptNodesIfNeeded(this, nodes);
+ }
+ }
+
+ enqueueMutation(this, 'childList', {
+ addedNodes: nodes,
+ nextSibling: refWrapper,
+ previousSibling: previousNode
+ });
+
+ nodesWereAdded(nodes);
+
+ return childWrapper;
+ },
+
+ removeChild: function(childWrapper) {
+ assertIsNodeWrapper(childWrapper);
+ if (childWrapper.parentNode !== this) {
+ // IE has invalid DOM trees at times.
+ var found = false;
+ var childNodes = this.childNodes;
+ for (var ieChild = this.firstChild; ieChild;
+ ieChild = ieChild.nextSibling) {
+ if (ieChild === childWrapper) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ // TODO(arv): DOMException
+ throw new Error('NotFoundError');
+ }
+ }
+
+ var childNode = unwrap(childWrapper);
+ var childWrapperNextSibling = childWrapper.nextSibling;
+ var childWrapperPreviousSibling = childWrapper.previousSibling;
+
+ if (this.invalidateShadowRenderer()) {
+ // We need to remove the real node from the DOM before updating the
+ // pointers. This is so that that mutation event is dispatched before
+ // the pointers have changed.
+ var thisFirstChild = this.firstChild;
+ var thisLastChild = this.lastChild;
+
+ var parentNode = childNode.parentNode;
+ if (parentNode)
+ removeChildOriginalHelper(parentNode, childNode);
+
+ if (thisFirstChild === childWrapper)
+ this.firstChild_ = childWrapperNextSibling;
+ if (thisLastChild === childWrapper)
+ this.lastChild_ = childWrapperPreviousSibling;
+ if (childWrapperPreviousSibling)
+ childWrapperPreviousSibling.nextSibling_ = childWrapperNextSibling;
+ if (childWrapperNextSibling) {
+ childWrapperNextSibling.previousSibling_ =
+ childWrapperPreviousSibling;
+ }
+
+ childWrapper.previousSibling_ = childWrapper.nextSibling_ =
+ childWrapper.parentNode_ = undefined;
+ } else {
+ clearChildNodes(this);
+ removeChildOriginalHelper(this.impl, childNode);
+ }
+
+ if (!surpressMutations) {
+ enqueueMutation(this, 'childList', {
+ removedNodes: createOneElementNodeList(childWrapper),
+ nextSibling: childWrapperNextSibling,
+ previousSibling: childWrapperPreviousSibling
+ });
+ }
+
+ registerTransientObservers(this, childWrapper);
+
+ return childWrapper;
+ },
+
+ replaceChild: function(newChildWrapper, oldChildWrapper) {
+ assertIsNodeWrapper(newChildWrapper);
+
+ var oldChildNode;
+ if (isWrapper(oldChildWrapper)) {
+ oldChildNode = unwrap(oldChildWrapper);
+ } else {
+ oldChildNode = oldChildWrapper;
+ oldChildWrapper = wrap(oldChildNode);
+ }
+
+ if (oldChildWrapper.parentNode !== this) {
+ // TODO(arv): DOMException
+ throw new Error('NotFoundError');
+ }
+
+ var nextNode = oldChildWrapper.nextSibling;
+ var previousNode = oldChildWrapper.previousSibling;
+ var nodes;
+
+ var useNative = !this.invalidateShadowRenderer() &&
+ !invalidateParent(newChildWrapper);
+
+ if (useNative) {
+ nodes = collectNodesNative(newChildWrapper);
+ } else {
+ if (nextNode === newChildWrapper)
+ nextNode = newChildWrapper.nextSibling;
+ nodes = collectNodes(newChildWrapper, this, previousNode, nextNode);
+ }
+
+ if (!useNative) {
+ if (this.firstChild === oldChildWrapper)
+ this.firstChild_ = nodes[0];
+ if (this.lastChild === oldChildWrapper)
+ this.lastChild_ = nodes[nodes.length - 1];
+
+ oldChildWrapper.previousSibling_ = oldChildWrapper.nextSibling_ =
+ oldChildWrapper.parentNode_ = undefined;
+
+ // replaceChild no matter what the parent is?
+ if (oldChildNode.parentNode) {
+ originalReplaceChild.call(
+ oldChildNode.parentNode,
+ unwrapNodesForInsertion(this, nodes),
+ oldChildNode);
+ }
+ } else {
+ ensureSameOwnerDocument(this, newChildWrapper);
+ clearChildNodes(this);
+ originalReplaceChild.call(this.impl, unwrap(newChildWrapper),
+ oldChildNode);
+ }
+
+ enqueueMutation(this, 'childList', {
+ addedNodes: nodes,
+ removedNodes: createOneElementNodeList(oldChildWrapper),
+ nextSibling: nextNode,
+ previousSibling: previousNode
+ });
+
+ nodeWasRemoved(oldChildWrapper);
+ nodesWereAdded(nodes);
+
+ return oldChildWrapper;
+ },
+
+ /**
+ * Called after a node was inserted. Subclasses override this to invalidate
+ * the renderer as needed.
+ * @private
+ */
+ nodeIsInserted_: function() {
+ for (var child = this.firstChild; child; child = child.nextSibling) {
+ child.nodeIsInserted_();
+ }
+ },
+
+ hasChildNodes: function() {
+ return this.firstChild !== null;
+ },
+
+ /** @type {Node} */
+ get parentNode() {
+ // If the parentNode has not been overridden, use the original parentNode.
+ return this.parentNode_ !== undefined ?
+ this.parentNode_ : wrap(this.impl.parentNode);
+ },
+
+ /** @type {Node} */
+ get firstChild() {
+ return this.firstChild_ !== undefined ?
+ this.firstChild_ : wrap(this.impl.firstChild);
+ },
+
+ /** @type {Node} */
+ get lastChild() {
+ return this.lastChild_ !== undefined ?
+ this.lastChild_ : wrap(this.impl.lastChild);
+ },
+
+ /** @type {Node} */
+ get nextSibling() {
+ return this.nextSibling_ !== undefined ?
+ this.nextSibling_ : wrap(this.impl.nextSibling);
+ },
+
+ /** @type {Node} */
+ get previousSibling() {
+ return this.previousSibling_ !== undefined ?
+ this.previousSibling_ : wrap(this.impl.previousSibling);
+ },
+
+ get parentElement() {
+ var p = this.parentNode;
+ while (p && p.nodeType !== Node.ELEMENT_NODE) {
+ p = p.parentNode;
+ }
+ return p;
+ },
+
+ get textContent() {
+ // TODO(arv): This should fallback to this.impl.textContent if there
+ // are no shadow trees below or above the context node.
+ var s = '';
+ for (var child = this.firstChild; child; child = child.nextSibling) {
+ if (child.nodeType != Node.COMMENT_NODE) {
+ s += child.textContent;
+ }
+ }
+ return s;
+ },
+ set textContent(textContent) {
+ var removedNodes = snapshotNodeList(this.childNodes);
+
+ if (this.invalidateShadowRenderer()) {
+ removeAllChildNodes(this);
+ if (textContent !== '') {
+ var textNode = this.impl.ownerDocument.createTextNode(textContent);
+ this.appendChild(textNode);
+ }
+ } else {
+ clearChildNodes(this);
+ this.impl.textContent = textContent;
+ }
+
+ var addedNodes = snapshotNodeList(this.childNodes);
+
+ enqueueMutation(this, 'childList', {
+ addedNodes: addedNodes,
+ removedNodes: removedNodes
+ });
+
+ nodesWereRemoved(removedNodes);
+ nodesWereAdded(addedNodes);
+ },
+
+ get childNodes() {
+ var wrapperList = new NodeList();
+ var i = 0;
+ for (var child = this.firstChild; child; child = child.nextSibling) {
+ wrapperList[i++] = child;
+ }
+ wrapperList.length = i;
+ return wrapperList;
+ },
+
+ cloneNode: function(deep) {
+ return cloneNode(this, deep);
+ },
+
+ contains: function(child) {
+ if (!child)
+ return false;
+
+ child = wrapIfNeeded(child);
+
+ // TODO(arv): Optimize using ownerDocument etc.
+ if (child === this)
+ return true;
+ var parentNode = child.parentNode;
+ if (!parentNode)
+ return false;
+ return this.contains(parentNode);
+ },
+
+ compareDocumentPosition: function(otherNode) {
+ // This only wraps, it therefore only operates on the composed DOM and not
+ // the logical DOM.
+ return originalCompareDocumentPosition.call(this.impl, unwrap(otherNode));
+ },
+
+ normalize: function() {
+ var nodes = snapshotNodeList(this.childNodes);
+ var remNodes = [];
+ var s = '';
+ var modNode;
+
+ for (var i = 0, n; i < nodes.length; i++) {
+ n = nodes[i];
+ if (n.nodeType === Node.TEXT_NODE) {
+ if (!modNode && !n.data.length)
+ this.removeNode(n);
+ else if (!modNode)
+ modNode = n;
+ else {
+ s += n.data;
+ remNodes.push(n);
+ }
+ } else {
+ if (modNode && remNodes.length) {
+ modNode.data += s;
+ cleanUpNodes(remNodes);
+ }
+ remNodes = [];
+ s = '';
+ modNode = null;
+ if (n.childNodes.length)
+ n.normalize();
+ }
+ }
+
+ // handle case where >1 text nodes are the last children
+ if (modNode && remNodes.length) {
+ modNode.data += s;
+ cleanupNodes(remNodes);
+ }
+ }
+ });
+
+ defineWrapGetter(Node, 'ownerDocument');
+
+ // We use a DocumentFragment as a base and then delete the properties of
+ // DocumentFragment.prototype from the wrapper Node. Since delete makes
+ // objects slow in some JS engines we recreate the prototype object.
+ registerWrapper(OriginalNode, Node, document.createDocumentFragment());
+ delete Node.prototype.querySelector;
+ delete Node.prototype.querySelectorAll;
+ Node.prototype = mixin(Object.create(EventTarget.prototype), Node.prototype);
+
+ scope.nodeWasAdded = nodeWasAdded;
+ scope.nodeWasRemoved = nodeWasRemoved;
+ scope.nodesWereAdded = nodesWereAdded;
+ scope.nodesWereRemoved = nodesWereRemoved;
+ scope.snapshotNodeList = snapshotNodeList;
+ scope.wrappers.Node = Node;
+ scope.cloneNode = cloneNode;
+
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ function findOne(node, selector) {
+ var m, el = node.firstElementChild;
+ while (el) {
+ if (el.matches(selector))
+ return el;
+ m = findOne(el, selector);
+ if (m)
+ return m;
+ el = el.nextElementSibling;
+ }
+ return null;
+ }
+
+ function findAll(node, selector, results) {
+ var el = node.firstElementChild;
+ while (el) {
+ if (el.matches(selector))
+ results[results.length++] = el;
+ findAll(el, selector, results);
+ el = el.nextElementSibling;
+ }
+ return results;
+ }
+
+ // find and findAll will only match Simple Selectors,
+ // Structural Pseudo Classes are not guarenteed to be correct
+ // http://www.w3.org/TR/css3-selectors/#simple-selectors
+
+ var SelectorsInterface = {
+ querySelector: function(selector) {
+ return findOne(this, selector);
+ },
+ querySelectorAll: function(selector) {
+ return findAll(this, selector, new NodeList())
+ }
+ };
+
+ var GetElementsByInterface = {
+ getElementsByTagName: function(tagName) {
+ // TODO(arv): Check tagName?
+ return this.querySelectorAll(tagName);
+ },
+ getElementsByClassName: function(className) {
+ // TODO(arv): Check className?
+ return this.querySelectorAll('.' + className);
+ },
+ getElementsByTagNameNS: function(ns, tagName) {
+ if (ns === '*')
+ return this.getElementsByTagName(tagName);
+
+ // TODO(arv): Check tagName?
+ var result = new NodeList;
+ var els = this.getElementsByTagName(tagName);
+ for (var i = 0, j = 0; i < els.length; i++) {
+ if (els[i].namespaceURI === ns)
+ result[j++] = els[i];
+ }
+ result.length = j;
+ return result;
+ }
+ };
+
+ scope.GetElementsByInterface = GetElementsByInterface;
+ scope.SelectorsInterface = SelectorsInterface;
+
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var NodeList = scope.wrappers.NodeList;
+
+ function forwardElement(node) {
+ while (node && node.nodeType !== Node.ELEMENT_NODE) {
+ node = node.nextSibling;
+ }
+ return node;
+ }
+
+ function backwardsElement(node) {
+ while (node && node.nodeType !== Node.ELEMENT_NODE) {
+ node = node.previousSibling;
+ }
+ return node;
+ }
+
+ var ParentNodeInterface = {
+ get firstElementChild() {
+ return forwardElement(this.firstChild);
+ },
+
+ get lastElementChild() {
+ return backwardsElement(this.lastChild);
+ },
+
+ get childElementCount() {
+ var count = 0;
+ for (var child = this.firstElementChild;
+ child;
+ child = child.nextElementSibling) {
+ count++;
+ }
+ return count;
+ },
+
+ get children() {
+ var wrapperList = new NodeList();
+ var i = 0;
+ for (var child = this.firstElementChild;
+ child;
+ child = child.nextElementSibling) {
+ wrapperList[i++] = child;
+ }
+ wrapperList.length = i;
+ return wrapperList;
+ }
+ };
+
+ var ChildNodeInterface = {
+ get nextElementSibling() {
+ return forwardElement(this.nextSibling);
+ },
+
+ get previousElementSibling() {
+ return backwardsElement(this.previousSibling);
+ }
+ };
+
+ scope.ChildNodeInterface = ChildNodeInterface;
+ scope.ParentNodeInterface = ParentNodeInterface;
+
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var ChildNodeInterface = scope.ChildNodeInterface;
+ var Node = scope.wrappers.Node;
+ var enqueueMutation = scope.enqueueMutation;
+ var mixin = scope.mixin;
+ var registerWrapper = scope.registerWrapper;
+
+ var OriginalCharacterData = window.CharacterData;
+
+ function CharacterData(node) {
+ Node.call(this, node);
+ }
+ CharacterData.prototype = Object.create(Node.prototype);
+ mixin(CharacterData.prototype, {
+ get textContent() {
+ return this.data;
+ },
+ set textContent(value) {
+ this.data = value;
+ },
+ get data() {
+ return this.impl.data;
+ },
+ set data(value) {
+ var oldValue = this.impl.data;
+ enqueueMutation(this, 'characterData', {
+ oldValue: oldValue
+ });
+ this.impl.data = value;
+ }
+ });
+
+ mixin(CharacterData.prototype, ChildNodeInterface);
+
+ registerWrapper(OriginalCharacterData, CharacterData,
+ document.createTextNode(''));
+
+ scope.wrappers.CharacterData = CharacterData;
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2014 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var CharacterData = scope.wrappers.CharacterData;
+ var enqueueMutation = scope.enqueueMutation;
+ var mixin = scope.mixin;
+ var registerWrapper = scope.registerWrapper;
+
+ function toUInt32(x) {
+ return x >>> 0;
+ }
+
+ var OriginalText = window.Text;
+
+ function Text(node) {
+ CharacterData.call(this, node);
+ }
+ Text.prototype = Object.create(CharacterData.prototype);
+ mixin(Text.prototype, {
+ splitText: function(offset) {
+ offset = toUInt32(offset);
+ var s = this.data;
+ if (offset > s.length)
+ throw new Error('IndexSizeError');
+ var head = s.slice(0, offset);
+ var tail = s.slice(offset);
+ this.data = head;
+ var newTextNode = this.ownerDocument.createTextNode(tail);
+ if (this.parentNode)
+ this.parentNode.insertBefore(newTextNode, this.nextSibling);
+ return newTextNode;
+ }
+ });
+
+ registerWrapper(OriginalText, Text, document.createTextNode(''));
+
+ scope.wrappers.Text = Text;
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var ChildNodeInterface = scope.ChildNodeInterface;
+ var GetElementsByInterface = scope.GetElementsByInterface;
+ var Node = scope.wrappers.Node;
+ var ParentNodeInterface = scope.ParentNodeInterface;
+ var SelectorsInterface = scope.SelectorsInterface;
+ var addWrapNodeListMethod = scope.addWrapNodeListMethod;
+ var enqueueMutation = scope.enqueueMutation;
+ var mixin = scope.mixin;
+ var oneOf = scope.oneOf;
+ var registerWrapper = scope.registerWrapper;
+ var wrappers = scope.wrappers;
+
+ var OriginalElement = window.Element;
+
+ var matchesNames = [
+ 'matches', // needs to come first.
+ 'mozMatchesSelector',
+ 'msMatchesSelector',
+ 'webkitMatchesSelector',
+ ].filter(function(name) {
+ return OriginalElement.prototype[name];
+ });
+
+ var matchesName = matchesNames[0];
+
+ var originalMatches = OriginalElement.prototype[matchesName];
+
+ function invalidateRendererBasedOnAttribute(element, name) {
+ // Only invalidate if parent node is a shadow host.
+ var p = element.parentNode;
+ if (!p || !p.shadowRoot)
+ return;
+
+ var renderer = scope.getRendererForHost(p);
+ if (renderer.dependsOnAttribute(name))
+ renderer.invalidate();
+ }
+
+ function enqueAttributeChange(element, name, oldValue) {
+ // This is not fully spec compliant. We should use localName (which might
+ // have a different case than name) and the namespace (which requires us
+ // to get the Attr object).
+ enqueueMutation(element, 'attributes', {
+ name: name,
+ namespace: null,
+ oldValue: oldValue
+ });
+ }
+
+ function Element(node) {
+ Node.call(this, node);
+ }
+ Element.prototype = Object.create(Node.prototype);
+ mixin(Element.prototype, {
+ createShadowRoot: function() {
+ var newShadowRoot = new wrappers.ShadowRoot(this);
+ this.impl.polymerShadowRoot_ = newShadowRoot;
+
+ var renderer = scope.getRendererForHost(this);
+ renderer.invalidate();
+
+ return newShadowRoot;
+ },
+
+ get shadowRoot() {
+ return this.impl.polymerShadowRoot_ || null;
+ },
+
+ setAttribute: function(name, value) {
+ var oldValue = this.impl.getAttribute(name);
+ this.impl.setAttribute(name, value);
+ enqueAttributeChange(this, name, oldValue);
+ invalidateRendererBasedOnAttribute(this, name);
+ },
+
+ removeAttribute: function(name) {
+ var oldValue = this.impl.getAttribute(name);
+ this.impl.removeAttribute(name);
+ enqueAttributeChange(this, name, oldValue);
+ invalidateRendererBasedOnAttribute(this, name);
+ },
+
+ matches: function(selector) {
+ return originalMatches.call(this.impl, selector);
+ }
+ });
+
+ matchesNames.forEach(function(name) {
+ if (name !== 'matches') {
+ Element.prototype[name] = function(selector) {
+ return this.matches(selector);
+ };
+ }
+ });
+
+ if (OriginalElement.prototype.webkitCreateShadowRoot) {
+ Element.prototype.webkitCreateShadowRoot =
+ Element.prototype.createShadowRoot;
+ }
+
+ /**
+ * Useful for generating the accessor pair for a property that reflects an
+ * attribute.
+ */
+ function setterDirtiesAttribute(prototype, propertyName, opt_attrName) {
+ var attrName = opt_attrName || propertyName;
+ Object.defineProperty(prototype, propertyName, {
+ get: function() {
+ return this.impl[propertyName];
+ },
+ set: function(v) {
+ this.impl[propertyName] = v;
+ invalidateRendererBasedOnAttribute(this, attrName);
+ },
+ configurable: true,
+ enumerable: true
+ });
+ }
+
+ setterDirtiesAttribute(Element.prototype, 'id');
+ setterDirtiesAttribute(Element.prototype, 'className', 'class');
+
+ mixin(Element.prototype, ChildNodeInterface);
+ mixin(Element.prototype, GetElementsByInterface);
+ mixin(Element.prototype, ParentNodeInterface);
+ mixin(Element.prototype, SelectorsInterface);
+
+ registerWrapper(OriginalElement, Element,
+ document.createElementNS(null, 'x'));
+
+ // TODO(arv): Export setterDirtiesAttribute and apply it to more bindings
+ // that reflect attributes.
+ scope.matchesNames = matchesNames;
+ scope.wrappers.Element = Element;
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var Element = scope.wrappers.Element;
+ var defineGetter = scope.defineGetter;
+ var enqueueMutation = scope.enqueueMutation;
+ var mixin = scope.mixin;
+ var nodesWereAdded = scope.nodesWereAdded;
+ var nodesWereRemoved = scope.nodesWereRemoved;
+ var registerWrapper = scope.registerWrapper;
+ var snapshotNodeList = scope.snapshotNodeList;
+ var unwrap = scope.unwrap;
+ var wrap = scope.wrap;
+ var wrappers = scope.wrappers;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // innerHTML and outerHTML
+
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#escapingString
+ var escapeAttrRegExp = /[&\u00A0"]/g;
+ var escapeDataRegExp = /[&\u00A0<>]/g;
+
+ function escapeReplace(c) {
+ switch (c) {
+ case '&':
+ return '&amp;';
+ case '<':
+ return '&lt;';
+ case '>':
+ return '&gt;';
+ case '"':
+ return '&quot;'
+ case '\u00A0':
+ return '&nbsp;';
+ }
+ }
+
+ function escapeAttr(s) {
+ return s.replace(escapeAttrRegExp, escapeReplace);
+ }
+
+ function escapeData(s) {
+ return s.replace(escapeDataRegExp, escapeReplace);
+ }
+
+ function makeSet(arr) {
+ var set = {};
+ for (var i = 0; i < arr.length; i++) {
+ set[arr[i]] = true;
+ }
+ return set;
+ }
+
+ // http://www.whatwg.org/specs/web-apps/current-work/#void-elements
+ var voidElements = makeSet([
+ 'area',
+ 'base',
+ 'br',
+ 'col',
+ 'command',
+ 'embed',
+ 'hr',
+ 'img',
+ 'input',
+ 'keygen',
+ 'link',
+ 'meta',
+ 'param',
+ 'source',
+ 'track',
+ 'wbr'
+ ]);
+
+ var plaintextParents = makeSet([
+ 'style',
+ 'script',
+ 'xmp',
+ 'iframe',
+ 'noembed',
+ 'noframes',
+ 'plaintext',
+ 'noscript'
+ ]);
+
+ function getOuterHTML(node, parentNode) {
+ switch (node.nodeType) {
+ case Node.ELEMENT_NODE:
+ var tagName = node.tagName.toLowerCase();
+ var s = '<' + tagName;
+ var attrs = node.attributes;
+ for (var i = 0, attr; attr = attrs[i]; i++) {
+ s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"';
+ }
+ s += '>';
+ if (voidElements[tagName])
+ return s;
+
+ return s + getInnerHTML(node) + '</' + tagName + '>';
+
+ case Node.TEXT_NODE:
+ var data = node.data;
+ if (parentNode && plaintextParents[parentNode.localName])
+ return data;
+ return escapeData(data);
+
+ case Node.COMMENT_NODE:
+ return '<!--' + node.data + '-->';
+
+ default:
+ console.error(node);
+ throw new Error('not implemented');
+ }
+ }
+
+ function getInnerHTML(node) {
+ if (node instanceof wrappers.HTMLTemplateElement)
+ node = node.content;
+
+ var s = '';
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ s += getOuterHTML(child, node);
+ }
+ return s;
+ }
+
+ function setInnerHTML(node, value, opt_tagName) {
+ var tagName = opt_tagName || 'div';
+ node.textContent = '';
+ var tempElement = unwrap(node.ownerDocument.createElement(tagName));
+ tempElement.innerHTML = value;
+ var firstChild;
+ while (firstChild = tempElement.firstChild) {
+ node.appendChild(wrap(firstChild));
+ }
+ }
+
+ // IE11 does not have MSIE in the user agent string.
+ var oldIe = /MSIE/.test(navigator.userAgent);
+
+ var OriginalHTMLElement = window.HTMLElement;
+ var OriginalHTMLTemplateElement = window.HTMLTemplateElement;
+
+ function HTMLElement(node) {
+ Element.call(this, node);
+ }
+ HTMLElement.prototype = Object.create(Element.prototype);
+ mixin(HTMLElement.prototype, {
+ get innerHTML() {
+ return getInnerHTML(this);
+ },
+ set innerHTML(value) {
+ // IE9 does not handle set innerHTML correctly on plaintextParents. It
+ // creates element children. For example
+ //
+ // scriptElement.innerHTML = '<a>test</a>'
+ //
+ // Creates a single HTMLAnchorElement child.
+ if (oldIe && plaintextParents[this.localName]) {
+ this.textContent = value;
+ return;
+ }
+
+ var removedNodes = snapshotNodeList(this.childNodes);
+
+ if (this.invalidateShadowRenderer()) {
+ if (this instanceof wrappers.HTMLTemplateElement)
+ setInnerHTML(this.content, value);
+ else
+ setInnerHTML(this, value, this.tagName);
+
+ // If we have a non native template element we need to handle this
+ // manually since setting impl.innerHTML would add the html as direct
+ // children and not be moved over to the content fragment.
+ } else if (!OriginalHTMLTemplateElement &&
+ this instanceof wrappers.HTMLTemplateElement) {
+ setInnerHTML(this.content, value);
+ } else {
+ this.impl.innerHTML = value;
+ }
+
+ var addedNodes = snapshotNodeList(this.childNodes);
+
+ enqueueMutation(this, 'childList', {
+ addedNodes: addedNodes,
+ removedNodes: removedNodes
+ });
+
+ nodesWereRemoved(removedNodes);
+ nodesWereAdded(addedNodes);
+ },
+
+ get outerHTML() {
+ return getOuterHTML(this, this.parentNode);
+ },
+ set outerHTML(value) {
+ var p = this.parentNode;
+ if (p) {
+ p.invalidateShadowRenderer();
+ var df = frag(p, value);
+ p.replaceChild(df, this);
+ }
+ },
+
+ insertAdjacentHTML: function(position, text) {
+ var contextElement, refNode;
+ switch (String(position).toLowerCase()) {
+ case 'beforebegin':
+ contextElement = this.parentNode;
+ refNode = this;
+ break;
+ case 'afterend':
+ contextElement = this.parentNode;
+ refNode = this.nextSibling;
+ break;
+ case 'afterbegin':
+ contextElement = this;
+ refNode = this.firstChild;
+ break;
+ case 'beforeend':
+ contextElement = this;
+ refNode = null;
+ break;
+ default:
+ return;
+ }
+
+ var df = frag(contextElement, text);
+ contextElement.insertBefore(df, refNode);
+ }
+ });
+
+ function frag(contextElement, html) {
+ // TODO(arv): This does not work with SVG and other non HTML elements.
+ var p = unwrap(contextElement.cloneNode(false));
+ p.innerHTML = html;
+ var df = unwrap(document.createDocumentFragment());
+ var c;
+ while (c = p.firstChild) {
+ df.appendChild(c);
+ }
+ return wrap(df);
+ }
+
+ function getter(name) {
+ return function() {
+ scope.renderAllPending();
+ return this.impl[name];
+ };
+ }
+
+ function getterRequiresRendering(name) {
+ defineGetter(HTMLElement, name, getter(name));
+ }
+
+ [
+ 'clientHeight',
+ 'clientLeft',
+ 'clientTop',
+ 'clientWidth',
+ 'offsetHeight',
+ 'offsetLeft',
+ 'offsetTop',
+ 'offsetWidth',
+ 'scrollHeight',
+ 'scrollWidth',
+ ].forEach(getterRequiresRendering);
+
+ function getterAndSetterRequiresRendering(name) {
+ Object.defineProperty(HTMLElement.prototype, name, {
+ get: getter(name),
+ set: function(v) {
+ scope.renderAllPending();
+ this.impl[name] = v;
+ },
+ configurable: true,
+ enumerable: true
+ });
+ }
+
+ [
+ 'scrollLeft',
+ 'scrollTop',
+ ].forEach(getterAndSetterRequiresRendering);
+
+ function methodRequiresRendering(name) {
+ Object.defineProperty(HTMLElement.prototype, name, {
+ value: function() {
+ scope.renderAllPending();
+ return this.impl[name].apply(this.impl, arguments);
+ },
+ configurable: true,
+ enumerable: true
+ });
+ }
+
+ [
+ 'getBoundingClientRect',
+ 'getClientRects',
+ 'scrollIntoView'
+ ].forEach(methodRequiresRendering);
+
+ // HTMLElement is abstract so we use a subclass that has no members.
+ registerWrapper(OriginalHTMLElement, HTMLElement,
+ document.createElement('b'));
+
+ scope.wrappers.HTMLElement = HTMLElement;
+
+ // TODO: Find a better way to share these two with WrapperShadowRoot.
+ scope.getInnerHTML = getInnerHTML;
+ scope.setInnerHTML = setInnerHTML
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var HTMLElement = scope.wrappers.HTMLElement;
+ var mixin = scope.mixin;
+ var registerWrapper = scope.registerWrapper;
+ var wrap = scope.wrap;
+
+ var OriginalHTMLCanvasElement = window.HTMLCanvasElement;
+
+ function HTMLCanvasElement(node) {
+ HTMLElement.call(this, node);
+ }
+ HTMLCanvasElement.prototype = Object.create(HTMLElement.prototype);
+
+ mixin(HTMLCanvasElement.prototype, {
+ getContext: function() {
+ var context = this.impl.getContext.apply(this.impl, arguments);
+ return context && wrap(context);
+ }
+ });
+
+ registerWrapper(OriginalHTMLCanvasElement, HTMLCanvasElement,
+ document.createElement('canvas'));
+
+ scope.wrappers.HTMLCanvasElement = HTMLCanvasElement;
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var HTMLElement = scope.wrappers.HTMLElement;
+ var mixin = scope.mixin;
+ var registerWrapper = scope.registerWrapper;
+
+ var OriginalHTMLContentElement = window.HTMLContentElement;
+
+ function HTMLContentElement(node) {
+ HTMLElement.call(this, node);
+ }
+ HTMLContentElement.prototype = Object.create(HTMLElement.prototype);
+ mixin(HTMLContentElement.prototype, {
+ get select() {
+ return this.getAttribute('select');
+ },
+ set select(value) {
+ this.setAttribute('select', value);
+ },
+
+ setAttribute: function(n, v) {
+ HTMLElement.prototype.setAttribute.call(this, n, v);
+ if (String(n).toLowerCase() === 'select')
+ this.invalidateShadowRenderer(true);
+ }
+
+ // getDistributedNodes is added in ShadowRenderer
+
+ // TODO: attribute boolean resetStyleInheritance;
+ });
+
+ if (OriginalHTMLContentElement)
+ registerWrapper(OriginalHTMLContentElement, HTMLContentElement);
+
+ scope.wrappers.HTMLContentElement = HTMLContentElement;
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var HTMLElement = scope.wrappers.HTMLElement;
+ var registerWrapper = scope.registerWrapper;
+ var unwrap = scope.unwrap;
+ var rewrap = scope.rewrap;
+
+ var OriginalHTMLImageElement = window.HTMLImageElement;
+
+ function HTMLImageElement(node) {
+ HTMLElement.call(this, node);
+ }
+ HTMLImageElement.prototype = Object.create(HTMLElement.prototype);
+
+ registerWrapper(OriginalHTMLImageElement, HTMLImageElement,
+ document.createElement('img'));
+
+ function Image(width, height) {
+ if (!(this instanceof Image)) {
+ throw new TypeError(
+ 'DOM object constructor cannot be called as a function.');
+ }
+
+ var node = unwrap(document.createElement('img'));
+ HTMLElement.call(this, node);
+ rewrap(node, this);
+
+ if (width !== undefined)
+ node.width = width;
+ if (height !== undefined)
+ node.height = height;
+ }
+
+ Image.prototype = HTMLImageElement.prototype;
+
+ scope.wrappers.HTMLImageElement = HTMLImageElement;
+ scope.wrappers.Image = Image;
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var HTMLElement = scope.wrappers.HTMLElement;
+ var mixin = scope.mixin;
+ var registerWrapper = scope.registerWrapper;
+
+ var OriginalHTMLShadowElement = window.HTMLShadowElement;
+
+ function HTMLShadowElement(node) {
+ HTMLElement.call(this, node);
+ }
+ HTMLShadowElement.prototype = Object.create(HTMLElement.prototype);
+ mixin(HTMLShadowElement.prototype, {
+ // TODO: attribute boolean resetStyleInheritance;
+ });
+
+ if (OriginalHTMLShadowElement)
+ registerWrapper(OriginalHTMLShadowElement, HTMLShadowElement);
+
+ scope.wrappers.HTMLShadowElement = HTMLShadowElement;
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var HTMLElement = scope.wrappers.HTMLElement;
+ var mixin = scope.mixin;
+ var registerWrapper = scope.registerWrapper;
+ var unwrap = scope.unwrap;
+ var wrap = scope.wrap;
+
+ var contentTable = new WeakMap();
+ var templateContentsOwnerTable = new WeakMap();
+
+ // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#dfn-template-contents-owner
+ function getTemplateContentsOwner(doc) {
+ if (!doc.defaultView)
+ return doc;
+ var d = templateContentsOwnerTable.get(doc);
+ 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);
+ }
+ templateContentsOwnerTable.set(doc, d);
+ }
+ return d;
+ }
+
+ function extractContent(templateElement) {
+ // templateElement is not a wrapper here.
+ var doc = getTemplateContentsOwner(templateElement.ownerDocument);
+ var df = unwrap(doc.createDocumentFragment());
+ var child;
+ while (child = templateElement.firstChild) {
+ df.appendChild(child);
+ }
+ return df;
+ }
+
+ var OriginalHTMLTemplateElement = window.HTMLTemplateElement;
+
+ function HTMLTemplateElement(node) {
+ HTMLElement.call(this, node);
+ if (!OriginalHTMLTemplateElement) {
+ var content = extractContent(node);
+ contentTable.set(this, wrap(content));
+ }
+ }
+ HTMLTemplateElement.prototype = Object.create(HTMLElement.prototype);
+
+ mixin(HTMLTemplateElement.prototype, {
+ get content() {
+ if (OriginalHTMLTemplateElement)
+ return wrap(this.impl.content);
+ return contentTable.get(this);
+ },
+
+ // TODO(arv): cloneNode needs to clone content.
+
+ });
+
+ if (OriginalHTMLTemplateElement)
+ registerWrapper(OriginalHTMLTemplateElement, HTMLTemplateElement);
+
+ scope.wrappers.HTMLTemplateElement = HTMLTemplateElement;
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var HTMLElement = scope.wrappers.HTMLElement;
+ var registerWrapper = scope.registerWrapper;
+
+ var OriginalHTMLMediaElement = window.HTMLMediaElement;
+
+ function HTMLMediaElement(node) {
+ HTMLElement.call(this, node);
+ }
+ HTMLMediaElement.prototype = Object.create(HTMLElement.prototype);
+
+ registerWrapper(OriginalHTMLMediaElement, HTMLMediaElement,
+ document.createElement('audio'));
+
+ scope.wrappers.HTMLMediaElement = HTMLMediaElement;
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var HTMLMediaElement = scope.wrappers.HTMLMediaElement;
+ var registerWrapper = scope.registerWrapper;
+ var unwrap = scope.unwrap;
+ var rewrap = scope.rewrap;
+
+ var OriginalHTMLAudioElement = window.HTMLAudioElement;
+
+ function HTMLAudioElement(node) {
+ HTMLMediaElement.call(this, node);
+ }
+ HTMLAudioElement.prototype = Object.create(HTMLMediaElement.prototype);
+
+ registerWrapper(OriginalHTMLAudioElement, HTMLAudioElement,
+ document.createElement('audio'));
+
+ function Audio(src) {
+ if (!(this instanceof Audio)) {
+ throw new TypeError(
+ 'DOM object constructor cannot be called as a function.');
+ }
+
+ var node = unwrap(document.createElement('audio'));
+ HTMLMediaElement.call(this, node);
+ rewrap(node, this);
+
+ node.setAttribute('preload', 'auto');
+ if (src !== undefined)
+ node.setAttribute('src', src);
+ }
+
+ Audio.prototype = HTMLAudioElement.prototype;
+
+ scope.wrappers.HTMLAudioElement = HTMLAudioElement;
+ scope.wrappers.Audio = Audio;
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var HTMLElement = scope.wrappers.HTMLElement;
+ var mixin = scope.mixin;
+ var registerWrapper = scope.registerWrapper;
+ var rewrap = scope.rewrap;
+ var unwrap = scope.unwrap;
+ var wrap = scope.wrap;
+
+ var OriginalHTMLOptionElement = window.HTMLOptionElement;
+
+ function trimText(s) {
+ return s.replace(/\s+/g, ' ').trim();
+ }
+
+ function HTMLOptionElement(node) {
+ HTMLElement.call(this, node);
+ }
+ HTMLOptionElement.prototype = Object.create(HTMLElement.prototype);
+ mixin(HTMLOptionElement.prototype, {
+ get text() {
+ return trimText(this.textContent);
+ },
+ set text(value) {
+ this.textContent = trimText(String(value));
+ },
+ get form() {
+ return wrap(unwrap(this).form);
+ }
+ });
+
+ registerWrapper(OriginalHTMLOptionElement, HTMLOptionElement,
+ document.createElement('option'));
+
+ function Option(text, value, defaultSelected, selected) {
+ if (!(this instanceof Option)) {
+ throw new TypeError(
+ 'DOM object constructor cannot be called as a function.');
+ }
+
+ var node = unwrap(document.createElement('option'));
+ HTMLElement.call(this, node);
+ rewrap(node, this);
+
+ if (text !== undefined)
+ node.text = text;
+ if (value !== undefined)
+ node.setAttribute('value', value);
+ if (defaultSelected === true)
+ node.setAttribute('selected', '');
+ node.selected = selected === true;
+ }
+
+ Option.prototype = HTMLOptionElement.prototype;
+
+ scope.wrappers.HTMLOptionElement = HTMLOptionElement;
+ scope.wrappers.Option = Option;
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var HTMLContentElement = scope.wrappers.HTMLContentElement;
+ var HTMLElement = scope.wrappers.HTMLElement;
+ var HTMLShadowElement = scope.wrappers.HTMLShadowElement;
+ var HTMLTemplateElement = scope.wrappers.HTMLTemplateElement;
+ var mixin = scope.mixin;
+ var registerWrapper = scope.registerWrapper;
+
+ var OriginalHTMLUnknownElement = window.HTMLUnknownElement;
+
+ function HTMLUnknownElement(node) {
+ switch (node.localName) {
+ case 'content':
+ return new HTMLContentElement(node);
+ case 'shadow':
+ return new HTMLShadowElement(node);
+ case 'template':
+ return new HTMLTemplateElement(node);
+ }
+ HTMLElement.call(this, node);
+ }
+ HTMLUnknownElement.prototype = Object.create(HTMLElement.prototype);
+ registerWrapper(OriginalHTMLUnknownElement, HTMLUnknownElement);
+ scope.wrappers.HTMLUnknownElement = HTMLUnknownElement;
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2014 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var registerObject = scope.registerObject;
+
+ var SVG_NS = 'http://www.w3.org/2000/svg';
+ var svgTitleElement = document.createElementNS(SVG_NS, 'title');
+ var SVGTitleElement = registerObject(svgTitleElement);
+ var SVGElement = Object.getPrototypeOf(SVGTitleElement.prototype).constructor;
+
+ scope.wrappers.SVGElement = SVGElement;
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2014 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var mixin = scope.mixin;
+ var registerWrapper = scope.registerWrapper;
+ var unwrap = scope.unwrap;
+ var wrap = scope.wrap;
+
+ var OriginalSVGUseElement = window.SVGUseElement;
+
+ // IE uses SVGElement as parent interface, SVG2 (Blink & Gecko) uses
+ // SVGGraphicsElement. Use the <g> element to get the right prototype.
+
+ var SVG_NS = 'http://www.w3.org/2000/svg';
+ var gWrapper = wrap(document.createElementNS(SVG_NS, 'g'));
+ var useElement = document.createElementNS(SVG_NS, 'use');
+ var SVGGElement = gWrapper.constructor;
+ var parentInterfacePrototype = Object.getPrototypeOf(SVGGElement.prototype);
+ var parentInterface = parentInterfacePrototype.constructor;
+
+ function SVGUseElement(impl) {
+ parentInterface.call(this, impl);
+ }
+
+ SVGUseElement.prototype = Object.create(parentInterfacePrototype);
+
+ // Firefox does not expose instanceRoot.
+ if ('instanceRoot' in useElement) {
+ mixin(SVGUseElement.prototype, {
+ get instanceRoot() {
+ return wrap(unwrap(this).instanceRoot);
+ },
+ get animatedInstanceRoot() {
+ return wrap(unwrap(this).animatedInstanceRoot);
+ },
+ });
+ }
+
+ registerWrapper(OriginalSVGUseElement, SVGUseElement, useElement);
+
+ scope.wrappers.SVGUseElement = SVGUseElement;
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2014 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var EventTarget = scope.wrappers.EventTarget;
+ var mixin = scope.mixin;
+ var registerWrapper = scope.registerWrapper;
+ var wrap = scope.wrap;
+
+ var OriginalSVGElementInstance = window.SVGElementInstance;
+ if (!OriginalSVGElementInstance)
+ return;
+
+ function SVGElementInstance(impl) {
+ EventTarget.call(this, impl);
+ }
+
+ SVGElementInstance.prototype = Object.create(EventTarget.prototype);
+ mixin(SVGElementInstance.prototype, {
+ /** @type {SVGElement} */
+ get correspondingElement() {
+ return wrap(this.impl.correspondingElement);
+ },
+
+ /** @type {SVGUseElement} */
+ get correspondingUseElement() {
+ return wrap(this.impl.correspondingUseElement);
+ },
+
+ /** @type {SVGElementInstance} */
+ get parentNode() {
+ return wrap(this.impl.parentNode);
+ },
+
+ /** @type {SVGElementInstanceList} */
+ get childNodes() {
+ throw new Error('Not implemented');
+ },
+
+ /** @type {SVGElementInstance} */
+ get firstChild() {
+ return wrap(this.impl.firstChild);
+ },
+
+ /** @type {SVGElementInstance} */
+ get lastChild() {
+ return wrap(this.impl.lastChild);
+ },
+
+ /** @type {SVGElementInstance} */
+ get previousSibling() {
+ return wrap(this.impl.previousSibling);
+ },
+
+ /** @type {SVGElementInstance} */
+ get nextSibling() {
+ return wrap(this.impl.nextSibling);
+ }
+ });
+
+ registerWrapper(OriginalSVGElementInstance, SVGElementInstance);
+
+ scope.wrappers.SVGElementInstance = SVGElementInstance;
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var mixin = scope.mixin;
+ var registerWrapper = scope.registerWrapper;
+ var unwrap = scope.unwrap;
+ var unwrapIfNeeded = scope.unwrapIfNeeded;
+ var wrap = scope.wrap;
+
+ var OriginalCanvasRenderingContext2D = window.CanvasRenderingContext2D;
+
+ function CanvasRenderingContext2D(impl) {
+ this.impl = impl;
+ }
+
+ mixin(CanvasRenderingContext2D.prototype, {
+ get canvas() {
+ return wrap(this.impl.canvas);
+ },
+
+ drawImage: function() {
+ arguments[0] = unwrapIfNeeded(arguments[0]);
+ this.impl.drawImage.apply(this.impl, arguments);
+ },
+
+ createPattern: function() {
+ arguments[0] = unwrap(arguments[0]);
+ return this.impl.createPattern.apply(this.impl, arguments);
+ }
+ });
+
+ registerWrapper(OriginalCanvasRenderingContext2D, CanvasRenderingContext2D,
+ document.createElement('canvas').getContext('2d'));
+
+ scope.wrappers.CanvasRenderingContext2D = CanvasRenderingContext2D;
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var mixin = scope.mixin;
+ var registerWrapper = scope.registerWrapper;
+ var unwrapIfNeeded = scope.unwrapIfNeeded;
+ var wrap = scope.wrap;
+
+ var OriginalWebGLRenderingContext = window.WebGLRenderingContext;
+
+ // IE10 does not have WebGL.
+ if (!OriginalWebGLRenderingContext)
+ return;
+
+ function WebGLRenderingContext(impl) {
+ this.impl = impl;
+ }
+
+ mixin(WebGLRenderingContext.prototype, {
+ get canvas() {
+ return wrap(this.impl.canvas);
+ },
+
+ texImage2D: function() {
+ arguments[5] = unwrapIfNeeded(arguments[5]);
+ this.impl.texImage2D.apply(this.impl, arguments);
+ },
+
+ texSubImage2D: function() {
+ arguments[6] = unwrapIfNeeded(arguments[6]);
+ this.impl.texSubImage2D.apply(this.impl, arguments);
+ }
+ });
+
+ // Blink/WebKit has broken DOM bindings. Usually we would create an instance
+ // of the object and pass it into registerWrapper as a "blueprint" but
+ // creating WebGL contexts is expensive and might fail so we use a dummy
+ // object with dummy instance properties for these broken browsers.
+ var instanceProperties = /WebKit/.test(navigator.userAgent) ?
+ {drawingBufferHeight: null, drawingBufferWidth: null} : {};
+
+ registerWrapper(OriginalWebGLRenderingContext, WebGLRenderingContext,
+ instanceProperties);
+
+ scope.wrappers.WebGLRenderingContext = WebGLRenderingContext;
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var registerWrapper = scope.registerWrapper;
+ var unwrap = scope.unwrap;
+ var unwrapIfNeeded = scope.unwrapIfNeeded;
+ var wrap = scope.wrap;
+
+ var OriginalRange = window.Range;
+
+ function Range(impl) {
+ this.impl = impl;
+ }
+ Range.prototype = {
+ get startContainer() {
+ return wrap(this.impl.startContainer);
+ },
+ get endContainer() {
+ return wrap(this.impl.endContainer);
+ },
+ get commonAncestorContainer() {
+ return wrap(this.impl.commonAncestorContainer);
+ },
+ setStart: function(refNode,offset) {
+ this.impl.setStart(unwrapIfNeeded(refNode), offset);
+ },
+ setEnd: function(refNode,offset) {
+ this.impl.setEnd(unwrapIfNeeded(refNode), offset);
+ },
+ setStartBefore: function(refNode) {
+ this.impl.setStartBefore(unwrapIfNeeded(refNode));
+ },
+ setStartAfter: function(refNode) {
+ this.impl.setStartAfter(unwrapIfNeeded(refNode));
+ },
+ setEndBefore: function(refNode) {
+ this.impl.setEndBefore(unwrapIfNeeded(refNode));
+ },
+ setEndAfter: function(refNode) {
+ this.impl.setEndAfter(unwrapIfNeeded(refNode));
+ },
+ selectNode: function(refNode) {
+ this.impl.selectNode(unwrapIfNeeded(refNode));
+ },
+ selectNodeContents: function(refNode) {
+ this.impl.selectNodeContents(unwrapIfNeeded(refNode));
+ },
+ compareBoundaryPoints: function(how, sourceRange) {
+ return this.impl.compareBoundaryPoints(how, unwrap(sourceRange));
+ },
+ extractContents: function() {
+ return wrap(this.impl.extractContents());
+ },
+ cloneContents: function() {
+ return wrap(this.impl.cloneContents());
+ },
+ insertNode: function(node) {
+ this.impl.insertNode(unwrapIfNeeded(node));
+ },
+ surroundContents: function(newParent) {
+ this.impl.surroundContents(unwrapIfNeeded(newParent));
+ },
+ cloneRange: function() {
+ return wrap(this.impl.cloneRange());
+ },
+ isPointInRange: function(node, offset) {
+ return this.impl.isPointInRange(unwrapIfNeeded(node), offset);
+ },
+ comparePoint: function(node, offset) {
+ return this.impl.comparePoint(unwrapIfNeeded(node), offset);
+ },
+ intersectsNode: function(node) {
+ return this.impl.intersectsNode(unwrapIfNeeded(node));
+ },
+ toString: function() {
+ return this.impl.toString();
+ }
+ };
+
+ // IE9 does not have createContextualFragment.
+ if (OriginalRange.prototype.createContextualFragment) {
+ Range.prototype.createContextualFragment = function(html) {
+ return wrap(this.impl.createContextualFragment(html));
+ };
+ }
+
+ registerWrapper(window.Range, Range, document.createRange());
+
+ scope.wrappers.Range = Range;
+
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var GetElementsByInterface = scope.GetElementsByInterface;
+ var ParentNodeInterface = scope.ParentNodeInterface;
+ var SelectorsInterface = scope.SelectorsInterface;
+ var mixin = scope.mixin;
+ var registerObject = scope.registerObject;
+
+ var DocumentFragment = registerObject(document.createDocumentFragment());
+ mixin(DocumentFragment.prototype, ParentNodeInterface);
+ mixin(DocumentFragment.prototype, SelectorsInterface);
+ mixin(DocumentFragment.prototype, GetElementsByInterface);
+
+ var Comment = registerObject(document.createComment(''));
+
+ scope.wrappers.Comment = Comment;
+ scope.wrappers.DocumentFragment = DocumentFragment;
+
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var DocumentFragment = scope.wrappers.DocumentFragment;
+ var elementFromPoint = scope.elementFromPoint;
+ var getInnerHTML = scope.getInnerHTML;
+ var mixin = scope.mixin;
+ var rewrap = scope.rewrap;
+ var setInnerHTML = scope.setInnerHTML;
+ var unwrap = scope.unwrap;
+
+ var shadowHostTable = new WeakMap();
+ var nextOlderShadowTreeTable = new WeakMap();
+
+ var spaceCharRe = /[ \t\n\r\f]/;
+
+ function ShadowRoot(hostWrapper) {
+ var node = unwrap(hostWrapper.impl.ownerDocument.createDocumentFragment());
+ DocumentFragment.call(this, node);
+
+ // createDocumentFragment associates the node with a wrapper
+ // DocumentFragment instance. Override that.
+ rewrap(node, this);
+
+ var oldShadowRoot = hostWrapper.shadowRoot;
+ nextOlderShadowTreeTable.set(this, oldShadowRoot);
+
+ shadowHostTable.set(this, hostWrapper);
+ }
+ ShadowRoot.prototype = Object.create(DocumentFragment.prototype);
+ mixin(ShadowRoot.prototype, {
+ get innerHTML() {
+ return getInnerHTML(this);
+ },
+ set innerHTML(value) {
+ setInnerHTML(this, value);
+ this.invalidateShadowRenderer();
+ },
+
+ get olderShadowRoot() {
+ return nextOlderShadowTreeTable.get(this) || null;
+ },
+
+ get host() {
+ return shadowHostTable.get(this) || null;
+ },
+
+ invalidateShadowRenderer: function() {
+ return shadowHostTable.get(this).invalidateShadowRenderer();
+ },
+
+ elementFromPoint: function(x, y) {
+ return elementFromPoint(this, this.ownerDocument, x, y);
+ },
+
+ getElementById: function(id) {
+ if (spaceCharRe.test(id))
+ return null;
+ return this.querySelector('[id="' + id + '"]');
+ }
+ });
+
+ scope.wrappers.ShadowRoot = ShadowRoot;
+
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var Element = scope.wrappers.Element;
+ var HTMLContentElement = scope.wrappers.HTMLContentElement;
+ var HTMLShadowElement = scope.wrappers.HTMLShadowElement;
+ var Node = scope.wrappers.Node;
+ var ShadowRoot = scope.wrappers.ShadowRoot;
+ var assert = scope.assert;
+ var mixin = scope.mixin;
+ var oneOf = scope.oneOf;
+ var unwrap = scope.unwrap;
+ var wrap = scope.wrap;
+
+ /**
+ * Updates the fields of a wrapper to a snapshot of the logical DOM as needed.
+ * Up means parentNode
+ * Sideways means previous and next sibling.
+ * @param {!Node} wrapper
+ */
+ function updateWrapperUpAndSideways(wrapper) {
+ wrapper.previousSibling_ = wrapper.previousSibling;
+ wrapper.nextSibling_ = wrapper.nextSibling;
+ wrapper.parentNode_ = wrapper.parentNode;
+ }
+
+ /**
+ * Updates the fields of a wrapper to a snapshot of the logical DOM as needed.
+ * Down means first and last child
+ * @param {!Node} wrapper
+ */
+ function updateWrapperDown(wrapper) {
+ wrapper.firstChild_ = wrapper.firstChild;
+ wrapper.lastChild_ = wrapper.lastChild;
+ }
+
+ function updateAllChildNodes(parentNodeWrapper) {
+ assert(parentNodeWrapper instanceof Node);
+ for (var childWrapper = parentNodeWrapper.firstChild;
+ childWrapper;
+ childWrapper = childWrapper.nextSibling) {
+ updateWrapperUpAndSideways(childWrapper);
+ }
+ updateWrapperDown(parentNodeWrapper);
+ }
+
+ function insertBefore(parentNodeWrapper, newChildWrapper, refChildWrapper) {
+ var parentNode = unwrap(parentNodeWrapper);
+ var newChild = unwrap(newChildWrapper);
+ var refChild = refChildWrapper ? unwrap(refChildWrapper) : null;
+
+ remove(newChildWrapper);
+ updateWrapperUpAndSideways(newChildWrapper);
+
+ if (!refChildWrapper) {
+ parentNodeWrapper.lastChild_ = parentNodeWrapper.lastChild;
+ if (parentNodeWrapper.lastChild === parentNodeWrapper.firstChild)
+ parentNodeWrapper.firstChild_ = parentNodeWrapper.firstChild;
+
+ var lastChildWrapper = wrap(parentNode.lastChild);
+ if (lastChildWrapper)
+ lastChildWrapper.nextSibling_ = lastChildWrapper.nextSibling;
+ } else {
+ if (parentNodeWrapper.firstChild === refChildWrapper)
+ parentNodeWrapper.firstChild_ = refChildWrapper;
+
+ refChildWrapper.previousSibling_ = refChildWrapper.previousSibling;
+ }
+
+ parentNode.insertBefore(newChild, refChild);
+ }
+
+ function remove(nodeWrapper) {
+ var node = unwrap(nodeWrapper)
+ var parentNode = node.parentNode;
+ if (!parentNode)
+ return;
+
+ var parentNodeWrapper = wrap(parentNode);
+ updateWrapperUpAndSideways(nodeWrapper);
+
+ if (nodeWrapper.previousSibling)
+ nodeWrapper.previousSibling.nextSibling_ = nodeWrapper;
+ if (nodeWrapper.nextSibling)
+ nodeWrapper.nextSibling.previousSibling_ = nodeWrapper;
+
+ if (parentNodeWrapper.lastChild === nodeWrapper)
+ parentNodeWrapper.lastChild_ = nodeWrapper;
+ if (parentNodeWrapper.firstChild === nodeWrapper)
+ parentNodeWrapper.firstChild_ = nodeWrapper;
+
+ parentNode.removeChild(node);
+ }
+
+ var distributedChildNodesTable = new WeakMap();
+ var eventParentsTable = new WeakMap();
+ var insertionParentTable = new WeakMap();
+ var rendererForHostTable = new WeakMap();
+
+ function distributeChildToInsertionPoint(child, insertionPoint) {
+ getDistributedChildNodes(insertionPoint).push(child);
+ assignToInsertionPoint(child, insertionPoint);
+
+ var eventParents = eventParentsTable.get(child);
+ if (!eventParents)
+ eventParentsTable.set(child, eventParents = []);
+ eventParents.push(insertionPoint);
+ }
+
+ function resetDistributedChildNodes(insertionPoint) {
+ distributedChildNodesTable.set(insertionPoint, []);
+ }
+
+ function getDistributedChildNodes(insertionPoint) {
+ return distributedChildNodesTable.get(insertionPoint);
+ }
+
+ function getChildNodesSnapshot(node) {
+ var result = [], i = 0;
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ result[i++] = child;
+ }
+ return result;
+ }
+
+ /**
+ * Visits all nodes in the tree that fulfils the |predicate|. If the |visitor|
+ * function returns |false| the traversal is aborted.
+ * @param {!Node} tree
+ * @param {function(!Node) : boolean} predicate
+ * @param {function(!Node) : *} visitor
+ */
+ function visit(tree, predicate, visitor) {
+ // This operates on logical DOM.
+ for (var node = tree.firstChild; node; node = node.nextSibling) {
+ if (predicate(node)) {
+ if (visitor(node) === false)
+ return;
+ } else {
+ visit(node, predicate, visitor);
+ }
+ }
+ }
+
+ // Matching Insertion Points
+ // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#matching-insertion-points
+
+ // TODO(arv): Verify this... I don't remember why I picked this regexp.
+ var selectorMatchRegExp = /^[*.:#[a-zA-Z_|]/;
+
+ var allowedPseudoRegExp = new RegExp('^:(' + [
+ 'link',
+ 'visited',
+ 'target',
+ 'enabled',
+ 'disabled',
+ 'checked',
+ 'indeterminate',
+ 'nth-child',
+ 'nth-last-child',
+ 'nth-of-type',
+ 'nth-last-of-type',
+ 'first-child',
+ 'last-child',
+ 'first-of-type',
+ 'last-of-type',
+ 'only-of-type',
+ ].join('|') + ')');
+
+
+ /**
+ * @param {Element} node
+ * @oaram {Element} point The insertion point element.
+ * @return {boolean} Whether the node matches the insertion point.
+ */
+ function matchesCriteria(node, point) {
+ var select = point.getAttribute('select');
+ if (!select)
+ return true;
+
+ // Here we know the select attribute is a non empty string.
+ select = select.trim();
+ if (!select)
+ return true;
+
+ if (!(node instanceof Element))
+ return false;
+
+ // The native matches function in IE9 does not correctly work with elements
+ // that are not in the document.
+ // TODO(arv): Implement matching in JS.
+ // https://github.com/Polymer/ShadowDOM/issues/361
+ if (select === '*' || select === node.localName)
+ return true;
+
+ // TODO(arv): This does not seem right. Need to check for a simple selector.
+ if (!selectorMatchRegExp.test(select))
+ return false;
+
+ // TODO(arv): This no longer matches the spec.
+ if (select[0] === ':' && !allowedPseudoRegExp.test(select))
+ return false;
+
+ try {
+ return node.matches(select);
+ } catch (ex) {
+ // Invalid selector.
+ return false;
+ }
+ }
+
+ var request = oneOf(window, [
+ 'requestAnimationFrame',
+ 'mozRequestAnimationFrame',
+ 'webkitRequestAnimationFrame',
+ 'setTimeout'
+ ]);
+
+ var pendingDirtyRenderers = [];
+ var renderTimer;
+
+ function renderAllPending() {
+ for (var i = 0; i < pendingDirtyRenderers.length; i++) {
+ pendingDirtyRenderers[i].render();
+ }
+ pendingDirtyRenderers = [];
+ }
+
+ function handleRequestAnimationFrame() {
+ renderTimer = null;
+ renderAllPending();
+ }
+
+ /**
+ * Returns existing shadow renderer for a host or creates it if it is needed.
+ * @params {!Element} host
+ * @return {!ShadowRenderer}
+ */
+ function getRendererForHost(host) {
+ var renderer = rendererForHostTable.get(host);
+ if (!renderer) {
+ renderer = new ShadowRenderer(host);
+ rendererForHostTable.set(host, renderer);
+ }
+ return renderer;
+ }
+
+ function getShadowRootAncestor(node) {
+ for (; node; node = node.parentNode) {
+ if (node instanceof ShadowRoot)
+ return node;
+ }
+ return null;
+ }
+
+ function getRendererForShadowRoot(shadowRoot) {
+ return getRendererForHost(shadowRoot.host);
+ }
+
+ var spliceDiff = new ArraySplice();
+ spliceDiff.equals = function(renderNode, rawNode) {
+ return unwrap(renderNode.node) === rawNode;
+ };
+
+ /**
+ * RenderNode is used as an in memory "render tree". When we render the
+ * composed tree we create a tree of RenderNodes, then we diff this against
+ * the real DOM tree and make minimal changes as needed.
+ */
+ function RenderNode(node) {
+ this.skip = false;
+ this.node = node;
+ this.childNodes = [];
+ }
+
+ RenderNode.prototype = {
+ append: function(node) {
+ var rv = new RenderNode(node);
+ this.childNodes.push(rv);
+ return rv;
+ },
+
+ sync: function(opt_added) {
+ if (this.skip)
+ return;
+
+ var nodeWrapper = this.node;
+ // plain array of RenderNodes
+ var newChildren = this.childNodes;
+ // plain array of real nodes.
+ var oldChildren = getChildNodesSnapshot(unwrap(nodeWrapper));
+ var added = opt_added || new WeakMap();
+
+ var splices = spliceDiff.calculateSplices(newChildren, oldChildren);
+
+ var newIndex = 0, oldIndex = 0;
+ var lastIndex = 0;
+ for (var i = 0; i < splices.length; i++) {
+ var splice = splices[i];
+ for (; lastIndex < splice.index; lastIndex++) {
+ oldIndex++;
+ newChildren[newIndex++].sync(added);
+ }
+
+ var removedCount = splice.removed.length;
+ for (var j = 0; j < removedCount; j++) {
+ var wrapper = wrap(oldChildren[oldIndex++]);
+ if (!added.get(wrapper))
+ remove(wrapper);
+ }
+
+ var addedCount = splice.addedCount;
+ var refNode = oldChildren[oldIndex] && wrap(oldChildren[oldIndex]);
+ for (var j = 0; j < addedCount; j++) {
+ var newChildRenderNode = newChildren[newIndex++];
+ var newChildWrapper = newChildRenderNode.node;
+ insertBefore(nodeWrapper, newChildWrapper, refNode);
+
+ // Keep track of added so that we do not remove the node after it
+ // has been added.
+ added.set(newChildWrapper, true);
+
+ newChildRenderNode.sync(added);
+ }
+
+ lastIndex += addedCount;
+ }
+
+ for (var i = lastIndex; i < newChildren.length; i++) {
+ newChildren[i].sync(added);
+ }
+ }
+ };
+
+ function ShadowRenderer(host) {
+ this.host = host;
+ this.dirty = false;
+ this.invalidateAttributes();
+ this.associateNode(host);
+ }
+
+ ShadowRenderer.prototype = {
+
+ // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#rendering-shadow-trees
+ render: function(opt_renderNode) {
+ if (!this.dirty)
+ return;
+
+ this.invalidateAttributes();
+ this.treeComposition();
+
+ var host = this.host;
+ var shadowRoot = host.shadowRoot;
+
+ this.associateNode(host);
+ var topMostRenderer = !renderNode;
+ var renderNode = opt_renderNode || new RenderNode(host);
+
+ for (var node = shadowRoot.firstChild; node; node = node.nextSibling) {
+ this.renderNode(shadowRoot, renderNode, node, false);
+ }
+
+ if (topMostRenderer)
+ renderNode.sync();
+
+ this.dirty = false;
+ },
+
+ invalidate: function() {
+ if (!this.dirty) {
+ this.dirty = true;
+ pendingDirtyRenderers.push(this);
+ if (renderTimer)
+ return;
+ renderTimer = window[request](handleRequestAnimationFrame, 0);
+ }
+ },
+
+ renderNode: function(shadowRoot, renderNode, node, isNested) {
+ if (isShadowHost(node)) {
+ renderNode = renderNode.append(node);
+ var renderer = getRendererForHost(node);
+ renderer.dirty = true; // Need to rerender due to reprojection.
+ renderer.render(renderNode);
+ } else if (isInsertionPoint(node)) {
+ this.renderInsertionPoint(shadowRoot, renderNode, node, isNested);
+ } else if (isShadowInsertionPoint(node)) {
+ this.renderShadowInsertionPoint(shadowRoot, renderNode, node);
+ } else {
+ this.renderAsAnyDomTree(shadowRoot, renderNode, node, isNested);
+ }
+ },
+
+ renderAsAnyDomTree: function(shadowRoot, renderNode, node, isNested) {
+ renderNode = renderNode.append(node);
+
+ if (isShadowHost(node)) {
+ var renderer = getRendererForHost(node);
+ renderNode.skip = !renderer.dirty;
+ renderer.render(renderNode);
+ } else {
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ this.renderNode(shadowRoot, renderNode, child, isNested);
+ }
+ }
+ },
+
+ renderInsertionPoint: function(shadowRoot, renderNode, insertionPoint,
+ isNested) {
+ var distributedChildNodes = getDistributedChildNodes(insertionPoint);
+ if (distributedChildNodes.length) {
+ this.associateNode(insertionPoint);
+
+ for (var i = 0; i < distributedChildNodes.length; i++) {
+ var child = distributedChildNodes[i];
+ if (isInsertionPoint(child) && isNested)
+ this.renderInsertionPoint(shadowRoot, renderNode, child, isNested);
+ else
+ this.renderAsAnyDomTree(shadowRoot, renderNode, child, isNested);
+ }
+ } else {
+ this.renderFallbackContent(shadowRoot, renderNode, insertionPoint);
+ }
+ this.associateNode(insertionPoint.parentNode);
+ },
+
+ renderShadowInsertionPoint: function(shadowRoot, renderNode,
+ shadowInsertionPoint) {
+ var nextOlderTree = shadowRoot.olderShadowRoot;
+ if (nextOlderTree) {
+ assignToInsertionPoint(nextOlderTree, shadowInsertionPoint);
+ this.associateNode(shadowInsertionPoint.parentNode);
+ for (var node = nextOlderTree.firstChild;
+ node;
+ node = node.nextSibling) {
+ this.renderNode(nextOlderTree, renderNode, node, true);
+ }
+ } else {
+ this.renderFallbackContent(shadowRoot, renderNode,
+ shadowInsertionPoint);
+ }
+ },
+
+ renderFallbackContent: function(shadowRoot, renderNode, fallbackHost) {
+ this.associateNode(fallbackHost);
+ this.associateNode(fallbackHost.parentNode);
+ for (var node = fallbackHost.firstChild; node; node = node.nextSibling) {
+ this.renderAsAnyDomTree(shadowRoot, renderNode, node, false);
+ }
+ },
+
+ /**
+ * Invalidates the attributes used to keep track of which attributes may
+ * cause the renderer to be invalidated.
+ */
+ invalidateAttributes: function() {
+ this.attributes = Object.create(null);
+ },
+
+ /**
+ * Parses the selector and makes this renderer dependent on the attribute
+ * being used in the selector.
+ * @param {string} selector
+ */
+ updateDependentAttributes: function(selector) {
+ if (!selector)
+ return;
+
+ var attributes = this.attributes;
+
+ // .class
+ if (/\.\w+/.test(selector))
+ attributes['class'] = true;
+
+ // #id
+ if (/#\w+/.test(selector))
+ attributes['id'] = true;
+
+ selector.replace(/\[\s*([^\s=\|~\]]+)/g, function(_, name) {
+ attributes[name] = true;
+ });
+
+ // Pseudo selectors have been removed from the spec.
+ },
+
+ dependsOnAttribute: function(name) {
+ return this.attributes[name];
+ },
+
+ // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#dfn-distribution-algorithm
+ distribute: function(tree, pool) {
+ var self = this;
+
+ visit(tree, isActiveInsertionPoint,
+ function(insertionPoint) {
+ resetDistributedChildNodes(insertionPoint);
+ self.updateDependentAttributes(
+ insertionPoint.getAttribute('select'));
+
+ for (var i = 0; i < pool.length; i++) { // 1.2
+ var node = pool[i]; // 1.2.1
+ if (node === undefined) // removed
+ continue;
+ if (matchesCriteria(node, insertionPoint)) { // 1.2.2
+ distributeChildToInsertionPoint(node, insertionPoint); // 1.2.2.1
+ pool[i] = undefined; // 1.2.2.2
+ }
+ }
+ });
+ },
+
+ // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#dfn-tree-composition
+ treeComposition: function () {
+ var shadowHost = this.host;
+ var tree = shadowHost.shadowRoot; // 1.
+ var pool = []; // 2.
+
+ for (var child = shadowHost.firstChild;
+ child;
+ child = child.nextSibling) { // 3.
+ if (isInsertionPoint(child)) { // 3.2.
+ var reprojected = getDistributedChildNodes(child); // 3.2.1.
+ // if reprojected is undef... reset it?
+ if (!reprojected || !reprojected.length) // 3.2.2.
+ reprojected = getChildNodesSnapshot(child);
+ pool.push.apply(pool, reprojected); // 3.2.3.
+ } else {
+ pool.push(child); // 3.3.
+ }
+ }
+
+ var shadowInsertionPoint, point;
+ while (tree) { // 4.
+ // 4.1.
+ shadowInsertionPoint = undefined; // Reset every iteration.
+ visit(tree, isActiveShadowInsertionPoint, function(point) {
+ shadowInsertionPoint = point;
+ return false;
+ });
+ point = shadowInsertionPoint;
+
+ this.distribute(tree, pool); // 4.2.
+ if (point) { // 4.3.
+ var nextOlderTree = tree.olderShadowRoot; // 4.3.1.
+ if (!nextOlderTree) {
+ break; // 4.3.1.1.
+ } else {
+ tree = nextOlderTree; // 4.3.2.2.
+ assignToInsertionPoint(tree, point); // 4.3.2.2.
+ continue; // 4.3.2.3.
+ }
+ } else {
+ break; // 4.4.
+ }
+ }
+ },
+
+ associateNode: function(node) {
+ node.impl.polymerShadowRenderer_ = this;
+ }
+ };
+
+ function isInsertionPoint(node) {
+ // Should this include <shadow>?
+ return node instanceof HTMLContentElement;
+ }
+
+ function isActiveInsertionPoint(node) {
+ // <content> inside another <content> or <shadow> is considered inactive.
+ return node instanceof HTMLContentElement;
+ }
+
+ function isShadowInsertionPoint(node) {
+ return node instanceof HTMLShadowElement;
+ }
+
+ function isActiveShadowInsertionPoint(node) {
+ // <shadow> inside another <content> or <shadow> is considered inactive.
+ return node instanceof HTMLShadowElement;
+ }
+
+ function isShadowHost(shadowHost) {
+ return shadowHost.shadowRoot;
+ }
+
+ function getShadowTrees(host) {
+ var trees = [];
+
+ for (var tree = host.shadowRoot; tree; tree = tree.olderShadowRoot) {
+ trees.push(tree);
+ }
+ return trees;
+ }
+
+ function assignToInsertionPoint(tree, point) {
+ insertionParentTable.set(tree, point);
+ }
+
+ // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#rendering-shadow-trees
+ function render(host) {
+ new ShadowRenderer(host).render();
+ };
+
+ // Need to rerender shadow host when:
+ //
+ // - a direct child to the ShadowRoot is added or removed
+ // - a direct child to the host is added or removed
+ // - a new shadow root is created
+ // - a direct child to a content/shadow element is added or removed
+ // - a sibling to a content/shadow element is added or removed
+ // - content[select] is changed
+ // - an attribute in a direct child to a host is modified
+
+ /**
+ * This gets called when a node was added or removed to it.
+ */
+ Node.prototype.invalidateShadowRenderer = function(force) {
+ var renderer = this.impl.polymerShadowRenderer_;
+ if (renderer) {
+ renderer.invalidate();
+ return true;
+ }
+
+ return false;
+ };
+
+ HTMLContentElement.prototype.getDistributedNodes = function() {
+ // TODO(arv): We should only rerender the dirty ancestor renderers (from
+ // the root and down).
+ renderAllPending();
+ return getDistributedChildNodes(this);
+ };
+
+ HTMLShadowElement.prototype.nodeIsInserted_ =
+ HTMLContentElement.prototype.nodeIsInserted_ = function() {
+ // Invalidate old renderer if any.
+ this.invalidateShadowRenderer();
+
+ var shadowRoot = getShadowRootAncestor(this);
+ var renderer;
+ if (shadowRoot)
+ renderer = getRendererForShadowRoot(shadowRoot);
+ this.impl.polymerShadowRenderer_ = renderer;
+ if (renderer)
+ renderer.invalidate();
+ };
+
+ scope.eventParentsTable = eventParentsTable;
+ scope.getRendererForHost = getRendererForHost;
+ scope.getShadowTrees = getShadowTrees;
+ scope.insertionParentTable = insertionParentTable;
+ scope.renderAllPending = renderAllPending;
+
+ // Exposed for testing
+ scope.visual = {
+ insertBefore: insertBefore,
+ remove: remove,
+ };
+
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var HTMLElement = scope.wrappers.HTMLElement;
+ var assert = scope.assert;
+ var mixin = scope.mixin;
+ var registerWrapper = scope.registerWrapper;
+ var unwrap = scope.unwrap;
+ var wrap = scope.wrap;
+
+ var elementsWithFormProperty = [
+ 'HTMLButtonElement',
+ 'HTMLFieldSetElement',
+ 'HTMLInputElement',
+ 'HTMLKeygenElement',
+ 'HTMLLabelElement',
+ 'HTMLLegendElement',
+ 'HTMLObjectElement',
+ // HTMLOptionElement is handled in HTMLOptionElement.js
+ 'HTMLOutputElement',
+ 'HTMLSelectElement',
+ 'HTMLTextAreaElement',
+ ];
+
+ function createWrapperConstructor(name) {
+ if (!window[name])
+ return;
+
+ // Ensure we are not overriding an already existing constructor.
+ assert(!scope.wrappers[name]);
+
+ var GeneratedWrapper = function(node) {
+ // At this point all of them extend HTMLElement.
+ HTMLElement.call(this, node);
+ }
+ GeneratedWrapper.prototype = Object.create(HTMLElement.prototype);
+ mixin(GeneratedWrapper.prototype, {
+ get form() {
+ return wrap(unwrap(this).form);
+ },
+ });
+
+ registerWrapper(window[name], GeneratedWrapper,
+ document.createElement(name.slice(4, -7)));
+ scope.wrappers[name] = GeneratedWrapper;
+ }
+
+ elementsWithFormProperty.forEach(createWrapperConstructor);
+
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2014 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var registerWrapper = scope.registerWrapper;
+ var unwrap = scope.unwrap;
+ var unwrapIfNeeded = scope.unwrapIfNeeded;
+ var wrap = scope.wrap;
+
+ var OriginalSelection = window.Selection;
+
+ function Selection(impl) {
+ this.impl = impl;
+ }
+ Selection.prototype = {
+ get anchorNode() {
+ return wrap(this.impl.anchorNode);
+ },
+ get focusNode() {
+ return wrap(this.impl.focusNode);
+ },
+ addRange: function(range) {
+ this.impl.addRange(unwrap(range));
+ },
+ collapse: function(node, index) {
+ this.impl.collapse(unwrapIfNeeded(node), index);
+ },
+ containsNode: function(node, allowPartial) {
+ return this.impl.containsNode(unwrapIfNeeded(node), allowPartial);
+ },
+ extend: function(node, offset) {
+ this.impl.extend(unwrapIfNeeded(node), offset);
+ },
+ getRangeAt: function(index) {
+ return wrap(this.impl.getRangeAt(index));
+ },
+ removeRange: function(range) {
+ this.impl.removeRange(unwrap(range));
+ },
+ selectAllChildren: function(node) {
+ this.impl.selectAllChildren(unwrapIfNeeded(node));
+ },
+ toString: function() {
+ return this.impl.toString();
+ }
+ };
+
+ // WebKit extensions. Not implemented.
+ // readonly attribute Node baseNode;
+ // readonly attribute long baseOffset;
+ // readonly attribute Node extentNode;
+ // readonly attribute long extentOffset;
+ // [RaisesException] void setBaseAndExtent([Default=Undefined] optional Node baseNode,
+ // [Default=Undefined] optional long baseOffset,
+ // [Default=Undefined] optional Node extentNode,
+ // [Default=Undefined] optional long extentOffset);
+ // [RaisesException, ImplementedAs=collapse] void setPosition([Default=Undefined] optional Node node,
+ // [Default=Undefined] optional long offset);
+
+ registerWrapper(window.Selection, Selection, window.getSelection());
+
+ scope.wrappers.Selection = Selection;
+
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var GetElementsByInterface = scope.GetElementsByInterface;
+ var Node = scope.wrappers.Node;
+ var ParentNodeInterface = scope.ParentNodeInterface;
+ var Selection = scope.wrappers.Selection;
+ var SelectorsInterface = scope.SelectorsInterface;
+ var ShadowRoot = scope.wrappers.ShadowRoot;
+ var cloneNode = scope.cloneNode;
+ var defineWrapGetter = scope.defineWrapGetter;
+ var elementFromPoint = scope.elementFromPoint;
+ var forwardMethodsToWrapper = scope.forwardMethodsToWrapper;
+ var matchesNames = scope.matchesNames;
+ var mixin = scope.mixin;
+ var registerWrapper = scope.registerWrapper;
+ var renderAllPending = scope.renderAllPending;
+ var rewrap = scope.rewrap;
+ var unwrap = scope.unwrap;
+ var wrap = scope.wrap;
+ var wrapEventTargetMethods = scope.wrapEventTargetMethods;
+ var wrapNodeList = scope.wrapNodeList;
+
+ var implementationTable = new WeakMap();
+
+ function Document(node) {
+ Node.call(this, node);
+ }
+ Document.prototype = Object.create(Node.prototype);
+
+ defineWrapGetter(Document, 'documentElement');
+
+ // Conceptually both body and head can be in a shadow but suporting that seems
+ // overkill at this point.
+ defineWrapGetter(Document, 'body');
+ defineWrapGetter(Document, 'head');
+
+ // document cannot be overridden so we override a bunch of its methods
+ // directly on the instance.
+
+ function wrapMethod(name) {
+ var original = document[name];
+ Document.prototype[name] = function() {
+ return wrap(original.apply(this.impl, arguments));
+ };
+ }
+
+ [
+ 'createComment',
+ 'createDocumentFragment',
+ 'createElement',
+ 'createElementNS',
+ 'createEvent',
+ 'createEventNS',
+ 'createRange',
+ 'createTextNode',
+ 'getElementById'
+ ].forEach(wrapMethod);
+
+ var originalAdoptNode = document.adoptNode;
+
+ function adoptNodeNoRemove(node, doc) {
+ originalAdoptNode.call(doc.impl, unwrap(node));
+ adoptSubtree(node, doc);
+ }
+
+ function adoptSubtree(node, doc) {
+ if (node.shadowRoot)
+ doc.adoptNode(node.shadowRoot);
+ if (node instanceof ShadowRoot)
+ adoptOlderShadowRoots(node, doc);
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ adoptSubtree(child, doc);
+ }
+ }
+
+ function adoptOlderShadowRoots(shadowRoot, doc) {
+ var oldShadowRoot = shadowRoot.olderShadowRoot;
+ if (oldShadowRoot)
+ doc.adoptNode(oldShadowRoot);
+ }
+
+ var originalGetSelection = document.getSelection;
+
+ mixin(Document.prototype, {
+ adoptNode: function(node) {
+ if (node.parentNode)
+ node.parentNode.removeChild(node);
+ adoptNodeNoRemove(node, this);
+ return node;
+ },
+ elementFromPoint: function(x, y) {
+ return elementFromPoint(this, this, x, y);
+ },
+ importNode: function(node, deep) {
+ return cloneNode(node, deep, this.impl);
+ },
+ getSelection: function() {
+ renderAllPending();
+ return new Selection(originalGetSelection.call(unwrap(this)));
+ }
+ });
+
+ if (document.registerElement) {
+ var originalRegisterElement = document.registerElement;
+ Document.prototype.registerElement = function(tagName, object) {
+ var prototype = object.prototype;
+
+ // If we already used the object as a prototype for another custom
+ // element.
+ if (scope.nativePrototypeTable.get(prototype)) {
+ // TODO(arv): DOMException
+ throw new Error('NotSupportedError');
+ }
+
+ // Find first object on the prototype chain that already have a native
+ // prototype. Keep track of all the objects before that so we can create
+ // a similar structure for the native case.
+ var proto = Object.getPrototypeOf(prototype);
+ var nativePrototype;
+ var prototypes = [];
+ while (proto) {
+ nativePrototype = scope.nativePrototypeTable.get(proto);
+ if (nativePrototype)
+ break;
+ prototypes.push(proto);
+ proto = Object.getPrototypeOf(proto);
+ }
+
+ if (!nativePrototype) {
+ // TODO(arv): DOMException
+ throw new Error('NotSupportedError');
+ }
+
+ // This works by creating a new prototype object that is empty, but has
+ // the native prototype as its proto. The original prototype object
+ // passed into register is used as the wrapper prototype.
+
+ var newPrototype = Object.create(nativePrototype);
+ for (var i = prototypes.length - 1; i >= 0; i--) {
+ newPrototype = Object.create(newPrototype);
+ }
+
+ // Add callbacks if present.
+ // Names are taken from:
+ // https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/bindings/v8/CustomElementConstructorBuilder.cpp&sq=package:chromium&type=cs&l=156
+ // and not from the spec since the spec is out of date.
+ [
+ 'createdCallback',
+ 'attachedCallback',
+ 'detachedCallback',
+ 'attributeChangedCallback',
+ ].forEach(function(name) {
+ var f = prototype[name];
+ if (!f)
+ return;
+ newPrototype[name] = function() {
+ // if this element has been wrapped prior to registration,
+ // the wrapper is stale; in this case rewrap
+ if (!(wrap(this) instanceof CustomElementConstructor)) {
+ rewrap(this);
+ }
+ f.apply(wrap(this), arguments);
+ };
+ });
+
+ var p = {prototype: newPrototype};
+ if (object.extends)
+ p.extends = object.extends;
+
+ function CustomElementConstructor(node) {
+ if (!node) {
+ if (object.extends) {
+ return document.createElement(object.extends, tagName);
+ } else {
+ return document.createElement(tagName);
+ }
+ }
+ this.impl = node;
+ }
+ CustomElementConstructor.prototype = prototype;
+ CustomElementConstructor.prototype.constructor = CustomElementConstructor;
+
+ scope.constructorTable.set(newPrototype, CustomElementConstructor);
+ scope.nativePrototypeTable.set(prototype, newPrototype);
+
+ // registration is synchronous so do it last
+ var nativeConstructor = originalRegisterElement.call(unwrap(this),
+ tagName, p);
+ return CustomElementConstructor;
+ };
+
+ forwardMethodsToWrapper([
+ window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument
+ ], [
+ 'registerElement',
+ ]);
+ }
+
+ // We also override some of the methods on document.body and document.head
+ // for convenience.
+ forwardMethodsToWrapper([
+ window.HTMLBodyElement,
+ window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument
+ window.HTMLHeadElement,
+ window.HTMLHtmlElement,
+ ], [
+ 'appendChild',
+ 'compareDocumentPosition',
+ 'contains',
+ 'getElementsByClassName',
+ 'getElementsByTagName',
+ 'getElementsByTagNameNS',
+ 'insertBefore',
+ 'querySelector',
+ 'querySelectorAll',
+ 'removeChild',
+ 'replaceChild',
+ ].concat(matchesNames));
+
+ forwardMethodsToWrapper([
+ window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument
+ ], [
+ 'adoptNode',
+ 'importNode',
+ 'contains',
+ 'createComment',
+ 'createDocumentFragment',
+ 'createElement',
+ 'createElementNS',
+ 'createEvent',
+ 'createEventNS',
+ 'createRange',
+ 'createTextNode',
+ 'elementFromPoint',
+ 'getElementById',
+ 'getSelection',
+ ]);
+
+ mixin(Document.prototype, GetElementsByInterface);
+ mixin(Document.prototype, ParentNodeInterface);
+ mixin(Document.prototype, SelectorsInterface);
+
+ mixin(Document.prototype, {
+ get implementation() {
+ var implementation = implementationTable.get(this);
+ if (implementation)
+ return implementation;
+ implementation =
+ new DOMImplementation(unwrap(this).implementation);
+ implementationTable.set(this, implementation);
+ return implementation;
+ }
+ });
+
+ registerWrapper(window.Document, Document,
+ document.implementation.createHTMLDocument(''));
+
+ // Both WebKit and Gecko uses HTMLDocument for document. HTML5/DOM only has
+ // one Document interface and IE implements the standard correctly.
+ if (window.HTMLDocument)
+ registerWrapper(window.HTMLDocument, Document);
+
+ wrapEventTargetMethods([
+ window.HTMLBodyElement,
+ window.HTMLDocument || window.Document, // Gecko adds these to HTMLDocument
+ window.HTMLHeadElement,
+ ]);
+
+ function DOMImplementation(impl) {
+ this.impl = impl;
+ }
+
+ function wrapImplMethod(constructor, name) {
+ var original = document.implementation[name];
+ constructor.prototype[name] = function() {
+ return wrap(original.apply(this.impl, arguments));
+ };
+ }
+
+ function forwardImplMethod(constructor, name) {
+ var original = document.implementation[name];
+ constructor.prototype[name] = function() {
+ return original.apply(this.impl, arguments);
+ };
+ }
+
+ wrapImplMethod(DOMImplementation, 'createDocumentType');
+ wrapImplMethod(DOMImplementation, 'createDocument');
+ wrapImplMethod(DOMImplementation, 'createHTMLDocument');
+ forwardImplMethod(DOMImplementation, 'hasFeature');
+
+ registerWrapper(window.DOMImplementation, DOMImplementation);
+
+ forwardMethodsToWrapper([
+ window.DOMImplementation,
+ ], [
+ 'createDocumentType',
+ 'createDocument',
+ 'createHTMLDocument',
+ 'hasFeature',
+ ]);
+
+ scope.adoptNodeNoRemove = adoptNodeNoRemove;
+ scope.wrappers.DOMImplementation = DOMImplementation;
+ scope.wrappers.Document = Document;
+
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var EventTarget = scope.wrappers.EventTarget;
+ var Selection = scope.wrappers.Selection;
+ var mixin = scope.mixin;
+ var registerWrapper = scope.registerWrapper;
+ var renderAllPending = scope.renderAllPending;
+ var unwrap = scope.unwrap;
+ var unwrapIfNeeded = scope.unwrapIfNeeded;
+ var wrap = scope.wrap;
+
+ var OriginalWindow = window.Window;
+ var originalGetComputedStyle = window.getComputedStyle;
+ var originalGetSelection = window.getSelection;
+
+ function Window(impl) {
+ EventTarget.call(this, impl);
+ }
+ Window.prototype = Object.create(EventTarget.prototype);
+
+ OriginalWindow.prototype.getComputedStyle = function(el, pseudo) {
+ return wrap(this || window).getComputedStyle(unwrapIfNeeded(el), pseudo);
+ };
+
+ OriginalWindow.prototype.getSelection = function() {
+ return wrap(this || window).getSelection();
+ };
+
+ // Work around for https://bugzilla.mozilla.org/show_bug.cgi?id=943065
+ delete window.getComputedStyle;
+ delete window.getSelection;
+
+ ['addEventListener', 'removeEventListener', 'dispatchEvent'].forEach(
+ function(name) {
+ OriginalWindow.prototype[name] = function() {
+ var w = wrap(this || window);
+ return w[name].apply(w, arguments);
+ };
+
+ // Work around for https://bugzilla.mozilla.org/show_bug.cgi?id=943065
+ delete window[name];
+ });
+
+ mixin(Window.prototype, {
+ getComputedStyle: function(el, pseudo) {
+ renderAllPending();
+ return originalGetComputedStyle.call(unwrap(this), unwrapIfNeeded(el),
+ pseudo);
+ },
+ getSelection: function() {
+ renderAllPending();
+ return new Selection(originalGetSelection.call(unwrap(this)));
+ },
+ });
+
+ registerWrapper(OriginalWindow, Window);
+
+ scope.wrappers.Window = Window;
+
+})(window.ShadowDOMPolyfill);
+
+// Copyright 2013 The Polymer Authors. All rights reserved.
+// Use of this source code is goverened by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function(scope) {
+ 'use strict';
+
+ var isWrapperFor = scope.isWrapperFor;
+
+ // This is a list of the elements we currently override the global constructor
+ // for.
+ var elements = {
+ 'a': 'HTMLAnchorElement',
+
+ // Do not create an applet element by default since it shows a warning in
+ // IE.
+ // https://github.com/Polymer/polymer/issues/217
+ // 'applet': 'HTMLAppletElement',
+
+ 'area': 'HTMLAreaElement',
+ 'br': 'HTMLBRElement',
+ 'base': 'HTMLBaseElement',
+ 'body': 'HTMLBodyElement',
+ 'button': 'HTMLButtonElement',
+ // 'command': 'HTMLCommandElement', // Not fully implemented in Gecko.
+ 'dl': 'HTMLDListElement',
+ 'datalist': 'HTMLDataListElement',
+ 'data': 'HTMLDataElement',
+ 'dir': 'HTMLDirectoryElement',
+ 'div': 'HTMLDivElement',
+ 'embed': 'HTMLEmbedElement',
+ 'fieldset': 'HTMLFieldSetElement',
+ 'font': 'HTMLFontElement',
+ 'form': 'HTMLFormElement',
+ 'frame': 'HTMLFrameElement',
+ 'frameset': 'HTMLFrameSetElement',
+ 'hr': 'HTMLHRElement',
+ 'head': 'HTMLHeadElement',
+ 'h1': 'HTMLHeadingElement',
+ 'html': 'HTMLHtmlElement',
+ 'iframe': 'HTMLIFrameElement',
+ 'input': 'HTMLInputElement',
+ 'li': 'HTMLLIElement',
+ 'label': 'HTMLLabelElement',
+ 'legend': 'HTMLLegendElement',
+ 'link': 'HTMLLinkElement',
+ 'map': 'HTMLMapElement',
+ 'marquee': 'HTMLMarqueeElement',
+ 'menu': 'HTMLMenuElement',
+ 'menuitem': 'HTMLMenuItemElement',
+ 'meta': 'HTMLMetaElement',
+ 'meter': 'HTMLMeterElement',
+ 'del': 'HTMLModElement',
+ 'ol': 'HTMLOListElement',
+ 'object': 'HTMLObjectElement',
+ 'optgroup': 'HTMLOptGroupElement',
+ 'option': 'HTMLOptionElement',
+ 'output': 'HTMLOutputElement',
+ 'p': 'HTMLParagraphElement',
+ 'param': 'HTMLParamElement',
+ 'pre': 'HTMLPreElement',
+ 'progress': 'HTMLProgressElement',
+ 'q': 'HTMLQuoteElement',
+ 'script': 'HTMLScriptElement',
+ 'select': 'HTMLSelectElement',
+ 'source': 'HTMLSourceElement',
+ 'span': 'HTMLSpanElement',
+ 'style': 'HTMLStyleElement',
+ 'time': 'HTMLTimeElement',
+ 'caption': 'HTMLTableCaptionElement',
+ // WebKit and Moz are wrong:
+ // https://bugs.webkit.org/show_bug.cgi?id=111469
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=848096
+ // 'td': 'HTMLTableCellElement',
+ 'col': 'HTMLTableColElement',
+ 'table': 'HTMLTableElement',
+ 'tr': 'HTMLTableRowElement',
+ 'thead': 'HTMLTableSectionElement',
+ 'tbody': 'HTMLTableSectionElement',
+ 'textarea': 'HTMLTextAreaElement',
+ 'track': 'HTMLTrackElement',
+ 'title': 'HTMLTitleElement',
+ 'ul': 'HTMLUListElement',
+ 'video': 'HTMLVideoElement',
+ };
+
+ function overrideConstructor(tagName) {
+ var nativeConstructorName = elements[tagName];
+ var nativeConstructor = window[nativeConstructorName];
+ if (!nativeConstructor)
+ return;
+ var element = document.createElement(tagName);
+ var wrapperConstructor = element.constructor;
+ window[nativeConstructorName] = wrapperConstructor;
+ }
+
+ Object.keys(elements).forEach(overrideConstructor);
+
+ Object.getOwnPropertyNames(scope.wrappers).forEach(function(name) {
+ window[name] = scope.wrappers[name]
+ });
+
+ // Export for testing.
+ scope.knownElements = elements;
+
+})(window.ShadowDOMPolyfill);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+(function() {
+
+ // convenient global
+ window.wrap = ShadowDOMPolyfill.wrapIfNeeded;
+ window.unwrap = ShadowDOMPolyfill.unwrapIfNeeded;
+
+ // users may want to customize other types
+ // TODO(sjmiles): 'button' is now supported by ShadowDOMPolyfill, but
+ // I've left this code here in case we need to temporarily patch another
+ // type
+ /*
+ (function() {
+ var elts = {HTMLButtonElement: 'button'};
+ for (var c in elts) {
+ window[c] = function() { throw 'Patched Constructor'; };
+ window[c].prototype = Object.getPrototypeOf(
+ document.createElement(elts[c]));
+ }
+ })();
+ */
+
+ // patch in prefixed name
+ Object.defineProperty(Element.prototype, 'webkitShadowRoot',
+ Object.getOwnPropertyDescriptor(Element.prototype, 'shadowRoot'));
+
+ var originalCreateShadowRoot = Element.prototype.createShadowRoot;
+ Element.prototype.createShadowRoot = function() {
+ var root = originalCreateShadowRoot.call(this);
+ CustomElements.watchShadow(this);
+ return root;
+ };
+
+ Element.prototype.webkitCreateShadowRoot = Element.prototype.createShadowRoot;
+})();
+
+/*
+ * Copyright 2012 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+/*
+ This is a limited shim for ShadowDOM css styling.
+ https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#styles
+
+ The intention here is to support only the styling features which can be
+ relatively simply implemented. The goal is to allow users to avoid the
+ most obvious pitfalls and do so without compromising performance significantly.
+ For ShadowDOM styling that's not covered here, a set of best practices
+ can be provided that should allow users to accomplish more complex styling.
+
+ The following is a list of specific ShadowDOM styling features and a brief
+ discussion of the approach used to shim.
+
+ Shimmed features:
+
+ * :host, :ancestor: ShadowDOM allows styling of the shadowRoot's host
+ element using the :host rule. To shim this feature, the :host styles are
+ reformatted and prefixed with a given scope name and promoted to a
+ document level stylesheet.
+ For example, given a scope name of .foo, a rule like this:
+
+ :host {
+ background: red;
+ }
+ }
+
+ becomes:
+
+ .foo {
+ background: red;
+ }
+
+ * encapsultion: Styles defined within ShadowDOM, apply only to
+ dom inside the ShadowDOM. Polymer uses one of two techniques to imlement
+ this feature.
+
+ By default, rules are prefixed with the host element tag name
+ as a descendant selector. This ensures styling does not leak out of the 'top'
+ of the element's ShadowDOM. For example,
+
+ div {
+ font-weight: bold;
+ }
+
+ becomes:
+
+ x-foo div {
+ font-weight: bold;
+ }
+
+ becomes:
+
+
+ Alternatively, if Platform.ShadowCSS.strictStyling is set to true then
+ selectors are scoped by adding an attribute selector suffix to each
+ simple selector that contains the host element tag name. Each element
+ in the element's ShadowDOM template is also given the scope attribute.
+ Thus, these rules match only elements that have the scope attribute.
+ For example, given a scope name of x-foo, a rule like this:
+
+ div {
+ font-weight: bold;
+ }
+
+ becomes:
+
+ div[x-foo] {
+ font-weight: bold;
+ }
+
+ Note that elements that are dynamically added to a scope must have the scope
+ selector added to them manually.
+
+ * upper/lower bound encapsulation: Styles which are defined outside a
+ shadowRoot should not cross the ShadowDOM boundary and should not apply
+ inside a shadowRoot.
+
+ This styling behavior is not emulated. Some possible ways to do this that
+ were rejected due to complexity and/or performance concerns include: (1) reset
+ every possible property for every possible selector for a given scope name;
+ (2) re-implement css in javascript.
+
+ As an alternative, users should make sure to use selectors
+ specific to the scope in which they are working.
+
+ * ::distributed: This behavior is not emulated. It's often not necessary
+ to style the contents of a specific insertion point and instead, descendants
+ of the host element can be styled selectively. Users can also create an
+ extra node around an insertion point and style that node's contents
+ via descendent selectors. For example, with a shadowRoot like this:
+
+ <style>
+ ::content(div) {
+ background: red;
+ }
+ </style>
+ <content></content>
+
+ could become:
+
+ <style>
+ / *@polyfill .content-container div * /
+ ::content(div) {
+ background: red;
+ }
+ </style>
+ <div class="content-container">
+ <content></content>
+ </div>
+
+ Note the use of @polyfill in the comment above a ShadowDOM specific style
+ declaration. This is a directive to the styling shim to use the selector
+ in comments in lieu of the next selector when running under polyfill.
+*/
+(function(scope) {
+
+var ShadowCSS = {
+ strictStyling: false,
+ registry: {},
+ // Shim styles for a given root associated with a name and extendsName
+ // 1. cache root styles by name
+ // 2. optionally tag root nodes with scope name
+ // 3. shim polyfill directives /* @polyfill */ and /* @polyfill-rule */
+ // 4. shim :host and scoping
+ shimStyling: function(root, name, extendsName) {
+ var typeExtension = this.isTypeExtension(extendsName);
+ // use caching to make working with styles nodes easier and to facilitate
+ // lookup of extendee
+ var def = this.registerDefinition(root, name, extendsName);
+ // find styles and apply shimming...
+ if (this.strictStyling) {
+ this.applyScopeToContent(root, name);
+ }
+ var cssText = this.stylesToShimmedCssText(def.rootStyles, def.scopeStyles,
+ name, typeExtension);
+ // provide shimmedStyle for user extensibility
+ def.shimmedStyle = cssTextToStyle(cssText);
+ if (root) {
+ root.shimmedStyle = def.shimmedStyle;
+ }
+ // remove existing style elements
+ for (var i=0, l=def.rootStyles.length, s; (i<l) && (s=def.rootStyles[i]);
+ i++) {
+ s.parentNode.removeChild(s);
+ }
+ // add style to document
+ addCssToDocument(cssText);
+ },
+ // apply @polyfill rules + :host and scope shimming
+ stylesToShimmedCssText: function(rootStyles, scopeStyles, name,
+ typeExtension) {
+ name = name || '';
+ // insert @polyfill and @polyfill-rule rules into style elements
+ // scoping process takes care of shimming these
+ this.insertPolyfillDirectives(rootStyles);
+ this.insertPolyfillRules(rootStyles);
+ var cssText = this.shimScoping(scopeStyles, name, typeExtension);
+ // note: we only need to do rootStyles since these are unscoped.
+ cssText += this.extractPolyfillUnscopedRules(rootStyles);
+ return cssText;
+ },
+ registerDefinition: function(root, name, extendsName) {
+ var def = this.registry[name] = {
+ root: root,
+ name: name,
+ extendsName: extendsName
+ }
+ var styles = root ? root.querySelectorAll('style') : [];
+ styles = styles ? Array.prototype.slice.call(styles, 0) : [];
+ def.rootStyles = styles;
+ def.scopeStyles = def.rootStyles;
+ var extendee = this.registry[def.extendsName];
+ if (extendee && (!root || root.querySelector('shadow'))) {
+ def.scopeStyles = extendee.scopeStyles.concat(def.scopeStyles);
+ }
+ return def;
+ },
+ isTypeExtension: function(extendsName) {
+ return extendsName && extendsName.indexOf('-') < 0;
+ },
+ applyScopeToContent: function(root, name) {
+ if (root) {
+ // add the name attribute to each node in root.
+ Array.prototype.forEach.call(root.querySelectorAll('*'),
+ function(node) {
+ node.setAttribute(name, '');
+ });
+ // and template contents too
+ Array.prototype.forEach.call(root.querySelectorAll('template'),
+ function(template) {
+ this.applyScopeToContent(template.content, name);
+ },
+ this);
+ }
+ },
+ /*
+ * Process styles to convert native ShadowDOM rules that will trip
+ * up the css parser; we rely on decorating the stylesheet with comments.
+ *
+ * For example, we convert this rule:
+ *
+ * (comment start) @polyfill :host menu-item (comment end)
+ * shadow::-webkit-distributed(menu-item) {
+ *
+ * to this:
+ *
+ * scopeName menu-item {
+ *
+ **/
+ insertPolyfillDirectives: function(styles) {
+ if (styles) {
+ Array.prototype.forEach.call(styles, function(s) {
+ s.textContent = this.insertPolyfillDirectivesInCssText(s.textContent);
+ }, this);
+ }
+ },
+ insertPolyfillDirectivesInCssText: function(cssText) {
+ return cssText.replace(cssPolyfillCommentRe, function(match, p1) {
+ // remove end comment delimiter and add block start
+ return p1.slice(0, -2) + '{';
+ });
+ },
+ /*
+ * Process styles to add rules which will only apply under the polyfill
+ *
+ * For example, we convert this rule:
+ *
+ * (comment start) @polyfill-rule :host menu-item {
+ * ... } (comment end)
+ *
+ * to this:
+ *
+ * scopeName menu-item {...}
+ *
+ **/
+ insertPolyfillRules: function(styles) {
+ if (styles) {
+ Array.prototype.forEach.call(styles, function(s) {
+ s.textContent = this.insertPolyfillRulesInCssText(s.textContent);
+ }, this);
+ }
+ },
+ insertPolyfillRulesInCssText: function(cssText) {
+ return cssText.replace(cssPolyfillRuleCommentRe, function(match, p1) {
+ // remove end comment delimiter
+ return p1.slice(0, -1);
+ });
+ },
+ /*
+ * Process styles to add rules which will only apply under the polyfill
+ * and do not process via CSSOM. (CSSOM is destructive to rules on rare
+ * occasions, e.g. -webkit-calc on Safari.)
+ * For example, we convert this rule:
+ *
+ * (comment start) @polyfill-unscoped-rule menu-item {
+ * ... } (comment end)
+ *
+ * to this:
+ *
+ * menu-item {...}
+ *
+ **/
+ extractPolyfillUnscopedRules: function(styles) {
+ var cssText = '';
+ if (styles) {
+ Array.prototype.forEach.call(styles, function(s) {
+ cssText += this.extractPolyfillUnscopedRulesFromCssText(
+ s.textContent) + '\n\n';
+ }, this);
+ }
+ return cssText;
+ },
+ extractPolyfillUnscopedRulesFromCssText: function(cssText) {
+ var r = '', matches;
+ while (matches = cssPolyfillUnscopedRuleCommentRe.exec(cssText)) {
+ r += matches[1].slice(0, -1) + '\n\n';
+ }
+ return r;
+ },
+ /* Ensure styles are scoped. Pseudo-scoping takes a rule like:
+ *
+ * .foo {... }
+ *
+ * and converts this to
+ *
+ * scopeName .foo { ... }
+ */
+ shimScoping: function(styles, name, typeExtension) {
+ if (styles) {
+ return this.convertScopedStyles(styles, name, typeExtension);
+ }
+ },
+ convertScopedStyles: function(styles, name, typeExtension) {
+ var cssText = stylesToCssText(styles);
+ cssText = this.insertPolyfillHostInCssText(cssText);
+ cssText = this.convertColonHost(cssText);
+ cssText = this.convertColonAncestor(cssText);
+ cssText = this.convertCombinators(cssText);
+ var rules = cssToRules(cssText);
+ if (name) {
+ cssText = this.scopeRules(rules, name, typeExtension);
+ }
+ return cssText;
+ },
+ /*
+ * convert a rule like :host(.foo) > .bar { }
+ *
+ * to
+ *
+ * scopeName.foo > .bar
+ */
+ convertColonHost: function(cssText) {
+ return this.convertColonRule(cssText, cssColonHostRe,
+ this.colonHostPartReplacer);
+ },
+ /*
+ * convert a rule like :ancestor(.foo) > .bar { }
+ *
+ * to
+ *
+ * scopeName.foo > .bar, .foo scopeName > .bar { }
+ *
+ * and
+ *
+ * :ancestor(.foo:host) .bar { ... }
+ *
+ * to
+ *
+ * scopeName.foo .bar { ... }
+ */
+ convertColonAncestor: function(cssText) {
+ return this.convertColonRule(cssText, cssColonAncestorRe,
+ this.colonAncestorPartReplacer);
+ },
+ convertColonRule: function(cssText, regExp, partReplacer) {
+ // p1 = :host, p2 = contents of (), p3 rest of rule
+ return cssText.replace(regExp, function(m, p1, p2, p3) {
+ p1 = polyfillHostNoCombinator;
+ if (p2) {
+ var parts = p2.split(','), r = [];
+ for (var i=0, l=parts.length, p; (i<l) && (p=parts[i]); i++) {
+ p = p.trim();
+ r.push(partReplacer(p1, p, p3));
+ }
+ return r.join(',');
+ } else {
+ return p1 + p3;
+ }
+ });
+ },
+ colonAncestorPartReplacer: function(host, part, suffix) {
+ if (part.match(polyfillHost)) {
+ return this.colonHostPartReplacer(host, part, suffix);
+ } else {
+ return host + part + suffix + ', ' + part + ' ' + host + suffix;
+ }
+ },
+ colonHostPartReplacer: function(host, part, suffix) {
+ return host + part.replace(polyfillHost, '') + suffix;
+ },
+ /*
+ * Convert ^ and ^^ combinators by replacing with space.
+ */
+ convertCombinators: function(cssText) {
+ return cssText.replace(/\^\^/g, ' ').replace(/\^/g, ' ');
+ },
+ // change a selector like 'div' to 'name div'
+ scopeRules: function(cssRules, name, typeExtension) {
+ var cssText = '';
+ Array.prototype.forEach.call(cssRules, function(rule) {
+ if (rule.selectorText && (rule.style && rule.style.cssText)) {
+ cssText += this.scopeSelector(rule.selectorText, name, typeExtension,
+ this.strictStyling) + ' {\n\t';
+ cssText += this.propertiesFromRule(rule) + '\n}\n\n';
+ } else if (rule.media) {
+ cssText += '@media ' + rule.media.mediaText + ' {\n';
+ cssText += this.scopeRules(rule.cssRules, name, typeExtension);
+ cssText += '\n}\n\n';
+ } else if (rule.cssText) {
+ cssText += rule.cssText + '\n\n';
+ }
+ }, this);
+ return cssText;
+ },
+ scopeSelector: function(selector, name, typeExtension, strict) {
+ var r = [], parts = selector.split(',');
+ parts.forEach(function(p) {
+ p = p.trim();
+ if (this.selectorNeedsScoping(p, name, typeExtension)) {
+ p = (strict && !p.match(polyfillHostNoCombinator)) ?
+ this.applyStrictSelectorScope(p, name) :
+ this.applySimpleSelectorScope(p, name, typeExtension);
+ }
+ r.push(p);
+ }, this);
+ return r.join(', ');
+ },
+ selectorNeedsScoping: function(selector, name, typeExtension) {
+ var re = this.makeScopeMatcher(name, typeExtension);
+ return !selector.match(re);
+ },
+ makeScopeMatcher: function(name, typeExtension) {
+ var matchScope = typeExtension ? '\\[is=[\'"]?' + name + '[\'"]?\\]' : name;
+ return new RegExp('^(' + matchScope + ')' + selectorReSuffix, 'm');
+ },
+ // scope via name and [is=name]
+ applySimpleSelectorScope: function(selector, name, typeExtension) {
+ var scoper = typeExtension ? '[is=' + name + ']' : name;
+ if (selector.match(polyfillHostRe)) {
+ selector = selector.replace(polyfillHostNoCombinator, scoper);
+ return selector.replace(polyfillHostRe, scoper + ' ');
+ } else {
+ return scoper + ' ' + selector;
+ }
+ },
+ // return a selector with [name] suffix on each simple selector
+ // e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name]
+ applyStrictSelectorScope: function(selector, name) {
+ var splits = [' ', '>', '+', '~'],
+ scoped = selector,
+ attrName = '[' + name + ']';
+ splits.forEach(function(sep) {
+ var parts = scoped.split(sep);
+ scoped = parts.map(function(p) {
+ // remove :host since it should be unnecessary
+ var t = p.trim().replace(polyfillHostRe, '');
+ if (t && (splits.indexOf(t) < 0) && (t.indexOf(attrName) < 0)) {
+ p = t.replace(/([^:]*)(:*)(.*)/, '$1' + attrName + '$2$3')
+ }
+ return p;
+ }).join(sep);
+ });
+ return scoped;
+ },
+ insertPolyfillHostInCssText: function(selector) {
+ return selector.replace(hostRe, polyfillHost).replace(colonHostRe,
+ polyfillHost).replace(colonAncestorRe, polyfillAncestor);
+ },
+ propertiesFromRule: function(rule) {
+ // TODO(sorvell): Safari cssom incorrectly removes quotes from the content
+ // property. (https://bugs.webkit.org/show_bug.cgi?id=118045)
+ if (rule.style.content && !rule.style.content.match(/['"]+/)) {
+ return rule.style.cssText.replace(/content:[^;]*;/g, 'content: \'' +
+ rule.style.content + '\';');
+ }
+ return rule.style.cssText;
+ }
+};
+
+var selectorRe = /([^{]*)({[\s\S]*?})/gim,
+ cssCommentRe = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,
+ cssPolyfillCommentRe = /\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^{]*?){/gim,
+ cssPolyfillRuleCommentRe = /\/\*\s@polyfill-rule([^*]*\*+([^/*][^*]*\*+)*)\//gim,
+ cssPolyfillUnscopedRuleCommentRe = /\/\*\s@polyfill-unscoped-rule([^*]*\*+([^/*][^*]*\*+)*)\//gim,
+ cssPseudoRe = /::(x-[^\s{,(]*)/gim,
+ cssPartRe = /::part\(([^)]*)\)/gim,
+ // note: :host pre-processed to -shadowcsshost.
+ polyfillHost = '-shadowcsshost',
+ // note: :ancestor pre-processed to -shadowcssancestor.
+ polyfillAncestor = '-shadowcssancestor',
+ parenSuffix = ')(?:\\((' +
+ '(?:\\([^)(]*\\)|[^)(]*)+?' +
+ ')\\))?([^,{]*)';
+ cssColonHostRe = new RegExp('(' + polyfillHost + parenSuffix, 'gim'),
+ cssColonAncestorRe = new RegExp('(' + polyfillAncestor + parenSuffix, 'gim'),
+ selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$',
+ hostRe = /@host/gim,
+ colonHostRe = /\:host/gim,
+ colonAncestorRe = /\:ancestor/gim,
+ /* host name without combinator */
+ polyfillHostNoCombinator = polyfillHost + '-no-combinator',
+ polyfillHostRe = new RegExp(polyfillHost, 'gim');
+ polyfillAncestorRe = new RegExp(polyfillAncestor, 'gim');
+
+function stylesToCssText(styles, preserveComments) {
+ var cssText = '';
+ Array.prototype.forEach.call(styles, function(s) {
+ cssText += s.textContent + '\n\n';
+ });
+ // strip comments for easier processing
+ if (!preserveComments) {
+ cssText = cssText.replace(cssCommentRe, '');
+ }
+ return cssText;
+}
+
+function cssTextToStyle(cssText) {
+ var style = document.createElement('style');
+ style.textContent = cssText;
+ return style;
+}
+
+function cssToRules(cssText) {
+ var style = cssTextToStyle(cssText);
+ document.head.appendChild(style);
+ var rules = style.sheet.cssRules;
+ style.parentNode.removeChild(style);
+ return rules;
+}
+
+function rulesToCss(cssRules) {
+ for (var i=0, css=[]; i < cssRules.length; i++) {
+ css.push(cssRules[i].cssText);
+ }
+ return css.join('\n\n');
+}
+
+function addCssToDocument(cssText) {
+ if (cssText) {
+ getSheet().appendChild(document.createTextNode(cssText));
+ }
+}
+
+var SHIM_ATTRIBUTE = 'shim-shadowdom';
+var SHIMMED_ATTRIBUTE = 'shim-shadowdom-css';
+
+var sheet;
+function getSheet() {
+ if (!sheet) {
+ sheet = document.createElement("style");
+ sheet.setAttribute(SHIMMED_ATTRIBUTE, '');
+ sheet[SHIMMED_ATTRIBUTE] = true;
+ }
+ return sheet;
+}
+
+// add polyfill stylesheet to document
+if (window.ShadowDOMPolyfill) {
+ addCssToDocument('style { display: none !important; }\n');
+ var doc = wrap(document);
+ var head = doc.querySelector('head');
+ head.insertBefore(getSheet(), head.childNodes[0]);
+
+ // TODO(sorvell): monkey-patching HTMLImports is abusive;
+ // consider a better solution.
+ document.addEventListener('DOMContentLoaded', function() {
+ var urlResolver = scope.urlResolver;
+
+ if (window.HTMLImports && !HTMLImports.useNative) {
+ var SHIM_SHEET_SELECTOR = 'link[rel=stylesheet]' +
+ '[' + SHIM_ATTRIBUTE + ']';
+ var SHIM_STYLE_SELECTOR = 'style[' + SHIM_ATTRIBUTE + ']';
+ HTMLImports.importer.documentPreloadSelectors += ',' + SHIM_SHEET_SELECTOR;
+ HTMLImports.importer.importsPreloadSelectors += ',' + SHIM_SHEET_SELECTOR;
+
+ HTMLImports.parser.documentSelectors = [
+ HTMLImports.parser.documentSelectors,
+ SHIM_SHEET_SELECTOR,
+ SHIM_STYLE_SELECTOR
+ ].join(',');
+
+ HTMLImports.parser.parseGeneric = function(elt) {
+ if (elt[SHIMMED_ATTRIBUTE]) {
+ return;
+ }
+ var style = elt.__importElement || elt;
+ if (elt.__resource) {
+ style = elt.ownerDocument.createElement('style');
+ style.textContent = urlResolver.resolveCssText(
+ elt.__resource, elt.href);
+ } else {
+ urlResolver.resolveStyles(style);
+ }
+ var styles = [style];
+ style.textContent = ShadowCSS.stylesToShimmedCssText(styles, styles);
+ style.removeAttribute(SHIM_ATTRIBUTE, '');
+ style.setAttribute(SHIMMED_ATTRIBUTE, '');
+ style[SHIMMED_ATTRIBUTE] = true;
+ // place in document
+ if (style.parentNode !== head) {
+ // replace links in head
+ if (elt.parentNode === head) {
+ head.replaceChild(style, elt);
+ } else {
+ head.appendChild(style);
+ }
+ }
+ style.__importParsed = true
+ this.markParsingComplete(elt);
+ }
+
+ var hasResource = HTMLImports.parser.hasResource;
+ HTMLImports.parser.hasResource = function(node) {
+ if (node.localName === 'link' && node.rel === 'stylesheet' &&
+ node.hasAttribute(SHIM_ATTRIBUTE)) {
+ return (node.__resource);
+ } else {
+ return hasResource.call(this, node);
+ }
+ }
+
+ }
+ });
+}
+
+// exports
+scope.ShadowCSS = ShadowCSS;
+
+})(window.Platform);
+} else {
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+(function() {
+
+ // poor man's adapter for template.content on various platform scenarios
+ window.templateContent = window.templateContent || function(inTemplate) {
+ return inTemplate.content;
+ };
+
+ // so we can call wrap/unwrap without testing for ShadowDOMPolyfill
+
+ window.wrap = window.unwrap = function(n){
+ return n;
+ }
+
+ var originalCreateShadowRoot = Element.prototype.webkitCreateShadowRoot;
+ Element.prototype.webkitCreateShadowRoot = function() {
+ var elderRoot = this.webkitShadowRoot;
+ var root = originalCreateShadowRoot.call(this);
+ root.olderShadowRoot = elderRoot;
+ root.host = this;
+ CustomElements.watchShadow(this);
+ return root;
+ }
+
+ Object.defineProperties(Element.prototype, {
+ shadowRoot: {
+ get: function() {
+ return this.webkitShadowRoot;
+ }
+ },
+ createShadowRoot: {
+ value: function() {
+ return this.webkitCreateShadowRoot();
+ }
+ }
+ });
+
+ window.templateContent = function(inTemplate) {
+ // if MDV exists, it may need to boostrap this template to reveal content
+ if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) {
+ HTMLTemplateElement.bootstrap(inTemplate);
+ }
+ // fallback when there is no Shadow DOM polyfill, no MDV polyfill, and no
+ // native template support
+ if (!inTemplate.content && !inTemplate._content) {
+ var frag = document.createDocumentFragment();
+ while (inTemplate.firstChild) {
+ frag.appendChild(inTemplate.firstChild);
+ }
+ inTemplate._content = frag;
+ }
+ return inTemplate.content || inTemplate._content;
+ };
+
+})();
+}
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+(function(scope) {
+ 'use strict';
+
+ // feature detect for URL constructor
+ var hasWorkingUrl = false;
+ if (!scope.forceJURL) {
+ try {
+ var u = new URL('b', 'http://a');
+ hasWorkingUrl = u.href === 'http://a/b';
+ } catch(e) {}
+ }
+
+ if (hasWorkingUrl)
+ return;
+
+ var relative = Object.create(null);
+ relative['ftp'] = 21;
+ relative['file'] = 0;
+ relative['gopher'] = 70;
+ relative['http'] = 80;
+ relative['https'] = 443;
+ relative['ws'] = 80;
+ relative['wss'] = 443;
+
+ var relativePathDotMapping = Object.create(null);
+ relativePathDotMapping['%2e'] = '.';
+ relativePathDotMapping['.%2e'] = '..';
+ relativePathDotMapping['%2e.'] = '..';
+ relativePathDotMapping['%2e%2e'] = '..';
+
+ function isRelativeScheme(scheme) {
+ return relative[scheme] !== undefined;
+ }
+
+ function invalid() {
+ clear.call(this);
+ this._isInvalid = true;
+ }
+
+ function IDNAToASCII(h) {
+ if ('' == h) {
+ invalid.call(this)
+ }
+ // XXX
+ return h.toLowerCase()
+ }
+
+ function percentEscape(c) {
+ var unicode = c.charCodeAt(0);
+ if (unicode > 0x20 &&
+ unicode < 0x7F &&
+ // " # < > ? `
+ [0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) == -1
+ ) {
+ return c;
+ }
+ return encodeURIComponent(c);
+ }
+
+ function percentEscapeQuery(c) {
+ // XXX This actually needs to encode c using encoding and then
+ // convert the bytes one-by-one.
+
+ var unicode = c.charCodeAt(0);
+ if (unicode > 0x20 &&
+ unicode < 0x7F &&
+ // " # < > ` (do not escape '?')
+ [0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) == -1
+ ) {
+ return c;
+ }
+ return encodeURIComponent(c);
+ }
+
+ var EOF = undefined,
+ ALPHA = /[a-zA-Z]/,
+ ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/;
+
+ function parse(input, stateOverride, base) {
+ function err(message) {
+ errors.push(message)
+ }
+
+ var state = stateOverride || 'scheme start',
+ cursor = 0,
+ buffer = '',
+ seenAt = false,
+ seenBracket = false,
+ errors = [];
+
+ loop: while ((input[cursor - 1] != EOF || cursor == 0) && !this._isInvalid) {
+ var c = input[cursor];
+ switch (state) {
+ case 'scheme start':
+ if (c && ALPHA.test(c)) {
+ buffer += c.toLowerCase(); // ASCII-safe
+ state = 'scheme';
+ } else if (!stateOverride) {
+ buffer = '';
+ state = 'no scheme';
+ continue;
+ } else {
+ err('Invalid scheme.');
+ break loop;
+ }
+ break;
+
+ case 'scheme':
+ if (c && ALPHANUMERIC.test(c)) {
+ buffer += c.toLowerCase(); // ASCII-safe
+ } else if (':' == c) {
+ this._scheme = buffer;
+ buffer = '';
+ if (stateOverride) {
+ break loop;
+ }
+ if (isRelativeScheme(this._scheme)) {
+ this._isRelative = true;
+ }
+ if ('file' == this._scheme) {
+ state = 'relative';
+ } else if (this._isRelative && base && base._scheme == this._scheme) {
+ state = 'relative or authority';
+ } else if (this._isRelative) {
+ state = 'authority first slash';
+ } else {
+ state = 'scheme data';
+ }
+ } else if (!stateOverride) {
+ buffer = '';
+ cursor = 0;
+ state = 'no scheme';
+ continue;
+ } else if (EOF == c) {
+ break loop;
+ } else {
+ err('Code point not allowed in scheme: ' + c)
+ break loop;
+ }
+ break;
+
+ case 'scheme data':
+ if ('?' == c) {
+ query = '?';
+ state = 'query';
+ } else if ('#' == c) {
+ this._fragment = '#';
+ state = 'fragment';
+ } else {
+ // XXX error handling
+ if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
+ this._schemeData += percentEscape(c);
+ }
+ }
+ break;
+
+ case 'no scheme':
+ if (!base || !(isRelativeScheme(base._scheme))) {
+ err('Missing scheme.');
+ invalid.call(this);
+ } else {
+ state = 'relative';
+ continue;
+ }
+ break;
+
+ case 'relative or authority':
+ if ('/' == c && '/' == input[cursor+1]) {
+ state = 'authority ignore slashes';
+ } else {
+ err('Expected /, got: ' + c);
+ state = 'relative';
+ continue
+ }
+ break;
+
+ case 'relative':
+ this._isRelative = true;
+ if ('file' != this._scheme)
+ this._scheme = base._scheme;
+ if (EOF == c) {
+ this._host = base._host;
+ this._port = base._port;
+ this._path = base._path.slice();
+ this._query = base._query;
+ break loop;
+ } else if ('/' == c || '\\' == c) {
+ if ('\\' == c)
+ err('\\ is an invalid code point.');
+ state = 'relative slash';
+ } else if ('?' == c) {
+ this._host = base._host;
+ this._port = base._port;
+ this._path = base._path.slice();
+ this._query = '?';
+ state = 'query';
+ } else if ('#' == c) {
+ this._host = base._host;
+ this._port = base._port;
+ this._path = base._path.slice();
+ this._query = base._query;
+ this._fragment = '#';
+ state = 'fragment';
+ } else {
+ var nextC = input[cursor+1]
+ var nextNextC = input[cursor+2]
+ if (
+ 'file' != this._scheme || !ALPHA.test(c) ||
+ (nextC != ':' && nextC != '|') ||
+ (EOF != nextNextC && '/' != nextNextC && '\\' != nextNextC && '?' != nextNextC && '#' != nextNextC)) {
+ this._host = base._host;
+ this._port = base._port;
+ this._path = base._path.slice();
+ this._path.pop();
+ }
+ state = 'relative path';
+ continue;
+ }
+ break;
+
+ case 'relative slash':
+ if ('/' == c || '\\' == c) {
+ if ('\\' == c) {
+ err('\\ is an invalid code point.');
+ }
+ if ('file' == this._scheme) {
+ state = 'file host';
+ } else {
+ state = 'authority ignore slashes';
+ }
+ } else {
+ if ('file' != this._scheme) {
+ this._host = base._host;
+ this._port = base._port;
+ }
+ state = 'relative path';
+ continue;
+ }
+ break;
+
+ case 'authority first slash':
+ if ('/' == c) {
+ state = 'authority second slash';
+ } else {
+ err("Expected '/', got: " + c);
+ state = 'authority ignore slashes';
+ continue;
+ }
+ break;
+
+ case 'authority second slash':
+ state = 'authority ignore slashes';
+ if ('/' != c) {
+ err("Expected '/', got: " + c);
+ continue;
+ }
+ break;
+
+ case 'authority ignore slashes':
+ if ('/' != c && '\\' != c) {
+ state = 'authority';
+ continue;
+ } else {
+ err('Expected authority, got: ' + c);
+ }
+ break;
+
+ case 'authority':
+ if ('@' == c) {
+ if (seenAt) {
+ err('@ already seen.');
+ buffer += '%40';
+ }
+ seenAt = true;
+ for (var i = 0; i < buffer.length; i++) {
+ var cp = buffer[i];
+ if ('\t' == cp || '\n' == cp || '\r' == cp) {
+ err('Invalid whitespace in authority.');
+ continue;
+ }
+ // XXX check URL code points
+ if (':' == cp && null === this._password) {
+ this._password = '';
+ continue;
+ }
+ var tempC = percentEscape(cp);
+ (null !== this._password) ? this._password += tempC : this._username += tempC;
+ }
+ buffer = '';
+ } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
+ cursor -= buffer.length;
+ buffer = '';
+ state = 'host';
+ continue;
+ } else {
+ buffer += c;
+ }
+ break;
+
+ case 'file host':
+ if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
+ if (buffer.length == 2 && ALPHA.test(buffer[0]) && (buffer[1] == ':' || buffer[1] == '|')) {
+ state = 'relative path';
+ } else if (buffer.length == 0) {
+ state = 'relative path start';
+ } else {
+ this._host = IDNAToASCII.call(this, buffer);
+ buffer = '';
+ state = 'relative path start';
+ }
+ continue;
+ } else if ('\t' == c || '\n' == c || '\r' == c) {
+ err('Invalid whitespace in file host.');
+ } else {
+ buffer += c;
+ }
+ break;
+
+ case 'host':
+ case 'hostname':
+ if (':' == c && !seenBracket) {
+ // XXX host parsing
+ this._host = IDNAToASCII.call(this, buffer);
+ buffer = '';
+ state = 'port';
+ if ('hostname' == stateOverride) {
+ break loop;
+ }
+ } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
+ this._host = IDNAToASCII.call(this, buffer);
+ buffer = '';
+ state = 'relative path start';
+ if (stateOverride) {
+ break loop;
+ }
+ continue;
+ } else if ('\t' != c && '\n' != c && '\r' != c) {
+ if ('[' == c) {
+ seenBracket = true;
+ } else if (']' == c) {
+ seenBracket = false;
+ }
+ buffer += c;
+ } else {
+ err('Invalid code point in host/hostname: ' + c);
+ }
+ break;
+
+ case 'port':
+ if (/[0-9]/.test(c)) {
+ buffer += c;
+ } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c || stateOverride) {
+ if ('' != buffer) {
+ var temp = parseInt(buffer, 10);
+ if (temp != relative[this._scheme]) {
+ this._port = temp + '';
+ }
+ buffer = '';
+ }
+ if (stateOverride) {
+ break loop;
+ }
+ state = 'relative path start';
+ continue;
+ } else if ('\t' == c || '\n' == c || '\r' == c) {
+ err('Invalid code point in port: ' + c);
+ } else {
+ invalid.call(this);
+ }
+ break;
+
+ case 'relative path start':
+ if ('\\' == c)
+ err("'\\' not allowed in path.");
+ state = 'relative path';
+ if ('/' != c && '\\' != c) {
+ continue;
+ }
+ break;
+
+ case 'relative path':
+ if (EOF == c || '/' == c || '\\' == c || (!stateOverride && ('?' == c || '#' == c))) {
+ if ('\\' == c) {
+ err('\\ not allowed in relative path.');
+ }
+ var tmp;
+ if (tmp = relativePathDotMapping[buffer.toLowerCase()]) {
+ buffer = tmp;
+ }
+ if ('..' == buffer) {
+ this._path.pop();
+ if ('/' != c && '\\' != c) {
+ this._path.push('');
+ }
+ } else if ('.' == buffer && '/' != c && '\\' != c) {
+ this._path.push('');
+ } else if ('.' != buffer) {
+ if ('file' == this._scheme && this._path.length == 0 && buffer.length == 2 && ALPHA.test(buffer[0]) && buffer[1] == '|') {
+ buffer = buffer[0] + ':';
+ }
+ this._path.push(buffer);
+ }
+ buffer = '';
+ if ('?' == c) {
+ this._query = '?';
+ state = 'query';
+ } else if ('#' == c) {
+ this._fragment = '#';
+ state = 'fragment';
+ }
+ } else if ('\t' != c && '\n' != c && '\r' != c) {
+ buffer += percentEscape(c);
+ }
+ break;
+
+ case 'query':
+ if (!stateOverride && '#' == c) {
+ this._fragment = '#';
+ state = 'fragment';
+ } else if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
+ this._query += percentEscapeQuery(c);
+ }
+ break;
+
+ case 'fragment':
+ if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
+ this._fragment += c;
+ }
+ break;
+ }
+
+ cursor++;
+ }
+ }
+
+ function clear() {
+ this._scheme = '';
+ this._schemeData = '';
+ this._username = '';
+ this._password = null;
+ this._host = '';
+ this._port = '';
+ this._path = [];
+ this._query = '';
+ this._fragment = '';
+ this._isInvalid = false;
+ this._isRelative = false;
+ }
+
+ // Does not process domain names or IP addresses.
+ // Does not handle encoding for the query parameter.
+ function jURL(url, base /* , encoding */) {
+ if (base !== undefined && !(base instanceof jURL))
+ base = new jURL(String(base));
+
+ this._url = url;
+ clear.call(this);
+
+ var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, '');
+ // encoding = encoding || 'utf-8'
+
+ parse.call(this, input, null, base);
+ }
+
+ jURL.prototype = {
+ get href() {
+ if (this._isInvalid)
+ return this._url;
+
+ var authority = '';
+ if ('' != this._username || null != this._password) {
+ authority = this._username +
+ (null != this._password ? ':' + this._password : '') + '@';
+ }
+
+ return this.protocol +
+ (this._isRelative ? '//' + authority + this.host : '') +
+ this.pathname + this._query + this._fragment;
+ },
+ set href(href) {
+ clear.call(this);
+ parse.call(this, href);
+ },
+
+ get protocol() {
+ return this._scheme + ':';
+ },
+ set protocol(protocol) {
+ if (this._isInvalid)
+ return;
+ parse.call(this, protocol + ':', 'scheme start');
+ },
+
+ get host() {
+ return this._isInvalid ? '' : this._port ?
+ this._host + ':' + this._port : this._host;
+ },
+ set host(host) {
+ if (this._isInvalid || !this._isRelative)
+ return;
+ parse.call(this, host, 'host');
+ },
+
+ get hostname() {
+ return this._host;
+ },
+ set hostname(hostname) {
+ if (this._isInvalid || !this._isRelative)
+ return;
+ parse.call(this, hostname, 'hostname');
+ },
+
+ get port() {
+ return this._port;
+ },
+ set port(port) {
+ if (this._isInvalid || !this._isRelative)
+ return;
+ parse.call(this, port, 'port');
+ },
+
+ get pathname() {
+ return this._isInvalid ? '' : this._isRelative ?
+ '/' + this._path.join('/') : this._schemeData;
+ },
+ set pathname(pathname) {
+ if (this._isInvalid || !this._isRelative)
+ return;
+ this._path = [];
+ parse.call(this, pathname, 'relative path start');
+ },
+
+ get search() {
+ return this._isInvalid || !this._query || '?' == this._query ?
+ '' : this._query;
+ },
+ set search(search) {
+ if (this._isInvalid || !this._isRelative)
+ return;
+ this._query = '?';
+ if ('?' == search[0])
+ search = search.slice(1);
+ parse.call(this, search, 'query');
+ },
+
+ get hash() {
+ return this._isInvalid || !this._fragment || '#' == this._fragment ?
+ '' : this._fragment;
+ },
+ set hash(hash) {
+ if (this._isInvalid)
+ return;
+ this._fragment = '#';
+ if ('#' == hash[0])
+ hash = hash.slice(1);
+ parse.call(this, hash, 'fragment');
+ }
+ };
+
+ scope.URL = jURL;
+
+})(window);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+(function(scope) {
+
+// Old versions of iOS do not have bind.
+
+if (!Function.prototype.bind) {
+ Function.prototype.bind = function(scope) {
+ var self = this;
+ var args = Array.prototype.slice.call(arguments, 1);
+ return function() {
+ var args2 = args.slice();
+ args2.push.apply(args2, arguments);
+ return self.apply(scope, args2);
+ };
+ };
+}
+
+// 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);
+ }
+}
+
+// export
+
+scope.mixin = mixin;
+
+})(window.Platform);
+// Copyright 2011 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(scope) {
+
+ 'use strict';
+
+ // 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;
+
+ // polyfill performance.now
+
+ if (!window.performance) {
+ var start = Date.now();
+ // only at millisecond precision
+ window.performance = {now: function(){ return Date.now() - start }};
+ }
+
+ // polyfill for requestAnimationFrame
+
+ if (!window.requestAnimationFrame) {
+ window.requestAnimationFrame = (function() {
+ var nativeRaf = window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame;
+
+ return nativeRaf ?
+ function(callback) {
+ return nativeRaf(function() {
+ callback(performance.now());
+ });
+ } :
+ function( callback ){
+ return window.setTimeout(callback, 1000 / 60);
+ };
+ })();
+ }
+
+ if (!window.cancelAnimationFrame) {
+ window.cancelAnimationFrame = (function() {
+ return window.webkitCancelAnimationFrame ||
+ window.mozCancelAnimationFrame ||
+ function(id) {
+ clearTimeout(id);
+ };
+ })();
+ }
+
+ // TODO(sorvell): workaround for bug:
+ // https://code.google.com/p/chromium/issues/detail?id=229142
+ // remove when this bug is addressed
+ // give main document templates a base that allows them to fetch eagerly
+ // resolved paths relative to the main document
+ var template = document.createElement('template');
+ var base = document.createElement('base');
+ base.href = document.baseURI;
+ template.content.ownerDocument.appendChild(base);
+
+
+ // 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;
+ }
+ // Make a stub for Polymer() for polyfill purposes; under the HTMLImports
+ // polyfill, scripts in the main document run before imports. That means
+ // if (1) polymer is imported and (2) Polymer() is called in the main document
+ // in a script after the import, 2 occurs before 1. We correct this here
+ // by specfiically patching Polymer(); this is not necessary under native
+ // HTMLImports.
+ var elementDeclarations = [];
+
+ var polymerStub = function(name, dictionary) {
+ elementDeclarations.push(arguments);
+ }
+ window.Polymer = polymerStub;
+
+ // deliver queued delcarations
+ scope.deliverDeclarations = function() {
+ scope.deliverDeclarations = null;
+ return elementDeclarations;
+ }
+
+ // Once DOMContent has loaded, any main document scripts that depend on
+ // Polymer() should have run. Calling Polymer() now is an error until
+ // polymer is imported.
+ window.addEventListener('DOMContentLoaded', function() {
+ if (window.Polymer === polymerStub) {
+ window.Polymer = function() {
+ console.error('You tried to use polymer without loading it first. To ' +
+ 'load polymer, <link rel="import" href="' +
+ 'components/polymer/polymer.html">');
+ };
+ }
+ });
+
+ // exports
+ scope.createDOM = createDOM;
+
+})(window.Platform);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+// poor man's adapter for template.content on various platform scenarios
+window.templateContent = window.templateContent || function(inTemplate) {
+ return inTemplate.content;
+};
+(function(scope) {
+
+ scope = scope || (window.Inspector = {});
+
+ var inspector;
+
+ window.sinspect = function(inNode, inProxy) {
+ if (!inspector) {
+ inspector = window.open('', 'ShadowDOM Inspector', null, true);
+ inspector.document.write(inspectorHTML);
+ //inspector.document.close();
+ inspector.api = {
+ shadowize: shadowize
+ };
+ }
+ inspect(inNode || wrap(document.body), inProxy);
+ };
+
+ var inspectorHTML = [
+ '<!DOCTYPE html>',
+ '<html>',
+ ' <head>',
+ ' <title>ShadowDOM Inspector</title>',
+ ' <style>',
+ ' body {',
+ ' }',
+ ' pre {',
+ ' font: 9pt "Courier New", monospace;',
+ ' line-height: 1.5em;',
+ ' }',
+ ' tag {',
+ ' color: purple;',
+ ' }',
+ ' ul {',
+ ' margin: 0;',
+ ' padding: 0;',
+ ' list-style: none;',
+ ' }',
+ ' li {',
+ ' display: inline-block;',
+ ' background-color: #f1f1f1;',
+ ' padding: 4px 6px;',
+ ' border-radius: 4px;',
+ ' margin-right: 4px;',
+ ' }',
+ ' </style>',
+ ' </head>',
+ ' <body>',
+ ' <ul id="crumbs">',
+ ' </ul>',
+ ' <div id="tree"></div>',
+ ' </body>',
+ '</html>'
+ ].join('\n');
+
+ var crumbs = [];
+
+ var displayCrumbs = function() {
+ // alias our document
+ var d = inspector.document;
+ // get crumbbar
+ var cb = d.querySelector('#crumbs');
+ // clear crumbs
+ cb.textContent = '';
+ // build new crumbs
+ for (var i=0, c; c=crumbs[i]; i++) {
+ var a = d.createElement('a');
+ a.href = '#';
+ a.textContent = c.localName;
+ a.idx = i;
+ a.onclick = function(event) {
+ var c;
+ while (crumbs.length > this.idx) {
+ c = crumbs.pop();
+ }
+ inspect(c.shadow || c, c);
+ event.preventDefault();
+ };
+ cb.appendChild(d.createElement('li')).appendChild(a);
+ }
+ };
+
+ var inspect = function(inNode, inProxy) {
+ // alias our document
+ var d = inspector.document;
+ // reset list of drillable nodes
+ drillable = [];
+ // memoize our crumb proxy
+ var proxy = inProxy || inNode;
+ crumbs.push(proxy);
+ // update crumbs
+ displayCrumbs();
+ // reflect local tree
+ d.body.querySelector('#tree').innerHTML =
+ '<pre>' + output(inNode, inNode.childNodes) + '</pre>';
+ };
+
+ var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
+
+ var blacklisted = {STYLE:1, SCRIPT:1, "#comment": 1, TEMPLATE: 1};
+ var blacklist = function(inNode) {
+ return blacklisted[inNode.nodeName];
+ };
+
+ var output = function(inNode, inChildNodes, inIndent) {
+ if (blacklist(inNode)) {
+ return '';
+ }
+ var indent = inIndent || '';
+ if (inNode.localName || inNode.nodeType == 11) {
+ var name = inNode.localName || 'shadow-root';
+ //inChildNodes = ShadowDOM.localNodes(inNode);
+ var info = indent + describe(inNode);
+ // if only textNodes
+ // TODO(sjmiles): make correct for ShadowDOM
+ /*if (!inNode.children.length && inNode.localName !== 'content' && inNode.localName !== 'shadow') {
+ info += catTextContent(inChildNodes);
+ } else*/ {
+ // TODO(sjmiles): native <shadow> has no reference to its projection
+ if (name == 'content' /*|| name == 'shadow'*/) {
+ inChildNodes = inNode.getDistributedNodes();
+ }
+ info += '<br/>';
+ var ind = indent + '&nbsp;&nbsp;';
+ forEach(inChildNodes, function(n) {
+ info += output(n, n.childNodes, ind);
+ });
+ info += indent;
+ }
+ if (!({br:1}[name])) {
+ info += '<tag>&lt;/' + name + '&gt;</tag>';
+ info += '<br/>';
+ }
+ } else {
+ var text = inNode.textContent.trim();
+ info = text ? indent + '"' + text + '"' + '<br/>' : '';
+ }
+ return info;
+ };
+
+ var catTextContent = function(inChildNodes) {
+ var info = '';
+ forEach(inChildNodes, function(n) {
+ info += n.textContent.trim();
+ });
+ return info;
+ };
+
+ var drillable = [];
+
+ var describe = function(inNode) {
+ var tag = '<tag>' + '&lt;';
+ var name = inNode.localName || 'shadow-root';
+ if (inNode.webkitShadowRoot || inNode.shadowRoot) {
+ tag += ' <button idx="' + drillable.length +
+ '" onclick="api.shadowize.call(this)">' + name + '</button>';
+ drillable.push(inNode);
+ } else {
+ tag += name || 'shadow-root';
+ }
+ if (inNode.attributes) {
+ forEach(inNode.attributes, function(a) {
+ tag += ' ' + a.name + (a.value ? '="' + a.value + '"' : '');
+ });
+ }
+ tag += '&gt;'+ '</tag>';
+ return tag;
+ };
+
+ // remote api
+
+ shadowize = function() {
+ var idx = Number(this.attributes.idx.value);
+ //alert(idx);
+ var node = drillable[idx];
+ if (node) {
+ inspect(node.webkitShadowRoot || node.shadowRoot, node)
+ } else {
+ console.log("bad shadowize node");
+ console.dir(this);
+ }
+ };
+
+ // export
+
+ scope.output = output;
+
+})(window.Inspector);
+
+
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+(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);
+
+(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:
+ module = dependsOrFactory.apply(this);
+ break;
+ default:
+ 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;
+ scope.module = module;
+ scope.using = using;
+
+})(window);
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+(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 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+(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);
+ }
+ }
+ }
+ },
+ 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) {
+ cssText = replaceUrlsInCssText(cssText, baseUrl, CSS_URL_REGEXP);
+ return replaceUrlsInCssText(cssText, baseUrl, 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];
+ if (attr && attr.value &&
+ (attr.value.search(URL_TEMPLATE_SEARCH) < 0)) {
+ var urlPath = resolveRelativeUrl(url, attr.value);
+ attr.value = urlPath;
+ }
+ });
+ }
+};
+
+var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g;
+var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g;
+var URL_ATTRS = ['href', 'src', 'action'];
+var URL_ATTRS_SELECTOR = '[' + URL_ATTRS.join('],[') + ']';
+var URL_TEMPLATE_SEARCH = '{{.*}}';
+
+function replaceUrlsInCssText(cssText, baseUrl, regexp) {
+ return cssText.replace(regexp, function(m, pre, url, post) {
+ var urlPath = url.replace(/["']/g, '');
+ urlPath = resolveRelativeUrl(baseUrl, urlPath);
+ return pre + '\'' + urlPath + '\'' + post;
+ });
+}
+
+function resolveRelativeUrl(baseUrl, url) {
+ var u = new URL(url, baseUrl);
+ return makeDocumentRelPath(u.href);
+}
+
+function makeDocumentRelPath(url) {
+ var root = document.baseURI;
+ var u = new URL(url, root);
+ if (u.host === root.host && u.port === root.port &&
+ u.protocol === root.protocol) {
+ return makeRelPath(root.pathname, u.pathname);
+ } else {
+ return url;
+ }
+}
+
+// make a relative path from source to target
+function makeRelPath(source, target) {
+ 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('/');
+}
+
+// exports
+scope.urlResolver = urlResolver;
+
+})(Platform);
+
+/*
+ * Copyright 2012 The Polymer Authors. All rights reserved.
+ * Use of this source code is goverened by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+(function(global) {
+
+ var registrationsTable = new WeakMap();
+
+ // We use setImmediate or postMessage for our future callback.
+ var setImmediate = window.msSetImmediate;
+
+ // Use post message to emulate setImmediate.
+ if (!setImmediate) {
+ var setImmediateQueue = [];
+ var sentinel = String(Math.random());
+ window.addEventListener('message', function(e) {
+ if (e.data === sentinel) {
+ var queue = setImmediateQueue;
+ setImmediateQueue = [];
+ queue.forEach(function(func) {
+ func();
+ });
+ }
+ });
+ setImmediate = function(func) {
+ setImmediateQueue.push(func);
+ window.postMessage(sentinel, '*');
+ };
+ }
+
+ // This is used to ensure that we never schedule 2 callas to setImmediate
+ var isScheduled = false;
+
+ // Keep track of observers that needs to be notified next time.
+ var scheduledObservers = [];
+
+ /**
+ * Schedules |dispatchCallback| to be called in the future.
+ * @param {MutationObserver} observer
+ */
+ function scheduleCallback(observer) {
+ scheduledObservers.push(observer);
+ if (!isScheduled) {
+ isScheduled = true;
+ setImmediate(dispatchCallbacks);
+ }
+ }
+
+ function wrapIfNeeded(node) {
+ return window.ShadowDOMPolyfill &&
+ window.ShadowDOMPolyfill.wrapIfNeeded(node) ||
+ node;
+ }
+
+ function dispatchCallbacks() {
+ // http://dom.spec.whatwg.org/#mutation-observers
+
+ isScheduled = false; // Used to allow a new setImmediate call above.
+
+ var observers = scheduledObservers;
+ scheduledObservers = [];
+ // Sort observers based on their creation UID (incremental).
+ observers.sort(function(o1, o2) {
+ return o1.uid_ - o2.uid_;
+ });
+
+ var anyNonEmpty = false;
+ observers.forEach(function(observer) {
+
+ // 2.1, 2.2
+ var queue = observer.takeRecords();
+ // 2.3. Remove all transient registered observers whose observer is mo.
+ removeTransientObserversFor(observer);
+
+ // 2.4
+ if (queue.length) {
+ observer.callback_(queue, observer);
+ anyNonEmpty = true;
+ }
+ });
+
+ // 3.
+ if (anyNonEmpty)
+ dispatchCallbacks();
+ }
+
+ function removeTransientObserversFor(observer) {
+ observer.nodes_.forEach(function(node) {
+ var registrations = registrationsTable.get(node);
+ if (!registrations)
+ return;
+ registrations.forEach(function(registration) {
+ if (registration.observer === observer)
+ registration.removeTransientObservers();
+ });
+ });
+ }
+
+ /**
+ * This function is used for the "For each registered observer observer (with
+ * observer's options as options) in target's list of registered observers,
+ * run these substeps:" and the "For each ancestor ancestor of target, and for
+ * each registered observer observer (with options options) in ancestor's list
+ * of registered observers, run these substeps:" part of the algorithms. The
+ * |options.subtree| is checked to ensure that the callback is called
+ * correctly.
+ *
+ * @param {Node} target
+ * @param {function(MutationObserverInit):MutationRecord} callback
+ */
+ function forEachAncestorAndObserverEnqueueRecord(target, callback) {
+ for (var node = target; node; node = node.parentNode) {
+ var registrations = registrationsTable.get(node);
+
+ if (registrations) {
+ for (var j = 0; j < registrations.length; j++) {
+ var registration = registrations[j];
+ var options = registration.options;
+
+ // Only target ignores subtree.
+ if (node !== target && !options.subtree)
+ continue;
+
+ var record = callback(options);
+ if (record)
+ registration.enqueue(record);
+ }
+ }
+ }
+ }
+
+ var uidCounter = 0;
+
+ /**
+ * The class that maps to the DOM MutationObserver interface.
+ * @param {Function} callback.
+ * @constructor
+ */
+ function JsMutationObserver(callback) {
+ this.callback_ = callback;
+ this.nodes_ = [];
+ this.records_ = [];
+ this.uid_ = ++uidCounter;
+ }
+
+ JsMutationObserver.prototype = {
+ observe: function(target, options) {
+ target = wrapIfNeeded(target);
+
+ // 1.1
+ if (!options.childList && !options.attributes && !options.characterData ||
+
+ // 1.2
+ options.attributeOldValue && !options.attributes ||
+
+ // 1.3
+ options.attributeFilter && options.attributeFilter.length &&
+ !options.attributes ||
+
+ // 1.4
+ options.characterDataOldValue && !options.characterData) {
+
+ throw new SyntaxError();
+ }
+
+ var registrations = registrationsTable.get(target);
+ if (!registrations)
+ registrationsTable.set(target, registrations = []);
+
+ // 2
+ // If target's list of registered observers already includes a registered
+ // observer associated with the context object, replace that registered
+ // observer's options with options.
+ var registration;
+ for (var i = 0; i < registrations.length; i++) {
+ if (registrations[i].observer === this) {
+ registration = registrations[i];
+ registration.removeListeners();
+ registration.options = options;
+ break;
+ }
+ }
+
+ // 3.
+ // Otherwise, add a new registered observer to target's list of registered
+ // observers with the context object as the observer and options as the
+ // options, and add target to context object's list of nodes on which it
+ // is registered.
+ if (!registration) {
+ registration = new Registration(this, target, options);
+ registrations.push(registration);
+ this.nodes_.push(target);
+ }
+
+ registration.addListeners();
+ },
+
+ disconnect: function() {
+ this.nodes_.forEach(function(node) {
+ var registrations = registrationsTable.get(node);
+ for (var i = 0; i < registrations.length; i++) {
+ var registration = registrations[i];
+ if (registration.observer === this) {
+ registration.removeListeners();
+ registrations.splice(i, 1);
+ // Each node can only have one registered observer associated with
+ // this observer.
+ break;
+ }
+ }
+ }, this);
+ this.records_ = [];
+ },
+
+ takeRecords: function() {
+ var copyOfRecords = this.records_;
+ this.records_ = [];
+ return copyOfRecords;
+ }
+ };
+
+ /**
+ * @param {string} type
+ * @param {Node} target
+ * @constructor
+ */
+ function MutationRecord(type, target) {
+ this.type = type;
+ this.target = target;
+ this.addedNodes = [];
+ this.removedNodes = [];
+ this.previousSibling = null;
+ this.nextSibling = null;
+ this.attributeName = null;
+ this.attributeNamespace = null;
+ this.oldValue = null;
+ }
+
+ function copyMutationRecord(original) {
+ var record = new MutationRecord(original.type, original.target);
+ record.addedNodes = original.addedNodes.slice();
+ record.removedNodes = original.removedNodes.slice();
+ record.previousSibling = original.previousSibling;
+ record.nextSibling = original.nextSibling;
+ record.attributeName = original.attributeName;
+ record.attributeNamespace = original.attributeNamespace;
+ record.oldValue = original.oldValue;
+ return record;
+ };
+
+ // We keep track of the two (possibly one) records used in a single mutation.
+ var currentRecord, recordWithOldValue;
+
+ /**
+ * Creates a record without |oldValue| and caches it as |currentRecord| for
+ * later use.
+ * @param {string} oldValue
+ * @return {MutationRecord}
+ */
+ function getRecord(type, target) {
+ return currentRecord = new MutationRecord(type, target);
+ }
+
+ /**
+ * Gets or creates a record with |oldValue| based in the |currentRecord|
+ * @param {string} oldValue
+ * @return {MutationRecord}
+ */
+ function getRecordWithOldValue(oldValue) {
+ if (recordWithOldValue)
+ return recordWithOldValue;
+ recordWithOldValue = copyMutationRecord(currentRecord);
+ recordWithOldValue.oldValue = oldValue;
+ return recordWithOldValue;
+ }
+
+ function clearRecords() {
+ currentRecord = recordWithOldValue = undefined;
+ }
+
+ /**
+ * @param {MutationRecord} record
+ * @return {boolean} Whether the record represents a record from the current
+ * mutation event.
+ */
+ function recordRepresentsCurrentMutation(record) {
+ return record === recordWithOldValue || record === currentRecord;
+ }
+
+ /**
+ * Selects which record, if any, to replace the last record in the queue.
+ * This returns |null| if no record should be replaced.
+ *
+ * @param {MutationRecord} lastRecord
+ * @param {MutationRecord} newRecord
+ * @param {MutationRecord}
+ */
+ function selectRecord(lastRecord, newRecord) {
+ if (lastRecord === newRecord)
+ return lastRecord;
+
+ // Check if the the record we are adding represents the same record. If
+ // so, we keep the one with the oldValue in it.
+ if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord))
+ return recordWithOldValue;
+
+ return null;
+ }
+
+ /**
+ * Class used to represent a registered observer.
+ * @param {MutationObserver} observer
+ * @param {Node} target
+ * @param {MutationObserverInit} options
+ * @constructor
+ */
+ function Registration(observer, target, options) {
+ this.observer = observer;
+ this.target = target;
+ this.options = options;
+ this.transientObservedNodes = [];
+ }
+
+ Registration.prototype = {
+ enqueue: function(record) {
+ var records = this.observer.records_;
+ var length = records.length;
+
+ // There are cases where we replace the last record with the new record.
+ // For example if the record represents the same mutation we need to use
+ // the one with the oldValue. If we get same record (this can happen as we
+ // walk up the tree) we ignore the new record.
+ if (records.length > 0) {
+ var lastRecord = records[length - 1];
+ var recordToReplaceLast = selectRecord(lastRecord, record);
+ if (recordToReplaceLast) {
+ records[length - 1] = recordToReplaceLast;
+ return;
+ }
+ } else {
+ scheduleCallback(this.observer);
+ }
+
+ records[length] = record;
+ },
+
+ addListeners: function() {
+ this.addListeners_(this.target);
+ },
+
+ addListeners_: function(node) {
+ var options = this.options;
+ if (options.attributes)
+ node.addEventListener('DOMAttrModified', this, true);
+
+ if (options.characterData)
+ node.addEventListener('DOMCharacterDataModified', this, true);
+
+ if (options.childList)
+ node.addEventListener('DOMNodeInserted', this, true);
+
+ if (options.childList || options.subtree)
+ node.addEventListener('DOMNodeRemoved', this, true);
+ },
+
+ removeListeners: function() {
+ this.removeListeners_(this.target);
+ },
+
+ removeListeners_: function(node) {
+ var options = this.options;
+ if (options.attributes)
+ node.removeEventListener('DOMAttrModified', this, true);
+
+ if (options.characterData)
+ node.removeEventListener('DOMCharacterDataModified', this, true);
+
+ if (options.childList)
+ node.removeEventListener('DOMNodeInserted', this, true);
+
+ if (options.childList || options.subtree)
+ node.removeEventListener('DOMNodeRemoved', this, true);
+ },
+
+ /**
+ * Adds a transient observer on node. The transient observer gets removed
+ * next time we deliver the change records.
+ * @param {Node} node
+ */
+ addTransientObserver: function(node) {
+ // Don't add transient observers on the target itself. We already have all
+ // the required listeners set up on the target.
+ if (node === this.target)
+ return;
+
+ this.addListeners_(node);
+ this.transientObservedNodes.push(node);
+ var registrations = registrationsTable.get(node);
+ if (!registrations)
+ registrationsTable.set(node, registrations = []);
+
+ // We know that registrations does not contain this because we already
+ // checked if node === this.target.
+ registrations.push(this);
+ },
+
+ removeTransientObservers: function() {
+ var transientObservedNodes = this.transientObservedNodes;
+ this.transientObservedNodes = [];
+
+ transientObservedNodes.forEach(function(node) {
+ // Transient observers are never added to the target.
+ this.removeListeners_(node);
+
+ var registrations = registrationsTable.get(node);
+ for (var i = 0; i < registrations.length; i++) {
+ if (registrations[i] === this) {
+ registrations.splice(i, 1);
+ // Each node can only have one registered observer associated with
+ // this observer.
+ break;
+ }
+ }
+ }, this);
+ },
+
+ handleEvent: function(e) {
+ // Stop propagation since we are managing the propagation manually.
+ // This means that other mutation events on the page will not work
+ // correctly but that is by design.
+ e.stopImmediatePropagation();
+
+ switch (e.type) {
+ case 'DOMAttrModified':
+ // http://dom.spec.whatwg.org/#concept-mo-queue-attributes
+
+ var name = e.attrName;
+ var namespace = e.relatedNode.namespaceURI;
+ var target = e.target;
+
+ // 1.
+ var record = new getRecord('attributes', target);
+ record.attributeName = name;
+ record.attributeNamespace = namespace;
+
+ // 2.
+ var oldValue =
+ e.attrChange === MutationEvent.ADDITION ? null : e.prevValue;
+
+ forEachAncestorAndObserverEnqueueRecord(target, function(options) {
+ // 3.1, 4.2
+ if (!options.attributes)
+ return;
+
+ // 3.2, 4.3
+ if (options.attributeFilter && options.attributeFilter.length &&
+ options.attributeFilter.indexOf(name) === -1 &&
+ options.attributeFilter.indexOf(namespace) === -1) {
+ return;
+ }
+ // 3.3, 4.4
+ if (options.attributeOldValue)
+ return getRecordWithOldValue(oldValue);
+
+ // 3.4, 4.5
+ return record;
+ });
+
+ break;
+
+ case 'DOMCharacterDataModified':
+ // http://dom.spec.whatwg.org/#concept-mo-queue-characterdata
+ var target = e.target;
+
+ // 1.
+ var record = getRecord('characterData', target);
+
+ // 2.
+ var oldValue = e.prevValue;
+
+
+ forEachAncestorAndObserverEnqueueRecord(target, function(options) {
+ // 3.1, 4.2
+ if (!options.characterData)
+ return;
+
+ // 3.2, 4.3
+ if (options.characterDataOldValue)
+ return getRecordWithOldValue(oldValue);
+
+ // 3.3, 4.4
+ return record;
+ });
+
+ break;
+
+ case 'DOMNodeRemoved':
+ this.addTransientObserver(e.target);
+ // Fall through.
+ case 'DOMNodeInserted':
+ // http://dom.spec.whatwg.org/#concept-mo-queue-childlist
+ var target = e.relatedNode;
+ var changedNode = e.target;
+ var addedNodes, removedNodes;
+ if (e.type === 'DOMNodeInserted') {
+ addedNodes = [changedNode];
+ removedNodes = [];
+ } else {
+
+ addedNodes = [];
+ removedNodes = [changedNode];
+ }
+ var previousSibling = changedNode.previousSibling;
+ var nextSibling = changedNode.nextSibling;
+
+ // 1.
+ var record = getRecord('childList', target);
+ record.addedNodes = addedNodes;
+ record.removedNodes = removedNodes;
+ record.previousSibling = previousSibling;
+ record.nextSibling = nextSibling;
+
+ forEachAncestorAndObserverEnqueueRecord(target, function(options) {
+ // 2.1, 3.2
+ if (!options.childList)
+ return;
+
+ // 2.2, 3.3
+ return record;
+ });
+
+ }
+
+ clearRecords();
+ }
+ };
+
+ global.JsMutationObserver = JsMutationObserver;
+
+ if (!global.MutationObserver)
+ global.MutationObserver = JsMutationObserver;
+
+
+})(this);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+window.HTMLImports = window.HTMLImports || {flags:{}};
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+(function(scope) {
+
+ // imports
+ var path = scope.path;
+ var xhr = scope.xhr;
+ var flags = scope.flags;
+
+ // TODO(sorvell): this loader supports a dynamic list of urls
+ // and an oncomplete callback that is called when the loader is done.
+ // The polyfill currently does *not* need this dynamism or the onComplete
+ // concept. Because of this, the loader could be simplified quite a bit.
+ var Loader = function(onLoad, onComplete) {
+ this.cache = {};
+ this.onload = onLoad;
+ this.oncomplete = onComplete;
+ this.inflight = 0;
+ this.pending = {};
+ };
+
+ Loader.prototype = {
+ addNodes: function(nodes) {
+ // number of transactions to complete
+ this.inflight += nodes.length;
+ // commence transactions
+ for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) {
+ this.require(n);
+ }
+ // anything to do?
+ this.checkDone();
+ },
+ addNode: function(node) {
+ // number of transactions to complete
+ this.inflight++;
+ // commence transactions
+ this.require(node);
+ // anything to do?
+ this.checkDone();
+ },
+ require: function(elt) {
+ var url = elt.src || elt.href;
+ // ensure we have a standard url that can be used
+ // reliably for deduping.
+ // TODO(sjmiles): ad-hoc
+ elt.__nodeUrl = url;
+ // deduplication
+ if (!this.dedupe(url, elt)) {
+ // fetch this resource
+ this.fetch(url, elt);
+ }
+ },
+ dedupe: function(url, elt) {
+ if (this.pending[url]) {
+ // add to list of nodes waiting for inUrl
+ this.pending[url].push(elt);
+ // don't need fetch
+ return true;
+ }
+ var resource;
+ if (this.cache[url]) {
+ this.onload(url, elt, this.cache[url]);
+ // finished this transaction
+ this.tail();
+ // don't need fetch
+ return true;
+ }
+ // first node waiting for inUrl
+ this.pending[url] = [elt];
+ // need fetch (not a dupe)
+ return false;
+ },
+ fetch: function(url, elt) {
+ flags.load && console.log('fetch', url, elt);
+ var receiveXhr = function(err, resource) {
+ this.receive(url, elt, err, resource);
+ }.bind(this);
+ xhr.load(url, receiveXhr);
+ // TODO(sorvell): blocked on
+ // https://code.google.com/p/chromium/issues/detail?id=257221
+ // xhr'ing for a document makes scripts in imports runnable; otherwise
+ // they are not; however, it requires that we have doctype=html in
+ // the import which is unacceptable. This is only needed on Chrome
+ // to avoid the bug above.
+ /*
+ if (isDocumentLink(elt)) {
+ xhr.loadDocument(url, receiveXhr);
+ } else {
+ xhr.load(url, receiveXhr);
+ }
+ */
+ },
+ receive: function(url, elt, err, resource) {
+ this.cache[url] = resource;
+ var $p = this.pending[url];
+ for (var i=0, l=$p.length, p; (i<l) && (p=$p[i]); i++) {
+ //if (!err) {
+ this.onload(url, p, resource);
+ //}
+ this.tail();
+ }
+ this.pending[url] = null;
+ },
+ tail: function() {
+ --this.inflight;
+ this.checkDone();
+ },
+ checkDone: function() {
+ if (!this.inflight) {
+ this.oncomplete();
+ }
+ }
+ };
+
+ xhr = xhr || {
+ async: true,
+ ok: function(request) {
+ return (request.status >= 200 && request.status < 300)
+ || (request.status === 304)
+ || (request.status === 0);
+ },
+ load: function(url, next, nextContext) {
+ var request = new XMLHttpRequest();
+ if (scope.flags.debug || scope.flags.bust) {
+ url += '?' + Math.random();
+ }
+ request.open('GET', url, xhr.async);
+ request.addEventListener('readystatechange', function(e) {
+ if (request.readyState === 4) {
+ next.call(nextContext, !xhr.ok(request) && request,
+ request.response || request.responseText, url);
+ }
+ });
+ request.send();
+ return request;
+ },
+ loadDocument: function(url, next, nextContext) {
+ this.load(url, next, nextContext).responseType = 'document';
+ }
+ };
+
+ // exports
+ scope.xhr = xhr;
+ scope.Loader = Loader;
+
+})(window.HTMLImports);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+(function(scope) {
+
+var IMPORT_LINK_TYPE = 'import';
+var flags = scope.flags;
+var isIe = /Trident/.test(navigator.userAgent);
+// TODO(sorvell): SD polyfill intrusion
+var mainDoc = window.ShadowDOMPolyfill ?
+ window.ShadowDOMPolyfill.wrapIfNeeded(document) : document;
+
+// importParser
+// highlander object to manage parsing of imports
+// parses import related elements
+// and ensures proper parse order
+// parse order is enforced by crawling the tree and monitoring which elements
+// have been parsed; async parsing is also supported.
+
+// highlander object for parsing a document tree
+var importParser = {
+ // parse selectors for main document elements
+ documentSelectors: 'link[rel=' + IMPORT_LINK_TYPE + ']',
+ // parse selectors for import document elements
+ importsSelectors: [
+ 'link[rel=' + IMPORT_LINK_TYPE + ']',
+ 'link[rel=stylesheet]',
+ 'style',
+ 'script:not([type])',
+ 'script[type="text/javascript"]'
+ ].join(','),
+ map: {
+ link: 'parseLink',
+ script: 'parseScript',
+ style: 'parseStyle'
+ },
+ // try to parse the next import in the tree
+ parseNext: function() {
+ var next = this.nextToParse();
+ if (next) {
+ this.parse(next);
+ }
+ },
+ parse: function(elt) {
+ if (this.isParsed(elt)) {
+ flags.parse && console.log('[%s] is already parsed', elt.localName);
+ return;
+ }
+ var fn = this[this.map[elt.localName]];
+ if (fn) {
+ this.markParsing(elt);
+ fn.call(this, elt);
+ }
+ },
+ // only 1 element may be parsed at a time; parsing is async so, each
+ // parsing implementation must inform the system that parsing is complete
+ // via markParsingComplete.
+ markParsing: function(elt) {
+ flags.parse && console.log('parsing', elt);
+ this.parsingElement = elt;
+ },
+ markParsingComplete: function(elt) {
+ elt.__importParsed = true;
+ if (elt.__importElement) {
+ elt.__importElement.__importParsed = true;
+ }
+ this.parsingElement = null;
+ flags.parse && console.log('completed', elt);
+ this.parseNext();
+ },
+ parseImport: function(elt) {
+ elt.import.__importParsed = true;
+ // TODO(sorvell): consider if there's a better way to do this;
+ // expose an imports parsing hook; this is needed, for example, by the
+ // CustomElements polyfill.
+ if (HTMLImports.__importsParsingHook) {
+ HTMLImports.__importsParsingHook(elt);
+ }
+ // fire load event
+ if (elt.__resource) {
+ elt.dispatchEvent(new CustomEvent('load', {bubbles: false}));
+ } else {
+ elt.dispatchEvent(new CustomEvent('error', {bubbles: false}));
+ }
+ // TODO(sorvell): workaround for Safari addEventListener not working
+ // for elements not in the main document.
+ if (elt.__pending) {
+ var fn;
+ while (elt.__pending.length) {
+ fn = elt.__pending.shift();
+ if (fn) {
+ fn({target: elt});
+ }
+ }
+ }
+ this.markParsingComplete(elt);
+ },
+ parseLink: function(linkElt) {
+ if (nodeIsImport(linkElt)) {
+ this.parseImport(linkElt);
+ } else {
+ // make href absolute
+ linkElt.href = linkElt.href;
+ this.parseGeneric(linkElt);
+ }
+ },
+ parseStyle: function(elt) {
+ // TODO(sorvell): style element load event can just not fire so clone styles
+ var src = elt;
+ elt = cloneStyle(elt);
+ elt.__importElement = src;
+ this.parseGeneric(elt);
+ },
+ parseGeneric: function(elt) {
+ this.trackElement(elt);
+ document.head.appendChild(elt);
+ },
+ // tracks when a loadable element has loaded
+ trackElement: function(elt) {
+ var self = this;
+ var done = function() {
+ self.markParsingComplete(elt);
+ };
+ elt.addEventListener('load', done);
+ elt.addEventListener('error', done);
+
+ // NOTE: IE does not fire "load" event for styles that have already loaded
+ // This is in violation of the spec, so we try our hardest to work around it
+ if (isIe && elt.localName === 'style') {
+ var fakeLoad = false;
+ // If there's not @import in the textContent, assume it has loaded
+ if (elt.textContent.indexOf('@import') == -1) {
+ fakeLoad = true;
+ // if we have a sheet, we have been parsed
+ } else if (elt.sheet) {
+ fakeLoad = true;
+ var csr = elt.sheet.cssRules;
+ var len = csr ? csr.length : 0;
+ // search the rules for @import's
+ for (var i = 0, r; (i < len) && (r = csr[i]); i++) {
+ if (r.type === CSSRule.IMPORT_RULE) {
+ // if every @import has resolved, fake the load
+ fakeLoad = fakeLoad && Boolean(r.styleSheet);
+ }
+ }
+ }
+ // dispatch a fake load event and continue parsing
+ if (fakeLoad) {
+ elt.dispatchEvent(new CustomEvent('load', {bubbles: false}));
+ }
+ }
+ },
+ parseScript: function(scriptElt) {
+ // acquire code to execute
+ var code = (scriptElt.__resource || scriptElt.textContent).trim();
+ if (code) {
+ // calculate source map hint
+ var moniker = scriptElt.__nodeUrl;
+ if (!moniker) {
+ moniker = scriptElt.ownerDocument.baseURI;
+ // there could be more than one script this url
+ var tag = '[' + Math.floor((Math.random()+1)*1000) + ']';
+ // TODO(sjmiles): Polymer hack, should be pluggable if we need to allow
+ // this sort of thing
+ var matches = code.match(/Polymer\(['"]([^'"]*)/);
+ tag = matches && matches[1] || tag;
+ // tag the moniker
+ moniker += '/' + tag + '.js';
+ }
+ // source map hint
+ code += "\n//# sourceURL=" + moniker + "\n";
+ // evaluate the code
+ scope.currentScript = scriptElt;
+ eval.call(window, code);
+ scope.currentScript = null;
+ }
+ this.markParsingComplete(scriptElt);
+ },
+ // determine the next element in the tree which should be parsed
+ nextToParse: function() {
+ return !this.parsingElement && this.nextToParseInDoc(mainDoc);
+ },
+ nextToParseInDoc: function(doc, link) {
+ var nodes = doc.querySelectorAll(this.parseSelectorsForNode(doc));
+ for (var i=0, l=nodes.length, p=0, n; (i<l) && (n=nodes[i]); i++) {
+ if (!this.isParsed(n)) {
+ if (this.hasResource(n)) {
+ return nodeIsImport(n) ? this.nextToParseInDoc(n.import, n) : n;
+ } else {
+ return;
+ }
+ }
+ }
+ // all nodes have been parsed, ready to parse import, if any
+ return link;
+ },
+ // return the set of parse selectors relevant for this node.
+ parseSelectorsForNode: function(node) {
+ var doc = node.ownerDocument || node;
+ return doc === mainDoc ? this.documentSelectors : this.importsSelectors;
+ },
+ isParsed: function(node) {
+ return node.__importParsed;
+ },
+ hasResource: function(node) {
+ if (nodeIsImport(node) && !node.import) {
+ return false;
+ }
+ if (node.localName === 'script' && node.src && !node.__resource) {
+ return false;
+ }
+ return true;
+ }
+};
+
+function nodeIsImport(elt) {
+ return (elt.localName === 'link') && (elt.rel === IMPORT_LINK_TYPE);
+}
+
+// style/stylesheet handling
+
+// clone style with proper path resolution for main document
+// NOTE: styles are the only elements that require direct path fixup.
+function cloneStyle(style) {
+ var clone = style.ownerDocument.createElement('style');
+ clone.textContent = style.textContent;
+ path.resolveUrlsInStyle(clone);
+ return clone;
+}
+
+// path fixup: style elements in imports must be made relative to the main
+// document. We fixup url's in url() and @import.
+var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g;
+var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g;
+
+var path = {
+ resolveUrlsInStyle: function(style) {
+ var doc = style.ownerDocument;
+ var resolver = doc.createElement('a');
+ style.textContent = this.resolveUrlsInCssText(style.textContent, resolver);
+ return style;
+ },
+ resolveUrlsInCssText: function(cssText, urlObj) {
+ var r = this.replaceUrls(cssText, urlObj, CSS_URL_REGEXP);
+ r = this.replaceUrls(r, urlObj, CSS_IMPORT_REGEXP);
+ return r;
+ },
+ replaceUrls: function(text, urlObj, regexp) {
+ return text.replace(regexp, function(m, pre, url, post) {
+ var urlPath = url.replace(/["']/g, '');
+ urlObj.href = urlPath;
+ urlPath = urlObj.href;
+ return pre + '\'' + urlPath + '\'' + post;
+ });
+ }
+}
+
+// exports
+scope.parser = importParser;
+scope.path = path;
+scope.isIE = isIe;
+
+})(HTMLImports);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+(function(scope) {
+
+var hasNative = ('import' in document.createElement('link'));
+var useNative = hasNative;
+var flags = scope.flags;
+var IMPORT_LINK_TYPE = 'import';
+
+// TODO(sorvell): SD polyfill intrusion
+var mainDoc = window.ShadowDOMPolyfill ?
+ ShadowDOMPolyfill.wrapIfNeeded(document) : document;
+
+if (!useNative) {
+
+ // imports
+ var xhr = scope.xhr;
+ var Loader = scope.Loader;
+ var parser = scope.parser;
+
+ // importer
+ // highlander object to manage loading of imports
+
+ // for any document, importer:
+ // - loads any linked import documents (with deduping)
+ // for any import document, importer also:
+ // - loads text of external script tags
+
+ var importer = {
+ documents: {},
+ // nodes to load in the mian document
+ documentPreloadSelectors: 'link[rel=' + IMPORT_LINK_TYPE + ']',
+ // nodes to load in imports
+ importsPreloadSelectors: [
+ 'link[rel=' + IMPORT_LINK_TYPE + ']',
+ 'script[src]:not([type])',
+ 'script[src][type="text/javascript"]'
+ ].join(','),
+ loadNode: function(node) {
+ importLoader.addNode(node);
+ },
+ // load all loadable elements within the parent element
+ loadSubtree: function(parent) {
+ var nodes = this.marshalNodes(parent);
+ // add these nodes to loader's queue
+ importLoader.addNodes(nodes);
+ },
+ marshalNodes: function(parent) {
+ // all preloadable nodes in inDocument
+ return parent.querySelectorAll(this.loadSelectorsForNode(parent));
+ },
+ // find the proper set of load selectors for a given node
+ loadSelectorsForNode: function(node) {
+ var doc = node.ownerDocument || node;
+ return doc === mainDoc ? this.documentPreloadSelectors :
+ this.importsPreloadSelectors;
+ },
+ loaded: function(url, elt, resource) {
+ flags.load && console.log('loaded', url, elt);
+ // store generic resource
+ // TODO(sorvell): fails for nodes inside <template>.content
+ // see https://code.google.com/p/chromium/issues/detail?id=249381.
+ elt.__resource = resource;
+ if (isDocumentLink(elt)) {
+ var doc = this.documents[url];
+ // if we've never seen a document at this url
+ if (!doc) {
+ // generate an HTMLDocument from data
+ doc = makeDocument(resource, url);
+ doc.__importLink = elt;
+ // TODO(sorvell): we cannot use MO to detect parsed nodes because
+ // SD polyfill does not report these as mutations.
+ this.bootDocument(doc);
+ // cache document
+ this.documents[url] = doc;
+ }
+ // don't store import record until we're actually loaded
+ // store document resource
+ elt.import = doc;
+ }
+ parser.parseNext();
+ },
+ bootDocument: function(doc) {
+ this.loadSubtree(doc);
+ this.observe(doc);
+ parser.parseNext();
+ },
+ loadedAll: function() {
+ parser.parseNext();
+ }
+ };
+
+ // loader singleton
+ var importLoader = new Loader(importer.loaded.bind(importer),
+ importer.loadedAll.bind(importer));
+
+ function isDocumentLink(elt) {
+ return isLinkRel(elt, IMPORT_LINK_TYPE);
+ }
+
+ function isLinkRel(elt, rel) {
+ return elt.localName === 'link' && elt.getAttribute('rel') === rel;
+ }
+
+ function isScript(elt) {
+ return elt.localName === 'script';
+ }
+
+ function makeDocument(resource, url) {
+ // create a new HTML document
+ var doc = resource;
+ if (!(doc instanceof Document)) {
+ doc = document.implementation.createHTMLDocument(IMPORT_LINK_TYPE);
+ }
+ // cache the new document's source url
+ doc._URL = url;
+ // establish a relative path via <base>
+ var base = doc.createElement('base');
+ base.setAttribute('href', url);
+ // add baseURI support to browsers (IE) that lack it.
+ if (!doc.baseURI) {
+ doc.baseURI = url;
+ }
+ doc.head.appendChild(base);
+ // install HTML last as it may trigger CustomElement upgrades
+ // TODO(sjmiles): problem wrt to template boostrapping below,
+ // template bootstrapping must (?) come before element upgrade
+ // but we cannot bootstrap templates until they are in a document
+ // which is too late
+ if (!(resource instanceof Document)) {
+ // install html
+ doc.body.innerHTML = resource;
+ }
+ // TODO(sorvell): ideally this code is not aware of Template polyfill,
+ // but for now the polyfill needs help to bootstrap these templates
+ if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) {
+ HTMLTemplateElement.bootstrap(doc);
+ }
+ return doc;
+ }
+} else {
+ // do nothing if using native imports
+ var importer = {};
+}
+
+// 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() {
+ return HTMLImports.currentScript || document.currentScript;
+ },
+ configurable: true
+};
+
+Object.defineProperty(document, '_currentScript', currentScriptDescriptor);
+Object.defineProperty(mainDoc, '_currentScript', currentScriptDescriptor);
+
+// Polyfill document.baseURI for browsers without it.
+if (!document.baseURI) {
+ var baseURIDescriptor = {
+ get: function() {
+ return window.location.href;
+ },
+ configurable: true
+ };
+
+ Object.defineProperty(document, 'baseURI', baseURIDescriptor);
+ Object.defineProperty(mainDoc, 'baseURI', baseURIDescriptor);
+}
+
+// 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 = HTMLImports.isIE ? 'complete' : 'interactive';
+var READY_EVENT = 'readystatechange';
+function isDocumentReady(doc) {
+ return (doc.readyState === 'complete' ||
+ doc.readyState === requiredReadyState);
+}
+
+// call <callback> when we ensure the document is in a ready state
+function whenDocumentReady(callback, doc) {
+ if (!isDocumentReady(doc)) {
+ var checkReady = function() {
+ if (doc.readyState === 'complete' ||
+ doc.readyState === requiredReadyState) {
+ doc.removeEventListener(READY_EVENT, checkReady);
+ whenDocumentReady(callback, doc);
+ }
+ }
+ doc.addEventListener(READY_EVENT, checkReady);
+ } else if (callback) {
+ callback();
+ }
+}
+
+// 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) {
+ // go async to ensure parser isn't stuck on a script tag
+ requestAnimationFrame(callback);
+ }
+ }
+ function loadedImport(e) {
+ loaded++;
+ checkDone();
+ }
+ if (l) {
+ for (var i=0, imp; (i<l) && (imp=imports[i]); i++) {
+ if (isImportLoaded(imp)) {
+ loadedImport.call(imp);
+ } else {
+ imp.addEventListener('load', loadedImport);
+ imp.addEventListener('error', loadedImport);
+ }
+ }
+ } else {
+ checkDone();
+ }
+}
+
+function isImportLoaded(link) {
+ return useNative ? (link.import && (link.import.readyState !== 'loading')) :
+ link.__importParsed;
+}
+
+// exports
+scope.hasNative = hasNative;
+scope.useNative = useNative;
+scope.importer = importer;
+scope.whenImportsReady = whenImportsReady;
+scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE;
+scope.isImportLoaded = isImportLoaded;
+scope.importLoader = importLoader;
+
+})(window.HTMLImports);
+
+ /*
+Copyright 2013 The Polymer Authors. All rights reserved.
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file.
+*/
+
+(function(scope){
+
+var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE;
+var importSelector = 'link[rel=' + IMPORT_LINK_TYPE + ']';
+var importer = scope.importer;
+
+// we track mutations for addedNodes, looking for imports
+function handler(mutations) {
+ for (var i=0, l=mutations.length, m; (i<l) && (m=mutations[i]); i++) {
+ if (m.type === 'childList' && m.addedNodes.length) {
+ addedNodes(m.addedNodes);
+ }
+ }
+}
+
+// find loadable elements and add them to the importer
+function addedNodes(nodes) {
+ for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) {
+ if (shouldLoadNode(n)) {
+ importer.loadNode(n);
+ }
+ if (n.children && n.children.length) {
+ addedNodes(n.children);
+ }
+ }
+}
+
+function shouldLoadNode(node) {
+ return (node.nodeType === 1) && matches.call(node,
+ importer.loadSelectorsForNode(node));
+}
+
+// x-plat matches
+var matches = HTMLElement.prototype.matches ||
+ HTMLElement.prototype.matchesSelector ||
+ HTMLElement.prototype.webkitMatchesSelector ||
+ HTMLElement.prototype.mozMatchesSelector ||
+ HTMLElement.prototype.msMatchesSelector;
+
+var observer = new MutationObserver(handler);
+
+// observe the given root for loadable elements
+function observe(root) {
+ observer.observe(root, {childList: true, subtree: true});
+}
+
+// exports
+// TODO(sorvell): factor so can put on scope
+scope.observe = observe;
+importer.observe = observe;
+
+})(HTMLImports);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+(function(){
+
+// bootstrap
+
+// IE shim for CustomEvent
+if (typeof window.CustomEvent !== 'function') {
+ window.CustomEvent = function(inType, dictionary) {
+ var e = document.createEvent('HTMLEvents');
+ e.initEvent(inType,
+ dictionary.bubbles === false ? false : true,
+ dictionary.cancelable === false ? false : true,
+ dictionary.detail);
+ return e;
+ };
+}
+
+// TODO(sorvell): SD polyfill intrusion
+var doc = window.ShadowDOMPolyfill ?
+ window.ShadowDOMPolyfill.wrapIfNeeded(document) : document;
+
+// 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.
+HTMLImports.whenImportsReady(function() {
+ HTMLImports.ready = true;
+ HTMLImports.readyTime = new Date().getTime();
+ doc.dispatchEvent(
+ new CustomEvent('HTMLImportsLoaded', {bubbles: true})
+ );
+});
+
+
+// no need to bootstrap the polyfill when native imports is available.
+if (!HTMLImports.useNative) {
+ function bootstrap() {
+ HTMLImports.importer.bootDocument(doc);
+ }
+
+ // TODO(sorvell): SD polyfill does *not* generate mutations for nodes added
+ // by the parser. For this reason, we must wait until the dom exists to
+ // bootstrap.
+ if (document.readyState === 'complete' ||
+ (document.readyState === 'interactive' && !window.attachEvent)) {
+ bootstrap();
+ } else {
+ document.addEventListener('DOMContentLoaded', bootstrap);
+ }
+}
+
+})();
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+window.CustomElements = window.CustomElements || {flags:{}};
+ /*
+Copyright 2013 The Polymer Authors. All rights reserved.
+Use of this source code is governed by a BSD-style
+license that can be found in the LICENSE file.
+*/
+
+(function(scope){
+
+var logFlags = window.logFlags || {};
+var IMPORT_LINK_TYPE = window.HTMLImports ? HTMLImports.IMPORT_LINK_TYPE : 'none';
+
+// walk the subtree rooted at node, applying 'find(element, data)' function
+// to each element
+// if 'find' returns true for 'element', do not search element's subtree
+function findAll(node, find, data) {
+ var e = node.firstElementChild;
+ if (!e) {
+ e = node.firstChild;
+ while (e && e.nodeType !== Node.ELEMENT_NODE) {
+ e = e.nextSibling;
+ }
+ }
+ while (e) {
+ if (find(e, data) !== true) {
+ findAll(e, find, data);
+ }
+ e = e.nextElementSibling;
+ }
+ return null;
+}
+
+// walk all shadowRoots on a given node.
+function forRoots(node, cb) {
+ var root = node.shadowRoot;
+ while(root) {
+ forSubtree(root, cb);
+ root = root.olderShadowRoot;
+ }
+}
+
+// walk the subtree rooted at node, including descent into shadow-roots,
+// applying 'cb' to each element
+function forSubtree(node, cb) {
+ //logFlags.dom && node.childNodes && node.childNodes.length && console.group('subTree: ', node);
+ findAll(node, function(e) {
+ if (cb(e)) {
+ return true;
+ }
+ forRoots(e, cb);
+ });
+ forRoots(node, cb);
+ //logFlags.dom && node.childNodes && node.childNodes.length && console.groupEnd();
+}
+
+// manage lifecycle on added node
+function added(node) {
+ if (upgrade(node)) {
+ insertedNode(node);
+ return true;
+ }
+ inserted(node);
+}
+
+// manage lifecycle on added node's subtree only
+function addedSubtree(node) {
+ forSubtree(node, function(e) {
+ if (added(e)) {
+ return true;
+ }
+ });
+}
+
+// manage lifecycle on added node and it's subtree
+function addedNode(node) {
+ return added(node) || addedSubtree(node);
+}
+
+// upgrade custom elements at node, if applicable
+function upgrade(node) {
+ if (!node.__upgraded__ && node.nodeType === Node.ELEMENT_NODE) {
+ var type = node.getAttribute('is') || node.localName;
+ var definition = scope.registry[type];
+ if (definition) {
+ logFlags.dom && console.group('upgrade:', node.localName);
+ scope.upgrade(node);
+ logFlags.dom && console.groupEnd();
+ return true;
+ }
+ }
+}
+
+function insertedNode(node) {
+ inserted(node);
+ if (inDocument(node)) {
+ forSubtree(node, function(e) {
+ inserted(e);
+ });
+ }
+}
+
+
+// TODO(sorvell): on platforms without MutationObserver, mutations may not be
+// reliable and therefore attached/detached are not reliable.
+// To make these callbacks less likely to fail, we defer all inserts and removes
+// to give a chance for elements to be inserted into dom.
+// This ensures attachedCallback fires for elements that are created and
+// immediately added to dom.
+var hasPolyfillMutations = (!window.MutationObserver ||
+ (window.MutationObserver === window.JsMutationObserver));
+scope.hasPolyfillMutations = hasPolyfillMutations;
+
+var isPendingMutations = false;
+var pendingMutations = [];
+function deferMutation(fn) {
+ pendingMutations.push(fn);
+ if (!isPendingMutations) {
+ isPendingMutations = true;
+ var async = (window.Platform && window.Platform.endOfMicrotask) ||
+ setTimeout;
+ async(takeMutations);
+ }
+}
+
+function takeMutations() {
+ isPendingMutations = false;
+ var $p = pendingMutations;
+ for (var i=0, l=$p.length, p; (i<l) && (p=$p[i]); i++) {
+ p();
+ }
+ pendingMutations = [];
+}
+
+function inserted(element) {
+ if (hasPolyfillMutations) {
+ deferMutation(function() {
+ _inserted(element);
+ });
+ } else {
+ _inserted(element);
+ }
+}
+
+// TODO(sjmiles): if there are descents into trees that can never have inDocument(*) true, fix this
+function _inserted(element) {
+ // TODO(sjmiles): it's possible we were inserted and removed in the space
+ // of one microtask, in which case we won't be 'inDocument' here
+ // But there are other cases where we are testing for inserted without
+ // specific knowledge of mutations, and must test 'inDocument' to determine
+ // whether to call inserted
+ // If we can factor these cases into separate code paths we can have
+ // better diagnostics.
+ // TODO(sjmiles): when logging, do work on all custom elements so we can
+ // track behavior even when callbacks not defined
+ //console.log('inserted: ', element.localName);
+ if (element.attachedCallback || element.detachedCallback || (element.__upgraded__ && logFlags.dom)) {
+ logFlags.dom && console.group('inserted:', element.localName);
+ if (inDocument(element)) {
+ element.__inserted = (element.__inserted || 0) + 1;
+ // if we are in a 'removed' state, bluntly adjust to an 'inserted' state
+ if (element.__inserted < 1) {
+ element.__inserted = 1;
+ }
+ // if we are 'over inserted', squelch the callback
+ if (element.__inserted > 1) {
+ logFlags.dom && console.warn('inserted:', element.localName,
+ 'insert/remove count:', element.__inserted)
+ } else if (element.attachedCallback) {
+ logFlags.dom && console.log('inserted:', element.localName);
+ element.attachedCallback();
+ }
+ }
+ logFlags.dom && console.groupEnd();
+ }
+}
+
+function removedNode(node) {
+ removed(node);
+ forSubtree(node, function(e) {
+ removed(e);
+ });
+}
+
+function removed(element) {
+ if (hasPolyfillMutations) {
+ deferMutation(function() {
+ _removed(element);
+ });
+ } else {
+ _removed(element);
+ }
+}
+
+function _removed(element) {
+ // TODO(sjmiles): temporary: do work on all custom elements so we can track
+ // behavior even when callbacks not defined
+ if (element.attachedCallback || element.detachedCallback || (element.__upgraded__ && logFlags.dom)) {
+ logFlags.dom && console.group('removed:', element.localName);
+ if (!inDocument(element)) {
+ element.__inserted = (element.__inserted || 0) - 1;
+ // if we are in a 'inserted' state, bluntly adjust to an 'removed' state
+ if (element.__inserted > 0) {
+ element.__inserted = 0;
+ }
+ // if we are 'over removed', squelch the callback
+ if (element.__inserted < 0) {
+ logFlags.dom && console.warn('removed:', element.localName,
+ 'insert/remove count:', element.__inserted)
+ } else if (element.detachedCallback) {
+ element.detachedCallback();
+ }
+ }
+ logFlags.dom && console.groupEnd();
+ }
+}
+
+// SD polyfill intrustion due mainly to the fact that 'document'
+// is not entirely wrapped
+function wrapIfNeeded(node) {
+ return window.ShadowDOMPolyfill ? ShadowDOMPolyfill.wrapIfNeeded(node)
+ : node;
+}
+
+function inDocument(element) {
+ var p = element;
+ var doc = wrapIfNeeded(document);
+ while (p) {
+ if (p == doc) {
+ return true;
+ }
+ p = p.parentNode || p.host;
+ }
+}
+
+function watchShadow(node) {
+ if (node.shadowRoot && !node.shadowRoot.__watched) {
+ logFlags.dom && console.log('watching shadow-root for: ', node.localName);
+ // watch all unwatched roots...
+ var root = node.shadowRoot;
+ while (root) {
+ watchRoot(root);
+ root = root.olderShadowRoot;
+ }
+ }
+}
+
+function watchRoot(root) {
+ if (!root.__watched) {
+ observe(root);
+ root.__watched = true;
+ }
+}
+
+function handler(mutations) {
+ //
+ if (logFlags.dom) {
+ var mx = mutations[0];
+ if (mx && mx.type === 'childList' && mx.addedNodes) {
+ if (mx.addedNodes) {
+ var d = mx.addedNodes[0];
+ while (d && d !== document && !d.host) {
+ d = d.parentNode;
+ }
+ var u = d && (d.URL || d._URL || (d.host && d.host.localName)) || '';
+ u = u.split('/?').shift().split('/').pop();
+ }
+ }
+ console.group('mutations (%d) [%s]', mutations.length, u || '');
+ }
+ //
+ mutations.forEach(function(mx) {
+ //logFlags.dom && console.group('mutation');
+ if (mx.type === 'childList') {
+ forEach(mx.addedNodes, function(n) {
+ //logFlags.dom && console.log(n.localName);
+ if (!n.localName) {
+ return;
+ }
+ // nodes added may need lifecycle management
+ addedNode(n);
+ });
+ // removed nodes may need lifecycle management
+ forEach(mx.removedNodes, function(n) {
+ //logFlags.dom && console.log(n.localName);
+ if (!n.localName) {
+ return;
+ }
+ removedNode(n);
+ });
+ }
+ //logFlags.dom && console.groupEnd();
+ });
+ logFlags.dom && console.groupEnd();
+};
+
+var observer = new MutationObserver(handler);
+
+function takeRecords() {
+ // TODO(sjmiles): ask Raf why we have to call handler ourselves
+ handler(observer.takeRecords());
+ takeMutations();
+}
+
+var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
+
+function observe(inRoot) {
+ observer.observe(inRoot, {childList: true, subtree: true});
+}
+
+function observeDocument(doc) {
+ observe(doc);
+}
+
+function upgradeDocument(doc) {
+ logFlags.dom && console.group('upgradeDocument: ', (doc.baseURI).split('/').pop());
+ addedNode(doc);
+ logFlags.dom && console.groupEnd();
+}
+
+function upgradeDocumentTree(doc) {
+ doc = wrapIfNeeded(doc);
+ upgradeDocument(doc);
+ //console.log('upgradeDocumentTree: ', (doc.baseURI).split('/').pop());
+ // upgrade contained imported documents
+ var imports = doc.querySelectorAll('link[rel=' + IMPORT_LINK_TYPE + ']');
+ for (var i=0, l=imports.length, n; (i<l) && (n=imports[i]); i++) {
+ if (n.import && n.import.__parsed) {
+ upgradeDocumentTree(n.import);
+ }
+ }
+}
+
+// exports
+scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE;
+scope.watchShadow = watchShadow;
+scope.upgradeDocumentTree = upgradeDocumentTree;
+scope.upgradeAll = addedNode;
+scope.upgradeSubtree = addedSubtree;
+
+scope.observeDocument = observeDocument;
+scope.upgradeDocument = upgradeDocument;
+
+scope.takeRecords = takeRecords;
+
+})(window.CustomElements);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+/**
+ * Implements `document.register`
+ * @module CustomElements
+*/
+
+/**
+ * Polyfilled extensions to the `document` object.
+ * @class Document
+*/
+
+(function(scope) {
+
+// imports
+
+if (!scope) {
+ scope = window.CustomElements = {flags:{}};
+}
+var flags = scope.flags;
+
+// native document.registerElement?
+
+var hasNative = Boolean(document.registerElement);
+// TODO(sorvell): See https://github.com/Polymer/polymer/issues/399
+// we'll address this by defaulting to CE polyfill in the presence of the SD
+// polyfill. This will avoid spamming excess attached/detached callbacks.
+// If there is a compelling need to run CE native with SD polyfill,
+// we'll need to fix this issue.
+var useNative = !flags.register && hasNative && !window.ShadowDOMPolyfill;
+
+if (useNative) {
+
+ // stub
+ var nop = function() {};
+
+ // exports
+ scope.registry = {};
+ scope.upgradeElement = nop;
+
+ scope.watchShadow = nop;
+ scope.upgrade = nop;
+ scope.upgradeAll = nop;
+ scope.upgradeSubtree = nop;
+ scope.observeDocument = nop;
+ scope.upgradeDocument = nop;
+ scope.upgradeDocumentTree = nop;
+ scope.takeRecords = nop;
+
+} else {
+
+ /**
+ * Registers a custom tag name with the document.
+ *
+ * When a registered element is created, a `readyCallback` method is called
+ * in the scope of the element. The `readyCallback` method can be specified on
+ * either `options.prototype` or `options.lifecycle` with the latter taking
+ * precedence.
+ *
+ * @method register
+ * @param {String} name The tag name to register. Must include a dash ('-'),
+ * for example 'x-component'.
+ * @param {Object} options
+ * @param {String} [options.extends]
+ * (_off spec_) Tag name of an element to extend (or blank for a new
+ * element). This parameter is not part of the specification, but instead
+ * is a hint for the polyfill because the extendee is difficult to infer.
+ * Remember that the input prototype must chain to the extended element's
+ * prototype (or HTMLElement.prototype) regardless of the value of
+ * `extends`.
+ * @param {Object} options.prototype The prototype to use for the new
+ * element. The prototype must inherit from HTMLElement.
+ * @param {Object} [options.lifecycle]
+ * Callbacks that fire at important phases in the life of the custom
+ * element.
+ *
+ * @example
+ * FancyButton = document.registerElement("fancy-button", {
+ * extends: 'button',
+ * prototype: Object.create(HTMLButtonElement.prototype, {
+ * readyCallback: {
+ * value: function() {
+ * console.log("a fancy-button was created",
+ * }
+ * }
+ * })
+ * });
+ * @return {Function} Constructor for the newly registered type.
+ */
+ function register(name, options) {
+ //console.warn('document.registerElement("' + name + '", ', options, ')');
+ // construct a defintion out of options
+ // TODO(sjmiles): probably should clone options instead of mutating it
+ var definition = options || {};
+ if (!name) {
+ // TODO(sjmiles): replace with more appropriate error (EricB can probably
+ // offer guidance)
+ throw new Error('document.registerElement: first argument `name` must not be empty');
+ }
+ if (name.indexOf('-') < 0) {
+ // TODO(sjmiles): replace with more appropriate error (EricB can probably
+ // offer guidance)
+ throw new Error('document.registerElement: first argument (\'name\') must contain a dash (\'-\'). Argument provided was \'' + String(name) + '\'.');
+ }
+ // elements may only be registered once
+ if (getRegisteredDefinition(name)) {
+ throw new Error('DuplicateDefinitionError: a type with name \'' + String(name) + '\' is already registered');
+ }
+ // must have a prototype, default to an extension of HTMLElement
+ // TODO(sjmiles): probably should throw if no prototype, check spec
+ if (!definition.prototype) {
+ // TODO(sjmiles): replace with more appropriate error (EricB can probably
+ // offer guidance)
+ throw new Error('Options missing required prototype property');
+ }
+ // record name
+ definition.__name = name.toLowerCase();
+ // ensure a lifecycle object so we don't have to null test it
+ definition.lifecycle = definition.lifecycle || {};
+ // build a list of ancestral custom elements (for native base detection)
+ // TODO(sjmiles): we used to need to store this, but current code only
+ // uses it in 'resolveTagName': it should probably be inlined
+ definition.ancestry = ancestry(definition.extends);
+ // extensions of native specializations of HTMLElement require localName
+ // to remain native, and use secondary 'is' specifier for extension type
+ resolveTagName(definition);
+ // some platforms require modifications to the user-supplied prototype
+ // chain
+ resolvePrototypeChain(definition);
+ // overrides to implement attributeChanged callback
+ overrideAttributeApi(definition.prototype);
+ // 7.1.5: Register the DEFINITION with DOCUMENT
+ registerDefinition(definition.__name, definition);
+ // 7.1.7. Run custom element constructor generation algorithm with PROTOTYPE
+ // 7.1.8. Return the output of the previous step.
+ definition.ctor = generateConstructor(definition);
+ definition.ctor.prototype = definition.prototype;
+ // force our .constructor to be our actual constructor
+ definition.prototype.constructor = definition.ctor;
+ // if initial parsing is complete
+ if (scope.ready) {
+ // upgrade any pre-existing nodes of this type
+ scope.upgradeDocumentTree(document);
+ }
+ return definition.ctor;
+ }
+
+ function ancestry(extnds) {
+ var extendee = getRegisteredDefinition(extnds);
+ if (extendee) {
+ return ancestry(extendee.extends).concat([extendee]);
+ }
+ return [];
+ }
+
+ function resolveTagName(definition) {
+ // if we are explicitly extending something, that thing is our
+ // baseTag, unless it represents a custom component
+ var baseTag = definition.extends;
+ // if our ancestry includes custom components, we only have a
+ // baseTag if one of them does
+ for (var i=0, a; (a=definition.ancestry[i]); i++) {
+ baseTag = a.is && a.tag;
+ }
+ // our tag is our baseTag, if it exists, and otherwise just our name
+ definition.tag = baseTag || definition.__name;
+ if (baseTag) {
+ // if there is a base tag, use secondary 'is' specifier
+ definition.is = definition.__name;
+ }
+ }
+
+ function resolvePrototypeChain(definition) {
+ // if we don't support __proto__ we need to locate the native level
+ // prototype for precise mixing in
+ if (!Object.__proto__) {
+ // default prototype
+ var nativePrototype = HTMLElement.prototype;
+ // work out prototype when using type-extension
+ if (definition.is) {
+ var inst = document.createElement(definition.tag);
+ nativePrototype = Object.getPrototypeOf(inst);
+ }
+ // ensure __proto__ reference is installed at each point on the prototype
+ // chain.
+ // NOTE: On platforms without __proto__, a mixin strategy is used instead
+ // of prototype swizzling. In this case, this generated __proto__ provides
+ // limited support for prototype traversal.
+ var proto = definition.prototype, ancestor;
+ while (proto && (proto !== nativePrototype)) {
+ var ancestor = Object.getPrototypeOf(proto);
+ proto.__proto__ = ancestor;
+ proto = ancestor;
+ }
+ }
+ // cache this in case of mixin
+ definition.native = nativePrototype;
+ }
+
+ // SECTION 4
+
+ function instantiate(definition) {
+ // 4.a.1. Create a new object that implements PROTOTYPE
+ // 4.a.2. Let ELEMENT by this new object
+ //
+ // the custom element instantiation algorithm must also ensure that the
+ // output is a valid DOM element with the proper wrapper in place.
+ //
+ return upgrade(domCreateElement(definition.tag), definition);
+ }
+
+ function upgrade(element, definition) {
+ // some definitions specify an 'is' attribute
+ if (definition.is) {
+ element.setAttribute('is', definition.is);
+ }
+ // remove 'unresolved' attr, which is a standin for :unresolved.
+ element.removeAttribute('unresolved');
+ // make 'element' implement definition.prototype
+ implement(element, definition);
+ // flag as upgraded
+ element.__upgraded__ = true;
+ // lifecycle management
+ created(element);
+ // there should never be a shadow root on element at this point
+ // we require child nodes be upgraded before `created`
+ scope.upgradeSubtree(element);
+ // OUTPUT
+ return element;
+ }
+
+ function implement(element, definition) {
+ // prototype swizzling is best
+ if (Object.__proto__) {
+ element.__proto__ = definition.prototype;
+ } else {
+ // where above we can re-acquire inPrototype via
+ // getPrototypeOf(Element), we cannot do so when
+ // we use mixin, so we install a magic reference
+ customMixin(element, definition.prototype, definition.native);
+ element.__proto__ = definition.prototype;
+ }
+ }
+
+ function customMixin(inTarget, inSrc, inNative) {
+ // TODO(sjmiles): 'used' allows us to only copy the 'youngest' version of
+ // any property. This set should be precalculated. We also need to
+ // consider this for supporting 'super'.
+ var used = {};
+ // start with inSrc
+ var p = inSrc;
+ // sometimes the default is HTMLUnknownElement.prototype instead of
+ // HTMLElement.prototype, so we add a test
+ // the idea is to avoid mixing in native prototypes, so adding
+ // the second test is WLOG
+ while (p !== inNative && p !== HTMLUnknownElement.prototype) {
+ var keys = Object.getOwnPropertyNames(p);
+ for (var i=0, k; k=keys[i]; i++) {
+ if (!used[k]) {
+ Object.defineProperty(inTarget, k,
+ Object.getOwnPropertyDescriptor(p, k));
+ used[k] = 1;
+ }
+ }
+ p = Object.getPrototypeOf(p);
+ }
+ }
+
+ function created(element) {
+ // invoke createdCallback
+ if (element.createdCallback) {
+ element.createdCallback();
+ }
+ }
+
+ // attribute watching
+
+ function overrideAttributeApi(prototype) {
+ // overrides to implement callbacks
+ // TODO(sjmiles): should support access via .attributes NamedNodeMap
+ // TODO(sjmiles): preserves user defined overrides, if any
+ if (prototype.setAttribute._polyfilled) {
+ return;
+ }
+ var setAttribute = prototype.setAttribute;
+ prototype.setAttribute = function(name, value) {
+ changeAttribute.call(this, name, value, setAttribute);
+ }
+ var removeAttribute = prototype.removeAttribute;
+ prototype.removeAttribute = function(name) {
+ changeAttribute.call(this, name, null, removeAttribute);
+ }
+ prototype.setAttribute._polyfilled = true;
+ }
+
+ // https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/
+ // index.html#dfn-attribute-changed-callback
+ function changeAttribute(name, value, operation) {
+ var oldValue = this.getAttribute(name);
+ operation.apply(this, arguments);
+ var newValue = this.getAttribute(name);
+ if (this.attributeChangedCallback
+ && (newValue !== oldValue)) {
+ this.attributeChangedCallback(name, oldValue, newValue);
+ }
+ }
+
+ // element registry (maps tag names to definitions)
+
+ var registry = {};
+
+ function getRegisteredDefinition(name) {
+ if (name) {
+ return registry[name.toLowerCase()];
+ }
+ }
+
+ function registerDefinition(name, definition) {
+ if (registry[name]) {
+ throw new Error('a type with that name is already registered.');
+ }
+ registry[name] = definition;
+ }
+
+ function generateConstructor(definition) {
+ return function() {
+ return instantiate(definition);
+ };
+ }
+
+ function createElement(tag, typeExtension) {
+ // TODO(sjmiles): ignore 'tag' when using 'typeExtension', we could
+ // error check it, or perhaps there should only ever be one argument
+ var definition = getRegisteredDefinition(typeExtension || tag);
+ if (definition) {
+ if (tag == definition.tag && typeExtension == definition.is) {
+ return new definition.ctor();
+ }
+ // Handle empty string for type extension.
+ if (!typeExtension && !definition.is) {
+ return new definition.ctor();
+ }
+ }
+
+ if (typeExtension) {
+ var element = createElement(tag);
+ element.setAttribute('is', typeExtension);
+ return element;
+ }
+ var element = domCreateElement(tag);
+ // Custom tags should be HTMLElements even if not upgraded.
+ if (tag.indexOf('-') >= 0) {
+ implement(element, HTMLElement);
+ }
+ return element;
+ }
+
+ function upgradeElement(element) {
+ if (!element.__upgraded__ && (element.nodeType === Node.ELEMENT_NODE)) {
+ var is = element.getAttribute('is');
+ var definition = getRegisteredDefinition(is || element.localName);
+ if (definition) {
+ if (is && definition.tag == element.localName) {
+ return upgrade(element, definition);
+ } else if (!is && !definition.extends) {
+ return upgrade(element, definition);
+ }
+ }
+ }
+ }
+
+ function cloneNode(deep) {
+ // call original clone
+ var n = domCloneNode.call(this, deep);
+ // upgrade the element and subtree
+ scope.upgradeAll(n);
+ // return the clone
+ return n;
+ }
+ // capture native createElement before we override it
+
+ var domCreateElement = document.createElement.bind(document);
+
+ // capture native cloneNode before we override it
+
+ var domCloneNode = Node.prototype.cloneNode;
+
+ // exports
+
+ document.registerElement = register;
+ document.createElement = createElement; // override
+ Node.prototype.cloneNode = cloneNode; // override
+
+ scope.registry = registry;
+
+ /**
+ * Upgrade an element to a custom element. Upgrading an element
+ * causes the custom prototype to be applied, an `is` attribute
+ * to be attached (as needed), and invocation of the `readyCallback`.
+ * `upgrade` does nothing if the element is already upgraded, or
+ * if it matches no registered custom tag name.
+ *
+ * @method ugprade
+ * @param {Element} element The element to upgrade.
+ * @return {Element} The upgraded element.
+ */
+ scope.upgrade = upgradeElement;
+}
+
+// bc
+document.register = document.registerElement;
+
+scope.hasNative = hasNative;
+scope.useNative = useNative;
+
+})(window.CustomElements);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+(function(scope) {
+
+// import
+
+var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE;
+
+// highlander object for parsing a document tree
+
+var parser = {
+ selectors: [
+ 'link[rel=' + IMPORT_LINK_TYPE + ']'
+ ],
+ map: {
+ link: 'parseLink'
+ },
+ parse: function(inDocument) {
+ if (!inDocument.__parsed) {
+ // only parse once
+ inDocument.__parsed = true;
+ // all parsable elements in inDocument (depth-first pre-order traversal)
+ var elts = inDocument.querySelectorAll(parser.selectors);
+ // for each parsable node type, call the mapped parsing method
+ forEach(elts, function(e) {
+ parser[parser.map[e.localName]](e);
+ });
+ // upgrade all upgradeable static elements, anything dynamically
+ // created should be caught by observer
+ CustomElements.upgradeDocument(inDocument);
+ // observe document for dom changes
+ CustomElements.observeDocument(inDocument);
+ }
+ },
+ parseLink: function(linkElt) {
+ // imports
+ if (isDocumentLink(linkElt)) {
+ this.parseImport(linkElt);
+ }
+ },
+ parseImport: function(linkElt) {
+ if (linkElt.import) {
+ parser.parse(linkElt.import);
+ }
+ }
+};
+
+function isDocumentLink(inElt) {
+ return (inElt.localName === 'link'
+ && inElt.getAttribute('rel') === IMPORT_LINK_TYPE);
+}
+
+var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
+
+// exports
+
+scope.parser = parser;
+scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE;
+
+})(window.CustomElements);
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+(function(scope){
+
+// bootstrap parsing
+function bootstrap() {
+ // parse document
+ CustomElements.parser.parse(document);
+ // one more pass before register is 'live'
+ CustomElements.upgradeDocument(document);
+ // choose async
+ var async = window.Platform && Platform.endOfMicrotask ?
+ Platform.endOfMicrotask :
+ setTimeout;
+ async(function() {
+ // set internal 'ready' flag, now document.registerElement will trigger
+ // synchronous upgrades
+ CustomElements.ready = true;
+ // capture blunt profiling data
+ CustomElements.readyTime = Date.now();
+ if (window.HTMLImports) {
+ CustomElements.elapsed = CustomElements.readyTime - HTMLImports.readyTime;
+ }
+ // notify the system that we are bootstrapped
+ document.dispatchEvent(
+ new CustomEvent('WebComponentsReady', {bubbles: true})
+ );
+
+ // install upgrade hook if HTMLImports are available
+ if (window.HTMLImports) {
+ HTMLImports.__importsParsingHook = function(elt) {
+ CustomElements.parser.parse(elt.import);
+ }
+ }
+ });
+}
+
+// CustomEvent shim for IE
+if (typeof window.CustomEvent !== 'function') {
+ window.CustomEvent = function(inType) {
+ var e = document.createEvent('HTMLEvents');
+ e.initEvent(inType, true, true);
+ return e;
+ };
+}
+
+// When loading at readyState complete time (or via flag), boot custom elements
+// immediately.
+// If relevant, HTMLImports must already be loaded.
+if (document.readyState === 'complete' || scope.flags.eager) {
+ bootstrap();
+// When loading at readyState interactive time, bootstrap only if HTMLImports
+// are not pending. Also avoid IE as the semantics of this state are unreliable.
+} else if (document.readyState === 'interactive' && !window.attachEvent &&
+ (!window.HTMLImports || window.HTMLImports.ready)) {
+ bootstrap();
+// When loading at other readyStates, wait for the appropriate DOM event to
+// bootstrap.
+} else {
+ var loadEvent = window.HTMLImports && !HTMLImports.ready ?
+ 'HTMLImportsLoaded' : 'DOMContentLoaded';
+ window.addEventListener(loadEvent, bootstrap);
+}
+
+})(window.CustomElements);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+(function() {
+
+// inject style sheet
+var style = document.createElement('style');
+style.textContent = 'element {display: none !important;} /* injected by platform.js */';
+var head = document.querySelector('head');
+head.insertBefore(style, head.firstChild);
+
+if (window.ShadowDOMPolyfill) {
+
+ // ensure wrapped inputs for these functions
+ var fns = ['upgradeAll', 'upgradeSubtree', 'observeDocument',
+ 'upgradeDocument'];
+
+ // cache originals
+ var original = {};
+ fns.forEach(function(fn) {
+ original[fn] = CustomElements[fn];
+ });
+
+ // override
+ fns.forEach(function(fn) {
+ CustomElements[fn] = function(inNode) {
+ return original[fn](wrap(inNode));
+ };
+ });
+
+}
+
+})();
+
+/*
+ * Copyright 2014 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+(function(scope) {
+
+var STYLE_SELECTOR = 'style';
+
+var urlResolver = scope.urlResolver;
+
+var loader = {
+ cacheStyles: function(styles, callback) {
+ var css = [];
+ for (var i=0, l=styles.length, s; (i<l) && (s=styles[i]); i++) {
+ css.push(s.textContent);
+ }
+ cacheCssText(css.join('\n'), callback);
+ },
+ xhrStyles: function(styles, callback) {
+ var loaded=0, l = styles.length;
+ // called in the context of the style
+ function loadedStyle(style) {
+ //console.log(style.textContent);
+ loaded++;
+ if (loaded === l && callback) {
+ callback();
+ }
+ }
+ for (var i=0, s; (i<l) && (s=styles[i]); i++) {
+ xhrLoadStyle(s, loadedStyle);
+ }
+ }
+};
+
+// use the platform to preload styles
+var preloadElement = document.createElement('preloader');
+preloadElement.style.display = 'none';
+var preloadRoot = preloadElement.createShadowRoot();
+document.head.appendChild(preloadElement);
+
+function cacheCssText(cssText, callback) {
+ var style = createStyleElement(cssText);
+ if (callback) {
+ style.addEventListener('load', callback);
+ style.addEventListener('error', callback);
+ }
+ preloadRoot.appendChild(style);
+}
+
+function createStyleElement(cssText, scope) {
+ scope = scope || document;
+ scope = scope.createElement ? scope : scope.ownerDocument;
+ var style = scope.createElement('style');
+ style.textContent = cssText;
+ return style;
+}
+
+// TODO(sorvell): use a common loader shared with HTMLImports polyfill
+// currently, this just loads the first @import per style element
+// and does not recurse into loaded elements; we'll address this with a
+// generalized loader that's built out of the one in the HTMLImports polyfill.
+// polyfill the loading of a style element's @import via xhr
+function xhrLoadStyle(style, callback) {
+ HTMLImports.xhr.load(atImportUrlFromStyle(style), function (err, resource,
+ url) {
+ replaceAtImportWithCssText(this, url, resource);
+ this.textContent = urlResolver.resolveCssText(this.textContent, url);
+ callback && callback(this);
+ }, style);
+}
+
+var atImportRe = /@import\s[(]?['"]?([^\s'";)]*)/;
+
+// get the first @import rule from a style
+function atImportUrlFromStyle(style) {
+ var matches = style.textContent.match(atImportRe);
+ return matches && matches[1];
+}
+
+function replaceAtImportWithCssText(style, url, cssText) {
+ var re = new RegExp('@import[^;]*' + url + '[^;]*;', 'i');
+ style.textContent = style.textContent.replace(re, cssText);
+}
+
+// exports
+scope.loader = loader;
+
+})(window.Platform);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+(function(scope) {
+ scope = scope || {};
+ scope.external = scope.external || {};
+ var target = {
+ shadow: function(inEl) {
+ if (inEl) {
+ return inEl.shadowRoot || inEl.webkitShadowRoot;
+ }
+ },
+ canTarget: function(shadow) {
+ return shadow && Boolean(shadow.elementFromPoint);
+ },
+ targetingShadow: function(inEl) {
+ var s = this.shadow(inEl);
+ if (this.canTarget(s)) {
+ return s;
+ }
+ },
+ olderShadow: function(shadow) {
+ var os = shadow.olderShadowRoot;
+ if (!os) {
+ var se = shadow.querySelector('shadow');
+ if (se) {
+ os = se.olderShadowRoot;
+ }
+ }
+ return os;
+ },
+ allShadows: function(element) {
+ var shadows = [], s = this.shadow(element);
+ while(s) {
+ shadows.push(s);
+ s = this.olderShadow(s);
+ }
+ return shadows;
+ },
+ searchRoot: function(inRoot, x, y) {
+ if (inRoot) {
+ var t = inRoot.elementFromPoint(x, y);
+ var st, sr, os;
+ // is element a shadow host?
+ sr = this.targetingShadow(t);
+ while (sr) {
+ // find the the element inside the shadow root
+ st = sr.elementFromPoint(x, y);
+ if (!st) {
+ // check for older shadows
+ sr = this.olderShadow(sr);
+ } else {
+ // shadowed element may contain a shadow root
+ var ssr = this.targetingShadow(st);
+ return this.searchRoot(ssr, x, y) || st;
+ }
+ }
+ // light dom element is the target
+ return t;
+ }
+ },
+ owner: function(element) {
+ var s = element;
+ // walk up until you hit the shadow root or document
+ while (s.parentNode) {
+ s = s.parentNode;
+ }
+ // the owner element is expected to be a Document or ShadowRoot
+ if (s.nodeType != Node.DOCUMENT_NODE && s.nodeType != Node.DOCUMENT_FRAGMENT_NODE) {
+ s = document;
+ }
+ return s;
+ },
+ findTarget: function(inEvent) {
+ var x = inEvent.clientX, y = inEvent.clientY;
+ // if the listener is in the shadow root, it is much faster to start there
+ var s = this.owner(inEvent.target);
+ // if x, y is not in this root, fall back to document search
+ if (!s.elementFromPoint(x, y)) {
+ s = document;
+ }
+ return this.searchRoot(s, x, y);
+ }
+ };
+ scope.targetFinding = target;
+ scope.findTarget = target.findTarget.bind(target);
+
+ window.PointerEventsPolyfill = scope;
+})(window.PointerEventsPolyfill);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+(function() {
+ function shadowSelector(v) {
+ return 'body ^^ ' + selector(v);
+ }
+ function selector(v) {
+ return '[touch-action="' + v + '"]';
+ }
+ function rule(v) {
+ return '{ -ms-touch-action: ' + v + '; touch-action: ' + v + '; touch-action-delay: none; }';
+ }
+ var attrib2css = [
+ 'none',
+ 'auto',
+ 'pan-x',
+ 'pan-y',
+ {
+ rule: 'pan-x pan-y',
+ selectors: [
+ 'pan-x pan-y',
+ 'pan-y pan-x'
+ ]
+ }
+ ];
+ var styles = '';
+ attrib2css.forEach(function(r) {
+ if (String(r) === r) {
+ styles += selector(r) + rule(r) + '\n';
+ styles += shadowSelector(r) + rule(r) + '\n';
+ } else {
+ styles += r.selectors.map(selector) + rule(r.rule) + '\n';
+ styles += r.selectors.map(shadowSelector) + rule(r.rule) + '\n';
+ }
+ });
+ var el = document.createElement('style');
+ el.textContent = styles;
+ document.head.appendChild(el);
+})();
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+/**
+ * This is the constructor for new PointerEvents.
+ *
+ * New Pointer Events must be given a type, and an optional dictionary of
+ * initialization properties.
+ *
+ * Due to certain platform requirements, events returned from the constructor
+ * identify as MouseEvents.
+ *
+ * @constructor
+ * @param {String} inType The type of the event to create.
+ * @param {Object} [inDict] An optional dictionary of initial event properties.
+ * @return {Event} A new PointerEvent of type `inType` and initialized with properties from `inDict`.
+ */
+(function(scope) {
+ // test for DOM Level 4 Events
+ var NEW_MOUSE_EVENT = false;
+ var HAS_BUTTONS = false;
+ try {
+ var ev = new MouseEvent('click', {buttons: 1});
+ NEW_MOUSE_EVENT = true;
+ HAS_BUTTONS = ev.buttons === 1;
+ } catch(e) {
+ }
+
+ var MOUSE_PROPS = [
+ 'bubbles',
+ 'cancelable',
+ 'view',
+ 'detail',
+ 'screenX',
+ 'screenY',
+ 'clientX',
+ 'clientY',
+ 'ctrlKey',
+ 'altKey',
+ 'shiftKey',
+ 'metaKey',
+ 'button',
+ 'relatedTarget',
+ ];
+
+ var MOUSE_DEFAULTS = [
+ false,
+ false,
+ null,
+ null,
+ 0,
+ 0,
+ 0,
+ 0,
+ false,
+ false,
+ false,
+ false,
+ 0,
+ null
+ ];
+
+ function PointerEvent(inType, inDict) {
+ inDict = inDict || {};
+ // According to the w3c spec,
+ // http://www.w3.org/TR/DOM-Level-3-Events/#events-MouseEvent-button
+ // MouseEvent.button == 0 can mean either no mouse button depressed, or the
+ // left mouse button depressed.
+ //
+ // As of now, the only way to distinguish between the two states of
+ // MouseEvent.button is by using the deprecated MouseEvent.which property, as
+ // this maps mouse buttons to positive integers > 0, and uses 0 to mean that
+ // no mouse button is held.
+ //
+ // MouseEvent.which is derived from MouseEvent.button at MouseEvent creation,
+ // but initMouseEvent does not expose an argument with which to set
+ // MouseEvent.which. Calling initMouseEvent with a buttonArg of 0 will set
+ // MouseEvent.button == 0 and MouseEvent.which == 1, breaking the expectations
+ // of app developers.
+ //
+ // The only way to propagate the correct state of MouseEvent.which and
+ // MouseEvent.button to a new MouseEvent.button == 0 and MouseEvent.which == 0
+ // is to call initMouseEvent with a buttonArg value of -1.
+ //
+ // This is fixed with DOM Level 4's use of buttons
+ var buttons;
+ if (inDict.buttons || HAS_BUTTONS) {
+ buttons = inDict.buttons;
+ } else {
+ switch (inDict.which) {
+ case 1: buttons = 1; break;
+ case 2: buttons = 4; break;
+ case 3: buttons = 2; break;
+ default: buttons = 0;
+ }
+ }
+
+ var e;
+ if (NEW_MOUSE_EVENT) {
+ e = new MouseEvent(inType, inDict);
+ } else {
+ e = document.createEvent('MouseEvent');
+
+ // import values from the given dictionary
+ var props = {}, p;
+ for(var i = 0; i < MOUSE_PROPS.length; i++) {
+ p = MOUSE_PROPS[i];
+ props[p] = inDict[p] || MOUSE_DEFAULTS[i];
+ }
+
+ // define the properties inherited from MouseEvent
+ e.initMouseEvent(
+ inType, props.bubbles, props.cancelable, props.view, props.detail,
+ props.screenX, props.screenY, props.clientX, props.clientY, props.ctrlKey,
+ props.altKey, props.shiftKey, props.metaKey, props.button, props.relatedTarget
+ );
+ }
+
+ // make the event pass instanceof checks
+ e.__proto__ = PointerEvent.prototype;
+
+ // define the buttons property according to DOM Level 3 spec
+ if (!HAS_BUTTONS) {
+ // IE 10 has buttons on MouseEvent.prototype as a getter w/o any setting
+ // mechanism
+ Object.defineProperty(e, 'buttons', {get: function(){ return buttons; }, enumerable: true});
+ }
+
+ // Spec requires that pointers without pressure specified use 0.5 for down
+ // state and 0 for up state.
+ var pressure = 0;
+ if (inDict.pressure) {
+ pressure = inDict.pressure;
+ } else {
+ pressure = buttons ? 0.5 : 0;
+ }
+
+ // define the properties of the PointerEvent interface
+ Object.defineProperties(e, {
+ pointerId: { value: inDict.pointerId || 0, enumerable: true },
+ width: { value: inDict.width || 0, enumerable: true },
+ height: { value: inDict.height || 0, enumerable: true },
+ pressure: { value: pressure, enumerable: true },
+ tiltX: { value: inDict.tiltX || 0, enumerable: true },
+ tiltY: { value: inDict.tiltY || 0, enumerable: true },
+ pointerType: { value: inDict.pointerType || '', enumerable: true },
+ hwTimestamp: { value: inDict.hwTimestamp || 0, enumerable: true },
+ isPrimary: { value: inDict.isPrimary || false, enumerable: true }
+ });
+ return e;
+ }
+
+ // PointerEvent extends MouseEvent
+ PointerEvent.prototype = Object.create(MouseEvent.prototype);
+
+ // attach to window
+ if (!scope.PointerEvent) {
+ scope.PointerEvent = PointerEvent;
+ }
+})(window);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+/**
+ * This module implements an map of pointer states
+ */
+(function(scope) {
+ var USE_MAP = window.Map && window.Map.prototype.forEach;
+ var POINTERS_FN = function(){ return this.size; };
+ function PointerMap() {
+ if (USE_MAP) {
+ var m = new Map();
+ m.pointers = POINTERS_FN;
+ return m;
+ } else {
+ this.keys = [];
+ this.values = [];
+ }
+ }
+
+ PointerMap.prototype = {
+ set: function(inId, inEvent) {
+ var i = this.keys.indexOf(inId);
+ if (i > -1) {
+ this.values[i] = inEvent;
+ } else {
+ this.keys.push(inId);
+ this.values.push(inEvent);
+ }
+ },
+ has: function(inId) {
+ return this.keys.indexOf(inId) > -1;
+ },
+ 'delete': function(inId) {
+ var i = this.keys.indexOf(inId);
+ if (i > -1) {
+ this.keys.splice(i, 1);
+ this.values.splice(i, 1);
+ }
+ },
+ get: function(inId) {
+ var i = this.keys.indexOf(inId);
+ return this.values[i];
+ },
+ clear: function() {
+ this.keys.length = 0;
+ this.values.length = 0;
+ },
+ // return value, key, map
+ forEach: function(callback, thisArg) {
+ this.values.forEach(function(v, i) {
+ callback.call(thisArg, v, this.keys[i], this);
+ }, this);
+ },
+ pointers: function() {
+ return this.keys.length;
+ }
+ };
+
+ scope.PointerMap = PointerMap;
+})(window.PointerEventsPolyfill);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+(function(scope) {
+ var CLONE_PROPS = [
+ // MouseEvent
+ 'bubbles',
+ 'cancelable',
+ 'view',
+ 'detail',
+ 'screenX',
+ 'screenY',
+ 'clientX',
+ 'clientY',
+ 'ctrlKey',
+ 'altKey',
+ 'shiftKey',
+ 'metaKey',
+ 'button',
+ 'relatedTarget',
+ // DOM Level 3
+ 'buttons',
+ // PointerEvent
+ 'pointerId',
+ 'width',
+ 'height',
+ 'pressure',
+ 'tiltX',
+ 'tiltY',
+ 'pointerType',
+ 'hwTimestamp',
+ 'isPrimary',
+ // event instance
+ 'type',
+ 'target',
+ 'currentTarget',
+ 'which'
+ ];
+
+ var CLONE_DEFAULTS = [
+ // MouseEvent
+ false,
+ false,
+ null,
+ null,
+ 0,
+ 0,
+ 0,
+ 0,
+ false,
+ false,
+ false,
+ false,
+ 0,
+ null,
+ // DOM Level 3
+ 0,
+ // PointerEvent
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ '',
+ 0,
+ false,
+ // event instance
+ '',
+ null,
+ null,
+ 0
+ ];
+
+ var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined');
+
+ /**
+ * This module is for normalizing events. Mouse and Touch events will be
+ * collected here, and fire PointerEvents that have the same semantics, no
+ * matter the source.
+ * Events fired:
+ * - pointerdown: a pointing is added
+ * - pointerup: a pointer is removed
+ * - pointermove: a pointer is moved
+ * - pointerover: a pointer crosses into an element
+ * - pointerout: a pointer leaves an element
+ * - pointercancel: a pointer will no longer generate events
+ */
+ var dispatcher = {
+ targets: new WeakMap(),
+ handledEvents: new WeakMap(),
+ pointermap: new scope.PointerMap(),
+ eventMap: {},
+ // Scope objects for native events.
+ // This exists for ease of testing.
+ eventSources: {},
+ eventSourceList: [],
+ /**
+ * Add a new event source that will generate pointer events.
+ *
+ * `inSource` must contain an array of event names named `events`, and
+ * functions with the names specified in the `events` array.
+ * @param {string} name A name for the event source
+ * @param {Object} source A new source of platform events.
+ */
+ registerSource: function(name, source) {
+ var s = source;
+ var newEvents = s.events;
+ if (newEvents) {
+ newEvents.forEach(function(e) {
+ if (s[e]) {
+ this.eventMap[e] = s[e].bind(s);
+ }
+ }, this);
+ this.eventSources[name] = s;
+ this.eventSourceList.push(s);
+ }
+ },
+ register: function(element) {
+ var l = this.eventSourceList.length;
+ for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) {
+ // call eventsource register
+ es.register.call(es, element);
+ }
+ },
+ unregister: function(element) {
+ var l = this.eventSourceList.length;
+ for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) {
+ // call eventsource register
+ es.unregister.call(es, element);
+ }
+ },
+ contains: scope.external.contains || function(container, contained) {
+ return container.contains(contained);
+ },
+ // EVENTS
+ down: function(inEvent) {
+ this.fireEvent('pointerdown', inEvent);
+ },
+ move: function(inEvent) {
+ this.fireEvent('pointermove', inEvent);
+ },
+ up: function(inEvent) {
+ this.fireEvent('pointerup', inEvent);
+ },
+ enter: function(inEvent) {
+ inEvent.bubbles = false;
+ this.fireEvent('pointerenter', inEvent);
+ },
+ leave: function(inEvent) {
+ inEvent.bubbles = false;
+ this.fireEvent('pointerleave', inEvent);
+ },
+ over: function(inEvent) {
+ inEvent.bubbles = true;
+ this.fireEvent('pointerover', inEvent);
+ },
+ out: function(inEvent) {
+ inEvent.bubbles = true;
+ this.fireEvent('pointerout', inEvent);
+ },
+ cancel: function(inEvent) {
+ this.fireEvent('pointercancel', inEvent);
+ },
+ leaveOut: function(event) {
+ this.out(event);
+ if (!this.contains(event.target, event.relatedTarget)) {
+ this.leave(event);
+ }
+ },
+ enterOver: function(event) {
+ this.over(event);
+ if (!this.contains(event.target, event.relatedTarget)) {
+ this.enter(event);
+ }
+ },
+ // LISTENER LOGIC
+ eventHandler: function(inEvent) {
+ // This is used to prevent multiple dispatch of pointerevents from
+ // platform events. This can happen when two elements in different scopes
+ // are set up to create pointer events, which is relevant to Shadow DOM.
+ if (this.handledEvents.get(inEvent)) {
+ return;
+ }
+ var type = inEvent.type;
+ var fn = this.eventMap && this.eventMap[type];
+ if (fn) {
+ fn(inEvent);
+ }
+ this.handledEvents.set(inEvent, true);
+ },
+ // set up event listeners
+ listen: function(target, events) {
+ events.forEach(function(e) {
+ this.addEvent(target, e);
+ }, this);
+ },
+ // remove event listeners
+ unlisten: function(target, events) {
+ events.forEach(function(e) {
+ this.removeEvent(target, e);
+ }, this);
+ },
+ addEvent: scope.external.addEvent || function(target, eventName) {
+ target.addEventListener(eventName, this.boundHandler);
+ },
+ removeEvent: scope.external.removeEvent || function(target, eventName) {
+ target.removeEventListener(eventName, this.boundHandler);
+ },
+ // EVENT CREATION AND TRACKING
+ /**
+ * Creates a new Event of type `inType`, based on the information in
+ * `inEvent`.
+ *
+ * @param {string} inType A string representing the type of event to create
+ * @param {Event} inEvent A platform event with a target
+ * @return {Event} A PointerEvent of type `inType`
+ */
+ makeEvent: function(inType, inEvent) {
+ // relatedTarget must be null if pointer is captured
+ if (this.captureInfo) {
+ inEvent.relatedTarget = null;
+ }
+ var e = new PointerEvent(inType, inEvent);
+ if (inEvent.preventDefault) {
+ e.preventDefault = inEvent.preventDefault;
+ }
+ this.targets.set(e, this.targets.get(inEvent) || inEvent.target);
+ return e;
+ },
+ // make and dispatch an event in one call
+ fireEvent: function(inType, inEvent) {
+ var e = this.makeEvent(inType, inEvent);
+ return this.dispatchEvent(e);
+ },
+ /**
+ * Returns a snapshot of inEvent, with writable properties.
+ *
+ * @param {Event} inEvent An event that contains properties to copy.
+ * @return {Object} An object containing shallow copies of `inEvent`'s
+ * properties.
+ */
+ cloneEvent: function(inEvent) {
+ var eventCopy = {}, p;
+ for (var i = 0; i < CLONE_PROPS.length; i++) {
+ p = CLONE_PROPS[i];
+ eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i];
+ // Work around SVGInstanceElement shadow tree
+ // Return the <use> element that is represented by the instance for Safari, Chrome, IE.
+ // This is the behavior implemented by Firefox.
+ if (HAS_SVG_INSTANCE && (p === 'target' || p === 'relatedTarget')) {
+ if (eventCopy[p] instanceof SVGElementInstance) {
+ eventCopy[p] = eventCopy[p].correspondingUseElement;
+ }
+ }
+ }
+ // keep the semantics of preventDefault
+ if (inEvent.preventDefault) {
+ eventCopy.preventDefault = function() {
+ inEvent.preventDefault();
+ };
+ }
+ return eventCopy;
+ },
+ getTarget: function(inEvent) {
+ // if pointer capture is set, route all events for the specified pointerId
+ // to the capture target
+ if (this.captureInfo) {
+ if (this.captureInfo.id === inEvent.pointerId) {
+ return this.captureInfo.target;
+ }
+ }
+ return this.targets.get(inEvent);
+ },
+ setCapture: function(inPointerId, inTarget) {
+ if (this.captureInfo) {
+ this.releaseCapture(this.captureInfo.id);
+ }
+ this.captureInfo = {id: inPointerId, target: inTarget};
+ var e = new PointerEvent('gotpointercapture', { bubbles: true });
+ this.implicitRelease = this.releaseCapture.bind(this, inPointerId);
+ document.addEventListener('pointerup', this.implicitRelease);
+ document.addEventListener('pointercancel', this.implicitRelease);
+ this.targets.set(e, inTarget);
+ this.asyncDispatchEvent(e);
+ },
+ releaseCapture: function(inPointerId) {
+ if (this.captureInfo && this.captureInfo.id === inPointerId) {
+ var e = new PointerEvent('lostpointercapture', { bubbles: true });
+ var t = this.captureInfo.target;
+ this.captureInfo = null;
+ document.removeEventListener('pointerup', this.implicitRelease);
+ document.removeEventListener('pointercancel', this.implicitRelease);
+ this.targets.set(e, t);
+ this.asyncDispatchEvent(e);
+ }
+ },
+ /**
+ * Dispatches the event to its target.
+ *
+ * @param {Event} inEvent The event to be dispatched.
+ * @return {Boolean} True if an event handler returns true, false otherwise.
+ */
+ dispatchEvent: scope.external.dispatchEvent || function(inEvent) {
+ var t = this.getTarget(inEvent);
+ if (t) {
+ return t.dispatchEvent(inEvent);
+ }
+ },
+ asyncDispatchEvent: function(inEvent) {
+ setTimeout(this.dispatchEvent.bind(this, inEvent), 0);
+ }
+ };
+ dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher);
+ scope.dispatcher = dispatcher;
+ scope.register = dispatcher.register.bind(dispatcher);
+ scope.unregister = dispatcher.unregister.bind(dispatcher);
+})(window.PointerEventsPolyfill);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+/**
+ * This module uses Mutation Observers to dynamically adjust which nodes will
+ * generate Pointer Events.
+ *
+ * All nodes that wish to generate Pointer Events must have the attribute
+ * `touch-action` set to `none`.
+ */
+(function(scope) {
+ var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
+ var map = Array.prototype.map.call.bind(Array.prototype.map);
+ var toArray = Array.prototype.slice.call.bind(Array.prototype.slice);
+ var filter = Array.prototype.filter.call.bind(Array.prototype.filter);
+ var MO = window.MutationObserver || window.WebKitMutationObserver;
+ var SELECTOR = '[touch-action]';
+ var OBSERVER_INIT = {
+ subtree: true,
+ childList: true,
+ attributes: true,
+ attributeOldValue: true,
+ attributeFilter: ['touch-action']
+ };
+
+ function Installer(add, remove, changed, binder) {
+ this.addCallback = add.bind(binder);
+ this.removeCallback = remove.bind(binder);
+ this.changedCallback = changed.bind(binder);
+ if (MO) {
+ this.observer = new MO(this.mutationWatcher.bind(this));
+ }
+ }
+
+ Installer.prototype = {
+ watchSubtree: function(target) {
+ // Only watch scopes that can target find, as these are top-level.
+ // Otherwise we can see duplicate additions and removals that add noise.
+ //
+ // TODO(dfreedman): For some instances with ShadowDOMPolyfill, we can see
+ // a removal without an insertion when a node is redistributed among
+ // shadows. Since it all ends up correct in the document, watching only
+ // the document will yield the correct mutations to watch.
+ if (scope.targetFinding.canTarget(target)) {
+ this.observer.observe(target, OBSERVER_INIT);
+ }
+ },
+ enableOnSubtree: function(target) {
+ this.watchSubtree(target);
+ if (target === document && document.readyState !== 'complete') {
+ this.installOnLoad();
+ } else {
+ this.installNewSubtree(target);
+ }
+ },
+ installNewSubtree: function(target) {
+ forEach(this.findElements(target), this.addElement, this);
+ },
+ findElements: function(target) {
+ if (target.querySelectorAll) {
+ return target.querySelectorAll(SELECTOR);
+ }
+ return [];
+ },
+ removeElement: function(el) {
+ this.removeCallback(el);
+ },
+ addElement: function(el) {
+ this.addCallback(el);
+ },
+ elementChanged: function(el, oldValue) {
+ this.changedCallback(el, oldValue);
+ },
+ concatLists: function(accum, list) {
+ return accum.concat(toArray(list));
+ },
+ // register all touch-action = none nodes on document load
+ installOnLoad: function() {
+ document.addEventListener('DOMContentLoaded', this.installNewSubtree.bind(this, document));
+ },
+ isElement: function(n) {
+ return n.nodeType === Node.ELEMENT_NODE;
+ },
+ flattenMutationTree: function(inNodes) {
+ // find children with touch-action
+ var tree = map(inNodes, this.findElements, this);
+ // make sure the added nodes are accounted for
+ tree.push(filter(inNodes, this.isElement));
+ // flatten the list
+ return tree.reduce(this.concatLists, []);
+ },
+ mutationWatcher: function(mutations) {
+ mutations.forEach(this.mutationHandler, this);
+ },
+ mutationHandler: function(m) {
+ if (m.type === 'childList') {
+ var added = this.flattenMutationTree(m.addedNodes);
+ added.forEach(this.addElement, this);
+ var removed = this.flattenMutationTree(m.removedNodes);
+ removed.forEach(this.removeElement, this);
+ } else if (m.type === 'attributes') {
+ this.elementChanged(m.target, m.oldValue);
+ }
+ }
+ };
+
+ if (!MO) {
+ Installer.prototype.watchSubtree = function(){
+ console.warn('PointerEventsPolyfill: MutationObservers not found, touch-action will not be dynamically detected');
+ };
+ }
+
+ scope.Installer = Installer;
+})(window.PointerEventsPolyfill);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+(function (scope) {
+ var dispatcher = scope.dispatcher;
+ var pointermap = dispatcher.pointermap;
+ // radius around touchend that swallows mouse events
+ var DEDUP_DIST = 25;
+
+ // handler block for native mouse events
+ var mouseEvents = {
+ POINTER_ID: 1,
+ POINTER_TYPE: 'mouse',
+ events: [
+ 'mousedown',
+ 'mousemove',
+ 'mouseup',
+ 'mouseover',
+ 'mouseout'
+ ],
+ register: function(target) {
+ dispatcher.listen(target, this.events);
+ },
+ unregister: function(target) {
+ dispatcher.unlisten(target, this.events);
+ },
+ lastTouches: [],
+ // collide with the global mouse listener
+ isEventSimulatedFromTouch: function(inEvent) {
+ var lts = this.lastTouches;
+ var x = inEvent.clientX, y = inEvent.clientY;
+ for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) {
+ // simulated mouse events will be swallowed near a primary touchend
+ var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
+ if (dx <= DEDUP_DIST && dy <= DEDUP_DIST) {
+ return true;
+ }
+ }
+ },
+ prepareEvent: function(inEvent) {
+ var e = dispatcher.cloneEvent(inEvent);
+ // forward mouse preventDefault
+ var pd = e.preventDefault;
+ e.preventDefault = function() {
+ inEvent.preventDefault();
+ pd();
+ };
+ e.pointerId = this.POINTER_ID;
+ e.isPrimary = true;
+ e.pointerType = this.POINTER_TYPE;
+ return e;
+ },
+ mousedown: function(inEvent) {
+ if (!this.isEventSimulatedFromTouch(inEvent)) {
+ var p = pointermap.has(this.POINTER_ID);
+ // TODO(dfreedman) workaround for some elements not sending mouseup
+ // http://crbug/149091
+ if (p) {
+ this.cancel(inEvent);
+ }
+ var e = this.prepareEvent(inEvent);
+ pointermap.set(this.POINTER_ID, inEvent);
+ dispatcher.down(e);
+ }
+ },
+ mousemove: function(inEvent) {
+ if (!this.isEventSimulatedFromTouch(inEvent)) {
+ var e = this.prepareEvent(inEvent);
+ dispatcher.move(e);
+ }
+ },
+ mouseup: function(inEvent) {
+ if (!this.isEventSimulatedFromTouch(inEvent)) {
+ var p = pointermap.get(this.POINTER_ID);
+ if (p && p.button === inEvent.button) {
+ var e = this.prepareEvent(inEvent);
+ dispatcher.up(e);
+ this.cleanupMouse();
+ }
+ }
+ },
+ mouseover: function(inEvent) {
+ if (!this.isEventSimulatedFromTouch(inEvent)) {
+ var e = this.prepareEvent(inEvent);
+ dispatcher.enterOver(e);
+ }
+ },
+ mouseout: function(inEvent) {
+ if (!this.isEventSimulatedFromTouch(inEvent)) {
+ var e = this.prepareEvent(inEvent);
+ dispatcher.leaveOut(e);
+ }
+ },
+ cancel: function(inEvent) {
+ var e = this.prepareEvent(inEvent);
+ dispatcher.cancel(e);
+ this.cleanupMouse();
+ },
+ cleanupMouse: function() {
+ pointermap['delete'](this.POINTER_ID);
+ }
+ };
+
+ scope.mouseEvents = mouseEvents;
+})(window.PointerEventsPolyfill);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+(function(scope) {
+ var dispatcher = scope.dispatcher;
+ var findTarget = scope.findTarget;
+ var allShadows = scope.targetFinding.allShadows.bind(scope.targetFinding);
+ var pointermap = dispatcher.pointermap;
+ var touchMap = Array.prototype.map.call.bind(Array.prototype.map);
+ // This should be long enough to ignore compat mouse events made by touch
+ var DEDUP_TIMEOUT = 2500;
+ var CLICK_COUNT_TIMEOUT = 200;
+ var ATTRIB = 'touch-action';
+ var INSTALLER;
+ // The presence of touch event handlers blocks scrolling, and so we must be careful to
+ // avoid adding handlers unnecessarily. Chrome plans to add a touch-action-delay property
+ // (crbug.com/329559) to address this, and once we have that we can opt-in to a simpler
+ // handler registration mechanism. Rather than try to predict how exactly to opt-in to
+ // that we'll just leave this disabled until there is a build of Chrome to test.
+ var HAS_TOUCH_ACTION_DELAY = false;
+
+ // handler block for native touch events
+ var touchEvents = {
+ scrollType: new WeakMap(),
+ events: [
+ 'touchstart',
+ 'touchmove',
+ 'touchend',
+ 'touchcancel'
+ ],
+ register: function(target) {
+ if (HAS_TOUCH_ACTION_DELAY) {
+ dispatcher.listen(target, this.events);
+ } else {
+ INSTALLER.enableOnSubtree(target);
+ }
+ },
+ unregister: function(target) {
+ if (HAS_TOUCH_ACTION_DELAY) {
+ dispatcher.unlisten(target, this.events);
+ } else {
+ // TODO(dfreedman): is it worth it to disconnect the MO?
+ }
+ },
+ elementAdded: function(el) {
+ var a = el.getAttribute(ATTRIB);
+ var st = this.touchActionToScrollType(a);
+ if (st) {
+ this.scrollType.set(el, st);
+ dispatcher.listen(el, this.events);
+ // set touch-action on shadows as well
+ allShadows(el).forEach(function(s) {
+ this.scrollType.set(s, st);
+ dispatcher.listen(s, this.events);
+ }, this);
+ }
+ },
+ elementRemoved: function(el) {
+ this.scrollType['delete'](el);
+ dispatcher.unlisten(el, this.events);
+ // remove touch-action from shadow
+ allShadows(el).forEach(function(s) {
+ this.scrollType['delete'](s);
+ dispatcher.unlisten(s, this.events);
+ }, this);
+ },
+ elementChanged: function(el, oldValue) {
+ var a = el.getAttribute(ATTRIB);
+ var st = this.touchActionToScrollType(a);
+ var oldSt = this.touchActionToScrollType(oldValue);
+ // simply update scrollType if listeners are already established
+ if (st && oldSt) {
+ this.scrollType.set(el, st);
+ allShadows(el).forEach(function(s) {
+ this.scrollType.set(s, st);
+ }, this);
+ } else if (oldSt) {
+ this.elementRemoved(el);
+ } else if (st) {
+ this.elementAdded(el);
+ }
+ },
+ scrollTypes: {
+ EMITTER: 'none',
+ XSCROLLER: 'pan-x',
+ YSCROLLER: 'pan-y',
+ SCROLLER: /^(?:pan-x pan-y)|(?:pan-y pan-x)|auto$/
+ },
+ touchActionToScrollType: function(touchAction) {
+ var t = touchAction;
+ var st = this.scrollTypes;
+ if (t === 'none') {
+ return 'none';
+ } else if (t === st.XSCROLLER) {
+ return 'X';
+ } else if (t === st.YSCROLLER) {
+ return 'Y';
+ } else if (st.SCROLLER.exec(t)) {
+ return 'XY';
+ }
+ },
+ POINTER_TYPE: 'touch',
+ firstTouch: null,
+ isPrimaryTouch: function(inTouch) {
+ return this.firstTouch === inTouch.identifier;
+ },
+ setPrimaryTouch: function(inTouch) {
+ // set primary touch if there no pointers, or the only pointer is the mouse
+ if (pointermap.pointers() === 0 || (pointermap.pointers() === 1 && pointermap.has(1))) {
+ this.firstTouch = inTouch.identifier;
+ this.firstXY = {X: inTouch.clientX, Y: inTouch.clientY};
+ this.scrolling = false;
+ this.cancelResetClickCount();
+ }
+ },
+ removePrimaryPointer: function(inPointer) {
+ if (inPointer.isPrimary) {
+ this.firstTouch = null;
+ this.firstXY = null;
+ this.resetClickCount();
+ }
+ },
+ clickCount: 0,
+ resetId: null,
+ resetClickCount: function() {
+ var fn = function() {
+ this.clickCount = 0;
+ this.resetId = null;
+ }.bind(this);
+ this.resetId = setTimeout(fn, CLICK_COUNT_TIMEOUT);
+ },
+ cancelResetClickCount: function() {
+ if (this.resetId) {
+ clearTimeout(this.resetId);
+ }
+ },
+ touchToPointer: function(inTouch) {
+ var e = dispatcher.cloneEvent(inTouch);
+ // Spec specifies that pointerId 1 is reserved for Mouse.
+ // Touch identifiers can start at 0.
+ // Add 2 to the touch identifier for compatibility.
+ e.pointerId = inTouch.identifier + 2;
+ e.target = findTarget(e);
+ e.bubbles = true;
+ e.cancelable = true;
+ e.detail = this.clickCount;
+ e.button = 0;
+ e.buttons = 1;
+ e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0;
+ e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0;
+ e.pressure = inTouch.webkitForce || inTouch.force || 0.5;
+ e.isPrimary = this.isPrimaryTouch(inTouch);
+ e.pointerType = this.POINTER_TYPE;
+ return e;
+ },
+ processTouches: function(inEvent, inFunction) {
+ var tl = inEvent.changedTouches;
+ var pointers = touchMap(tl, this.touchToPointer, this);
+ // forward touch preventDefaults
+ pointers.forEach(function(p) {
+ p.preventDefault = function() {
+ this.scrolling = false;
+ this.firstXY = null;
+ inEvent.preventDefault();
+ };
+ }, this);
+ pointers.forEach(inFunction, this);
+ },
+ // For single axis scrollers, determines whether the element should emit
+ // pointer events or behave as a scroller
+ shouldScroll: function(inEvent) {
+ if (this.firstXY) {
+ var ret;
+ var scrollAxis = this.scrollType.get(inEvent.currentTarget);
+ if (scrollAxis === 'none') {
+ // this element is a touch-action: none, should never scroll
+ ret = false;
+ } else if (scrollAxis === 'XY') {
+ // this element should always scroll
+ ret = true;
+ } else {
+ var t = inEvent.changedTouches[0];
+ // check the intended scroll axis, and other axis
+ var a = scrollAxis;
+ var oa = scrollAxis === 'Y' ? 'X' : 'Y';
+ var da = Math.abs(t['client' + a] - this.firstXY[a]);
+ var doa = Math.abs(t['client' + oa] - this.firstXY[oa]);
+ // if delta in the scroll axis > delta other axis, scroll instead of
+ // making events
+ ret = da >= doa;
+ }
+ this.firstXY = null;
+ return ret;
+ }
+ },
+ findTouch: function(inTL, inId) {
+ for (var i = 0, l = inTL.length, t; i < l && (t = inTL[i]); i++) {
+ if (t.identifier === inId) {
+ return true;
+ }
+ }
+ },
+ // In some instances, a touchstart can happen without a touchend. This
+ // leaves the pointermap in a broken state.
+ // Therefore, on every touchstart, we remove the touches that did not fire a
+ // touchend event.
+ // To keep state globally consistent, we fire a
+ // pointercancel for this "abandoned" touch
+ vacuumTouches: function(inEvent) {
+ var tl = inEvent.touches;
+ // pointermap.pointers() should be < tl.length here, as the touchstart has not
+ // been processed yet.
+ if (pointermap.pointers() >= tl.length) {
+ var d = [];
+ pointermap.forEach(function(value, key) {
+ // Never remove pointerId == 1, which is mouse.
+ // Touch identifiers are 2 smaller than their pointerId, which is the
+ // index in pointermap.
+ if (key !== 1 && !this.findTouch(tl, key - 2)) {
+ var p = value.out;
+ d.push(this.touchToPointer(p));
+ }
+ }, this);
+ d.forEach(this.cancelOut, this);
+ }
+ },
+ touchstart: function(inEvent) {
+ this.vacuumTouches(inEvent);
+ this.setPrimaryTouch(inEvent.changedTouches[0]);
+ this.dedupSynthMouse(inEvent);
+ if (!this.scrolling) {
+ this.clickCount++;
+ this.processTouches(inEvent, this.overDown);
+ }
+ },
+ overDown: function(inPointer) {
+ var p = pointermap.set(inPointer.pointerId, {
+ target: inPointer.target,
+ out: inPointer,
+ outTarget: inPointer.target
+ });
+ dispatcher.over(inPointer);
+ dispatcher.enter(inPointer);
+ dispatcher.down(inPointer);
+ },
+ touchmove: function(inEvent) {
+ if (!this.scrolling) {
+ if (this.shouldScroll(inEvent)) {
+ this.scrolling = true;
+ this.touchcancel(inEvent);
+ } else {
+ inEvent.preventDefault();
+ this.processTouches(inEvent, this.moveOverOut);
+ }
+ }
+ },
+ moveOverOut: function(inPointer) {
+ var event = inPointer;
+ var pointer = pointermap.get(event.pointerId);
+ // a finger drifted off the screen, ignore it
+ if (!pointer) {
+ return;
+ }
+ var outEvent = pointer.out;
+ var outTarget = pointer.outTarget;
+ dispatcher.move(event);
+ if (outEvent && outTarget !== event.target) {
+ outEvent.relatedTarget = event.target;
+ event.relatedTarget = outTarget;
+ // recover from retargeting by shadow
+ outEvent.target = outTarget;
+ if (event.target) {
+ dispatcher.leaveOut(outEvent);
+ dispatcher.enterOver(event);
+ } else {
+ // clean up case when finger leaves the screen
+ event.target = outTarget;
+ event.relatedTarget = null;
+ this.cancelOut(event);
+ }
+ }
+ pointer.out = event;
+ pointer.outTarget = event.target;
+ },
+ touchend: function(inEvent) {
+ this.dedupSynthMouse(inEvent);
+ this.processTouches(inEvent, this.upOut);
+ },
+ upOut: function(inPointer) {
+ if (!this.scrolling) {
+ dispatcher.up(inPointer);
+ dispatcher.out(inPointer);
+ dispatcher.leave(inPointer);
+ }
+ this.cleanUpPointer(inPointer);
+ },
+ touchcancel: function(inEvent) {
+ this.processTouches(inEvent, this.cancelOut);
+ },
+ cancelOut: function(inPointer) {
+ dispatcher.cancel(inPointer);
+ dispatcher.out(inPointer);
+ dispatcher.leave(inPointer);
+ this.cleanUpPointer(inPointer);
+ },
+ cleanUpPointer: function(inPointer) {
+ pointermap['delete'](inPointer.pointerId);
+ this.removePrimaryPointer(inPointer);
+ },
+ // prevent synth mouse events from creating pointer events
+ dedupSynthMouse: function(inEvent) {
+ var lts = scope.mouseEvents.lastTouches;
+ var t = inEvent.changedTouches[0];
+ // only the primary finger will synth mouse events
+ if (this.isPrimaryTouch(t)) {
+ // remember x/y of last touch
+ var lt = {x: t.clientX, y: t.clientY};
+ lts.push(lt);
+ var fn = (function(lts, lt){
+ var i = lts.indexOf(lt);
+ if (i > -1) {
+ lts.splice(i, 1);
+ }
+ }).bind(null, lts, lt);
+ setTimeout(fn, DEDUP_TIMEOUT);
+ }
+ }
+ };
+
+ if (!HAS_TOUCH_ACTION_DELAY) {
+ INSTALLER = new scope.Installer(touchEvents.elementAdded, touchEvents.elementRemoved, touchEvents.elementChanged, touchEvents);
+ }
+
+ scope.touchEvents = touchEvents;
+})(window.PointerEventsPolyfill);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+(function(scope) {
+ var dispatcher = scope.dispatcher;
+ var pointermap = dispatcher.pointermap;
+ var HAS_BITMAP_TYPE = window.MSPointerEvent && typeof window.MSPointerEvent.MSPOINTER_TYPE_MOUSE === 'number';
+ var msEvents = {
+ events: [
+ 'MSPointerDown',
+ 'MSPointerMove',
+ 'MSPointerUp',
+ 'MSPointerOut',
+ 'MSPointerOver',
+ 'MSPointerCancel',
+ 'MSGotPointerCapture',
+ 'MSLostPointerCapture'
+ ],
+ register: function(target) {
+ dispatcher.listen(target, this.events);
+ },
+ unregister: function(target) {
+ dispatcher.unlisten(target, this.events);
+ },
+ POINTER_TYPES: [
+ '',
+ 'unavailable',
+ 'touch',
+ 'pen',
+ 'mouse'
+ ],
+ prepareEvent: function(inEvent) {
+ var e = inEvent;
+ if (HAS_BITMAP_TYPE) {
+ e = dispatcher.cloneEvent(inEvent);
+ e.pointerType = this.POINTER_TYPES[inEvent.pointerType];
+ }
+ return e;
+ },
+ cleanup: function(id) {
+ pointermap['delete'](id);
+ },
+ MSPointerDown: function(inEvent) {
+ pointermap.set(inEvent.pointerId, inEvent);
+ var e = this.prepareEvent(inEvent);
+ dispatcher.down(e);
+ },
+ MSPointerMove: function(inEvent) {
+ var e = this.prepareEvent(inEvent);
+ dispatcher.move(e);
+ },
+ MSPointerUp: function(inEvent) {
+ var e = this.prepareEvent(inEvent);
+ dispatcher.up(e);
+ this.cleanup(inEvent.pointerId);
+ },
+ MSPointerOut: function(inEvent) {
+ var e = this.prepareEvent(inEvent);
+ dispatcher.leaveOut(e);
+ },
+ MSPointerOver: function(inEvent) {
+ var e = this.prepareEvent(inEvent);
+ dispatcher.enterOver(e);
+ },
+ MSPointerCancel: function(inEvent) {
+ var e = this.prepareEvent(inEvent);
+ dispatcher.cancel(e);
+ this.cleanup(inEvent.pointerId);
+ },
+ MSLostPointerCapture: function(inEvent) {
+ var e = dispatcher.makeEvent('lostpointercapture', inEvent);
+ dispatcher.dispatchEvent(e);
+ },
+ MSGotPointerCapture: function(inEvent) {
+ var e = dispatcher.makeEvent('gotpointercapture', inEvent);
+ dispatcher.dispatchEvent(e);
+ }
+ };
+
+ scope.msEvents = msEvents;
+})(window.PointerEventsPolyfill);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+/**
+ * This module contains the handlers for native platform events.
+ * From here, the dispatcher is called to create unified pointer events.
+ * Included are touch events (v1), mouse events, and MSPointerEvents.
+ */
+(function(scope) {
+ var dispatcher = scope.dispatcher;
+
+ // only activate if this platform does not have pointer events
+ if (window.navigator.pointerEnabled === undefined) {
+ Object.defineProperty(window.navigator, 'pointerEnabled', {value: true, enumerable: true});
+
+ if (window.navigator.msPointerEnabled) {
+ var tp = window.navigator.msMaxTouchPoints;
+ Object.defineProperty(window.navigator, 'maxTouchPoints', {
+ value: tp,
+ enumerable: true
+ });
+ dispatcher.registerSource('ms', scope.msEvents);
+ } else {
+ dispatcher.registerSource('mouse', scope.mouseEvents);
+ if (window.ontouchstart !== undefined) {
+ dispatcher.registerSource('touch', scope.touchEvents);
+ }
+ }
+
+ dispatcher.register(document);
+ }
+})(window.PointerEventsPolyfill);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+(function(scope) {
+ var dispatcher = scope.dispatcher;
+ var n = window.navigator;
+ var s, r;
+ function assertDown(id) {
+ if (!dispatcher.pointermap.has(id)) {
+ throw new Error('InvalidPointerId');
+ }
+ }
+ if (n.msPointerEnabled) {
+ s = function(pointerId) {
+ assertDown(pointerId);
+ this.msSetPointerCapture(pointerId);
+ };
+ r = function(pointerId) {
+ assertDown(pointerId);
+ this.msReleasePointerCapture(pointerId);
+ };
+ } else {
+ s = function setPointerCapture(pointerId) {
+ assertDown(pointerId);
+ dispatcher.setCapture(pointerId, this);
+ };
+ r = function releasePointerCapture(pointerId) {
+ assertDown(pointerId);
+ dispatcher.releaseCapture(pointerId, this);
+ };
+ }
+ if (window.Element && !Element.prototype.setPointerCapture) {
+ Object.defineProperties(Element.prototype, {
+ 'setPointerCapture': {
+ value: s
+ },
+ 'releasePointerCapture': {
+ value: r
+ }
+ });
+ }
+})(window.PointerEventsPolyfill);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+/**
+ * PointerGestureEvent is the constructor for all PointerGesture events.
+ *
+ * @module PointerGestures
+ * @class PointerGestureEvent
+ * @extends UIEvent
+ * @constructor
+ * @param {String} inType Event type
+ * @param {Object} [inDict] Dictionary of properties to initialize on the event
+ */
+
+function PointerGestureEvent(inType, inDict) {
+ var dict = inDict || {};
+ var e = document.createEvent('Event');
+ var props = {
+ bubbles: Boolean(dict.bubbles) === dict.bubbles || true,
+ cancelable: Boolean(dict.cancelable) === dict.cancelable || true
+ };
+
+ e.initEvent(inType, props.bubbles, props.cancelable);
+
+ var keys = Object.keys(dict), k;
+ for (var i = 0; i < keys.length; i++) {
+ k = keys[i];
+ e[k] = dict[k];
+ }
+
+ e.preventTap = this.preventTap;
+
+ return e;
+}
+
+/**
+ * Allows for any gesture to prevent the tap gesture.
+ *
+ * @method preventTap
+ */
+PointerGestureEvent.prototype.preventTap = function() {
+ this.tapPrevented = true;
+};
+
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+(function(scope) {
+ /**
+ * This class contains the gesture recognizers that create the PointerGesture
+ * events.
+ *
+ * @class PointerGestures
+ * @static
+ */
+ scope = scope || {};
+ scope.utils = {
+ LCA: {
+ // Determines the lowest node in the ancestor chain of a and b
+ find: function(a, b) {
+ if (a === b) {
+ return a;
+ }
+ // fast case, a is a direct descendant of b or vice versa
+ if (a.contains) {
+ if (a.contains(b)) {
+ return a;
+ }
+ if (b.contains(a)) {
+ return b;
+ }
+ }
+ var adepth = this.depth(a);
+ var bdepth = this.depth(b);
+ var d = adepth - bdepth;
+ if (d > 0) {
+ a = this.walk(a, d);
+ } else {
+ b = this.walk(b, -d);
+ }
+ while(a && b && a !== b) {
+ a = this.walk(a, 1);
+ b = this.walk(b, 1);
+ }
+ return a;
+ },
+ walk: function(n, u) {
+ for (var i = 0; i < u; i++) {
+ n = n.parentNode;
+ }
+ return n;
+ },
+ depth: function(n) {
+ var d = 0;
+ while(n) {
+ d++;
+ n = n.parentNode;
+ }
+ return d;
+ }
+ }
+ };
+ scope.findLCA = function(a, b) {
+ return scope.utils.LCA.find(a, b);
+ }
+ window.PointerGestures = scope;
+})(window.PointerGestures);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+/**
+ * This module implements an map of pointer states
+ */
+(function(scope) {
+ var USE_MAP = window.Map && window.Map.prototype.forEach;
+ var POINTERS_FN = function(){ return this.size; };
+ function PointerMap() {
+ if (USE_MAP) {
+ var m = new Map();
+ m.pointers = POINTERS_FN;
+ return m;
+ } else {
+ this.keys = [];
+ this.values = [];
+ }
+ }
+
+ PointerMap.prototype = {
+ set: function(inId, inEvent) {
+ var i = this.keys.indexOf(inId);
+ if (i > -1) {
+ this.values[i] = inEvent;
+ } else {
+ this.keys.push(inId);
+ this.values.push(inEvent);
+ }
+ },
+ has: function(inId) {
+ return this.keys.indexOf(inId) > -1;
+ },
+ 'delete': function(inId) {
+ var i = this.keys.indexOf(inId);
+ if (i > -1) {
+ this.keys.splice(i, 1);
+ this.values.splice(i, 1);
+ }
+ },
+ get: function(inId) {
+ var i = this.keys.indexOf(inId);
+ return this.values[i];
+ },
+ clear: function() {
+ this.keys.length = 0;
+ this.values.length = 0;
+ },
+ // return value, key, map
+ forEach: function(callback, thisArg) {
+ this.values.forEach(function(v, i) {
+ callback.call(thisArg, v, this.keys[i], this);
+ }, this);
+ },
+ pointers: function() {
+ return this.keys.length;
+ }
+ };
+
+ scope.PointerMap = PointerMap;
+})(window.PointerGestures);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+(function(scope) {
+ var CLONE_PROPS = [
+ // MouseEvent
+ 'bubbles',
+ 'cancelable',
+ 'view',
+ 'detail',
+ 'screenX',
+ 'screenY',
+ 'clientX',
+ 'clientY',
+ 'ctrlKey',
+ 'altKey',
+ 'shiftKey',
+ 'metaKey',
+ 'button',
+ 'relatedTarget',
+ // DOM Level 3
+ 'buttons',
+ // PointerEvent
+ 'pointerId',
+ 'width',
+ 'height',
+ 'pressure',
+ 'tiltX',
+ 'tiltY',
+ 'pointerType',
+ 'hwTimestamp',
+ 'isPrimary',
+ // event instance
+ 'type',
+ 'target',
+ 'currentTarget',
+ 'screenX',
+ 'screenY',
+ 'pageX',
+ 'pageY',
+ 'tapPrevented'
+ ];
+
+ var CLONE_DEFAULTS = [
+ // MouseEvent
+ false,
+ false,
+ null,
+ null,
+ 0,
+ 0,
+ 0,
+ 0,
+ false,
+ false,
+ false,
+ false,
+ 0,
+ null,
+ // DOM Level 3
+ 0,
+ // PointerEvent
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ '',
+ 0,
+ false,
+ // event instance
+ '',
+ null,
+ null,
+ 0,
+ 0,
+ 0,
+ 0
+ ];
+
+ var dispatcher = {
+ handledEvents: new WeakMap(),
+ targets: new WeakMap(),
+ handlers: {},
+ recognizers: {},
+ events: {},
+ // Add a new gesture recognizer to the event listeners.
+ // Recognizer needs an `events` property.
+ registerRecognizer: function(inName, inRecognizer) {
+ var r = inRecognizer;
+ this.recognizers[inName] = r;
+ r.events.forEach(function(e) {
+ if (r[e]) {
+ this.events[e] = true;
+ var f = r[e].bind(r);
+ this.addHandler(e, f);
+ }
+ }, this);
+ },
+ addHandler: function(inEvent, inFn) {
+ var e = inEvent;
+ if (!this.handlers[e]) {
+ this.handlers[e] = [];
+ }
+ this.handlers[e].push(inFn);
+ },
+ // add event listeners for inTarget
+ registerTarget: function(inTarget) {
+ this.listen(Object.keys(this.events), inTarget);
+ },
+ // remove event listeners for inTarget
+ unregisterTarget: function(inTarget) {
+ this.unlisten(Object.keys(this.events), inTarget);
+ },
+ // LISTENER LOGIC
+ eventHandler: function(inEvent) {
+ if (this.handledEvents.get(inEvent)) {
+ return;
+ }
+ var type = inEvent.type, fns = this.handlers[type];
+ if (fns) {
+ this.makeQueue(fns, inEvent);
+ }
+ this.handledEvents.set(inEvent, true);
+ },
+ // queue event for async dispatch
+ makeQueue: function(inHandlerFns, inEvent) {
+ // must clone events to keep the (possibly shadowed) target correct for
+ // async dispatching
+ var e = this.cloneEvent(inEvent);
+ setTimeout(this.runQueue.bind(this, inHandlerFns, e), 0);
+ },
+ // Dispatch the queued events
+ runQueue: function(inHandlers, inEvent) {
+ this.currentPointerId = inEvent.pointerId;
+ for (var i = 0, f, l = inHandlers.length; (i < l) && (f = inHandlers[i]); i++) {
+ f(inEvent);
+ }
+ this.currentPointerId = 0;
+ },
+ // set up event listeners
+ listen: function(inEvents, inTarget) {
+ inEvents.forEach(function(e) {
+ this.addEvent(e, this.boundHandler, false, inTarget);
+ }, this);
+ },
+ // remove event listeners
+ unlisten: function(inEvents) {
+ inEvents.forEach(function(e) {
+ this.removeEvent(e, this.boundHandler, false, inTarget);
+ }, this);
+ },
+ addEvent: function(inEventName, inEventHandler, inCapture, inTarget) {
+ inTarget.addEventListener(inEventName, inEventHandler, inCapture);
+ },
+ removeEvent: function(inEventName, inEventHandler, inCapture, inTarget) {
+ inTarget.removeEventListener(inEventName, inEventHandler, inCapture);
+ },
+ // EVENT CREATION AND TRACKING
+ // Creates a new Event of type `inType`, based on the information in
+ // `inEvent`.
+ makeEvent: function(inType, inDict) {
+ return new PointerGestureEvent(inType, inDict);
+ },
+ /*
+ * Returns a snapshot of inEvent, with writable properties.
+ *
+ * @method cloneEvent
+ * @param {Event} inEvent An event that contains properties to copy.
+ * @return {Object} An object containing shallow copies of `inEvent`'s
+ * properties.
+ */
+ cloneEvent: function(inEvent) {
+ var eventCopy = {}, p;
+ for (var i = 0; i < CLONE_PROPS.length; i++) {
+ p = CLONE_PROPS[i];
+ eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i];
+ }
+ return eventCopy;
+ },
+ // Dispatches the event to its target.
+ dispatchEvent: function(inEvent, inTarget) {
+ var t = inTarget || this.targets.get(inEvent);
+ if (t) {
+ t.dispatchEvent(inEvent);
+ if (inEvent.tapPrevented) {
+ this.preventTap(this.currentPointerId);
+ }
+ }
+ },
+ asyncDispatchEvent: function(inEvent, inTarget) {
+ var fn = function() {
+ this.dispatchEvent(inEvent, inTarget);
+ }.bind(this);
+ setTimeout(fn, 0);
+ },
+ preventTap: function(inPointerId) {
+ var t = this.recognizers.tap;
+ if (t){
+ t.preventTap(inPointerId);
+ }
+ }
+ };
+ dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher);
+ scope.dispatcher = dispatcher;
+ var registerQueue = [];
+ var immediateRegister = false;
+ /**
+ * Enable gesture events for a given scope, typically
+ * [ShadowRoots](https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#shadow-root-object).
+ *
+ * @for PointerGestures
+ * @method register
+ * @param {ShadowRoot} scope A top level scope to enable gesture
+ * support on.
+ */
+ scope.register = function(inScope) {
+ if (immediateRegister) {
+ var pe = window.PointerEventsPolyfill;
+ if (pe) {
+ pe.register(inScope);
+ }
+ scope.dispatcher.registerTarget(inScope);
+ } else {
+ registerQueue.push(inScope);
+ }
+ };
+ // wait to register scopes until recognizers load
+ document.addEventListener('DOMContentLoaded', function() {
+ immediateRegister = true;
+ registerQueue.push(document);
+ registerQueue.forEach(scope.register);
+ });
+})(window.PointerGestures);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+/**
+ * This event is fired when a pointer is held down for 200ms.
+ *
+ * @module PointerGestures
+ * @submodule Events
+ * @class hold
+ */
+/**
+ * Milliseconds pointer has been held down.
+ * @type Number
+ * @property holdTime
+ */
+/**
+ * Type of pointer that made the holding event.
+ * @type String
+ * @property pointerType
+ */
+/**
+ * This event is fired every 200ms while a pointer is held down.
+ *
+ * @class holdpulse
+ * @extends hold
+ */
+/**
+ * This event is fired when a held pointer is released or moved.
+ *
+ * @class released
+ */
+/**
+ * Type of pointer that made the holding event.
+ * @type String
+ * @property pointerType
+ */
+
+(function(scope) {
+ var dispatcher = scope.dispatcher;
+ var hold = {
+ // wait at least HOLD_DELAY ms between hold and pulse events
+ HOLD_DELAY: 200,
+ // pointer can move WIGGLE_THRESHOLD pixels before not counting as a hold
+ WIGGLE_THRESHOLD: 16,
+ events: [
+ 'pointerdown',
+ 'pointermove',
+ 'pointerup',
+ 'pointercancel'
+ ],
+ heldPointer: null,
+ holdJob: null,
+ pulse: function() {
+ var hold = Date.now() - this.heldPointer.timeStamp;
+ var type = this.held ? 'holdpulse' : 'hold';
+ this.fireHold(type, hold);
+ this.held = true;
+ },
+ cancel: function() {
+ clearInterval(this.holdJob);
+ if (this.held) {
+ this.fireHold('release');
+ }
+ this.held = false;
+ this.heldPointer = null;
+ this.target = null;
+ this.holdJob = null;
+ },
+ pointerdown: function(inEvent) {
+ if (inEvent.isPrimary && !this.heldPointer) {
+ this.heldPointer = inEvent;
+ this.target = inEvent.target;
+ this.holdJob = setInterval(this.pulse.bind(this), this.HOLD_DELAY);
+ }
+ },
+ pointerup: function(inEvent) {
+ if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId) {
+ this.cancel();
+ }
+ },
+ pointercancel: function(inEvent) {
+ this.cancel();
+ },
+ pointermove: function(inEvent) {
+ if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId) {
+ var x = inEvent.clientX - this.heldPointer.clientX;
+ var y = inEvent.clientY - this.heldPointer.clientY;
+ if ((x * x + y * y) > this.WIGGLE_THRESHOLD) {
+ this.cancel();
+ }
+ }
+ },
+ fireHold: function(inType, inHoldTime) {
+ var p = {
+ pointerType: this.heldPointer.pointerType
+ };
+ if (inHoldTime) {
+ p.holdTime = inHoldTime;
+ }
+ var e = dispatcher.makeEvent(inType, p);
+ dispatcher.dispatchEvent(e, this.target);
+ if (e.tapPrevented) {
+ dispatcher.preventTap(this.heldPointer.pointerId);
+ }
+ }
+ };
+ dispatcher.registerRecognizer('hold', hold);
+})(window.PointerGestures);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+/**
+ * This event denotes the beginning of a series of tracking events.
+ *
+ * @module PointerGestures
+ * @submodule Events
+ * @class trackstart
+ */
+/**
+ * Pixels moved in the x direction since trackstart.
+ * @type Number
+ * @property dx
+ */
+/**
+ * Pixes moved in the y direction since trackstart.
+ * @type Number
+ * @property dy
+ */
+/**
+ * Pixels moved in the x direction since the last track.
+ * @type Number
+ * @property ddx
+ */
+/**
+ * Pixles moved in the y direction since the last track.
+ * @type Number
+ * @property ddy
+ */
+/**
+ * The clientX position of the track gesture.
+ * @type Number
+ * @property clientX
+ */
+/**
+ * The clientY position of the track gesture.
+ * @type Number
+ * @property clientY
+ */
+/**
+ * The pageX position of the track gesture.
+ * @type Number
+ * @property pageX
+ */
+/**
+ * The pageY position of the track gesture.
+ * @type Number
+ * @property pageY
+ */
+/**
+ * The screenX position of the track gesture.
+ * @type Number
+ * @property screenX
+ */
+/**
+ * The screenY position of the track gesture.
+ * @type Number
+ * @property screenY
+ */
+/**
+ * The last x axis direction of the pointer.
+ * @type Number
+ * @property xDirection
+ */
+/**
+ * The last y axis direction of the pointer.
+ * @type Number
+ * @property yDirection
+ */
+/**
+ * A shared object between all tracking events.
+ * @type Object
+ * @property trackInfo
+ */
+/**
+ * The element currently under the pointer.
+ * @type Element
+ * @property relatedTarget
+ */
+/**
+ * The type of pointer that make the track gesture.
+ * @type String
+ * @property pointerType
+ */
+/**
+ *
+ * This event fires for all pointer movement being tracked.
+ *
+ * @class track
+ * @extends trackstart
+ */
+/**
+ * This event fires when the pointer is no longer being tracked.
+ *
+ * @class trackend
+ * @extends trackstart
+ */
+
+ (function(scope) {
+ var dispatcher = scope.dispatcher;
+ var pointermap = new scope.PointerMap();
+ var track = {
+ events: [
+ 'pointerdown',
+ 'pointermove',
+ 'pointerup',
+ 'pointercancel'
+ ],
+ WIGGLE_THRESHOLD: 4,
+ clampDir: function(inDelta) {
+ return inDelta > 0 ? 1 : -1;
+ },
+ calcPositionDelta: function(inA, inB) {
+ var x = 0, y = 0;
+ if (inA && inB) {
+ x = inB.pageX - inA.pageX;
+ y = inB.pageY - inA.pageY;
+ }
+ return {x: x, y: y};
+ },
+ fireTrack: function(inType, inEvent, inTrackingData) {
+ var t = inTrackingData;
+ var d = this.calcPositionDelta(t.downEvent, inEvent);
+ var dd = this.calcPositionDelta(t.lastMoveEvent, inEvent);
+ if (dd.x) {
+ t.xDirection = this.clampDir(dd.x);
+ }
+ if (dd.y) {
+ t.yDirection = this.clampDir(dd.y);
+ }
+ var trackData = {
+ dx: d.x,
+ dy: d.y,
+ ddx: dd.x,
+ ddy: dd.y,
+ clientX: inEvent.clientX,
+ clientY: inEvent.clientY,
+ pageX: inEvent.pageX,
+ pageY: inEvent.pageY,
+ screenX: inEvent.screenX,
+ screenY: inEvent.screenY,
+ xDirection: t.xDirection,
+ yDirection: t.yDirection,
+ trackInfo: t.trackInfo,
+ relatedTarget: inEvent.target,
+ pointerType: inEvent.pointerType
+ };
+ var e = dispatcher.makeEvent(inType, trackData);
+ t.lastMoveEvent = inEvent;
+ dispatcher.dispatchEvent(e, t.downTarget);
+ },
+ pointerdown: function(inEvent) {
+ if (inEvent.isPrimary && (inEvent.pointerType === 'mouse' ? inEvent.buttons === 1 : true)) {
+ var p = {
+ downEvent: inEvent,
+ downTarget: inEvent.target,
+ trackInfo: {},
+ lastMoveEvent: null,
+ xDirection: 0,
+ yDirection: 0,
+ tracking: false
+ };
+ pointermap.set(inEvent.pointerId, p);
+ }
+ },
+ pointermove: function(inEvent) {
+ var p = pointermap.get(inEvent.pointerId);
+ if (p) {
+ if (!p.tracking) {
+ var d = this.calcPositionDelta(p.downEvent, inEvent);
+ var move = d.x * d.x + d.y * d.y;
+ // start tracking only if finger moves more than WIGGLE_THRESHOLD
+ if (move > this.WIGGLE_THRESHOLD) {
+ p.tracking = true;
+ this.fireTrack('trackstart', p.downEvent, p);
+ this.fireTrack('track', inEvent, p);
+ }
+ } else {
+ this.fireTrack('track', inEvent, p);
+ }
+ }
+ },
+ pointerup: function(inEvent) {
+ var p = pointermap.get(inEvent.pointerId);
+ if (p) {
+ if (p.tracking) {
+ this.fireTrack('trackend', inEvent, p);
+ }
+ pointermap.delete(inEvent.pointerId);
+ }
+ },
+ pointercancel: function(inEvent) {
+ this.pointerup(inEvent);
+ }
+ };
+ dispatcher.registerRecognizer('track', track);
+ })(window.PointerGestures);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+/**
+ * This event denotes a rapid down/move/up sequence from a pointer.
+ *
+ * The event is sent to the first element the pointer went down on.
+ *
+ * @module PointerGestures
+ * @submodule Events
+ * @class flick
+ */
+/**
+ * Signed velocity of the flick in the x direction.
+ * @property xVelocity
+ * @type Number
+ */
+/**
+ * Signed velocity of the flick in the y direction.
+ * @type Number
+ * @property yVelocity
+ */
+/**
+ * Unsigned total velocity of the flick.
+ * @type Number
+ * @property velocity
+ */
+/**
+ * Angle of the flick in degrees, with 0 along the
+ * positive x axis.
+ * @type Number
+ * @property angle
+ */
+/**
+ * Axis with the greatest absolute velocity. Denoted
+ * with 'x' or 'y'.
+ * @type String
+ * @property majorAxis
+ */
+/**
+ * Type of the pointer that made the flick.
+ * @type String
+ * @property pointerType
+ */
+
+(function(scope) {
+ var dispatcher = scope.dispatcher;
+ var flick = {
+ // TODO(dfreedman): value should be low enough for low speed flicks, but
+ // high enough to remove accidental flicks
+ MIN_VELOCITY: 0.5 /* px/ms */,
+ MAX_QUEUE: 4,
+ moveQueue: [],
+ target: null,
+ pointerId: null,
+ events: [
+ 'pointerdown',
+ 'pointermove',
+ 'pointerup',
+ 'pointercancel'
+ ],
+ pointerdown: function(inEvent) {
+ if (inEvent.isPrimary && !this.pointerId) {
+ this.pointerId = inEvent.pointerId;
+ this.target = inEvent.target;
+ this.addMove(inEvent);
+ }
+ },
+ pointermove: function(inEvent) {
+ if (inEvent.pointerId === this.pointerId) {
+ this.addMove(inEvent);
+ }
+ },
+ pointerup: function(inEvent) {
+ if (inEvent.pointerId === this.pointerId) {
+ this.fireFlick(inEvent);
+ }
+ this.cleanup();
+ },
+ pointercancel: function(inEvent) {
+ this.cleanup();
+ },
+ cleanup: function() {
+ this.moveQueue = [];
+ this.target = null;
+ this.pointerId = null;
+ },
+ addMove: function(inEvent) {
+ if (this.moveQueue.length >= this.MAX_QUEUE) {
+ this.moveQueue.shift();
+ }
+ this.moveQueue.push(inEvent);
+ },
+ fireFlick: function(inEvent) {
+ var e = inEvent;
+ var l = this.moveQueue.length;
+ var dt, dx, dy, tx, ty, tv, x = 0, y = 0, v = 0;
+ // flick based off the fastest segment of movement
+ for (var i = 0, m; i < l && (m = this.moveQueue[i]); i++) {
+ dt = e.timeStamp - m.timeStamp;
+ dx = e.clientX - m.clientX, dy = e.clientY - m.clientY;
+ tx = dx / dt, ty = dy / dt, tv = Math.sqrt(tx * tx + ty * ty);
+ if (tv > v) {
+ x = tx, y = ty, v = tv;
+ }
+ }
+ var ma = Math.abs(x) > Math.abs(y) ? 'x' : 'y';
+ var a = this.calcAngle(x, y);
+ if (Math.abs(v) >= this.MIN_VELOCITY) {
+ var ev = dispatcher.makeEvent('flick', {
+ xVelocity: x,
+ yVelocity: y,
+ velocity: v,
+ angle: a,
+ majorAxis: ma,
+ pointerType: inEvent.pointerType
+ });
+ dispatcher.dispatchEvent(ev, this.target);
+ }
+ },
+ calcAngle: function(inX, inY) {
+ return (Math.atan2(inY, inX) * 180 / Math.PI);
+ }
+ };
+ dispatcher.registerRecognizer('flick', flick);
+})(window.PointerGestures);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+/*
+ * Basic strategy: find the farthest apart points, use as diameter of circle
+ * react to size change and rotation of the chord
+ */
+
+/**
+ * @module PointerGestures
+ * @submodule Events
+ * @class pinch
+ */
+/**
+ * Scale of the pinch zoom gesture
+ * @property scale
+ * @type Number
+ */
+/**
+ * Center X position of pointers causing pinch
+ * @property centerX
+ * @type Number
+ */
+/**
+ * Center Y position of pointers causing pinch
+ * @property centerY
+ * @type Number
+ */
+
+/**
+ * @module PointerGestures
+ * @submodule Events
+ * @class rotate
+ */
+/**
+ * Angle (in degrees) of rotation. Measured from starting positions of pointers.
+ * @property angle
+ * @type Number
+ */
+/**
+ * Center X position of pointers causing rotation
+ * @property centerX
+ * @type Number
+ */
+/**
+ * Center Y position of pointers causing rotation
+ * @property centerY
+ * @type Number
+ */
+(function(scope) {
+ var dispatcher = scope.dispatcher;
+ var pointermap = new scope.PointerMap();
+ var RAD_TO_DEG = 180 / Math.PI;
+ var pinch = {
+ events: [
+ 'pointerdown',
+ 'pointermove',
+ 'pointerup',
+ 'pointercancel'
+ ],
+ reference: {},
+ pointerdown: function(ev) {
+ pointermap.set(ev.pointerId, ev);
+ if (pointermap.pointers() == 2) {
+ var points = this.calcChord();
+ var angle = this.calcAngle(points);
+ this.reference = {
+ angle: angle,
+ diameter: points.diameter,
+ target: scope.findLCA(points.a.target, points.b.target)
+ };
+ }
+ },
+ pointerup: function(ev) {
+ pointermap.delete(ev.pointerId);
+ },
+ pointermove: function(ev) {
+ if (pointermap.has(ev.pointerId)) {
+ pointermap.set(ev.pointerId, ev);
+ if (pointermap.pointers() > 1) {
+ this.calcPinchRotate();
+ }
+ }
+ },
+ pointercancel: function(ev) {
+ this.pointerup(ev);
+ },
+ dispatchPinch: function(diameter, points) {
+ var zoom = diameter / this.reference.diameter;
+ var ev = dispatcher.makeEvent('pinch', {
+ scale: zoom,
+ centerX: points.center.x,
+ centerY: points.center.y
+ });
+ dispatcher.dispatchEvent(ev, this.reference.target);
+ },
+ dispatchRotate: function(angle, points) {
+ var diff = Math.round((angle - this.reference.angle) % 360);
+ var ev = dispatcher.makeEvent('rotate', {
+ angle: diff,
+ centerX: points.center.x,
+ centerY: points.center.y
+ });
+ dispatcher.dispatchEvent(ev, this.reference.target);
+ },
+ calcPinchRotate: function() {
+ var points = this.calcChord();
+ var diameter = points.diameter;
+ var angle = this.calcAngle(points);
+ if (diameter != this.reference.diameter) {
+ this.dispatchPinch(diameter, points);
+ }
+ if (angle != this.reference.angle) {
+ this.dispatchRotate(angle, points);
+ }
+ },
+ calcChord: function() {
+ var pointers = [];
+ pointermap.forEach(function(p) {
+ pointers.push(p);
+ });
+ var dist = 0;
+ var points = {};
+ var x, y, d;
+ for (var i = 0; i < pointers.length; i++) {
+ var a = pointers[i];
+ for (var j = i + 1; j < pointers.length; j++) {
+ var b = pointers[j];
+ x = Math.abs(a.clientX - b.clientX);
+ y = Math.abs(a.clientY - b.clientY);
+ d = x + y;
+ if (d > dist) {
+ dist = d;
+ points = {a: a, b: b};
+ }
+ }
+ }
+ x = Math.abs(points.a.clientX + points.b.clientX) / 2;
+ y = Math.abs(points.a.clientY + points.b.clientY) / 2;
+ points.center = { x: x, y: y };
+ points.diameter = dist;
+ return points;
+ },
+ calcAngle: function(points) {
+ var x = points.a.clientX - points.b.clientX;
+ var y = points.a.clientY - points.b.clientY;
+ return (360 + Math.atan2(y, x) * RAD_TO_DEG) % 360;
+ },
+ };
+ dispatcher.registerRecognizer('pinch', pinch);
+})(window.PointerGestures);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+/**
+ * This event is fired when a pointer quickly goes down and up, and is used to
+ * denote activation.
+ *
+ * Any gesture event can prevent the tap event from being created by calling
+ * `event.preventTap`.
+ *
+ * Any pointer event can prevent the tap by setting the `tapPrevented` property
+ * on itself.
+ *
+ * @module PointerGestures
+ * @submodule Events
+ * @class tap
+ */
+/**
+ * X axis position of the tap.
+ * @property x
+ * @type Number
+ */
+/**
+ * Y axis position of the tap.
+ * @property y
+ * @type Number
+ */
+/**
+ * Type of the pointer that made the tap.
+ * @property pointerType
+ * @type String
+ */
+(function(scope) {
+ var dispatcher = scope.dispatcher;
+ var pointermap = new scope.PointerMap();
+ var tap = {
+ events: [
+ 'pointerdown',
+ 'pointermove',
+ 'pointerup',
+ 'pointercancel',
+ 'keyup'
+ ],
+ pointerdown: function(inEvent) {
+ if (inEvent.isPrimary && !inEvent.tapPrevented) {
+ pointermap.set(inEvent.pointerId, {
+ target: inEvent.target,
+ x: inEvent.clientX,
+ y: inEvent.clientY
+ });
+ }
+ },
+ pointermove: function(inEvent) {
+ if (inEvent.isPrimary) {
+ var start = pointermap.get(inEvent.pointerId);
+ if (start) {
+ if (inEvent.tapPrevented) {
+ pointermap.delete(inEvent.pointerId);
+ }
+ }
+ }
+ },
+ pointerup: function(inEvent) {
+ var start = pointermap.get(inEvent.pointerId);
+ if (start && !inEvent.tapPrevented) {
+ var t = scope.findLCA(start.target, inEvent.target);
+ if (t) {
+ var e = dispatcher.makeEvent('tap', {
+ x: inEvent.clientX,
+ y: inEvent.clientY,
+ detail: inEvent.detail,
+ pointerType: inEvent.pointerType
+ });
+ dispatcher.dispatchEvent(e, t);
+ }
+ }
+ pointermap.delete(inEvent.pointerId);
+ },
+ pointercancel: function(inEvent) {
+ pointermap.delete(inEvent.pointerId);
+ },
+ keyup: function(inEvent) {
+ var code = inEvent.keyCode;
+ // 32 == spacebar
+ if (code === 32) {
+ var t = inEvent.target;
+ if (!(t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement)) {
+ dispatcher.dispatchEvent(dispatcher.makeEvent('tap', {
+ x: 0,
+ y: 0,
+ detail: 0,
+ pointerType: 'unavailable'
+ }), t);
+ }
+ }
+ },
+ preventTap: function(inPointerId) {
+ pointermap.delete(inPointerId);
+ }
+ };
+ dispatcher.registerRecognizer('tap', tap);
+})(window.PointerGestures);
+
+// Copyright 2011 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 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;
+ }
+
+ // 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);
+ }
+ }
+
+ Node.prototype.bind = function(name, observable) {
+ console.error('Unhandled binding to Node: ', this, name, observable);
+ };
+
+ function unbind(node, name) {
+ var bindings = node.bindings;
+ if (!bindings) {
+ node.bindings = {};
+ return;
+ }
+
+ var binding = bindings[name];
+ if (!binding)
+ return;
+
+ binding.close();
+ bindings[name] = undefined;
+ }
+
+ Node.prototype.unbind = function(name) {
+ unbind(this, name);
+ };
+
+ Node.prototype.unbindAll = function() {
+ if (!this.bindings)
+ return;
+ var names = Object.keys(this.bindings);
+ for (var i = 0; i < names.length; i++) {
+ var binding = this.bindings[names[i]];
+ if (binding)
+ binding.close();
+ }
+
+ this.bindings = {};
+ };
+
+ 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);
+ };
+ }
+
+ 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);
+
+ unbind(this, 'textContent');
+ updateText(this, value.open(textBinding(this)));
+ return this.bindings.textContent = value;
+ }
+
+ 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);
+
+ unbind(this, name);
+ updateAttribute(this, name, conditional,
+ value.open(attributeBinding(this, name, conditional)));
+
+ return this.bindings[name] = value;
+ };
+
+ 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';
+ 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);
+
+ var capturedClose = observable.close;
+ observable.close = function() {
+ if (!capturedClose)
+ return;
+ input.removeEventListener(eventType, eventHandler);
+
+ observable.close = capturedClose;
+ observable.close();
+ capturedClose = undefined;
+ }
+ }
+
+ 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.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);
+
+ unbind(this, name);
+ bindInputEvent(this, name, value, postEventFn);
+ updateInput(this, name,
+ value.open(inputBinding(this, name, sanitizeFn)),
+ sanitizeFn);
+
+ return this.bindings[name] = value;
+ }
+
+ 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);
+
+ unbind(this, 'value');
+ bindInputEvent(this, 'value', value);
+ updateInput(this, 'value',
+ value.open(inputBinding(this, 'value', sanitizeValue)));
+
+ return this.bindings.value = value;
+ }
+
+ 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.setValue(select.value);
+ selectBinding.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);
+
+ unbind(this, 'value');
+ bindInputEvent(this, 'value', value);
+ updateOption(this, value.open(optionBinding(this)));
+ return this.bindings.value = value;
+ }
+
+ 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);
+
+ unbind(this, name);
+ bindInputEvent(this, name, value);
+ updateInput(this, name,
+ value.open(inputBinding(this, name)));
+ return this.bindings[name] = value;
+ }
+})(this);
+
+// Copyright 2011 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';
+
+ 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';
+
+ 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_.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);
+ }
+ }
+
+ /**
+ * 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, {
+ 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;
+ this.bindings.iterator = undefined;
+ }
+
+ return;
+ }
+
+ if (!this.iterator_) {
+ this.iterator_ = new TemplateIterator(this);
+ this.bindings = this.bindings || {};
+ this.bindings.iterator = this.iterator_;
+ }
+
+ this.iterator_.updateDependencies(directives, this.model_);
+ return this.iterator_;
+ },
+
+ createInstance: function(model, bindingDelegate, delegate_,
+ instanceBindings_) {
+ if (bindingDelegate)
+ delegate_ = this.newDelegate_(bindingDelegate);
+
+ var content = this.ref.content;
+ var map = this.bindingMap_;
+ if (!map || map.content !== content) {
+ // TODO(rafaelw): Setup a MutationObserver on content to detect
+ // when the instanceMap is invalid.
+ map = createInstanceBindingMap(content,
+ delegate_ && delegate_.prepareBinding) || [];
+ map.content = content;
+ this.bindingMap_ = map;
+ }
+
+ var stagingDocument = getTemplateStagingDocument(this);
+ var instance = stagingDocument.createDocumentFragment();
+ instance.templateCreator_ = this;
+ instance.protoContent_ = content;
+
+ var instanceRecord = {
+ firstNode: null,
+ lastNode: null,
+ model: model
+ };
+
+ var i = 0;
+ for (var child = content.firstChild; child; child = child.nextSibling) {
+ var clone = cloneAndBindInstance(child, instance, stagingDocument,
+ map.children[i++],
+ model,
+ delegate_,
+ instanceBindings_);
+ clone.templateInstance_ = instanceRecord;
+ }
+
+ 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;
+ },
+
+ 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 {
+ raw: bindingDelegate,
+ prepareBinding: delegateFn('prepareBinding'),
+ prepareInstanceModel: delegateFn('prepareInstanceModel'),
+ prepareInstancePositionChanged:
+ delegateFn('prepareInstancePositionChanged')
+ };
+ },
+
+ // TODO(rafaelw): Assigning .bindingDelegate always succeeds. It may
+ // make sense to issue a warning or even throw if the template is already
+ // "activated", since this would be a strange thing to do.
+ set bindingDelegate(bindingDelegate) {
+ 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;
+ tokens.push(Path.get(pathString)); // PATH
+ var delegateFn = prepareBindingFn &&
+ prepareBindingFn(pathString, name, node);
+ 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);
+ }
+
+ 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;
+ }
+
+ Object.defineProperty(Node.prototype, 'templateInstance', {
+ get: function() {
+ var instance = this.templateInstance_;
+ return instance ? instance :
+ (this.parentNode ? this.parentNode.templateInstance : undefined);
+ }
+ });
+
+ function TemplateIterator(templateElement) {
+ this.closed = false;
+ this.templateElement_ = templateElement;
+
+ // Flattened array of tuples:
+ // <instanceTerminatorNode, [bindingsSetupByInstance]>
+ this.terminators = [];
+
+ 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_;
+
+ if (directives.if) {
+ deps.hasIf = true;
+ deps.ifOneTime = directives.if.onlyOneTime;
+ deps.ifValue = processBinding(IF, directives.if, template, model);
+
+ // oneTime if & predicate is false. nothing else to do.
+ if (deps.ifOneTime && !deps.ifValue) {
+ this.updateIteratedValue();
+ return;
+ }
+
+ if (!deps.ifOneTime)
+ deps.ifValue.open(this.updateIteratedValue, 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);
+ }
+
+ if (!deps.oneTime)
+ deps.value.open(this.updateIteratedValue, this);
+
+ this.updateIteratedValue();
+ },
+
+ updateIteratedValue: function() {
+ if (this.deps.hasIf) {
+ var ifValue = this.deps.ifValue;
+ if (!this.deps.ifOneTime)
+ ifValue = ifValue.discardChanges();
+ if (!ifValue) {
+ this.valueChanged();
+ return;
+ }
+ }
+
+ var value = this.deps.value;
+ if (!this.deps.oneTime)
+ value = value.discardChanges();
+ 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));
+ },
+
+ getTerminatorAt: function(index) {
+ if (index == -1)
+ return this.templateElement_;
+ var terminator = this.terminators[index*2];
+ if (terminator.nodeType !== Node.ELEMENT_NODE ||
+ this.templateElement_ === terminator) {
+ return terminator;
+ }
+
+ var subIterator = terminator.iterator_;
+ if (!subIterator)
+ return terminator;
+
+ return subIterator.getTerminatorAt(subIterator.terminators.length/2 - 1);
+ },
+
+ // TODO(rafaelw): If we inserting sequences of instances we can probably
+ // avoid lots of calls to getTerminatorAt(), or cache its result.
+ insertInstanceAt: function(index, fragment, instanceNodes,
+ instanceBindings) {
+ var previousTerminator = this.getTerminatorAt(index - 1);
+ var terminator = previousTerminator;
+ if (fragment)
+ terminator = fragment.lastChild || terminator;
+ else if (instanceNodes)
+ terminator = instanceNodes[instanceNodes.length - 1] || terminator;
+
+ this.terminators.splice(index*2, 0, terminator, instanceBindings);
+ var parent = this.templateElement_.parentNode;
+ var insertBeforeNode = previousTerminator.nextSibling;
+
+ if (fragment) {
+ parent.insertBefore(fragment, insertBeforeNode);
+ } else if (instanceNodes) {
+ for (var i = 0; i < instanceNodes.length; i++)
+ parent.insertBefore(instanceNodes[i], insertBeforeNode);
+ }
+ },
+
+ extractInstanceAt: function(index) {
+ var instanceNodes = [];
+ var previousTerminator = this.getTerminatorAt(index - 1);
+ var terminator = this.getTerminatorAt(index);
+ instanceNodes.instanceBindings = this.terminators[index*2 + 1];
+ this.terminators.splice(index*2, 2);
+
+ var parent = this.templateElement_.parentNode;
+ while (terminator !== previousTerminator) {
+ var node = previousTerminator.nextSibling;
+ if (node == terminator)
+ terminator = previousTerminator;
+
+ parent.removeChild(node);
+ instanceNodes.push(node);
+ }
+
+ return instanceNodes;
+ },
+
+ 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);
+ }
+
+ var instanceCache = new Map;
+ var removeDelta = 0;
+ splices.forEach(function(splice) {
+ splice.removed.forEach(function(model) {
+ var instanceNodes =
+ this.extractInstanceAt(splice.index + removeDelta);
+ instanceCache.set(model, instanceNodes);
+ }, this);
+
+ removeDelta -= splice.addedCount;
+ }, this);
+
+ splices.forEach(function(splice) {
+ var addIndex = splice.index;
+ for (; addIndex < splice.index + splice.addedCount; addIndex++) {
+ var model = this.iteratedValue[addIndex];
+ var fragment = undefined;
+ var instanceNodes = instanceCache.get(model);
+ var instanceBindings;
+ if (instanceNodes) {
+ instanceCache.delete(model);
+ instanceBindings = instanceNodes.instanceBindings;
+ } else {
+ instanceBindings = [];
+ if (this.instanceModelFn_)
+ model = this.instanceModelFn_(model);
+
+ if (model !== undefined) {
+ fragment = template.createInstance(model, undefined, delegate,
+ instanceBindings);
+ }
+ }
+
+ this.insertInstanceAt(addIndex, fragment, instanceNodes,
+ instanceBindings);
+ }
+ }, this);
+
+ instanceCache.forEach(function(instanceNodes) {
+ this.closeInstanceBindings(instanceNodes.instanceBindings);
+ }, this);
+
+ if (this.instancePositionChangedFn_)
+ this.reportInstancesMoved(splices);
+ },
+
+ reportInstanceMoved: function(index) {
+ var previousTerminator = this.getTerminatorAt(index - 1);
+ var terminator = this.getTerminatorAt(index);
+ if (previousTerminator === terminator)
+ return; // instance has zero nodes.
+
+ // We must use the first node of the instance, because any subsequent
+ // nodes may have been generated by sub-templates.
+ // TODO(rafaelw): This is brittle WRT instance mutation -- e.g. if the
+ // first node was removed by script.
+ var templateInstance = previousTerminator.nextSibling.templateInstance;
+ this.instancePositionChangedFn_(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.terminators.length / 2;
+ while (index < length) {
+ this.reportInstanceMoved(index);
+ index++;
+ }
+ },
+
+ closeInstanceBindings: function(instanceBindings) {
+ for (var i = 0; i < instanceBindings.length; i++) {
+ instanceBindings[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 = 1; i < this.terminators.length; i += 2) {
+ this.closeInstanceBindings(this.terminators[i]);
+ }
+
+ this.terminators.length = 0;
+ this.closeDeps();
+ this.templateElement_.iterator_ = undefined;
+ this.closed = true;
+ }
+ };
+
+ // Polyfill-specific API.
+ HTMLTemplateElement.forAllTemplatesFrom_ = forAllTemplatesFrom;
+})(this);
+
+/*
+ Copyright (C) 2013 Ariya Hidayat <ariya.hidayat@gmail.com>
+ Copyright (C) 2013 Thaddee Tyl <thaddee.tyl@gmail.com>
+ Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>
+ Copyright (C) 2012 Mathias Bynens <mathias@qiwi.be>
+ Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl>
+ Copyright (C) 2012 Kris Kowal <kris.kowal@cixar.com>
+ Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com>
+ Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com>
+ Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+(function (global) {
+ 'use strict';
+
+ var Token,
+ TokenName,
+ Syntax,
+ Messages,
+ source,
+ index,
+ length,
+ delegate,
+ lookahead,
+ state;
+
+ Token = {
+ BooleanLiteral: 1,
+ EOF: 2,
+ Identifier: 3,
+ Keyword: 4,
+ NullLiteral: 5,
+ NumericLiteral: 6,
+ Punctuator: 7,
+ StringLiteral: 8
+ };
+
+ TokenName = {};
+ TokenName[Token.BooleanLiteral] = 'Boolean';
+ TokenName[Token.EOF] = '<end>';
+ TokenName[Token.Identifier] = 'Identifier';
+ TokenName[Token.Keyword] = 'Keyword';
+ TokenName[Token.NullLiteral] = 'Null';
+ TokenName[Token.NumericLiteral] = 'Numeric';
+ TokenName[Token.Punctuator] = 'Punctuator';
+ TokenName[Token.StringLiteral] = 'String';
+
+ Syntax = {
+ ArrayExpression: 'ArrayExpression',
+ BinaryExpression: 'BinaryExpression',
+ CallExpression: 'CallExpression',
+ ConditionalExpression: 'ConditionalExpression',
+ EmptyStatement: 'EmptyStatement',
+ ExpressionStatement: 'ExpressionStatement',
+ Identifier: 'Identifier',
+ Literal: 'Literal',
+ LabeledStatement: 'LabeledStatement',
+ LogicalExpression: 'LogicalExpression',
+ MemberExpression: 'MemberExpression',
+ ObjectExpression: 'ObjectExpression',
+ Program: 'Program',
+ Property: 'Property',
+ ThisExpression: 'ThisExpression',
+ UnaryExpression: 'UnaryExpression'
+ };
+
+ // Error messages should be identical to V8.
+ Messages = {
+ UnexpectedToken: 'Unexpected token %0',
+ UnknownLabel: 'Undefined label \'%0\'',
+ Redeclaration: '%0 \'%1\' has already been declared'
+ };
+
+ // Ensure the condition is true, otherwise throw an error.
+ // This is only to have a better contract semantic, i.e. another safety net
+ // to catch a logic error. The condition shall be fulfilled in normal case.
+ // Do NOT use this to enforce a certain condition on any user input.
+
+ function assert(condition, message) {
+ if (!condition) {
+ throw new Error('ASSERT: ' + message);
+ }
+ }
+
+ function isDecimalDigit(ch) {
+ return (ch >= 48 && ch <= 57); // 0..9
+ }
+
+
+ // 7.2 White Space
+
+ function isWhiteSpace(ch) {
+ return (ch === 32) || // space
+ (ch === 9) || // tab
+ (ch === 0xB) ||
+ (ch === 0xC) ||
+ (ch === 0xA0) ||
+ (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(String.fromCharCode(ch)) > 0);
+ }
+
+ // 7.3 Line Terminators
+
+ function isLineTerminator(ch) {
+ return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029);
+ }
+
+ // 7.6 Identifier Names and Identifiers
+
+ function isIdentifierStart(ch) {
+ return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore)
+ (ch >= 65 && ch <= 90) || // A..Z
+ (ch >= 97 && ch <= 122); // a..z
+ }
+
+ function isIdentifierPart(ch) {
+ return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore)
+ (ch >= 65 && ch <= 90) || // A..Z
+ (ch >= 97 && ch <= 122) || // a..z
+ (ch >= 48 && ch <= 57); // 0..9
+ }
+
+ // 7.6.1.1 Keywords
+
+ function isKeyword(id) {
+ return (id === 'this')
+ }
+
+ // 7.4 Comments
+
+ function skipWhitespace() {
+ while (index < length && isWhiteSpace(source.charCodeAt(index))) {
+ ++index;
+ }
+ }
+
+ function getIdentifier() {
+ var start, ch;
+
+ start = index++;
+ while (index < length) {
+ ch = source.charCodeAt(index);
+ if (isIdentifierPart(ch)) {
+ ++index;
+ } else {
+ break;
+ }
+ }
+
+ return source.slice(start, index);
+ }
+
+ function scanIdentifier() {
+ var start, id, type;
+
+ start = index;
+
+ id = getIdentifier();
+
+ // There is no keyword or literal with only one character.
+ // Thus, it must be an identifier.
+ if (id.length === 1) {
+ type = Token.Identifier;
+ } else if (isKeyword(id)) {
+ type = Token.Keyword;
+ } else if (id === 'null') {
+ type = Token.NullLiteral;
+ } else if (id === 'true' || id === 'false') {
+ type = Token.BooleanLiteral;
+ } else {
+ type = Token.Identifier;
+ }
+
+ return {
+ type: type,
+ value: id,
+ range: [start, index]
+ };
+ }
+
+
+ // 7.7 Punctuators
+
+ function scanPunctuator() {
+ var start = index,
+ code = source.charCodeAt(index),
+ code2,
+ ch1 = source[index],
+ ch2;
+
+ switch (code) {
+
+ // Check for most common single-character punctuators.
+ case 46: // . dot
+ case 40: // ( open bracket
+ case 41: // ) close bracket
+ case 59: // ; semicolon
+ case 44: // , comma
+ case 123: // { open curly brace
+ case 125: // } close curly brace
+ case 91: // [
+ case 93: // ]
+ case 58: // :
+ case 63: // ?
+ ++index;
+ return {
+ type: Token.Punctuator,
+ value: String.fromCharCode(code),
+ range: [start, index]
+ };
+
+ default:
+ code2 = source.charCodeAt(index + 1);
+
+ // '=' (char #61) marks an assignment or comparison operator.
+ if (code2 === 61) {
+ switch (code) {
+ case 37: // %
+ case 38: // &
+ case 42: // *:
+ case 43: // +
+ case 45: // -
+ case 47: // /
+ case 60: // <
+ case 62: // >
+ case 124: // |
+ index += 2;
+ return {
+ type: Token.Punctuator,
+ value: String.fromCharCode(code) + String.fromCharCode(code2),
+ range: [start, index]
+ };
+
+ case 33: // !
+ case 61: // =
+ index += 2;
+
+ // !== and ===
+ if (source.charCodeAt(index) === 61) {
+ ++index;
+ }
+ return {
+ type: Token.Punctuator,
+ value: source.slice(start, index),
+ range: [start, index]
+ };
+ default:
+ break;
+ }
+ }
+ break;
+ }
+
+ // Peek more characters.
+
+ ch2 = source[index + 1];
+
+ // Other 2-character punctuators: && ||
+
+ if (ch1 === ch2 && ('&|'.indexOf(ch1) >= 0)) {
+ index += 2;
+ return {
+ type: Token.Punctuator,
+ value: ch1 + ch2,
+ range: [start, index]
+ };
+ }
+
+ if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) {
+ ++index;
+ return {
+ type: Token.Punctuator,
+ value: ch1,
+ range: [start, index]
+ };
+ }
+
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ // 7.8.3 Numeric Literals
+ function scanNumericLiteral() {
+ var number, start, ch;
+
+ ch = source[index];
+ assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'),
+ 'Numeric literal must start with a decimal digit or a decimal point');
+
+ start = index;
+ number = '';
+ if (ch !== '.') {
+ number = source[index++];
+ ch = source[index];
+
+ // Hex number starts with '0x'.
+ // Octal number starts with '0'.
+ if (number === '0') {
+ // decimal number starts with '0' such as '09' is illegal.
+ if (ch && isDecimalDigit(ch.charCodeAt(0))) {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+ }
+
+ while (isDecimalDigit(source.charCodeAt(index))) {
+ number += source[index++];
+ }
+ ch = source[index];
+ }
+
+ if (ch === '.') {
+ number += source[index++];
+ while (isDecimalDigit(source.charCodeAt(index))) {
+ number += source[index++];
+ }
+ ch = source[index];
+ }
+
+ if (ch === 'e' || ch === 'E') {
+ number += source[index++];
+
+ ch = source[index];
+ if (ch === '+' || ch === '-') {
+ number += source[index++];
+ }
+ if (isDecimalDigit(source.charCodeAt(index))) {
+ while (isDecimalDigit(source.charCodeAt(index))) {
+ number += source[index++];
+ }
+ } else {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+ }
+
+ if (isIdentifierStart(source.charCodeAt(index))) {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ return {
+ type: Token.NumericLiteral,
+ value: parseFloat(number),
+ range: [start, index]
+ };
+ }
+
+ // 7.8.4 String Literals
+
+ function scanStringLiteral() {
+ var str = '', quote, start, ch, octal = false;
+
+ quote = source[index];
+ assert((quote === '\'' || quote === '"'),
+ 'String literal must starts with a quote');
+
+ start = index;
+ ++index;
+
+ while (index < length) {
+ ch = source[index++];
+
+ if (ch === quote) {
+ quote = '';
+ break;
+ } else if (ch === '\\') {
+ ch = source[index++];
+ if (!ch || !isLineTerminator(ch.charCodeAt(0))) {
+ switch (ch) {
+ case 'n':
+ str += '\n';
+ break;
+ case 'r':
+ str += '\r';
+ break;
+ case 't':
+ str += '\t';
+ break;
+ case 'b':
+ str += '\b';
+ break;
+ case 'f':
+ str += '\f';
+ break;
+ case 'v':
+ str += '\x0B';
+ break;
+
+ default:
+ str += ch;
+ break;
+ }
+ } else {
+ if (ch === '\r' && source[index] === '\n') {
+ ++index;
+ }
+ }
+ } else if (isLineTerminator(ch.charCodeAt(0))) {
+ break;
+ } else {
+ str += ch;
+ }
+ }
+
+ if (quote !== '') {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ return {
+ type: Token.StringLiteral,
+ value: str,
+ octal: octal,
+ range: [start, index]
+ };
+ }
+
+ function isIdentifierName(token) {
+ return token.type === Token.Identifier ||
+ token.type === Token.Keyword ||
+ token.type === Token.BooleanLiteral ||
+ token.type === Token.NullLiteral;
+ }
+
+ function advance() {
+ var ch;
+
+ skipWhitespace();
+
+ if (index >= length) {
+ return {
+ type: Token.EOF,
+ range: [index, index]
+ };
+ }
+
+ ch = source.charCodeAt(index);
+
+ // Very common: ( and ) and ;
+ if (ch === 40 || ch === 41 || ch === 58) {
+ return scanPunctuator();
+ }
+
+ // String literal starts with single quote (#39) or double quote (#34).
+ if (ch === 39 || ch === 34) {
+ return scanStringLiteral();
+ }
+
+ if (isIdentifierStart(ch)) {
+ return scanIdentifier();
+ }
+
+ // Dot (.) char #46 can also start a floating-point number, hence the need
+ // to check the next character.
+ if (ch === 46) {
+ if (isDecimalDigit(source.charCodeAt(index + 1))) {
+ return scanNumericLiteral();
+ }
+ return scanPunctuator();
+ }
+
+ if (isDecimalDigit(ch)) {
+ return scanNumericLiteral();
+ }
+
+ return scanPunctuator();
+ }
+
+ function lex() {
+ var token;
+
+ token = lookahead;
+ index = token.range[1];
+
+ lookahead = advance();
+
+ index = token.range[1];
+
+ return token;
+ }
+
+ function peek() {
+ var pos;
+
+ pos = index;
+ lookahead = advance();
+ index = pos;
+ }
+
+ // Throw an exception
+
+ function throwError(token, messageFormat) {
+ var error,
+ args = Array.prototype.slice.call(arguments, 2),
+ msg = messageFormat.replace(
+ /%(\d)/g,
+ function (whole, index) {
+ assert(index < args.length, 'Message reference must be in range');
+ return args[index];
+ }
+ );
+
+ error = new Error(msg);
+ error.index = index;
+ error.description = msg;
+ throw error;
+ }
+
+ // Throw an exception because of the token.
+
+ function throwUnexpected(token) {
+ throwError(token, Messages.UnexpectedToken, token.value);
+ }
+
+ // Expect the next token to match the specified punctuator.
+ // If not, an exception will be thrown.
+
+ function expect(value) {
+ var token = lex();
+ if (token.type !== Token.Punctuator || token.value !== value) {
+ throwUnexpected(token);
+ }
+ }
+
+ // Return true if the next token matches the specified punctuator.
+
+ function match(value) {
+ return lookahead.type === Token.Punctuator && lookahead.value === value;
+ }
+
+ // Return true if the next token matches the specified keyword
+
+ function matchKeyword(keyword) {
+ return lookahead.type === Token.Keyword && lookahead.value === keyword;
+ }
+
+ function consumeSemicolon() {
+ // Catch the very common case first: immediately a semicolon (char #59).
+ if (source.charCodeAt(index) === 59) {
+ lex();
+ return;
+ }
+
+ skipWhitespace();
+
+ if (match(';')) {
+ lex();
+ return;
+ }
+
+ if (lookahead.type !== Token.EOF && !match('}')) {
+ throwUnexpected(lookahead);
+ }
+ }
+
+ // 11.1.4 Array Initialiser
+
+ function parseArrayInitialiser() {
+ var elements = [];
+
+ expect('[');
+
+ while (!match(']')) {
+ if (match(',')) {
+ lex();
+ elements.push(null);
+ } else {
+ elements.push(parseExpression());
+
+ if (!match(']')) {
+ expect(',');
+ }
+ }
+ }
+
+ expect(']');
+
+ return delegate.createArrayExpression(elements);
+ }
+
+ // 11.1.5 Object Initialiser
+
+ function parseObjectPropertyKey() {
+ var token;
+
+ skipWhitespace();
+ token = lex();
+
+ // Note: This function is called only from parseObjectProperty(), where
+ // EOF and Punctuator tokens are already filtered out.
+ if (token.type === Token.StringLiteral || token.type === Token.NumericLiteral) {
+ return delegate.createLiteral(token);
+ }
+
+ return delegate.createIdentifier(token.value);
+ }
+
+ function parseObjectProperty() {
+ var token, key;
+
+ token = lookahead;
+ skipWhitespace();
+
+ if (token.type === Token.EOF || token.type === Token.Punctuator) {
+ throwUnexpected(token);
+ }
+
+ key = parseObjectPropertyKey();
+ expect(':');
+ return delegate.createProperty('init', key, parseExpression());
+ }
+
+ function parseObjectInitialiser() {
+ var properties = [];
+
+ expect('{');
+
+ while (!match('}')) {
+ properties.push(parseObjectProperty());
+
+ if (!match('}')) {
+ expect(',');
+ }
+ }
+
+ expect('}');
+
+ return delegate.createObjectExpression(properties);
+ }
+
+ // 11.1.6 The Grouping Operator
+
+ function parseGroupExpression() {
+ var expr;
+
+ expect('(');
+
+ expr = parseExpression();
+
+ expect(')');
+
+ return expr;
+ }
+
+
+ // 11.1 Primary Expressions
+
+ function parsePrimaryExpression() {
+ var type, token, expr;
+
+ if (match('(')) {
+ return parseGroupExpression();
+ }
+
+ type = lookahead.type;
+
+ if (type === Token.Identifier) {
+ expr = delegate.createIdentifier(lex().value);
+ } else if (type === Token.StringLiteral || type === Token.NumericLiteral) {
+ expr = delegate.createLiteral(lex());
+ } else if (type === Token.Keyword) {
+ if (matchKeyword('this')) {
+ lex();
+ expr = delegate.createThisExpression();
+ }
+ } else if (type === Token.BooleanLiteral) {
+ token = lex();
+ token.value = (token.value === 'true');
+ expr = delegate.createLiteral(token);
+ } else if (type === Token.NullLiteral) {
+ token = lex();
+ token.value = null;
+ expr = delegate.createLiteral(token);
+ } else if (match('[')) {
+ expr = parseArrayInitialiser();
+ } else if (match('{')) {
+ expr = parseObjectInitialiser();
+ }
+
+ if (expr) {
+ return expr;
+ }
+
+ throwUnexpected(lex());
+ }
+
+ // 11.2 Left-Hand-Side Expressions
+
+ function parseArguments() {
+ var args = [];
+
+ expect('(');
+
+ if (!match(')')) {
+ while (index < length) {
+ args.push(parseExpression());
+ if (match(')')) {
+ break;
+ }
+ expect(',');
+ }
+ }
+
+ expect(')');
+
+ return args;
+ }
+
+ function parseNonComputedProperty() {
+ var token;
+
+ token = lex();
+
+ if (!isIdentifierName(token)) {
+ throwUnexpected(token);
+ }
+
+ return delegate.createIdentifier(token.value);
+ }
+
+ function parseNonComputedMember() {
+ expect('.');
+
+ return parseNonComputedProperty();
+ }
+
+ function parseComputedMember() {
+ var expr;
+
+ expect('[');
+
+ expr = parseExpression();
+
+ expect(']');
+
+ return expr;
+ }
+
+ function parseLeftHandSideExpression() {
+ var expr, property;
+
+ expr = parsePrimaryExpression();
+
+ while (match('.') || match('[')) {
+ if (match('[')) {
+ property = parseComputedMember();
+ expr = delegate.createMemberExpression('[', expr, property);
+ } else {
+ property = parseNonComputedMember();
+ expr = delegate.createMemberExpression('.', expr, property);
+ }
+ }
+
+ return expr;
+ }
+
+ // 11.3 Postfix Expressions
+
+ var parsePostfixExpression = parseLeftHandSideExpression;
+
+ // 11.4 Unary Operators
+
+ function parseUnaryExpression() {
+ var token, expr;
+
+ if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyword) {
+ expr = parsePostfixExpression();
+ } else if (match('+') || match('-') || match('!')) {
+ token = lex();
+ expr = parseUnaryExpression();
+ expr = delegate.createUnaryExpression(token.value, expr);
+ } else if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) {
+ throwError({}, Messages.UnexpectedToken);
+ } else {
+ expr = parsePostfixExpression();
+ }
+
+ return expr;
+ }
+
+ function binaryPrecedence(token) {
+ var prec = 0;
+
+ if (token.type !== Token.Punctuator && token.type !== Token.Keyword) {
+ return 0;
+ }
+
+ switch (token.value) {
+ case '||':
+ prec = 1;
+ break;
+
+ case '&&':
+ prec = 2;
+ break;
+
+ case '==':
+ case '!=':
+ case '===':
+ case '!==':
+ prec = 6;
+ break;
+
+ case '<':
+ case '>':
+ case '<=':
+ case '>=':
+ case 'instanceof':
+ prec = 7;
+ break;
+
+ case 'in':
+ prec = 7;
+ break;
+
+ case '+':
+ case '-':
+ prec = 9;
+ break;
+
+ case '*':
+ case '/':
+ case '%':
+ prec = 11;
+ break;
+
+ default:
+ break;
+ }
+
+ return prec;
+ }
+
+ // 11.5 Multiplicative Operators
+ // 11.6 Additive Operators
+ // 11.7 Bitwise Shift Operators
+ // 11.8 Relational Operators
+ // 11.9 Equality Operators
+ // 11.10 Binary Bitwise Operators
+ // 11.11 Binary Logical Operators
+
+ function parseBinaryExpression() {
+ var expr, token, prec, stack, right, operator, left, i;
+
+ left = parseUnaryExpression();
+
+ token = lookahead;
+ prec = binaryPrecedence(token);
+ if (prec === 0) {
+ return left;
+ }
+ token.prec = prec;
+ lex();
+
+ right = parseUnaryExpression();
+
+ stack = [left, token, right];
+
+ while ((prec = binaryPrecedence(lookahead)) > 0) {
+
+ // Reduce: make a binary expression from the three topmost entries.
+ while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) {
+ right = stack.pop();
+ operator = stack.pop().value;
+ left = stack.pop();
+ expr = delegate.createBinaryExpression(operator, left, right);
+ stack.push(expr);
+ }
+
+ // Shift.
+ token = lex();
+ token.prec = prec;
+ stack.push(token);
+ expr = parseUnaryExpression();
+ stack.push(expr);
+ }
+
+ // Final reduce to clean-up the stack.
+ i = stack.length - 1;
+ expr = stack[i];
+ while (i > 1) {
+ expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i - 2], expr);
+ i -= 2;
+ }
+
+ return expr;
+ }
+
+
+ // 11.12 Conditional Operator
+
+ function parseConditionalExpression() {
+ var expr, consequent, alternate;
+
+ expr = parseBinaryExpression();
+
+ if (match('?')) {
+ lex();
+ consequent = parseConditionalExpression();
+ expect(':');
+ alternate = parseConditionalExpression();
+
+ expr = delegate.createConditionalExpression(expr, consequent, alternate);
+ }
+
+ return expr;
+ }
+
+ // Simplification since we do not support AssignmentExpression.
+ var parseExpression = parseConditionalExpression;
+
+ // Polymer Syntax extensions
+
+ // Filter ::
+ // Identifier
+ // Identifier "(" ")"
+ // Identifier "(" FilterArguments ")"
+
+ function parseFilter() {
+ var identifier, args;
+
+ identifier = lex();
+
+ if (identifier.type !== Token.Identifier) {
+ throwUnexpected(identifier);
+ }
+
+ args = match('(') ? parseArguments() : [];
+
+ return delegate.createFilter(identifier.value, args);
+ }
+
+ // Filters ::
+ // "|" Filter
+ // Filters "|" Filter
+
+ function parseFilters() {
+ while (match('|')) {
+ lex();
+ parseFilter();
+ }
+ }
+
+ // TopLevel ::
+ // LabelledExpressions
+ // AsExpression
+ // InExpression
+ // FilterExpression
+
+ // AsExpression ::
+ // FilterExpression as Identifier
+
+ // InExpression ::
+ // Identifier, Identifier in FilterExpression
+ // Identifier in FilterExpression
+
+ // FilterExpression ::
+ // Expression
+ // Expression Filters
+
+ function parseTopLevel() {
+ skipWhitespace();
+ peek();
+
+ var expr = parseExpression();
+ if (expr) {
+ if (lookahead.value === ',' || lookahead.value == 'in' &&
+ expr.type === Syntax.Identifier) {
+ parseInExpression(expr);
+ } else {
+ parseFilters();
+ if (lookahead.value === 'as') {
+ parseAsExpression(expr);
+ } else {
+ delegate.createTopLevel(expr);
+ }
+ }
+ }
+
+ if (lookahead.type !== Token.EOF) {
+ throwUnexpected(lookahead);
+ }
+ }
+
+ function parseAsExpression(expr) {
+ lex(); // as
+ var identifier = lex().value;
+ delegate.createAsExpression(expr, identifier);
+ }
+
+ function parseInExpression(identifier) {
+ var indexName;
+ if (lookahead.value === ',') {
+ lex();
+ if (lookahead.type !== Token.Identifier)
+ throwUnexpected(lookahead);
+ indexName = lex().value;
+ }
+
+ lex(); // in
+ var expr = parseExpression();
+ parseFilters();
+ delegate.createInExpression(identifier.name, indexName, expr);
+ }
+
+ function parse(code, inDelegate) {
+ delegate = inDelegate;
+ source = code;
+ index = 0;
+ length = source.length;
+ lookahead = null;
+ state = {
+ labelSet: {}
+ };
+
+ return parseTopLevel();
+ }
+
+ global.esprima = {
+ parse: parse
+ };
+})(this);
+
+// Copyright 2013 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';
+
+ // 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;
+ };
+
+ function prepareBinding(expressionText, name, node, filterRegistry) {
+ var expression;
+ try {
+ expression = getExpression(expressionText);
+ if (expression.scopeIdent &&
+ (node.nodeType !== Node.ELEMENT_NODE ||
+ node.tagName !== 'TEMPLATE' ||
+ (name !== 'bind' && name !== 'repeat'))) {
+ throw Error('as and in can only be used within <template bind/repeat>');
+ }
+ } catch (ex) {
+ console.error('Invalid expression syntax: ' + expressionText, ex);
+ return;
+ }
+
+ return function(model, node, oneTime) {
+ var binding = expression.getBinding(model, filterRegistry, oneTime);
+ if (expression.scopeIdent && binding) {
+ node.polymerExpressionScopeIdent_ = expression.scopeIdent;
+ if (expression.indexIdent)
+ node.polymerExpressionIndexIdent_ = expression.indexIdent;
+ }
+
+ return binding;
+ }
+ }
+
+ // TODO(rafaelw): Implement simple LRU.
+ var expressionParseCache = Object.create(null);
+
+ function getExpression(expressionText) {
+ var expression = expressionParseCache[expressionText];
+ if (!expression) {
+ var delegate = new ASTDelegate();
+ esprima.parse(expressionText, delegate);
+ expression = new Expression(delegate);
+ expressionParseCache[expressionText] = expression;
+ }
+ return expression;
+ }
+
+ function Literal(value) {
+ this.value = value;
+ this.valueFn_ = undefined;
+ }
+
+ Literal.prototype = {
+ valueFn: function() {
+ if (!this.valueFn_) {
+ var value = this.value;
+ this.valueFn_ = function() {
+ return value;
+ }
+ }
+
+ return this.valueFn_;
+ }
+ }
+
+ function IdentPath(name) {
+ this.name = name;
+ this.path = Path.get(name);
+ }
+
+ IdentPath.prototype = {
+ valueFn: function() {
+ if (!this.valueFn_) {
+ var name = this.name;
+ var path = this.path;
+ this.valueFn_ = function(model, observer) {
+ if (observer)
+ observer.addPath(model, path);
+
+ return path.getValueFrom(model);
+ }
+ }
+
+ return this.valueFn_;
+ },
+
+ setValue: function(model, newValue) {
+ return this.path.setValueFrom(model, newValue);
+ }
+ };
+
+ function MemberExpression(object, property, accessor) {
+ // convert literal computed property access where literal value is a value
+ // path to ident dot-access.
+ if (accessor == '[' &&
+ property instanceof Literal &&
+ Path.get(property.value).valid) {
+ accessor = '.';
+ property = new IdentPath(property.value);
+ }
+
+ this.dynamicDeps = typeof object == 'function' || object.dynamic;
+
+ this.dynamic = typeof property == 'function' ||
+ property.dynamic ||
+ accessor == '[';
+
+ this.simplePath =
+ !this.dynamic &&
+ !this.dynamicDeps &&
+ property instanceof IdentPath &&
+ (object instanceof MemberExpression || object instanceof IdentPath);
+
+ this.object = this.simplePath ? object : getFn(object);
+ this.property = accessor == '.' ? property : getFn(property);
+ }
+
+ MemberExpression.prototype = {
+ get fullPath() {
+ if (!this.fullPath_) {
+ var last = this.object instanceof IdentPath ?
+ this.object.name : this.object.fullPath;
+ this.fullPath_ = Path.get(last + '.' + this.property.name);
+ }
+
+ return this.fullPath_;
+ },
+
+ valueFn: function() {
+ if (!this.valueFn_) {
+ var object = this.object;
+
+ if (this.simplePath) {
+ var path = this.fullPath;
+
+ this.valueFn_ = function(model, observer) {
+ if (observer)
+ observer.addPath(model, path);
+
+ return path.getValueFrom(model);
+ };
+ } else if (this.property instanceof IdentPath) {
+ var path = Path.get(this.property.name);
+
+ this.valueFn_ = function(model, observer) {
+ var context = object(model, observer);
+
+ if (observer)
+ observer.addPath(context, path);
+
+ return path.getValueFrom(context);
+ }
+ } else {
+ // Computed property.
+ var property = this.property;
+
+ this.valueFn_ = function(model, observer) {
+ var context = object(model, observer);
+ var propName = property(model, observer);
+ if (observer)
+ observer.addPath(context, propName);
+
+ return context ? context[propName] : undefined;
+ };
+ }
+ }
+ return this.valueFn_;
+ },
+
+ setValue: function(model, newValue) {
+ if (this.simplePath) {
+ this.fullPath.setValueFrom(model, newValue);
+ return newValue;
+ }
+
+ var object = this.object(model);
+ var propName = this.property instanceof IdentPath ? this.property.name :
+ this.property(model);
+ return object[propName] = newValue;
+ }
+ };
+
+ function Filter(name, args) {
+ this.name = name;
+ this.args = [];
+ for (var i = 0; i < args.length; i++) {
+ this.args[i] = getFn(args[i]);
+ }
+ }
+
+ Filter.prototype = {
+ transform: function(value, toModelDirection, filterRegistry, model,
+ observer) {
+ var fn = filterRegistry[this.name];
+ var context = model;
+ if (fn) {
+ context = undefined;
+ } else {
+ fn = context[this.name];
+ if (!fn) {
+ console.error('Cannot find filter: ' + this.name);
+ return;
+ }
+ }
+
+ // If toModelDirection is falsey, then the "normal" (dom-bound) direction
+ // is used. Otherwise, it looks for a 'toModel' property function on the
+ // object.
+ if (toModelDirection) {
+ fn = fn.toModel;
+ } else if (typeof fn.toDOM == 'function') {
+ fn = fn.toDOM;
+ }
+
+ if (typeof fn != 'function') {
+ console.error('No ' + (toModelDirection ? 'toModel' : 'toDOM') +
+ ' found on' + this.name);
+ return;
+ }
+
+ var args = [value];
+ for (var i = 0; i < this.args.length; i++) {
+ args[i + 1] = getFn(this.args[i])(model, observer);
+ }
+
+ return fn.apply(context, args);
+ }
+ };
+
+ function notImplemented() { throw Error('Not Implemented'); }
+
+ var unaryOperators = {
+ '+': function(v) { return +v; },
+ '-': function(v) { return -v; },
+ '!': function(v) { return !v; }
+ };
+
+ var binaryOperators = {
+ '+': function(l, r) { return l+r; },
+ '-': function(l, r) { return l-r; },
+ '*': function(l, r) { return l*r; },
+ '/': function(l, r) { return l/r; },
+ '%': function(l, r) { return l%r; },
+ '<': function(l, r) { return l<r; },
+ '>': function(l, r) { return l>r; },
+ '<=': function(l, r) { return l<=r; },
+ '>=': function(l, r) { return l>=r; },
+ '==': function(l, r) { return l==r; },
+ '!=': function(l, r) { return l!=r; },
+ '===': function(l, r) { return l===r; },
+ '!==': function(l, r) { return l!==r; },
+ '&&': function(l, r) { return l&&r; },
+ '||': function(l, r) { return l||r; },
+ };
+
+ function getFn(arg) {
+ return typeof arg == 'function' ? arg : arg.valueFn();
+ }
+
+ function ASTDelegate() {
+ this.expression = null;
+ this.filters = [];
+ this.deps = {};
+ this.currentPath = undefined;
+ this.scopeIdent = undefined;
+ this.indexIdent = undefined;
+ this.dynamicDeps = false;
+ }
+
+ ASTDelegate.prototype = {
+ createUnaryExpression: function(op, argument) {
+ if (!unaryOperators[op])
+ throw Error('Disallowed operator: ' + op);
+
+ argument = getFn(argument);
+
+ return function(model, observer) {
+ return unaryOperators[op](argument(model, observer));
+ };
+ },
+
+ createBinaryExpression: function(op, left, right) {
+ if (!binaryOperators[op])
+ throw Error('Disallowed operator: ' + op);
+
+ left = getFn(left);
+ right = getFn(right);
+
+ return function(model, observer) {
+ return binaryOperators[op](left(model, observer),
+ right(model, observer));
+ };
+ },
+
+ createConditionalExpression: function(test, consequent, alternate) {
+ test = getFn(test);
+ consequent = getFn(consequent);
+ alternate = getFn(alternate);
+
+ return function(model, observer) {
+ return test(model, observer) ?
+ consequent(model, observer) : alternate(model, observer);
+ }
+ },
+
+ createIdentifier: function(name) {
+ var ident = new IdentPath(name);
+ ident.type = 'Identifier';
+ return ident;
+ },
+
+ createMemberExpression: function(accessor, object, property) {
+ var ex = new MemberExpression(object, property, accessor);
+ if (ex.dynamicDeps)
+ this.dynamicDeps = true;
+ return ex;
+ },
+
+ createLiteral: function(token) {
+ return new Literal(token.value);
+ },
+
+ createArrayExpression: function(elements) {
+ for (var i = 0; i < elements.length; i++)
+ elements[i] = getFn(elements[i]);
+
+ return function(model, observer) {
+ var arr = []
+ for (var i = 0; i < elements.length; i++)
+ arr.push(elements[i](model, observer));
+ return arr;
+ }
+ },
+
+ createProperty: function(kind, key, value) {
+ return {
+ key: key instanceof IdentPath ? key.name : key.value,
+ value: value
+ };
+ },
+
+ createObjectExpression: function(properties) {
+ for (var i = 0; i < properties.length; i++)
+ properties[i].value = getFn(properties[i].value);
+
+ return function(model, observer) {
+ var obj = {};
+ for (var i = 0; i < properties.length; i++)
+ obj[properties[i].key] = properties[i].value(model, observer);
+ return obj;
+ }
+ },
+
+ createFilter: function(name, args) {
+ this.filters.push(new Filter(name, args));
+ },
+
+ createAsExpression: function(expression, scopeIdent) {
+ this.expression = expression;
+ this.scopeIdent = scopeIdent;
+ },
+
+ createInExpression: function(scopeIdent, indexIdent, expression) {
+ this.expression = expression;
+ this.scopeIdent = scopeIdent;
+ this.indexIdent = indexIdent;
+ },
+
+ createTopLevel: function(expression) {
+ this.expression = expression;
+ },
+
+ createThisExpression: notImplemented
+ }
+
+ function ConstantObservable(value) {
+ this.value_ = value;
+ }
+
+ ConstantObservable.prototype = {
+ open: function() { return this.value_; },
+ discardChanges: function() { return this.value_; },
+ deliver: function() {},
+ close: function() {},
+ }
+
+ function Expression(delegate) {
+ this.scopeIdent = delegate.scopeIdent;
+ this.indexIdent = delegate.indexIdent;
+
+ if (!delegate.expression)
+ throw Error('No expression found.');
+
+ this.expression = delegate.expression;
+ getFn(this.expression); // forces enumeration of path dependencies
+
+ this.filters = delegate.filters;
+ this.dynamicDeps = delegate.dynamicDeps;
+ }
+
+ Expression.prototype = {
+ getBinding: function(model, filterRegistry, oneTime) {
+ if (oneTime)
+ return this.getValue(model, undefined, filterRegistry);
+
+ var observer = new CompoundObserver();
+ this.getValue(model, observer, filterRegistry); // captures deps.
+ var self = this;
+
+ function valueFn() {
+ if (self.dynamicDeps)
+ observer.startReset();
+
+ var value = self.getValue(model,
+ self.dynamicDeps ? observer : undefined,
+ filterRegistry);
+ if (self.dynamicDeps)
+ observer.finishReset();
+
+ return value;
+ }
+
+ function setValueFn(newValue) {
+ self.setValue(model, newValue, filterRegistry);
+ return newValue;
+ }
+
+ return new ObserverTransform(observer, valueFn, setValueFn, true);
+ },
+
+ getValue: function(model, observer, filterRegistry) {
+ var value = getFn(this.expression)(model, observer);
+ for (var i = 0; i < this.filters.length; i++) {
+ value = this.filters[i].transform(value, false, filterRegistry, model,
+ observer);
+ }
+
+ return value;
+ },
+
+ setValue: function(model, newValue, filterRegistry) {
+ var count = this.filters ? this.filters.length : 0;
+ while (count-- > 0) {
+ newValue = this.filters[count].transform(newValue, true, filterRegistry,
+ model);
+ }
+
+ if (this.expression.setValue)
+ return this.expression.setValue(model, newValue);
+ }
+ }
+
+ /**
+ * Converts a style property name to a css property name. For example:
+ * "WebkitUserSelect" to "-webkit-user-select"
+ */
+ function convertStylePropertyName(name) {
+ return String(name).replace(/[A-Z]/g, function(c) {
+ return '-' + c.toLowerCase();
+ });
+ }
+
+ function isEventHandler(name) {
+ return name[0] === 'o' &&
+ name[1] === 'n' &&
+ name[2] === '-';
+ }
+
+ var mixedCaseEventTypes = {};
+ [
+ 'webkitAnimationStart',
+ 'webkitAnimationEnd',
+ 'webkitTransitionEnd',
+ 'DOMFocusOut',
+ 'DOMFocusIn',
+ 'DOMMouseScroll'
+ ].forEach(function(e) {
+ mixedCaseEventTypes[e.toLowerCase()] = e;
+ });
+
+ function prepareEventBinding(path, name) {
+ var eventType = name.substring(3);
+ eventType = mixedCaseEventTypes[eventType] || eventType;
+
+ return function(model, node, oneTime) {
+ var fn = path.getValueFrom(model);
+
+ function handler(e) {
+ if (!oneTime)
+ fn = path.getValueFrom(model);
+
+ fn.apply(model, [e, e.detail, e.currentTarget]);
+
+ if (Platform && typeof Platform.flush == 'function')
+ Platform.flush();
+ }
+
+ node.addEventListener(eventType, handler);
+
+ if (oneTime)
+ return;
+
+ function bindingValue() {
+ return '{{ ' + path + ' }}';
+ }
+
+ return {
+ open: bindingValue,
+ discardChanges: bindingValue,
+ close: function() {
+ node.removeEventListener(eventType, handler);
+ }
+ };
+ }
+ }
+
+ function PolymerExpressions() {}
+
+ PolymerExpressions.prototype = {
+ // "built-in" filters
+ styleObject: function(value) {
+ var parts = [];
+ for (var key in value) {
+ parts.push(convertStylePropertyName(key) + ': ' + value[key]);
+ }
+ return parts.join('; ');
+ },
+
+ tokenList: function(value) {
+ var tokens = [];
+ for (var key in value) {
+ if (value[key])
+ tokens.push(key);
+ }
+ return tokens.join(' ');
+ },
+
+ // binding delegate API
+ prepareInstancePositionChanged: function(template) {
+ var indexIdent = template.polymerExpressionIndexIdent_;
+ if (!indexIdent)
+ return;
+
+ return function(templateInstance, index) {
+ templateInstance.model[indexIdent] = index;
+ };
+ },
+
+ prepareBinding: function(pathString, name, node) {
+ if (isEventHandler(name)) {
+ var path = Path.get(pathString);
+ if (!path.valid) {
+ console.error('on-* bindings must be simple path expressions');
+ return;
+ }
+
+ return prepareEventBinding(path, name);
+ }
+
+ if (Path.get(pathString).valid)
+ return; // bail out early if pathString is simple path.
+
+ return prepareBinding(pathString, name, node, this);
+ },
+
+ prepareInstanceModel: function(template) {
+ var scopeName = template.polymerExpressionScopeIdent_;
+ if (!scopeName)
+ return;
+
+ var parentScope = template.templateInstance ?
+ template.templateInstance.model :
+ template.model;
+
+ return function(model) {
+ var scope = Object.create(parentScope);
+ scope[scopeName] = model;
+ return scope;
+ };
+ }
+ };
+
+ global.PolymerExpressions = PolymerExpressions;
+ if (global.exposeGetExpression)
+ global.getExpression_ = getExpression;
+
+ global.PolymerExpressions.prepareEventBinding = prepareEventBinding;
+})(this);
+
+/*
+ * Copyright 2013 The Polymer Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+(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
+var FLUSH_POLL_INTERVAL = 125;
+window.addEventListener('WebComponentsReady', function() {
+ flush();
+ // flush periodically if platform does not have object observe.
+ if (!Observer.hasObjectObserve) {
+ scope.flushPoll = setInterval(flush, FLUSH_POLL_INTERVAL);
+ }
+});
+
+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);
+
+
+//# sourceMappingURL=platform.concat.js.map

Powered by Google App Engine
This is Rietveld 408576698