| Index: pkg/shadow_dom/lib/shadow_dom.debug.js
|
| diff --git a/pkg/shadow_dom/lib/shadow_dom.debug.js b/pkg/shadow_dom/lib/shadow_dom.debug.js
|
| index 07b283e64de2933b022ee187a427d77c69176556..b889a5ef7641e88b18def663b474fd422bcafc32 100644
|
| --- a/pkg/shadow_dom/lib/shadow_dom.debug.js
|
| +++ b/pkg/shadow_dom/lib/shadow_dom.debug.js
|
| @@ -16,28 +16,1350 @@ if ((!HTMLElement.prototype.createShadowRoot &&
|
| }
|
| })();
|
|
|
| +// 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';
|
| +
|
| + function detectObjectObserve() {
|
| + if (typeof Object.observe !== 'function' ||
|
| + typeof Array.observe !== 'function') {
|
| + return false;
|
| + }
|
| +
|
| + var gotSplice = false;
|
| + function callback(records) {
|
| + if (records[0].type === 'splice' && records[1].type === 'splice')
|
| + gotSplice = true;
|
| + }
|
| +
|
| + var test = [0];
|
| + Array.observe(test, callback);
|
| + test[1] = 1;
|
| + test.length = 0;
|
| + Object.deliverChangeRecords(callback);
|
| + return gotSplice;
|
| + }
|
| +
|
| + 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 && !hasObserve && 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, observedSet) {
|
| + for (var i = 0; i < this.length; i++) {
|
| + if (obj == null)
|
| + return;
|
| + if (observedSet)
|
| + observedSet.observe(obj);
|
| + obj = obj[this[i]];
|
| + }
|
| + return 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()) {
|
| + observer.report();
|
| + cycles++;
|
| + }
|
| + if (global.testingExposeCycleCount)
|
| + global.dirtyCheckCycleCount = cycles;
|
| + }
|
| +
|
| + 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 = {};
|
| + var oldObjectHas = {};
|
| +
|
| + 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
|
| + };
|
| + }
|
| +
|
| + function copyObject(object, opt_copy) {
|
| + var copy = opt_copy || (Array.isArray(object) ? [] : {});
|
| + for (var prop in object) {
|
| + copy[prop] = object[prop];
|
| + };
|
| + if (Array.isArray(object))
|
| + copy.length = object.length;
|
| + return copy;
|
| + }
|
| +
|
| + function Observer(object, callback, target, token) {
|
| + this.closed = false;
|
| + this.object = object;
|
| + this.callback = callback;
|
| + // TODO(rafaelw): Hold this.target weakly when WeakRef is available.
|
| + this.target = target;
|
| + this.token = token;
|
| + this.reporting = true;
|
| + if (hasObserve) {
|
| + var self = this;
|
| + this.boundInternalCallback = function(records) {
|
| + self.internalCallback(records);
|
| + };
|
| + }
|
| +
|
| + addToAll(this);
|
| + }
|
| +
|
| + Observer.prototype = {
|
| + internalCallback: function(records) {
|
| + if (this.closed)
|
| + return;
|
| + if (this.reporting && this.check(records)) {
|
| + this.report();
|
| + if (this.testingResults)
|
| + this.testingResults.anyChanged = true;
|
| + }
|
| + },
|
| +
|
| + close: function() {
|
| + if (this.closed)
|
| + return;
|
| + if (this.object && typeof this.object.close === 'function')
|
| + this.object.close();
|
| +
|
| + this.disconnect();
|
| + this.object = undefined;
|
| + this.closed = true;
|
| + },
|
| +
|
| + deliver: function(testingResults) {
|
| + if (this.closed)
|
| + return;
|
| + if (hasObserve) {
|
| + this.testingResults = testingResults;
|
| + Object.deliverChangeRecords(this.boundInternalCallback);
|
| + this.testingResults = undefined;
|
| + } else {
|
| + dirtyCheck(this);
|
| + }
|
| + },
|
| +
|
| + report: function() {
|
| + if (!this.reporting)
|
| + return;
|
| +
|
| + this.sync(false);
|
| + if (this.callback) {
|
| + this.reportArgs.push(this.token);
|
| + this.invokeCallback(this.reportArgs);
|
| + }
|
| + this.reportArgs = undefined;
|
| + },
|
| +
|
| + invokeCallback: function(args) {
|
| + try {
|
| + this.callback.apply(this.target, args);
|
| + } catch (ex) {
|
| + Observer._errorThrownDuringCallback = true;
|
| + console.error('Exception caught during observer callback: ' + (ex.stack || ex));
|
| + }
|
| + },
|
| +
|
| + reset: function() {
|
| + if (this.closed)
|
| + return;
|
| +
|
| + if (hasObserve) {
|
| + this.reporting = false;
|
| + Object.deliverChangeRecords(this.boundInternalCallback);
|
| + this.reporting = true;
|
| + }
|
| +
|
| + this.sync(true);
|
| + }
|
| + }
|
| +
|
| + var collectObservers = !hasObserve || global.forceCollectObservers;
|
| + var allObservers;
|
| + Observer._allObserversCount = 0;
|
| +
|
| + if (collectObservers) {
|
| + allObservers = [];
|
| + }
|
| +
|
| + function addToAll(observer) {
|
| + if (!collectObservers)
|
| + return;
|
| +
|
| + allObservers.push(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 results = {};
|
| +
|
| + do {
|
| + cycles++;
|
| + var toCheck = allObservers;
|
| + allObservers = [];
|
| + results.anyChanged = false;
|
| +
|
| + for (var i = 0; i < toCheck.length; i++) {
|
| + var observer = toCheck[i];
|
| + if (observer.closed)
|
| + continue;
|
| +
|
| + if (hasObserve) {
|
| + observer.deliver(results);
|
| + } else if (observer.check()) {
|
| + results.anyChanged = true;
|
| + observer.report();
|
| + }
|
| +
|
| + allObservers.push(observer);
|
| + }
|
| + } while (cycles < MAX_DIRTY_CHECK_CYCLES && results.anyChanged);
|
| +
|
| + if (global.testingExposeCycleCount)
|
| + global.dirtyCheckCycleCount = cycles;
|
| +
|
| + Observer._allObserversCount = allObservers.length;
|
| + runningMicrotaskCheckpoint = false;
|
| + };
|
| +
|
| + if (collectObservers) {
|
| + global.Platform.clearObservers = function() {
|
| + allObservers = [];
|
| + };
|
| + }
|
| +
|
| + function ObjectObserver(object, callback, target, token) {
|
| + Observer.call(this, object, callback, target, token);
|
| + this.connect();
|
| + this.sync(true);
|
| + }
|
| +
|
| + ObjectObserver.prototype = createObject({
|
| + __proto__: Observer.prototype,
|
| +
|
| + connect: function() {
|
| + if (hasObserve)
|
| + Object.observe(this.object, this.boundInternalCallback);
|
| + },
|
| +
|
| + sync: function(hard) {
|
| + if (!hasObserve)
|
| + this.oldObject = copyObject(this.object);
|
| + },
|
| +
|
| + check: function(changeRecords) {
|
| + var diff;
|
| + var oldValues;
|
| + if (hasObserve) {
|
| + if (!changeRecords)
|
| + return false;
|
| +
|
| + oldValues = {};
|
| + diff = diffObjectFromChangeRecords(this.object, changeRecords,
|
| + oldValues);
|
| + } else {
|
| + oldValues = this.oldObject;
|
| + diff = diffObjectFromOldObject(this.object, this.oldObject);
|
| + }
|
| +
|
| + if (diffIsEmpty(diff))
|
| + return false;
|
| +
|
| + this.reportArgs =
|
| + [diff.added || {}, diff.removed || {}, diff.changed || {}];
|
| + this.reportArgs.push(function(property) {
|
| + return oldValues[property];
|
| + });
|
| +
|
| + return true;
|
| + },
|
| +
|
| + disconnect: function() {
|
| + if (!hasObserve)
|
| + this.oldObject = undefined;
|
| + else if (this.object)
|
| + Object.unobserve(this.object, this.boundInternalCallback);
|
| + }
|
| + });
|
| +
|
| + function ArrayObserver(array, callback, target, token) {
|
| + if (!Array.isArray(array))
|
| + throw Error('Provided object is not an Array');
|
| + ObjectObserver.call(this, array, callback, target, token);
|
| + }
|
| +
|
| + ArrayObserver.prototype = createObject({
|
| + __proto__: ObjectObserver.prototype,
|
| +
|
| + connect: function() {
|
| + if (hasObserve)
|
| + Array.observe(this.object, this.boundInternalCallback);
|
| + },
|
| +
|
| + sync: function() {
|
| + if (!hasObserve)
|
| + this.oldObject = this.object.slice();
|
| + },
|
| +
|
| + check: function(changeRecords) {
|
| + var splices;
|
| + if (hasObserve) {
|
| + if (!changeRecords)
|
| + return false;
|
| + splices = projectArraySplices(this.object, changeRecords);
|
| + } else {
|
| + splices = calcSplices(this.object, 0, this.object.length,
|
| + this.oldObject, 0, this.oldObject.length);
|
| + }
|
| +
|
| + if (!splices || !splices.length)
|
| + return false;
|
| +
|
| + this.reportArgs = [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 ObservedSet(callback) {
|
| + this.arr = [];
|
| + this.callback = callback;
|
| + this.isObserved = true;
|
| + }
|
| +
|
| + var objProto = Object.getPrototypeOf({});
|
| + var arrayProto = Object.getPrototypeOf([]);
|
| + ObservedSet.prototype = {
|
| + reset: function() {
|
| + this.isObserved = !this.isObserved;
|
| + },
|
| +
|
| + observe: function(obj) {
|
| + if (!isObject(obj) || obj === objProto || obj === arrayProto)
|
| + return;
|
| + var i = this.arr.indexOf(obj);
|
| + if (i >= 0 && this.arr[i+1] === this.isObserved)
|
| + return;
|
| +
|
| + if (i < 0) {
|
| + i = this.arr.length;
|
| + this.arr[i] = obj;
|
| + Object.observe(obj, this.callback);
|
| + }
|
| +
|
| + this.arr[i+1] = this.isObserved;
|
| + this.observe(Object.getPrototypeOf(obj));
|
| + },
|
| +
|
| + cleanup: function() {
|
| + var i = 0, j = 0;
|
| + var isObserved = this.isObserved;
|
| + while(j < this.arr.length) {
|
| + var obj = this.arr[j];
|
| + if (this.arr[j + 1] == isObserved) {
|
| + if (i < j) {
|
| + this.arr[i] = obj;
|
| + this.arr[i + 1] = isObserved;
|
| + }
|
| + i += 2;
|
| + } else {
|
| + Object.unobserve(obj, this.callback);
|
| + }
|
| + j += 2;
|
| + }
|
| +
|
| + this.arr.length = i;
|
| + }
|
| + };
|
| +
|
| + function PathObserver(object, path, callback, target, token, valueFn,
|
| + setValueFn) {
|
| + var path = path instanceof Path ? path : getPath(path);
|
| + if (!path || !path.length || !isObject(object)) {
|
| + this.value_ = path ? path.getValueFrom(object) : undefined;
|
| + this.value = valueFn ? valueFn(this.value_) : this.value_;
|
| + this.closed = true;
|
| + return;
|
| + }
|
| +
|
| + Observer.call(this, object, callback, target, token);
|
| + this.valueFn = valueFn;
|
| + this.setValueFn = setValueFn;
|
| + this.path = path;
|
| +
|
| + this.connect();
|
| + this.sync(true);
|
| + }
|
| +
|
| + PathObserver.prototype = createObject({
|
| + __proto__: Observer.prototype,
|
| +
|
| + connect: function() {
|
| + if (hasObserve)
|
| + this.observedSet = new ObservedSet(this.boundInternalCallback);
|
| + },
|
| +
|
| + disconnect: function() {
|
| + this.value = undefined;
|
| + this.value_ = undefined;
|
| + if (this.observedSet) {
|
| + this.observedSet.reset();
|
| + this.observedSet.cleanup();
|
| + this.observedSet = undefined;
|
| + }
|
| + },
|
| +
|
| + check: function() {
|
| + // Note: Extracting this to a member function for use here and below
|
| + // regresses dirty-checking path perf by about 25% =-(.
|
| + if (this.observedSet)
|
| + this.observedSet.reset();
|
| +
|
| + this.value_ = this.path.getValueFrom(this.object, this.observedSet);
|
| +
|
| + if (this.observedSet)
|
| + this.observedSet.cleanup();
|
| +
|
| + if (areSameValue(this.value_, this.oldValue_))
|
| + return false;
|
| +
|
| + this.value = this.valueFn ? this.valueFn(this.value_) : this.value_;
|
| + this.reportArgs = [this.value, this.oldValue];
|
| + return true;
|
| + },
|
| +
|
| + sync: function(hard) {
|
| + if (hard) {
|
| + if (this.observedSet)
|
| + this.observedSet.reset();
|
| +
|
| + this.value_ = this.path.getValueFrom(this.object, this.observedSet);
|
| + this.value = this.valueFn ? this.valueFn(this.value_) : this.value_;
|
| +
|
| + if (this.observedSet)
|
| + this.observedSet.cleanup();
|
| + }
|
| +
|
| + this.oldValue_ = this.value_;
|
| + this.oldValue = this.value;
|
| + },
|
| +
|
| + setValue: function(newValue) {
|
| + if (!this.path)
|
| + return;
|
| + if (typeof this.setValueFn === 'function')
|
| + newValue = this.setValueFn(newValue);
|
| + this.path.setValueFrom(this.object, newValue);
|
| + }
|
| + });
|
| +
|
| + function CompoundPathObserver(callback, target, token, valueFn) {
|
| + Observer.call(this, undefined, callback, target, token);
|
| + this.valueFn = valueFn;
|
| +
|
| + this.observed = [];
|
| + this.values = [];
|
| + this.started = false;
|
| + }
|
| +
|
| + CompoundPathObserver.prototype = createObject({
|
| + __proto__: PathObserver.prototype,
|
| +
|
| + addPath: function(object, path) {
|
| + if (this.started)
|
| + throw Error('Cannot add more paths once started.');
|
| +
|
| + var path = path instanceof Path ? path : getPath(path);
|
| + var value = path ? path.getValueFrom(object) : undefined;
|
| +
|
| + this.observed.push(object, path);
|
| + this.values.push(value);
|
| + },
|
| +
|
| + start: function() {
|
| + this.connect();
|
| + this.sync(true);
|
| + },
|
| +
|
| + getValues: function() {
|
| + if (this.observedSet)
|
| + this.observedSet.reset();
|
| +
|
| + var anyChanged = false;
|
| + for (var i = 0; i < this.observed.length; i = i+2) {
|
| + var path = this.observed[i+1];
|
| + if (!path)
|
| + continue;
|
| + var object = this.observed[i];
|
| + var value = path.getValueFrom(object, this.observedSet);
|
| + var oldValue = this.values[i/2];
|
| + if (!areSameValue(value, oldValue)) {
|
| + this.values[i/2] = value;
|
| + anyChanged = true;
|
| + }
|
| + }
|
| +
|
| + if (this.observedSet)
|
| + this.observedSet.cleanup();
|
| +
|
| + return anyChanged;
|
| + },
|
| +
|
| + check: function() {
|
| + if (!this.getValues())
|
| + return;
|
| +
|
| + this.value = this.valueFn(this.values);
|
| +
|
| + if (areSameValue(this.value, this.oldValue))
|
| + return false;
|
| +
|
| + this.reportArgs = [this.value, this.oldValue];
|
| + return true;
|
| + },
|
| +
|
| + sync: function(hard) {
|
| + if (hard) {
|
| + this.getValues();
|
| + this.value = this.valueFn(this.values);
|
| + }
|
| +
|
| + this.oldValue = this.value;
|
| + },
|
| +
|
| + close: function() {
|
| + if (this.observed) {
|
| + for (var i = 0; i < this.observed.length; i = i + 2) {
|
| + var object = this.observed[i];
|
| + if (object && typeof object.close === 'function')
|
| + object.close();
|
| + }
|
| + this.observed = undefined;
|
| + this.values = undefined;
|
| + }
|
| +
|
| + Observer.prototype.close.call(this);
|
| + }
|
| + });
|
| +
|
| + var knownRecordTypes = {
|
| + 'new': true,
|
| + 'updated': true,
|
| + 'deleted': 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);
|
| + }
|
| + }
|
| +
|
| + // TODO(rafaelw): It should be possible for the Object.observe case to have
|
| + // every PathObserver used by defineProperty share a single Object.observe
|
| + // callback, and thus get() can simply call observer.deliver() and any changes
|
| + // to any dependent value will be observed.
|
| + PathObserver.defineProperty = function(object, name, descriptor) {
|
| + // TODO(rafaelw): Validate errors
|
| + var obj = descriptor.object;
|
| + var path = getPath(descriptor.path);
|
| + var notify = notifyFunction(object, name);
|
| +
|
| + var observer = new PathObserver(obj, descriptor.path,
|
| + function(newValue, oldValue) {
|
| + if (notify)
|
| + notify('updated', oldValue);
|
| + }
|
| + );
|
| +
|
| + Object.defineProperty(object, name, {
|
| + get: function() {
|
| + return path.getValueFrom(obj);
|
| + },
|
| + set: function(newValue) {
|
| + path.setValueFrom(obj, newValue);
|
| + },
|
| + configurable: true
|
| + });
|
| +
|
| + return {
|
| + close: function() {
|
| + var oldValue = path.getValueFrom(obj);
|
| + if (notify)
|
| + observer.deliver();
|
| + observer.close();
|
| + Object.defineProperty(object, name, {
|
| + value: oldValue,
|
| + 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 (!knownRecordTypes[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 == 'updated')
|
| + continue;
|
| +
|
| + if (record.type == 'new') {
|
| + if (record.name in removed)
|
| + delete removed[record.name];
|
| + else
|
| + added[record.name] = true;
|
| +
|
| + continue;
|
| + }
|
| +
|
| + // type = 'deleted'
|
| + if (record.name in added) {
|
| + delete added[record.name];
|
| + delete oldValues[record.name];
|
| + } else {
|
| + removed[record.name] = true;
|
| + }
|
| + }
|
| +
|
| + for (var prop in added)
|
| + added[prop] = object[prop];
|
| +
|
| + for (var prop in removed)
|
| + removed[prop] = undefined;
|
| +
|
| + var changed = {};
|
| + for (var prop in oldValues) {
|
| + if (prop in added || prop in removed)
|
| + continue;
|
| +
|
| + var newValue = object[prop];
|
| + if (oldValues[prop] !== newValue)
|
| + changed[prop] = newValue;
|
| + }
|
| +
|
| + return {
|
| + added: added,
|
| + removed: removed,
|
| + changed: changed
|
| + };
|
| + }
|
| +
|
| + function newSplice(index, removed, addedCount) {
|
| + return {
|
| + index: index,
|
| + removed: removed,
|
| + addedCount: addedCount
|
| + };
|
| + }
|
| +
|
| + var EDIT_LEAVE = 0;
|
| + var EDIT_UPDATE = 1;
|
| + var EDIT_ADD = 2;
|
| + var EDIT_DELETE = 3;
|
| +
|
| + function ArraySplice() {}
|
| +
|
| + ArraySplice.prototype = {
|
| +
|
| + // Note: This function is *based* on the computation of the Levenshtein
|
| + // "edit" distance. The one change is that "updates" are treated as two
|
| + // edits - not one. With Array splices, an update is really a delete
|
| + // followed by an add. By retaining this, we optimize for "keeping" the
|
| + // maximum array items in the original array. For example:
|
| + //
|
| + // 'xxxx123' -> '123yyyy'
|
| + //
|
| + // With 1-edit updates, the shortest path would be just to update all seven
|
| + // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
|
| + // leaves the substring '123' intact.
|
| + calcEditDistances: function(current, currentStart, currentEnd,
|
| + old, oldStart, oldEnd) {
|
| + // "Deletion" columns
|
| + var rowCount = oldEnd - oldStart + 1;
|
| + var columnCount = currentEnd - currentStart + 1;
|
| + var distances = new Array(rowCount);
|
| +
|
| + // "Addition" rows. Initialize null column.
|
| + for (var i = 0; i < rowCount; i++) {
|
| + distances[i] = new Array(columnCount);
|
| + distances[i][0] = i;
|
| + }
|
| +
|
| + // Initialize null row
|
| + for (var j = 0; j < columnCount; j++)
|
| + distances[0][j] = j;
|
| +
|
| + for (var i = 1; i < rowCount; i++) {
|
| + for (var j = 1; j < columnCount; j++) {
|
| + if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1]))
|
| + distances[i][j] = distances[i - 1][j - 1];
|
| + else {
|
| + var north = distances[i - 1][j] + 1;
|
| + var west = distances[i][j - 1] + 1;
|
| + distances[i][j] = north < west ? north : west;
|
| + }
|
| + }
|
| + }
|
| +
|
| + return distances;
|
| + },
|
| +
|
| + // This starts at the final weight, and walks "backward" by finding
|
| + // the minimum previous weight recursively until the origin of the weight
|
| + // matrix.
|
| + spliceOperationsFromEditDistances: function(distances) {
|
| + var i = distances.length - 1;
|
| + var j = distances[0].length - 1;
|
| + var current = distances[i][j];
|
| + var edits = [];
|
| + while (i > 0 || j > 0) {
|
| + if (i == 0) {
|
| + edits.push(EDIT_ADD);
|
| + j--;
|
| + continue;
|
| + }
|
| + if (j == 0) {
|
| + edits.push(EDIT_DELETE);
|
| + i--;
|
| + continue;
|
| + }
|
| + var northWest = distances[i - 1][j - 1];
|
| + var west = distances[i - 1][j];
|
| + var north = distances[i][j - 1];
|
| +
|
| + var min;
|
| + if (west < north)
|
| + min = west < northWest ? west : northWest;
|
| + else
|
| + min = north < northWest ? north : northWest;
|
| +
|
| + if (min == northWest) {
|
| + if (northWest == current) {
|
| + edits.push(EDIT_LEAVE);
|
| + } else {
|
| + edits.push(EDIT_UPDATE);
|
| + current = northWest;
|
| + }
|
| + i--;
|
| + j--;
|
| + } else if (min == west) {
|
| + edits.push(EDIT_DELETE);
|
| + i--;
|
| + current = west;
|
| + } else {
|
| + edits.push(EDIT_ADD);
|
| + j--;
|
| + current = north;
|
| + }
|
| + }
|
| +
|
| + edits.reverse();
|
| + return edits;
|
| + },
|
| +
|
| + /**
|
| + * Splice Projection functions:
|
| + *
|
| + * A splice map is a representation of how a previous array of items
|
| + * was transformed into a new array of items. Conceptually it is a list of
|
| + * tuples of
|
| + *
|
| + * <index, removed, addedCount>
|
| + *
|
| + * which are kept in ascending index order of. The tuple represents that at
|
| + * the |index|, |removed| sequence of items were removed, and counting forward
|
| + * from |index|, |addedCount| items were added.
|
| + */
|
| +
|
| + /**
|
| + * Lacking individual splice mutation information, the minimal set of
|
| + * splices can be synthesized given the previous state and final state of an
|
| + * array. The basic approach is to calculate the edit distance matrix and
|
| + * choose the shortest path through it.
|
| + *
|
| + * Complexity: O(l * p)
|
| + * l: The length of the current array
|
| + * p: The length of the old array
|
| + */
|
| + calcSplices: function(current, currentStart, currentEnd,
|
| + old, oldStart, oldEnd) {
|
| + var prefixCount = 0;
|
| + var suffixCount = 0;
|
| +
|
| + var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart);
|
| + if (currentStart == 0 && oldStart == 0)
|
| + prefixCount = this.sharedPrefix(current, old, minLength);
|
| +
|
| + if (currentEnd == current.length && oldEnd == old.length)
|
| + suffixCount = this.sharedSuffix(current, old, minLength - prefixCount);
|
| +
|
| + currentStart += prefixCount;
|
| + oldStart += prefixCount;
|
| + currentEnd -= suffixCount;
|
| + oldEnd -= suffixCount;
|
| +
|
| + if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0)
|
| + return [];
|
| +
|
| + if (currentStart == currentEnd) {
|
| + var splice = newSplice(currentStart, [], 0);
|
| + while (oldStart < oldEnd)
|
| + splice.removed.push(old[oldStart++]);
|
| +
|
| + return [ splice ];
|
| + } else if (oldStart == oldEnd)
|
| + return [ newSplice(currentStart, [], currentEnd - currentStart) ];
|
| +
|
| + var ops = this.spliceOperationsFromEditDistances(
|
| + this.calcEditDistances(current, currentStart, currentEnd,
|
| + old, oldStart, oldEnd));
|
| +
|
| + var splice = undefined;
|
| + var splices = [];
|
| + var index = currentStart;
|
| + var oldIndex = oldStart;
|
| + for (var i = 0; i < ops.length; i++) {
|
| + switch(ops[i]) {
|
| + case EDIT_LEAVE:
|
| + if (splice) {
|
| + splices.push(splice);
|
| + splice = undefined;
|
| + }
|
| +
|
| + index++;
|
| + oldIndex++;
|
| + break;
|
| + case EDIT_UPDATE:
|
| + if (!splice)
|
| + splice = newSplice(index, [], 0);
|
| +
|
| + splice.addedCount++;
|
| + index++;
|
| +
|
| + splice.removed.push(old[oldIndex]);
|
| + oldIndex++;
|
| + break;
|
| + case EDIT_ADD:
|
| + if (!splice)
|
| + splice = newSplice(index, [], 0);
|
| +
|
| + splice.addedCount++;
|
| + index++;
|
| + break;
|
| + case EDIT_DELETE:
|
| + if (!splice)
|
| + splice = newSplice(index, [], 0);
|
| +
|
| + splice.removed.push(old[oldIndex]);
|
| + oldIndex++;
|
| + break;
|
| + }
|
| + }
|
| +
|
| + if (splice) {
|
| + splices.push(splice);
|
| + }
|
| + return splices;
|
| + },
|
| +
|
| + sharedPrefix: function(current, old, searchLength) {
|
| + for (var i = 0; i < searchLength; i++)
|
| + if (!this.equals(current[i], old[i]))
|
| + return i;
|
| + return searchLength;
|
| + },
|
| +
|
| + sharedSuffix: function(current, old, searchLength) {
|
| + var index1 = current.length;
|
| + var index2 = old.length;
|
| + var count = 0;
|
| + while (count < searchLength && this.equals(current[--index1], old[--index2]))
|
| + count++;
|
| +
|
| + return count;
|
| + },
|
| +
|
| + calculateSplices: function(current, previous) {
|
| + return this.calcSplices(current, 0, current.length, previous, 0,
|
| + previous.length);
|
| + },
|
| +
|
| + equals: function(currentValue, previousValue) {
|
| + return currentValue === previousValue;
|
| + }
|
| + };
|
| +
|
| + var arraySplice = new ArraySplice();
|
| +
|
| + function calcSplices(current, currentStart, currentEnd,
|
| + old, oldStart, oldEnd) {
|
| + return arraySplice.calcSplices(current, currentStart, currentEnd,
|
| + old, oldStart, oldEnd);
|
| + }
|
| +
|
| + function intersect(start1, end1, start2, end2) {
|
| + // Disjoint
|
| + if (end1 < start2 || end2 < start1)
|
| + return -1;
|
| +
|
| + // Adjacent
|
| + if (end1 == start2 || end2 == start1)
|
| + return 0;
|
| +
|
| + // Non-zero intersect, span1 first
|
| + if (start1 < start2) {
|
| + if (end1 < end2)
|
| + return end1 - start2; // Overlap
|
| + else
|
| + return end2 - start2; // Contained
|
| + } else {
|
| + // Non-zero intersect, span2 first
|
| + if (end2 < end1)
|
| + return end2 - start1; // Overlap
|
| + else
|
| + return end1 - start1; // Contained
|
| + }
|
| + }
|
| +
|
| + function mergeSplice(splices, index, removed, addedCount) {
|
| +
|
| + var splice = newSplice(index, removed, addedCount);
|
| +
|
| + var inserted = false;
|
| + var insertionOffset = 0;
|
| +
|
| + for (var i = 0; i < splices.length; i++) {
|
| + var current = splices[i];
|
| + current.index += insertionOffset;
|
| +
|
| + if (inserted)
|
| + continue;
|
| +
|
| + var intersectCount = intersect(splice.index,
|
| + splice.index + splice.removed.length,
|
| + current.index,
|
| + current.index + current.addedCount);
|
| +
|
| + if (intersectCount >= 0) {
|
| + // Merge the two splices
|
| +
|
| + splices.splice(i, 1);
|
| + i--;
|
| +
|
| + insertionOffset -= current.addedCount - current.removed.length;
|
| +
|
| + splice.addedCount += current.addedCount - intersectCount;
|
| + var deleteCount = splice.removed.length +
|
| + current.removed.length - intersectCount;
|
| +
|
| + if (!splice.addedCount && !deleteCount) {
|
| + // merged splice is a noop. discard.
|
| + inserted = true;
|
| + } else {
|
| + var removed = current.removed;
|
| +
|
| + if (splice.index < current.index) {
|
| + // some prefix of splice.removed is prepended to current.removed.
|
| + var prepend = splice.removed.slice(0, current.index - splice.index);
|
| + Array.prototype.push.apply(prepend, removed);
|
| + removed = prepend;
|
| + }
|
| +
|
| + if (splice.index + splice.removed.length > current.index + current.addedCount) {
|
| + // some suffix of splice.removed is appended to current.removed.
|
| + var append = splice.removed.slice(current.index + current.addedCount - splice.index);
|
| + Array.prototype.push.apply(removed, append);
|
| + }
|
| +
|
| + splice.removed = removed;
|
| + if (current.index < splice.index) {
|
| + splice.index = current.index;
|
| + }
|
| + }
|
| + } else if (splice.index < current.index) {
|
| + // Insert splice here.
|
| +
|
| + inserted = true;
|
| +
|
| + splices.splice(i, 0, splice);
|
| + i++;
|
| +
|
| + var offset = splice.addedCount - splice.removed.length
|
| + current.index += offset;
|
| + insertionOffset += offset;
|
| + }
|
| + }
|
| +
|
| + if (!inserted)
|
| + splices.push(splice);
|
| + }
|
| +
|
| + function createInitialSplices(array, changeRecords) {
|
| + var splices = [];
|
| +
|
| + for (var i = 0; i < changeRecords.length; i++) {
|
| + var record = changeRecords[i];
|
| + switch(record.type) {
|
| + case 'splice':
|
| + mergeSplice(splices, record.index, record.removed.slice(), record.addedCount);
|
| + break;
|
| + case 'new':
|
| + case 'updated':
|
| + case 'deleted':
|
| + 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.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.CompoundPathObserver = CompoundPathObserver;
|
| + global.Path = Path;
|
| +})(typeof global !== 'undefined' && global ? global : this);
|
| +
|
| /*
|
| * Copyright 2012 The Polymer Authors. All rights reserved.
|
| - * Use of this source code is goverened by a BSD-style
|
| + * Use of this source code is governed by a BSD-style
|
| * license that can be found in the LICENSE file.
|
| */
|
|
|
| -// SideTable is a weak map where possible. If WeakMap is not available the
|
| -// association is stored as an expando property.
|
| -var SideTable;
|
| +// If WeakMap is not available, the association is stored as an expando property on the "key".
|
| // TODO(arv): WeakMap does not allow for Node etc to be keys in Firefox
|
| -if (typeof WeakMap !== 'undefined' && navigator.userAgent.indexOf('Firefox/') < 0) {
|
| - SideTable = WeakMap;
|
| -} else {
|
| +if (typeof WeakMap === 'undefined' || navigator.userAgent.indexOf('Firefox/') > -1) {
|
| (function() {
|
| var defineProperty = Object.defineProperty;
|
| var counter = Date.now() % 1e9;
|
|
|
| - SideTable = function() {
|
| + var WeakMap = function() {
|
| this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__');
|
| };
|
|
|
| - SideTable.prototype = {
|
| + WeakMap.prototype = {
|
| set: function(key, value) {
|
| var entry = key[this.name];
|
| if (entry && entry[0] === key)
|
| @@ -53,7 +1375,9 @@ if (typeof WeakMap !== 'undefined' && navigator.userAgent.indexOf('Firefox/') <
|
| delete: function(key) {
|
| this.set(key, undefined);
|
| }
|
| - }
|
| + };
|
| +
|
| + window.WeakMap = WeakMap;
|
| })();
|
| }
|
|
|
| @@ -66,11 +1390,23 @@ var ShadowDOMPolyfill = {};
|
| (function(scope) {
|
| 'use strict';
|
|
|
| - var wrapperTable = new SideTable();
|
| - var constructorTable = new SideTable();
|
| - var nativePrototypeTable = new SideTable();
|
| + 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) {
|
| + }
|
| + }
|
| +
|
| function assert(b) {
|
| if (!b)
|
| throw new Error('Assertion failed');
|
| @@ -150,6 +1486,25 @@ var ShadowDOMPolyfill = {};
|
| return /^on[a-z]+$/.test(name);
|
| }
|
|
|
| + function getGetter(name) {
|
| + return hasEval ?
|
| + new Function('return this.impl.' + name) :
|
| + function() { return this.impl[name]; };
|
| + }
|
| +
|
| + function getSetter(name) {
|
| + return hasEval ?
|
| + new Function('v', 'this.impl.' + name + ' = v') :
|
| + function(v) { this.impl[name] = v; };
|
| + }
|
| +
|
| + function getMethod(name) {
|
| + return hasEval ?
|
| + new Function('return this.impl.' + name +
|
| + '.apply(this.impl, arguments)') :
|
| + function() { return this.impl[name].apply(this.impl, arguments); };
|
| + }
|
| +
|
| function installProperty(source, target, allowMethod) {
|
| Object.getOwnPropertyNames(source).forEach(function(name) {
|
| if (name in target)
|
| @@ -170,29 +1525,21 @@ var ShadowDOMPolyfill = {};
|
| }
|
| var getter, setter;
|
| if (allowMethod && typeof descriptor.value === 'function') {
|
| - target[name] = function() {
|
| - return this.impl[name].apply(this.impl, arguments);
|
| - };
|
| + target[name] = getMethod(name);
|
| return;
|
| }
|
|
|
| var isEvent = isEventHandlerName(name);
|
| - if (isEvent) {
|
| + if (isEvent)
|
| getter = scope.getEventHandlerGetter(name);
|
| - } else {
|
| - getter = function() {
|
| - return this.impl[name];
|
| - };
|
| - }
|
| + else
|
| + getter = getGetter(name);
|
|
|
| if (descriptor.writable || descriptor.set) {
|
| - if (isEvent) {
|
| + if (isEvent)
|
| setter = scope.getEventHandlerSetter(name);
|
| - } else {
|
| - setter = function(value) {
|
| - this.impl[name] = value;
|
| - };
|
| - }
|
| + else
|
| + setter = getSetter(name);
|
| }
|
|
|
| Object.defineProperty(target, name, {
|
| @@ -296,10 +1643,8 @@ var ShadowDOMPolyfill = {};
|
| return null;
|
|
|
| assert(isNative(impl));
|
| - var wrapper = wrapperTable.get(impl);
|
| - if (!wrapper)
|
| - wrapperTable.set(impl, wrapper = new (getWrapperConstructor(impl))(impl));
|
| - return wrapper;
|
| + return impl.polymerWrapper_ ||
|
| + (impl.polymerWrapper_ = new (getWrapperConstructor(impl))(impl));
|
| }
|
|
|
| /**
|
| @@ -343,7 +1688,7 @@ var ShadowDOMPolyfill = {};
|
| return;
|
| assert(isNative(node));
|
| assert(wrapper === undefined || isWrapper(wrapper));
|
| - wrapperTable.set(node, wrapper);
|
| + node.polymerWrapper_ = wrapper;
|
| }
|
|
|
| function defineGetter(constructor, name, getter) {
|
| @@ -411,17 +1756,17 @@ var ShadowDOMPolyfill = {};
|
| var wrap = scope.wrap;
|
| var wrappers = scope.wrappers;
|
|
|
| - var wrappedFuns = new SideTable();
|
| - var listenersTable = new SideTable();
|
| - var handledEventsTable = new SideTable();
|
| - var targetTable = new SideTable();
|
| - var currentTargetTable = new SideTable();
|
| - var relatedTargetTable = new SideTable();
|
| - var eventPhaseTable = new SideTable();
|
| - var stopPropagationTable = new SideTable();
|
| - var stopImmediatePropagationTable = new SideTable();
|
| - var eventHandlersTable = new SideTable();
|
| - var eventPathTable = new SideTable();
|
| + var wrappedFuns = new WeakMap();
|
| + var listenersTable = new WeakMap();
|
| + var handledEventsTable = 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;
|
| @@ -578,7 +1923,6 @@ var ShadowDOMPolyfill = {};
|
| return enclosedBy(rootOfNode(host), b);
|
| }
|
| return false;
|
| -
|
| }
|
|
|
| function isMutationEvent(type) {
|
| @@ -1042,7 +2386,6 @@ var ShadowDOMPolyfill = {};
|
| }
|
| },
|
| dispatchEvent: function(event) {
|
| - scope.renderAllPending();
|
| var target = getTargetToListenAt(this);
|
| return target.dispatchEvent_(unwrap(event));
|
| }
|
| @@ -1211,7 +2554,7 @@ var ShadowDOMPolyfill = {};
|
| * This updates the internal pointers for node, previousNode and nextNode.
|
| */
|
| function collectNodes(node, parentNode, previousNode, nextNode) {
|
| - if (node.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) {
|
| + if (!(node instanceof DocumentFragment)) {
|
| if (node.parentNode)
|
| node.parentNode.removeChild(node);
|
| node.parentNode_ = parentNode;
|
| @@ -1245,6 +2588,24 @@ var ShadowDOMPolyfill = {};
|
| return nodes;
|
| }
|
|
|
| + function collectNodesNoNeedToUpdatePointers(node) {
|
| + if (node instanceof DocumentFragment) {
|
| + var nodes = [];
|
| + var i = 0;
|
| + for (var child = node.firstChild; child; child = child.nextSibling) {
|
| + nodes[i++] = child;
|
| + }
|
| + return nodes;
|
| + }
|
| + return [node];
|
| + }
|
| +
|
| + function nodesWereAdded(nodes) {
|
| + for (var i = 0; i < nodes.length; i++) {
|
| + nodes[i].nodeWasAdded_();
|
| + }
|
| + }
|
| +
|
| function ensureSameOwnerDocument(parent, child) {
|
| var ownerDoc = parent.nodeType === Node.DOCUMENT_NODE ?
|
| parent : parent.ownerDocument;
|
| @@ -1373,10 +2734,12 @@ var ShadowDOMPolyfill = {};
|
| appendChild: function(childWrapper) {
|
| assertIsNodeWrapper(childWrapper);
|
|
|
| + var nodes;
|
| +
|
| if (this.invalidateShadowRenderer() || invalidateParent(childWrapper)) {
|
| var previousNode = this.lastChild;
|
| var nextNode = null;
|
| - var nodes = collectNodes(childWrapper, this, previousNode, nextNode);
|
| + nodes = collectNodes(childWrapper, this, previousNode, nextNode);
|
|
|
| this.lastChild_ = nodes[nodes.length - 1];
|
| if (!previousNode)
|
| @@ -1384,11 +2747,12 @@ var ShadowDOMPolyfill = {};
|
|
|
| originalAppendChild.call(this.impl, unwrapNodesForInsertion(this, nodes));
|
| } else {
|
| + nodes = collectNodesNoNeedToUpdatePointers(childWrapper)
|
| ensureSameOwnerDocument(this, childWrapper);
|
| originalAppendChild.call(this.impl, unwrap(childWrapper));
|
| }
|
|
|
| - childWrapper.nodeWasAdded_();
|
| + nodesWereAdded(nodes);
|
|
|
| return childWrapper;
|
| },
|
| @@ -1402,10 +2766,12 @@ var ShadowDOMPolyfill = {};
|
| assertIsNodeWrapper(refWrapper);
|
| assert(refWrapper.parentNode === this);
|
|
|
| + var nodes;
|
| +
|
| if (this.invalidateShadowRenderer() || invalidateParent(childWrapper)) {
|
| var previousNode = refWrapper.previousSibling;
|
| var nextNode = refWrapper;
|
| - var nodes = collectNodes(childWrapper, this, previousNode, nextNode);
|
| + nodes = collectNodes(childWrapper, this, previousNode, nextNode);
|
|
|
| if (this.firstChild === refWrapper)
|
| this.firstChild_ = nodes[0];
|
| @@ -1423,12 +2789,13 @@ var ShadowDOMPolyfill = {};
|
| adoptNodesIfNeeded(this, nodes);
|
| }
|
| } else {
|
| + nodes = collectNodesNoNeedToUpdatePointers(childWrapper);
|
| ensureSameOwnerDocument(this, childWrapper);
|
| originalInsertBefore.call(this.impl, unwrap(childWrapper),
|
| unwrap(refWrapper));
|
| }
|
|
|
| - childWrapper.nodeWasAdded_();
|
| + nodesWereAdded(nodes);
|
|
|
| return childWrapper;
|
| },
|
| @@ -1485,6 +2852,7 @@ var ShadowDOMPolyfill = {};
|
| }
|
|
|
| var oldChildNode = unwrap(oldChildWrapper);
|
| + var nodes;
|
|
|
| if (this.invalidateShadowRenderer() ||
|
| invalidateParent(newChildWrapper)) {
|
| @@ -1492,8 +2860,7 @@ var ShadowDOMPolyfill = {};
|
| var nextNode = oldChildWrapper.nextSibling;
|
| if (nextNode === newChildWrapper)
|
| nextNode = newChildWrapper.nextSibling;
|
| - var nodes = collectNodes(newChildWrapper, this,
|
| - previousNode, nextNode);
|
| + nodes = collectNodes(newChildWrapper, this, previousNode, nextNode);
|
|
|
| if (this.firstChild === oldChildWrapper)
|
| this.firstChild_ = nodes[0];
|
| @@ -1511,12 +2878,13 @@ var ShadowDOMPolyfill = {};
|
| oldChildNode);
|
| }
|
| } else {
|
| + nodes = collectNodesNoNeedToUpdatePointers(newChildWrapper);
|
| ensureSameOwnerDocument(this, newChildWrapper);
|
| originalReplaceChild.call(this.impl, unwrap(newChildWrapper),
|
| oldChildNode);
|
| }
|
|
|
| - newChildWrapper.nodeWasAdded_();
|
| + nodesWereAdded(nodes);
|
|
|
| return oldChildWrapper;
|
| },
|
| @@ -1635,19 +3003,10 @@ var ShadowDOMPolyfill = {};
|
| // This only wraps, it therefore only operates on the composed DOM and not
|
| // the logical DOM.
|
| return originalCompareDocumentPosition.call(this.impl, unwrap(otherNode));
|
| - },
|
| -
|
| - // TODO(jmesserly): this is a workaround for
|
| - // https://github.com/Polymer/ShadowDOM/issues/200
|
| - get ownerDocument() {
|
| - scope.renderAllPending();
|
| - return wrap(this.impl.ownerDocument);
|
| }
|
| });
|
|
|
| - // TODO(jmesserly): this is commented out to workaround:
|
| - // https://github.com/Polymer/ShadowDOM/issues/200
|
| - //defineWrapGetter(Node, 'ownerDocument');
|
| + 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
|
| @@ -1858,7 +3217,7 @@ var ShadowDOMPolyfill = {};
|
| var registerWrapper = scope.registerWrapper;
|
| var wrappers = scope.wrappers;
|
|
|
| - var shadowRootTable = new SideTable();
|
| + var shadowRootTable = new WeakMap();
|
| var OriginalElement = window.Element;
|
|
|
|
|
| @@ -2224,8 +3583,8 @@ var ShadowDOMPolyfill = {};
|
| var unwrap = scope.unwrap;
|
| var wrap = scope.wrap;
|
|
|
| - var contentTable = new SideTable();
|
| - var templateContentsOwnerTable = new SideTable();
|
| + 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) {
|
| @@ -2362,8 +3721,8 @@ var ShadowDOMPolyfill = {};
|
| var setInnerHTML = scope.setInnerHTML;
|
| var unwrap = scope.unwrap;
|
|
|
| - var shadowHostTable = new SideTable();
|
| - var nextOlderShadowTreeTable = new SideTable();
|
| + var shadowHostTable = new WeakMap();
|
| + var nextOlderShadowTreeTable = new WeakMap();
|
|
|
| function ShadowRoot(hostWrapper) {
|
| var node = unwrap(hostWrapper.impl.ownerDocument.createDocumentFragment());
|
| @@ -2417,6 +3776,7 @@ var ShadowDOMPolyfill = {};
|
| (function(scope) {
|
| 'use strict';
|
|
|
| + var Element = scope.wrappers.Element;
|
| var HTMLContentElement = scope.wrappers.HTMLContentElement;
|
| var HTMLShadowElement = scope.wrappers.HTMLShadowElement;
|
| var Node = scope.wrappers.Node;
|
| @@ -2460,80 +3820,60 @@ var ShadowDOMPolyfill = {};
|
| updateWrapperDown(parentNodeWrapper);
|
| }
|
|
|
| - // This object groups DOM operations. This is supposed to be the DOM as the
|
| - // browser/render tree sees it.
|
| - // When changes are done to the visual DOM the logical DOM needs to be updated
|
| - // to reflect the correct tree.
|
| - function removeAllChildNodes(parentNodeWrapper) {
|
| + function insertBefore(parentNodeWrapper, newChildWrapper, refChildWrapper) {
|
| var parentNode = unwrap(parentNodeWrapper);
|
| - updateAllChildNodes(parentNodeWrapper);
|
| - if (parentNode.firstChild)
|
| - parentNode.textContent = '';
|
| - }
|
| + var newChild = unwrap(newChildWrapper);
|
| + var refChild = refChildWrapper ? unwrap(refChildWrapper) : null;
|
|
|
| - function appendChild(parentNodeWrapper, childWrapper) {
|
| - var parentNode = unwrap(parentNodeWrapper);
|
| - var child = unwrap(childWrapper);
|
| - if (child.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
| - updateAllChildNodes(childWrapper);
|
| + remove(newChildWrapper);
|
| + updateWrapperUpAndSideways(newChildWrapper);
|
|
|
| - } else {
|
| - remove(childWrapper);
|
| - updateWrapperUpAndSideways(childWrapper);
|
| - }
|
| + if (!refChildWrapper) {
|
| + parentNodeWrapper.lastChild_ = parentNodeWrapper.lastChild;
|
| + if (parentNodeWrapper.lastChild === parentNodeWrapper.firstChild)
|
| + parentNodeWrapper.firstChild_ = parentNodeWrapper.firstChild;
|
|
|
| - 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;
|
|
|
| - var lastChildWrapper = wrap(parentNode.lastChild);
|
| - if (lastChildWrapper) {
|
| - lastChildWrapper.nextSibling_ = lastChildWrapper.nextSibling;
|
| + refChildWrapper.previousSibling_ = refChildWrapper.previousSibling;
|
| }
|
|
|
| - parentNode.appendChild(child);
|
| - }
|
| -
|
| - function removeChild(parentNodeWrapper, childWrapper) {
|
| - var parentNode = unwrap(parentNodeWrapper);
|
| - var child = unwrap(childWrapper);
|
| -
|
| - updateWrapperUpAndSideways(childWrapper);
|
| -
|
| - if (childWrapper.previousSibling)
|
| - childWrapper.previousSibling.nextSibling_ = childWrapper;
|
| - if (childWrapper.nextSibling)
|
| - childWrapper.nextSibling.previousSibling_ = childWrapper;
|
| -
|
| - if (parentNodeWrapper.lastChild === childWrapper)
|
| - parentNodeWrapper.lastChild_ = childWrapper;
|
| - if (parentNodeWrapper.firstChild === childWrapper)
|
| - parentNodeWrapper.firstChild_ = childWrapper;
|
| -
|
| - parentNode.removeChild(child);
|
| + parentNode.insertBefore(newChild, refChild);
|
| }
|
|
|
| function remove(nodeWrapper) {
|
| var node = unwrap(nodeWrapper)
|
| var parentNode = node.parentNode;
|
| - if (parentNode)
|
| - removeChild(wrap(parentNode), nodeWrapper);
|
| - }
|
| + if (!parentNode)
|
| + return;
|
| +
|
| + var parentNodeWrapper = wrap(parentNode);
|
| + updateWrapperUpAndSideways(nodeWrapper);
|
|
|
| - var distributedChildNodesTable = new SideTable();
|
| - var eventParentsTable = new SideTable();
|
| - var insertionParentTable = new SideTable();
|
| - var rendererForHostTable = new SideTable();
|
| - var shadowDOMRendererTable = new SideTable();
|
| + if (nodeWrapper.previousSibling)
|
| + nodeWrapper.previousSibling.nextSibling_ = nodeWrapper;
|
| + if (nodeWrapper.nextSibling)
|
| + nodeWrapper.nextSibling.previousSibling_ = nodeWrapper;
|
|
|
| - var reprCounter = 0;
|
| + if (parentNodeWrapper.lastChild === nodeWrapper)
|
| + parentNodeWrapper.lastChild_ = nodeWrapper;
|
| + if (parentNodeWrapper.firstChild === nodeWrapper)
|
| + parentNodeWrapper.firstChild_ = nodeWrapper;
|
|
|
| - function repr(node) {
|
| - if (!node.displayName)
|
| - node.displayName = node.nodeName + '-' + ++reprCounter;
|
| - return node.displayName;
|
| + parentNode.removeChild(node);
|
| }
|
|
|
| + var distributedChildNodesTable = new WeakMap();
|
| + var eventParentsTable = new WeakMap();
|
| + var insertionParentTable = new WeakMap();
|
| + var rendererForHostTable = new WeakMap();
|
| + var shadowDOMRendererTable = new WeakMap();
|
| +
|
| function distributeChildToInsertionPoint(child, insertionPoint) {
|
| getDistributedChildNodes(insertionPoint).push(child);
|
| assignToInsertionPoint(child, insertionPoint);
|
| @@ -2569,9 +3909,7 @@ var ShadowDOMPolyfill = {};
|
| */
|
| function visit(tree, predicate, visitor) {
|
| // This operates on logical DOM.
|
| - var nodes = getChildNodesSnapshot(tree);
|
| - for (var i = 0; i < nodes.length; i++) {
|
| - var node = nodes[i];
|
| + for (var node = tree.firstChild; node; node = node.nextSibling) {
|
| if (predicate(node)) {
|
| if (visitor(node) === false)
|
| return;
|
| @@ -2622,14 +3960,14 @@ var ShadowDOMPolyfill = {};
|
| if (!select)
|
| return true;
|
|
|
| - if (node.nodeType !== Node.ELEMENT_NODE)
|
| + if (!(node instanceof Element))
|
| return false;
|
|
|
| // TODO(arv): This does not seem right. Need to check for a simple selector.
|
| if (!selectorMatchRegExp.test(select))
|
| return false;
|
|
|
| - if (select[0] === ':' &&!allowedPseudoRegExp.test(select))
|
| + if (select[0] === ':' && !allowedPseudoRegExp.test(select))
|
| return false;
|
|
|
| try {
|
| @@ -2651,18 +3989,15 @@ var ShadowDOMPolyfill = {};
|
| var renderTimer;
|
|
|
| function renderAllPending() {
|
| - renderTimer = null;
|
| - pendingDirtyRenderers.forEach(function(owner) {
|
| - owner.render();
|
| - });
|
| + for (var i = 0; i < pendingDirtyRenderers.length; i++) {
|
| + pendingDirtyRenderers[i].render();
|
| + }
|
| pendingDirtyRenderers = [];
|
| }
|
|
|
| - function ShadowRenderer(host) {
|
| - this.host = host;
|
| - this.dirty = false;
|
| - this.invalidateAttributes();
|
| - this.associateNode(host);
|
| + function handleRequestAnimationFrame() {
|
| + renderTimer = null;
|
| + renderAllPending();
|
| }
|
|
|
| /**
|
| @@ -2691,10 +4026,92 @@ var ShadowDOMPolyfill = {};
|
| return getRendererForHost(getHostForShadowRoot(shadowRoot));
|
| }
|
|
|
| + 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() {
|
| + render: function(opt_renderNode) {
|
| if (!this.dirty)
|
| return;
|
|
|
| @@ -2702,14 +4119,18 @@ var ShadowDOMPolyfill = {};
|
| this.treeComposition();
|
|
|
| var host = this.host;
|
| - var shadowDOM = host.shadowRoot;
|
| + var shadowRoot = host.shadowRoot;
|
|
|
| - this.removeAllChildNodes(this.host);
|
| + this.associateNode(host);
|
| + var topMostRenderer = !renderNode;
|
| + var renderNode = opt_renderNode || new RenderNode(host);
|
|
|
| - var shadowDOMChildNodes = getChildNodesSnapshot(shadowDOM);
|
| - shadowDOMChildNodes.forEach(function(node) {
|
| - this.renderNode(host, shadowDOM, node, false);
|
| - }, this);
|
| + for (var node = shadowRoot.firstChild; node; node = node.nextSibling) {
|
| + this.renderNode(shadowRoot, renderNode, node, false);
|
| + }
|
| +
|
| + if (topMostRenderer)
|
| + renderNode.sync();
|
|
|
| this.dirty = false;
|
| },
|
| @@ -2720,81 +4141,81 @@ var ShadowDOMPolyfill = {};
|
| pendingDirtyRenderers.push(this);
|
| if (renderTimer)
|
| return;
|
| - renderTimer = window[request](renderAllPending, 0);
|
| + renderTimer = window[request](handleRequestAnimationFrame, 0);
|
| }
|
| },
|
|
|
| - renderNode: function(visualParent, tree, node, isNested) {
|
| + renderNode: function(shadowRoot, renderNode, node, isNested) {
|
| if (isShadowHost(node)) {
|
| - this.appendChild(visualParent, node);
|
| + renderNode = renderNode.append(node);
|
| var renderer = getRendererForHost(node);
|
| renderer.dirty = true; // Need to rerender due to reprojection.
|
| - renderer.render();
|
| + renderer.render(renderNode);
|
| } else if (isInsertionPoint(node)) {
|
| - this.renderInsertionPoint(visualParent, tree, node, isNested);
|
| + this.renderInsertionPoint(shadowRoot, renderNode, node, isNested);
|
| } else if (isShadowInsertionPoint(node)) {
|
| - this.renderShadowInsertionPoint(visualParent, tree, node);
|
| + this.renderShadowInsertionPoint(shadowRoot, renderNode, node);
|
| } else {
|
| - this.renderAsAnyDomTree(visualParent, tree, node, isNested);
|
| + this.renderAsAnyDomTree(shadowRoot, renderNode, node, isNested);
|
| }
|
| },
|
|
|
| - renderAsAnyDomTree: function(visualParent, tree, node, isNested) {
|
| - this.appendChild(visualParent, node);
|
| + renderAsAnyDomTree: function(shadowRoot, renderNode, node, isNested) {
|
| + renderNode = renderNode.append(node);
|
|
|
| if (isShadowHost(node)) {
|
| - render(node);
|
| + var renderer = getRendererForHost(node);
|
| + renderNode.skip = !renderer.dirty;
|
| + renderer.render(renderNode);
|
| } else {
|
| - var parent = node;
|
| - var logicalChildNodes = getChildNodesSnapshot(parent);
|
| - // We associate the parent of a content/shadow with the renderer
|
| - // because we may need to remove stale childNodes.
|
| - if (shadowDOMRendererTable.get(parent))
|
| - this.removeAllChildNodes(parent);
|
| - logicalChildNodes.forEach(function(node) {
|
| - this.renderNode(parent, tree, node, isNested);
|
| - }, this);
|
| + for (var child = node.firstChild; child; child = child.nextSibling) {
|
| + this.renderNode(shadowRoot, renderNode, child, isNested);
|
| + }
|
| }
|
| },
|
|
|
| - renderInsertionPoint: function(visualParent, tree, insertionPoint, isNested) {
|
| + renderInsertionPoint: function(shadowRoot, renderNode, insertionPoint,
|
| + isNested) {
|
| var distributedChildNodes = getDistributedChildNodes(insertionPoint);
|
| if (distributedChildNodes.length) {
|
| - this.removeAllChildNodes(insertionPoint);
|
| + this.associateNode(insertionPoint);
|
|
|
| - distributedChildNodes.forEach(function(child) {
|
| + for (var i = 0; i < distributedChildNodes.length; i++) {
|
| + var child = distributedChildNodes[i];
|
| if (isInsertionPoint(child) && isNested)
|
| - this.renderInsertionPoint(visualParent, tree, child, isNested);
|
| + this.renderInsertionPoint(shadowRoot, renderNode, child, isNested);
|
| else
|
| - this.renderAsAnyDomTree(visualParent, tree, child, isNested);
|
| - }, this);
|
| + this.renderAsAnyDomTree(shadowRoot, renderNode, child, isNested);
|
| + }
|
| } else {
|
| - this.renderFallbackContent(visualParent, insertionPoint);
|
| + this.renderFallbackContent(shadowRoot, renderNode, insertionPoint);
|
| }
|
| - this.remove(insertionPoint);
|
| + this.associateNode(insertionPoint.parentNode);
|
| },
|
|
|
| - renderShadowInsertionPoint: function(visualParent, tree, shadowInsertionPoint) {
|
| - var nextOlderTree = tree.olderShadowRoot;
|
| + renderShadowInsertionPoint: function(shadowRoot, renderNode,
|
| + shadowInsertionPoint) {
|
| + var nextOlderTree = shadowRoot.olderShadowRoot;
|
| if (nextOlderTree) {
|
| assignToInsertionPoint(nextOlderTree, shadowInsertionPoint);
|
| - this.remove(shadowInsertionPoint);
|
| - var shadowDOMChildNodes = getChildNodesSnapshot(nextOlderTree);
|
| - shadowDOMChildNodes.forEach(function(node) {
|
| - this.renderNode(visualParent, nextOlderTree, node, true);
|
| - }, this);
|
| + this.associateNode(shadowInsertionPoint.parentNode);
|
| + for (var node = nextOlderTree.firstChild;
|
| + node;
|
| + node = node.nextSibling) {
|
| + this.renderNode(nextOlderTree, renderNode, node, true);
|
| + }
|
| } else {
|
| - this.renderFallbackContent(visualParent, shadowInsertionPoint);
|
| + this.renderFallbackContent(shadowRoot, renderNode,
|
| + shadowInsertionPoint);
|
| }
|
| },
|
|
|
| - renderFallbackContent: function (visualParent, fallbackHost) {
|
| - var logicalChildNodes = getChildNodesSnapshot(fallbackHost);
|
| + renderFallbackContent: function(shadowRoot, renderNode, fallbackHost) {
|
| this.associateNode(fallbackHost);
|
| - this.remove(fallbackHost);
|
| - logicalChildNodes.forEach(function(node) {
|
| - this.appendChild(visualParent, node);
|
| - }, this);
|
| + this.associateNode(fallbackHost.parentNode);
|
| + for (var node = fallbackHost.firstChild; node; node = node.nextSibling) {
|
| + this.renderAsAnyDomTree(shadowRoot, renderNode, node, false);
|
| + }
|
| },
|
|
|
| /**
|
| @@ -2837,7 +4258,6 @@ var ShadowDOMPolyfill = {};
|
|
|
| // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#dfn-distribution-algorithm
|
| distribute: function(tree, pool) {
|
| - var anyRemoved = false;
|
| var self = this;
|
|
|
| visit(tree, isActiveInsertionPoint,
|
| @@ -2853,17 +4273,9 @@ var ShadowDOMPolyfill = {};
|
| if (matchesCriteria(node, insertionPoint)) { // 1.2.2
|
| distributeChildToInsertionPoint(node, insertionPoint); // 1.2.2.1
|
| pool[i] = undefined; // 1.2.2.2
|
| - anyRemoved = true;
|
| }
|
| }
|
| });
|
| -
|
| - if (!anyRemoved)
|
| - return pool;
|
| -
|
| - return pool.filter(function(item) {
|
| - return item !== undefined;
|
| - });
|
| },
|
|
|
| // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#dfn-tree-composition
|
| @@ -2871,8 +4283,10 @@ var ShadowDOMPolyfill = {};
|
| var shadowHost = this.host;
|
| var tree = shadowHost.shadowRoot; // 1.
|
| var pool = []; // 2.
|
| - var shadowHostChildNodes = getChildNodesSnapshot(shadowHost);
|
| - shadowHostChildNodes.forEach(function(child) { // 3.
|
| +
|
| + 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?
|
| @@ -2882,7 +4296,7 @@ var ShadowDOMPolyfill = {};
|
| } else {
|
| pool.push(child); // 3.3.
|
| }
|
| - });
|
| + }
|
|
|
| var shadowInsertionPoint, point;
|
| while (tree) { // 4.
|
| @@ -2894,7 +4308,7 @@ var ShadowDOMPolyfill = {};
|
| });
|
| point = shadowInsertionPoint;
|
|
|
| - pool = this.distribute(tree, pool); // 4.2.
|
| + this.distribute(tree, pool); // 4.2.
|
| if (point) { // 4.3.
|
| var nextOlderTree = tree.olderShadowRoot; // 4.3.1.
|
| if (!nextOlderTree) {
|
| @@ -2910,23 +4324,6 @@ var ShadowDOMPolyfill = {};
|
| }
|
| },
|
|
|
| - appendChild: function(parent, child) {
|
| - // this.associateNode(child);
|
| - this.associateNode(parent);
|
| - appendChild(parent, child);
|
| - },
|
| -
|
| - remove: function(node) {
|
| - // this.associateNode(node);
|
| - this.associateNode(node.parentNode);
|
| - remove(node);
|
| - },
|
| -
|
| - removeAllChildNodes: function(parent) {
|
| - this.associateNode(parent);
|
| - removeAllChildNodes(parent);
|
| - },
|
| -
|
| associateNode: function(node) {
|
| shadowDOMRendererTable.set(node, this);
|
| }
|
| @@ -2934,21 +4331,21 @@ var ShadowDOMPolyfill = {};
|
|
|
| function isInsertionPoint(node) {
|
| // Should this include <shadow>?
|
| - return node.localName === 'content';
|
| + return node instanceof HTMLContentElement;
|
| }
|
|
|
| function isActiveInsertionPoint(node) {
|
| // <content> inside another <content> or <shadow> is considered inactive.
|
| - return node.localName === 'content';
|
| + return node instanceof HTMLContentElement;
|
| }
|
|
|
| function isShadowInsertionPoint(node) {
|
| - return node.localName === 'shadow';
|
| + return node instanceof HTMLShadowElement;
|
| }
|
|
|
| function isActiveShadowInsertionPoint(node) {
|
| // <shadow> inside another <content> or <shadow> is considered inactive.
|
| - return node.localName === 'shadow';
|
| + return node instanceof HTMLShadowElement;
|
| }
|
|
|
| function isShadowHost(shadowHost) {
|
| @@ -3025,9 +4422,8 @@ var ShadowDOMPolyfill = {};
|
|
|
| // Exposed for testing
|
| scope.visual = {
|
| - removeAllChildNodes: removeAllChildNodes,
|
| - appendChild: appendChild,
|
| - removeChild: removeChild
|
| + insertBefore: insertBefore,
|
| + remove: remove,
|
| };
|
|
|
| })(this.ShadowDOMPolyfill);
|
| @@ -3109,7 +4505,7 @@ var ShadowDOMPolyfill = {};
|
| var wrapEventTargetMethods = scope.wrapEventTargetMethods;
|
| var wrapNodeList = scope.wrapNodeList;
|
|
|
| - var implementationTable = new SideTable();
|
| + var implementationTable = new WeakMap();
|
|
|
| function Document(node) {
|
| Node.call(this, node);
|
| @@ -3384,6 +4780,7 @@ var ShadowDOMPolyfill = {};
|
| var unwrap = scope.unwrap;
|
| var unwrapIfNeeded = scope.unwrapIfNeeded;
|
| var wrap = scope.wrap;
|
| + var renderAllPending = scope.renderAllPending;
|
|
|
| var OriginalWindow = window.Window;
|
|
|
| @@ -3394,6 +4791,7 @@ var ShadowDOMPolyfill = {};
|
|
|
| var originalGetComputedStyle = window.getComputedStyle;
|
| OriginalWindow.prototype.getComputedStyle = function(el, pseudo) {
|
| + renderAllPending();
|
| return originalGetComputedStyle.call(this || window, unwrapIfNeeded(el),
|
| pseudo);
|
| };
|
| @@ -3745,11 +5143,9 @@ var ShadowDOMPolyfill = {};
|
| // Fix up class names for Firefox.
|
| // For some of them (like HTMLFormElement and HTMLInputElement),
|
| // the "constructor" property of the unwrapped nodes points at the
|
| - // wrapper.
|
| - // Note: it is safe to check for the GeneratedWrapper string because
|
| - // we know it is some kind of Shadow DOM wrapper object.
|
| - var ctor = obj.constructor;
|
| - if (ctor && ctor.name == 'GeneratedWrapper') {
|
| + // same constructor as the wrapper.
|
| + var ctor = obj.constructor
|
| + if (ctor === unwrapped.constructor) {
|
| var name = ctor._ShadowDOMPolyfill$cacheTag_;
|
| if (!name) {
|
| name = Object.prototype.toString.call(unwrapped);
|
|
|