| 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 '&';
|
| + case '<':
|
| + return '<';
|
| + case '>':
|
| + return '>';
|
| + case '"':
|
| + return '"'
|
| + case '\u00A0':
|
| + return ' ';
|
| + }
|
| + }
|
| +
|
| + 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 + ' ';
|
| + forEach(inChildNodes, function(n) {
|
| + info += output(n, n.childNodes, ind);
|
| + });
|
| + info += indent;
|
| + }
|
| + if (!({br:1}[name])) {
|
| + info += '<tag></' + name + '></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>' + '<';
|
| + 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 += '>'+ '</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
|
|
|