d12f8b560b
R=sigmund@google.com Review URL: https://codereview.chromium.org//349313005 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@37629 260f80e4-7a28-3924-810f-c04153c831b5
14165 lines
408 KiB
JavaScript
14165 lines
408 KiB
JavaScript
/**
|
|
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
* Code distributed by Google as part of the polymer project is also
|
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
*/
|
|
|
|
window.Platform = window.Platform || {};
|
|
// prepopulate window.logFlags if necessary
|
|
window.logFlags = window.logFlags || {};
|
|
// process flags
|
|
(function(scope){
|
|
// import
|
|
var flags = scope.flags || {};
|
|
// populate flags from location
|
|
location.search.slice(1).split('&').forEach(function(o) {
|
|
o = o.split('=');
|
|
o[0] && (flags[o[0]] = o[1] || true);
|
|
});
|
|
var entryPoint = document.currentScript ||
|
|
document.querySelector('script[src*="platform.js"]');
|
|
if (entryPoint) {
|
|
var a = entryPoint.attributes;
|
|
for (var i = 0, n; i < a.length; i++) {
|
|
n = a[i];
|
|
if (n.name !== 'src') {
|
|
flags[n.name] = n.value || true;
|
|
}
|
|
}
|
|
}
|
|
if (flags.log) {
|
|
flags.log.split(',').forEach(function(f) {
|
|
window.logFlags[f] = true;
|
|
});
|
|
}
|
|
// If any of these flags match 'native', then force native ShadowDOM; any
|
|
// other truthy value, or failure to detect native
|
|
// ShadowDOM, results in polyfill
|
|
flags.shadow = flags.shadow || flags.shadowdom || flags.polyfill;
|
|
if (flags.shadow === 'native') {
|
|
flags.shadow = false;
|
|
} else {
|
|
flags.shadow = flags.shadow || !HTMLElement.prototype.createShadowRoot;
|
|
}
|
|
|
|
if (flags.shadow && document.querySelectorAll('script').length > 1) {
|
|
console.warn('platform.js is not the first script on the page. ' +
|
|
'See http://www.polymer-project.org/docs/start/platform.html#setup ' +
|
|
'for details.');
|
|
}
|
|
|
|
// CustomElements polyfill flag
|
|
if (flags.register) {
|
|
window.CustomElements = window.CustomElements || {flags: {}};
|
|
window.CustomElements.flags.register = flags.register;
|
|
}
|
|
|
|
if (flags.imports) {
|
|
window.HTMLImports = window.HTMLImports || {flags: {}};
|
|
window.HTMLImports.flags.imports = flags.imports;
|
|
}
|
|
|
|
// export
|
|
scope.flags = flags;
|
|
})(Platform);
|
|
|
|
/*
|
|
* Copyright 2012 The Polymer Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style
|
|
* license that can be found in the LICENSE file.
|
|
*/
|
|
|
|
if (typeof WeakMap === 'undefined') {
|
|
(function() {
|
|
var defineProperty = Object.defineProperty;
|
|
var counter = Date.now() % 1e9;
|
|
|
|
var WeakMap = function() {
|
|
this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__');
|
|
};
|
|
|
|
WeakMap.prototype = {
|
|
set: function(key, value) {
|
|
var entry = key[this.name];
|
|
if (entry && entry[0] === key)
|
|
entry[1] = value;
|
|
else
|
|
defineProperty(key, this.name, {value: [key, value], writable: true});
|
|
},
|
|
get: function(key) {
|
|
var entry;
|
|
return (entry = key[this.name]) && entry[0] === key ?
|
|
entry[1] : undefined;
|
|
},
|
|
delete: function(key) {
|
|
this.set(key, undefined);
|
|
}
|
|
};
|
|
|
|
window.WeakMap = WeakMap;
|
|
})();
|
|
}
|
|
|
|
// Copyright 2012 Google Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
(function(global) {
|
|
'use strict';
|
|
|
|
// 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 = {};
|
|
var arr = [];
|
|
Object.observe(test, callback);
|
|
Array.observe(arr, callback);
|
|
test.id = 1;
|
|
test.id = 2;
|
|
delete test.id;
|
|
arr.push(1, 2);
|
|
arr.length = 0;
|
|
|
|
Object.deliverChangeRecords(callback);
|
|
if (records.length !== 5)
|
|
return false;
|
|
|
|
if (records[0].type != 'add' ||
|
|
records[1].type != 'update' ||
|
|
records[2].type != 'delete' ||
|
|
records[3].type != 'splice' ||
|
|
records[4].type != 'splice') {
|
|
return false;
|
|
}
|
|
|
|
Object.unobserve(test, callback);
|
|
Array.unobserve(arr, callback);
|
|
|
|
return true;
|
|
}
|
|
|
|
var hasObserve = detectObjectObserve();
|
|
|
|
function detectEval() {
|
|
// Don't test for eval if we're running in a Chrome App environment.
|
|
// We check for APIs set that only exist in a Chrome App context.
|
|
if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) {
|
|
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(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 identRegExp = new RegExp('^' + identStart + '+' + identPart + '*' + '$');
|
|
|
|
function getPathCharType(char) {
|
|
if (char === undefined)
|
|
return 'eof';
|
|
|
|
var code = char.charCodeAt(0);
|
|
|
|
switch(code) {
|
|
case 0x5B: // [
|
|
case 0x5D: // ]
|
|
case 0x2E: // .
|
|
case 0x22: // "
|
|
case 0x27: // '
|
|
case 0x30: // 0
|
|
return char;
|
|
|
|
case 0x5F: // _
|
|
case 0x24: // $
|
|
return 'ident';
|
|
|
|
case 0x20: // Space
|
|
case 0x09: // Tab
|
|
case 0x0A: // Newline
|
|
case 0x0D: // Return
|
|
case 0xA0: // No-break space
|
|
case 0xFEFF: // Byte Order Mark
|
|
case 0x2028: // Line Separator
|
|
case 0x2029: // Paragraph Separator
|
|
return 'ws';
|
|
}
|
|
|
|
// a-z, A-Z
|
|
if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A))
|
|
return 'ident';
|
|
|
|
// 1-9
|
|
if (0x31 <= code && code <= 0x39)
|
|
return 'number';
|
|
|
|
return 'else';
|
|
}
|
|
|
|
var pathStateMachine = {
|
|
'beforePath': {
|
|
'ws': ['beforePath'],
|
|
'ident': ['inIdent', 'append'],
|
|
'[': ['beforeElement'],
|
|
'eof': ['afterPath']
|
|
},
|
|
|
|
'inPath': {
|
|
'ws': ['inPath'],
|
|
'.': ['beforeIdent'],
|
|
'[': ['beforeElement'],
|
|
'eof': ['afterPath']
|
|
},
|
|
|
|
'beforeIdent': {
|
|
'ws': ['beforeIdent'],
|
|
'ident': ['inIdent', 'append']
|
|
},
|
|
|
|
'inIdent': {
|
|
'ident': ['inIdent', 'append'],
|
|
'0': ['inIdent', 'append'],
|
|
'number': ['inIdent', 'append'],
|
|
'ws': ['inPath', 'push'],
|
|
'.': ['beforeIdent', 'push'],
|
|
'[': ['beforeElement', 'push'],
|
|
'eof': ['afterPath', 'push']
|
|
},
|
|
|
|
'beforeElement': {
|
|
'ws': ['beforeElement'],
|
|
'0': ['afterZero', 'append'],
|
|
'number': ['inIndex', 'append'],
|
|
"'": ['inSingleQuote', 'append', ''],
|
|
'"': ['inDoubleQuote', 'append', '']
|
|
},
|
|
|
|
'afterZero': {
|
|
'ws': ['afterElement', 'push'],
|
|
']': ['inPath', 'push']
|
|
},
|
|
|
|
'inIndex': {
|
|
'0': ['inIndex', 'append'],
|
|
'number': ['inIndex', 'append'],
|
|
'ws': ['afterElement'],
|
|
']': ['inPath', 'push']
|
|
},
|
|
|
|
'inSingleQuote': {
|
|
"'": ['afterElement'],
|
|
'eof': ['error'],
|
|
'else': ['inSingleQuote', 'append']
|
|
},
|
|
|
|
'inDoubleQuote': {
|
|
'"': ['afterElement'],
|
|
'eof': ['error'],
|
|
'else': ['inDoubleQuote', 'append']
|
|
},
|
|
|
|
'afterElement': {
|
|
'ws': ['afterElement'],
|
|
']': ['inPath', 'push']
|
|
}
|
|
}
|
|
|
|
function noop() {}
|
|
|
|
function parsePath(path) {
|
|
var keys = [];
|
|
var index = -1;
|
|
var c, newChar, key, type, transition, action, typeMap, mode = 'beforePath';
|
|
|
|
var actions = {
|
|
push: function() {
|
|
if (key === undefined)
|
|
return;
|
|
|
|
keys.push(key);
|
|
key = undefined;
|
|
},
|
|
|
|
append: function() {
|
|
if (key === undefined)
|
|
key = newChar
|
|
else
|
|
key += newChar;
|
|
}
|
|
};
|
|
|
|
function maybeUnescapeQuote() {
|
|
if (index >= path.length)
|
|
return;
|
|
|
|
var nextChar = path[index + 1];
|
|
if ((mode == 'inSingleQuote' && nextChar == "'") ||
|
|
(mode == 'inDoubleQuote' && nextChar == '"')) {
|
|
index++;
|
|
newChar = nextChar;
|
|
actions.append();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
while (mode) {
|
|
index++;
|
|
c = path[index];
|
|
|
|
if (c == '\\' && maybeUnescapeQuote(mode))
|
|
continue;
|
|
|
|
type = getPathCharType(c);
|
|
typeMap = pathStateMachine[mode];
|
|
transition = typeMap[type] || typeMap['else'] || 'error';
|
|
|
|
if (transition == 'error')
|
|
return; // parse error;
|
|
|
|
mode = transition[0];
|
|
action = actions[transition[1]] || noop;
|
|
newChar = transition[2] === undefined ? c : transition[2];
|
|
action();
|
|
|
|
if (mode === 'afterPath') {
|
|
return keys;
|
|
}
|
|
}
|
|
|
|
return; // parse error
|
|
}
|
|
|
|
function isIdent(s) {
|
|
return identRegExp.test(s);
|
|
}
|
|
|
|
var constructorIsPrivate = {};
|
|
|
|
function Path(parts, privateToken) {
|
|
if (privateToken !== constructorIsPrivate)
|
|
throw Error('Use Path.get to retrieve path objects');
|
|
|
|
if (parts.length)
|
|
Array.prototype.push.apply(this, parts.slice());
|
|
|
|
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.length == 0)
|
|
pathString = '';
|
|
|
|
if (typeof pathString != 'string') {
|
|
if (isIndex(pathString.length)) {
|
|
// Constructed with array-like (pre-parsed) keys
|
|
return new Path(pathString, constructorIsPrivate);
|
|
}
|
|
|
|
pathString = String(pathString);
|
|
}
|
|
|
|
var path = pathCache[pathString];
|
|
if (path)
|
|
return path;
|
|
|
|
var parts = parsePath(pathString);
|
|
if (!parts)
|
|
return invalidPath;
|
|
|
|
var path = new Path(parts, constructorIsPrivate);
|
|
pathCache[pathString] = path;
|
|
return path;
|
|
}
|
|
|
|
Path.get = getPath;
|
|
|
|
function formatAccessor(key) {
|
|
if (isIndex(key)) {
|
|
return '[' + key + ']';
|
|
} else {
|
|
return '["' + key.replace(/"/g, '\\"') + '"]';
|
|
}
|
|
}
|
|
|
|
Path.prototype = createObject({
|
|
__proto__: [],
|
|
valid: true,
|
|
|
|
toString: function() {
|
|
var pathString = '';
|
|
for (var i = 0; i < this.length; i++) {
|
|
var key = this[i];
|
|
if (isIdent(key)) {
|
|
pathString += i ? '.' + key : key;
|
|
} else {
|
|
pathString += formatAccessor(key);
|
|
}
|
|
}
|
|
|
|
return pathString;
|
|
},
|
|
|
|
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 (!isObject(obj))
|
|
return;
|
|
observe(obj, this[0]);
|
|
}
|
|
},
|
|
|
|
compiledGetValueFromFn: function() {
|
|
var str = '';
|
|
var pathString = 'obj';
|
|
str += 'if (obj != null';
|
|
var i = 0;
|
|
var key;
|
|
for (; i < (this.length - 1); i++) {
|
|
key = this[i];
|
|
pathString += isIdent(key) ? '.' + key : formatAccessor(key);
|
|
str += ' &&\n ' + pathString + ' != null';
|
|
}
|
|
str += ')\n';
|
|
|
|
var key = this[i];
|
|
pathString += isIdent(key) ? '.' + key : formatAccessor(key);
|
|
|
|
str += ' return ' + pathString + ';\nelse\n return undefined;';
|
|
return new Function('obj', str);
|
|
},
|
|
|
|
setValueFrom: function(obj, value) {
|
|
if (!this.length)
|
|
return false;
|
|
|
|
for (var i = 0; i < this.length - 1; i++) {
|
|
if (!isObject(obj))
|
|
return false;
|
|
obj = obj[this[i]];
|
|
}
|
|
|
|
if (!isObject(obj))
|
|
return false;
|
|
|
|
obj[this[i]] = value;
|
|
return true;
|
|
}
|
|
});
|
|
|
|
var invalidPath = new Path('', constructorIsPrivate);
|
|
invalidPath.valid = false;
|
|
invalidPath.getValueFrom = invalidPath.setValueFrom = function() {};
|
|
|
|
var MAX_DIRTY_CHECK_CYCLES = 1000;
|
|
|
|
function dirtyCheck(observer) {
|
|
var cycles = 0;
|
|
while (cycles < MAX_DIRTY_CHECK_CYCLES && observer.check_()) {
|
|
cycles++;
|
|
}
|
|
if (global.testingExposeCycleCount)
|
|
global.dirtyCheckCycleCount = cycles;
|
|
|
|
return cycles > 0;
|
|
}
|
|
|
|
function objectIsEmpty(object) {
|
|
for (var prop in object)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
function diffIsEmpty(diff) {
|
|
return objectIsEmpty(diff.added) &&
|
|
objectIsEmpty(diff.removed) &&
|
|
objectIsEmpty(diff.changed);
|
|
}
|
|
|
|
function diffObjectFromOldObject(object, oldObject) {
|
|
var added = {};
|
|
var removed = {};
|
|
var changed = {};
|
|
|
|
for (var prop in oldObject) {
|
|
var newValue = object[prop];
|
|
|
|
if (newValue !== undefined && newValue === oldObject[prop])
|
|
continue;
|
|
|
|
if (!(prop in object)) {
|
|
removed[prop] = undefined;
|
|
continue;
|
|
}
|
|
|
|
if (newValue !== oldObject[prop])
|
|
changed[prop] = newValue;
|
|
}
|
|
|
|
for (var prop in object) {
|
|
if (prop in oldObject)
|
|
continue;
|
|
|
|
added[prop] = object[prop];
|
|
}
|
|
|
|
if (Array.isArray(object) && object.length !== oldObject.length)
|
|
changed.length = object.length;
|
|
|
|
return {
|
|
added: added,
|
|
removed: removed,
|
|
changed: changed
|
|
};
|
|
}
|
|
|
|
var eomTasks = [];
|
|
function runEOMTasks() {
|
|
if (!eomTasks.length)
|
|
return false;
|
|
|
|
for (var i = 0; i < eomTasks.length; i++) {
|
|
eomTasks[i]();
|
|
}
|
|
eomTasks.length = 0;
|
|
return true;
|
|
}
|
|
|
|
var runEOM = hasObserve ? (function(){
|
|
var eomObj = { pingPong: true };
|
|
var eomRunScheduled = false;
|
|
|
|
Object.observe(eomObj, function() {
|
|
runEOMTasks();
|
|
eomRunScheduled = false;
|
|
});
|
|
|
|
return function(fn) {
|
|
eomTasks.push(fn);
|
|
if (!eomRunScheduled) {
|
|
eomRunScheduled = true;
|
|
eomObj.pingPong = !eomObj.pingPong;
|
|
}
|
|
};
|
|
})() :
|
|
(function() {
|
|
return function(fn) {
|
|
eomTasks.push(fn);
|
|
};
|
|
})();
|
|
|
|
var observedObjectCache = [];
|
|
|
|
function newObservedObject() {
|
|
var observer;
|
|
var object;
|
|
var discardRecords = false;
|
|
var first = true;
|
|
|
|
function callback(records) {
|
|
if (observer && observer.state_ === OPENED && !discardRecords)
|
|
observer.check_(records);
|
|
}
|
|
|
|
return {
|
|
open: function(obs) {
|
|
if (observer)
|
|
throw Error('ObservedObject in use');
|
|
|
|
if (!first)
|
|
Object.deliverChangeRecords(callback);
|
|
|
|
observer = obs;
|
|
first = false;
|
|
},
|
|
observe: function(obj, arrayObserve) {
|
|
object = obj;
|
|
if (arrayObserve)
|
|
Array.observe(object, callback);
|
|
else
|
|
Object.observe(object, callback);
|
|
},
|
|
deliver: function(discard) {
|
|
discardRecords = discard;
|
|
Object.deliverChangeRecords(callback);
|
|
discardRecords = false;
|
|
},
|
|
close: function() {
|
|
observer = undefined;
|
|
Object.unobserve(object, callback);
|
|
observedObjectCache.push(this);
|
|
}
|
|
};
|
|
}
|
|
|
|
/*
|
|
* The observedSet abstraction is a perf optimization which reduces the total
|
|
* number of Object.observe observations of a set of objects. The idea is that
|
|
* groups of Observers will have some object dependencies in common and this
|
|
* observed set ensures that each object in the transitive closure of
|
|
* dependencies is only observed once. The observedSet acts as a write barrier
|
|
* such that whenever any change comes through, all Observers are checked for
|
|
* changed values.
|
|
*
|
|
* Note that this optimization is explicitly moving work from setup-time to
|
|
* change-time.
|
|
*
|
|
* TODO(rafaelw): Implement "garbage collection". In order to move work off
|
|
* the critical path, when Observers are closed, their observed objects are
|
|
* not Object.unobserve(d). As a result, it's possible that if the observedSet
|
|
* is kept open, but some Observers have been closed, it could cause "leaks"
|
|
* (prevent otherwise collectable objects from being collected). At some
|
|
* point, we should implement incremental "gc" which keeps a list of
|
|
* observedSets which may need clean-up and does small amounts of cleanup on a
|
|
* timeout until all is clean.
|
|
*/
|
|
|
|
function getObservedObject(observer, object, arrayObserve) {
|
|
var dir = observedObjectCache.pop() || newObservedObject();
|
|
dir.open(observer);
|
|
dir.observe(object, arrayObserve);
|
|
return dir;
|
|
}
|
|
|
|
var observedSetCache = [];
|
|
|
|
function newObservedSet() {
|
|
var observerCount = 0;
|
|
var observers = [];
|
|
var objects = [];
|
|
var rootObj;
|
|
var rootObjProps;
|
|
|
|
function observe(obj, prop) {
|
|
if (!obj)
|
|
return;
|
|
|
|
if (obj === rootObj)
|
|
rootObjProps[prop] = true;
|
|
|
|
if (objects.indexOf(obj) < 0) {
|
|
objects.push(obj);
|
|
Object.observe(obj, callback);
|
|
}
|
|
|
|
observe(Object.getPrototypeOf(obj), prop);
|
|
}
|
|
|
|
function allRootObjNonObservedProps(recs) {
|
|
for (var i = 0; i < recs.length; i++) {
|
|
var rec = recs[i];
|
|
if (rec.object !== rootObj ||
|
|
rootObjProps[rec.name] ||
|
|
rec.type === 'setPrototype') {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function callback(recs) {
|
|
if (allRootObjNonObservedProps(recs))
|
|
return;
|
|
|
|
var observer;
|
|
for (var i = 0; i < observers.length; i++) {
|
|
observer = observers[i];
|
|
if (observer.state_ == OPENED) {
|
|
observer.iterateObjects_(observe);
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < observers.length; i++) {
|
|
observer = observers[i];
|
|
if (observer.state_ == OPENED) {
|
|
observer.check_();
|
|
}
|
|
}
|
|
}
|
|
|
|
var record = {
|
|
object: undefined,
|
|
objects: objects,
|
|
open: function(obs, object) {
|
|
if (!rootObj) {
|
|
rootObj = object;
|
|
rootObjProps = {};
|
|
}
|
|
|
|
observers.push(obs);
|
|
observerCount++;
|
|
obs.iterateObjects_(observe);
|
|
},
|
|
close: function(obs) {
|
|
observerCount--;
|
|
if (observerCount > 0) {
|
|
return;
|
|
}
|
|
|
|
for (var i = 0; i < objects.length; i++) {
|
|
Object.unobserve(objects[i], callback);
|
|
Observer.unobservedCount++;
|
|
}
|
|
|
|
observers.length = 0;
|
|
objects.length = 0;
|
|
rootObj = undefined;
|
|
rootObjProps = undefined;
|
|
observedSetCache.push(this);
|
|
}
|
|
};
|
|
|
|
return record;
|
|
}
|
|
|
|
var lastObservedSet;
|
|
|
|
function getObservedSet(observer, obj) {
|
|
if (!lastObservedSet || lastObservedSet.object !== obj) {
|
|
lastObservedSet = observedSetCache.pop() || newObservedSet();
|
|
lastObservedSet.object = obj;
|
|
}
|
|
lastObservedSet.open(observer, obj);
|
|
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.connect_();
|
|
this.state_ = OPENED;
|
|
return this.value_;
|
|
},
|
|
|
|
close: function() {
|
|
if (this.state_ != OPENED)
|
|
return;
|
|
|
|
removeFromAll(this);
|
|
this.disconnect_();
|
|
this.value_ = undefined;
|
|
this.callback_ = undefined;
|
|
this.target_ = undefined;
|
|
this.state_ = CLOSED;
|
|
},
|
|
|
|
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 = hasObserve && (function() {
|
|
try {
|
|
eval('%RunMicrotasks()');
|
|
return true;
|
|
} catch (ex) {
|
|
return false;
|
|
}
|
|
})();
|
|
|
|
global.Platform = global.Platform || {};
|
|
|
|
global.Platform.performMicrotaskCheckpoint = function() {
|
|
if (runningMicrotaskCheckpoint)
|
|
return;
|
|
|
|
if (hasDebugForceFullDelivery) {
|
|
eval('%RunMicrotasks()');
|
|
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_ = getPath(path);
|
|
this.directObserver_ = undefined;
|
|
}
|
|
|
|
PathObserver.prototype = createObject({
|
|
__proto__: Observer.prototype,
|
|
|
|
get path() {
|
|
return this.path_;
|
|
},
|
|
|
|
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, this]);
|
|
return true;
|
|
},
|
|
|
|
setValue: function(newValue) {
|
|
if (this.path_)
|
|
this.path_.setValueFrom(this.object_, newValue);
|
|
}
|
|
});
|
|
|
|
function CompoundObserver(reportChangesOnOpen) {
|
|
Observer.call(this);
|
|
|
|
this.reportChangesOnOpen_ = reportChangesOnOpen;
|
|
this.value_ = [];
|
|
this.directObserver_ = undefined;
|
|
this.observed_ = [];
|
|
}
|
|
|
|
var observerSentinel = {};
|
|
|
|
CompoundObserver.prototype = createObject({
|
|
__proto__: Observer.prototype,
|
|
|
|
connect_: function() {
|
|
if (hasObserve) {
|
|
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 (needsDirectObserver)
|
|
this.directObserver_ = getObservedSet(this, object);
|
|
}
|
|
|
|
this.check_(undefined, !this.reportChangesOnOpen_);
|
|
},
|
|
|
|
disconnect_: 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;
|
|
this.value_.length = 0;
|
|
|
|
if (this.directObserver_) {
|
|
this.directObserver_.close(this);
|
|
this.directObserver_ = undefined;
|
|
}
|
|
},
|
|
|
|
addPath: function(object, path) {
|
|
if (this.state_ != UNOPENED && this.state_ != RESETTING)
|
|
throw Error('Cannot add paths once started.');
|
|
|
|
var path = getPath(path);
|
|
this.observed_.push(object, path);
|
|
if (!this.reportChangesOnOpen_)
|
|
return;
|
|
var index = this.observed_.length / 2 - 1;
|
|
this.value_[index] = path.getValueFrom(object);
|
|
},
|
|
|
|
addObserver: function(observer) {
|
|
if (this.state_ != UNOPENED && this.state_ != RESETTING)
|
|
throw Error('Cannot add observers once started.');
|
|
|
|
this.observed_.push(observerSentinel, observer);
|
|
if (!this.reportChangesOnOpen_)
|
|
return;
|
|
var index = this.observed_.length / 2 - 1;
|
|
this.value_[index] = observer.open(this.deliver, this);
|
|
},
|
|
|
|
startReset: function() {
|
|
if (this.state_ != OPENED)
|
|
throw Error('Can only reset while open');
|
|
|
|
this.state_ = RESETTING;
|
|
this.disconnect_();
|
|
},
|
|
|
|
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 object = this.observed_[i];
|
|
var path = this.observed_[i+1];
|
|
var value;
|
|
if (object === observerSentinel) {
|
|
var observable = path;
|
|
value = this.state_ === UNOPENED ?
|
|
observable.open(this.deliver, this) :
|
|
observable.discardChanges();
|
|
} else {
|
|
value = path.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 = {
|
|
add: true,
|
|
update: true,
|
|
delete: 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 == 'update')
|
|
continue;
|
|
|
|
if (record.type == 'add') {
|
|
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 'splice':
|
|
mergeSplice(splices, record.index, record.removed.slice(), record.addedCount);
|
|
break;
|
|
case 'add':
|
|
case 'update':
|
|
case 'delete':
|
|
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.observerSentinel_ = observerSentinel; // for testing.
|
|
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;
|
|
})(typeof global !== 'undefined' && global && typeof module !== 'undefined' && module ? global : this || window);
|
|
|
|
// select ShadowDOM impl
|
|
if (Platform.flags.shadow) {
|
|
|
|
// Copyright 2012 The Polymer Authors. All rights reserved.
|
|
// Use of this source code is goverened by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
window.ShadowDOMPolyfill = {};
|
|
|
|
(function(scope) {
|
|
'use strict';
|
|
|
|
var constructorTable = new WeakMap();
|
|
var nativePrototypeTable = new WeakMap();
|
|
var wrappers = Object.create(null);
|
|
|
|
function detectEval() {
|
|
// Don't test for eval if we're running in a Chrome App environment.
|
|
// We check for APIs set that only exist in a Chrome App context.
|
|
if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
var f = new Function('return true;');
|
|
return f();
|
|
} catch (ex) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
var hasEval = detectEval();
|
|
|
|
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) {
|
|
var names = getOwnPropertyNames(from);
|
|
for (var i = 0; i < names.length; i++) {
|
|
var name = names[i];
|
|
defineProperty(to, name, getOwnPropertyDescriptor(from, name));
|
|
}
|
|
return to;
|
|
};
|
|
|
|
function mixinStatics(to, from) {
|
|
var names = getOwnPropertyNames(from);
|
|
for (var i = 0; i < names.length; i++) {
|
|
var name = names[i];
|
|
switch (name) {
|
|
case 'arguments':
|
|
case 'caller':
|
|
case 'length':
|
|
case 'name':
|
|
case 'prototype':
|
|
case 'toString':
|
|
continue;
|
|
}
|
|
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];
|
|
}
|
|
}
|
|
|
|
var nonEnumerableDataDescriptor = {
|
|
value: undefined,
|
|
configurable: true,
|
|
enumerable: false,
|
|
writable: true
|
|
};
|
|
|
|
function defineNonEnumerableDataProperty(object, name, value) {
|
|
nonEnumerableDataDescriptor.value = value;
|
|
defineProperty(object, name, nonEnumerableDataDescriptor);
|
|
}
|
|
|
|
// 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);
|
|
|
|
defineNonEnumerableDataProperty(
|
|
wrapperPrototype, 'constructor', wrapperConstructor);
|
|
// Set it again. Some VMs optimizes objects that are used as prototypes.
|
|
wrapperConstructor.prototype = wrapperPrototype;
|
|
}
|
|
|
|
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);
|
|
}
|
|
var p = Object.create(superWrapperConstructor.prototype);
|
|
p.constructor = GeneratedWrapper;
|
|
GeneratedWrapper.prototype = p;
|
|
|
|
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;
|
|
}
|
|
|
|
var getterDescriptor = {
|
|
get: undefined,
|
|
configurable: true,
|
|
enumerable: true
|
|
};
|
|
|
|
function defineGetter(constructor, name, getter) {
|
|
getterDescriptor.get = getter;
|
|
defineProperty(constructor.prototype, name, getterDescriptor);
|
|
}
|
|
|
|
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 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';
|
|
|
|
/**
|
|
* A tree scope represents the root of a tree. All nodes in a tree point to
|
|
* the same TreeScope object. The tree scope of a node get set the first time
|
|
* it is accessed or when a node is added or remove to a tree.
|
|
*
|
|
* The root is a Node that has no parent.
|
|
*
|
|
* The parent is another TreeScope. For ShadowRoots, it is the TreeScope of
|
|
* the host of the ShadowRoot.
|
|
*
|
|
* @param {!Node} root
|
|
* @param {TreeScope} parent
|
|
* @constructor
|
|
*/
|
|
function TreeScope(root, parent) {
|
|
/** @type {!Node} */
|
|
this.root = root;
|
|
|
|
/** @type {TreeScope} */
|
|
this.parent = parent;
|
|
}
|
|
|
|
TreeScope.prototype = {
|
|
get renderer() {
|
|
if (this.root instanceof scope.wrappers.ShadowRoot) {
|
|
return scope.getRendererForHost(this.root.host);
|
|
}
|
|
return null;
|
|
},
|
|
|
|
contains: function(treeScope) {
|
|
for (; treeScope; treeScope = treeScope.parent) {
|
|
if (treeScope === this)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
function setTreeScope(node, treeScope) {
|
|
if (node.treeScope_ !== treeScope) {
|
|
node.treeScope_ = treeScope;
|
|
for (var sr = node.shadowRoot; sr; sr = sr.olderShadowRoot) {
|
|
sr.treeScope_.parent = treeScope;
|
|
}
|
|
for (var child = node.firstChild; child; child = child.nextSibling) {
|
|
setTreeScope(child, treeScope);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getTreeScope(node) {
|
|
if (node instanceof scope.wrappers.Window) {
|
|
debugger;
|
|
}
|
|
|
|
if (node.treeScope_)
|
|
return node.treeScope_;
|
|
var parent = node.parentNode;
|
|
var treeScope;
|
|
if (parent)
|
|
treeScope = getTreeScope(parent);
|
|
else
|
|
treeScope = new TreeScope(node, null);
|
|
return node.treeScope_ = treeScope;
|
|
}
|
|
|
|
scope.TreeScope = TreeScope;
|
|
scope.getTreeScope = getTreeScope;
|
|
scope.setTreeScope = setTreeScope;
|
|
|
|
})(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 getTreeScope = scope.getTreeScope;
|
|
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 rootOfNode(node) {
|
|
return getTreeScope(node).root;
|
|
}
|
|
|
|
// http://w3c.github.io/webcomponents/spec/shadow/#event-paths
|
|
function getEventPath(node, event) {
|
|
var path = [];
|
|
var current = node;
|
|
path.push(current);
|
|
while (current) {
|
|
// 4.1.
|
|
var destinationInsertionPoints = getDestinationInsertionPoints(current);
|
|
if (destinationInsertionPoints && destinationInsertionPoints.length > 0) {
|
|
// 4.1.1
|
|
for (var i = 0; i < destinationInsertionPoints.length; i++) {
|
|
var insertionPoint = destinationInsertionPoints[i];
|
|
// 4.1.1.1
|
|
if (isShadowInsertionPoint(insertionPoint)) {
|
|
var shadowRoot = rootOfNode(insertionPoint);
|
|
// 4.1.1.1.2
|
|
var olderShadowRoot = shadowRoot.olderShadowRoot;
|
|
if (olderShadowRoot)
|
|
path.push(olderShadowRoot);
|
|
}
|
|
|
|
// 4.1.1.2
|
|
path.push(insertionPoint);
|
|
}
|
|
|
|
// 4.1.2
|
|
current = destinationInsertionPoints[
|
|
destinationInsertionPoints.length - 1];
|
|
|
|
// 4.2
|
|
} else {
|
|
if (isShadowRoot(current)) {
|
|
if (inSameTree(node, current) && eventMustBeStopped(event)) {
|
|
// Stop this algorithm
|
|
break;
|
|
}
|
|
current = current.host;
|
|
path.push(current);
|
|
|
|
// 4.2.2
|
|
} else {
|
|
current = current.parentNode;
|
|
if (current)
|
|
path.push(current);
|
|
}
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
// http://w3c.github.io/webcomponents/spec/shadow/#dfn-events-always-stopped
|
|
function eventMustBeStopped(event) {
|
|
if (!event)
|
|
return false;
|
|
|
|
switch (event.type) {
|
|
case 'abort':
|
|
case 'error':
|
|
case 'select':
|
|
case 'change':
|
|
case 'load':
|
|
case 'reset':
|
|
case 'resize':
|
|
case 'scroll':
|
|
case 'selectstart':
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// http://w3c.github.io/webcomponents/spec/shadow/#dfn-shadow-insertion-point
|
|
function isShadowInsertionPoint(node) {
|
|
return node instanceof HTMLShadowElement;
|
|
// and make sure that there are no shadow precing this?
|
|
// and that there is no content ancestor?
|
|
}
|
|
|
|
function getDestinationInsertionPoints(node) {
|
|
return scope.getDestinationInsertionPoints(node);
|
|
}
|
|
|
|
// http://w3c.github.io/webcomponents/spec/shadow/#event-retargeting
|
|
function eventRetargetting(path, currentTarget) {
|
|
if (path.length === 0)
|
|
return currentTarget;
|
|
|
|
// The currentTarget might be the window object. Use its document for the
|
|
// purpose of finding the retargetted node.
|
|
if (currentTarget instanceof wrappers.Window)
|
|
currentTarget = currentTarget.document;
|
|
|
|
var currentTargetTree = getTreeScope(currentTarget);
|
|
var originalTarget = path[0];
|
|
var originalTargetTree = getTreeScope(originalTarget);
|
|
var relativeTargetTree =
|
|
lowestCommonInclusiveAncestor(currentTargetTree, originalTargetTree);
|
|
|
|
for (var i = 0; i < path.length; i++) {
|
|
var node = path[i];
|
|
if (getTreeScope(node) === relativeTargetTree)
|
|
return node;
|
|
}
|
|
|
|
return path[path.length - 1];
|
|
}
|
|
|
|
function getTreeScopeAncestors(treeScope) {
|
|
var ancestors = [];
|
|
for (;treeScope; treeScope = treeScope.parent) {
|
|
ancestors.push(treeScope);
|
|
}
|
|
return ancestors;
|
|
}
|
|
|
|
function lowestCommonInclusiveAncestor(tsA, tsB) {
|
|
var ancestorsA = getTreeScopeAncestors(tsA);
|
|
var ancestorsB = getTreeScopeAncestors(tsB);
|
|
|
|
var result = null;
|
|
while (ancestorsA.length > 0 && ancestorsB.length > 0) {
|
|
var a = ancestorsA.pop();
|
|
var b = ancestorsB.pop();
|
|
if (a === b)
|
|
result = a;
|
|
else
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function getTreeScopeRoot(ts) {
|
|
if (!ts.parent)
|
|
return ts;
|
|
return getTreeScopeRoot(ts.parent);
|
|
}
|
|
|
|
function relatedTargetResolution(event, currentTarget, relatedTarget) {
|
|
// In case the current target is a window use its document for the purpose
|
|
// of retargetting the related target.
|
|
if (currentTarget instanceof wrappers.Window)
|
|
currentTarget = currentTarget.document;
|
|
|
|
var currentTargetTree = getTreeScope(currentTarget);
|
|
var relatedTargetTree = getTreeScope(relatedTarget);
|
|
|
|
var relatedTargetEventPath = getEventPath(relatedTarget, event);
|
|
|
|
var lowestCommonAncestorTree;
|
|
|
|
// 4
|
|
var lowestCommonAncestorTree =
|
|
lowestCommonInclusiveAncestor(currentTargetTree, relatedTargetTree);
|
|
|
|
// 5
|
|
if (!lowestCommonAncestorTree)
|
|
lowestCommonAncestorTree = relatedTargetTree.root;
|
|
|
|
// 6
|
|
for (var commonAncestorTree = lowestCommonAncestorTree;
|
|
commonAncestorTree;
|
|
commonAncestorTree = commonAncestorTree.parent) {
|
|
// 6.1
|
|
var adjustedRelatedTarget;
|
|
for (var i = 0; i < relatedTargetEventPath.length; i++) {
|
|
var node = relatedTargetEventPath[i];
|
|
if (getTreeScope(node) === commonAncestorTree)
|
|
return node;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function inSameTree(a, b) {
|
|
return getTreeScope(a) === getTreeScope(b);
|
|
}
|
|
|
|
var NONE = 0;
|
|
var CAPTURING_PHASE = 1;
|
|
var AT_TARGET = 2;
|
|
var BUBBLING_PHASE = 3;
|
|
|
|
// pendingError is used to rethrow the first error we got during an event
|
|
// dispatch. The browser actually reports all errors but to do that we would
|
|
// need to rethrow the error asynchronously.
|
|
var pendingError;
|
|
|
|
function dispatchOriginalEvent(originalEvent) {
|
|
// Make sure this event is only dispatched once.
|
|
if (handledEventsTable.get(originalEvent))
|
|
return;
|
|
handledEventsTable.set(originalEvent, true);
|
|
dispatchEvent(wrap(originalEvent), wrap(originalEvent.target));
|
|
if (pendingError) {
|
|
var err = pendingError;
|
|
pendingError = null;
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#events-and-the-window-object
|
|
// All events dispatched on Nodes with a default view, except load events,
|
|
// should propagate to the Window.
|
|
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#the-end
|
|
var overrideTarget;
|
|
var win;
|
|
var type = event.type;
|
|
|
|
// Should really be not cancelable too but since Firefox has a bug there
|
|
// we skip that check.
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=999456
|
|
if (type === 'load' && !event.bubbles) {
|
|
var doc = originalWrapperTarget;
|
|
if (doc instanceof wrappers.Document && (win = doc.defaultView)) {
|
|
overrideTarget = doc;
|
|
eventPath = [];
|
|
}
|
|
}
|
|
|
|
if (!eventPath) {
|
|
if (originalWrapperTarget instanceof wrappers.Window) {
|
|
win = originalWrapperTarget;
|
|
eventPath = [];
|
|
} else {
|
|
eventPath = getEventPath(originalWrapperTarget, event);
|
|
|
|
if (event.type !== 'load') {
|
|
var doc = eventPath[eventPath.length - 1];
|
|
if (doc instanceof wrappers.Document)
|
|
win = doc.defaultView;
|
|
}
|
|
}
|
|
}
|
|
|
|
eventPathTable.set(event, eventPath);
|
|
|
|
if (dispatchCapturing(event, eventPath, win, overrideTarget)) {
|
|
if (dispatchAtTarget(event, eventPath, win, overrideTarget)) {
|
|
dispatchBubbling(event, eventPath, win, overrideTarget);
|
|
}
|
|
}
|
|
|
|
eventPhaseTable.set(event, NONE);
|
|
currentTargetTable.delete(event, null);
|
|
currentlyDispatchingEvents.delete(event);
|
|
|
|
return event.defaultPrevented;
|
|
}
|
|
|
|
function dispatchCapturing(event, eventPath, win, overrideTarget) {
|
|
var phase = CAPTURING_PHASE;
|
|
|
|
if (win) {
|
|
if (!invoke(win, event, phase, eventPath, overrideTarget))
|
|
return false;
|
|
}
|
|
|
|
for (var i = eventPath.length - 1; i > 0; i--) {
|
|
if (!invoke(eventPath[i], event, phase, eventPath, overrideTarget))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function dispatchAtTarget(event, eventPath, win, overrideTarget) {
|
|
var phase = AT_TARGET;
|
|
var currentTarget = eventPath[0] || win;
|
|
return invoke(currentTarget, event, phase, eventPath, overrideTarget);
|
|
}
|
|
|
|
function dispatchBubbling(event, eventPath, win, overrideTarget) {
|
|
var phase = BUBBLING_PHASE;
|
|
for (var i = 1; i < eventPath.length; i++) {
|
|
if (!invoke(eventPath[i], event, phase, eventPath, overrideTarget))
|
|
return;
|
|
}
|
|
|
|
if (win && eventPath.length > 0) {
|
|
invoke(win, event, phase, eventPath, overrideTarget);
|
|
}
|
|
}
|
|
|
|
function invoke(currentTarget, event, phase, eventPath, overrideTarget) {
|
|
var listeners = listenersTable.get(currentTarget);
|
|
if (!listeners)
|
|
return true;
|
|
|
|
var target = overrideTarget || eventRetargetting(eventPath, currentTarget);
|
|
|
|
if (target === currentTarget) {
|
|
if (phase === CAPTURING_PHASE)
|
|
return true;
|
|
|
|
if (phase === BUBBLING_PHASE)
|
|
phase = AT_TARGET;
|
|
|
|
} else if (phase === BUBBLING_PHASE && !event.bubbles) {
|
|
return true;
|
|
}
|
|
|
|
if ('relatedTarget' in event) {
|
|
var originalEvent = unwrap(event);
|
|
var unwrappedRelatedTarget = originalEvent.relatedTarget;
|
|
|
|
// 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 (unwrappedRelatedTarget) {
|
|
// In IE we can get objects that are not EventTargets at this point.
|
|
// Safari does not have an EventTarget interface so revert to checking
|
|
// for addEventListener as an approximation.
|
|
if (unwrappedRelatedTarget instanceof Object &&
|
|
unwrappedRelatedTarget.addEventListener) {
|
|
var relatedTarget = wrap(unwrappedRelatedTarget);
|
|
|
|
var adjusted =
|
|
relatedTargetResolution(event, currentTarget, relatedTarget);
|
|
if (adjusted === target)
|
|
return true;
|
|
} else {
|
|
adjusted = null;
|
|
}
|
|
relatedTargetTable.set(event, adjusted);
|
|
}
|
|
}
|
|
|
|
eventPhaseTable.set(event, phase);
|
|
var type = event.type;
|
|
|
|
var anyRemoved = false;
|
|
// targetTable.set(event, target);
|
|
targetTable.set(event, target);
|
|
currentTargetTable.set(event, currentTarget);
|
|
|
|
// Keep track of the invoke depth so that we only clean up the removed
|
|
// listeners if we are in the outermost invoke.
|
|
listeners.depth++;
|
|
|
|
for (var i = 0, len = listeners.length; i < len; i++) {
|
|
var listener = listeners[i];
|
|
if (listener.removed) {
|
|
anyRemoved = true;
|
|
continue;
|
|
}
|
|
|
|
if (listener.type !== type ||
|
|
!listener.capture && phase === CAPTURING_PHASE ||
|
|
listener.capture && phase === 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 (!pendingError)
|
|
pendingError = ex;
|
|
}
|
|
}
|
|
|
|
listeners.depth--;
|
|
|
|
if (anyRemoved && listeners.depth === 0) {
|
|
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) {
|
|
var impl = type;
|
|
if (!OriginalBeforeUnloadEvent && impl.type === 'beforeunload')
|
|
return new BeforeUnloadEvent(impl);
|
|
this.impl = impl;
|
|
} 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 eventPath = eventPathTable.get(this);
|
|
if (!eventPath)
|
|
return [];
|
|
// TODO(arv): Event path should contain window.
|
|
return eventPath.slice();
|
|
},
|
|
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() {
|
|
var relatedTarget = relatedTargetTable.get(this);
|
|
// relatedTarget can be null.
|
|
if (relatedTarget !== undefined)
|
|
return relatedTarget;
|
|
return 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');
|
|
}
|
|
|
|
// Safari 7 does not yet have BeforeUnloadEvent.
|
|
// https://bugs.webkit.org/show_bug.cgi?id=120849
|
|
var OriginalBeforeUnloadEvent = window.BeforeUnloadEvent;
|
|
|
|
function BeforeUnloadEvent(impl) {
|
|
Event.call(this, impl);
|
|
}
|
|
BeforeUnloadEvent.prototype = Object.create(Event.prototype);
|
|
mixin(BeforeUnloadEvent.prototype, {
|
|
get returnValue() {
|
|
return this.impl.returnValue;
|
|
},
|
|
set returnValue(v) {
|
|
this.impl.returnValue = v;
|
|
}
|
|
});
|
|
|
|
if (OriginalBeforeUnloadEvent)
|
|
registerWrapper(OriginalBeforeUnloadEvent, BeforeUnloadEvent);
|
|
|
|
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 = [];
|
|
listeners.depth = 0;
|
|
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));
|
|
if (!element)
|
|
return null;
|
|
var path = getEventPath(element, null);
|
|
|
|
// scope the path to this TreeScope
|
|
var idx = path.lastIndexOf(self);
|
|
if (idx == -1)
|
|
return null;
|
|
else
|
|
path = path.slice(0, idx);
|
|
|
|
// TODO(dfreedm): pass idx to eventRetargetting to avoid array copy
|
|
return eventRetargetting(path, self);
|
|
}
|
|
|
|
/**
|
|
* 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.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 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 UIEvent = scope.wrappers.UIEvent;
|
|
var mixin = scope.mixin;
|
|
var registerWrapper = scope.registerWrapper;
|
|
var unwrap = scope.unwrap;
|
|
var wrap = scope.wrap;
|
|
|
|
// TouchEvent is WebKit/Blink only.
|
|
var OriginalTouchEvent = window.TouchEvent;
|
|
if (!OriginalTouchEvent)
|
|
return;
|
|
|
|
var nativeEvent;
|
|
try {
|
|
nativeEvent = document.createEvent('TouchEvent');
|
|
} catch (ex) {
|
|
// In Chrome creating a TouchEvent fails if the feature is not turned on
|
|
// which it isn't on desktop Chrome.
|
|
return;
|
|
}
|
|
|
|
var nonEnumDescriptor = {enumerable: false};
|
|
|
|
function nonEnum(obj, prop) {
|
|
Object.defineProperty(obj, prop, nonEnumDescriptor);
|
|
}
|
|
|
|
function Touch(impl) {
|
|
this.impl = impl;
|
|
}
|
|
|
|
Touch.prototype = {
|
|
get target() {
|
|
return wrap(this.impl.target);
|
|
}
|
|
};
|
|
|
|
var descr = {
|
|
configurable: true,
|
|
enumerable: true,
|
|
get: null
|
|
};
|
|
|
|
[
|
|
'clientX',
|
|
'clientY',
|
|
'screenX',
|
|
'screenY',
|
|
'pageX',
|
|
'pageY',
|
|
'identifier',
|
|
'webkitRadiusX',
|
|
'webkitRadiusY',
|
|
'webkitRotationAngle',
|
|
'webkitForce'
|
|
].forEach(function(name) {
|
|
descr.get = function() {
|
|
return this.impl[name];
|
|
};
|
|
Object.defineProperty(Touch.prototype, name, descr);
|
|
});
|
|
|
|
function TouchList() {
|
|
this.length = 0;
|
|
nonEnum(this, 'length');
|
|
}
|
|
|
|
TouchList.prototype = {
|
|
item: function(index) {
|
|
return this[index];
|
|
}
|
|
};
|
|
|
|
function wrapTouchList(nativeTouchList) {
|
|
var list = new TouchList();
|
|
for (var i = 0; i < nativeTouchList.length; i++) {
|
|
list[i] = new Touch(nativeTouchList[i]);
|
|
}
|
|
list.length = i;
|
|
return list;
|
|
}
|
|
|
|
function TouchEvent(impl) {
|
|
UIEvent.call(this, impl);
|
|
}
|
|
|
|
TouchEvent.prototype = Object.create(UIEvent.prototype);
|
|
|
|
mixin(TouchEvent.prototype, {
|
|
get touches() {
|
|
return wrapTouchList(unwrap(this).touches);
|
|
},
|
|
|
|
get targetTouches() {
|
|
return wrapTouchList(unwrap(this).targetTouches);
|
|
},
|
|
|
|
get changedTouches() {
|
|
return wrapTouchList(unwrap(this).changedTouches);
|
|
},
|
|
|
|
initTouchEvent: function() {
|
|
// The only way to use this is to reuse the TouchList from an existing
|
|
// TouchEvent. Since this is WebKit/Blink proprietary API we will not
|
|
// implement this until someone screams.
|
|
throw new Error('Not implemented');
|
|
}
|
|
});
|
|
|
|
registerWrapper(OriginalTouchEvent, TouchEvent, nativeEvent);
|
|
|
|
scope.wrappers.Touch = Touch;
|
|
scope.wrappers.TouchEvent = TouchEvent;
|
|
scope.wrappers.TouchList = TouchList;
|
|
|
|
})(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;
|
|
|
|
var nonEnumDescriptor = {enumerable: false};
|
|
|
|
function nonEnum(obj, prop) {
|
|
Object.defineProperty(obj, prop, nonEnumDescriptor);
|
|
}
|
|
|
|
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 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';
|
|
|
|
// TODO(arv): Implement.
|
|
|
|
scope.wrapHTMLCollection = scope.wrapNodeList;
|
|
scope.wrappers.HTMLCollection = scope.wrappers.NodeList;
|
|
|
|
})(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 TreeScope = scope.TreeScope;
|
|
var assert = scope.assert;
|
|
var defineWrapGetter = scope.defineWrapGetter;
|
|
var enqueueMutation = scope.enqueueMutation;
|
|
var getTreeScope = scope.getTreeScope;
|
|
var isWrapper = scope.isWrapper;
|
|
var mixin = scope.mixin;
|
|
var registerTransientObservers = scope.registerTransientObservers;
|
|
var registerWrapper = scope.registerWrapper;
|
|
var setTreeScope = scope.setTreeScope;
|
|
var unwrap = scope.unwrap;
|
|
var unwrapIfNeeded = scope.unwrapIfNeeded;
|
|
var wrap = scope.wrap;
|
|
var wrapIfNeeded = scope.wrapIfNeeded;
|
|
var wrappers = scope.wrappers;
|
|
|
|
function assertIsNodeWrapper(node) {
|
|
assert(node instanceof Node);
|
|
}
|
|
|
|
function createOneElementNodeList(node) {
|
|
var nodes = new NodeList();
|
|
nodes[0] = node;
|
|
nodes.length = 1;
|
|
return nodes;
|
|
}
|
|
|
|
var surpressMutations = false;
|
|
|
|
/**
|
|
* Called before node is inserted into a node to enqueue its removal from its
|
|
* old parent.
|
|
* @param {!Node} node The node that is about to be removed.
|
|
* @param {!Node} parent The parent node that the node is being removed from.
|
|
* @param {!NodeList} nodes The collected nodes.
|
|
*/
|
|
function enqueueRemovalForInsertedNodes(node, parent, nodes) {
|
|
enqueueMutation(parent, 'childList', {
|
|
removedNodes: nodes,
|
|
previousSibling: node.previousSibling,
|
|
nextSibling: node.nextSibling
|
|
});
|
|
}
|
|
|
|
function enqueueRemovalForInsertedDocumentFragment(df, nodes) {
|
|
enqueueMutation(df, 'childList', {
|
|
removedNodes: nodes
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Collects nodes from a DocumentFragment or a Node for removal followed
|
|
* by an insertion.
|
|
*
|
|
* This updates the internal pointers for node, previousNode and nextNode.
|
|
*/
|
|
function collectNodes(node, parentNode, previousNode, nextNode) {
|
|
if (node instanceof DocumentFragment) {
|
|
var nodes = collectNodesForDocumentFragment(node);
|
|
|
|
// The extra loop is to work around bugs with DocumentFragments in IE.
|
|
surpressMutations = true;
|
|
for (var i = nodes.length - 1; i >= 0; i--) {
|
|
node.removeChild(nodes[i]);
|
|
nodes[i].parentNode_ = parentNode;
|
|
}
|
|
surpressMutations = false;
|
|
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
nodes[i].previousSibling_ = nodes[i - 1] || previousNode;
|
|
nodes[i].nextSibling_ = nodes[i + 1] || nextNode;
|
|
}
|
|
|
|
if (previousNode)
|
|
previousNode.nextSibling_ = nodes[0];
|
|
if (nextNode)
|
|
nextNode.previousSibling_ = nodes[nodes.length - 1];
|
|
|
|
return nodes;
|
|
}
|
|
|
|
var nodes = createOneElementNodeList(node);
|
|
var oldParent = node.parentNode;
|
|
if (oldParent) {
|
|
// This will enqueue the mutation record for the removal as needed.
|
|
oldParent.removeChild(node);
|
|
}
|
|
|
|
node.parentNode_ = parentNode;
|
|
node.previousSibling_ = previousNode;
|
|
node.nextSibling_ = nextNode;
|
|
if (previousNode)
|
|
previousNode.nextSibling_ = node;
|
|
if (nextNode)
|
|
nextNode.previousSibling_ = node;
|
|
|
|
return nodes;
|
|
}
|
|
|
|
function collectNodesNative(node) {
|
|
if (node instanceof DocumentFragment)
|
|
return collectNodesForDocumentFragment(node);
|
|
|
|
var nodes = createOneElementNodeList(node);
|
|
var oldParent = node.parentNode;
|
|
if (oldParent)
|
|
enqueueRemovalForInsertedNodes(node, oldParent, nodes);
|
|
return nodes;
|
|
}
|
|
|
|
function collectNodesForDocumentFragment(node) {
|
|
var nodes = new NodeList();
|
|
var i = 0;
|
|
for (var child = node.firstChild; child; child = child.nextSibling) {
|
|
nodes[i++] = child;
|
|
}
|
|
nodes.length = i;
|
|
enqueueRemovalForInsertedDocumentFragment(node, nodes);
|
|
return nodes;
|
|
}
|
|
|
|
function snapshotNodeList(nodeList) {
|
|
// NodeLists are not live at the moment so just return the same object.
|
|
return nodeList;
|
|
}
|
|
|
|
// http://dom.spec.whatwg.org/#node-is-inserted
|
|
function nodeWasAdded(node, treeScope) {
|
|
setTreeScope(node, treeScope);
|
|
node.nodeIsInserted_();
|
|
}
|
|
|
|
function nodesWereAdded(nodes, parent) {
|
|
var treeScope = getTreeScope(parent);
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
nodeWasAdded(nodes[i], treeScope);
|
|
}
|
|
}
|
|
|
|
// http://dom.spec.whatwg.org/#node-is-removed
|
|
function nodeWasRemoved(node) {
|
|
setTreeScope(node, new TreeScope(node, null));
|
|
}
|
|
|
|
function nodesWereRemoved(nodes) {
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
nodeWasRemoved(nodes[i]);
|
|
}
|
|
}
|
|
|
|
function ensureSameOwnerDocument(parent, child) {
|
|
var ownerDoc = parent.nodeType === Node.DOCUMENT_NODE ?
|
|
parent : parent.ownerDocument;
|
|
if (ownerDoc !== child.ownerDocument)
|
|
ownerDoc.adoptNode(child);
|
|
}
|
|
|
|
function adoptNodesIfNeeded(owner, nodes) {
|
|
if (!nodes.length)
|
|
return;
|
|
|
|
var ownerDoc = owner.ownerDocument;
|
|
|
|
// All nodes have the same ownerDocument when we get here.
|
|
if (ownerDoc === nodes[0].ownerDocument)
|
|
return;
|
|
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
scope.adoptNodeNoRemove(nodes[i], ownerDoc);
|
|
}
|
|
}
|
|
|
|
function unwrapNodesForInsertion(owner, nodes) {
|
|
adoptNodesIfNeeded(owner, nodes);
|
|
var length = nodes.length;
|
|
|
|
if (length === 1)
|
|
return unwrap(nodes[0]);
|
|
|
|
var df = unwrap(owner.ownerDocument.createDocumentFragment());
|
|
for (var i = 0; i < length; i++) {
|
|
df.appendChild(unwrap(nodes[i]));
|
|
}
|
|
return df;
|
|
}
|
|
|
|
function clearChildNodes(wrapper) {
|
|
if (wrapper.firstChild_ !== undefined) {
|
|
var child = wrapper.firstChild_;
|
|
while (child) {
|
|
var tmp = child;
|
|
child = child.nextSibling_;
|
|
tmp.parentNode_ = tmp.previousSibling_ = tmp.nextSibling_ = undefined;
|
|
}
|
|
}
|
|
wrapper.firstChild_ = wrapper.lastChild_ = undefined;
|
|
}
|
|
|
|
function removeAllChildNodes(wrapper) {
|
|
if (wrapper.invalidateShadowRenderer()) {
|
|
var childWrapper = wrapper.firstChild;
|
|
while (childWrapper) {
|
|
assert(childWrapper.parentNode === wrapper);
|
|
var nextSibling = childWrapper.nextSibling;
|
|
var childNode = unwrap(childWrapper);
|
|
var parentNode = childNode.parentNode;
|
|
if (parentNode)
|
|
originalRemoveChild.call(parentNode, childNode);
|
|
childWrapper.previousSibling_ = childWrapper.nextSibling_ =
|
|
childWrapper.parentNode_ = null;
|
|
childWrapper = nextSibling;
|
|
}
|
|
wrapper.firstChild_ = wrapper.lastChild_ = null;
|
|
} else {
|
|
var node = unwrap(wrapper);
|
|
var child = node.firstChild;
|
|
var nextSibling;
|
|
while (child) {
|
|
nextSibling = child.nextSibling;
|
|
originalRemoveChild.call(node, child);
|
|
child = nextSibling;
|
|
}
|
|
}
|
|
}
|
|
|
|
function invalidateParent(node) {
|
|
var p = node.parentNode;
|
|
return p && p.invalidateShadowRenderer();
|
|
}
|
|
|
|
function cleanupNodes(nodes) {
|
|
for (var i = 0, n; i < nodes.length; i++) {
|
|
n = nodes[i];
|
|
n.parentNode.removeChild(n);
|
|
}
|
|
}
|
|
|
|
var originalImportNode = document.importNode;
|
|
var originalCloneNode = window.Node.prototype.cloneNode;
|
|
|
|
function cloneNode(node, deep, opt_doc) {
|
|
var clone;
|
|
if (opt_doc)
|
|
clone = wrap(originalImportNode.call(opt_doc, node.impl, false));
|
|
else
|
|
clone = wrap(originalCloneNode.call(node.impl, false));
|
|
|
|
if (deep) {
|
|
for (var child = node.firstChild; child; child = child.nextSibling) {
|
|
clone.appendChild(cloneNode(child, true, opt_doc));
|
|
}
|
|
|
|
if (node instanceof wrappers.HTMLTemplateElement) {
|
|
var cloneContent = clone.content;
|
|
for (var child = node.content.firstChild;
|
|
child;
|
|
child = child.nextSibling) {
|
|
cloneContent.appendChild(cloneNode(child, true, opt_doc));
|
|
}
|
|
}
|
|
}
|
|
// TODO(arv): Some HTML elements also clone other data like value.
|
|
return clone;
|
|
}
|
|
|
|
function contains(self, child) {
|
|
if (!child || getTreeScope(self) !== getTreeScope(child))
|
|
return false;
|
|
|
|
for (var node = child; node; node = node.parentNode) {
|
|
if (node === self)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
|
|
this.treeScope_ = 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];
|
|
if (this.firstChild_ === undefined)
|
|
this.firstChild_ = this.firstChild;
|
|
}
|
|
|
|
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, this);
|
|
|
|
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, this);
|
|
|
|
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, this);
|
|
},
|
|
|
|
get childNodes() {
|
|
var wrapperList = new NodeList();
|
|
var i = 0;
|
|
for (var child = this.firstChild; child; child = child.nextSibling) {
|
|
wrapperList[i++] = child;
|
|
}
|
|
wrapperList.length = i;
|
|
return wrapperList;
|
|
},
|
|
|
|
cloneNode: function(deep) {
|
|
return cloneNode(this, deep);
|
|
},
|
|
|
|
contains: function(child) {
|
|
return contains(this, wrapIfNeeded(child));
|
|
},
|
|
|
|
compareDocumentPosition: function(otherNode) {
|
|
// This only wraps, it therefore only operates on the composed DOM and not
|
|
// the logical DOM.
|
|
return originalCompareDocumentPosition.call(this.impl,
|
|
unwrapIfNeeded(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.cloneNode = cloneNode;
|
|
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';
|
|
|
|
var HTMLCollection = scope.wrappers.HTMLCollection;
|
|
var NodeList = scope.wrappers.NodeList;
|
|
|
|
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 matchesSelector(el, selector) {
|
|
return el.matches(selector);
|
|
}
|
|
|
|
var XHTML_NS = 'http://www.w3.org/1999/xhtml';
|
|
|
|
function matchesTagName(el, localName, localNameLowerCase) {
|
|
var ln = el.localName;
|
|
return ln === localName ||
|
|
ln === localNameLowerCase && el.namespaceURI === XHTML_NS;
|
|
}
|
|
|
|
function matchesEveryThing() {
|
|
return true;
|
|
}
|
|
|
|
function matchesLocalName(el, localName) {
|
|
return el.localName === localName;
|
|
}
|
|
|
|
function matchesNameSpace(el, ns) {
|
|
return el.namespaceURI === ns;
|
|
}
|
|
|
|
function matchesLocalNameNS(el, ns, localName) {
|
|
return el.namespaceURI === ns && el.localName === localName;
|
|
}
|
|
|
|
function findElements(node, result, p, arg0, arg1) {
|
|
var el = node.firstElementChild;
|
|
while (el) {
|
|
if (p(el, arg0, arg1))
|
|
result[result.length++] = el;
|
|
findElements(el, result, p, arg0, arg1);
|
|
el = el.nextElementSibling;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// 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 findElements(this, new NodeList(), matchesSelector, selector);
|
|
}
|
|
};
|
|
|
|
var GetElementsByInterface = {
|
|
getElementsByTagName: function(localName) {
|
|
var result = new HTMLCollection();
|
|
if (localName === '*')
|
|
return findElements(this, result, matchesEveryThing);
|
|
|
|
return findElements(this, result,
|
|
matchesTagName,
|
|
localName,
|
|
localName.toLowerCase());
|
|
},
|
|
|
|
getElementsByClassName: function(className) {
|
|
// TODO(arv): Check className?
|
|
return this.querySelectorAll('.' + className);
|
|
},
|
|
|
|
getElementsByTagNameNS: function(ns, localName) {
|
|
var result = new HTMLCollection();
|
|
|
|
if (ns === '') {
|
|
ns = null;
|
|
} else if (ns === '*') {
|
|
if (localName === '*')
|
|
return findElements(this, result, matchesEveryThing);
|
|
return findElements(this, result, matchesLocalName, localName);
|
|
}
|
|
|
|
if (localName === '*')
|
|
return findElements(this, result, matchesNameSpace, ns);
|
|
|
|
return findElements(this, result, matchesLocalNameNS, ns, localName);
|
|
}
|
|
};
|
|
|
|
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;
|
|
},
|
|
|
|
remove: function() {
|
|
var p = this.parentNode;
|
|
if (p)
|
|
p.removeChild(this);
|
|
}
|
|
};
|
|
|
|
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 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';
|
|
|
|
function invalidateClass(el) {
|
|
scope.invalidateRendererBasedOnAttribute(el, 'class');
|
|
}
|
|
|
|
function DOMTokenList(impl, ownerElement) {
|
|
this.impl = impl;
|
|
this.ownerElement_ = ownerElement;
|
|
}
|
|
|
|
DOMTokenList.prototype = {
|
|
get length() {
|
|
return this.impl.length;
|
|
},
|
|
item: function(index) {
|
|
return this.impl.item(index);
|
|
},
|
|
contains: function(token) {
|
|
return this.impl.contains(token);
|
|
},
|
|
add: function() {
|
|
this.impl.add.apply(this.impl, arguments);
|
|
invalidateClass(this.ownerElement_);
|
|
},
|
|
remove: function() {
|
|
this.impl.remove.apply(this.impl, arguments);
|
|
invalidateClass(this.ownerElement_);
|
|
},
|
|
toggle: function(token) {
|
|
var rv = this.impl.toggle.apply(this.impl, arguments);
|
|
invalidateClass(this.ownerElement_);
|
|
return rv;
|
|
},
|
|
toString: function() {
|
|
return this.impl.toString();
|
|
}
|
|
};
|
|
|
|
scope.wrappers.DOMTokenList = DOMTokenList;
|
|
})(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 DOMTokenList = scope.wrappers.DOMTokenList;
|
|
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 unwrap = scope.unwrap;
|
|
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
|
|
});
|
|
}
|
|
|
|
var classListTable = new WeakMap();
|
|
|
|
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;
|
|
},
|
|
|
|
// getDestinationInsertionPoints added in ShadowRenderer.js
|
|
|
|
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);
|
|
},
|
|
|
|
get classList() {
|
|
var list = classListTable.get(this);
|
|
if (!list) {
|
|
classListTable.set(this,
|
|
list = new DOMTokenList(unwrap(this).classList, this));
|
|
}
|
|
return list;
|
|
},
|
|
|
|
get className() {
|
|
return unwrap(this).className;
|
|
},
|
|
|
|
set className(v) {
|
|
this.setAttribute('class', v);
|
|
},
|
|
|
|
get id() {
|
|
return unwrap(this).id;
|
|
},
|
|
|
|
set id(v) {
|
|
this.setAttribute('id', v);
|
|
}
|
|
});
|
|
|
|
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;
|
|
}
|
|
|
|
mixin(Element.prototype, ChildNodeInterface);
|
|
mixin(Element.prototype, GetElementsByInterface);
|
|
mixin(Element.prototype, ParentNodeInterface);
|
|
mixin(Element.prototype, SelectorsInterface);
|
|
|
|
registerWrapper(OriginalElement, Element,
|
|
document.createElementNS(null, 'x'));
|
|
|
|
scope.invalidateRendererBasedOnAttribute = invalidateRendererBasedOnAttribute;
|
|
scope.matchesNames = matchesNames;
|
|
scope.wrappers.Element = Element;
|
|
})(window.ShadowDOMPolyfill);
|
|
|
|
// Copyright 2013 The Polymer Authors. All rights reserved.
|
|
// Use of this source code is goverened by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
(function(scope) {
|
|
'use strict';
|
|
|
|
var Element = scope.wrappers.Element;
|
|
var defineGetter = scope.defineGetter;
|
|
var enqueueMutation = scope.enqueueMutation;
|
|
var mixin = scope.mixin;
|
|
var nodesWereAdded = scope.nodesWereAdded;
|
|
var nodesWereRemoved = scope.nodesWereRemoved;
|
|
var registerWrapper = scope.registerWrapper;
|
|
var snapshotNodeList = scope.snapshotNodeList;
|
|
var unwrap = scope.unwrap;
|
|
var wrap = scope.wrap;
|
|
var wrappers = scope.wrappers;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// innerHTML and outerHTML
|
|
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#escapingString
|
|
var escapeAttrRegExp = /[&\u00A0"]/g;
|
|
var escapeDataRegExp = /[&\u00A0<>]/g;
|
|
|
|
function escapeReplace(c) {
|
|
switch (c) {
|
|
case '&':
|
|
return '&';
|
|
case '<':
|
|
return '<';
|
|
case '>':
|
|
return '>';
|
|
case '"':
|
|
return '"'
|
|
case '\u00A0':
|
|
return ' ';
|
|
}
|
|
}
|
|
|
|
function escapeAttr(s) {
|
|
return s.replace(escapeAttrRegExp, escapeReplace);
|
|
}
|
|
|
|
function escapeData(s) {
|
|
return s.replace(escapeDataRegExp, escapeReplace);
|
|
}
|
|
|
|
function makeSet(arr) {
|
|
var set = {};
|
|
for (var i = 0; i < arr.length; i++) {
|
|
set[arr[i]] = true;
|
|
}
|
|
return set;
|
|
}
|
|
|
|
// http://www.whatwg.org/specs/web-apps/current-work/#void-elements
|
|
var voidElements = makeSet([
|
|
'area',
|
|
'base',
|
|
'br',
|
|
'col',
|
|
'command',
|
|
'embed',
|
|
'hr',
|
|
'img',
|
|
'input',
|
|
'keygen',
|
|
'link',
|
|
'meta',
|
|
'param',
|
|
'source',
|
|
'track',
|
|
'wbr'
|
|
]);
|
|
|
|
var plaintextParents = makeSet([
|
|
'style',
|
|
'script',
|
|
'xmp',
|
|
'iframe',
|
|
'noembed',
|
|
'noframes',
|
|
'plaintext',
|
|
'noscript'
|
|
]);
|
|
|
|
function getOuterHTML(node, parentNode) {
|
|
switch (node.nodeType) {
|
|
case Node.ELEMENT_NODE:
|
|
var tagName = node.tagName.toLowerCase();
|
|
var s = '<' + tagName;
|
|
var attrs = node.attributes;
|
|
for (var i = 0, attr; attr = attrs[i]; i++) {
|
|
s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"';
|
|
}
|
|
s += '>';
|
|
if (voidElements[tagName])
|
|
return s;
|
|
|
|
return s + getInnerHTML(node) + '</' + tagName + '>';
|
|
|
|
case Node.TEXT_NODE:
|
|
var data = node.data;
|
|
if (parentNode && plaintextParents[parentNode.localName])
|
|
return data;
|
|
return escapeData(data);
|
|
|
|
case Node.COMMENT_NODE:
|
|
return '<!--' + node.data + '-->';
|
|
|
|
default:
|
|
console.error(node);
|
|
throw new Error('not implemented');
|
|
}
|
|
}
|
|
|
|
function getInnerHTML(node) {
|
|
if (node instanceof wrappers.HTMLTemplateElement)
|
|
node = node.content;
|
|
|
|
var s = '';
|
|
for (var child = node.firstChild; child; child = child.nextSibling) {
|
|
s += getOuterHTML(child, node);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
function setInnerHTML(node, value, opt_tagName) {
|
|
var tagName = opt_tagName || 'div';
|
|
node.textContent = '';
|
|
var tempElement = unwrap(node.ownerDocument.createElement(tagName));
|
|
tempElement.innerHTML = value;
|
|
var firstChild;
|
|
while (firstChild = tempElement.firstChild) {
|
|
node.appendChild(wrap(firstChild));
|
|
}
|
|
}
|
|
|
|
// IE11 does not have MSIE in the user agent string.
|
|
var oldIe = /MSIE/.test(navigator.userAgent);
|
|
|
|
var OriginalHTMLElement = window.HTMLElement;
|
|
var OriginalHTMLTemplateElement = window.HTMLTemplateElement;
|
|
|
|
function HTMLElement(node) {
|
|
Element.call(this, node);
|
|
}
|
|
HTMLElement.prototype = Object.create(Element.prototype);
|
|
mixin(HTMLElement.prototype, {
|
|
get innerHTML() {
|
|
return getInnerHTML(this);
|
|
},
|
|
set innerHTML(value) {
|
|
// IE9 does not handle set innerHTML correctly on plaintextParents. It
|
|
// creates element children. For example
|
|
//
|
|
// scriptElement.innerHTML = '<a>test</a>'
|
|
//
|
|
// Creates a single HTMLAnchorElement child.
|
|
if (oldIe && plaintextParents[this.localName]) {
|
|
this.textContent = value;
|
|
return;
|
|
}
|
|
|
|
var removedNodes = snapshotNodeList(this.childNodes);
|
|
|
|
if (this.invalidateShadowRenderer()) {
|
|
if (this instanceof wrappers.HTMLTemplateElement)
|
|
setInnerHTML(this.content, value);
|
|
else
|
|
setInnerHTML(this, value, this.tagName);
|
|
|
|
// If we have a non native template element we need to handle this
|
|
// manually since setting impl.innerHTML would add the html as direct
|
|
// children and not be moved over to the content fragment.
|
|
} else if (!OriginalHTMLTemplateElement &&
|
|
this instanceof wrappers.HTMLTemplateElement) {
|
|
setInnerHTML(this.content, value);
|
|
} else {
|
|
this.impl.innerHTML = value;
|
|
}
|
|
|
|
var addedNodes = snapshotNodeList(this.childNodes);
|
|
|
|
enqueueMutation(this, 'childList', {
|
|
addedNodes: addedNodes,
|
|
removedNodes: removedNodes
|
|
});
|
|
|
|
nodesWereRemoved(removedNodes);
|
|
nodesWereAdded(addedNodes, this);
|
|
},
|
|
|
|
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
|
|
});
|
|
|
|
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 NodeList = scope.wrappers.NodeList;
|
|
var registerWrapper = scope.registerWrapper;
|
|
|
|
var OriginalHTMLShadowElement = window.HTMLShadowElement;
|
|
|
|
function HTMLShadowElement(node) {
|
|
HTMLElement.call(this, node);
|
|
}
|
|
HTMLShadowElement.prototype = Object.create(HTMLElement.prototype);
|
|
|
|
// getDistributedNodes is added in ShadowRenderer
|
|
|
|
if (OriginalHTMLShadowElement)
|
|
registerWrapper(OriginalHTMLShadowElement, HTMLShadowElement);
|
|
|
|
scope.wrappers.HTMLShadowElement = HTMLShadowElement;
|
|
})(window.ShadowDOMPolyfill);
|
|
|
|
// Copyright 2013 The Polymer Authors. All rights reserved.
|
|
// Use of this source code is goverened by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
(function(scope) {
|
|
'use strict';
|
|
|
|
var HTMLElement = scope.wrappers.HTMLElement;
|
|
var mixin = scope.mixin;
|
|
var registerWrapper = scope.registerWrapper;
|
|
var unwrap = scope.unwrap;
|
|
var wrap = scope.wrap;
|
|
|
|
var contentTable = new WeakMap();
|
|
var templateContentsOwnerTable = new WeakMap();
|
|
|
|
// http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#dfn-template-contents-owner
|
|
function getTemplateContentsOwner(doc) {
|
|
if (!doc.defaultView)
|
|
return doc;
|
|
var d = templateContentsOwnerTable.get(doc);
|
|
if (!d) {
|
|
// TODO(arv): This should either be a Document or HTMLDocument depending
|
|
// on doc.
|
|
d = doc.implementation.createHTMLDocument('');
|
|
while (d.lastChild) {
|
|
d.removeChild(d.lastChild);
|
|
}
|
|
templateContentsOwnerTable.set(doc, d);
|
|
}
|
|
return d;
|
|
}
|
|
|
|
function extractContent(templateElement) {
|
|
// templateElement is not a wrapper here.
|
|
var doc = getTemplateContentsOwner(templateElement.ownerDocument);
|
|
var df = unwrap(doc.createDocumentFragment());
|
|
var child;
|
|
while (child = templateElement.firstChild) {
|
|
df.appendChild(child);
|
|
}
|
|
return df;
|
|
}
|
|
|
|
var OriginalHTMLTemplateElement = window.HTMLTemplateElement;
|
|
|
|
function HTMLTemplateElement(node) {
|
|
HTMLElement.call(this, node);
|
|
if (!OriginalHTMLTemplateElement) {
|
|
var content = extractContent(node);
|
|
contentTable.set(this, wrap(content));
|
|
}
|
|
}
|
|
HTMLTemplateElement.prototype = Object.create(HTMLElement.prototype);
|
|
|
|
mixin(HTMLTemplateElement.prototype, {
|
|
get content() {
|
|
if (OriginalHTMLTemplateElement)
|
|
return wrap(this.impl.content);
|
|
return contentTable.get(this);
|
|
},
|
|
|
|
// TODO(arv): cloneNode needs to clone content.
|
|
|
|
});
|
|
|
|
if (OriginalHTMLTemplateElement)
|
|
registerWrapper(OriginalHTMLTemplateElement, HTMLTemplateElement);
|
|
|
|
scope.wrappers.HTMLTemplateElement = HTMLTemplateElement;
|
|
})(window.ShadowDOMPolyfill);
|
|
|
|
// Copyright 2013 The Polymer Authors. All rights reserved.
|
|
// Use of this source code is goverened by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
(function(scope) {
|
|
'use strict';
|
|
|
|
var HTMLElement = scope.wrappers.HTMLElement;
|
|
var registerWrapper = scope.registerWrapper;
|
|
|
|
var OriginalHTMLMediaElement = window.HTMLMediaElement;
|
|
|
|
function HTMLMediaElement(node) {
|
|
HTMLElement.call(this, node);
|
|
}
|
|
HTMLMediaElement.prototype = Object.create(HTMLElement.prototype);
|
|
|
|
registerWrapper(OriginalHTMLMediaElement, HTMLMediaElement,
|
|
document.createElement('audio'));
|
|
|
|
scope.wrappers.HTMLMediaElement = HTMLMediaElement;
|
|
})(window.ShadowDOMPolyfill);
|
|
|
|
// Copyright 2013 The Polymer Authors. All rights reserved.
|
|
// Use of this source code is goverened by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
(function(scope) {
|
|
'use strict';
|
|
|
|
var HTMLMediaElement = scope.wrappers.HTMLMediaElement;
|
|
var registerWrapper = scope.registerWrapper;
|
|
var unwrap = scope.unwrap;
|
|
var rewrap = scope.rewrap;
|
|
|
|
var OriginalHTMLAudioElement = window.HTMLAudioElement;
|
|
|
|
function HTMLAudioElement(node) {
|
|
HTMLMediaElement.call(this, node);
|
|
}
|
|
HTMLAudioElement.prototype = Object.create(HTMLMediaElement.prototype);
|
|
|
|
registerWrapper(OriginalHTMLAudioElement, HTMLAudioElement,
|
|
document.createElement('audio'));
|
|
|
|
function Audio(src) {
|
|
if (!(this instanceof Audio)) {
|
|
throw new TypeError(
|
|
'DOM object constructor cannot be called as a function.');
|
|
}
|
|
|
|
var node = unwrap(document.createElement('audio'));
|
|
HTMLMediaElement.call(this, node);
|
|
rewrap(node, this);
|
|
|
|
node.setAttribute('preload', 'auto');
|
|
if (src !== undefined)
|
|
node.setAttribute('src', src);
|
|
}
|
|
|
|
Audio.prototype = HTMLAudioElement.prototype;
|
|
|
|
scope.wrappers.HTMLAudioElement = HTMLAudioElement;
|
|
scope.wrappers.Audio = Audio;
|
|
})(window.ShadowDOMPolyfill);
|
|
|
|
// Copyright 2013 The Polymer Authors. All rights reserved.
|
|
// Use of this source code is goverened by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
(function(scope) {
|
|
'use strict';
|
|
|
|
var HTMLElement = scope.wrappers.HTMLElement;
|
|
var mixin = scope.mixin;
|
|
var registerWrapper = scope.registerWrapper;
|
|
var rewrap = scope.rewrap;
|
|
var unwrap = scope.unwrap;
|
|
var wrap = scope.wrap;
|
|
|
|
var OriginalHTMLOptionElement = window.HTMLOptionElement;
|
|
|
|
function trimText(s) {
|
|
return s.replace(/\s+/g, ' ').trim();
|
|
}
|
|
|
|
function HTMLOptionElement(node) {
|
|
HTMLElement.call(this, node);
|
|
}
|
|
HTMLOptionElement.prototype = Object.create(HTMLElement.prototype);
|
|
mixin(HTMLOptionElement.prototype, {
|
|
get text() {
|
|
return trimText(this.textContent);
|
|
},
|
|
set text(value) {
|
|
this.textContent = trimText(String(value));
|
|
},
|
|
get form() {
|
|
return wrap(unwrap(this).form);
|
|
}
|
|
});
|
|
|
|
registerWrapper(OriginalHTMLOptionElement, HTMLOptionElement,
|
|
document.createElement('option'));
|
|
|
|
function Option(text, value, defaultSelected, selected) {
|
|
if (!(this instanceof Option)) {
|
|
throw new TypeError(
|
|
'DOM object constructor cannot be called as a function.');
|
|
}
|
|
|
|
var node = unwrap(document.createElement('option'));
|
|
HTMLElement.call(this, node);
|
|
rewrap(node, this);
|
|
|
|
if (text !== undefined)
|
|
node.text = text;
|
|
if (value !== undefined)
|
|
node.setAttribute('value', value);
|
|
if (defaultSelected === true)
|
|
node.setAttribute('selected', '');
|
|
node.selected = selected === true;
|
|
}
|
|
|
|
Option.prototype = HTMLOptionElement.prototype;
|
|
|
|
scope.wrappers.HTMLOptionElement = HTMLOptionElement;
|
|
scope.wrappers.Option = Option;
|
|
})(window.ShadowDOMPolyfill);
|
|
|
|
// Copyright 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 HTMLElement = scope.wrappers.HTMLElement;
|
|
var mixin = scope.mixin;
|
|
var registerWrapper = scope.registerWrapper;
|
|
var unwrap = scope.unwrap;
|
|
var wrap = scope.wrap;
|
|
|
|
var OriginalHTMLSelectElement = window.HTMLSelectElement;
|
|
|
|
function HTMLSelectElement(node) {
|
|
HTMLElement.call(this, node);
|
|
}
|
|
HTMLSelectElement.prototype = Object.create(HTMLElement.prototype);
|
|
mixin(HTMLSelectElement.prototype, {
|
|
add: function(element, before) {
|
|
if (typeof before === 'object') // also includes null
|
|
before = unwrap(before);
|
|
unwrap(this).add(unwrap(element), before);
|
|
},
|
|
|
|
remove: function(indexOrNode) {
|
|
// Spec only allows index but implementations allow index or node.
|
|
// remove() is also allowed which is same as remove(undefined)
|
|
if (indexOrNode === undefined) {
|
|
HTMLElement.prototype.remove.call(this);
|
|
return;
|
|
}
|
|
|
|
if (typeof indexOrNode === 'object')
|
|
indexOrNode = unwrap(indexOrNode);
|
|
|
|
unwrap(this).remove(indexOrNode);
|
|
},
|
|
|
|
get form() {
|
|
return wrap(unwrap(this).form);
|
|
}
|
|
});
|
|
|
|
registerWrapper(OriginalHTMLSelectElement, HTMLSelectElement,
|
|
document.createElement('select'));
|
|
|
|
scope.wrappers.HTMLSelectElement = HTMLSelectElement;
|
|
})(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 HTMLElement = scope.wrappers.HTMLElement;
|
|
var mixin = scope.mixin;
|
|
var registerWrapper = scope.registerWrapper;
|
|
var unwrap = scope.unwrap;
|
|
var wrap = scope.wrap;
|
|
var wrapHTMLCollection = scope.wrapHTMLCollection;
|
|
|
|
var OriginalHTMLTableElement = window.HTMLTableElement;
|
|
|
|
function HTMLTableElement(node) {
|
|
HTMLElement.call(this, node);
|
|
}
|
|
HTMLTableElement.prototype = Object.create(HTMLElement.prototype);
|
|
mixin(HTMLTableElement.prototype, {
|
|
get caption() {
|
|
return wrap(unwrap(this).caption);
|
|
},
|
|
createCaption: function() {
|
|
return wrap(unwrap(this).createCaption());
|
|
},
|
|
|
|
get tHead() {
|
|
return wrap(unwrap(this).tHead);
|
|
},
|
|
createTHead: function() {
|
|
return wrap(unwrap(this).createTHead());
|
|
},
|
|
|
|
createTFoot: function() {
|
|
return wrap(unwrap(this).createTFoot());
|
|
},
|
|
get tFoot() {
|
|
return wrap(unwrap(this).tFoot);
|
|
},
|
|
|
|
get tBodies() {
|
|
return wrapHTMLCollection(unwrap(this).tBodies);
|
|
},
|
|
createTBody: function() {
|
|
return wrap(unwrap(this).createTBody());
|
|
},
|
|
|
|
get rows() {
|
|
return wrapHTMLCollection(unwrap(this).rows);
|
|
},
|
|
insertRow: function(index) {
|
|
return wrap(unwrap(this).insertRow(index));
|
|
}
|
|
});
|
|
|
|
registerWrapper(OriginalHTMLTableElement, HTMLTableElement,
|
|
document.createElement('table'));
|
|
|
|
scope.wrappers.HTMLTableElement = HTMLTableElement;
|
|
})(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 HTMLElement = scope.wrappers.HTMLElement;
|
|
var mixin = scope.mixin;
|
|
var registerWrapper = scope.registerWrapper;
|
|
var wrapHTMLCollection = scope.wrapHTMLCollection;
|
|
var unwrap = scope.unwrap;
|
|
var wrap = scope.wrap;
|
|
|
|
var OriginalHTMLTableSectionElement = window.HTMLTableSectionElement;
|
|
|
|
function HTMLTableSectionElement(node) {
|
|
HTMLElement.call(this, node);
|
|
}
|
|
HTMLTableSectionElement.prototype = Object.create(HTMLElement.prototype);
|
|
mixin(HTMLTableSectionElement.prototype, {
|
|
get rows() {
|
|
return wrapHTMLCollection(unwrap(this).rows);
|
|
},
|
|
insertRow: function(index) {
|
|
return wrap(unwrap(this).insertRow(index));
|
|
}
|
|
});
|
|
|
|
registerWrapper(OriginalHTMLTableSectionElement, HTMLTableSectionElement,
|
|
document.createElement('thead'));
|
|
|
|
scope.wrappers.HTMLTableSectionElement = HTMLTableSectionElement;
|
|
})(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 HTMLElement = scope.wrappers.HTMLElement;
|
|
var mixin = scope.mixin;
|
|
var registerWrapper = scope.registerWrapper;
|
|
var wrapHTMLCollection = scope.wrapHTMLCollection;
|
|
var unwrap = scope.unwrap;
|
|
var wrap = scope.wrap;
|
|
|
|
var OriginalHTMLTableRowElement = window.HTMLTableRowElement;
|
|
|
|
function HTMLTableRowElement(node) {
|
|
HTMLElement.call(this, node);
|
|
}
|
|
HTMLTableRowElement.prototype = Object.create(HTMLElement.prototype);
|
|
mixin(HTMLTableRowElement.prototype, {
|
|
get cells() {
|
|
return wrapHTMLCollection(unwrap(this).cells);
|
|
},
|
|
|
|
insertCell: function(index) {
|
|
return wrap(unwrap(this).insertCell(index));
|
|
}
|
|
});
|
|
|
|
registerWrapper(OriginalHTMLTableRowElement, HTMLTableRowElement,
|
|
document.createElement('tr'));
|
|
|
|
scope.wrappers.HTMLTableRowElement = HTMLTableRowElement;
|
|
})(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 Element = scope.wrappers.Element;
|
|
var HTMLElement = scope.wrappers.HTMLElement;
|
|
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;
|
|
|
|
// IE11 does not have classList for SVG elements. The spec says that classList
|
|
// is an accessor on Element, but IE11 puts classList on HTMLElement, leaving
|
|
// SVGElement without a classList property. We therefore move the accessor for
|
|
// IE11.
|
|
if (!('classList' in svgTitleElement)) {
|
|
var descr = Object.getOwnPropertyDescriptor(Element.prototype, 'classList');
|
|
Object.defineProperty(HTMLElement.prototype, 'classList', descr);
|
|
delete Element.prototype.classList;
|
|
}
|
|
|
|
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 TreeScope = scope.TreeScope;
|
|
var elementFromPoint = scope.elementFromPoint;
|
|
var getInnerHTML = scope.getInnerHTML;
|
|
var getTreeScope = scope.getTreeScope;
|
|
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);
|
|
|
|
this.treeScope_ =
|
|
new TreeScope(this, getTreeScope(oldShadowRoot || hostWrapper));
|
|
|
|
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 getTreeScope = scope.getTreeScope;
|
|
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 distributedNodesTable = new WeakMap();
|
|
var destinationInsertionPointsTable = new WeakMap();
|
|
var rendererForHostTable = new WeakMap();
|
|
|
|
function resetDistributedNodes(insertionPoint) {
|
|
distributedNodesTable.set(insertionPoint, []);
|
|
}
|
|
|
|
function getDistributedNodes(insertionPoint) {
|
|
var rv = distributedNodesTable.get(insertionPoint);
|
|
if (!rv)
|
|
distributedNodesTable.set(insertionPoint, rv = []);
|
|
return rv;
|
|
}
|
|
|
|
function getChildNodesSnapshot(node) {
|
|
var result = [], i = 0;
|
|
for (var child = node.firstChild; child; child = child.nextSibling) {
|
|
result[i++] = child;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
var request = oneOf(window, [
|
|
'requestAnimationFrame',
|
|
'mozRequestAnimationFrame',
|
|
'webkitRequestAnimationFrame',
|
|
'setTimeout'
|
|
]);
|
|
|
|
var pendingDirtyRenderers = [];
|
|
var renderTimer;
|
|
|
|
function renderAllPending() {
|
|
// TODO(arv): Order these in document order. That way we do not have to
|
|
// render something twice.
|
|
for (var i = 0; i < pendingDirtyRenderers.length; i++) {
|
|
var renderer = pendingDirtyRenderers[i];
|
|
var parentRenderer = renderer.parentRenderer;
|
|
if (parentRenderer && parentRenderer.dirty)
|
|
continue;
|
|
renderer.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) {
|
|
var root = getTreeScope(node).root;
|
|
if (root instanceof ShadowRoot)
|
|
return root;
|
|
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();
|
|
|
|
var host = this.host;
|
|
|
|
this.distribution(host);
|
|
var renderNode = opt_renderNode || new RenderNode(host);
|
|
this.buildRenderTree(renderNode, host);
|
|
|
|
var topMostRenderer = !opt_renderNode;
|
|
if (topMostRenderer)
|
|
renderNode.sync();
|
|
|
|
this.dirty = false;
|
|
},
|
|
|
|
get parentRenderer() {
|
|
return getTreeScope(this.host).renderer;
|
|
},
|
|
|
|
invalidate: function() {
|
|
if (!this.dirty) {
|
|
this.dirty = true;
|
|
pendingDirtyRenderers.push(this);
|
|
if (renderTimer)
|
|
return;
|
|
renderTimer = window[request](handleRequestAnimationFrame, 0);
|
|
}
|
|
},
|
|
|
|
// http://w3c.github.io/webcomponents/spec/shadow/#distribution-algorithms
|
|
distribution: function(root) {
|
|
this.resetAll(root);
|
|
this.distributionResolution(root);
|
|
},
|
|
|
|
resetAll: function(node) {
|
|
if (isInsertionPoint(node))
|
|
resetDistributedNodes(node);
|
|
else
|
|
resetDestinationInsertionPoints(node);
|
|
|
|
for (var child = node.firstChild; child; child = child.nextSibling) {
|
|
this.resetAll(child);
|
|
}
|
|
|
|
if (node.shadowRoot)
|
|
this.resetAll(node.shadowRoot);
|
|
|
|
if (node.olderShadowRoot)
|
|
this.resetAll(node.olderShadowRoot);
|
|
},
|
|
|
|
// http://w3c.github.io/webcomponents/spec/shadow/#distribution-results
|
|
distributionResolution: function(node) {
|
|
if (isShadowHost(node)) {
|
|
var shadowHost = node;
|
|
// 1.1
|
|
var pool = poolPopulation(shadowHost);
|
|
|
|
var shadowTrees = getShadowTrees(shadowHost);
|
|
|
|
// 1.2
|
|
for (var i = 0; i < shadowTrees.length; i++) {
|
|
// 1.2.1
|
|
this.poolDistribution(shadowTrees[i], pool);
|
|
}
|
|
|
|
// 1.3
|
|
for (var i = shadowTrees.length - 1; i >= 0; i--) {
|
|
var shadowTree = shadowTrees[i];
|
|
|
|
// 1.3.1
|
|
// TODO(arv): We should keep the shadow insertion points on the
|
|
// shadow root (or renderer) so we don't have to search the tree
|
|
// every time.
|
|
var shadow = getShadowInsertionPoint(shadowTree);
|
|
|
|
// 1.3.2
|
|
if (shadow) {
|
|
|
|
// 1.3.2.1
|
|
var olderShadowRoot = shadowTree.olderShadowRoot;
|
|
if (olderShadowRoot) {
|
|
// 1.3.2.1.1
|
|
pool = poolPopulation(olderShadowRoot);
|
|
}
|
|
|
|
// 1.3.2.2
|
|
for (var j = 0; j < pool.length; j++) {
|
|
// 1.3.2.2.1
|
|
destributeNodeInto(pool[j], shadow);
|
|
}
|
|
}
|
|
|
|
// 1.3.3
|
|
this.distributionResolution(shadowTree);
|
|
}
|
|
}
|
|
|
|
for (var child = node.firstChild; child; child = child.nextSibling) {
|
|
this.distributionResolution(child);
|
|
}
|
|
},
|
|
|
|
// http://w3c.github.io/webcomponents/spec/shadow/#dfn-pool-distribution-algorithm
|
|
poolDistribution: function (node, pool) {
|
|
if (node instanceof HTMLShadowElement)
|
|
return;
|
|
|
|
if (node instanceof HTMLContentElement) {
|
|
var content = node;
|
|
this.updateDependentAttributes(content.getAttribute('select'));
|
|
|
|
var anyDistributed = false;
|
|
|
|
// 1.1
|
|
for (var i = 0; i < pool.length; i++) {
|
|
var node = pool[i];
|
|
if (!node)
|
|
continue;
|
|
if (matches(node, content)) {
|
|
destributeNodeInto(node, content);
|
|
pool[i] = undefined;
|
|
anyDistributed = true;
|
|
}
|
|
}
|
|
|
|
// 1.2
|
|
// Fallback content
|
|
if (!anyDistributed) {
|
|
for (var child = content.firstChild;
|
|
child;
|
|
child = child.nextSibling) {
|
|
destributeNodeInto(child, content);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
for (var child = node.firstChild; child; child = child.nextSibling) {
|
|
this.poolDistribution(child, pool);
|
|
}
|
|
},
|
|
|
|
buildRenderTree: function(renderNode, node) {
|
|
var children = this.compose(node);
|
|
for (var i = 0; i < children.length; i++) {
|
|
var child = children[i];
|
|
var childRenderNode = renderNode.append(child);
|
|
this.buildRenderTree(childRenderNode, child);
|
|
}
|
|
|
|
if (isShadowHost(node)) {
|
|
var renderer = getRendererForHost(node);
|
|
renderer.dirty = false;
|
|
}
|
|
|
|
},
|
|
|
|
compose: function(node) {
|
|
var children = [];
|
|
var p = node.shadowRoot || node;
|
|
for (var child = p.firstChild; child; child = child.nextSibling) {
|
|
if (isInsertionPoint(child)) {
|
|
this.associateNode(p);
|
|
var distributedNodes = getDistributedNodes(child);
|
|
for (var j = 0; j < distributedNodes.length; j++) {
|
|
var distributedNode = distributedNodes[j];
|
|
if (isFinalDestination(child, distributedNode))
|
|
children.push(distributedNode);
|
|
}
|
|
} else {
|
|
children.push(child);
|
|
}
|
|
}
|
|
return children;
|
|
},
|
|
|
|
/**
|
|
* 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];
|
|
},
|
|
|
|
associateNode: function(node) {
|
|
node.impl.polymerShadowRenderer_ = this;
|
|
}
|
|
};
|
|
|
|
// http://w3c.github.io/webcomponents/spec/shadow/#dfn-pool-population-algorithm
|
|
function poolPopulation(node) {
|
|
var pool = [];
|
|
for (var child = node.firstChild; child; child = child.nextSibling) {
|
|
if (isInsertionPoint(child)) {
|
|
pool.push.apply(pool, getDistributedNodes(child));
|
|
} else {
|
|
pool.push(child);
|
|
}
|
|
}
|
|
return pool;
|
|
}
|
|
|
|
function getShadowInsertionPoint(node) {
|
|
if (node instanceof HTMLShadowElement)
|
|
return node;
|
|
if (node instanceof HTMLContentElement)
|
|
return null;
|
|
for (var child = node.firstChild; child; child = child.nextSibling) {
|
|
var res = getShadowInsertionPoint(child);
|
|
if (res)
|
|
return res;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function destributeNodeInto(child, insertionPoint) {
|
|
getDistributedNodes(insertionPoint).push(child);
|
|
var points = destinationInsertionPointsTable.get(child);
|
|
if (!points)
|
|
destinationInsertionPointsTable.set(child, [insertionPoint]);
|
|
else
|
|
points.push(insertionPoint);
|
|
}
|
|
|
|
function getDestinationInsertionPoints(node) {
|
|
return destinationInsertionPointsTable.get(node);
|
|
}
|
|
|
|
function resetDestinationInsertionPoints(node) {
|
|
// IE11 crashes when delete is used.
|
|
destinationInsertionPointsTable.set(node, undefined);
|
|
}
|
|
|
|
// AllowedSelectors :
|
|
// TypeSelector
|
|
// *
|
|
// ClassSelector
|
|
// IDSelector
|
|
// AttributeSelector
|
|
var selectorStartCharRe = /^[*.#[a-zA-Z_|]/;
|
|
|
|
function matches(node, contentElement) {
|
|
var select = contentElement.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;
|
|
|
|
if (!selectorStartCharRe.test(select))
|
|
return false;
|
|
|
|
try {
|
|
return node.matches(select);
|
|
} catch (ex) {
|
|
// Invalid selector.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function isFinalDestination(insertionPoint, node) {
|
|
var points = getDestinationInsertionPoints(node);
|
|
return points && points[points.length - 1] === insertionPoint;
|
|
}
|
|
|
|
function isInsertionPoint(node) {
|
|
return node instanceof HTMLContentElement ||
|
|
node instanceof HTMLShadowElement;
|
|
}
|
|
|
|
function isShadowHost(shadowHost) {
|
|
return shadowHost.shadowRoot;
|
|
}
|
|
|
|
// Returns the shadow trees as an array, with the youngest tree at the
|
|
// beginning of the array.
|
|
function getShadowTrees(host) {
|
|
var trees = [];
|
|
|
|
for (var tree = host.shadowRoot; tree; tree = tree.olderShadowRoot) {
|
|
trees.push(tree);
|
|
}
|
|
return 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 =
|
|
HTMLShadowElement.prototype.getDistributedNodes = function() {
|
|
// TODO(arv): We should only rerender the dirty ancestor renderers (from
|
|
// the root and down).
|
|
renderAllPending();
|
|
return getDistributedNodes(this);
|
|
};
|
|
|
|
Element.prototype.getDestinationInsertionPoints = function() {
|
|
renderAllPending();
|
|
return getDestinationInsertionPoints(this) || [];
|
|
};
|
|
|
|
HTMLContentElement.prototype.nodeIsInserted_ =
|
|
HTMLShadowElement.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.getRendererForHost = getRendererForHost;
|
|
scope.getShadowTrees = getShadowTrees;
|
|
scope.renderAllPending = renderAllPending;
|
|
|
|
scope.getDestinationInsertionPoints = getDestinationInsertionPoints;
|
|
|
|
// 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 is handled in HTMLSelectElement.js
|
|
'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 TreeScope = scope.TreeScope;
|
|
var cloneNode = scope.cloneNode;
|
|
var defineWrapGetter = scope.defineWrapGetter;
|
|
var elementFromPoint = scope.elementFromPoint;
|
|
var forwardMethodsToWrapper = scope.forwardMethodsToWrapper;
|
|
var matchesNames = scope.matchesNames;
|
|
var mixin = scope.mixin;
|
|
var registerWrapper = scope.registerWrapper;
|
|
var renderAllPending = scope.renderAllPending;
|
|
var rewrap = scope.rewrap;
|
|
var unwrap = scope.unwrap;
|
|
var wrap = scope.wrap;
|
|
var wrapEventTargetMethods = scope.wrapEventTargetMethods;
|
|
var wrapNodeList = scope.wrapNodeList;
|
|
|
|
var implementationTable = new WeakMap();
|
|
|
|
function Document(node) {
|
|
Node.call(this, node);
|
|
this.treeScope_ = new TreeScope(this, null);
|
|
}
|
|
Document.prototype = Object.create(Node.prototype);
|
|
|
|
defineWrapGetter(Document, 'documentElement');
|
|
|
|
// Conceptually both body and head can be in a shadow but suporting that seems
|
|
// overkill at this point.
|
|
defineWrapGetter(Document, 'body');
|
|
defineWrapGetter(Document, 'head');
|
|
|
|
// document cannot be overridden so we override a bunch of its methods
|
|
// directly on the instance.
|
|
|
|
function wrapMethod(name) {
|
|
var original = document[name];
|
|
Document.prototype[name] = function() {
|
|
return wrap(original.apply(this.impl, arguments));
|
|
};
|
|
}
|
|
|
|
[
|
|
'createComment',
|
|
'createDocumentFragment',
|
|
'createElement',
|
|
'createElementNS',
|
|
'createEvent',
|
|
'createEventNS',
|
|
'createRange',
|
|
'createTextNode',
|
|
'getElementById'
|
|
].forEach(wrapMethod);
|
|
|
|
var originalAdoptNode = document.adoptNode;
|
|
|
|
function adoptNodeNoRemove(node, doc) {
|
|
originalAdoptNode.call(doc.impl, unwrap(node));
|
|
adoptSubtree(node, doc);
|
|
}
|
|
|
|
function adoptSubtree(node, doc) {
|
|
if (node.shadowRoot)
|
|
doc.adoptNode(node.shadowRoot);
|
|
if (node instanceof ShadowRoot)
|
|
adoptOlderShadowRoots(node, doc);
|
|
for (var child = node.firstChild; child; child = child.nextSibling) {
|
|
adoptSubtree(child, doc);
|
|
}
|
|
}
|
|
|
|
function adoptOlderShadowRoots(shadowRoot, doc) {
|
|
var oldShadowRoot = shadowRoot.olderShadowRoot;
|
|
if (oldShadowRoot)
|
|
doc.adoptNode(oldShadowRoot);
|
|
}
|
|
|
|
var originalGetSelection = document.getSelection;
|
|
|
|
mixin(Document.prototype, {
|
|
adoptNode: function(node) {
|
|
if (node.parentNode)
|
|
node.parentNode.removeChild(node);
|
|
adoptNodeNoRemove(node, this);
|
|
return node;
|
|
},
|
|
elementFromPoint: function(x, y) {
|
|
return elementFromPoint(this, this, x, y);
|
|
},
|
|
importNode: function(node, deep) {
|
|
return cloneNode(node, deep, this.impl);
|
|
},
|
|
getSelection: function() {
|
|
renderAllPending();
|
|
return new Selection(originalGetSelection.call(unwrap(this)));
|
|
},
|
|
getElementsByName: function(name) {
|
|
return SelectorsInterface.querySelectorAll.call(this,
|
|
'[name=' + JSON.stringify(String(name)) + ']');
|
|
}
|
|
});
|
|
|
|
if (document.registerElement) {
|
|
var originalRegisterElement = document.registerElement;
|
|
Document.prototype.registerElement = function(tagName, object) {
|
|
var prototype, extendsOption;
|
|
if (object !== undefined) {
|
|
prototype = object.prototype;
|
|
extendsOption = object.extends;
|
|
}
|
|
|
|
if (!prototype)
|
|
prototype = Object.create(HTMLElement.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 (extendsOption)
|
|
p.extends = extendsOption;
|
|
|
|
function CustomElementConstructor(node) {
|
|
if (!node) {
|
|
if (extendsOption) {
|
|
return document.createElement(extendsOption, 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',
|
|
'getElementsByName',
|
|
'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;
|
|
},
|
|
|
|
get defaultView() {
|
|
return wrap(unwrap(this).defaultView);
|
|
}
|
|
});
|
|
|
|
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)));
|
|
},
|
|
|
|
get document() {
|
|
return wrap(unwrap(this).document);
|
|
}
|
|
});
|
|
|
|
registerWrapper(OriginalWindow, Window, window);
|
|
|
|
scope.wrappers.Window = Window;
|
|
|
|
})(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 unwrap = scope.unwrap;
|
|
|
|
// DataTransfer (Clipboard in old Blink/WebKit) has a single method that
|
|
// requires wrapping. Since it is only a method we do not need a real wrapper,
|
|
// we can just override the method.
|
|
|
|
var OriginalDataTransfer = window.DataTransfer || window.Clipboard;
|
|
var OriginalDataTransferSetDragImage =
|
|
OriginalDataTransfer.prototype.setDragImage;
|
|
|
|
OriginalDataTransfer.prototype.setDragImage = function(image, x, y) {
|
|
OriginalDataTransferSetDragImage.call(this, unwrap(image), x, y);
|
|
};
|
|
|
|
})(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',
|
|
'audio': 'HTMLAudioElement',
|
|
'base': 'HTMLBaseElement',
|
|
'body': 'HTMLBodyElement',
|
|
'br': 'HTMLBRElement',
|
|
'button': 'HTMLButtonElement',
|
|
'canvas': 'HTMLCanvasElement',
|
|
'caption': 'HTMLTableCaptionElement',
|
|
'col': 'HTMLTableColElement',
|
|
// 'command': 'HTMLCommandElement', // Not fully implemented in Gecko.
|
|
'content': 'HTMLContentElement',
|
|
'data': 'HTMLDataElement',
|
|
'datalist': 'HTMLDataListElement',
|
|
'del': 'HTMLModElement',
|
|
'dir': 'HTMLDirectoryElement',
|
|
'div': 'HTMLDivElement',
|
|
'dl': 'HTMLDListElement',
|
|
'embed': 'HTMLEmbedElement',
|
|
'fieldset': 'HTMLFieldSetElement',
|
|
'font': 'HTMLFontElement',
|
|
'form': 'HTMLFormElement',
|
|
'frame': 'HTMLFrameElement',
|
|
'frameset': 'HTMLFrameSetElement',
|
|
'h1': 'HTMLHeadingElement',
|
|
'head': 'HTMLHeadElement',
|
|
'hr': 'HTMLHRElement',
|
|
'html': 'HTMLHtmlElement',
|
|
'iframe': 'HTMLIFrameElement',
|
|
'img': 'HTMLImageElement',
|
|
'input': 'HTMLInputElement',
|
|
'keygen': 'HTMLKeygenElement',
|
|
'label': 'HTMLLabelElement',
|
|
'legend': 'HTMLLegendElement',
|
|
'li': 'HTMLLIElement',
|
|
'link': 'HTMLLinkElement',
|
|
'map': 'HTMLMapElement',
|
|
'marquee': 'HTMLMarqueeElement',
|
|
'menu': 'HTMLMenuElement',
|
|
'menuitem': 'HTMLMenuItemElement',
|
|
'meta': 'HTMLMetaElement',
|
|
'meter': 'HTMLMeterElement',
|
|
'object': 'HTMLObjectElement',
|
|
'ol': 'HTMLOListElement',
|
|
'optgroup': 'HTMLOptGroupElement',
|
|
'option': 'HTMLOptionElement',
|
|
'output': 'HTMLOutputElement',
|
|
'p': 'HTMLParagraphElement',
|
|
'param': 'HTMLParamElement',
|
|
'pre': 'HTMLPreElement',
|
|
'progress': 'HTMLProgressElement',
|
|
'q': 'HTMLQuoteElement',
|
|
'script': 'HTMLScriptElement',
|
|
'select': 'HTMLSelectElement',
|
|
'shadow': 'HTMLShadowElement',
|
|
'source': 'HTMLSourceElement',
|
|
'span': 'HTMLSpanElement',
|
|
'style': 'HTMLStyleElement',
|
|
'table': 'HTMLTableElement',
|
|
'tbody': 'HTMLTableSectionElement',
|
|
// 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',
|
|
'template': 'HTMLTemplateElement',
|
|
'textarea': 'HTMLTextAreaElement',
|
|
'thead': 'HTMLTableSectionElement',
|
|
'time': 'HTMLTimeElement',
|
|
'title': 'HTMLTitleElement',
|
|
'tr': 'HTMLTableRowElement',
|
|
'track': 'HTMLTrackElement',
|
|
'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]
|
|
});
|
|
|
|
})(window.ShadowDOMPolyfill);
|
|
|
|
/*
|
|
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
* Code distributed by Google as part of the polymer project is also
|
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
*/
|
|
|
|
(function(scope) {
|
|
|
|
// convenient global
|
|
window.wrap = ShadowDOMPolyfill.wrapIfNeeded;
|
|
window.unwrap = ShadowDOMPolyfill.unwrapIfNeeded;
|
|
|
|
// users may want to customize other types
|
|
// TODO(sjmiles): 'button' is now supported by ShadowDOMPolyfill, but
|
|
// I've left this code here in case we need to temporarily patch another
|
|
// type
|
|
/*
|
|
(function() {
|
|
var elts = {HTMLButtonElement: 'button'};
|
|
for (var c in elts) {
|
|
window[c] = function() { throw 'Patched Constructor'; };
|
|
window[c].prototype = Object.getPrototypeOf(
|
|
document.createElement(elts[c]));
|
|
}
|
|
})();
|
|
*/
|
|
|
|
// patch in prefixed name
|
|
Object.defineProperty(Element.prototype, 'webkitShadowRoot',
|
|
Object.getOwnPropertyDescriptor(Element.prototype, 'shadowRoot'));
|
|
|
|
var originalCreateShadowRoot = Element.prototype.createShadowRoot;
|
|
Element.prototype.createShadowRoot = function() {
|
|
var root = originalCreateShadowRoot.call(this);
|
|
CustomElements.watchShadow(this);
|
|
return root;
|
|
};
|
|
|
|
Element.prototype.webkitCreateShadowRoot = Element.prototype.createShadowRoot;
|
|
|
|
function queryShadow(node, selector) {
|
|
var m, el = node.firstElementChild;
|
|
var shadows, sr, i;
|
|
shadows = [];
|
|
sr = node.shadowRoot;
|
|
while(sr) {
|
|
shadows.push(sr);
|
|
sr = sr.olderShadowRoot;
|
|
}
|
|
for(i = shadows.length - 1; i >= 0; i--) {
|
|
m = shadows[i].querySelector(selector);
|
|
if (m) {
|
|
return m;
|
|
}
|
|
}
|
|
while(el) {
|
|
m = queryShadow(el, selector);
|
|
if (m) {
|
|
return m;
|
|
}
|
|
el = el.nextElementSibling;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function queryAllShadows(node, selector, results) {
|
|
var el = node.firstElementChild;
|
|
var temp, sr, shadows, i, j;
|
|
shadows = [];
|
|
sr = node.shadowRoot;
|
|
while(sr) {
|
|
shadows.push(sr);
|
|
sr = sr.olderShadowRoot;
|
|
}
|
|
for (i = shadows.length - 1; i >= 0; i--) {
|
|
temp = shadows[i].querySelectorAll(selector);
|
|
for(j = 0; j < temp.length; j++) {
|
|
results.push(temp[j]);
|
|
}
|
|
}
|
|
while (el) {
|
|
queryAllShadows(el, selector, results);
|
|
el = el.nextElementSibling;
|
|
}
|
|
return results;
|
|
}
|
|
|
|
scope.queryAllShadows = function(node, selector, all) {
|
|
if (all) {
|
|
return queryAllShadows(node, selector, []);
|
|
} else {
|
|
return queryShadow(node, selector);
|
|
}
|
|
};
|
|
})(window.Platform);
|
|
|
|
/*
|
|
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
* Code distributed by Google as part of the polymer project is also
|
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
*/
|
|
|
|
/*
|
|
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, :host-context: ShadowDOM allows styling of the shadowRoot's host
|
|
element using the :host rule. To shim this feature, the :host styles are
|
|
reformatted and prefixed with a given scope name and promoted to a
|
|
document level stylesheet.
|
|
For example, given a scope name of .foo, a rule like this:
|
|
|
|
:host {
|
|
background: red;
|
|
}
|
|
}
|
|
|
|
becomes:
|
|
|
|
.foo {
|
|
background: red;
|
|
}
|
|
|
|
* encapsultion: Styles defined within ShadowDOM, apply only to
|
|
dom inside the ShadowDOM. Polymer uses one of two techniques to imlement
|
|
this feature.
|
|
|
|
By default, rules are prefixed with the host element tag name
|
|
as a descendant selector. This ensures styling does not leak out of the 'top'
|
|
of the element's ShadowDOM. For example,
|
|
|
|
div {
|
|
font-weight: bold;
|
|
}
|
|
|
|
becomes:
|
|
|
|
x-foo div {
|
|
font-weight: bold;
|
|
}
|
|
|
|
becomes:
|
|
|
|
|
|
Alternatively, if Platform.ShadowCSS.strictStyling is set to true then
|
|
selectors are scoped by adding an attribute selector suffix to each
|
|
simple selector that contains the host element tag name. Each element
|
|
in the element's ShadowDOM template is also given the scope attribute.
|
|
Thus, these rules match only elements that have the scope attribute.
|
|
For example, given a scope name of x-foo, a rule like this:
|
|
|
|
div {
|
|
font-weight: bold;
|
|
}
|
|
|
|
becomes:
|
|
|
|
div[x-foo] {
|
|
font-weight: bold;
|
|
}
|
|
|
|
Note that elements that are dynamically added to a scope must have the scope
|
|
selector added to them manually.
|
|
|
|
* upper/lower bound encapsulation: Styles which are defined outside a
|
|
shadowRoot should not cross the ShadowDOM boundary and should not apply
|
|
inside a shadowRoot.
|
|
|
|
This styling behavior is not emulated. Some possible ways to do this that
|
|
were rejected due to complexity and/or performance concerns include: (1) reset
|
|
every possible property for every possible selector for a given scope name;
|
|
(2) re-implement css in javascript.
|
|
|
|
As an alternative, users should make sure to use selectors
|
|
specific to the scope in which they are working.
|
|
|
|
* ::distributed: This behavior is not emulated. It's often not necessary
|
|
to style the contents of a specific insertion point and instead, descendants
|
|
of the host element can be styled selectively. Users can also create an
|
|
extra node around an insertion point and style that node's contents
|
|
via descendent selectors. For example, with a shadowRoot like this:
|
|
|
|
<style>
|
|
::content(div) {
|
|
background: red;
|
|
}
|
|
</style>
|
|
<content></content>
|
|
|
|
could become:
|
|
|
|
<style>
|
|
/ *@polyfill .content-container div * /
|
|
::content(div) {
|
|
background: red;
|
|
}
|
|
</style>
|
|
<div class="content-container">
|
|
<content></content>
|
|
</div>
|
|
|
|
Note the use of @polyfill in the comment above a ShadowDOM specific style
|
|
declaration. This is a directive to the styling shim to use the selector
|
|
in comments in lieu of the next selector when running under polyfill.
|
|
*/
|
|
(function(scope) {
|
|
|
|
var ShadowCSS = {
|
|
strictStyling: false,
|
|
registry: {},
|
|
// Shim styles for a given root associated with a name and extendsName
|
|
// 1. cache root styles by name
|
|
// 2. optionally tag root nodes with scope name
|
|
// 3. shim polyfill directives /* @polyfill */ and /* @polyfill-rule */
|
|
// 4. shim :host and scoping
|
|
shimStyling: function(root, name, extendsName) {
|
|
var scopeStyles = this.prepareRoot(root, name, extendsName);
|
|
var typeExtension = this.isTypeExtension(extendsName);
|
|
var scopeSelector = this.makeScopeSelector(name, typeExtension);
|
|
// use caching to make working with styles nodes easier and to facilitate
|
|
// lookup of extendee
|
|
var cssText = stylesToCssText(scopeStyles, true);
|
|
cssText = this.scopeCssText(cssText, scopeSelector);
|
|
// cache shimmed css on root for user extensibility
|
|
if (root) {
|
|
root.shimmedStyle = cssText;
|
|
}
|
|
// add style to document
|
|
this.addCssToDocument(cssText, name);
|
|
},
|
|
/*
|
|
* Shim a style element with the given selector. Returns cssText that can
|
|
* be included in the document via Platform.ShadowCSS.addCssToDocument(css).
|
|
*/
|
|
shimStyle: function(style, selector) {
|
|
return this.shimCssText(style.textContent, selector);
|
|
},
|
|
/*
|
|
* Shim some cssText with the given selector. Returns cssText that can
|
|
* be included in the document via Platform.ShadowCSS.addCssToDocument(css).
|
|
*/
|
|
shimCssText: function(cssText, selector) {
|
|
cssText = this.insertDirectives(cssText);
|
|
return this.scopeCssText(cssText, selector);
|
|
},
|
|
makeScopeSelector: function(name, typeExtension) {
|
|
if (name) {
|
|
return typeExtension ? '[is=' + name + ']' : name;
|
|
}
|
|
return '';
|
|
},
|
|
isTypeExtension: function(extendsName) {
|
|
return extendsName && extendsName.indexOf('-') < 0;
|
|
},
|
|
prepareRoot: function(root, name, extendsName) {
|
|
var def = this.registerRoot(root, name, extendsName);
|
|
this.replaceTextInStyles(def.rootStyles, this.insertDirectives);
|
|
// remove existing style elements
|
|
this.removeStyles(root, def.rootStyles);
|
|
// apply strict attr
|
|
if (this.strictStyling) {
|
|
this.applyScopeToContent(root, name);
|
|
}
|
|
return def.scopeStyles;
|
|
},
|
|
removeStyles: function(root, styles) {
|
|
for (var i=0, l=styles.length, s; (i<l) && (s=styles[i]); i++) {
|
|
s.parentNode.removeChild(s);
|
|
}
|
|
},
|
|
registerRoot: function(root, name, extendsName) {
|
|
var def = this.registry[name] = {
|
|
root: root,
|
|
name: name,
|
|
extendsName: extendsName
|
|
}
|
|
var styles = this.findStyles(root);
|
|
def.rootStyles = styles;
|
|
def.scopeStyles = def.rootStyles;
|
|
var extendee = this.registry[def.extendsName];
|
|
if (extendee) {
|
|
def.scopeStyles = extendee.scopeStyles.concat(def.scopeStyles);
|
|
}
|
|
return def;
|
|
},
|
|
findStyles: function(root) {
|
|
if (!root) {
|
|
return [];
|
|
}
|
|
var styles = root.querySelectorAll('style');
|
|
return Array.prototype.filter.call(styles, function(s) {
|
|
return !s.hasAttribute(NO_SHIM_ATTRIBUTE);
|
|
});
|
|
},
|
|
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);
|
|
}
|
|
},
|
|
insertDirectives: function(cssText) {
|
|
cssText = this.insertPolyfillDirectivesInCssText(cssText);
|
|
return this.insertPolyfillRulesInCssText(cssText);
|
|
},
|
|
/*
|
|
* Process styles to convert native ShadowDOM rules that will trip
|
|
* up the css parser; we rely on decorating the stylesheet with inert rules.
|
|
*
|
|
* For example, we convert this rule:
|
|
*
|
|
* polyfill-next-selector { content: ':host menu-item'; }
|
|
* ::content menu-item {
|
|
*
|
|
* to this:
|
|
*
|
|
* scopeName menu-item {
|
|
*
|
|
**/
|
|
insertPolyfillDirectivesInCssText: function(cssText) {
|
|
// TODO(sorvell): remove either content or comment
|
|
cssText = cssText.replace(cssCommentNextSelectorRe, function(match, p1) {
|
|
// remove end comment delimiter and add block start
|
|
return p1.slice(0, -2) + '{';
|
|
});
|
|
return cssText.replace(cssContentNextSelectorRe, function(match, p1) {
|
|
return p1 + ' {';
|
|
});
|
|
},
|
|
/*
|
|
* Process styles to add rules which will only apply under the polyfill
|
|
*
|
|
* For example, we convert this rule:
|
|
*
|
|
* polyfill-rule {
|
|
* content: ':host menu-item';
|
|
* ...
|
|
* }
|
|
*
|
|
* to this:
|
|
*
|
|
* scopeName menu-item {...}
|
|
*
|
|
**/
|
|
insertPolyfillRulesInCssText: function(cssText) {
|
|
// TODO(sorvell): remove either content or comment
|
|
cssText = cssText.replace(cssCommentRuleRe, function(match, p1) {
|
|
// remove end comment delimiter
|
|
return p1.slice(0, -1);
|
|
});
|
|
return cssText.replace(cssContentRuleRe, function(match, p1, p2, p3) {
|
|
var rule = match.replace(p1, '').replace(p2, '');
|
|
return p3 + rule;
|
|
});
|
|
},
|
|
/* Ensure styles are scoped. Pseudo-scoping takes a rule like:
|
|
*
|
|
* .foo {... }
|
|
*
|
|
* and converts this to
|
|
*
|
|
* scopeName .foo { ... }
|
|
*/
|
|
scopeCssText: function(cssText, scopeSelector) {
|
|
var unscoped = this.extractUnscopedRulesFromCssText(cssText);
|
|
cssText = this.insertPolyfillHostInCssText(cssText);
|
|
cssText = this.convertColonHost(cssText);
|
|
cssText = this.convertColonHostContext(cssText);
|
|
cssText = this.convertCombinators(cssText);
|
|
if (scopeSelector) {
|
|
var self = this, cssText;
|
|
withCssRules(cssText, function(rules) {
|
|
cssText = self.scopeRules(rules, scopeSelector);
|
|
});
|
|
|
|
}
|
|
cssText = cssText + '\n' + unscoped;
|
|
return cssText.trim();
|
|
},
|
|
/*
|
|
* 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 {...}
|
|
*
|
|
**/
|
|
extractUnscopedRulesFromCssText: function(cssText) {
|
|
// TODO(sorvell): remove either content or comment
|
|
var r = '', m;
|
|
while (m = cssCommentUnscopedRuleRe.exec(cssText)) {
|
|
r += m[1].slice(0, -1) + '\n\n';
|
|
}
|
|
while (m = cssContentUnscopedRuleRe.exec(cssText)) {
|
|
r += m[0].replace(m[2], '').replace(m[1], m[3]) + '\n\n';
|
|
}
|
|
return r;
|
|
},
|
|
/*
|
|
* 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 :host-context(.foo) > .bar { }
|
|
*
|
|
* to
|
|
*
|
|
* scopeName.foo > .bar, .foo scopeName > .bar { }
|
|
*
|
|
* and
|
|
*
|
|
* :host-context(.foo:host) .bar { ... }
|
|
*
|
|
* to
|
|
*
|
|
* scopeName.foo .bar { ... }
|
|
*/
|
|
convertColonHostContext: function(cssText) {
|
|
return this.convertColonRule(cssText, cssColonHostContextRe,
|
|
this.colonHostContextPartReplacer);
|
|
},
|
|
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;
|
|
}
|
|
});
|
|
},
|
|
colonHostContextPartReplacer: 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) {
|
|
for (var i=0; i < combinatorsRe.length; i++) {
|
|
cssText = cssText.replace(combinatorsRe[i], ' ');
|
|
}
|
|
return cssText;
|
|
},
|
|
// change a selector like 'div' to 'name div'
|
|
scopeRules: function(cssRules, scopeSelector) {
|
|
var cssText = '';
|
|
if (cssRules) {
|
|
Array.prototype.forEach.call(cssRules, function(rule) {
|
|
if (rule.selectorText && (rule.style && rule.style.cssText !== undefined)) {
|
|
cssText += this.scopeSelector(rule.selectorText, scopeSelector,
|
|
this.strictStyling) + ' {\n\t';
|
|
cssText += this.propertiesFromRule(rule) + '\n}\n\n';
|
|
} else if (rule.type === CSSRule.MEDIA_RULE) {
|
|
cssText += '@media ' + rule.media.mediaText + ' {\n';
|
|
cssText += this.scopeRules(rule.cssRules, scopeSelector);
|
|
cssText += '\n}\n\n';
|
|
} else {
|
|
// TODO(sjmiles): KEYFRAMES_RULE in IE11 throws when we query cssText
|
|
// 'cssText' in rule returns true, but rule.cssText throws anyway
|
|
// We can test the rule type, e.g.
|
|
// else if (rule.type !== CSSRule.KEYFRAMES_RULE && rule.cssText) {
|
|
// but this will prevent cssText propagation in other browsers which
|
|
// support it.
|
|
// KEYFRAMES_RULE has a CSSRuleSet, so the text can probably be reconstructed
|
|
// from that collection; this would be a proper fix.
|
|
// For now, I'm trapping the exception so IE11 is unblocked in other areas.
|
|
try {
|
|
if (rule.cssText) {
|
|
cssText += rule.cssText + '\n\n';
|
|
}
|
|
} catch(x) {
|
|
// squelch
|
|
}
|
|
}
|
|
}, this);
|
|
}
|
|
return cssText;
|
|
},
|
|
scopeSelector: function(selector, scopeSelector, strict) {
|
|
var r = [], parts = selector.split(',');
|
|
parts.forEach(function(p) {
|
|
p = p.trim();
|
|
if (this.selectorNeedsScoping(p, scopeSelector)) {
|
|
p = (strict && !p.match(polyfillHostNoCombinator)) ?
|
|
this.applyStrictSelectorScope(p, scopeSelector) :
|
|
this.applySelectorScope(p, scopeSelector);
|
|
}
|
|
r.push(p);
|
|
}, this);
|
|
return r.join(', ');
|
|
},
|
|
selectorNeedsScoping: function(selector, scopeSelector) {
|
|
if (Array.isArray(scopeSelector)) {
|
|
return true;
|
|
}
|
|
var re = this.makeScopeMatcher(scopeSelector);
|
|
return !selector.match(re);
|
|
},
|
|
makeScopeMatcher: function(scopeSelector) {
|
|
scopeSelector = scopeSelector.replace(/\[/g, '\\[').replace(/\[/g, '\\]');
|
|
return new RegExp('^(' + scopeSelector + ')' + selectorReSuffix, 'm');
|
|
},
|
|
applySelectorScope: function(selector, selectorScope) {
|
|
return Array.isArray(selectorScope) ?
|
|
this.applySelectorScopeList(selector, selectorScope) :
|
|
this.applySimpleSelectorScope(selector, selectorScope);
|
|
},
|
|
// apply an array of selectors
|
|
applySelectorScopeList: function(selector, scopeSelectorList) {
|
|
var r = [];
|
|
for (var i=0, s; (s=scopeSelectorList[i]); i++) {
|
|
r.push(this.applySimpleSelectorScope(selector, s));
|
|
}
|
|
return r.join(', ');
|
|
},
|
|
// scope via name and [is=name]
|
|
applySimpleSelectorScope: function(selector, scopeSelector) {
|
|
if (selector.match(polyfillHostRe)) {
|
|
selector = selector.replace(polyfillHostNoCombinator, scopeSelector);
|
|
return selector.replace(polyfillHostRe, scopeSelector + ' ');
|
|
} else {
|
|
return scopeSelector + ' ' + 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, scopeSelector) {
|
|
scopeSelector = scopeSelector.replace(/\[is=([^\]]*)\]/g, '$1');
|
|
var splits = [' ', '>', '+', '~'],
|
|
scoped = selector,
|
|
attrName = '[' + scopeSelector + ']';
|
|
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(colonHostContextRe, polyfillHostContext).replace(
|
|
colonHostRe, polyfillHost);
|
|
},
|
|
propertiesFromRule: function(rule) {
|
|
var cssText = rule.style.cssText;
|
|
// TODO(sorvell): Safari cssom incorrectly removes quotes from the content
|
|
// property. (https://bugs.webkit.org/show_bug.cgi?id=118045)
|
|
// don't replace attr rules
|
|
if (rule.style.content && !rule.style.content.match(/['"]+|attr/)) {
|
|
cssText = cssText.replace(/content:[^;]*;/g, 'content: \'' +
|
|
rule.style.content + '\';');
|
|
}
|
|
// TODO(sorvell): we can workaround this issue here, but we need a list
|
|
// of troublesome properties to fix https://github.com/Polymer/platform/issues/53
|
|
//
|
|
// inherit rules can be omitted from cssText
|
|
// TODO(sorvell): remove when Blink bug is fixed:
|
|
// https://code.google.com/p/chromium/issues/detail?id=358273
|
|
var style = rule.style;
|
|
for (var i in style) {
|
|
if (style[i] === 'initial') {
|
|
cssText += i + ': initial; ';
|
|
}
|
|
}
|
|
return cssText;
|
|
},
|
|
replaceTextInStyles: function(styles, action) {
|
|
if (styles && action) {
|
|
if (!(styles instanceof Array)) {
|
|
styles = [styles];
|
|
}
|
|
Array.prototype.forEach.call(styles, function(s) {
|
|
s.textContent = action.call(this, s.textContent);
|
|
}, this);
|
|
}
|
|
},
|
|
addCssToDocument: function(cssText, name) {
|
|
if (cssText.match('@import')) {
|
|
addOwnSheet(cssText, name);
|
|
} else {
|
|
addCssToDocument(cssText);
|
|
}
|
|
}
|
|
};
|
|
|
|
var selectorRe = /([^{]*)({[\s\S]*?})/gim,
|
|
cssCommentRe = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,
|
|
// TODO(sorvell): remove either content or comment
|
|
cssCommentNextSelectorRe = /\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^{]*?){/gim,
|
|
cssContentNextSelectorRe = /polyfill-next-selector[^}]*content\:[\s]*'([^']*)'[^}]*}([^{]*?){/gim,
|
|
// TODO(sorvell): remove either content or comment
|
|
cssCommentRuleRe = /\/\*\s@polyfill-rule([^*]*\*+([^/*][^*]*\*+)*)\//gim,
|
|
cssContentRuleRe = /(polyfill-rule)[^}]*(content\:[\s]*'([^']*)'[^;]*;)[^}]*}/gim,
|
|
// TODO(sorvell): remove either content or comment
|
|
cssCommentUnscopedRuleRe = /\/\*\s@polyfill-unscoped-rule([^*]*\*+([^/*][^*]*\*+)*)\//gim,
|
|
cssContentUnscopedRuleRe = /(polyfill-unscoped-rule)[^}]*(content\:[\s]*'([^']*)'[^;]*;)[^}]*}/gim,
|
|
cssPseudoRe = /::(x-[^\s{,(]*)/gim,
|
|
cssPartRe = /::part\(([^)]*)\)/gim,
|
|
// note: :host pre-processed to -shadowcsshost.
|
|
polyfillHost = '-shadowcsshost',
|
|
// note: :host-context pre-processed to -shadowcsshostcontext.
|
|
polyfillHostContext = '-shadowcsscontext',
|
|
parenSuffix = ')(?:\\((' +
|
|
'(?:\\([^)(]*\\)|[^)(]*)+?' +
|
|
')\\))?([^,{]*)';
|
|
cssColonHostRe = new RegExp('(' + polyfillHost + parenSuffix, 'gim'),
|
|
cssColonHostContextRe = new RegExp('(' + polyfillHostContext + parenSuffix, 'gim'),
|
|
selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$',
|
|
colonHostRe = /\:host/gim,
|
|
colonHostContextRe = /\:host-context/gim,
|
|
/* host name without combinator */
|
|
polyfillHostNoCombinator = polyfillHost + '-no-combinator',
|
|
polyfillHostRe = new RegExp(polyfillHost, 'gim'),
|
|
polyfillHostContextRe = new RegExp(polyfillHostContext, 'gim'),
|
|
combinatorsRe = [
|
|
/\^\^/g,
|
|
/\^/g,
|
|
/\/shadow\//g,
|
|
/\/shadow-deep\//g,
|
|
/::shadow/g,
|
|
/\/deep\//g
|
|
];
|
|
|
|
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 = [];
|
|
if (style.sheet) {
|
|
// TODO(sorvell): Firefox throws when accessing the rules of a stylesheet
|
|
// with an @import
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=625013
|
|
try {
|
|
rules = style.sheet.cssRules;
|
|
} catch(e) {
|
|
//
|
|
}
|
|
} else {
|
|
console.warn('sheet not found', style);
|
|
}
|
|
style.parentNode.removeChild(style);
|
|
return rules;
|
|
}
|
|
|
|
var frame = document.createElement('iframe');
|
|
frame.style.display = 'none';
|
|
|
|
function initFrame() {
|
|
frame.initialized = true;
|
|
document.body.appendChild(frame);
|
|
var doc = frame.contentDocument;
|
|
var base = doc.createElement('base');
|
|
base.href = document.baseURI;
|
|
doc.head.appendChild(base);
|
|
}
|
|
|
|
function inFrame(fn) {
|
|
if (!frame.initialized) {
|
|
initFrame();
|
|
}
|
|
document.body.appendChild(frame);
|
|
fn(frame.contentDocument);
|
|
document.body.removeChild(frame);
|
|
}
|
|
|
|
// TODO(sorvell): use an iframe if the cssText contains an @import to workaround
|
|
// https://code.google.com/p/chromium/issues/detail?id=345114
|
|
var isChrome = navigator.userAgent.match('Chrome');
|
|
function withCssRules(cssText, callback) {
|
|
if (!callback) {
|
|
return;
|
|
}
|
|
var rules;
|
|
if (cssText.match('@import') && isChrome) {
|
|
var style = cssTextToStyle(cssText);
|
|
inFrame(function(doc) {
|
|
doc.head.appendChild(style.impl);
|
|
rules = style.sheet.cssRules;
|
|
callback(rules);
|
|
});
|
|
} else {
|
|
rules = cssToRules(cssText);
|
|
callback(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));
|
|
}
|
|
}
|
|
|
|
function addOwnSheet(cssText, name) {
|
|
var style = cssTextToStyle(cssText);
|
|
style.setAttribute(name, '');
|
|
style.setAttribute(SHIMMED_ATTRIBUTE, '');
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
var SHIM_ATTRIBUTE = 'shim-shadowdom';
|
|
var SHIMMED_ATTRIBUTE = 'shim-shadowdom-css';
|
|
var NO_SHIM_ATTRIBUTE = 'no-shim';
|
|
|
|
var sheet;
|
|
function getSheet() {
|
|
if (!sheet) {
|
|
sheet = document.createElement("style");
|
|
sheet.setAttribute(SHIMMED_ATTRIBUTE, '');
|
|
sheet[SHIMMED_ATTRIBUTE] = true;
|
|
}
|
|
return sheet;
|
|
}
|
|
|
|
// add polyfill stylesheet to document
|
|
if (window.ShadowDOMPolyfill) {
|
|
addCssToDocument('style { display: none !important; }\n');
|
|
var doc = wrap(document);
|
|
var head = doc.querySelector('head');
|
|
head.insertBefore(getSheet(), head.childNodes[0]);
|
|
|
|
// TODO(sorvell): monkey-patching HTMLImports is abusive;
|
|
// consider a better solution.
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
var urlResolver = scope.urlResolver;
|
|
|
|
if (window.HTMLImports && !HTMLImports.useNative) {
|
|
var SHIM_SHEET_SELECTOR = 'link[rel=stylesheet]' +
|
|
'[' + SHIM_ATTRIBUTE + ']';
|
|
var SHIM_STYLE_SELECTOR = 'style[' + SHIM_ATTRIBUTE + ']';
|
|
HTMLImports.importer.documentPreloadSelectors += ',' + SHIM_SHEET_SELECTOR;
|
|
HTMLImports.importer.importsPreloadSelectors += ',' + SHIM_SHEET_SELECTOR;
|
|
|
|
HTMLImports.parser.documentSelectors = [
|
|
HTMLImports.parser.documentSelectors,
|
|
SHIM_SHEET_SELECTOR,
|
|
SHIM_STYLE_SELECTOR
|
|
].join(',');
|
|
|
|
var originalParseGeneric = HTMLImports.parser.parseGeneric;
|
|
|
|
HTMLImports.parser.parseGeneric = function(elt) {
|
|
if (elt[SHIMMED_ATTRIBUTE]) {
|
|
return;
|
|
}
|
|
var style = elt.__importElement || elt;
|
|
if (!style.hasAttribute(SHIM_ATTRIBUTE)) {
|
|
originalParseGeneric.call(this, elt);
|
|
return;
|
|
}
|
|
if (elt.__resource) {
|
|
style = elt.ownerDocument.createElement('style');
|
|
style.textContent = urlResolver.resolveCssText(
|
|
elt.__resource, elt.href);
|
|
} else {
|
|
urlResolver.resolveStyle(style);
|
|
}
|
|
style.textContent = ShadowCSS.shimStyle(style);
|
|
style.removeAttribute(SHIM_ATTRIBUTE, '');
|
|
style.setAttribute(SHIMMED_ATTRIBUTE, '');
|
|
style[SHIMMED_ATTRIBUTE] = true;
|
|
// place in document
|
|
if (style.parentNode !== head) {
|
|
// replace links in head
|
|
if (elt.parentNode === head) {
|
|
head.replaceChild(style, elt);
|
|
} else {
|
|
head.appendChild(style);
|
|
}
|
|
}
|
|
style.__importParsed = true;
|
|
this.markParsingComplete(elt);
|
|
this.parseNext();
|
|
}
|
|
|
|
var hasResource = HTMLImports.parser.hasResource;
|
|
HTMLImports.parser.hasResource = function(node) {
|
|
if (node.localName === 'link' && node.rel === 'stylesheet' &&
|
|
node.hasAttribute(SHIM_ATTRIBUTE)) {
|
|
return (node.__resource);
|
|
} else {
|
|
return hasResource.call(this, node);
|
|
}
|
|
}
|
|
|
|
}
|
|
});
|
|
}
|
|
|
|
// exports
|
|
scope.ShadowCSS = ShadowCSS;
|
|
|
|
})(window.Platform);
|
|
|
|
} else {
|
|
/*
|
|
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
* Code distributed by Google as part of the polymer project is also
|
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
*/
|
|
|
|
(function(scope) {
|
|
|
|
// so we can call wrap/unwrap without testing for ShadowDOMPolyfill
|
|
window.wrap = window.unwrap = function(n){
|
|
return n;
|
|
}
|
|
|
|
addEventListener('DOMContentLoaded', function() {
|
|
if (CustomElements.useNative === false) {
|
|
var originalCreateShadowRoot = Element.prototype.createShadowRoot;
|
|
Element.prototype.createShadowRoot = function() {
|
|
var root = originalCreateShadowRoot.call(this);
|
|
CustomElements.watchShadow(this);
|
|
return root;
|
|
};
|
|
}
|
|
});
|
|
|
|
Platform.templateContent = function(inTemplate) {
|
|
// if MDV exists, it may need to boostrap this template to reveal content
|
|
if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) {
|
|
HTMLTemplateElement.bootstrap(inTemplate);
|
|
}
|
|
// fallback when there is no Shadow DOM polyfill, no MDV polyfill, and no
|
|
// native template support
|
|
if (!inTemplate.content && !inTemplate._content) {
|
|
var frag = document.createDocumentFragment();
|
|
while (inTemplate.firstChild) {
|
|
frag.appendChild(inTemplate.firstChild);
|
|
}
|
|
inTemplate._content = frag;
|
|
}
|
|
return inTemplate.content || inTemplate._content;
|
|
};
|
|
|
|
})(window.Platform);
|
|
|
|
}
|
|
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
(function(scope) {
|
|
'use strict';
|
|
|
|
// feature detect for URL constructor
|
|
var hasWorkingUrl = false;
|
|
if (!scope.forceJURL) {
|
|
try {
|
|
var u = new URL('b', 'http://a');
|
|
hasWorkingUrl = u.href === 'http://a/b';
|
|
} catch(e) {}
|
|
}
|
|
|
|
if (hasWorkingUrl)
|
|
return;
|
|
|
|
var relative = Object.create(null);
|
|
relative['ftp'] = 21;
|
|
relative['file'] = 0;
|
|
relative['gopher'] = 70;
|
|
relative['http'] = 80;
|
|
relative['https'] = 443;
|
|
relative['ws'] = 80;
|
|
relative['wss'] = 443;
|
|
|
|
var relativePathDotMapping = Object.create(null);
|
|
relativePathDotMapping['%2e'] = '.';
|
|
relativePathDotMapping['.%2e'] = '..';
|
|
relativePathDotMapping['%2e.'] = '..';
|
|
relativePathDotMapping['%2e%2e'] = '..';
|
|
|
|
function isRelativeScheme(scheme) {
|
|
return relative[scheme] !== undefined;
|
|
}
|
|
|
|
function invalid() {
|
|
clear.call(this);
|
|
this._isInvalid = true;
|
|
}
|
|
|
|
function IDNAToASCII(h) {
|
|
if ('' == h) {
|
|
invalid.call(this)
|
|
}
|
|
// XXX
|
|
return h.toLowerCase()
|
|
}
|
|
|
|
function percentEscape(c) {
|
|
var unicode = c.charCodeAt(0);
|
|
if (unicode > 0x20 &&
|
|
unicode < 0x7F &&
|
|
// " # < > ? `
|
|
[0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) == -1
|
|
) {
|
|
return c;
|
|
}
|
|
return encodeURIComponent(c);
|
|
}
|
|
|
|
function percentEscapeQuery(c) {
|
|
// XXX This actually needs to encode c using encoding and then
|
|
// convert the bytes one-by-one.
|
|
|
|
var unicode = c.charCodeAt(0);
|
|
if (unicode > 0x20 &&
|
|
unicode < 0x7F &&
|
|
// " # < > ` (do not escape '?')
|
|
[0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) == -1
|
|
) {
|
|
return c;
|
|
}
|
|
return encodeURIComponent(c);
|
|
}
|
|
|
|
var EOF = undefined,
|
|
ALPHA = /[a-zA-Z]/,
|
|
ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/;
|
|
|
|
function parse(input, stateOverride, base) {
|
|
function err(message) {
|
|
errors.push(message)
|
|
}
|
|
|
|
var state = stateOverride || 'scheme start',
|
|
cursor = 0,
|
|
buffer = '',
|
|
seenAt = false,
|
|
seenBracket = false,
|
|
errors = [];
|
|
|
|
loop: while ((input[cursor - 1] != EOF || cursor == 0) && !this._isInvalid) {
|
|
var c = input[cursor];
|
|
switch (state) {
|
|
case 'scheme start':
|
|
if (c && ALPHA.test(c)) {
|
|
buffer += c.toLowerCase(); // ASCII-safe
|
|
state = 'scheme';
|
|
} else if (!stateOverride) {
|
|
buffer = '';
|
|
state = 'no scheme';
|
|
continue;
|
|
} else {
|
|
err('Invalid scheme.');
|
|
break loop;
|
|
}
|
|
break;
|
|
|
|
case 'scheme':
|
|
if (c && ALPHANUMERIC.test(c)) {
|
|
buffer += c.toLowerCase(); // ASCII-safe
|
|
} else if (':' == c) {
|
|
this._scheme = buffer;
|
|
buffer = '';
|
|
if (stateOverride) {
|
|
break loop;
|
|
}
|
|
if (isRelativeScheme(this._scheme)) {
|
|
this._isRelative = true;
|
|
}
|
|
if ('file' == this._scheme) {
|
|
state = 'relative';
|
|
} else if (this._isRelative && base && base._scheme == this._scheme) {
|
|
state = 'relative or authority';
|
|
} else if (this._isRelative) {
|
|
state = 'authority first slash';
|
|
} else {
|
|
state = 'scheme data';
|
|
}
|
|
} else if (!stateOverride) {
|
|
buffer = '';
|
|
cursor = 0;
|
|
state = 'no scheme';
|
|
continue;
|
|
} else if (EOF == c) {
|
|
break loop;
|
|
} else {
|
|
err('Code point not allowed in scheme: ' + c)
|
|
break loop;
|
|
}
|
|
break;
|
|
|
|
case 'scheme data':
|
|
if ('?' == c) {
|
|
query = '?';
|
|
state = 'query';
|
|
} else if ('#' == c) {
|
|
this._fragment = '#';
|
|
state = 'fragment';
|
|
} else {
|
|
// XXX error handling
|
|
if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
|
|
this._schemeData += percentEscape(c);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'no scheme':
|
|
if (!base || !(isRelativeScheme(base._scheme))) {
|
|
err('Missing scheme.');
|
|
invalid.call(this);
|
|
} else {
|
|
state = 'relative';
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case 'relative or authority':
|
|
if ('/' == c && '/' == input[cursor+1]) {
|
|
state = 'authority ignore slashes';
|
|
} else {
|
|
err('Expected /, got: ' + c);
|
|
state = 'relative';
|
|
continue
|
|
}
|
|
break;
|
|
|
|
case 'relative':
|
|
this._isRelative = true;
|
|
if ('file' != this._scheme)
|
|
this._scheme = base._scheme;
|
|
if (EOF == c) {
|
|
this._host = base._host;
|
|
this._port = base._port;
|
|
this._path = base._path.slice();
|
|
this._query = base._query;
|
|
break loop;
|
|
} else if ('/' == c || '\\' == c) {
|
|
if ('\\' == c)
|
|
err('\\ is an invalid code point.');
|
|
state = 'relative slash';
|
|
} else if ('?' == c) {
|
|
this._host = base._host;
|
|
this._port = base._port;
|
|
this._path = base._path.slice();
|
|
this._query = '?';
|
|
state = 'query';
|
|
} else if ('#' == c) {
|
|
this._host = base._host;
|
|
this._port = base._port;
|
|
this._path = base._path.slice();
|
|
this._query = base._query;
|
|
this._fragment = '#';
|
|
state = 'fragment';
|
|
} else {
|
|
var nextC = input[cursor+1]
|
|
var nextNextC = input[cursor+2]
|
|
if (
|
|
'file' != this._scheme || !ALPHA.test(c) ||
|
|
(nextC != ':' && nextC != '|') ||
|
|
(EOF != nextNextC && '/' != nextNextC && '\\' != nextNextC && '?' != nextNextC && '#' != nextNextC)) {
|
|
this._host = base._host;
|
|
this._port = base._port;
|
|
this._path = base._path.slice();
|
|
this._path.pop();
|
|
}
|
|
state = 'relative path';
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case 'relative slash':
|
|
if ('/' == c || '\\' == c) {
|
|
if ('\\' == c) {
|
|
err('\\ is an invalid code point.');
|
|
}
|
|
if ('file' == this._scheme) {
|
|
state = 'file host';
|
|
} else {
|
|
state = 'authority ignore slashes';
|
|
}
|
|
} else {
|
|
if ('file' != this._scheme) {
|
|
this._host = base._host;
|
|
this._port = base._port;
|
|
}
|
|
state = 'relative path';
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case 'authority first slash':
|
|
if ('/' == c) {
|
|
state = 'authority second slash';
|
|
} else {
|
|
err("Expected '/', got: " + c);
|
|
state = 'authority ignore slashes';
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case 'authority second slash':
|
|
state = 'authority ignore slashes';
|
|
if ('/' != c) {
|
|
err("Expected '/', got: " + c);
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case 'authority ignore slashes':
|
|
if ('/' != c && '\\' != c) {
|
|
state = 'authority';
|
|
continue;
|
|
} else {
|
|
err('Expected authority, got: ' + c);
|
|
}
|
|
break;
|
|
|
|
case 'authority':
|
|
if ('@' == c) {
|
|
if (seenAt) {
|
|
err('@ already seen.');
|
|
buffer += '%40';
|
|
}
|
|
seenAt = true;
|
|
for (var i = 0; i < buffer.length; i++) {
|
|
var cp = buffer[i];
|
|
if ('\t' == cp || '\n' == cp || '\r' == cp) {
|
|
err('Invalid whitespace in authority.');
|
|
continue;
|
|
}
|
|
// XXX check URL code points
|
|
if (':' == cp && null === this._password) {
|
|
this._password = '';
|
|
continue;
|
|
}
|
|
var tempC = percentEscape(cp);
|
|
(null !== this._password) ? this._password += tempC : this._username += tempC;
|
|
}
|
|
buffer = '';
|
|
} else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
|
|
cursor -= buffer.length;
|
|
buffer = '';
|
|
state = 'host';
|
|
continue;
|
|
} else {
|
|
buffer += c;
|
|
}
|
|
break;
|
|
|
|
case 'file host':
|
|
if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
|
|
if (buffer.length == 2 && ALPHA.test(buffer[0]) && (buffer[1] == ':' || buffer[1] == '|')) {
|
|
state = 'relative path';
|
|
} else if (buffer.length == 0) {
|
|
state = 'relative path start';
|
|
} else {
|
|
this._host = IDNAToASCII.call(this, buffer);
|
|
buffer = '';
|
|
state = 'relative path start';
|
|
}
|
|
continue;
|
|
} else if ('\t' == c || '\n' == c || '\r' == c) {
|
|
err('Invalid whitespace in file host.');
|
|
} else {
|
|
buffer += c;
|
|
}
|
|
break;
|
|
|
|
case 'host':
|
|
case 'hostname':
|
|
if (':' == c && !seenBracket) {
|
|
// XXX host parsing
|
|
this._host = IDNAToASCII.call(this, buffer);
|
|
buffer = '';
|
|
state = 'port';
|
|
if ('hostname' == stateOverride) {
|
|
break loop;
|
|
}
|
|
} else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) {
|
|
this._host = IDNAToASCII.call(this, buffer);
|
|
buffer = '';
|
|
state = 'relative path start';
|
|
if (stateOverride) {
|
|
break loop;
|
|
}
|
|
continue;
|
|
} else if ('\t' != c && '\n' != c && '\r' != c) {
|
|
if ('[' == c) {
|
|
seenBracket = true;
|
|
} else if (']' == c) {
|
|
seenBracket = false;
|
|
}
|
|
buffer += c;
|
|
} else {
|
|
err('Invalid code point in host/hostname: ' + c);
|
|
}
|
|
break;
|
|
|
|
case 'port':
|
|
if (/[0-9]/.test(c)) {
|
|
buffer += c;
|
|
} else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c || stateOverride) {
|
|
if ('' != buffer) {
|
|
var temp = parseInt(buffer, 10);
|
|
if (temp != relative[this._scheme]) {
|
|
this._port = temp + '';
|
|
}
|
|
buffer = '';
|
|
}
|
|
if (stateOverride) {
|
|
break loop;
|
|
}
|
|
state = 'relative path start';
|
|
continue;
|
|
} else if ('\t' == c || '\n' == c || '\r' == c) {
|
|
err('Invalid code point in port: ' + c);
|
|
} else {
|
|
invalid.call(this);
|
|
}
|
|
break;
|
|
|
|
case 'relative path start':
|
|
if ('\\' == c)
|
|
err("'\\' not allowed in path.");
|
|
state = 'relative path';
|
|
if ('/' != c && '\\' != c) {
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case 'relative path':
|
|
if (EOF == c || '/' == c || '\\' == c || (!stateOverride && ('?' == c || '#' == c))) {
|
|
if ('\\' == c) {
|
|
err('\\ not allowed in relative path.');
|
|
}
|
|
var tmp;
|
|
if (tmp = relativePathDotMapping[buffer.toLowerCase()]) {
|
|
buffer = tmp;
|
|
}
|
|
if ('..' == buffer) {
|
|
this._path.pop();
|
|
if ('/' != c && '\\' != c) {
|
|
this._path.push('');
|
|
}
|
|
} else if ('.' == buffer && '/' != c && '\\' != c) {
|
|
this._path.push('');
|
|
} else if ('.' != buffer) {
|
|
if ('file' == this._scheme && this._path.length == 0 && buffer.length == 2 && ALPHA.test(buffer[0]) && buffer[1] == '|') {
|
|
buffer = buffer[0] + ':';
|
|
}
|
|
this._path.push(buffer);
|
|
}
|
|
buffer = '';
|
|
if ('?' == c) {
|
|
this._query = '?';
|
|
state = 'query';
|
|
} else if ('#' == c) {
|
|
this._fragment = '#';
|
|
state = 'fragment';
|
|
}
|
|
} else if ('\t' != c && '\n' != c && '\r' != c) {
|
|
buffer += percentEscape(c);
|
|
}
|
|
break;
|
|
|
|
case 'query':
|
|
if (!stateOverride && '#' == c) {
|
|
this._fragment = '#';
|
|
state = 'fragment';
|
|
} else if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
|
|
this._query += percentEscapeQuery(c);
|
|
}
|
|
break;
|
|
|
|
case 'fragment':
|
|
if (EOF != c && '\t' != c && '\n' != c && '\r' != c) {
|
|
this._fragment += c;
|
|
}
|
|
break;
|
|
}
|
|
|
|
cursor++;
|
|
}
|
|
}
|
|
|
|
function clear() {
|
|
this._scheme = '';
|
|
this._schemeData = '';
|
|
this._username = '';
|
|
this._password = null;
|
|
this._host = '';
|
|
this._port = '';
|
|
this._path = [];
|
|
this._query = '';
|
|
this._fragment = '';
|
|
this._isInvalid = false;
|
|
this._isRelative = false;
|
|
}
|
|
|
|
// Does not process domain names or IP addresses.
|
|
// Does not handle encoding for the query parameter.
|
|
function jURL(url, base /* , encoding */) {
|
|
if (base !== undefined && !(base instanceof jURL))
|
|
base = new jURL(String(base));
|
|
|
|
this._url = url;
|
|
clear.call(this);
|
|
|
|
var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, '');
|
|
// encoding = encoding || 'utf-8'
|
|
|
|
parse.call(this, input, null, base);
|
|
}
|
|
|
|
jURL.prototype = {
|
|
get href() {
|
|
if (this._isInvalid)
|
|
return this._url;
|
|
|
|
var authority = '';
|
|
if ('' != this._username || null != this._password) {
|
|
authority = this._username +
|
|
(null != this._password ? ':' + this._password : '') + '@';
|
|
}
|
|
|
|
return this.protocol +
|
|
(this._isRelative ? '//' + authority + this.host : '') +
|
|
this.pathname + this._query + this._fragment;
|
|
},
|
|
set href(href) {
|
|
clear.call(this);
|
|
parse.call(this, href);
|
|
},
|
|
|
|
get protocol() {
|
|
return this._scheme + ':';
|
|
},
|
|
set protocol(protocol) {
|
|
if (this._isInvalid)
|
|
return;
|
|
parse.call(this, protocol + ':', 'scheme start');
|
|
},
|
|
|
|
get host() {
|
|
return this._isInvalid ? '' : this._port ?
|
|
this._host + ':' + this._port : this._host;
|
|
},
|
|
set host(host) {
|
|
if (this._isInvalid || !this._isRelative)
|
|
return;
|
|
parse.call(this, host, 'host');
|
|
},
|
|
|
|
get hostname() {
|
|
return this._host;
|
|
},
|
|
set hostname(hostname) {
|
|
if (this._isInvalid || !this._isRelative)
|
|
return;
|
|
parse.call(this, hostname, 'hostname');
|
|
},
|
|
|
|
get port() {
|
|
return this._port;
|
|
},
|
|
set port(port) {
|
|
if (this._isInvalid || !this._isRelative)
|
|
return;
|
|
parse.call(this, port, 'port');
|
|
},
|
|
|
|
get pathname() {
|
|
return this._isInvalid ? '' : this._isRelative ?
|
|
'/' + this._path.join('/') : this._schemeData;
|
|
},
|
|
set pathname(pathname) {
|
|
if (this._isInvalid || !this._isRelative)
|
|
return;
|
|
this._path = [];
|
|
parse.call(this, pathname, 'relative path start');
|
|
},
|
|
|
|
get search() {
|
|
return this._isInvalid || !this._query || '?' == this._query ?
|
|
'' : this._query;
|
|
},
|
|
set search(search) {
|
|
if (this._isInvalid || !this._isRelative)
|
|
return;
|
|
this._query = '?';
|
|
if ('?' == search[0])
|
|
search = search.slice(1);
|
|
parse.call(this, search, 'query');
|
|
},
|
|
|
|
get hash() {
|
|
return this._isInvalid || !this._fragment || '#' == this._fragment ?
|
|
'' : this._fragment;
|
|
},
|
|
set hash(hash) {
|
|
if (this._isInvalid)
|
|
return;
|
|
this._fragment = '#';
|
|
if ('#' == hash[0])
|
|
hash = hash.slice(1);
|
|
parse.call(this, hash, 'fragment');
|
|
}
|
|
};
|
|
|
|
scope.URL = jURL;
|
|
|
|
})(window);
|
|
|
|
/*
|
|
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
* Code distributed by Google as part of the polymer project is also
|
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
*/
|
|
|
|
(function(scope) {
|
|
|
|
// Old versions of iOS do not have bind.
|
|
|
|
if (!Function.prototype.bind) {
|
|
Function.prototype.bind = function(scope) {
|
|
var self = this;
|
|
var args = Array.prototype.slice.call(arguments, 1);
|
|
return function() {
|
|
var args2 = args.slice();
|
|
args2.push.apply(args2, arguments);
|
|
return self.apply(scope, args2);
|
|
};
|
|
};
|
|
}
|
|
|
|
// mixin
|
|
|
|
// copy all properties from inProps (et al) to inObj
|
|
function mixin(inObj/*, inProps, inMoreProps, ...*/) {
|
|
var obj = inObj || {};
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var p = arguments[i];
|
|
try {
|
|
for (var n in p) {
|
|
copyProperty(n, p, obj);
|
|
}
|
|
} catch(x) {
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
// copy property inName from inSource object to inTarget object
|
|
function copyProperty(inName, inSource, inTarget) {
|
|
var pd = getPropertyDescriptor(inSource, inName);
|
|
Object.defineProperty(inTarget, inName, pd);
|
|
}
|
|
|
|
// get property descriptor for inName on inObject, even if
|
|
// inName exists on some link in inObject's prototype chain
|
|
function getPropertyDescriptor(inObject, inName) {
|
|
if (inObject) {
|
|
var pd = Object.getOwnPropertyDescriptor(inObject, inName);
|
|
return pd || getPropertyDescriptor(Object.getPrototypeOf(inObject), inName);
|
|
}
|
|
}
|
|
|
|
// export
|
|
|
|
scope.mixin = mixin;
|
|
|
|
})(window.Platform);
|
|
|
|
/*
|
|
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
* Code distributed by Google as part of the polymer project is also
|
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
*/
|
|
|
|
(function(scope) {
|
|
|
|
'use strict';
|
|
|
|
// polyfill DOMTokenList
|
|
// * add/remove: allow these methods to take multiple classNames
|
|
// * toggle: add a 2nd argument which forces the given state rather
|
|
// than toggling.
|
|
|
|
var add = DOMTokenList.prototype.add;
|
|
var remove = DOMTokenList.prototype.remove;
|
|
DOMTokenList.prototype.add = function() {
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
add.call(this, arguments[i]);
|
|
}
|
|
};
|
|
DOMTokenList.prototype.remove = function() {
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
remove.call(this, arguments[i]);
|
|
}
|
|
};
|
|
DOMTokenList.prototype.toggle = function(name, bool) {
|
|
if (arguments.length == 1) {
|
|
bool = !this.contains(name);
|
|
}
|
|
bool ? this.add(name) : this.remove(name);
|
|
};
|
|
DOMTokenList.prototype.switch = function(oldName, newName) {
|
|
oldName && this.remove(oldName);
|
|
newName && this.add(newName);
|
|
};
|
|
|
|
// add array() to NodeList, NamedNodeMap, HTMLCollection
|
|
|
|
var ArraySlice = function() {
|
|
return Array.prototype.slice.call(this);
|
|
};
|
|
|
|
var namedNodeMap = (window.NamedNodeMap || window.MozNamedAttrMap || {});
|
|
|
|
NodeList.prototype.array = ArraySlice;
|
|
namedNodeMap.prototype.array = ArraySlice;
|
|
HTMLCollection.prototype.array = ArraySlice;
|
|
|
|
// polyfill performance.now
|
|
|
|
if (!window.performance) {
|
|
var start = Date.now();
|
|
// only at millisecond precision
|
|
window.performance = {now: function(){ return Date.now() - start }};
|
|
}
|
|
|
|
// polyfill for requestAnimationFrame
|
|
|
|
if (!window.requestAnimationFrame) {
|
|
window.requestAnimationFrame = (function() {
|
|
var nativeRaf = window.webkitRequestAnimationFrame ||
|
|
window.mozRequestAnimationFrame;
|
|
|
|
return nativeRaf ?
|
|
function(callback) {
|
|
return nativeRaf(function() {
|
|
callback(performance.now());
|
|
});
|
|
} :
|
|
function( callback ){
|
|
return window.setTimeout(callback, 1000 / 60);
|
|
};
|
|
})();
|
|
}
|
|
|
|
if (!window.cancelAnimationFrame) {
|
|
window.cancelAnimationFrame = (function() {
|
|
return window.webkitCancelAnimationFrame ||
|
|
window.mozCancelAnimationFrame ||
|
|
function(id) {
|
|
clearTimeout(id);
|
|
};
|
|
})();
|
|
}
|
|
|
|
// utility
|
|
|
|
function createDOM(inTagOrNode, inHTML, inAttrs) {
|
|
var dom = typeof inTagOrNode == 'string' ?
|
|
document.createElement(inTagOrNode) : inTagOrNode.cloneNode(true);
|
|
dom.innerHTML = inHTML;
|
|
if (inAttrs) {
|
|
for (var n in inAttrs) {
|
|
dom.setAttribute(n, inAttrs[n]);
|
|
}
|
|
}
|
|
return dom;
|
|
}
|
|
// Make a stub for Polymer() for polyfill purposes; under the HTMLImports
|
|
// polyfill, scripts in the main document run before imports. That means
|
|
// if (1) polymer is imported and (2) Polymer() is called in the main document
|
|
// in a script after the import, 2 occurs before 1. We correct this here
|
|
// by specfiically patching Polymer(); this is not necessary under native
|
|
// HTMLImports.
|
|
var elementDeclarations = [];
|
|
|
|
var polymerStub = function(name, dictionary) {
|
|
elementDeclarations.push(arguments);
|
|
}
|
|
window.Polymer = polymerStub;
|
|
|
|
// deliver queued delcarations
|
|
scope.deliverDeclarations = function() {
|
|
scope.deliverDeclarations = function() {
|
|
throw 'Possible attempt to load Polymer twice';
|
|
};
|
|
return elementDeclarations;
|
|
}
|
|
|
|
// Once DOMContent has loaded, any main document scripts that depend on
|
|
// Polymer() should have run. Calling Polymer() now is an error until
|
|
// polymer is imported.
|
|
window.addEventListener('DOMContentLoaded', function() {
|
|
if (window.Polymer === polymerStub) {
|
|
window.Polymer = function() {
|
|
console.error('You tried to use polymer without loading it first. To ' +
|
|
'load polymer, <link rel="import" href="' +
|
|
'components/polymer/polymer.html">');
|
|
};
|
|
}
|
|
});
|
|
|
|
// exports
|
|
scope.createDOM = createDOM;
|
|
|
|
})(window.Platform);
|
|
|
|
/*
|
|
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
* Code distributed by Google as part of the polymer project is also
|
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
*/
|
|
|
|
// poor man's adapter for template.content on various platform scenarios
|
|
(function(scope) {
|
|
scope.templateContent = scope.templateContent || function(inTemplate) {
|
|
return inTemplate.content;
|
|
};
|
|
})(window.Platform);
|
|
|
|
/*
|
|
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
* Code distributed by Google as part of the polymer project is also
|
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
*/
|
|
|
|
(function(scope) {
|
|
|
|
scope = scope || (window.Inspector = {});
|
|
|
|
var inspector;
|
|
|
|
window.sinspect = function(inNode, inProxy) {
|
|
if (!inspector) {
|
|
inspector = window.open('', 'ShadowDOM Inspector', null, true);
|
|
inspector.document.write(inspectorHTML);
|
|
//inspector.document.close();
|
|
inspector.api = {
|
|
shadowize: shadowize
|
|
};
|
|
}
|
|
inspect(inNode || wrap(document.body), inProxy);
|
|
};
|
|
|
|
var inspectorHTML = [
|
|
'<!DOCTYPE html>',
|
|
'<html>',
|
|
' <head>',
|
|
' <title>ShadowDOM Inspector</title>',
|
|
' <style>',
|
|
' body {',
|
|
' }',
|
|
' pre {',
|
|
' font: 9pt "Courier New", monospace;',
|
|
' line-height: 1.5em;',
|
|
' }',
|
|
' tag {',
|
|
' color: purple;',
|
|
' }',
|
|
' ul {',
|
|
' margin: 0;',
|
|
' padding: 0;',
|
|
' list-style: none;',
|
|
' }',
|
|
' li {',
|
|
' display: inline-block;',
|
|
' background-color: #f1f1f1;',
|
|
' padding: 4px 6px;',
|
|
' border-radius: 4px;',
|
|
' margin-right: 4px;',
|
|
' }',
|
|
' </style>',
|
|
' </head>',
|
|
' <body>',
|
|
' <ul id="crumbs">',
|
|
' </ul>',
|
|
' <div id="tree"></div>',
|
|
' </body>',
|
|
'</html>'
|
|
].join('\n');
|
|
|
|
var crumbs = [];
|
|
|
|
var displayCrumbs = function() {
|
|
// alias our document
|
|
var d = inspector.document;
|
|
// get crumbbar
|
|
var cb = d.querySelector('#crumbs');
|
|
// clear crumbs
|
|
cb.textContent = '';
|
|
// build new crumbs
|
|
for (var i=0, c; c=crumbs[i]; i++) {
|
|
var a = d.createElement('a');
|
|
a.href = '#';
|
|
a.textContent = c.localName;
|
|
a.idx = i;
|
|
a.onclick = function(event) {
|
|
var c;
|
|
while (crumbs.length > this.idx) {
|
|
c = crumbs.pop();
|
|
}
|
|
inspect(c.shadow || c, c);
|
|
event.preventDefault();
|
|
};
|
|
cb.appendChild(d.createElement('li')).appendChild(a);
|
|
}
|
|
};
|
|
|
|
var inspect = function(inNode, inProxy) {
|
|
// alias our document
|
|
var d = inspector.document;
|
|
// reset list of drillable nodes
|
|
drillable = [];
|
|
// memoize our crumb proxy
|
|
var proxy = inProxy || inNode;
|
|
crumbs.push(proxy);
|
|
// update crumbs
|
|
displayCrumbs();
|
|
// reflect local tree
|
|
d.body.querySelector('#tree').innerHTML =
|
|
'<pre>' + output(inNode, inNode.childNodes) + '</pre>';
|
|
};
|
|
|
|
var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
|
|
|
|
var blacklisted = {STYLE:1, SCRIPT:1, "#comment": 1, TEMPLATE: 1};
|
|
var blacklist = function(inNode) {
|
|
return blacklisted[inNode.nodeName];
|
|
};
|
|
|
|
var output = function(inNode, inChildNodes, inIndent) {
|
|
if (blacklist(inNode)) {
|
|
return '';
|
|
}
|
|
var indent = inIndent || '';
|
|
if (inNode.localName || inNode.nodeType == 11) {
|
|
var name = inNode.localName || 'shadow-root';
|
|
//inChildNodes = ShadowDOM.localNodes(inNode);
|
|
var info = indent + describe(inNode);
|
|
// if only textNodes
|
|
// TODO(sjmiles): make correct for ShadowDOM
|
|
/*if (!inNode.children.length && inNode.localName !== 'content' && inNode.localName !== 'shadow') {
|
|
info += catTextContent(inChildNodes);
|
|
} else*/ {
|
|
// TODO(sjmiles): native <shadow> has no reference to its projection
|
|
if (name == 'content' /*|| name == 'shadow'*/) {
|
|
inChildNodes = inNode.getDistributedNodes();
|
|
}
|
|
info += '<br/>';
|
|
var ind = indent + ' ';
|
|
forEach(inChildNodes, function(n) {
|
|
info += output(n, n.childNodes, ind);
|
|
});
|
|
info += indent;
|
|
}
|
|
if (!({br:1}[name])) {
|
|
info += '<tag></' + name + '></tag>';
|
|
info += '<br/>';
|
|
}
|
|
} else {
|
|
var text = inNode.textContent.trim();
|
|
info = text ? indent + '"' + text + '"' + '<br/>' : '';
|
|
}
|
|
return info;
|
|
};
|
|
|
|
var catTextContent = function(inChildNodes) {
|
|
var info = '';
|
|
forEach(inChildNodes, function(n) {
|
|
info += n.textContent.trim();
|
|
});
|
|
return info;
|
|
};
|
|
|
|
var drillable = [];
|
|
|
|
var describe = function(inNode) {
|
|
var tag = '<tag>' + '<';
|
|
var name = inNode.localName || 'shadow-root';
|
|
if (inNode.webkitShadowRoot || inNode.shadowRoot) {
|
|
tag += ' <button idx="' + drillable.length +
|
|
'" onclick="api.shadowize.call(this)">' + name + '</button>';
|
|
drillable.push(inNode);
|
|
} else {
|
|
tag += name || 'shadow-root';
|
|
}
|
|
if (inNode.attributes) {
|
|
forEach(inNode.attributes, function(a) {
|
|
tag += ' ' + a.name + (a.value ? '="' + a.value + '"' : '');
|
|
});
|
|
}
|
|
tag += '>'+ '</tag>';
|
|
return tag;
|
|
};
|
|
|
|
// remote api
|
|
|
|
shadowize = function() {
|
|
var idx = Number(this.attributes.idx.value);
|
|
//alert(idx);
|
|
var node = drillable[idx];
|
|
if (node) {
|
|
inspect(node.webkitShadowRoot || node.shadowRoot, node)
|
|
} else {
|
|
console.log("bad shadowize node");
|
|
console.dir(this);
|
|
}
|
|
};
|
|
|
|
// export
|
|
|
|
scope.output = output;
|
|
|
|
})(window.Inspector);
|
|
|
|
/*
|
|
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
* Code distributed by Google as part of the polymer project is also
|
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
*/
|
|
|
|
(function(scope) {
|
|
|
|
// TODO(sorvell): It's desireable to provide a default stylesheet
|
|
// that's convenient for styling unresolved elements, but
|
|
// it's cumbersome to have to include this manually in every page.
|
|
// It would make sense to put inside some HTMLImport but
|
|
// the HTMLImports polyfill does not allow loading of stylesheets
|
|
// that block rendering. Therefore this injection is tolerated here.
|
|
|
|
var style = document.createElement('style');
|
|
style.textContent = ''
|
|
+ 'body {'
|
|
+ 'transition: opacity ease-in 0.2s;'
|
|
+ ' } \n'
|
|
+ 'body[unresolved] {'
|
|
+ 'opacity: 0; display: block; overflow: hidden;'
|
|
+ ' } \n'
|
|
;
|
|
var head = document.querySelector('head');
|
|
head.insertBefore(style, head.firstChild);
|
|
|
|
})(Platform);
|
|
|
|
/*
|
|
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
* Code distributed by Google as part of the polymer project is also
|
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
*/
|
|
|
|
(function(scope) {
|
|
|
|
function withDependencies(task, depends) {
|
|
depends = depends || [];
|
|
if (!depends.map) {
|
|
depends = [depends];
|
|
}
|
|
return task.apply(this, depends.map(marshal));
|
|
}
|
|
|
|
function module(name, dependsOrFactory, moduleFactory) {
|
|
var module;
|
|
switch (arguments.length) {
|
|
case 0:
|
|
return;
|
|
case 1:
|
|
module = null;
|
|
break;
|
|
case 2:
|
|
// dependsOrFactory is `factory` in this case
|
|
module = dependsOrFactory.apply(this);
|
|
break;
|
|
default:
|
|
// dependsOrFactory is `depends` in this case
|
|
module = withDependencies(moduleFactory, dependsOrFactory);
|
|
break;
|
|
}
|
|
modules[name] = module;
|
|
};
|
|
|
|
function marshal(name) {
|
|
return modules[name];
|
|
}
|
|
|
|
var modules = {};
|
|
|
|
function using(depends, task) {
|
|
HTMLImports.whenImportsReady(function() {
|
|
withDependencies(task, depends);
|
|
});
|
|
};
|
|
|
|
// exports
|
|
|
|
scope.marshal = marshal;
|
|
// `module` confuses commonjs detectors
|
|
scope.modularize = module;
|
|
scope.using = using;
|
|
|
|
})(window);
|
|
|
|
/*
|
|
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
* Code distributed by Google as part of the polymer project is also
|
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
*/
|
|
|
|
(function(scope) {
|
|
|
|
var iterations = 0;
|
|
var callbacks = [];
|
|
var twiddle = document.createTextNode('');
|
|
|
|
function endOfMicrotask(callback) {
|
|
twiddle.textContent = iterations++;
|
|
callbacks.push(callback);
|
|
}
|
|
|
|
function atEndOfMicrotask() {
|
|
while (callbacks.length) {
|
|
callbacks.shift()();
|
|
}
|
|
}
|
|
|
|
new (window.MutationObserver || JsMutationObserver)(atEndOfMicrotask)
|
|
.observe(twiddle, {characterData: true})
|
|
;
|
|
|
|
// exports
|
|
|
|
scope.endOfMicrotask = endOfMicrotask;
|
|
|
|
})(Platform);
|
|
|
|
|
|
/*
|
|
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
* Code distributed by Google as part of the polymer project is also
|
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
*/
|
|
|
|
(function(scope) {
|
|
|
|
var urlResolver = {
|
|
resolveDom: function(root, url) {
|
|
url = url || root.ownerDocument.baseURI;
|
|
this.resolveAttributes(root, url);
|
|
this.resolveStyles(root, url);
|
|
// handle template.content
|
|
var templates = root.querySelectorAll('template');
|
|
if (templates) {
|
|
for (var i = 0, l = templates.length, t; (i < l) && (t = templates[i]); i++) {
|
|
if (t.content) {
|
|
this.resolveDom(t.content, url);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
resolveTemplate: function(template) {
|
|
this.resolveDom(template.content, template.ownerDocument.baseURI);
|
|
},
|
|
resolveStyles: function(root, url) {
|
|
var styles = root.querySelectorAll('style');
|
|
if (styles) {
|
|
for (var i = 0, l = styles.length, s; (i < l) && (s = styles[i]); i++) {
|
|
this.resolveStyle(s, url);
|
|
}
|
|
}
|
|
},
|
|
resolveStyle: function(style, url) {
|
|
url = url || style.ownerDocument.baseURI;
|
|
style.textContent = this.resolveCssText(style.textContent, url);
|
|
},
|
|
resolveCssText: function(cssText, baseUrl, keepAbsolute) {
|
|
cssText = replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_URL_REGEXP);
|
|
return replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_IMPORT_REGEXP);
|
|
},
|
|
resolveAttributes: function(root, url) {
|
|
if (root.hasAttributes && root.hasAttributes()) {
|
|
this.resolveElementAttributes(root, url);
|
|
}
|
|
// search for attributes that host urls
|
|
var nodes = root && root.querySelectorAll(URL_ATTRS_SELECTOR);
|
|
if (nodes) {
|
|
for (var i = 0, l = nodes.length, n; (i < l) && (n = nodes[i]); i++) {
|
|
this.resolveElementAttributes(n, url);
|
|
}
|
|
}
|
|
},
|
|
resolveElementAttributes: function(node, url) {
|
|
url = url || node.ownerDocument.baseURI;
|
|
URL_ATTRS.forEach(function(v) {
|
|
var attr = node.attributes[v];
|
|
var value = attr && attr.value;
|
|
var replacement;
|
|
if (value && value.search(URL_TEMPLATE_SEARCH) < 0) {
|
|
if (v === 'style') {
|
|
replacement = replaceUrlsInCssText(value, url, false, CSS_URL_REGEXP);
|
|
} else {
|
|
replacement = resolveRelativeUrl(url, value);
|
|
}
|
|
attr.value = replacement;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g;
|
|
var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g;
|
|
var URL_ATTRS = ['href', 'src', 'action', 'style', 'url'];
|
|
var URL_ATTRS_SELECTOR = '[' + URL_ATTRS.join('],[') + ']';
|
|
var URL_TEMPLATE_SEARCH = '{{.*}}';
|
|
|
|
function replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, regexp) {
|
|
return cssText.replace(regexp, function(m, pre, url, post) {
|
|
var urlPath = url.replace(/["']/g, '');
|
|
urlPath = resolveRelativeUrl(baseUrl, urlPath, keepAbsolute);
|
|
return pre + '\'' + urlPath + '\'' + post;
|
|
});
|
|
}
|
|
|
|
function resolveRelativeUrl(baseUrl, url, keepAbsolute) {
|
|
// do not resolve '/' absolute urls
|
|
if (url && url[0] === '/') {
|
|
return url;
|
|
}
|
|
var u = new URL(url, baseUrl);
|
|
return keepAbsolute ? u.href : makeDocumentRelPath(u.href);
|
|
}
|
|
|
|
function makeDocumentRelPath(url) {
|
|
var root = new URL(document.baseURI);
|
|
var u = new URL(url, root);
|
|
if (u.host === root.host && u.port === root.port &&
|
|
u.protocol === root.protocol) {
|
|
return makeRelPath(root, u);
|
|
} else {
|
|
return url;
|
|
}
|
|
}
|
|
|
|
// make a relative path from source to target
|
|
function makeRelPath(sourceUrl, targetUrl) {
|
|
var source = sourceUrl.pathname;
|
|
var target = targetUrl.pathname;
|
|
var s = source.split('/');
|
|
var t = target.split('/');
|
|
while (s.length && s[0] === t[0]){
|
|
s.shift();
|
|
t.shift();
|
|
}
|
|
for (var i = 0, l = s.length - 1; i < l; i++) {
|
|
t.unshift('..');
|
|
}
|
|
return t.join('/') + targetUrl.search + targetUrl.hash;
|
|
}
|
|
|
|
// exports
|
|
scope.urlResolver = urlResolver;
|
|
|
|
})(Platform);
|
|
|
|
/*
|
|
* Copyright 2012 The Polymer Authors. All rights reserved.
|
|
* Use of this source code is goverened by a BSD-style
|
|
* license that can be found in the LICENSE file.
|
|
*/
|
|
|
|
(function(global) {
|
|
|
|
var registrationsTable = new WeakMap();
|
|
|
|
// We use setImmediate or postMessage for our future callback.
|
|
var setImmediate = window.msSetImmediate;
|
|
|
|
// Use post message to emulate setImmediate.
|
|
if (!setImmediate) {
|
|
var setImmediateQueue = [];
|
|
var sentinel = String(Math.random());
|
|
window.addEventListener('message', function(e) {
|
|
if (e.data === sentinel) {
|
|
var queue = setImmediateQueue;
|
|
setImmediateQueue = [];
|
|
queue.forEach(function(func) {
|
|
func();
|
|
});
|
|
}
|
|
});
|
|
setImmediate = function(func) {
|
|
setImmediateQueue.push(func);
|
|
window.postMessage(sentinel, '*');
|
|
};
|
|
}
|
|
|
|
// This is used to ensure that we never schedule 2 callas to setImmediate
|
|
var isScheduled = false;
|
|
|
|
// Keep track of observers that needs to be notified next time.
|
|
var scheduledObservers = [];
|
|
|
|
/**
|
|
* Schedules |dispatchCallback| to be called in the future.
|
|
* @param {MutationObserver} observer
|
|
*/
|
|
function scheduleCallback(observer) {
|
|
scheduledObservers.push(observer);
|
|
if (!isScheduled) {
|
|
isScheduled = true;
|
|
setImmediate(dispatchCallbacks);
|
|
}
|
|
}
|
|
|
|
function wrapIfNeeded(node) {
|
|
return window.ShadowDOMPolyfill &&
|
|
window.ShadowDOMPolyfill.wrapIfNeeded(node) ||
|
|
node;
|
|
}
|
|
|
|
function dispatchCallbacks() {
|
|
// http://dom.spec.whatwg.org/#mutation-observers
|
|
|
|
isScheduled = false; // Used to allow a new setImmediate call above.
|
|
|
|
var observers = scheduledObservers;
|
|
scheduledObservers = [];
|
|
// Sort observers based on their creation UID (incremental).
|
|
observers.sort(function(o1, o2) {
|
|
return o1.uid_ - o2.uid_;
|
|
});
|
|
|
|
var anyNonEmpty = false;
|
|
observers.forEach(function(observer) {
|
|
|
|
// 2.1, 2.2
|
|
var queue = observer.takeRecords();
|
|
// 2.3. Remove all transient registered observers whose observer is mo.
|
|
removeTransientObserversFor(observer);
|
|
|
|
// 2.4
|
|
if (queue.length) {
|
|
observer.callback_(queue, observer);
|
|
anyNonEmpty = true;
|
|
}
|
|
});
|
|
|
|
// 3.
|
|
if (anyNonEmpty)
|
|
dispatchCallbacks();
|
|
}
|
|
|
|
function removeTransientObserversFor(observer) {
|
|
observer.nodes_.forEach(function(node) {
|
|
var registrations = registrationsTable.get(node);
|
|
if (!registrations)
|
|
return;
|
|
registrations.forEach(function(registration) {
|
|
if (registration.observer === observer)
|
|
registration.removeTransientObservers();
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* This function is used for the "For each registered observer observer (with
|
|
* observer's options as options) in target's list of registered observers,
|
|
* run these substeps:" and the "For each ancestor ancestor of target, and for
|
|
* each registered observer observer (with options options) in ancestor's list
|
|
* of registered observers, run these substeps:" part of the algorithms. The
|
|
* |options.subtree| is checked to ensure that the callback is called
|
|
* correctly.
|
|
*
|
|
* @param {Node} target
|
|
* @param {function(MutationObserverInit):MutationRecord} callback
|
|
*/
|
|
function forEachAncestorAndObserverEnqueueRecord(target, callback) {
|
|
for (var node = target; node; node = node.parentNode) {
|
|
var registrations = registrationsTable.get(node);
|
|
|
|
if (registrations) {
|
|
for (var j = 0; j < registrations.length; j++) {
|
|
var registration = registrations[j];
|
|
var options = registration.options;
|
|
|
|
// Only target ignores subtree.
|
|
if (node !== target && !options.subtree)
|
|
continue;
|
|
|
|
var record = callback(options);
|
|
if (record)
|
|
registration.enqueue(record);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var uidCounter = 0;
|
|
|
|
/**
|
|
* The class that maps to the DOM MutationObserver interface.
|
|
* @param {Function} callback.
|
|
* @constructor
|
|
*/
|
|
function JsMutationObserver(callback) {
|
|
this.callback_ = callback;
|
|
this.nodes_ = [];
|
|
this.records_ = [];
|
|
this.uid_ = ++uidCounter;
|
|
}
|
|
|
|
JsMutationObserver.prototype = {
|
|
observe: function(target, options) {
|
|
target = wrapIfNeeded(target);
|
|
|
|
// 1.1
|
|
if (!options.childList && !options.attributes && !options.characterData ||
|
|
|
|
// 1.2
|
|
options.attributeOldValue && !options.attributes ||
|
|
|
|
// 1.3
|
|
options.attributeFilter && options.attributeFilter.length &&
|
|
!options.attributes ||
|
|
|
|
// 1.4
|
|
options.characterDataOldValue && !options.characterData) {
|
|
|
|
throw new SyntaxError();
|
|
}
|
|
|
|
var registrations = registrationsTable.get(target);
|
|
if (!registrations)
|
|
registrationsTable.set(target, registrations = []);
|
|
|
|
// 2
|
|
// If target's list of registered observers already includes a registered
|
|
// observer associated with the context object, replace that registered
|
|
// observer's options with options.
|
|
var registration;
|
|
for (var i = 0; i < registrations.length; i++) {
|
|
if (registrations[i].observer === this) {
|
|
registration = registrations[i];
|
|
registration.removeListeners();
|
|
registration.options = options;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 3.
|
|
// Otherwise, add a new registered observer to target's list of registered
|
|
// observers with the context object as the observer and options as the
|
|
// options, and add target to context object's list of nodes on which it
|
|
// is registered.
|
|
if (!registration) {
|
|
registration = new Registration(this, target, options);
|
|
registrations.push(registration);
|
|
this.nodes_.push(target);
|
|
}
|
|
|
|
registration.addListeners();
|
|
},
|
|
|
|
disconnect: function() {
|
|
this.nodes_.forEach(function(node) {
|
|
var registrations = registrationsTable.get(node);
|
|
for (var i = 0; i < registrations.length; i++) {
|
|
var registration = registrations[i];
|
|
if (registration.observer === this) {
|
|
registration.removeListeners();
|
|
registrations.splice(i, 1);
|
|
// Each node can only have one registered observer associated with
|
|
// this observer.
|
|
break;
|
|
}
|
|
}
|
|
}, this);
|
|
this.records_ = [];
|
|
},
|
|
|
|
takeRecords: function() {
|
|
var copyOfRecords = this.records_;
|
|
this.records_ = [];
|
|
return copyOfRecords;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @param {string} type
|
|
* @param {Node} target
|
|
* @constructor
|
|
*/
|
|
function MutationRecord(type, target) {
|
|
this.type = type;
|
|
this.target = target;
|
|
this.addedNodes = [];
|
|
this.removedNodes = [];
|
|
this.previousSibling = null;
|
|
this.nextSibling = null;
|
|
this.attributeName = null;
|
|
this.attributeNamespace = null;
|
|
this.oldValue = null;
|
|
}
|
|
|
|
function copyMutationRecord(original) {
|
|
var record = new MutationRecord(original.type, original.target);
|
|
record.addedNodes = original.addedNodes.slice();
|
|
record.removedNodes = original.removedNodes.slice();
|
|
record.previousSibling = original.previousSibling;
|
|
record.nextSibling = original.nextSibling;
|
|
record.attributeName = original.attributeName;
|
|
record.attributeNamespace = original.attributeNamespace;
|
|
record.oldValue = original.oldValue;
|
|
return record;
|
|
};
|
|
|
|
// We keep track of the two (possibly one) records used in a single mutation.
|
|
var currentRecord, recordWithOldValue;
|
|
|
|
/**
|
|
* Creates a record without |oldValue| and caches it as |currentRecord| for
|
|
* later use.
|
|
* @param {string} oldValue
|
|
* @return {MutationRecord}
|
|
*/
|
|
function getRecord(type, target) {
|
|
return currentRecord = new MutationRecord(type, target);
|
|
}
|
|
|
|
/**
|
|
* Gets or creates a record with |oldValue| based in the |currentRecord|
|
|
* @param {string} oldValue
|
|
* @return {MutationRecord}
|
|
*/
|
|
function getRecordWithOldValue(oldValue) {
|
|
if (recordWithOldValue)
|
|
return recordWithOldValue;
|
|
recordWithOldValue = copyMutationRecord(currentRecord);
|
|
recordWithOldValue.oldValue = oldValue;
|
|
return recordWithOldValue;
|
|
}
|
|
|
|
function clearRecords() {
|
|
currentRecord = recordWithOldValue = undefined;
|
|
}
|
|
|
|
/**
|
|
* @param {MutationRecord} record
|
|
* @return {boolean} Whether the record represents a record from the current
|
|
* mutation event.
|
|
*/
|
|
function recordRepresentsCurrentMutation(record) {
|
|
return record === recordWithOldValue || record === currentRecord;
|
|
}
|
|
|
|
/**
|
|
* Selects which record, if any, to replace the last record in the queue.
|
|
* This returns |null| if no record should be replaced.
|
|
*
|
|
* @param {MutationRecord} lastRecord
|
|
* @param {MutationRecord} newRecord
|
|
* @param {MutationRecord}
|
|
*/
|
|
function selectRecord(lastRecord, newRecord) {
|
|
if (lastRecord === newRecord)
|
|
return lastRecord;
|
|
|
|
// Check if the the record we are adding represents the same record. If
|
|
// so, we keep the one with the oldValue in it.
|
|
if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord))
|
|
return recordWithOldValue;
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Class used to represent a registered observer.
|
|
* @param {MutationObserver} observer
|
|
* @param {Node} target
|
|
* @param {MutationObserverInit} options
|
|
* @constructor
|
|
*/
|
|
function Registration(observer, target, options) {
|
|
this.observer = observer;
|
|
this.target = target;
|
|
this.options = options;
|
|
this.transientObservedNodes = [];
|
|
}
|
|
|
|
Registration.prototype = {
|
|
enqueue: function(record) {
|
|
var records = this.observer.records_;
|
|
var length = records.length;
|
|
|
|
// There are cases where we replace the last record with the new record.
|
|
// For example if the record represents the same mutation we need to use
|
|
// the one with the oldValue. If we get same record (this can happen as we
|
|
// walk up the tree) we ignore the new record.
|
|
if (records.length > 0) {
|
|
var lastRecord = records[length - 1];
|
|
var recordToReplaceLast = selectRecord(lastRecord, record);
|
|
if (recordToReplaceLast) {
|
|
records[length - 1] = recordToReplaceLast;
|
|
return;
|
|
}
|
|
} else {
|
|
scheduleCallback(this.observer);
|
|
}
|
|
|
|
records[length] = record;
|
|
},
|
|
|
|
addListeners: function() {
|
|
this.addListeners_(this.target);
|
|
},
|
|
|
|
addListeners_: function(node) {
|
|
var options = this.options;
|
|
if (options.attributes)
|
|
node.addEventListener('DOMAttrModified', this, true);
|
|
|
|
if (options.characterData)
|
|
node.addEventListener('DOMCharacterDataModified', this, true);
|
|
|
|
if (options.childList)
|
|
node.addEventListener('DOMNodeInserted', this, true);
|
|
|
|
if (options.childList || options.subtree)
|
|
node.addEventListener('DOMNodeRemoved', this, true);
|
|
},
|
|
|
|
removeListeners: function() {
|
|
this.removeListeners_(this.target);
|
|
},
|
|
|
|
removeListeners_: function(node) {
|
|
var options = this.options;
|
|
if (options.attributes)
|
|
node.removeEventListener('DOMAttrModified', this, true);
|
|
|
|
if (options.characterData)
|
|
node.removeEventListener('DOMCharacterDataModified', this, true);
|
|
|
|
if (options.childList)
|
|
node.removeEventListener('DOMNodeInserted', this, true);
|
|
|
|
if (options.childList || options.subtree)
|
|
node.removeEventListener('DOMNodeRemoved', this, true);
|
|
},
|
|
|
|
/**
|
|
* Adds a transient observer on node. The transient observer gets removed
|
|
* next time we deliver the change records.
|
|
* @param {Node} node
|
|
*/
|
|
addTransientObserver: function(node) {
|
|
// Don't add transient observers on the target itself. We already have all
|
|
// the required listeners set up on the target.
|
|
if (node === this.target)
|
|
return;
|
|
|
|
this.addListeners_(node);
|
|
this.transientObservedNodes.push(node);
|
|
var registrations = registrationsTable.get(node);
|
|
if (!registrations)
|
|
registrationsTable.set(node, registrations = []);
|
|
|
|
// We know that registrations does not contain this because we already
|
|
// checked if node === this.target.
|
|
registrations.push(this);
|
|
},
|
|
|
|
removeTransientObservers: function() {
|
|
var transientObservedNodes = this.transientObservedNodes;
|
|
this.transientObservedNodes = [];
|
|
|
|
transientObservedNodes.forEach(function(node) {
|
|
// Transient observers are never added to the target.
|
|
this.removeListeners_(node);
|
|
|
|
var registrations = registrationsTable.get(node);
|
|
for (var i = 0; i < registrations.length; i++) {
|
|
if (registrations[i] === this) {
|
|
registrations.splice(i, 1);
|
|
// Each node can only have one registered observer associated with
|
|
// this observer.
|
|
break;
|
|
}
|
|
}
|
|
}, this);
|
|
},
|
|
|
|
handleEvent: function(e) {
|
|
// Stop propagation since we are managing the propagation manually.
|
|
// This means that other mutation events on the page will not work
|
|
// correctly but that is by design.
|
|
e.stopImmediatePropagation();
|
|
|
|
switch (e.type) {
|
|
case 'DOMAttrModified':
|
|
// http://dom.spec.whatwg.org/#concept-mo-queue-attributes
|
|
|
|
var name = e.attrName;
|
|
var namespace = e.relatedNode.namespaceURI;
|
|
var target = e.target;
|
|
|
|
// 1.
|
|
var record = new getRecord('attributes', target);
|
|
record.attributeName = name;
|
|
record.attributeNamespace = namespace;
|
|
|
|
// 2.
|
|
var oldValue =
|
|
e.attrChange === MutationEvent.ADDITION ? null : e.prevValue;
|
|
|
|
forEachAncestorAndObserverEnqueueRecord(target, function(options) {
|
|
// 3.1, 4.2
|
|
if (!options.attributes)
|
|
return;
|
|
|
|
// 3.2, 4.3
|
|
if (options.attributeFilter && options.attributeFilter.length &&
|
|
options.attributeFilter.indexOf(name) === -1 &&
|
|
options.attributeFilter.indexOf(namespace) === -1) {
|
|
return;
|
|
}
|
|
// 3.3, 4.4
|
|
if (options.attributeOldValue)
|
|
return getRecordWithOldValue(oldValue);
|
|
|
|
// 3.4, 4.5
|
|
return record;
|
|
});
|
|
|
|
break;
|
|
|
|
case 'DOMCharacterDataModified':
|
|
// http://dom.spec.whatwg.org/#concept-mo-queue-characterdata
|
|
var target = e.target;
|
|
|
|
// 1.
|
|
var record = getRecord('characterData', target);
|
|
|
|
// 2.
|
|
var oldValue = e.prevValue;
|
|
|
|
|
|
forEachAncestorAndObserverEnqueueRecord(target, function(options) {
|
|
// 3.1, 4.2
|
|
if (!options.characterData)
|
|
return;
|
|
|
|
// 3.2, 4.3
|
|
if (options.characterDataOldValue)
|
|
return getRecordWithOldValue(oldValue);
|
|
|
|
// 3.3, 4.4
|
|
return record;
|
|
});
|
|
|
|
break;
|
|
|
|
case 'DOMNodeRemoved':
|
|
this.addTransientObserver(e.target);
|
|
// Fall through.
|
|
case 'DOMNodeInserted':
|
|
// http://dom.spec.whatwg.org/#concept-mo-queue-childlist
|
|
var target = e.relatedNode;
|
|
var changedNode = e.target;
|
|
var addedNodes, removedNodes;
|
|
if (e.type === 'DOMNodeInserted') {
|
|
addedNodes = [changedNode];
|
|
removedNodes = [];
|
|
} else {
|
|
|
|
addedNodes = [];
|
|
removedNodes = [changedNode];
|
|
}
|
|
var previousSibling = changedNode.previousSibling;
|
|
var nextSibling = changedNode.nextSibling;
|
|
|
|
// 1.
|
|
var record = getRecord('childList', target);
|
|
record.addedNodes = addedNodes;
|
|
record.removedNodes = removedNodes;
|
|
record.previousSibling = previousSibling;
|
|
record.nextSibling = nextSibling;
|
|
|
|
forEachAncestorAndObserverEnqueueRecord(target, function(options) {
|
|
// 2.1, 3.2
|
|
if (!options.childList)
|
|
return;
|
|
|
|
// 2.2, 3.3
|
|
return record;
|
|
});
|
|
|
|
}
|
|
|
|
clearRecords();
|
|
}
|
|
};
|
|
|
|
global.JsMutationObserver = JsMutationObserver;
|
|
|
|
if (!global.MutationObserver)
|
|
global.MutationObserver = JsMutationObserver;
|
|
|
|
|
|
})(this);
|
|
|
|
/*
|
|
* Copyright 2013 The Polymer Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style
|
|
* license that can be found in the LICENSE file.
|
|
*/
|
|
window.HTMLImports = window.HTMLImports || {flags:{}};
|
|
/*
|
|
* Copyright 2013 The Polymer Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style
|
|
* license that can be found in the LICENSE file.
|
|
*/
|
|
|
|
(function(scope) {
|
|
|
|
// imports
|
|
var path = scope.path;
|
|
var xhr = scope.xhr;
|
|
var flags = scope.flags;
|
|
|
|
// TODO(sorvell): this loader supports a dynamic list of urls
|
|
// and an oncomplete callback that is called when the loader is done.
|
|
// The polyfill currently does *not* need this dynamism or the onComplete
|
|
// concept. Because of this, the loader could be simplified quite a bit.
|
|
var Loader = function(onLoad, onComplete) {
|
|
this.cache = {};
|
|
this.onload = onLoad;
|
|
this.oncomplete = onComplete;
|
|
this.inflight = 0;
|
|
this.pending = {};
|
|
};
|
|
|
|
Loader.prototype = {
|
|
addNodes: function(nodes) {
|
|
// number of transactions to complete
|
|
this.inflight += nodes.length;
|
|
// commence transactions
|
|
for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) {
|
|
this.require(n);
|
|
}
|
|
// anything to do?
|
|
this.checkDone();
|
|
},
|
|
addNode: function(node) {
|
|
// number of transactions to complete
|
|
this.inflight++;
|
|
// commence transactions
|
|
this.require(node);
|
|
// anything to do?
|
|
this.checkDone();
|
|
},
|
|
require: function(elt) {
|
|
var url = elt.src || elt.href;
|
|
// ensure we have a standard url that can be used
|
|
// reliably for deduping.
|
|
// TODO(sjmiles): ad-hoc
|
|
elt.__nodeUrl = url;
|
|
// deduplication
|
|
if (!this.dedupe(url, elt)) {
|
|
// fetch this resource
|
|
this.fetch(url, elt);
|
|
}
|
|
},
|
|
dedupe: function(url, elt) {
|
|
if (this.pending[url]) {
|
|
// add to list of nodes waiting for inUrl
|
|
this.pending[url].push(elt);
|
|
// don't need fetch
|
|
return true;
|
|
}
|
|
var resource;
|
|
if (this.cache[url]) {
|
|
this.onload(url, elt, this.cache[url]);
|
|
// finished this transaction
|
|
this.tail();
|
|
// don't need fetch
|
|
return true;
|
|
}
|
|
// first node waiting for inUrl
|
|
this.pending[url] = [elt];
|
|
// need fetch (not a dupe)
|
|
return false;
|
|
},
|
|
fetch: function(url, elt) {
|
|
flags.load && console.log('fetch', url, elt);
|
|
if (url.match(/^data:/)) {
|
|
// Handle Data URI Scheme
|
|
var pieces = url.split(',');
|
|
var header = pieces[0];
|
|
var body = pieces[1];
|
|
if(header.indexOf(';base64') > -1) {
|
|
body = atob(body);
|
|
} else {
|
|
body = decodeURIComponent(body);
|
|
}
|
|
setTimeout(function() {
|
|
this.receive(url, elt, null, body);
|
|
}.bind(this), 0);
|
|
} else {
|
|
var receiveXhr = function(err, resource, redirectedUrl) {
|
|
this.receive(url, elt, err, resource, redirectedUrl);
|
|
}.bind(this);
|
|
xhr.load(url, receiveXhr);
|
|
// TODO(sorvell): blocked on)
|
|
// https://code.google.com/p/chromium/issues/detail?id=257221
|
|
// xhr'ing for a document makes scripts in imports runnable; otherwise
|
|
// they are not; however, it requires that we have doctype=html in
|
|
// the import which is unacceptable. This is only needed on Chrome
|
|
// to avoid the bug above.
|
|
/*
|
|
if (isDocumentLink(elt)) {
|
|
xhr.loadDocument(url, receiveXhr);
|
|
} else {
|
|
xhr.load(url, receiveXhr);
|
|
}
|
|
*/
|
|
}
|
|
},
|
|
receive: function(url, elt, err, resource, redirectedUrl) {
|
|
this.cache[url] = resource;
|
|
var $p = this.pending[url];
|
|
if ( redirectedUrl && redirectedUrl !== url ) {
|
|
this.cache[redirectedUrl] = resource;
|
|
$p = $p.concat(this.pending[redirectedUrl]);
|
|
}
|
|
for (var i=0, l=$p.length, p; (i<l) && (p=$p[i]); i++) {
|
|
//if (!err) {
|
|
// If url was redirected, use the redirected location so paths are
|
|
// calculated relative to that.
|
|
this.onload(redirectedUrl || url, p, resource);
|
|
//}
|
|
this.tail();
|
|
}
|
|
this.pending[url] = null;
|
|
if ( redirectedUrl && redirectedUrl !== url ) {
|
|
this.pending[redirectedUrl] = null;
|
|
}
|
|
},
|
|
tail: function() {
|
|
--this.inflight;
|
|
this.checkDone();
|
|
},
|
|
checkDone: function() {
|
|
if (!this.inflight) {
|
|
this.oncomplete();
|
|
}
|
|
}
|
|
};
|
|
|
|
xhr = xhr || {
|
|
async: true,
|
|
ok: function(request) {
|
|
return (request.status >= 200 && request.status < 300)
|
|
|| (request.status === 304)
|
|
|| (request.status === 0);
|
|
},
|
|
load: function(url, next, nextContext) {
|
|
var request = new XMLHttpRequest();
|
|
if (scope.flags.debug || scope.flags.bust) {
|
|
url += '?' + Math.random();
|
|
}
|
|
request.open('GET', url, xhr.async);
|
|
request.addEventListener('readystatechange', function(e) {
|
|
if (request.readyState === 4) {
|
|
// Servers redirecting an import can add a Location header to help us
|
|
// polyfill correctly.
|
|
var locationHeader = request.getResponseHeader("Location");
|
|
var redirectedUrl = null;
|
|
if (locationHeader) {
|
|
var redirectedUrl = (locationHeader.substr( 0, 1 ) === "/")
|
|
? location.origin + locationHeader // Location is a relative path
|
|
: redirectedUrl; // Full path
|
|
}
|
|
next.call(nextContext, !xhr.ok(request) && request,
|
|
request.response || request.responseText, redirectedUrl);
|
|
}
|
|
});
|
|
request.send();
|
|
return request;
|
|
},
|
|
loadDocument: function(url, next, nextContext) {
|
|
this.load(url, next, nextContext).responseType = 'document';
|
|
}
|
|
};
|
|
|
|
// exports
|
|
scope.xhr = xhr;
|
|
scope.Loader = Loader;
|
|
|
|
})(window.HTMLImports);
|
|
|
|
/*
|
|
* Copyright 2013 The Polymer Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style
|
|
* license that can be found in the LICENSE file.
|
|
*/
|
|
|
|
(function(scope) {
|
|
|
|
var IMPORT_LINK_TYPE = 'import';
|
|
var flags = scope.flags;
|
|
var isIe = /Trident/.test(navigator.userAgent);
|
|
// TODO(sorvell): SD polyfill intrusion
|
|
var mainDoc = window.ShadowDOMPolyfill ?
|
|
window.ShadowDOMPolyfill.wrapIfNeeded(document) : document;
|
|
|
|
// importParser
|
|
// highlander object to manage parsing of imports
|
|
// parses import related elements
|
|
// and ensures proper parse order
|
|
// parse order is enforced by crawling the tree and monitoring which elements
|
|
// have been parsed; async parsing is also supported.
|
|
|
|
// highlander object for parsing a document tree
|
|
var importParser = {
|
|
// parse selectors for main document elements
|
|
documentSelectors: 'link[rel=' + IMPORT_LINK_TYPE + ']',
|
|
// parse selectors for import document elements
|
|
importsSelectors: [
|
|
'link[rel=' + IMPORT_LINK_TYPE + ']',
|
|
'link[rel=stylesheet]',
|
|
'style',
|
|
'script:not([type])',
|
|
'script[type="text/javascript"]'
|
|
].join(','),
|
|
map: {
|
|
link: 'parseLink',
|
|
script: 'parseScript',
|
|
style: 'parseStyle'
|
|
},
|
|
// try to parse the next import in the tree
|
|
parseNext: function() {
|
|
var next = this.nextToParse();
|
|
if (next) {
|
|
this.parse(next);
|
|
}
|
|
},
|
|
parse: function(elt) {
|
|
if (this.isParsed(elt)) {
|
|
flags.parse && console.log('[%s] is already parsed', elt.localName);
|
|
return;
|
|
}
|
|
var fn = this[this.map[elt.localName]];
|
|
if (fn) {
|
|
this.markParsing(elt);
|
|
fn.call(this, elt);
|
|
}
|
|
},
|
|
// only 1 element may be parsed at a time; parsing is async so each
|
|
// parsing implementation must inform the system that parsing is complete
|
|
// via markParsingComplete.
|
|
// To prompt the system to parse the next element, parseNext should then be
|
|
// called.
|
|
// Note, parseNext used to be included at the end of markParsingComplete, but
|
|
// we must not do this so that, for example, we can (1) mark parsing complete
|
|
// then (2) fire an import load event, and then (3) parse the next resource.
|
|
markParsing: function(elt) {
|
|
flags.parse && console.log('parsing', elt);
|
|
this.parsingElement = elt;
|
|
},
|
|
markParsingComplete: function(elt) {
|
|
elt.__importParsed = true;
|
|
if (elt.__importElement) {
|
|
elt.__importElement.__importParsed = true;
|
|
}
|
|
this.parsingElement = null;
|
|
flags.parse && console.log('completed', elt);
|
|
},
|
|
invalidateParse: function(doc) {
|
|
if (doc && doc.__importLink) {
|
|
doc.__importParsed = doc.__importLink.__importParsed = false;
|
|
this.parseSoon();
|
|
}
|
|
},
|
|
parseSoon: function() {
|
|
if (this._parseSoon) {
|
|
cancelAnimationFrame(this._parseDelay);
|
|
}
|
|
var parser = this;
|
|
this._parseSoon = requestAnimationFrame(function() {
|
|
parser.parseNext();
|
|
});
|
|
},
|
|
parseImport: function(elt) {
|
|
// TODO(sorvell): consider if there's a better way to do this;
|
|
// expose an imports parsing hook; this is needed, for example, by the
|
|
// CustomElements polyfill.
|
|
if (HTMLImports.__importsParsingHook) {
|
|
HTMLImports.__importsParsingHook(elt);
|
|
}
|
|
elt.import.__importParsed = true;
|
|
this.markParsingComplete(elt);
|
|
// fire load event
|
|
if (elt.__resource) {
|
|
elt.dispatchEvent(new CustomEvent('load', {bubbles: false}));
|
|
} else {
|
|
elt.dispatchEvent(new CustomEvent('error', {bubbles: false}));
|
|
}
|
|
// TODO(sorvell): workaround for Safari addEventListener not working
|
|
// for elements not in the main document.
|
|
if (elt.__pending) {
|
|
var fn;
|
|
while (elt.__pending.length) {
|
|
fn = elt.__pending.shift();
|
|
if (fn) {
|
|
fn({target: elt});
|
|
}
|
|
}
|
|
}
|
|
this.parseNext();
|
|
},
|
|
parseLink: function(linkElt) {
|
|
if (nodeIsImport(linkElt)) {
|
|
this.parseImport(linkElt);
|
|
} else {
|
|
// make href absolute
|
|
linkElt.href = linkElt.href;
|
|
this.parseGeneric(linkElt);
|
|
}
|
|
},
|
|
parseStyle: function(elt) {
|
|
// TODO(sorvell): style element load event can just not fire so clone styles
|
|
var src = elt;
|
|
elt = cloneStyle(elt);
|
|
elt.__importElement = src;
|
|
this.parseGeneric(elt);
|
|
},
|
|
parseGeneric: function(elt) {
|
|
this.trackElement(elt);
|
|
document.head.appendChild(elt);
|
|
},
|
|
// tracks when a loadable element has loaded
|
|
trackElement: function(elt, callback) {
|
|
var self = this;
|
|
var done = function(e) {
|
|
if (callback) {
|
|
callback(e);
|
|
}
|
|
self.markParsingComplete(elt);
|
|
self.parseNext();
|
|
};
|
|
elt.addEventListener('load', done);
|
|
elt.addEventListener('error', done);
|
|
|
|
// NOTE: IE does not fire "load" event for styles that have already loaded
|
|
// This is in violation of the spec, so we try our hardest to work around it
|
|
if (isIe && elt.localName === 'style') {
|
|
var fakeLoad = false;
|
|
// If there's not @import in the textContent, assume it has loaded
|
|
if (elt.textContent.indexOf('@import') == -1) {
|
|
fakeLoad = true;
|
|
// if we have a sheet, we have been parsed
|
|
} else if (elt.sheet) {
|
|
fakeLoad = true;
|
|
var csr = elt.sheet.cssRules;
|
|
var len = csr ? csr.length : 0;
|
|
// search the rules for @import's
|
|
for (var i = 0, r; (i < len) && (r = csr[i]); i++) {
|
|
if (r.type === CSSRule.IMPORT_RULE) {
|
|
// if every @import has resolved, fake the load
|
|
fakeLoad = fakeLoad && Boolean(r.styleSheet);
|
|
}
|
|
}
|
|
}
|
|
// dispatch a fake load event and continue parsing
|
|
if (fakeLoad) {
|
|
elt.dispatchEvent(new CustomEvent('load', {bubbles: false}));
|
|
}
|
|
}
|
|
},
|
|
// NOTE: execute scripts by injecting them and watching for the load/error
|
|
// event. Inline scripts are handled via dataURL's because browsers tend to
|
|
// provide correct parsing errors in this case. If this has any compatibility
|
|
// issues, we can switch to injecting the inline script with textContent.
|
|
// Scripts with dataURL's do not appear to generate load events and therefore
|
|
// we assume they execute synchronously.
|
|
parseScript: function(scriptElt) {
|
|
var script = document.createElement('script');
|
|
script.__importElement = scriptElt;
|
|
script.src = scriptElt.src ? scriptElt.src :
|
|
generateScriptDataUrl(scriptElt);
|
|
scope.currentScript = scriptElt;
|
|
this.trackElement(script, function(e) {
|
|
script.parentNode.removeChild(script);
|
|
scope.currentScript = null;
|
|
});
|
|
document.head.appendChild(script);
|
|
},
|
|
// determine the next element in the tree which should be parsed
|
|
nextToParse: function() {
|
|
return !this.parsingElement && this.nextToParseInDoc(mainDoc);
|
|
},
|
|
nextToParseInDoc: function(doc, link) {
|
|
var nodes = doc.querySelectorAll(this.parseSelectorsForNode(doc));
|
|
for (var i=0, l=nodes.length, p=0, n; (i<l) && (n=nodes[i]); i++) {
|
|
if (!this.isParsed(n)) {
|
|
if (this.hasResource(n)) {
|
|
return nodeIsImport(n) ? this.nextToParseInDoc(n.import, n) : n;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
// all nodes have been parsed, ready to parse import, if any
|
|
return link;
|
|
},
|
|
// return the set of parse selectors relevant for this node.
|
|
parseSelectorsForNode: function(node) {
|
|
var doc = node.ownerDocument || node;
|
|
return doc === mainDoc ? this.documentSelectors : this.importsSelectors;
|
|
},
|
|
isParsed: function(node) {
|
|
return node.__importParsed;
|
|
},
|
|
hasResource: function(node) {
|
|
if (nodeIsImport(node) && !node.import) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
function nodeIsImport(elt) {
|
|
return (elt.localName === 'link') && (elt.rel === IMPORT_LINK_TYPE);
|
|
}
|
|
|
|
function generateScriptDataUrl(script) {
|
|
var scriptContent = generateScriptContent(script), b64;
|
|
try {
|
|
b64 = btoa(scriptContent);
|
|
} catch(e) {
|
|
b64 = btoa(unescape(encodeURIComponent(scriptContent)));
|
|
console.warn('Script contained non-latin characters that were forced ' +
|
|
'to latin. Some characters may be wrong.', script);
|
|
}
|
|
return 'data:text/javascript;base64,' + b64;
|
|
}
|
|
|
|
function generateScriptContent(script) {
|
|
return script.textContent + generateSourceMapHint(script);
|
|
}
|
|
|
|
// calculate source map hint
|
|
function generateSourceMapHint(script) {
|
|
var moniker = script.__nodeUrl;
|
|
if (!moniker) {
|
|
moniker = script.ownerDocument.baseURI;
|
|
// there could be more than one script this url
|
|
var tag = '[' + Math.floor((Math.random()+1)*1000) + ']';
|
|
// TODO(sjmiles): Polymer hack, should be pluggable if we need to allow
|
|
// this sort of thing
|
|
var matches = script.textContent.match(/Polymer\(['"]([^'"]*)/);
|
|
tag = matches && matches[1] || tag;
|
|
// tag the moniker
|
|
moniker += '/' + tag + '.js';
|
|
}
|
|
return '\n//# sourceURL=' + moniker + '\n';
|
|
}
|
|
|
|
// style/stylesheet handling
|
|
|
|
// clone style with proper path resolution for main document
|
|
// NOTE: styles are the only elements that require direct path fixup.
|
|
function cloneStyle(style) {
|
|
var clone = style.ownerDocument.createElement('style');
|
|
clone.textContent = style.textContent;
|
|
path.resolveUrlsInStyle(clone);
|
|
return clone;
|
|
}
|
|
|
|
// path fixup: style elements in imports must be made relative to the main
|
|
// document. We fixup url's in url() and @import.
|
|
var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g;
|
|
var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g;
|
|
|
|
var path = {
|
|
resolveUrlsInStyle: function(style) {
|
|
var doc = style.ownerDocument;
|
|
var resolver = doc.createElement('a');
|
|
style.textContent = this.resolveUrlsInCssText(style.textContent, resolver);
|
|
return style;
|
|
},
|
|
resolveUrlsInCssText: function(cssText, urlObj) {
|
|
var r = this.replaceUrls(cssText, urlObj, CSS_URL_REGEXP);
|
|
r = this.replaceUrls(r, urlObj, CSS_IMPORT_REGEXP);
|
|
return r;
|
|
},
|
|
replaceUrls: function(text, urlObj, regexp) {
|
|
return text.replace(regexp, function(m, pre, url, post) {
|
|
var urlPath = url.replace(/["']/g, '');
|
|
urlObj.href = urlPath;
|
|
urlPath = urlObj.href;
|
|
return pre + '\'' + urlPath + '\'' + post;
|
|
});
|
|
}
|
|
}
|
|
|
|
// exports
|
|
scope.parser = importParser;
|
|
scope.path = path;
|
|
scope.isIE = isIe;
|
|
|
|
})(HTMLImports);
|
|
|
|
/*
|
|
* Copyright 2013 The Polymer Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style
|
|
* license that can be found in the LICENSE file.
|
|
*/
|
|
|
|
(function(scope) {
|
|
|
|
var hasNative = ('import' in document.createElement('link'));
|
|
var useNative = hasNative;
|
|
var flags = scope.flags;
|
|
var IMPORT_LINK_TYPE = 'import';
|
|
|
|
// TODO(sorvell): SD polyfill intrusion
|
|
var mainDoc = window.ShadowDOMPolyfill ?
|
|
ShadowDOMPolyfill.wrapIfNeeded(document) : document;
|
|
|
|
if (!useNative) {
|
|
|
|
// imports
|
|
var xhr = scope.xhr;
|
|
var Loader = scope.Loader;
|
|
var parser = scope.parser;
|
|
|
|
// importer
|
|
// highlander object to manage loading of imports
|
|
|
|
// for any document, importer:
|
|
// - loads any linked import documents (with deduping)
|
|
|
|
var importer = {
|
|
documents: {},
|
|
// nodes to load in the mian document
|
|
documentPreloadSelectors: 'link[rel=' + IMPORT_LINK_TYPE + ']',
|
|
// nodes to load in imports
|
|
importsPreloadSelectors: [
|
|
'link[rel=' + IMPORT_LINK_TYPE + ']'
|
|
].join(','),
|
|
loadNode: function(node) {
|
|
importLoader.addNode(node);
|
|
},
|
|
// load all loadable elements within the parent element
|
|
loadSubtree: function(parent) {
|
|
var nodes = this.marshalNodes(parent);
|
|
// add these nodes to loader's queue
|
|
importLoader.addNodes(nodes);
|
|
},
|
|
marshalNodes: function(parent) {
|
|
// all preloadable nodes in inDocument
|
|
return parent.querySelectorAll(this.loadSelectorsForNode(parent));
|
|
},
|
|
// find the proper set of load selectors for a given node
|
|
loadSelectorsForNode: function(node) {
|
|
var doc = node.ownerDocument || node;
|
|
return doc === mainDoc ? this.documentPreloadSelectors :
|
|
this.importsPreloadSelectors;
|
|
},
|
|
loaded: function(url, elt, resource) {
|
|
flags.load && console.log('loaded', url, elt);
|
|
// store generic resource
|
|
// TODO(sorvell): fails for nodes inside <template>.content
|
|
// see https://code.google.com/p/chromium/issues/detail?id=249381.
|
|
elt.__resource = resource;
|
|
if (isDocumentLink(elt)) {
|
|
var doc = this.documents[url];
|
|
// if we've never seen a document at this url
|
|
if (!doc) {
|
|
// generate an HTMLDocument from data
|
|
doc = makeDocument(resource, url);
|
|
doc.__importLink = elt;
|
|
// TODO(sorvell): we cannot use MO to detect parsed nodes because
|
|
// SD polyfill does not report these as mutations.
|
|
this.bootDocument(doc);
|
|
// cache document
|
|
this.documents[url] = doc;
|
|
}
|
|
// don't store import record until we're actually loaded
|
|
// store document resource
|
|
elt.import = doc;
|
|
}
|
|
parser.parseNext();
|
|
},
|
|
bootDocument: function(doc) {
|
|
this.loadSubtree(doc);
|
|
this.observe(doc);
|
|
parser.parseNext();
|
|
},
|
|
loadedAll: function() {
|
|
parser.parseNext();
|
|
}
|
|
};
|
|
|
|
// loader singleton
|
|
var importLoader = new Loader(importer.loaded.bind(importer),
|
|
importer.loadedAll.bind(importer));
|
|
|
|
function isDocumentLink(elt) {
|
|
return isLinkRel(elt, IMPORT_LINK_TYPE);
|
|
}
|
|
|
|
function isLinkRel(elt, rel) {
|
|
return elt.localName === 'link' && elt.getAttribute('rel') === rel;
|
|
}
|
|
|
|
function isScript(elt) {
|
|
return elt.localName === 'script';
|
|
}
|
|
|
|
function makeDocument(resource, url) {
|
|
// create a new HTML document
|
|
var doc = resource;
|
|
if (!(doc instanceof Document)) {
|
|
doc = document.implementation.createHTMLDocument(IMPORT_LINK_TYPE);
|
|
}
|
|
// cache the new document's source url
|
|
doc._URL = url;
|
|
// establish a relative path via <base>
|
|
var base = doc.createElement('base');
|
|
base.setAttribute('href', url);
|
|
// add baseURI support to browsers (IE) that lack it.
|
|
if (!doc.baseURI) {
|
|
doc.baseURI = url;
|
|
}
|
|
// ensure UTF-8 charset
|
|
var meta = doc.createElement('meta');
|
|
meta.setAttribute('charset', 'utf-8');
|
|
|
|
doc.head.appendChild(meta);
|
|
doc.head.appendChild(base);
|
|
// install HTML last as it may trigger CustomElement upgrades
|
|
// TODO(sjmiles): problem wrt to template boostrapping below,
|
|
// template bootstrapping must (?) come before element upgrade
|
|
// but we cannot bootstrap templates until they are in a document
|
|
// which is too late
|
|
if (!(resource instanceof Document)) {
|
|
// install html
|
|
doc.body.innerHTML = resource;
|
|
}
|
|
// TODO(sorvell): ideally this code is not aware of Template polyfill,
|
|
// but for now the polyfill needs help to bootstrap these templates
|
|
if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) {
|
|
HTMLTemplateElement.bootstrap(doc);
|
|
}
|
|
return doc;
|
|
}
|
|
} else {
|
|
// do nothing if using native imports
|
|
var importer = {};
|
|
}
|
|
|
|
// NOTE: We cannot polyfill document.currentScript because it's not possible
|
|
// both to override and maintain the ability to capture the native value;
|
|
// therefore we choose to expose _currentScript both when native imports
|
|
// and the polyfill are in use.
|
|
var currentScriptDescriptor = {
|
|
get: function() {
|
|
return HTMLImports.currentScript || document.currentScript;
|
|
},
|
|
configurable: true
|
|
};
|
|
|
|
Object.defineProperty(document, '_currentScript', currentScriptDescriptor);
|
|
Object.defineProperty(mainDoc, '_currentScript', currentScriptDescriptor);
|
|
|
|
// Polyfill document.baseURI for browsers without it.
|
|
if (!document.baseURI) {
|
|
var baseURIDescriptor = {
|
|
get: function() {
|
|
return window.location.href;
|
|
},
|
|
configurable: true
|
|
};
|
|
|
|
Object.defineProperty(document, 'baseURI', baseURIDescriptor);
|
|
Object.defineProperty(mainDoc, 'baseURI', baseURIDescriptor);
|
|
}
|
|
|
|
// call a callback when all HTMLImports in the document at call (or at least
|
|
// document ready) time have loaded.
|
|
// 1. ensure the document is in a ready state (has dom), then
|
|
// 2. watch for loading of imports and call callback when done
|
|
function whenImportsReady(callback, doc) {
|
|
doc = doc || mainDoc;
|
|
// if document is loading, wait and try again
|
|
whenDocumentReady(function() {
|
|
watchImportsLoad(callback, doc);
|
|
}, doc);
|
|
}
|
|
|
|
// call the callback when the document is in a ready state (has dom)
|
|
var requiredReadyState = HTMLImports.isIE ? 'complete' : 'interactive';
|
|
var READY_EVENT = 'readystatechange';
|
|
function isDocumentReady(doc) {
|
|
return (doc.readyState === 'complete' ||
|
|
doc.readyState === requiredReadyState);
|
|
}
|
|
|
|
// call <callback> when we ensure the document is in a ready state
|
|
function whenDocumentReady(callback, doc) {
|
|
if (!isDocumentReady(doc)) {
|
|
var checkReady = function() {
|
|
if (doc.readyState === 'complete' ||
|
|
doc.readyState === requiredReadyState) {
|
|
doc.removeEventListener(READY_EVENT, checkReady);
|
|
whenDocumentReady(callback, doc);
|
|
}
|
|
}
|
|
doc.addEventListener(READY_EVENT, checkReady);
|
|
} else if (callback) {
|
|
callback();
|
|
}
|
|
}
|
|
|
|
// call <callback> when we ensure all imports have loaded
|
|
function watchImportsLoad(callback, doc) {
|
|
var imports = doc.querySelectorAll('link[rel=import]');
|
|
var loaded = 0, l = imports.length;
|
|
function checkDone(d) {
|
|
if (loaded == l) {
|
|
callback && callback();
|
|
}
|
|
}
|
|
function loadedImport(e) {
|
|
loaded++;
|
|
checkDone();
|
|
}
|
|
if (l) {
|
|
for (var i=0, imp; (i<l) && (imp=imports[i]); i++) {
|
|
if (isImportLoaded(imp)) {
|
|
loadedImport.call(imp);
|
|
} else {
|
|
imp.addEventListener('load', loadedImport);
|
|
imp.addEventListener('error', loadedImport);
|
|
}
|
|
}
|
|
} else {
|
|
checkDone();
|
|
}
|
|
}
|
|
|
|
function isImportLoaded(link) {
|
|
return useNative ? (link.import && (link.import.readyState !== 'loading')) || link.__loaded :
|
|
link.__importParsed;
|
|
}
|
|
|
|
// TODO(sorvell): install a mutation observer to see if HTMLImports have loaded
|
|
// this is a workaround for https://www.w3.org/Bugs/Public/show_bug.cgi?id=25007
|
|
// and should be removed when this bug is addressed.
|
|
if (useNative) {
|
|
new MutationObserver(function(mxns) {
|
|
for (var i=0, l=mxns.length, m; (i < l) && (m=mxns[i]); i++) {
|
|
if (m.addedNodes) {
|
|
handleImports(m.addedNodes);
|
|
}
|
|
}
|
|
}).observe(document.head, {childList: true});
|
|
|
|
function handleImports(nodes) {
|
|
for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) {
|
|
if (isImport(n)) {
|
|
handleImport(n);
|
|
}
|
|
}
|
|
}
|
|
|
|
function isImport(element) {
|
|
return element.localName === 'link' && element.rel === 'import';
|
|
}
|
|
|
|
function handleImport(element) {
|
|
var loaded = element.import;
|
|
if (loaded) {
|
|
markTargetLoaded({target: element});
|
|
} else {
|
|
element.addEventListener('load', markTargetLoaded);
|
|
element.addEventListener('error', markTargetLoaded);
|
|
}
|
|
}
|
|
|
|
function markTargetLoaded(event) {
|
|
event.target.__loaded = true;
|
|
}
|
|
|
|
}
|
|
|
|
// exports
|
|
scope.hasNative = hasNative;
|
|
scope.useNative = useNative;
|
|
scope.importer = importer;
|
|
scope.whenImportsReady = whenImportsReady;
|
|
scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE;
|
|
scope.isImportLoaded = isImportLoaded;
|
|
scope.importLoader = importLoader;
|
|
|
|
})(window.HTMLImports);
|
|
|
|
/*
|
|
Copyright 2013 The Polymer Authors. All rights reserved.
|
|
Use of this source code is governed by a BSD-style
|
|
license that can be found in the LICENSE file.
|
|
*/
|
|
|
|
(function(scope){
|
|
|
|
var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE;
|
|
var importSelector = 'link[rel=' + IMPORT_LINK_TYPE + ']';
|
|
var importer = scope.importer;
|
|
var parser = scope.parser;
|
|
|
|
// we track mutations for addedNodes, looking for imports
|
|
function handler(mutations) {
|
|
for (var i=0, l=mutations.length, m; (i<l) && (m=mutations[i]); i++) {
|
|
if (m.type === 'childList' && m.addedNodes.length) {
|
|
addedNodes(m.addedNodes);
|
|
}
|
|
}
|
|
}
|
|
|
|
// find loadable elements and add them to the importer
|
|
function addedNodes(nodes) {
|
|
var owner;
|
|
for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) {
|
|
owner = owner || n.ownerDocument;
|
|
if (shouldLoadNode(n)) {
|
|
importer.loadNode(n);
|
|
}
|
|
if (n.children && n.children.length) {
|
|
addedNodes(n.children);
|
|
}
|
|
}
|
|
// TODO(sorvell): This is not the right approach here. We shouldn't need to
|
|
// invalidate parsing when an element is added. Disabling this code
|
|
// until a better approach is found.
|
|
/*
|
|
if (owner) {
|
|
parser.invalidateParse(owner);
|
|
}
|
|
*/
|
|
}
|
|
|
|
function shouldLoadNode(node) {
|
|
return (node.nodeType === 1) && matches.call(node,
|
|
importer.loadSelectorsForNode(node));
|
|
}
|
|
|
|
// x-plat matches
|
|
var matches = HTMLElement.prototype.matches ||
|
|
HTMLElement.prototype.matchesSelector ||
|
|
HTMLElement.prototype.webkitMatchesSelector ||
|
|
HTMLElement.prototype.mozMatchesSelector ||
|
|
HTMLElement.prototype.msMatchesSelector;
|
|
|
|
var observer = new MutationObserver(handler);
|
|
|
|
// observe the given root for loadable elements
|
|
function observe(root) {
|
|
observer.observe(root, {childList: true, subtree: true});
|
|
}
|
|
|
|
// exports
|
|
// TODO(sorvell): factor so can put on scope
|
|
scope.observe = observe;
|
|
importer.observe = observe;
|
|
|
|
})(HTMLImports);
|
|
|
|
/*
|
|
* Copyright 2013 The Polymer Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style
|
|
* license that can be found in the LICENSE file.
|
|
*/
|
|
(function(){
|
|
|
|
// bootstrap
|
|
|
|
// IE shim for CustomEvent
|
|
if (typeof window.CustomEvent !== 'function') {
|
|
window.CustomEvent = function(inType, dictionary) {
|
|
var e = document.createEvent('HTMLEvents');
|
|
e.initEvent(inType,
|
|
dictionary.bubbles === false ? false : true,
|
|
dictionary.cancelable === false ? false : true,
|
|
dictionary.detail);
|
|
return e;
|
|
};
|
|
}
|
|
|
|
// TODO(sorvell): SD polyfill intrusion
|
|
var doc = window.ShadowDOMPolyfill ?
|
|
window.ShadowDOMPolyfill.wrapIfNeeded(document) : document;
|
|
|
|
// Fire the 'HTMLImportsLoaded' event when imports in document at load time
|
|
// have loaded. This event is required to simulate the script blocking
|
|
// behavior of native imports. A main document script that needs to be sure
|
|
// imports have loaded should wait for this event.
|
|
HTMLImports.whenImportsReady(function() {
|
|
HTMLImports.ready = true;
|
|
HTMLImports.readyTime = new Date().getTime();
|
|
doc.dispatchEvent(
|
|
new CustomEvent('HTMLImportsLoaded', {bubbles: true})
|
|
);
|
|
});
|
|
|
|
|
|
// no need to bootstrap the polyfill when native imports is available.
|
|
if (!HTMLImports.useNative) {
|
|
function bootstrap() {
|
|
HTMLImports.importer.bootDocument(doc);
|
|
}
|
|
|
|
// TODO(sorvell): SD polyfill does *not* generate mutations for nodes added
|
|
// by the parser. For this reason, we must wait until the dom exists to
|
|
// bootstrap.
|
|
if (document.readyState === 'complete' ||
|
|
(document.readyState === 'interactive' && !window.attachEvent)) {
|
|
bootstrap();
|
|
} else {
|
|
document.addEventListener('DOMContentLoaded', bootstrap);
|
|
}
|
|
}
|
|
|
|
})();
|
|
|
|
/*
|
|
* Copyright 2013 The Polymer Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style
|
|
* license that can be found in the LICENSE file.
|
|
*/
|
|
window.CustomElements = window.CustomElements || {flags:{}};
|
|
/*
|
|
Copyright 2013 The Polymer Authors. All rights reserved.
|
|
Use of this source code is governed by a BSD-style
|
|
license that can be found in the LICENSE file.
|
|
*/
|
|
|
|
(function(scope){
|
|
|
|
var logFlags = window.logFlags || {};
|
|
var IMPORT_LINK_TYPE = window.HTMLImports ? HTMLImports.IMPORT_LINK_TYPE : 'none';
|
|
|
|
// walk the subtree rooted at node, applying 'find(element, data)' function
|
|
// to each element
|
|
// if 'find' returns true for 'element', do not search element's subtree
|
|
function findAll(node, find, data) {
|
|
var e = node.firstElementChild;
|
|
if (!e) {
|
|
e = node.firstChild;
|
|
while (e && e.nodeType !== Node.ELEMENT_NODE) {
|
|
e = e.nextSibling;
|
|
}
|
|
}
|
|
while (e) {
|
|
if (find(e, data) !== true) {
|
|
findAll(e, find, data);
|
|
}
|
|
e = e.nextElementSibling;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// walk all shadowRoots on a given node.
|
|
function forRoots(node, cb) {
|
|
var root = node.shadowRoot;
|
|
while(root) {
|
|
forSubtree(root, cb);
|
|
root = root.olderShadowRoot;
|
|
}
|
|
}
|
|
|
|
// walk the subtree rooted at node, including descent into shadow-roots,
|
|
// applying 'cb' to each element
|
|
function forSubtree(node, cb) {
|
|
//logFlags.dom && node.childNodes && node.childNodes.length && console.group('subTree: ', node);
|
|
findAll(node, function(e) {
|
|
if (cb(e)) {
|
|
return true;
|
|
}
|
|
forRoots(e, cb);
|
|
});
|
|
forRoots(node, cb);
|
|
//logFlags.dom && node.childNodes && node.childNodes.length && console.groupEnd();
|
|
}
|
|
|
|
// manage lifecycle on added node
|
|
function added(node) {
|
|
if (upgrade(node)) {
|
|
insertedNode(node);
|
|
return true;
|
|
}
|
|
inserted(node);
|
|
}
|
|
|
|
// manage lifecycle on added node's subtree only
|
|
function addedSubtree(node) {
|
|
forSubtree(node, function(e) {
|
|
if (added(e)) {
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
// manage lifecycle on added node and it's subtree
|
|
function addedNode(node) {
|
|
return added(node) || addedSubtree(node);
|
|
}
|
|
|
|
// upgrade custom elements at node, if applicable
|
|
function upgrade(node) {
|
|
if (!node.__upgraded__ && node.nodeType === Node.ELEMENT_NODE) {
|
|
var type = node.getAttribute('is') || node.localName;
|
|
var definition = scope.registry[type];
|
|
if (definition) {
|
|
logFlags.dom && console.group('upgrade:', node.localName);
|
|
scope.upgrade(node);
|
|
logFlags.dom && console.groupEnd();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
function insertedNode(node) {
|
|
inserted(node);
|
|
if (inDocument(node)) {
|
|
forSubtree(node, function(e) {
|
|
inserted(e);
|
|
});
|
|
}
|
|
}
|
|
|
|
// TODO(sorvell): on platforms without MutationObserver, mutations may not be
|
|
// reliable and therefore attached/detached are not reliable.
|
|
// To make these callbacks less likely to fail, we defer all inserts and removes
|
|
// to give a chance for elements to be inserted into dom.
|
|
// This ensures attachedCallback fires for elements that are created and
|
|
// immediately added to dom.
|
|
var hasPolyfillMutations = (!window.MutationObserver ||
|
|
(window.MutationObserver === window.JsMutationObserver));
|
|
scope.hasPolyfillMutations = hasPolyfillMutations;
|
|
|
|
var isPendingMutations = false;
|
|
var pendingMutations = [];
|
|
function deferMutation(fn) {
|
|
pendingMutations.push(fn);
|
|
if (!isPendingMutations) {
|
|
isPendingMutations = true;
|
|
var async = (window.Platform && window.Platform.endOfMicrotask) ||
|
|
setTimeout;
|
|
async(takeMutations);
|
|
}
|
|
}
|
|
|
|
function takeMutations() {
|
|
isPendingMutations = false;
|
|
var $p = pendingMutations;
|
|
for (var i=0, l=$p.length, p; (i<l) && (p=$p[i]); i++) {
|
|
p();
|
|
}
|
|
pendingMutations = [];
|
|
}
|
|
|
|
function inserted(element) {
|
|
if (hasPolyfillMutations) {
|
|
deferMutation(function() {
|
|
_inserted(element);
|
|
});
|
|
} else {
|
|
_inserted(element);
|
|
}
|
|
}
|
|
|
|
// TODO(sjmiles): if there are descents into trees that can never have inDocument(*) true, fix this
|
|
function _inserted(element) {
|
|
// TODO(sjmiles): it's possible we were inserted and removed in the space
|
|
// of one microtask, in which case we won't be 'inDocument' here
|
|
// But there are other cases where we are testing for inserted without
|
|
// specific knowledge of mutations, and must test 'inDocument' to determine
|
|
// whether to call inserted
|
|
// If we can factor these cases into separate code paths we can have
|
|
// better diagnostics.
|
|
// TODO(sjmiles): when logging, do work on all custom elements so we can
|
|
// track behavior even when callbacks not defined
|
|
//console.log('inserted: ', element.localName);
|
|
if (element.attachedCallback || element.detachedCallback || (element.__upgraded__ && logFlags.dom)) {
|
|
logFlags.dom && console.group('inserted:', element.localName);
|
|
if (inDocument(element)) {
|
|
element.__inserted = (element.__inserted || 0) + 1;
|
|
// if we are in a 'removed' state, bluntly adjust to an 'inserted' state
|
|
if (element.__inserted < 1) {
|
|
element.__inserted = 1;
|
|
}
|
|
// if we are 'over inserted', squelch the callback
|
|
if (element.__inserted > 1) {
|
|
logFlags.dom && console.warn('inserted:', element.localName,
|
|
'insert/remove count:', element.__inserted)
|
|
} else if (element.attachedCallback) {
|
|
logFlags.dom && console.log('inserted:', element.localName);
|
|
element.attachedCallback();
|
|
}
|
|
}
|
|
logFlags.dom && console.groupEnd();
|
|
}
|
|
}
|
|
|
|
function removedNode(node) {
|
|
removed(node);
|
|
forSubtree(node, function(e) {
|
|
removed(e);
|
|
});
|
|
}
|
|
|
|
function removed(element) {
|
|
if (hasPolyfillMutations) {
|
|
deferMutation(function() {
|
|
_removed(element);
|
|
});
|
|
} else {
|
|
_removed(element);
|
|
}
|
|
}
|
|
|
|
function _removed(element) {
|
|
// TODO(sjmiles): temporary: do work on all custom elements so we can track
|
|
// behavior even when callbacks not defined
|
|
if (element.attachedCallback || element.detachedCallback || (element.__upgraded__ && logFlags.dom)) {
|
|
logFlags.dom && console.group('removed:', element.localName);
|
|
if (!inDocument(element)) {
|
|
element.__inserted = (element.__inserted || 0) - 1;
|
|
// if we are in a 'inserted' state, bluntly adjust to an 'removed' state
|
|
if (element.__inserted > 0) {
|
|
element.__inserted = 0;
|
|
}
|
|
// if we are 'over removed', squelch the callback
|
|
if (element.__inserted < 0) {
|
|
logFlags.dom && console.warn('removed:', element.localName,
|
|
'insert/remove count:', element.__inserted)
|
|
} else if (element.detachedCallback) {
|
|
element.detachedCallback();
|
|
}
|
|
}
|
|
logFlags.dom && console.groupEnd();
|
|
}
|
|
}
|
|
|
|
// SD polyfill intrustion due mainly to the fact that 'document'
|
|
// is not entirely wrapped
|
|
function wrapIfNeeded(node) {
|
|
return window.ShadowDOMPolyfill ? ShadowDOMPolyfill.wrapIfNeeded(node)
|
|
: node;
|
|
}
|
|
|
|
function inDocument(element) {
|
|
var p = element;
|
|
var doc = wrapIfNeeded(document);
|
|
while (p) {
|
|
if (p == doc) {
|
|
return true;
|
|
}
|
|
p = p.parentNode || p.host;
|
|
}
|
|
}
|
|
|
|
function watchShadow(node) {
|
|
if (node.shadowRoot && !node.shadowRoot.__watched) {
|
|
logFlags.dom && console.log('watching shadow-root for: ', node.localName);
|
|
// watch all unwatched roots...
|
|
var root = node.shadowRoot;
|
|
while (root) {
|
|
watchRoot(root);
|
|
root = root.olderShadowRoot;
|
|
}
|
|
}
|
|
}
|
|
|
|
function watchRoot(root) {
|
|
if (!root.__watched) {
|
|
observe(root);
|
|
root.__watched = true;
|
|
}
|
|
}
|
|
|
|
function handler(mutations) {
|
|
//
|
|
if (logFlags.dom) {
|
|
var mx = mutations[0];
|
|
if (mx && mx.type === 'childList' && mx.addedNodes) {
|
|
if (mx.addedNodes) {
|
|
var d = mx.addedNodes[0];
|
|
while (d && d !== document && !d.host) {
|
|
d = d.parentNode;
|
|
}
|
|
var u = d && (d.URL || d._URL || (d.host && d.host.localName)) || '';
|
|
u = u.split('/?').shift().split('/').pop();
|
|
}
|
|
}
|
|
console.group('mutations (%d) [%s]', mutations.length, u || '');
|
|
}
|
|
//
|
|
mutations.forEach(function(mx) {
|
|
//logFlags.dom && console.group('mutation');
|
|
if (mx.type === 'childList') {
|
|
forEach(mx.addedNodes, function(n) {
|
|
//logFlags.dom && console.log(n.localName);
|
|
if (!n.localName) {
|
|
return;
|
|
}
|
|
// nodes added may need lifecycle management
|
|
addedNode(n);
|
|
});
|
|
// removed nodes may need lifecycle management
|
|
forEach(mx.removedNodes, function(n) {
|
|
//logFlags.dom && console.log(n.localName);
|
|
if (!n.localName) {
|
|
return;
|
|
}
|
|
removedNode(n);
|
|
});
|
|
}
|
|
//logFlags.dom && console.groupEnd();
|
|
});
|
|
logFlags.dom && console.groupEnd();
|
|
};
|
|
|
|
var observer = new MutationObserver(handler);
|
|
|
|
function takeRecords() {
|
|
// TODO(sjmiles): ask Raf why we have to call handler ourselves
|
|
handler(observer.takeRecords());
|
|
takeMutations();
|
|
}
|
|
|
|
var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
|
|
|
|
function observe(inRoot) {
|
|
observer.observe(inRoot, {childList: true, subtree: true});
|
|
}
|
|
|
|
function observeDocument(doc) {
|
|
observe(doc);
|
|
}
|
|
|
|
function upgradeDocument(doc) {
|
|
logFlags.dom && console.group('upgradeDocument: ', (doc.baseURI).split('/').pop());
|
|
addedNode(doc);
|
|
logFlags.dom && console.groupEnd();
|
|
}
|
|
|
|
function upgradeDocumentTree(doc) {
|
|
doc = wrapIfNeeded(doc);
|
|
//console.log('upgradeDocumentTree: ', (doc.baseURI).split('/').pop());
|
|
// upgrade contained imported documents
|
|
var imports = doc.querySelectorAll('link[rel=' + IMPORT_LINK_TYPE + ']');
|
|
for (var i=0, l=imports.length, n; (i<l) && (n=imports[i]); i++) {
|
|
if (n.import && n.import.__parsed) {
|
|
upgradeDocumentTree(n.import);
|
|
}
|
|
}
|
|
upgradeDocument(doc);
|
|
}
|
|
|
|
// exports
|
|
scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE;
|
|
scope.watchShadow = watchShadow;
|
|
scope.upgradeDocumentTree = upgradeDocumentTree;
|
|
scope.upgradeAll = addedNode;
|
|
scope.upgradeSubtree = addedSubtree;
|
|
scope.insertedNode = insertedNode;
|
|
|
|
scope.observeDocument = observeDocument;
|
|
scope.upgradeDocument = upgradeDocument;
|
|
|
|
scope.takeRecords = takeRecords;
|
|
|
|
})(window.CustomElements);
|
|
|
|
/*
|
|
* Copyright 2013 The Polymer Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style
|
|
* license that can be found in the LICENSE file.
|
|
*/
|
|
|
|
/**
|
|
* Implements `document.register`
|
|
* @module CustomElements
|
|
*/
|
|
|
|
/**
|
|
* Polyfilled extensions to the `document` object.
|
|
* @class Document
|
|
*/
|
|
|
|
(function(scope) {
|
|
|
|
// imports
|
|
|
|
if (!scope) {
|
|
scope = window.CustomElements = {flags:{}};
|
|
}
|
|
var flags = scope.flags;
|
|
|
|
// native document.registerElement?
|
|
|
|
var hasNative = Boolean(document.registerElement);
|
|
// For consistent timing, use native custom elements only when not polyfilling
|
|
// other key related web components features.
|
|
var useNative = !flags.register && hasNative && !window.ShadowDOMPolyfill && (!window.HTMLImports || HTMLImports.useNative);
|
|
|
|
if (useNative) {
|
|
|
|
// stub
|
|
var nop = function() {};
|
|
|
|
// exports
|
|
scope.registry = {};
|
|
scope.upgradeElement = nop;
|
|
|
|
scope.watchShadow = nop;
|
|
scope.upgrade = nop;
|
|
scope.upgradeAll = nop;
|
|
scope.upgradeSubtree = nop;
|
|
scope.observeDocument = nop;
|
|
scope.upgradeDocument = nop;
|
|
scope.upgradeDocumentTree = nop;
|
|
scope.takeRecords = nop;
|
|
scope.reservedTagList = [];
|
|
|
|
} else {
|
|
|
|
/**
|
|
* Registers a custom tag name with the document.
|
|
*
|
|
* When a registered element is created, a `readyCallback` method is called
|
|
* in the scope of the element. The `readyCallback` method can be specified on
|
|
* either `options.prototype` or `options.lifecycle` with the latter taking
|
|
* precedence.
|
|
*
|
|
* @method register
|
|
* @param {String} name The tag name to register. Must include a dash ('-'),
|
|
* for example 'x-component'.
|
|
* @param {Object} options
|
|
* @param {String} [options.extends]
|
|
* (_off spec_) Tag name of an element to extend (or blank for a new
|
|
* element). This parameter is not part of the specification, but instead
|
|
* is a hint for the polyfill because the extendee is difficult to infer.
|
|
* Remember that the input prototype must chain to the extended element's
|
|
* prototype (or HTMLElement.prototype) regardless of the value of
|
|
* `extends`.
|
|
* @param {Object} options.prototype The prototype to use for the new
|
|
* element. The prototype must inherit from HTMLElement.
|
|
* @param {Object} [options.lifecycle]
|
|
* Callbacks that fire at important phases in the life of the custom
|
|
* element.
|
|
*
|
|
* @example
|
|
* FancyButton = document.registerElement("fancy-button", {
|
|
* extends: 'button',
|
|
* prototype: Object.create(HTMLButtonElement.prototype, {
|
|
* readyCallback: {
|
|
* value: function() {
|
|
* console.log("a fancy-button was created",
|
|
* }
|
|
* }
|
|
* })
|
|
* });
|
|
* @return {Function} Constructor for the newly registered type.
|
|
*/
|
|
function register(name, options) {
|
|
//console.warn('document.registerElement("' + name + '", ', options, ')');
|
|
// construct a defintion out of options
|
|
// TODO(sjmiles): probably should clone options instead of mutating it
|
|
var definition = options || {};
|
|
if (!name) {
|
|
// TODO(sjmiles): replace with more appropriate error (EricB can probably
|
|
// offer guidance)
|
|
throw new Error('document.registerElement: first argument `name` must not be empty');
|
|
}
|
|
if (name.indexOf('-') < 0) {
|
|
// TODO(sjmiles): replace with more appropriate error (EricB can probably
|
|
// offer guidance)
|
|
throw new Error('document.registerElement: first argument (\'name\') must contain a dash (\'-\'). Argument provided was \'' + String(name) + '\'.');
|
|
}
|
|
// prevent registering reserved names
|
|
if (isReservedTag(name)) {
|
|
throw new Error('Failed to execute \'registerElement\' on \'Document\': Registration failed for type \'' + String(name) + '\'. The type name is invalid.');
|
|
}
|
|
// elements may only be registered once
|
|
if (getRegisteredDefinition(name)) {
|
|
throw new Error('DuplicateDefinitionError: a type with name \'' + String(name) + '\' is already registered');
|
|
}
|
|
// must have a prototype, default to an extension of HTMLElement
|
|
// TODO(sjmiles): probably should throw if no prototype, check spec
|
|
if (!definition.prototype) {
|
|
// TODO(sjmiles): replace with more appropriate error (EricB can probably
|
|
// offer guidance)
|
|
throw new Error('Options missing required prototype property');
|
|
}
|
|
// record name
|
|
definition.__name = name.toLowerCase();
|
|
// ensure a lifecycle object so we don't have to null test it
|
|
definition.lifecycle = definition.lifecycle || {};
|
|
// build a list of ancestral custom elements (for native base detection)
|
|
// TODO(sjmiles): we used to need to store this, but current code only
|
|
// uses it in 'resolveTagName': it should probably be inlined
|
|
definition.ancestry = ancestry(definition.extends);
|
|
// extensions of native specializations of HTMLElement require localName
|
|
// to remain native, and use secondary 'is' specifier for extension type
|
|
resolveTagName(definition);
|
|
// some platforms require modifications to the user-supplied prototype
|
|
// chain
|
|
resolvePrototypeChain(definition);
|
|
// overrides to implement attributeChanged callback
|
|
overrideAttributeApi(definition.prototype);
|
|
// 7.1.5: Register the DEFINITION with DOCUMENT
|
|
registerDefinition(definition.__name, definition);
|
|
// 7.1.7. Run custom element constructor generation algorithm with PROTOTYPE
|
|
// 7.1.8. Return the output of the previous step.
|
|
definition.ctor = generateConstructor(definition);
|
|
definition.ctor.prototype = definition.prototype;
|
|
// force our .constructor to be our actual constructor
|
|
definition.prototype.constructor = definition.ctor;
|
|
// if initial parsing is complete
|
|
if (scope.ready) {
|
|
// upgrade any pre-existing nodes of this type
|
|
scope.upgradeDocumentTree(document);
|
|
}
|
|
return definition.ctor;
|
|
}
|
|
|
|
function isReservedTag(name) {
|
|
for (var i = 0; i < reservedTagList.length; i++) {
|
|
if (name === reservedTagList[i]) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
var reservedTagList = [
|
|
'annotation-xml', 'color-profile', 'font-face', 'font-face-src',
|
|
'font-face-uri', 'font-face-format', 'font-face-name', 'missing-glyph'
|
|
];
|
|
|
|
function ancestry(extnds) {
|
|
var extendee = getRegisteredDefinition(extnds);
|
|
if (extendee) {
|
|
return ancestry(extendee.extends).concat([extendee]);
|
|
}
|
|
return [];
|
|
}
|
|
|
|
function resolveTagName(definition) {
|
|
// if we are explicitly extending something, that thing is our
|
|
// baseTag, unless it represents a custom component
|
|
var baseTag = definition.extends;
|
|
// if our ancestry includes custom components, we only have a
|
|
// baseTag if one of them does
|
|
for (var i=0, a; (a=definition.ancestry[i]); i++) {
|
|
baseTag = a.is && a.tag;
|
|
}
|
|
// our tag is our baseTag, if it exists, and otherwise just our name
|
|
definition.tag = baseTag || definition.__name;
|
|
if (baseTag) {
|
|
// if there is a base tag, use secondary 'is' specifier
|
|
definition.is = definition.__name;
|
|
}
|
|
}
|
|
|
|
function resolvePrototypeChain(definition) {
|
|
// if we don't support __proto__ we need to locate the native level
|
|
// prototype for precise mixing in
|
|
if (!Object.__proto__) {
|
|
// default prototype
|
|
var nativePrototype = HTMLElement.prototype;
|
|
// work out prototype when using type-extension
|
|
if (definition.is) {
|
|
var inst = document.createElement(definition.tag);
|
|
var expectedPrototype = Object.getPrototypeOf(inst);
|
|
// only set nativePrototype if it will actually appear in the definition's chain
|
|
if (expectedPrototype === definition.prototype) {
|
|
nativePrototype = expectedPrototype;
|
|
}
|
|
}
|
|
// ensure __proto__ reference is installed at each point on the prototype
|
|
// chain.
|
|
// NOTE: On platforms without __proto__, a mixin strategy is used instead
|
|
// of prototype swizzling. In this case, this generated __proto__ provides
|
|
// limited support for prototype traversal.
|
|
var proto = definition.prototype, ancestor;
|
|
while (proto && (proto !== nativePrototype)) {
|
|
ancestor = Object.getPrototypeOf(proto);
|
|
proto.__proto__ = ancestor;
|
|
proto = ancestor;
|
|
}
|
|
// cache this in case of mixin
|
|
definition.native = nativePrototype;
|
|
}
|
|
}
|
|
|
|
// SECTION 4
|
|
|
|
function instantiate(definition) {
|
|
// 4.a.1. Create a new object that implements PROTOTYPE
|
|
// 4.a.2. Let ELEMENT by this new object
|
|
//
|
|
// the custom element instantiation algorithm must also ensure that the
|
|
// output is a valid DOM element with the proper wrapper in place.
|
|
//
|
|
return upgrade(domCreateElement(definition.tag), definition);
|
|
}
|
|
|
|
function upgrade(element, definition) {
|
|
// some definitions specify an 'is' attribute
|
|
if (definition.is) {
|
|
element.setAttribute('is', definition.is);
|
|
}
|
|
// remove 'unresolved' attr, which is a standin for :unresolved.
|
|
element.removeAttribute('unresolved');
|
|
// make 'element' implement definition.prototype
|
|
implement(element, definition);
|
|
// flag as upgraded
|
|
element.__upgraded__ = true;
|
|
// lifecycle management
|
|
created(element);
|
|
// attachedCallback fires in tree order, call before recursing
|
|
scope.insertedNode(element);
|
|
// there should never be a shadow root on element at this point
|
|
scope.upgradeSubtree(element);
|
|
// OUTPUT
|
|
return element;
|
|
}
|
|
|
|
function implement(element, definition) {
|
|
// prototype swizzling is best
|
|
if (Object.__proto__) {
|
|
element.__proto__ = definition.prototype;
|
|
} else {
|
|
// where above we can re-acquire inPrototype via
|
|
// getPrototypeOf(Element), we cannot do so when
|
|
// we use mixin, so we install a magic reference
|
|
customMixin(element, definition.prototype, definition.native);
|
|
element.__proto__ = definition.prototype;
|
|
}
|
|
}
|
|
|
|
function customMixin(inTarget, inSrc, inNative) {
|
|
// TODO(sjmiles): 'used' allows us to only copy the 'youngest' version of
|
|
// any property. This set should be precalculated. We also need to
|
|
// consider this for supporting 'super'.
|
|
var used = {};
|
|
// start with inSrc
|
|
var p = inSrc;
|
|
// The default is HTMLElement.prototype, so we add a test to avoid mixing in
|
|
// native prototypes
|
|
while (p !== inNative && p !== HTMLElement.prototype) {
|
|
var keys = Object.getOwnPropertyNames(p);
|
|
for (var i=0, k; k=keys[i]; i++) {
|
|
if (!used[k]) {
|
|
Object.defineProperty(inTarget, k,
|
|
Object.getOwnPropertyDescriptor(p, k));
|
|
used[k] = 1;
|
|
}
|
|
}
|
|
p = Object.getPrototypeOf(p);
|
|
}
|
|
}
|
|
|
|
function created(element) {
|
|
// invoke createdCallback
|
|
if (element.createdCallback) {
|
|
element.createdCallback();
|
|
}
|
|
}
|
|
|
|
// attribute watching
|
|
|
|
function overrideAttributeApi(prototype) {
|
|
// overrides to implement callbacks
|
|
// TODO(sjmiles): should support access via .attributes NamedNodeMap
|
|
// TODO(sjmiles): preserves user defined overrides, if any
|
|
if (prototype.setAttribute._polyfilled) {
|
|
return;
|
|
}
|
|
var setAttribute = prototype.setAttribute;
|
|
prototype.setAttribute = function(name, value) {
|
|
changeAttribute.call(this, name, value, setAttribute);
|
|
}
|
|
var removeAttribute = prototype.removeAttribute;
|
|
prototype.removeAttribute = function(name) {
|
|
changeAttribute.call(this, name, null, removeAttribute);
|
|
}
|
|
prototype.setAttribute._polyfilled = true;
|
|
}
|
|
|
|
// https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/
|
|
// index.html#dfn-attribute-changed-callback
|
|
function changeAttribute(name, value, operation) {
|
|
name = name.toLowerCase();
|
|
var oldValue = this.getAttribute(name);
|
|
operation.apply(this, arguments);
|
|
var newValue = this.getAttribute(name);
|
|
if (this.attributeChangedCallback
|
|
&& (newValue !== oldValue)) {
|
|
this.attributeChangedCallback(name, oldValue, newValue);
|
|
}
|
|
}
|
|
|
|
// element registry (maps tag names to definitions)
|
|
|
|
var registry = {};
|
|
|
|
function getRegisteredDefinition(name) {
|
|
if (name) {
|
|
return registry[name.toLowerCase()];
|
|
}
|
|
}
|
|
|
|
function registerDefinition(name, definition) {
|
|
registry[name] = definition;
|
|
}
|
|
|
|
function generateConstructor(definition) {
|
|
return function() {
|
|
return instantiate(definition);
|
|
};
|
|
}
|
|
|
|
var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
|
|
function createElementNS(namespace, tag, typeExtension) {
|
|
// NOTE: we do not support non-HTML elements,
|
|
// just call createElementNS for non HTML Elements
|
|
if (namespace === HTML_NAMESPACE) {
|
|
return createElement(tag, typeExtension);
|
|
} else {
|
|
return domCreateElementNS(namespace, tag);
|
|
}
|
|
}
|
|
|
|
function createElement(tag, typeExtension) {
|
|
// TODO(sjmiles): ignore 'tag' when using 'typeExtension', we could
|
|
// error check it, or perhaps there should only ever be one argument
|
|
var definition = getRegisteredDefinition(typeExtension || tag);
|
|
if (definition) {
|
|
if (tag == definition.tag && typeExtension == definition.is) {
|
|
return new definition.ctor();
|
|
}
|
|
// Handle empty string for type extension.
|
|
if (!typeExtension && !definition.is) {
|
|
return new definition.ctor();
|
|
}
|
|
}
|
|
|
|
if (typeExtension) {
|
|
var element = createElement(tag);
|
|
element.setAttribute('is', typeExtension);
|
|
return element;
|
|
}
|
|
var element = domCreateElement(tag);
|
|
// Custom tags should be HTMLElements even if not upgraded.
|
|
if (tag.indexOf('-') >= 0) {
|
|
implement(element, HTMLElement);
|
|
}
|
|
return element;
|
|
}
|
|
|
|
function upgradeElement(element) {
|
|
if (!element.__upgraded__ && (element.nodeType === Node.ELEMENT_NODE)) {
|
|
var is = element.getAttribute('is');
|
|
var definition = getRegisteredDefinition(is || element.localName);
|
|
if (definition) {
|
|
if (is && definition.tag == element.localName) {
|
|
return upgrade(element, definition);
|
|
} else if (!is && !definition.extends) {
|
|
return upgrade(element, definition);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function cloneNode(deep) {
|
|
// call original clone
|
|
var n = domCloneNode.call(this, deep);
|
|
// upgrade the element and subtree
|
|
scope.upgradeAll(n);
|
|
// return the clone
|
|
return n;
|
|
}
|
|
// capture native createElement before we override it
|
|
|
|
var domCreateElement = document.createElement.bind(document);
|
|
var domCreateElementNS = document.createElementNS.bind(document);
|
|
|
|
// capture native cloneNode before we override it
|
|
|
|
var domCloneNode = Node.prototype.cloneNode;
|
|
|
|
// exports
|
|
|
|
document.registerElement = register;
|
|
document.createElement = createElement; // override
|
|
document.createElementNS = createElementNS; // override
|
|
Node.prototype.cloneNode = cloneNode; // override
|
|
|
|
scope.registry = registry;
|
|
|
|
/**
|
|
* Upgrade an element to a custom element. Upgrading an element
|
|
* causes the custom prototype to be applied, an `is` attribute
|
|
* to be attached (as needed), and invocation of the `readyCallback`.
|
|
* `upgrade` does nothing if the element is already upgraded, or
|
|
* if it matches no registered custom tag name.
|
|
*
|
|
* @method ugprade
|
|
* @param {Element} element The element to upgrade.
|
|
* @return {Element} The upgraded element.
|
|
*/
|
|
scope.upgrade = upgradeElement;
|
|
}
|
|
|
|
// Create a custom 'instanceof'. This is necessary when CustomElements
|
|
// are implemented via a mixin strategy, as for example on IE10.
|
|
var isInstance;
|
|
if (!Object.__proto__ && !useNative) {
|
|
isInstance = function(obj, ctor) {
|
|
var p = obj;
|
|
while (p) {
|
|
// NOTE: this is not technically correct since we're not checking if
|
|
// an object is an instance of a constructor; however, this should
|
|
// be good enough for the mixin strategy.
|
|
if (p === ctor.prototype) {
|
|
return true;
|
|
}
|
|
p = p.__proto__;
|
|
}
|
|
return false;
|
|
}
|
|
} else {
|
|
isInstance = function(obj, base) {
|
|
return obj instanceof base;
|
|
}
|
|
}
|
|
|
|
// exports
|
|
scope.instanceof = isInstance;
|
|
scope.reservedTagList = reservedTagList;
|
|
|
|
// bc
|
|
document.register = document.registerElement;
|
|
|
|
scope.hasNative = hasNative;
|
|
scope.useNative = useNative;
|
|
|
|
})(window.CustomElements);
|
|
|
|
/*
|
|
* Copyright 2013 The Polymer Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style
|
|
* license that can be found in the LICENSE file.
|
|
*/
|
|
|
|
(function(scope) {
|
|
|
|
// import
|
|
|
|
var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE;
|
|
|
|
// highlander object for parsing a document tree
|
|
|
|
var parser = {
|
|
selectors: [
|
|
'link[rel=' + IMPORT_LINK_TYPE + ']'
|
|
],
|
|
map: {
|
|
link: 'parseLink'
|
|
},
|
|
parse: function(inDocument) {
|
|
if (!inDocument.__parsed) {
|
|
// only parse once
|
|
inDocument.__parsed = true;
|
|
// all parsable elements in inDocument (depth-first pre-order traversal)
|
|
var elts = inDocument.querySelectorAll(parser.selectors);
|
|
// for each parsable node type, call the mapped parsing method
|
|
forEach(elts, function(e) {
|
|
parser[parser.map[e.localName]](e);
|
|
});
|
|
// upgrade all upgradeable static elements, anything dynamically
|
|
// created should be caught by observer
|
|
CustomElements.upgradeDocument(inDocument);
|
|
// observe document for dom changes
|
|
CustomElements.observeDocument(inDocument);
|
|
}
|
|
},
|
|
parseLink: function(linkElt) {
|
|
// imports
|
|
if (isDocumentLink(linkElt)) {
|
|
this.parseImport(linkElt);
|
|
}
|
|
},
|
|
parseImport: function(linkElt) {
|
|
if (linkElt.import) {
|
|
parser.parse(linkElt.import);
|
|
}
|
|
}
|
|
};
|
|
|
|
function isDocumentLink(inElt) {
|
|
return (inElt.localName === 'link'
|
|
&& inElt.getAttribute('rel') === IMPORT_LINK_TYPE);
|
|
}
|
|
|
|
var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
|
|
|
|
// exports
|
|
|
|
scope.parser = parser;
|
|
scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE;
|
|
|
|
})(window.CustomElements);
|
|
/*
|
|
* Copyright 2013 The Polymer Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style
|
|
* license that can be found in the LICENSE file.
|
|
*/
|
|
(function(scope){
|
|
|
|
// bootstrap parsing
|
|
function bootstrap() {
|
|
// parse document
|
|
CustomElements.parser.parse(document);
|
|
// one more pass before register is 'live'
|
|
CustomElements.upgradeDocument(document);
|
|
// choose async
|
|
var async = window.Platform && Platform.endOfMicrotask ?
|
|
Platform.endOfMicrotask :
|
|
setTimeout;
|
|
async(function() {
|
|
// set internal 'ready' flag, now document.registerElement will trigger
|
|
// synchronous upgrades
|
|
CustomElements.ready = true;
|
|
// capture blunt profiling data
|
|
CustomElements.readyTime = Date.now();
|
|
if (window.HTMLImports) {
|
|
CustomElements.elapsed = CustomElements.readyTime - HTMLImports.readyTime;
|
|
}
|
|
// notify the system that we are bootstrapped
|
|
document.dispatchEvent(
|
|
new CustomEvent('WebComponentsReady', {bubbles: true})
|
|
);
|
|
|
|
// install upgrade hook if HTMLImports are available
|
|
if (window.HTMLImports) {
|
|
HTMLImports.__importsParsingHook = function(elt) {
|
|
CustomElements.parser.parse(elt.import);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// CustomEvent shim for IE
|
|
if (typeof window.CustomEvent !== 'function') {
|
|
window.CustomEvent = function(inType) {
|
|
var e = document.createEvent('HTMLEvents');
|
|
e.initEvent(inType, true, true);
|
|
return e;
|
|
};
|
|
}
|
|
|
|
// When loading at readyState complete time (or via flag), boot custom elements
|
|
// immediately.
|
|
// If relevant, HTMLImports must already be loaded.
|
|
if (document.readyState === 'complete' || scope.flags.eager) {
|
|
bootstrap();
|
|
// When loading at readyState interactive time, bootstrap only if HTMLImports
|
|
// are not pending. Also avoid IE as the semantics of this state are unreliable.
|
|
} else if (document.readyState === 'interactive' && !window.attachEvent &&
|
|
(!window.HTMLImports || window.HTMLImports.ready)) {
|
|
bootstrap();
|
|
// When loading at other readyStates, wait for the appropriate DOM event to
|
|
// bootstrap.
|
|
} else {
|
|
var loadEvent = window.HTMLImports && !HTMLImports.ready ?
|
|
'HTMLImportsLoaded' : 'DOMContentLoaded';
|
|
window.addEventListener(loadEvent, bootstrap);
|
|
}
|
|
|
|
})(window.CustomElements);
|
|
|
|
/*
|
|
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
* Code distributed by Google as part of the polymer project is also
|
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
*/
|
|
|
|
(function() {
|
|
|
|
if (window.ShadowDOMPolyfill) {
|
|
|
|
// ensure wrapped inputs for these functions
|
|
var fns = ['upgradeAll', 'upgradeSubtree', 'observeDocument',
|
|
'upgradeDocument'];
|
|
|
|
// cache originals
|
|
var original = {};
|
|
fns.forEach(function(fn) {
|
|
original[fn] = CustomElements[fn];
|
|
});
|
|
|
|
// override
|
|
fns.forEach(function(fn) {
|
|
CustomElements[fn] = function(inNode) {
|
|
return original[fn](wrap(inNode));
|
|
};
|
|
});
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
/*
|
|
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
* Code distributed by Google as part of the polymer project is also
|
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
*/
|
|
|
|
(function(scope) {
|
|
var endOfMicrotask = scope.endOfMicrotask;
|
|
|
|
// Generic url loader
|
|
function Loader(regex) {
|
|
this.cache = Object.create(null);
|
|
this.map = Object.create(null);
|
|
this.requests = 0;
|
|
this.regex = regex;
|
|
}
|
|
Loader.prototype = {
|
|
|
|
// TODO(dfreedm): there may be a better factoring here
|
|
// extract absolute urls from the text (full of relative urls)
|
|
extractUrls: function(text, base) {
|
|
var matches = [];
|
|
var matched, u;
|
|
while ((matched = this.regex.exec(text))) {
|
|
u = new URL(matched[1], base);
|
|
matches.push({matched: matched[0], url: u.href});
|
|
}
|
|
return matches;
|
|
},
|
|
// take a text blob, a root url, and a callback and load all the urls found within the text
|
|
// returns a map of absolute url to text
|
|
process: function(text, root, callback) {
|
|
var matches = this.extractUrls(text, root);
|
|
|
|
// every call to process returns all the text this loader has ever received
|
|
var done = callback.bind(null, this.map);
|
|
this.fetch(matches, done);
|
|
},
|
|
// build a mapping of url -> text from matches
|
|
fetch: function(matches, callback) {
|
|
var inflight = matches.length;
|
|
|
|
// return early if there is no fetching to be done
|
|
if (!inflight) {
|
|
return callback();
|
|
}
|
|
|
|
// wait for all subrequests to return
|
|
var done = function() {
|
|
if (--inflight === 0) {
|
|
callback();
|
|
}
|
|
};
|
|
|
|
// start fetching all subrequests
|
|
var m, req, url;
|
|
for (var i = 0; i < inflight; i++) {
|
|
m = matches[i];
|
|
url = m.url;
|
|
req = this.cache[url];
|
|
// if this url has already been requested, skip requesting it again
|
|
if (!req) {
|
|
req = this.xhr(url);
|
|
req.match = m;
|
|
this.cache[url] = req;
|
|
}
|
|
// wait for the request to process its subrequests
|
|
req.wait(done);
|
|
}
|
|
},
|
|
handleXhr: function(request) {
|
|
var match = request.match;
|
|
var url = match.url;
|
|
|
|
// handle errors with an empty string
|
|
var response = request.response || request.responseText || '';
|
|
this.map[url] = response;
|
|
this.fetch(this.extractUrls(response, url), request.resolve);
|
|
},
|
|
xhr: function(url) {
|
|
this.requests++;
|
|
var request = new XMLHttpRequest();
|
|
request.open('GET', url, true);
|
|
request.send();
|
|
request.onerror = request.onload = this.handleXhr.bind(this, request);
|
|
|
|
// queue of tasks to run after XHR returns
|
|
request.pending = [];
|
|
request.resolve = function() {
|
|
var pending = request.pending;
|
|
for(var i = 0; i < pending.length; i++) {
|
|
pending[i]();
|
|
}
|
|
request.pending = null;
|
|
};
|
|
|
|
// if we have already resolved, pending is null, async call the callback
|
|
request.wait = function(fn) {
|
|
if (request.pending) {
|
|
request.pending.push(fn);
|
|
} else {
|
|
endOfMicrotask(fn);
|
|
}
|
|
};
|
|
|
|
return request;
|
|
}
|
|
};
|
|
|
|
scope.Loader = Loader;
|
|
})(window.Platform);
|
|
|
|
/*
|
|
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
* Code distributed by Google as part of the polymer project is also
|
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
*/
|
|
|
|
(function(scope) {
|
|
|
|
var urlResolver = scope.urlResolver;
|
|
var Loader = scope.Loader;
|
|
|
|
function StyleResolver() {
|
|
this.loader = new Loader(this.regex);
|
|
}
|
|
StyleResolver.prototype = {
|
|
regex: /@import\s+(?:url)?["'\(]*([^'"\)]*)['"\)]*;/g,
|
|
// Recursively replace @imports with the text at that url
|
|
resolve: function(text, url, callback) {
|
|
var done = function(map) {
|
|
callback(this.flatten(text, url, map));
|
|
}.bind(this);
|
|
this.loader.process(text, url, done);
|
|
},
|
|
// resolve the textContent of a style node
|
|
resolveNode: function(style, url, callback) {
|
|
var text = style.textContent;
|
|
var done = function(text) {
|
|
style.textContent = text;
|
|
callback(style);
|
|
};
|
|
this.resolve(text, url, done);
|
|
},
|
|
// flatten all the @imports to text
|
|
flatten: function(text, base, map) {
|
|
var matches = this.loader.extractUrls(text, base);
|
|
var match, url, intermediate;
|
|
for (var i = 0; i < matches.length; i++) {
|
|
match = matches[i];
|
|
url = match.url;
|
|
// resolve any css text to be relative to the importer, keep absolute url
|
|
intermediate = urlResolver.resolveCssText(map[url], url, true);
|
|
// flatten intermediate @imports
|
|
intermediate = this.flatten(intermediate, base, map);
|
|
text = text.replace(match.matched, intermediate);
|
|
}
|
|
return text;
|
|
},
|
|
loadStyles: function(styles, base, callback) {
|
|
var loaded=0, l = styles.length;
|
|
// called in the context of the style
|
|
function loadedStyle(style) {
|
|
loaded++;
|
|
if (loaded === l && callback) {
|
|
callback();
|
|
}
|
|
}
|
|
for (var i=0, s; (i<l) && (s=styles[i]); i++) {
|
|
this.resolveNode(s, base, loadedStyle);
|
|
}
|
|
}
|
|
};
|
|
|
|
var styleResolver = new StyleResolver();
|
|
|
|
// exports
|
|
scope.styleResolver = styleResolver;
|
|
|
|
})(window.Platform);
|
|
|
|
// Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
// This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
// The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
// The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
// Code distributed by Google as part of the polymer project is also
|
|
// subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
|
|
(function(global) {
|
|
'use strict';
|
|
|
|
var filter = Array.prototype.filter.call.bind(Array.prototype.filter);
|
|
|
|
function getTreeScope(node) {
|
|
while (node.parentNode) {
|
|
node = node.parentNode;
|
|
}
|
|
|
|
return typeof node.getElementById === 'function' ? node : null;
|
|
}
|
|
|
|
Node.prototype.bind = function(name, observable) {
|
|
console.error('Unhandled binding to Node: ', this, name, observable);
|
|
};
|
|
|
|
Node.prototype.bindFinished = function() {};
|
|
|
|
function updateBindings(node, name, binding) {
|
|
var bindings = node.bindings_;
|
|
if (!bindings)
|
|
bindings = node.bindings_ = {};
|
|
|
|
if (bindings[name])
|
|
binding[name].close();
|
|
|
|
return bindings[name] = binding;
|
|
}
|
|
|
|
function returnBinding(node, name, binding) {
|
|
return binding;
|
|
}
|
|
|
|
function sanitizeValue(value) {
|
|
return value == null ? '' : value;
|
|
}
|
|
|
|
function updateText(node, value) {
|
|
node.data = sanitizeValue(value);
|
|
}
|
|
|
|
function textBinding(node) {
|
|
return function(value) {
|
|
return updateText(node, value);
|
|
};
|
|
}
|
|
|
|
var maybeUpdateBindings = returnBinding;
|
|
|
|
Object.defineProperty(Platform, 'enableBindingsReflection', {
|
|
get: function() {
|
|
return maybeUpdateBindings === updateBindings;
|
|
},
|
|
set: function(enable) {
|
|
maybeUpdateBindings = enable ? updateBindings : returnBinding;
|
|
return enable;
|
|
},
|
|
configurable: true
|
|
});
|
|
|
|
Text.prototype.bind = function(name, value, oneTime) {
|
|
if (name !== 'textContent')
|
|
return Node.prototype.bind.call(this, name, value, oneTime);
|
|
|
|
if (oneTime)
|
|
return updateText(this, value);
|
|
|
|
var observable = value;
|
|
updateText(this, observable.open(textBinding(this)));
|
|
return maybeUpdateBindings(this, name, observable);
|
|
}
|
|
|
|
function updateAttribute(el, name, conditional, value) {
|
|
if (conditional) {
|
|
if (value)
|
|
el.setAttribute(name, '');
|
|
else
|
|
el.removeAttribute(name);
|
|
return;
|
|
}
|
|
|
|
el.setAttribute(name, sanitizeValue(value));
|
|
}
|
|
|
|
function attributeBinding(el, name, conditional) {
|
|
return function(value) {
|
|
updateAttribute(el, name, conditional, value);
|
|
};
|
|
}
|
|
|
|
Element.prototype.bind = function(name, value, oneTime) {
|
|
var conditional = name[name.length - 1] == '?';
|
|
if (conditional) {
|
|
this.removeAttribute(name);
|
|
name = name.slice(0, -1);
|
|
}
|
|
|
|
if (oneTime)
|
|
return updateAttribute(this, name, conditional, value);
|
|
|
|
|
|
var observable = value;
|
|
updateAttribute(this, name, conditional,
|
|
observable.open(attributeBinding(this, name, conditional)));
|
|
|
|
return maybeUpdateBindings(this, name, observable);
|
|
};
|
|
|
|
var checkboxEventType;
|
|
(function() {
|
|
// Attempt to feature-detect which event (change or click) is fired first
|
|
// for checkboxes.
|
|
var div = document.createElement('div');
|
|
var checkbox = div.appendChild(document.createElement('input'));
|
|
checkbox.setAttribute('type', 'checkbox');
|
|
var first;
|
|
var count = 0;
|
|
checkbox.addEventListener('click', function(e) {
|
|
count++;
|
|
first = first || 'click';
|
|
});
|
|
checkbox.addEventListener('change', function() {
|
|
count++;
|
|
first = first || 'change';
|
|
});
|
|
|
|
var event = document.createEvent('MouseEvent');
|
|
event.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false,
|
|
false, false, false, 0, null);
|
|
checkbox.dispatchEvent(event);
|
|
// WebKit/Blink don't fire the change event if the element is outside the
|
|
// document, so assume 'change' for that case.
|
|
checkboxEventType = count == 1 ? 'change' : first;
|
|
})();
|
|
|
|
function getEventForInputType(element) {
|
|
switch (element.type) {
|
|
case 'checkbox':
|
|
return checkboxEventType;
|
|
case 'radio':
|
|
case 'select-multiple':
|
|
case 'select-one':
|
|
return 'change';
|
|
case 'range':
|
|
if (/Trident|MSIE/.test(navigator.userAgent))
|
|
return 'change';
|
|
default:
|
|
return 'input';
|
|
}
|
|
}
|
|
|
|
function updateInput(input, property, value, santizeFn) {
|
|
input[property] = (santizeFn || sanitizeValue)(value);
|
|
}
|
|
|
|
function inputBinding(input, property, santizeFn) {
|
|
return function(value) {
|
|
return updateInput(input, property, value, santizeFn);
|
|
}
|
|
}
|
|
|
|
function noop() {}
|
|
|
|
function bindInputEvent(input, property, observable, postEventFn) {
|
|
var eventType = getEventForInputType(input);
|
|
|
|
function eventHandler() {
|
|
observable.setValue(input[property]);
|
|
observable.discardChanges();
|
|
(postEventFn || noop)(input);
|
|
Platform.performMicrotaskCheckpoint();
|
|
}
|
|
input.addEventListener(eventType, eventHandler);
|
|
|
|
return {
|
|
close: function() {
|
|
input.removeEventListener(eventType, eventHandler);
|
|
observable.close();
|
|
},
|
|
|
|
observable_: observable
|
|
}
|
|
}
|
|
|
|
function booleanSanitize(value) {
|
|
return Boolean(value);
|
|
}
|
|
|
|
// |element| is assumed to be an HTMLInputElement with |type| == 'radio'.
|
|
// Returns an array containing all radio buttons other than |element| that
|
|
// have the same |name|, either in the form that |element| belongs to or,
|
|
// if no form, in the document tree to which |element| belongs.
|
|
//
|
|
// This implementation is based upon the HTML spec definition of a
|
|
// "radio button group":
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.html#radio-button-group
|
|
//
|
|
function getAssociatedRadioButtons(element) {
|
|
if (element.form) {
|
|
return filter(element.form.elements, function(el) {
|
|
return el != element &&
|
|
el.tagName == 'INPUT' &&
|
|
el.type == 'radio' &&
|
|
el.name == element.name;
|
|
});
|
|
} else {
|
|
var treeScope = getTreeScope(element);
|
|
if (!treeScope)
|
|
return [];
|
|
var radios = treeScope.querySelectorAll(
|
|
'input[type="radio"][name="' + element.name + '"]');
|
|
return filter(radios, function(el) {
|
|
return el != element && !el.form;
|
|
});
|
|
}
|
|
}
|
|
|
|
function checkedPostEvent(input) {
|
|
// Only the radio button that is getting checked gets an event. We
|
|
// therefore find all the associated radio buttons and update their
|
|
// check binding manually.
|
|
if (input.tagName === 'INPUT' &&
|
|
input.type === 'radio') {
|
|
getAssociatedRadioButtons(input).forEach(function(radio) {
|
|
var checkedBinding = radio.bindings_.checked;
|
|
if (checkedBinding) {
|
|
// Set the value directly to avoid an infinite call stack.
|
|
checkedBinding.observable_.setValue(false);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
HTMLInputElement.prototype.bind = function(name, value, oneTime) {
|
|
if (name !== 'value' && name !== 'checked')
|
|
return HTMLElement.prototype.bind.call(this, name, value, oneTime);
|
|
|
|
this.removeAttribute(name);
|
|
var sanitizeFn = name == 'checked' ? booleanSanitize : sanitizeValue;
|
|
var postEventFn = name == 'checked' ? checkedPostEvent : noop;
|
|
|
|
if (oneTime)
|
|
return updateInput(this, name, value, sanitizeFn);
|
|
|
|
|
|
var observable = value;
|
|
var binding = bindInputEvent(this, name, observable, postEventFn);
|
|
updateInput(this, name,
|
|
observable.open(inputBinding(this, name, sanitizeFn)),
|
|
sanitizeFn);
|
|
|
|
// Checkboxes may need to update bindings of other checkboxes.
|
|
return updateBindings(this, name, binding);
|
|
}
|
|
|
|
HTMLTextAreaElement.prototype.bind = function(name, value, oneTime) {
|
|
if (name !== 'value')
|
|
return HTMLElement.prototype.bind.call(this, name, value, oneTime);
|
|
|
|
this.removeAttribute('value');
|
|
|
|
if (oneTime)
|
|
return updateInput(this, 'value', value);
|
|
|
|
var observable = value;
|
|
var binding = bindInputEvent(this, 'value', observable);
|
|
updateInput(this, 'value',
|
|
observable.open(inputBinding(this, 'value', sanitizeValue)));
|
|
return maybeUpdateBindings(this, name, binding);
|
|
}
|
|
|
|
function updateOption(option, value) {
|
|
var parentNode = option.parentNode;;
|
|
var select;
|
|
var selectBinding;
|
|
var oldValue;
|
|
if (parentNode instanceof HTMLSelectElement &&
|
|
parentNode.bindings_ &&
|
|
parentNode.bindings_.value) {
|
|
select = parentNode;
|
|
selectBinding = select.bindings_.value;
|
|
oldValue = select.value;
|
|
}
|
|
|
|
option.value = sanitizeValue(value);
|
|
|
|
if (select && select.value != oldValue) {
|
|
selectBinding.observable_.setValue(select.value);
|
|
selectBinding.observable_.discardChanges();
|
|
Platform.performMicrotaskCheckpoint();
|
|
}
|
|
}
|
|
|
|
function optionBinding(option) {
|
|
return function(value) {
|
|
updateOption(option, value);
|
|
}
|
|
}
|
|
|
|
HTMLOptionElement.prototype.bind = function(name, value, oneTime) {
|
|
if (name !== 'value')
|
|
return HTMLElement.prototype.bind.call(this, name, value, oneTime);
|
|
|
|
this.removeAttribute('value');
|
|
|
|
if (oneTime)
|
|
return updateOption(this, value);
|
|
|
|
var observable = value;
|
|
var binding = bindInputEvent(this, 'value', observable);
|
|
updateOption(this, observable.open(optionBinding(this)));
|
|
return maybeUpdateBindings(this, name, binding);
|
|
}
|
|
|
|
HTMLSelectElement.prototype.bind = function(name, value, oneTime) {
|
|
if (name === 'selectedindex')
|
|
name = 'selectedIndex';
|
|
|
|
if (name !== 'selectedIndex' && name !== 'value')
|
|
return HTMLElement.prototype.bind.call(this, name, value, oneTime);
|
|
|
|
this.removeAttribute(name);
|
|
|
|
if (oneTime)
|
|
return updateInput(this, name, value);
|
|
|
|
var observable = value;
|
|
var binding = bindInputEvent(this, name, observable);
|
|
updateInput(this, name,
|
|
observable.open(inputBinding(this, name)));
|
|
|
|
// Option update events may need to access select bindings.
|
|
return updateBindings(this, name, binding);
|
|
}
|
|
})(this);
|
|
|
|
// Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
// This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
// The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
// The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
// Code distributed by Google as part of the polymer project is also
|
|
// subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
|
|
(function(global) {
|
|
'use strict';
|
|
|
|
function assert(v) {
|
|
if (!v)
|
|
throw new Error('Assertion failed');
|
|
}
|
|
|
|
var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
|
|
|
|
function getFragmentRoot(node) {
|
|
var p;
|
|
while (p = node.parentNode) {
|
|
node = p;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
function searchRefId(node, id) {
|
|
if (!id)
|
|
return;
|
|
|
|
var ref;
|
|
var selector = '#' + id;
|
|
while (!ref) {
|
|
node = getFragmentRoot(node);
|
|
|
|
if (node.protoContent_)
|
|
ref = node.protoContent_.querySelector(selector);
|
|
else if (node.getElementById)
|
|
ref = node.getElementById(id);
|
|
|
|
if (ref || !node.templateCreator_)
|
|
break
|
|
|
|
node = node.templateCreator_;
|
|
}
|
|
|
|
return ref;
|
|
}
|
|
|
|
function getInstanceRoot(node) {
|
|
while (node.parentNode) {
|
|
node = node.parentNode;
|
|
}
|
|
return node.templateCreator_ ? node : null;
|
|
}
|
|
|
|
var Map;
|
|
if (global.Map && typeof global.Map.prototype.forEach === 'function') {
|
|
Map = global.Map;
|
|
} else {
|
|
Map = function() {
|
|
this.keys = [];
|
|
this.values = [];
|
|
};
|
|
|
|
Map.prototype = {
|
|
set: function(key, value) {
|
|
var index = this.keys.indexOf(key);
|
|
if (index < 0) {
|
|
this.keys.push(key);
|
|
this.values.push(value);
|
|
} else {
|
|
this.values[index] = value;
|
|
}
|
|
},
|
|
|
|
get: function(key) {
|
|
var index = this.keys.indexOf(key);
|
|
if (index < 0)
|
|
return;
|
|
|
|
return this.values[index];
|
|
},
|
|
|
|
delete: function(key, value) {
|
|
var index = this.keys.indexOf(key);
|
|
if (index < 0)
|
|
return false;
|
|
|
|
this.keys.splice(index, 1);
|
|
this.values.splice(index, 1);
|
|
return true;
|
|
},
|
|
|
|
forEach: function(f, opt_this) {
|
|
for (var i = 0; i < this.keys.length; i++)
|
|
f.call(opt_this || this, this.values[i], this.keys[i], this);
|
|
}
|
|
};
|
|
}
|
|
|
|
// JScript does not have __proto__. We wrap all object literals with
|
|
// createObject which uses Object.create, Object.defineProperty and
|
|
// Object.getOwnPropertyDescriptor to create a new object that does the exact
|
|
// same thing. The main downside to this solution is that we have to extract
|
|
// all those property descriptors for IE.
|
|
var createObject = ('__proto__' in {}) ?
|
|
function(obj) { return obj; } :
|
|
function(obj) {
|
|
var proto = obj.__proto__;
|
|
if (!proto)
|
|
return obj;
|
|
var newObject = Object.create(proto);
|
|
Object.getOwnPropertyNames(obj).forEach(function(name) {
|
|
Object.defineProperty(newObject, name,
|
|
Object.getOwnPropertyDescriptor(obj, name));
|
|
});
|
|
return newObject;
|
|
};
|
|
|
|
// IE does not support have Document.prototype.contains.
|
|
if (typeof document.contains != 'function') {
|
|
Document.prototype.contains = function(node) {
|
|
if (node === this || node.parentNode === this)
|
|
return true;
|
|
return this.documentElement.contains(node);
|
|
}
|
|
}
|
|
|
|
var BIND = 'bind';
|
|
var REPEAT = 'repeat';
|
|
var IF = 'if';
|
|
|
|
var templateAttributeDirectives = {
|
|
'template': true,
|
|
'repeat': true,
|
|
'bind': true,
|
|
'ref': true
|
|
};
|
|
|
|
var semanticTemplateElements = {
|
|
'THEAD': true,
|
|
'TBODY': true,
|
|
'TFOOT': true,
|
|
'TH': true,
|
|
'TR': true,
|
|
'TD': true,
|
|
'COLGROUP': true,
|
|
'COL': true,
|
|
'CAPTION': true,
|
|
'OPTION': true,
|
|
'OPTGROUP': true
|
|
};
|
|
|
|
var hasTemplateElement = typeof HTMLTemplateElement !== 'undefined';
|
|
if (hasTemplateElement) {
|
|
// TODO(rafaelw): Remove when fix for
|
|
// https://codereview.chromium.org/164803002/
|
|
// makes it to Chrome release.
|
|
(function() {
|
|
var t = document.createElement('template');
|
|
var d = t.content.ownerDocument;
|
|
var html = d.appendChild(d.createElement('html'));
|
|
var head = html.appendChild(d.createElement('head'));
|
|
var base = d.createElement('base');
|
|
base.href = document.baseURI;
|
|
head.appendChild(base);
|
|
})();
|
|
}
|
|
|
|
var allTemplatesSelectors = 'template, ' +
|
|
Object.keys(semanticTemplateElements).map(function(tagName) {
|
|
return tagName.toLowerCase() + '[template]';
|
|
}).join(', ');
|
|
|
|
function isSVGTemplate(el) {
|
|
return el.tagName == 'template' &&
|
|
el.namespaceURI == 'http://www.w3.org/2000/svg';
|
|
}
|
|
|
|
function isHTMLTemplate(el) {
|
|
return el.tagName == 'TEMPLATE' &&
|
|
el.namespaceURI == 'http://www.w3.org/1999/xhtml';
|
|
}
|
|
|
|
function isAttributeTemplate(el) {
|
|
return Boolean(semanticTemplateElements[el.tagName] &&
|
|
el.hasAttribute('template'));
|
|
}
|
|
|
|
function isTemplate(el) {
|
|
if (el.isTemplate_ === undefined)
|
|
el.isTemplate_ = el.tagName == 'TEMPLATE' || isAttributeTemplate(el);
|
|
|
|
return el.isTemplate_;
|
|
}
|
|
|
|
// FIXME: Observe templates being added/removed from documents
|
|
// FIXME: Expose imperative API to decorate and observe templates in
|
|
// "disconnected tress" (e.g. ShadowRoot)
|
|
document.addEventListener('DOMContentLoaded', function(e) {
|
|
bootstrapTemplatesRecursivelyFrom(document);
|
|
// FIXME: Is this needed? Seems like it shouldn't be.
|
|
Platform.performMicrotaskCheckpoint();
|
|
}, false);
|
|
|
|
function forAllTemplatesFrom(node, fn) {
|
|
var subTemplates = node.querySelectorAll(allTemplatesSelectors);
|
|
|
|
if (isTemplate(node))
|
|
fn(node)
|
|
forEach(subTemplates, fn);
|
|
}
|
|
|
|
function bootstrapTemplatesRecursivelyFrom(node) {
|
|
function bootstrap(template) {
|
|
if (!HTMLTemplateElement.decorate(template))
|
|
bootstrapTemplatesRecursivelyFrom(template.content);
|
|
}
|
|
|
|
forAllTemplatesFrom(node, bootstrap);
|
|
}
|
|
|
|
if (!hasTemplateElement) {
|
|
/**
|
|
* This represents a <template> element.
|
|
* @constructor
|
|
* @extends {HTMLElement}
|
|
*/
|
|
global.HTMLTemplateElement = function() {
|
|
throw TypeError('Illegal constructor');
|
|
};
|
|
}
|
|
|
|
var hasProto = '__proto__' in {};
|
|
|
|
function mixin(to, from) {
|
|
Object.getOwnPropertyNames(from).forEach(function(name) {
|
|
Object.defineProperty(to, name,
|
|
Object.getOwnPropertyDescriptor(from, name));
|
|
});
|
|
}
|
|
|
|
// http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#dfn-template-contents-owner
|
|
function getOrCreateTemplateContentsOwner(template) {
|
|
var doc = template.ownerDocument
|
|
if (!doc.defaultView)
|
|
return doc;
|
|
var d = doc.templateContentsOwner_;
|
|
if (!d) {
|
|
// TODO(arv): This should either be a Document or HTMLDocument depending
|
|
// on doc.
|
|
d = doc.implementation.createHTMLDocument('');
|
|
while (d.lastChild) {
|
|
d.removeChild(d.lastChild);
|
|
}
|
|
doc.templateContentsOwner_ = d;
|
|
}
|
|
return d;
|
|
}
|
|
|
|
function getTemplateStagingDocument(template) {
|
|
if (!template.stagingDocument_) {
|
|
var owner = template.ownerDocument;
|
|
if (!owner.stagingDocument_) {
|
|
owner.stagingDocument_ = owner.implementation.createHTMLDocument('');
|
|
owner.stagingDocument_.isStagingDocument = true;
|
|
// TODO(rafaelw): Remove when fix for
|
|
// https://codereview.chromium.org/164803002/
|
|
// makes it to Chrome release.
|
|
var base = owner.stagingDocument_.createElement('base');
|
|
base.href = document.baseURI;
|
|
owner.stagingDocument_.head.appendChild(base);
|
|
|
|
owner.stagingDocument_.stagingDocument_ = owner.stagingDocument_;
|
|
}
|
|
|
|
template.stagingDocument_ = owner.stagingDocument_;
|
|
}
|
|
|
|
return template.stagingDocument_;
|
|
}
|
|
|
|
// For non-template browsers, the parser will disallow <template> in certain
|
|
// locations, so we allow "attribute templates" which combine the template
|
|
// element with the top-level container node of the content, e.g.
|
|
//
|
|
// <tr template repeat="{{ foo }}"" class="bar"><td>Bar</td></tr>
|
|
//
|
|
// becomes
|
|
//
|
|
// <template repeat="{{ foo }}">
|
|
// + #document-fragment
|
|
// + <tr class="bar">
|
|
// + <td>Bar</td>
|
|
//
|
|
function extractTemplateFromAttributeTemplate(el) {
|
|
var template = el.ownerDocument.createElement('template');
|
|
el.parentNode.insertBefore(template, el);
|
|
|
|
var attribs = el.attributes;
|
|
var count = attribs.length;
|
|
while (count-- > 0) {
|
|
var attrib = attribs[count];
|
|
if (templateAttributeDirectives[attrib.name]) {
|
|
if (attrib.name !== 'template')
|
|
template.setAttribute(attrib.name, attrib.value);
|
|
el.removeAttribute(attrib.name);
|
|
}
|
|
}
|
|
|
|
return template;
|
|
}
|
|
|
|
function extractTemplateFromSVGTemplate(el) {
|
|
var template = el.ownerDocument.createElement('template');
|
|
el.parentNode.insertBefore(template, el);
|
|
|
|
var attribs = el.attributes;
|
|
var count = attribs.length;
|
|
while (count-- > 0) {
|
|
var attrib = attribs[count];
|
|
template.setAttribute(attrib.name, attrib.value);
|
|
el.removeAttribute(attrib.name);
|
|
}
|
|
|
|
el.parentNode.removeChild(el);
|
|
return template;
|
|
}
|
|
|
|
function liftNonNativeTemplateChildrenIntoContent(template, el, useRoot) {
|
|
var content = template.content;
|
|
if (useRoot) {
|
|
content.appendChild(el);
|
|
return;
|
|
}
|
|
|
|
var child;
|
|
while (child = el.firstChild) {
|
|
content.appendChild(child);
|
|
}
|
|
}
|
|
|
|
var templateObserver;
|
|
if (typeof MutationObserver == 'function') {
|
|
templateObserver = new MutationObserver(function(records) {
|
|
for (var i = 0; i < records.length; i++) {
|
|
records[i].target.refChanged_();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Ensures proper API and content model for template elements.
|
|
* @param {HTMLTemplateElement} opt_instanceRef The template element which
|
|
* |el| template element will return as the value of its ref(), and whose
|
|
* content will be used as source when createInstance() is invoked.
|
|
*/
|
|
HTMLTemplateElement.decorate = function(el, opt_instanceRef) {
|
|
if (el.templateIsDecorated_)
|
|
return false;
|
|
|
|
var templateElement = el;
|
|
templateElement.templateIsDecorated_ = true;
|
|
|
|
var isNativeHTMLTemplate = isHTMLTemplate(templateElement) &&
|
|
hasTemplateElement;
|
|
var bootstrapContents = isNativeHTMLTemplate;
|
|
var liftContents = !isNativeHTMLTemplate;
|
|
var liftRoot = false;
|
|
|
|
if (!isNativeHTMLTemplate) {
|
|
if (isAttributeTemplate(templateElement)) {
|
|
assert(!opt_instanceRef);
|
|
templateElement = extractTemplateFromAttributeTemplate(el);
|
|
templateElement.templateIsDecorated_ = true;
|
|
isNativeHTMLTemplate = hasTemplateElement;
|
|
liftRoot = true;
|
|
} else if (isSVGTemplate(templateElement)) {
|
|
templateElement = extractTemplateFromSVGTemplate(el);
|
|
templateElement.templateIsDecorated_ = true;
|
|
isNativeHTMLTemplate = hasTemplateElement;
|
|
}
|
|
}
|
|
|
|
if (!isNativeHTMLTemplate) {
|
|
fixTemplateElementPrototype(templateElement);
|
|
var doc = getOrCreateTemplateContentsOwner(templateElement);
|
|
templateElement.content_ = doc.createDocumentFragment();
|
|
}
|
|
|
|
if (opt_instanceRef) {
|
|
// template is contained within an instance, its direct content must be
|
|
// empty
|
|
templateElement.instanceRef_ = opt_instanceRef;
|
|
} else if (liftContents) {
|
|
liftNonNativeTemplateChildrenIntoContent(templateElement,
|
|
el,
|
|
liftRoot);
|
|
} else if (bootstrapContents) {
|
|
bootstrapTemplatesRecursivelyFrom(templateElement.content);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
// TODO(rafaelw): This used to decorate recursively all templates from a given
|
|
// node. This happens by default on 'DOMContentLoaded', but may be needed
|
|
// in subtrees not descendent from document (e.g. ShadowRoot).
|
|
// Review whether this is the right public API.
|
|
HTMLTemplateElement.bootstrap = bootstrapTemplatesRecursivelyFrom;
|
|
|
|
var htmlElement = global.HTMLUnknownElement || HTMLElement;
|
|
|
|
var contentDescriptor = {
|
|
get: function() {
|
|
return this.content_;
|
|
},
|
|
enumerable: true,
|
|
configurable: true
|
|
};
|
|
|
|
if (!hasTemplateElement) {
|
|
// Gecko is more picky with the prototype than WebKit. Make sure to use the
|
|
// same prototype as created in the constructor.
|
|
HTMLTemplateElement.prototype = Object.create(htmlElement.prototype);
|
|
|
|
Object.defineProperty(HTMLTemplateElement.prototype, 'content',
|
|
contentDescriptor);
|
|
}
|
|
|
|
function fixTemplateElementPrototype(el) {
|
|
if (hasProto)
|
|
el.__proto__ = HTMLTemplateElement.prototype;
|
|
else
|
|
mixin(el, HTMLTemplateElement.prototype);
|
|
}
|
|
|
|
function ensureSetModelScheduled(template) {
|
|
if (!template.setModelFn_) {
|
|
template.setModelFn_ = function() {
|
|
template.setModelFnScheduled_ = false;
|
|
var map = getBindings(template,
|
|
template.delegate_ && template.delegate_.prepareBinding);
|
|
processBindings(template, map, template.model_);
|
|
};
|
|
}
|
|
|
|
if (!template.setModelFnScheduled_) {
|
|
template.setModelFnScheduled_ = true;
|
|
Observer.runEOM_(template.setModelFn_);
|
|
}
|
|
}
|
|
|
|
mixin(HTMLTemplateElement.prototype, {
|
|
bind: function(name, value, oneTime) {
|
|
if (name != 'ref')
|
|
return Element.prototype.bind.call(this, name, value, oneTime);
|
|
|
|
var self = this;
|
|
var ref = oneTime ? value : value.open(function(ref) {
|
|
self.setAttribute('ref', ref);
|
|
self.refChanged_();
|
|
});
|
|
|
|
this.setAttribute('ref', ref);
|
|
this.refChanged_();
|
|
if (oneTime)
|
|
return;
|
|
|
|
if (!this.bindings_) {
|
|
this.bindings_ = { ref: value };
|
|
} else {
|
|
this.bindings_.ref = value;
|
|
}
|
|
|
|
return value;
|
|
},
|
|
|
|
processBindingDirectives_: function(directives) {
|
|
if (this.iterator_)
|
|
this.iterator_.closeDeps();
|
|
|
|
if (!directives.if && !directives.bind && !directives.repeat) {
|
|
if (this.iterator_) {
|
|
this.iterator_.close();
|
|
this.iterator_ = undefined;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (!this.iterator_) {
|
|
this.iterator_ = new TemplateIterator(this);
|
|
}
|
|
|
|
this.iterator_.updateDependencies(directives, this.model_);
|
|
|
|
if (templateObserver) {
|
|
templateObserver.observe(this, { attributes: true,
|
|
attributeFilter: ['ref'] });
|
|
}
|
|
|
|
return this.iterator_;
|
|
},
|
|
|
|
createInstance: function(model, bindingDelegate, delegate_) {
|
|
if (bindingDelegate)
|
|
delegate_ = this.newDelegate_(bindingDelegate);
|
|
else if (!delegate_)
|
|
delegate_ = this.delegate_;
|
|
|
|
if (!this.refContent_)
|
|
this.refContent_ = this.ref_.content;
|
|
var content = this.refContent_;
|
|
if (content.firstChild === null)
|
|
return emptyInstance;
|
|
|
|
var map = getInstanceBindingMap(content, delegate_);
|
|
var stagingDocument = getTemplateStagingDocument(this);
|
|
var instance = stagingDocument.createDocumentFragment();
|
|
instance.templateCreator_ = this;
|
|
instance.protoContent_ = content;
|
|
instance.bindings_ = [];
|
|
instance.terminator_ = null;
|
|
var instanceRecord = instance.templateInstance_ = {
|
|
firstNode: null,
|
|
lastNode: null,
|
|
model: model
|
|
};
|
|
|
|
var i = 0;
|
|
var collectTerminator = false;
|
|
for (var child = content.firstChild; child; child = child.nextSibling) {
|
|
// The terminator of the instance is the clone of the last child of the
|
|
// content. If the last child is an active template, it may produce
|
|
// instances as a result of production, so simply collecting the last
|
|
// child of the instance after it has finished producing may be wrong.
|
|
if (child.nextSibling === null)
|
|
collectTerminator = true;
|
|
|
|
var clone = cloneAndBindInstance(child, instance, stagingDocument,
|
|
map.children[i++],
|
|
model,
|
|
delegate_,
|
|
instance.bindings_);
|
|
clone.templateInstance_ = instanceRecord;
|
|
if (collectTerminator)
|
|
instance.terminator_ = clone;
|
|
}
|
|
|
|
instanceRecord.firstNode = instance.firstChild;
|
|
instanceRecord.lastNode = instance.lastChild;
|
|
instance.templateCreator_ = undefined;
|
|
instance.protoContent_ = undefined;
|
|
return instance;
|
|
},
|
|
|
|
get model() {
|
|
return this.model_;
|
|
},
|
|
|
|
set model(model) {
|
|
this.model_ = model;
|
|
ensureSetModelScheduled(this);
|
|
},
|
|
|
|
get bindingDelegate() {
|
|
return this.delegate_ && this.delegate_.raw;
|
|
},
|
|
|
|
refChanged_: function() {
|
|
if (!this.iterator_ || this.refContent_ === this.ref_.content)
|
|
return;
|
|
|
|
this.refContent_ = undefined;
|
|
this.iterator_.valueChanged();
|
|
this.iterator_.updateIteratedValue();
|
|
},
|
|
|
|
clear: function() {
|
|
this.model_ = undefined;
|
|
this.delegate_ = undefined;
|
|
if (this.bindings_ && this.bindings_.ref)
|
|
this.bindings_.ref.close()
|
|
this.refContent_ = undefined;
|
|
if (!this.iterator_)
|
|
return;
|
|
this.iterator_.valueChanged();
|
|
this.iterator_.close()
|
|
this.iterator_ = undefined;
|
|
},
|
|
|
|
setDelegate_: function(delegate) {
|
|
this.delegate_ = delegate;
|
|
this.bindingMap_ = undefined;
|
|
if (this.iterator_) {
|
|
this.iterator_.instancePositionChangedFn_ = undefined;
|
|
this.iterator_.instanceModelFn_ = undefined;
|
|
}
|
|
},
|
|
|
|
newDelegate_: function(bindingDelegate) {
|
|
if (!bindingDelegate)
|
|
return;
|
|
|
|
function delegateFn(name) {
|
|
var fn = bindingDelegate && bindingDelegate[name];
|
|
if (typeof fn != 'function')
|
|
return;
|
|
|
|
return function() {
|
|
return fn.apply(bindingDelegate, arguments);
|
|
};
|
|
}
|
|
|
|
return {
|
|
bindingMaps: {},
|
|
raw: bindingDelegate,
|
|
prepareBinding: delegateFn('prepareBinding'),
|
|
prepareInstanceModel: delegateFn('prepareInstanceModel'),
|
|
prepareInstancePositionChanged:
|
|
delegateFn('prepareInstancePositionChanged')
|
|
};
|
|
},
|
|
|
|
set bindingDelegate(bindingDelegate) {
|
|
if (this.delegate_) {
|
|
throw Error('Template must be cleared before a new bindingDelegate ' +
|
|
'can be assigned');
|
|
}
|
|
|
|
this.setDelegate_(this.newDelegate_(bindingDelegate));
|
|
},
|
|
|
|
get ref_() {
|
|
var ref = searchRefId(this, this.getAttribute('ref'));
|
|
if (!ref)
|
|
ref = this.instanceRef_;
|
|
|
|
if (!ref)
|
|
return this;
|
|
|
|
var nextRef = ref.ref_;
|
|
return nextRef ? nextRef : ref;
|
|
}
|
|
});
|
|
|
|
// Returns
|
|
// a) undefined if there are no mustaches.
|
|
// b) [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least one mustache.
|
|
function parseMustaches(s, name, node, prepareBindingFn) {
|
|
if (!s || !s.length)
|
|
return;
|
|
|
|
var tokens;
|
|
var length = s.length;
|
|
var startIndex = 0, lastIndex = 0, endIndex = 0;
|
|
var onlyOneTime = true;
|
|
while (lastIndex < length) {
|
|
var startIndex = s.indexOf('{{', lastIndex);
|
|
var oneTimeStart = s.indexOf('[[', lastIndex);
|
|
var oneTime = false;
|
|
var terminator = '}}';
|
|
|
|
if (oneTimeStart >= 0 &&
|
|
(startIndex < 0 || oneTimeStart < startIndex)) {
|
|
startIndex = oneTimeStart;
|
|
oneTime = true;
|
|
terminator = ']]';
|
|
}
|
|
|
|
endIndex = startIndex < 0 ? -1 : s.indexOf(terminator, startIndex + 2);
|
|
|
|
if (endIndex < 0) {
|
|
if (!tokens)
|
|
return;
|
|
|
|
tokens.push(s.slice(lastIndex)); // TEXT
|
|
break;
|
|
}
|
|
|
|
tokens = tokens || [];
|
|
tokens.push(s.slice(lastIndex, startIndex)); // TEXT
|
|
var pathString = s.slice(startIndex + 2, endIndex).trim();
|
|
tokens.push(oneTime); // ONE_TIME?
|
|
onlyOneTime = onlyOneTime && oneTime;
|
|
var delegateFn = prepareBindingFn &&
|
|
prepareBindingFn(pathString, name, node);
|
|
// Don't try to parse the expression if there's a prepareBinding function
|
|
if (delegateFn == null) {
|
|
tokens.push(Path.get(pathString)); // PATH
|
|
} else {
|
|
tokens.push(null);
|
|
}
|
|
tokens.push(delegateFn); // DELEGATE_FN
|
|
lastIndex = endIndex + 2;
|
|
}
|
|
|
|
if (lastIndex === length)
|
|
tokens.push(''); // TEXT
|
|
|
|
tokens.hasOnePath = tokens.length === 5;
|
|
tokens.isSimplePath = tokens.hasOnePath &&
|
|
tokens[0] == '' &&
|
|
tokens[4] == '';
|
|
tokens.onlyOneTime = onlyOneTime;
|
|
|
|
tokens.combinator = function(values) {
|
|
var newValue = tokens[0];
|
|
|
|
for (var i = 1; i < tokens.length; i += 4) {
|
|
var value = tokens.hasOnePath ? values : values[(i - 1) / 4];
|
|
if (value !== undefined)
|
|
newValue += value;
|
|
newValue += tokens[i + 3];
|
|
}
|
|
|
|
return newValue;
|
|
}
|
|
|
|
return tokens;
|
|
};
|
|
|
|
function processOneTimeBinding(name, tokens, node, model) {
|
|
if (tokens.hasOnePath) {
|
|
var delegateFn = tokens[3];
|
|
var value = delegateFn ? delegateFn(model, node, true) :
|
|
tokens[2].getValueFrom(model);
|
|
return tokens.isSimplePath ? value : tokens.combinator(value);
|
|
}
|
|
|
|
var values = [];
|
|
for (var i = 1; i < tokens.length; i += 4) {
|
|
var delegateFn = tokens[i + 2];
|
|
values[(i - 1) / 4] = delegateFn ? delegateFn(model, node) :
|
|
tokens[i + 1].getValueFrom(model);
|
|
}
|
|
|
|
return tokens.combinator(values);
|
|
}
|
|
|
|
function processSinglePathBinding(name, tokens, node, model) {
|
|
var delegateFn = tokens[3];
|
|
var observer = delegateFn ? delegateFn(model, node, false) :
|
|
new PathObserver(model, tokens[2]);
|
|
|
|
return tokens.isSimplePath ? observer :
|
|
new ObserverTransform(observer, tokens.combinator);
|
|
}
|
|
|
|
function processBinding(name, tokens, node, model) {
|
|
if (tokens.onlyOneTime)
|
|
return processOneTimeBinding(name, tokens, node, model);
|
|
|
|
if (tokens.hasOnePath)
|
|
return processSinglePathBinding(name, tokens, node, model);
|
|
|
|
var observer = new CompoundObserver();
|
|
|
|
for (var i = 1; i < tokens.length; i += 4) {
|
|
var oneTime = tokens[i];
|
|
var delegateFn = tokens[i + 2];
|
|
|
|
if (delegateFn) {
|
|
var value = delegateFn(model, node, oneTime);
|
|
if (oneTime)
|
|
observer.addPath(value)
|
|
else
|
|
observer.addObserver(value);
|
|
continue;
|
|
}
|
|
|
|
var path = tokens[i + 1];
|
|
if (oneTime)
|
|
observer.addPath(path.getValueFrom(model))
|
|
else
|
|
observer.addPath(model, path);
|
|
}
|
|
|
|
return new ObserverTransform(observer, tokens.combinator);
|
|
}
|
|
|
|
function processBindings(node, bindings, model, instanceBindings) {
|
|
for (var i = 0; i < bindings.length; i += 2) {
|
|
var name = bindings[i]
|
|
var tokens = bindings[i + 1];
|
|
var value = processBinding(name, tokens, node, model);
|
|
var binding = node.bind(name, value, tokens.onlyOneTime);
|
|
if (binding && instanceBindings)
|
|
instanceBindings.push(binding);
|
|
}
|
|
|
|
node.bindFinished();
|
|
if (!bindings.isTemplate)
|
|
return;
|
|
|
|
node.model_ = model;
|
|
var iter = node.processBindingDirectives_(bindings);
|
|
if (instanceBindings && iter)
|
|
instanceBindings.push(iter);
|
|
}
|
|
|
|
function parseWithDefault(el, name, prepareBindingFn) {
|
|
var v = el.getAttribute(name);
|
|
return parseMustaches(v == '' ? '{{}}' : v, name, el, prepareBindingFn);
|
|
}
|
|
|
|
function parseAttributeBindings(element, prepareBindingFn) {
|
|
assert(element);
|
|
|
|
var bindings = [];
|
|
var ifFound = false;
|
|
var bindFound = false;
|
|
|
|
for (var i = 0; i < element.attributes.length; i++) {
|
|
var attr = element.attributes[i];
|
|
var name = attr.name;
|
|
var value = attr.value;
|
|
|
|
// Allow bindings expressed in attributes to be prefixed with underbars.
|
|
// We do this to allow correct semantics for browsers that don't implement
|
|
// <template> where certain attributes might trigger side-effects -- and
|
|
// for IE which sanitizes certain attributes, disallowing mustache
|
|
// replacements in their text.
|
|
while (name[0] === '_') {
|
|
name = name.substring(1);
|
|
}
|
|
|
|
if (isTemplate(element) &&
|
|
(name === IF || name === BIND || name === REPEAT)) {
|
|
continue;
|
|
}
|
|
|
|
var tokens = parseMustaches(value, name, element,
|
|
prepareBindingFn);
|
|
if (!tokens)
|
|
continue;
|
|
|
|
bindings.push(name, tokens);
|
|
}
|
|
|
|
if (isTemplate(element)) {
|
|
bindings.isTemplate = true;
|
|
bindings.if = parseWithDefault(element, IF, prepareBindingFn);
|
|
bindings.bind = parseWithDefault(element, BIND, prepareBindingFn);
|
|
bindings.repeat = parseWithDefault(element, REPEAT, prepareBindingFn);
|
|
|
|
if (bindings.if && !bindings.bind && !bindings.repeat)
|
|
bindings.bind = parseMustaches('{{}}', BIND, element, prepareBindingFn);
|
|
}
|
|
|
|
return bindings;
|
|
}
|
|
|
|
function getBindings(node, prepareBindingFn) {
|
|
if (node.nodeType === Node.ELEMENT_NODE)
|
|
return parseAttributeBindings(node, prepareBindingFn);
|
|
|
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
var tokens = parseMustaches(node.data, 'textContent', node,
|
|
prepareBindingFn);
|
|
if (tokens)
|
|
return ['textContent', tokens];
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
function cloneAndBindInstance(node, parent, stagingDocument, bindings, model,
|
|
delegate,
|
|
instanceBindings,
|
|
instanceRecord) {
|
|
var clone = parent.appendChild(stagingDocument.importNode(node, false));
|
|
|
|
var i = 0;
|
|
for (var child = node.firstChild; child; child = child.nextSibling) {
|
|
cloneAndBindInstance(child, clone, stagingDocument,
|
|
bindings.children[i++],
|
|
model,
|
|
delegate,
|
|
instanceBindings);
|
|
}
|
|
|
|
if (bindings.isTemplate) {
|
|
HTMLTemplateElement.decorate(clone, node);
|
|
if (delegate)
|
|
clone.setDelegate_(delegate);
|
|
}
|
|
|
|
processBindings(clone, bindings, model, instanceBindings);
|
|
return clone;
|
|
}
|
|
|
|
function createInstanceBindingMap(node, prepareBindingFn) {
|
|
var map = getBindings(node, prepareBindingFn);
|
|
map.children = {};
|
|
var index = 0;
|
|
for (var child = node.firstChild; child; child = child.nextSibling) {
|
|
map.children[index++] = createInstanceBindingMap(child, prepareBindingFn);
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
var contentUidCounter = 1;
|
|
|
|
// TODO(rafaelw): Setup a MutationObserver on content which clears the id
|
|
// so that bindingMaps regenerate when the template.content changes.
|
|
function getContentUid(content) {
|
|
var id = content.id_;
|
|
if (!id)
|
|
id = content.id_ = contentUidCounter++;
|
|
return id;
|
|
}
|
|
|
|
// Each delegate is associated with a set of bindingMaps, one for each
|
|
// content which may be used by a template. The intent is that each binding
|
|
// delegate gets the opportunity to prepare the instance (via the prepare*
|
|
// delegate calls) once across all uses.
|
|
// TODO(rafaelw): Separate out the parse map from the binding map. In the
|
|
// current implementation, if two delegates need a binding map for the same
|
|
// content, the second will have to reparse.
|
|
function getInstanceBindingMap(content, delegate_) {
|
|
var contentId = getContentUid(content);
|
|
if (delegate_) {
|
|
var map = delegate_.bindingMaps[contentId];
|
|
if (!map) {
|
|
map = delegate_.bindingMaps[contentId] =
|
|
createInstanceBindingMap(content, delegate_.prepareBinding) || [];
|
|
}
|
|
return map;
|
|
}
|
|
|
|
var map = content.bindingMap_;
|
|
if (!map) {
|
|
map = content.bindingMap_ =
|
|
createInstanceBindingMap(content, undefined) || [];
|
|
}
|
|
return map;
|
|
}
|
|
|
|
Object.defineProperty(Node.prototype, 'templateInstance', {
|
|
get: function() {
|
|
var instance = this.templateInstance_;
|
|
return instance ? instance :
|
|
(this.parentNode ? this.parentNode.templateInstance : undefined);
|
|
}
|
|
});
|
|
|
|
var emptyInstance = document.createDocumentFragment();
|
|
emptyInstance.bindings_ = [];
|
|
emptyInstance.terminator_ = null;
|
|
|
|
function TemplateIterator(templateElement) {
|
|
this.closed = false;
|
|
this.templateElement_ = templateElement;
|
|
this.instances = [];
|
|
this.deps = undefined;
|
|
this.iteratedValue = [];
|
|
this.presentValue = undefined;
|
|
this.arrayObserver = undefined;
|
|
}
|
|
|
|
TemplateIterator.prototype = {
|
|
closeDeps: function() {
|
|
var deps = this.deps;
|
|
if (deps) {
|
|
if (deps.ifOneTime === false)
|
|
deps.ifValue.close();
|
|
if (deps.oneTime === false)
|
|
deps.value.close();
|
|
}
|
|
},
|
|
|
|
updateDependencies: function(directives, model) {
|
|
this.closeDeps();
|
|
|
|
var deps = this.deps = {};
|
|
var template = this.templateElement_;
|
|
|
|
if (directives.if) {
|
|
deps.hasIf = true;
|
|
deps.ifOneTime = directives.if.onlyOneTime;
|
|
deps.ifValue = processBinding(IF, directives.if, template, model);
|
|
|
|
// oneTime if & predicate is false. nothing else to do.
|
|
if (deps.ifOneTime && !deps.ifValue) {
|
|
this.updateIteratedValue();
|
|
return;
|
|
}
|
|
|
|
if (!deps.ifOneTime)
|
|
deps.ifValue.open(this.updateIteratedValue, this);
|
|
}
|
|
|
|
if (directives.repeat) {
|
|
deps.repeat = true;
|
|
deps.oneTime = directives.repeat.onlyOneTime;
|
|
deps.value = processBinding(REPEAT, directives.repeat, template, model);
|
|
} else {
|
|
deps.repeat = false;
|
|
deps.oneTime = directives.bind.onlyOneTime;
|
|
deps.value = processBinding(BIND, directives.bind, template, model);
|
|
}
|
|
|
|
if (!deps.oneTime)
|
|
deps.value.open(this.updateIteratedValue, this);
|
|
|
|
this.updateIteratedValue();
|
|
},
|
|
|
|
updateIteratedValue: function() {
|
|
if (this.deps.hasIf) {
|
|
var ifValue = this.deps.ifValue;
|
|
if (!this.deps.ifOneTime)
|
|
ifValue = ifValue.discardChanges();
|
|
if (!ifValue) {
|
|
this.valueChanged();
|
|
return;
|
|
}
|
|
}
|
|
|
|
var value = this.deps.value;
|
|
if (!this.deps.oneTime)
|
|
value = value.discardChanges();
|
|
if (!this.deps.repeat)
|
|
value = [value];
|
|
var observe = this.deps.repeat &&
|
|
!this.deps.oneTime &&
|
|
Array.isArray(value);
|
|
this.valueChanged(value, observe);
|
|
},
|
|
|
|
valueChanged: function(value, observeValue) {
|
|
if (!Array.isArray(value))
|
|
value = [];
|
|
|
|
if (value === this.iteratedValue)
|
|
return;
|
|
|
|
this.unobserve();
|
|
this.presentValue = value;
|
|
if (observeValue) {
|
|
this.arrayObserver = new ArrayObserver(this.presentValue);
|
|
this.arrayObserver.open(this.handleSplices, this);
|
|
}
|
|
|
|
this.handleSplices(ArrayObserver.calculateSplices(this.presentValue,
|
|
this.iteratedValue));
|
|
},
|
|
|
|
getLastInstanceNode: function(index) {
|
|
if (index == -1)
|
|
return this.templateElement_;
|
|
var instance = this.instances[index];
|
|
var terminator = instance.terminator_;
|
|
if (!terminator)
|
|
return this.getLastInstanceNode(index - 1);
|
|
|
|
if (terminator.nodeType !== Node.ELEMENT_NODE ||
|
|
this.templateElement_ === terminator) {
|
|
return terminator;
|
|
}
|
|
|
|
var subtemplateIterator = terminator.iterator_;
|
|
if (!subtemplateIterator)
|
|
return terminator;
|
|
|
|
return subtemplateIterator.getLastTemplateNode();
|
|
},
|
|
|
|
getLastTemplateNode: function() {
|
|
return this.getLastInstanceNode(this.instances.length - 1);
|
|
},
|
|
|
|
insertInstanceAt: function(index, fragment) {
|
|
var previousInstanceLast = this.getLastInstanceNode(index - 1);
|
|
var parent = this.templateElement_.parentNode;
|
|
this.instances.splice(index, 0, fragment);
|
|
|
|
parent.insertBefore(fragment, previousInstanceLast.nextSibling);
|
|
},
|
|
|
|
extractInstanceAt: function(index) {
|
|
var previousInstanceLast = this.getLastInstanceNode(index - 1);
|
|
var lastNode = this.getLastInstanceNode(index);
|
|
var parent = this.templateElement_.parentNode;
|
|
var instance = this.instances.splice(index, 1)[0];
|
|
|
|
while (lastNode !== previousInstanceLast) {
|
|
var node = previousInstanceLast.nextSibling;
|
|
if (node == lastNode)
|
|
lastNode = previousInstanceLast;
|
|
|
|
instance.appendChild(parent.removeChild(node));
|
|
}
|
|
|
|
return instance;
|
|
},
|
|
|
|
getDelegateFn: function(fn) {
|
|
fn = fn && fn(this.templateElement_);
|
|
return typeof fn === 'function' ? fn : null;
|
|
},
|
|
|
|
handleSplices: function(splices) {
|
|
if (this.closed || !splices.length)
|
|
return;
|
|
|
|
var template = this.templateElement_;
|
|
|
|
if (!template.parentNode) {
|
|
this.close();
|
|
return;
|
|
}
|
|
|
|
ArrayObserver.applySplices(this.iteratedValue, this.presentValue,
|
|
splices);
|
|
|
|
var delegate = template.delegate_;
|
|
if (this.instanceModelFn_ === undefined) {
|
|
this.instanceModelFn_ =
|
|
this.getDelegateFn(delegate && delegate.prepareInstanceModel);
|
|
}
|
|
|
|
if (this.instancePositionChangedFn_ === undefined) {
|
|
this.instancePositionChangedFn_ =
|
|
this.getDelegateFn(delegate &&
|
|
delegate.prepareInstancePositionChanged);
|
|
}
|
|
|
|
// Instance Removals
|
|
var instanceCache = new Map;
|
|
var removeDelta = 0;
|
|
for (var i = 0; i < splices.length; i++) {
|
|
var splice = splices[i];
|
|
var removed = splice.removed;
|
|
for (var j = 0; j < removed.length; j++) {
|
|
var model = removed[j];
|
|
var instance = this.extractInstanceAt(splice.index + removeDelta);
|
|
if (instance !== emptyInstance) {
|
|
instanceCache.set(model, instance);
|
|
}
|
|
}
|
|
|
|
removeDelta -= splice.addedCount;
|
|
}
|
|
|
|
// Instance Insertions
|
|
for (var i = 0; i < splices.length; i++) {
|
|
var splice = splices[i];
|
|
var addIndex = splice.index;
|
|
for (; addIndex < splice.index + splice.addedCount; addIndex++) {
|
|
var model = this.iteratedValue[addIndex];
|
|
var instance = instanceCache.get(model);
|
|
if (instance) {
|
|
instanceCache.delete(model);
|
|
} else {
|
|
if (this.instanceModelFn_) {
|
|
model = this.instanceModelFn_(model);
|
|
}
|
|
|
|
if (model === undefined) {
|
|
instance = emptyInstance;
|
|
} else {
|
|
instance = template.createInstance(model, undefined, delegate);
|
|
}
|
|
}
|
|
|
|
this.insertInstanceAt(addIndex, instance);
|
|
}
|
|
}
|
|
|
|
instanceCache.forEach(function(instance) {
|
|
this.closeInstanceBindings(instance);
|
|
}, this);
|
|
|
|
if (this.instancePositionChangedFn_)
|
|
this.reportInstancesMoved(splices);
|
|
},
|
|
|
|
reportInstanceMoved: function(index) {
|
|
var instance = this.instances[index];
|
|
if (instance === emptyInstance)
|
|
return;
|
|
|
|
this.instancePositionChangedFn_(instance.templateInstance_, index);
|
|
},
|
|
|
|
reportInstancesMoved: function(splices) {
|
|
var index = 0;
|
|
var offset = 0;
|
|
for (var i = 0; i < splices.length; i++) {
|
|
var splice = splices[i];
|
|
if (offset != 0) {
|
|
while (index < splice.index) {
|
|
this.reportInstanceMoved(index);
|
|
index++;
|
|
}
|
|
} else {
|
|
index = splice.index;
|
|
}
|
|
|
|
while (index < splice.index + splice.addedCount) {
|
|
this.reportInstanceMoved(index);
|
|
index++;
|
|
}
|
|
|
|
offset += splice.addedCount - splice.removed.length;
|
|
}
|
|
|
|
if (offset == 0)
|
|
return;
|
|
|
|
var length = this.instances.length;
|
|
while (index < length) {
|
|
this.reportInstanceMoved(index);
|
|
index++;
|
|
}
|
|
},
|
|
|
|
closeInstanceBindings: function(instance) {
|
|
var bindings = instance.bindings_;
|
|
for (var i = 0; i < bindings.length; i++) {
|
|
bindings[i].close();
|
|
}
|
|
},
|
|
|
|
unobserve: function() {
|
|
if (!this.arrayObserver)
|
|
return;
|
|
|
|
this.arrayObserver.close();
|
|
this.arrayObserver = undefined;
|
|
},
|
|
|
|
close: function() {
|
|
if (this.closed)
|
|
return;
|
|
this.unobserve();
|
|
for (var i = 0; i < this.instances.length; i++) {
|
|
this.closeInstanceBindings(this.instances[i]);
|
|
}
|
|
|
|
this.instances.length = 0;
|
|
this.closeDeps();
|
|
this.templateElement_.iterator_ = undefined;
|
|
this.closed = true;
|
|
}
|
|
};
|
|
|
|
// Polyfill-specific API.
|
|
HTMLTemplateElement.forAllTemplatesFrom_ = forAllTemplatesFrom;
|
|
})(this);
|
|
|
|
/*
|
|
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
* Code distributed by Google as part of the polymer project is also
|
|
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
*/
|
|
|
|
(function(scope) {
|
|
|
|
// inject style sheet
|
|
var style = document.createElement('style');
|
|
style.textContent = 'template {display: none !important;} /* injected by platform.js */';
|
|
var head = document.querySelector('head');
|
|
head.insertBefore(style, head.firstChild);
|
|
|
|
// flush (with logging)
|
|
var flushing;
|
|
function flush() {
|
|
if (!flushing) {
|
|
flushing = true;
|
|
scope.endOfMicrotask(function() {
|
|
flushing = false;
|
|
logFlags.data && console.group('Platform.flush()');
|
|
scope.performMicrotaskCheckpoint();
|
|
logFlags.data && console.groupEnd();
|
|
});
|
|
}
|
|
};
|
|
|
|
// polling dirty checker
|
|
// flush periodically if platform does not have object observe.
|
|
if (!Observer.hasObjectObserve) {
|
|
var FLUSH_POLL_INTERVAL = 125;
|
|
window.addEventListener('WebComponentsReady', function() {
|
|
flush();
|
|
scope.flushPoll = setInterval(flush, FLUSH_POLL_INTERVAL);
|
|
});
|
|
} else {
|
|
// make flush a no-op when we have Object.observe
|
|
flush = function() {};
|
|
}
|
|
|
|
if (window.CustomElements && !CustomElements.useNative) {
|
|
var originalImportNode = Document.prototype.importNode;
|
|
Document.prototype.importNode = function(node, deep) {
|
|
var imported = originalImportNode.call(this, node, deep);
|
|
CustomElements.upgradeAll(imported);
|
|
return imported;
|
|
}
|
|
}
|
|
|
|
// exports
|
|
scope.flush = flush;
|
|
|
|
})(window.Platform);
|
|
|
|
|
|
//# sourceMappingURL=platform.concat.js.map
|