// 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; })(); } 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; } } })();