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 |