blob: f411852d5eb1aea73001f3be60b3f21a61691b76 [file] [log] [blame]
if (!HTMLElement.prototype.createShadowRoot
|| window.__forceShadowDomPolyfill) {
/*
* 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() {
// TODO(jmesserly): fix dart:html to use unprefixed name
if (Element.prototype.webkitCreateShadowRoot) {
Element.prototype.webkitCreateShadowRoot = function() {
return window.ShadowDOMPolyfill.wrapIfNeeded(this).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';
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 = {};
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
};
}
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);
/*
* 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 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;
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 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) {
var clone = wrap(this.impl.cloneNode(false));
if (deep) {
for (var child = this.firstChild; child; child = child.nextSibling) {
clone.appendChild(child.cloneNode(true));
}
}
// TODO(arv): Some HTML elements also clone other data like value.
return clone;
},
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;
})(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;
/////////////////////////////////////////////////////////////////////////////
// innerHTML and outerHTML
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#escapingString
var escapeAttrRegExp = /[&\u00A0"]/g;
var escapeDataRegExp = /[&\u00A0<>]/g;
function escapeReplace(c) {
switch (c) {
case '&':
return '&amp;';
case '<':
return '&lt;';
case '>':
return '&gt;';
case '"':
return '&quot;'
case '\u00A0':
return '&nbsp;';
}
}
function escapeAttr(s) {
return s.replace(escapeAttrRegExp, escapeReplace);
}
function escapeData(s) {
return s.replace(escapeDataRegExp, escapeReplace);
}
function makeSet(arr) {
var set = {};
for (var i = 0; i < arr.length; i++) {
set[arr[i]] = true;
}
return set;
}
// http://www.whatwg.org/specs/web-apps/current-work/#void-elements
var voidElements = makeSet([
'area',
'base',
'br',
'col',
'command',
'embed',
'hr',
'img',
'input',
'keygen',
'link',
'meta',
'param',
'source',
'track',
'wbr'
]);
var plaintextParents = makeSet([
'style',
'script',
'xmp',
'iframe',
'noembed',
'noframes',
'plaintext',
'noscript'
]);
function getOuterHTML(node, parentNode) {
switch (node.nodeType) {
case Node.ELEMENT_NODE:
var tagName = node.tagName.toLowerCase();
var s = '<' + tagName;
var attrs = node.attributes;
for (var i = 0, attr; attr = attrs[i]; i++) {
s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"';
}
s += '>';
if (voidElements[tagName])
return s;
return s + getInnerHTML(node) + '</' + tagName + '>';
case Node.TEXT_NODE:
var data = node.data;
if (parentNode && plaintextParents[parentNode.localName])
return data;
return escapeData(data);
case Node.COMMENT_NODE:
return '<!--' + node.data + '-->';
default:
console.error(node);
throw new Error('not implemented');
}
}
function getInnerHTML(node) {
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;
function HTMLElement(node) {
Element.call(this, node);
}
HTMLElement.prototype = Object.create(Element.prototype);
mixin(HTMLElement.prototype, {
get innerHTML() {
// TODO(arv): This should fallback to this.impl.innerHTML if there
// are no shadow trees below or above the context node.
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())
setInnerHTML(this, value, this.tagName);
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 getInnerHTML = scope.getInnerHTML;
var mixin = scope.mixin;
var registerWrapper = scope.registerWrapper;
var setInnerHTML = scope.setInnerHTML;
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);
},
get innerHTML() {
return getInnerHTML(this.content);
},
set innerHTML(value) {
setInnerHTML(this.content, value);
}
// 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 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 originalImportNode = document.importNode;
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) {
// We need to manually walk the tree to ensure we do not include rendered
// shadow trees.
var clone = wrap(originalImportNode.call(this.impl, unwrap(node), false));
if (deep) {
for (var child = node.firstChild; child; child = child.nextSibling) {
clone.appendChild(this.importNode(child, true));
}
}
return clone;
},
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() {
var ShadowDOMPolyfill = window.ShadowDOMPolyfill;
var wrap = ShadowDOMPolyfill.wrap;
// patch in prefixed name
Object.defineProperties(HTMLElement.prototype, {
//TODO(sjmiles): review accessor alias with Arv
webkitShadowRoot: {
get: function() {
return this.shadowRoot;
}
}
});
// ShadowCSS needs this:
window.wrap = window.ShadowDOMPolyfill.wrap;
window.unwrap = window.ShadowDOMPolyfill.unwrap;
//TODO(sjmiles): review method alias with Arv
HTMLElement.prototype.webkitCreateShadowRoot =
HTMLElement.prototype.createShadowRoot;
// TODO(jmesserly): we need to wrap document somehow (a dart:html hook?)
window.dartExperimentalFixupGetTag = function(originalGetTag) {
var NodeList = ShadowDOMPolyfill.wrappers.NodeList;
var ShadowRoot = ShadowDOMPolyfill.wrappers.ShadowRoot;
var unwrapIfNeeded = ShadowDOMPolyfill.unwrapIfNeeded;
function getTag(obj) {
// TODO(jmesserly): do we still need these?
if (obj instanceof NodeList) return 'NodeList';
if (obj instanceof ShadowRoot) return 'ShadowRoot';
if (window.MutationRecord && (obj instanceof MutationRecord))
return 'MutationRecord';
if (window.MutationObserver && (obj instanceof MutationObserver))
return 'MutationObserver';
// TODO(jmesserly): this prevents incorrect interaction between ShadowDOM
// and dart:html's <template> polyfill. Essentially, ShadowDOM is
// polyfilling native template, but our Dart polyfill fails to detect this
// because the unwrapped node is an HTMLUnknownElement, leading it to
// think the node has no content.
if (obj instanceof HTMLTemplateElement) return 'HTMLTemplateElement';
var unwrapped = unwrapIfNeeded(obj);
if (obj !== unwrapped) {
// Fix up class names for Firefox.
// For some of them (like HTMLFormElement and HTMLInputElement),
// the "constructor" property of the unwrapped nodes points at the
// 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);
name = name.substring(8, name.length - 1);
ctor._ShadowDOMPolyfill$cacheTag_ = name;
}
return name;
}
obj = unwrapped;
}
return originalGetTag(obj);
}
return getTag;
};
})();
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
var Platform = {};
/*
* 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: 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.
* ::pseudo: These rules are converted to rules that take advantage of the
pseudo attribute. For example, a shadowRoot like this inside an x-foo
<div pseudo="x-special">Special</div>
with a rule like this:
x-foo::x-special { ... }
becomes:
x-foo [pseudo=x-special] { ... }
* ::part(): These rules are converted to rules that take advantage of the
part attribute. For example, a shadowRoot like this inside an x-foo
<div part="special">Special</div>
with a rule like this:
x-foo::part(special) { ... }
becomes:
x-foo [part=special] { ... }
Unaddressed ShadowDOM styling features:
* 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::-webkit-distributed(div) {
background: red;
}
</style>
<content></content>
could become:
<style>
/ *@polyfill .content-container div * /
content::-webkit-distributed(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 loader = scope.loader;
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.shimAtHost(scopeStyles, name, typeExtension) +
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;
},
// form: @host { .foo { declarations } }
// becomes: scopeName.foo { declarations }
shimAtHost: function(styles, name, typeExtension) {
if (styles) {
return this.convertAtHostStyles(styles, name, typeExtension);
}
},
convertAtHostStyles: function(styles, name, typeExtension) {
var cssText = stylesToCssText(styles), self = this;
cssText = cssText.replace(hostRuleRe, function(m, p1) {
return self.scopeHostCss(p1, name, typeExtension);
});
cssText = rulesToCss(this.findAtHostRules(cssToRules(cssText),
this.makeScopeMatcher(name, typeExtension)));
return cssText;
},
scopeHostCss: function(cssText, name, typeExtension) {
var self = this;
return cssText.replace(selectorRe, function(m, p1, p2) {
return self.scopeHostSelector(p1, name, typeExtension) + ' ' + p2 + '\n\t';
});
},
// supports scopig by name and [is=name] syntax
scopeHostSelector: function(selector, name, typeExtension) {
var r = [], parts = selector.split(','), is = '[is=' + name + ']';
parts.forEach(function(p) {
p = p.trim();
// selector: *|:scope -> name
if (p.match(hostElementRe)) {
p = p.replace(hostElementRe, typeExtension ? is + '$1$3' :
name + '$1$3');
// selector: .foo -> name.foo (OR) [bar] -> name[bar]
} else if (p.match(hostFixableRe)) {
p = typeExtension ? is + p : name + p;
}
r.push(p);
}, this);
return r.join(', ');
},
// consider styles that do not include component name in the selector to be
// unscoped and in need of promotion;
// for convenience, also consider keyframe rules this way.
findAtHostRules: function(cssRules, matcher) {
return Array.prototype.filter.call(cssRules,
this.isHostRule.bind(this, matcher));
},
isHostRule: function(matcher, cssRule) {
return (cssRule.selectorText && cssRule.selectorText.match(matcher)) ||
(cssRule.cssRules && this.findAtHostRules(cssRule.cssRules, matcher).length) ||
(cssRule.type == CSSRule.WEBKIT_KEYFRAMES_RULE);
},
/* 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).replace(hostRuleRe, '');
cssText = this.insertPolyfillHostInCssText(cssText);
cssText = this.convertColonHost(cssText);
cssText = this.convertColonAncestor(cssText);
// TODO(sorvell): deprecated, remove
cssText = this.convertPseudos(cssText);
// TODO(sorvell): deprecated, remove
cssText = this.convertParts(cssText);
cssText = this.convertCombinators(cssText);
var rules = cssToRules(cssText);
if (name) {
cssText = this.scopeRules(rules, name, typeExtension);
}
return cssText;
},
convertPseudos: function(cssText) {
return cssText.replace(cssPseudoRe, ' [pseudo=$1]');
},
convertParts: function(cssText) {
return cssText.replace(cssPartRe, ' [part=$1]');
},
/*
* 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 hostRuleRe = /@host[^{]*{(([^}]*?{[^{]*?}[\s\S]*?)+)}/gim,
selectorRe = /([^{]*)({[\s\S]*?})/gim,
hostElementRe = /(.*)((?:\*)|(?:\:scope))(.*)/,
hostFixableRe = /^[.\[:]/,
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 sheet;
function getSheet() {
if (!sheet) {
sheet = document.createElement("style");
sheet.setAttribute('ShadowCSSShim', '');
sheet.shadowCssShim = 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]);
document.addEventListener('DOMContentLoaded', function() {
if (window.HTMLImports && !HTMLImports.useNative) {
HTMLImports.importer.preloadSelectors +=
', link[rel=stylesheet]:not([nopolyfill])';
HTMLImports.parser.parseGeneric = function(elt) {
if (elt.shadowCssShim) {
return;
}
var style = elt;
if (!elt.hasAttribute('nopolyfill')) {
if (elt.__resource) {
style = elt.ownerDocument.createElement('style');
style.textContent = Platform.loader.resolveUrlsInCssText(
elt.__resource, elt.href);
// remove links from main document
if (elt.ownerDocument === doc) {
elt.parentNode.removeChild(elt);
}
} else {
Platform.loader.resolveUrlsInStyle(style);
}
var styles = [style];
style.textContent = ShadowCSS.stylesToShimmedCssText(styles, styles);
style.shadowCssShim = true;
}
// place in document
if (style.parentNode !== head) {
head.appendChild(style);
}
}
}
});
}
// exports
scope.ShadowCSS = ShadowCSS;
})(window.Platform);
}