| /** |
| * 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'); |
| |
| for (var i = 0; i < parts.length; i++) { |
| this.push(String(parts[i])); |
| } |
| |
| 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 && hasEval && (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 2014 The Polymer Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style |
| * license that can be found in the LICENSE file. |
| */ |
| |
| (function(scope) { |
| 'use strict'; |
| |
| var HTMLElement = scope.wrappers.HTMLElement; |
| var mixin = scope.mixin; |
| var registerWrapper = scope.registerWrapper; |
| var wrapHTMLCollection = scope.wrapHTMLCollection; |
| var unwrap = scope.unwrap; |
| |
| var OriginalHTMLFormElement = window.HTMLFormElement; |
| |
| function HTMLFormElement(node) { |
| HTMLElement.call(this, node); |
| } |
| HTMLFormElement.prototype = Object.create(HTMLElement.prototype); |
| mixin(HTMLFormElement.prototype, { |
| get elements() { |
| // Note: technically this should be an HTMLFormControlsCollection, but |
| // that inherits from HTMLCollection, so should be good enough. Spec: |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#htmlformcontrolscollection |
| return wrapHTMLCollection(unwrap(this).elements); |
| } |
| }); |
| |
| registerWrapper(OriginalHTMLFormElement, HTMLFormElement, |
| document.createElement('form')); |
| |
| scope.wrappers.HTMLFormElement = HTMLFormElement; |
| })(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; |
| var parentRenderer = this.parentRenderer; |
| if (parentRenderer) |
| parentRenderer.invalidate(); |
| 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; |
| |
| if (OriginalDataTransferSetDragImage) { |
| OriginalDataTransfer.prototype.setDragImage = function(image, x, y) { |
| OriginalDataTransferSetDragImage.call(this, unwrap(image), x, y); |
| }; |
| } |
| |
| })(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 OriginalFormData = window.FormData; |
| |
| function FormData(formElement) { |
| this.impl = new OriginalFormData(formElement && unwrap(formElement)); |
| } |
| |
| registerWrapper(OriginalFormData, FormData, new OriginalFormData()); |
| |
| scope.wrappers.FormData = FormData; |
| |
| })(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.convertShadowDOMSelectors(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 combinators like ::shadow and pseudo-elements like ::content |
| * by replacing with space. |
| */ |
| convertShadowDOMSelectors: function(cssText) { |
| for (var i=0; i < shadowDOMSelectorsRe.length; i++) { |
| cssText = cssText.replace(shadowDOMSelectorsRe[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'), |
| shadowDOMSelectorsRe = [ |
| /\^\^/g, |
| /\^/g, |
| /\/shadow\//g, |
| /\/shadow-deep\//g, |
| /::shadow/g, |
| /\/deep\//g, |
| /::content/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); |
| var b64 = 'data:text/javascript'; |
| // base64 may be smaller, but does not handle unicode characters |
| // attempt base64 first, fall back to escaped text |
| try { |
| b64 += (';base64,' + btoa(scriptContent)); |
| } catch(e) { |
| b64 += (';charset=utf-8,' + encodeURIComponent(scriptContent)); |
| } |
| return 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.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; |
| scope.isImportLoaded = isImportLoaded; |
| scope.importLoader = importLoader; |
| scope.whenReady = whenImportsReady; |
| |
| // deprecated |
| scope.whenImportsReady = whenImportsReady; |
| |
| })(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 |