// Copyright (c) 2012 The Polymer Authors. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 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; })(); } (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); window.CustomElements = window.CustomElements || {flags:{}}; (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 1) { logFlags.dom && console.warn('inserted:', element.localName, 'insert/remove count:', element.__inserted) } else if (element.attachedCallback) { logFlags.dom && console.log('inserted:', element.localName); element.attachedCallback(); } } logFlags.dom && console.groupEnd(); } } function removedNode(node) { removed(node); forSubtree(node, function(e) { removed(e); }); } function removed(element) { if (hasPolyfillMutations) { deferMutation(function() { _removed(element); }); } else { _removed(element); } } function _removed(element) { // TODO(sjmiles): temporary: do work on all custom elements so we can track // behavior even when callbacks not defined if (element.attachedCallback || element.detachedCallback || (element.__upgraded__ && logFlags.dom)) { logFlags.dom && console.group('removed:', element.localName); if (!inDocument(element)) { element.__inserted = (element.__inserted || 0) - 1; // if we are in a 'inserted' state, bluntly adjust to an 'removed' state if (element.__inserted > 0) { element.__inserted = 0; } // if we are 'over removed', squelch the callback if (element.__inserted < 0) { logFlags.dom && console.warn('removed:', element.localName, 'insert/remove count:', element.__inserted) } else if (element.detachedCallback) { element.detachedCallback(); } } logFlags.dom && console.groupEnd(); } } // SD polyfill intrustion due mainly to the fact that 'document' // is not entirely wrapped function wrapIfNeeded(node) { return window.ShadowDOMPolyfill ? ShadowDOMPolyfill.wrapIfNeeded(node) : node; } function inDocument(element) { var p = element; var doc = wrapIfNeeded(document); while (p) { if (p == doc) { return true; } p = p.parentNode || p.host; } } function watchShadow(node) { if (node.shadowRoot && !node.shadowRoot.__watched) { logFlags.dom && console.log('watching shadow-root for: ', node.localName); // watch all unwatched roots... var root = node.shadowRoot; while (root) { watchRoot(root); root = root.olderShadowRoot; } } } function watchRoot(root) { if (!root.__watched) { observe(root); root.__watched = true; } } function handler(mutations) { // if (logFlags.dom) { var mx = mutations[0]; if (mx && mx.type === 'childList' && mx.addedNodes) { if (mx.addedNodes) { var d = mx.addedNodes[0]; while (d && d !== document && !d.host) { d = d.parentNode; } var u = d && (d.URL || d._URL || (d.host && d.host.localName)) || ''; u = u.split('/?').shift().split('/').pop(); } } console.group('mutations (%d) [%s]', mutations.length, u || ''); } // mutations.forEach(function(mx) { //logFlags.dom && console.group('mutation'); if (mx.type === 'childList') { forEach(mx.addedNodes, function(n) { //logFlags.dom && console.log(n.localName); if (!n.localName) { return; } // nodes added may need lifecycle management addedNode(n); }); // removed nodes may need lifecycle management forEach(mx.removedNodes, function(n) { //logFlags.dom && console.log(n.localName); if (!n.localName) { return; } removedNode(n); }); } //logFlags.dom && console.groupEnd(); }); logFlags.dom && console.groupEnd(); }; var observer = new MutationObserver(handler); function takeRecords() { // TODO(sjmiles): ask Raf why we have to call handler ourselves handler(observer.takeRecords()); takeMutations(); } var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); function observe(inRoot) { observer.observe(inRoot, {childList: true, subtree: true}); } function observeDocument(doc) { observe(doc); } function upgradeDocument(doc) { logFlags.dom && console.group('upgradeDocument: ', (doc.baseURI).split('/').pop()); addedNode(doc); logFlags.dom && console.groupEnd(); } function upgradeDocumentTree(doc) { doc = wrapIfNeeded(doc); upgradeDocument(doc); //console.log('upgradeDocumentTree: ', (doc.baseURI).split('/').pop()); // upgrade contained imported documents var imports = doc.querySelectorAll('link[rel=' + IMPORT_LINK_TYPE + ']'); for (var i=0, l=imports.length, n; (i= 0) { implement(element, HTMLElement); } return element; } function upgradeElement(element) { if (!element.__upgraded__ && (element.nodeType === Node.ELEMENT_NODE)) { var is = element.getAttribute('is'); var definition = registry[is || element.localName]; if (definition) { if (is && definition.tag == element.localName) { return upgrade(element, definition); } else if (!is && !definition.extends) { return upgrade(element, definition); } } } } function cloneNode(deep) { // call original clone var n = domCloneNode.call(this, deep); // upgrade the element and subtree scope.upgradeAll(n); // return the clone return n; } // capture native createElement before we override it var domCreateElement = document.createElement.bind(document); // capture native cloneNode before we override it var domCloneNode = Node.prototype.cloneNode; // exports document.registerElement = register; document.createElement = createElement; // override Node.prototype.cloneNode = cloneNode; // override scope.registry = registry; /** * Upgrade an element to a custom element. Upgrading an element * causes the custom prototype to be applied, an `is` attribute * to be attached (as needed), and invocation of the `readyCallback`. * `upgrade` does nothing if the element is already upgraded, or * if it matches no registered custom tag name. * * @method ugprade * @param {Element} element The element to upgrade. * @return {Element} The upgraded element. */ scope.upgrade = upgradeElement; } // bc document.register = document.registerElement; scope.hasNative = hasNative; scope.useNative = useNative; })(window.CustomElements); (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); (function(scope){ // bootstrap parsing function bootstrap() { // parse document CustomElements.parser.parse(document); // one more pass before register is 'live' CustomElements.upgradeDocument(document); CustomElements.performedInitialDocumentUpgrade = true; // 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}) ); }); } // 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' : document.readyState == 'loading' ? 'DOMContentLoaded' : 'load'; window.addEventListener(loadEvent, bootstrap); } })(window.CustomElements); (function() { // Patch to allow custom element and shadow dom to work together, from: // https://github.com/Polymer/platform-dev/blob/60ece8c323c5d9325cbfdfd6e8cd180d4f38a3bc/src/patches-shadowdom-polyfill.js // include .host reference if (HTMLElement.prototype.createShadowRoot) { var originalCreateShadowRoot = HTMLElement.prototype.createShadowRoot; HTMLElement.prototype.createShadowRoot = function() { var root = originalCreateShadowRoot.call(this); root.host = this; CustomElements.watchShadow(this); return root; } } // Patch to allow custom elements and shadow dom to work together, from: // https://github.com/Polymer/platform-dev/blob/2bb9c56d90f9ac19c2e65cdad368668aff514f14/src/patches-custom-elements.js 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](window.ShadowDOMPolyfill.wrapIfNeeded(inNode)); }; }); } // Patch to make importNode work. // https://github.com/Polymer/platform-dev/blob/64a92f273462f04a84abbe2f054294f2b62dbcd6/src/patches-mdv.js 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; } } })();