mirror of
https://github.com/jlengrand/webcomponentsjs.git
synced 2026-03-10 08:51:22 +00:00
8058 lines
247 KiB
JavaScript
8058 lines
247 KiB
JavaScript
(function () {
|
|
'use strict';
|
|
|
|
/**
|
|
* @license
|
|
* Copyright (c) 2016 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
|
|
*/
|
|
(scope => {
|
|
|
|
/********************* base setup *********************/
|
|
const useNative = Boolean('import' in document.createElement('link'));
|
|
|
|
// Polyfill `currentScript` for browsers without it.
|
|
let currentScript = null;
|
|
if ('currentScript' in document === false) {
|
|
Object.defineProperty(document, 'currentScript', {
|
|
get() {
|
|
return currentScript ||
|
|
// NOTE: only works when called in synchronously executing code.
|
|
// readyState should check if `loading` but IE10 is
|
|
// interactive when scripts run so we cheat. This is not needed by
|
|
// html-imports polyfill but helps generally polyfill `currentScript`.
|
|
(document.readyState !== 'complete' ?
|
|
document.scripts[document.scripts.length - 1] : null);
|
|
},
|
|
configurable: true
|
|
});
|
|
}
|
|
|
|
/********************* path fixup *********************/
|
|
const ABS_URL_TEST = /(^\/)|(^#)|(^[\w-\d]*:)/;
|
|
const CSS_URL_REGEXP = /(url\()([^)]*)(\))/g;
|
|
const CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g;
|
|
const STYLESHEET_REGEXP = /(<link[^>]*)(rel=['|"]?stylesheet['|"]?[^>]*>)/g;
|
|
|
|
// path fixup: style elements in imports must be made relative to the main
|
|
// document. We fixup url's in url() and @import.
|
|
const Path = {
|
|
|
|
fixUrls(element, base) {
|
|
if (element.href) {
|
|
element.setAttribute('href',
|
|
Path.replaceAttrUrl(element.getAttribute('href'), base));
|
|
}
|
|
if (element.src) {
|
|
element.setAttribute('src',
|
|
Path.replaceAttrUrl(element.getAttribute('src'), base));
|
|
}
|
|
if (element.localName === 'style') {
|
|
const r = Path.replaceUrls(element.textContent, base, CSS_URL_REGEXP);
|
|
element.textContent = Path.replaceUrls(r, base, CSS_IMPORT_REGEXP);
|
|
}
|
|
},
|
|
|
|
replaceUrls(text, linkUrl, regexp) {
|
|
return text.replace(regexp, (m, pre, url, post) => {
|
|
let urlPath = url.replace(/["']/g, '');
|
|
if (linkUrl) {
|
|
urlPath = Path.resolveUrl(urlPath, linkUrl);
|
|
}
|
|
return pre + '\'' + urlPath + '\'' + post;
|
|
});
|
|
},
|
|
|
|
replaceAttrUrl(text, linkUrl) {
|
|
if (text && ABS_URL_TEST.test(text)) {
|
|
return text;
|
|
} else {
|
|
return Path.resolveUrl(text, linkUrl);
|
|
}
|
|
},
|
|
|
|
resolveUrl(url, base) {
|
|
// Lazy feature detection.
|
|
if (Path.__workingURL === undefined) {
|
|
Path.__workingURL = false;
|
|
try {
|
|
const u = new URL('b', 'http://a');
|
|
u.pathname = 'c%20d';
|
|
Path.__workingURL = (u.href === 'http://a/c%20d');
|
|
} catch (e) {}
|
|
}
|
|
|
|
if (Path.__workingURL) {
|
|
return (new URL(url, base)).href;
|
|
}
|
|
|
|
// Fallback to creating an anchor into a disconnected document.
|
|
let doc = Path.__tempDoc;
|
|
if (!doc) {
|
|
doc = document.implementation.createHTMLDocument('temp');
|
|
Path.__tempDoc = doc;
|
|
doc.__base = doc.createElement('base');
|
|
doc.head.appendChild(doc.__base);
|
|
doc.__anchor = doc.createElement('a');
|
|
}
|
|
doc.__base.href = base;
|
|
doc.__anchor.href = url;
|
|
return doc.__anchor.href || url;
|
|
}
|
|
};
|
|
|
|
/********************* Xhr processor *********************/
|
|
const Xhr = {
|
|
|
|
async: true,
|
|
|
|
/**
|
|
* @param {!string} url
|
|
* @param {!function(!string, string=)} success
|
|
* @param {!function(!string)} fail
|
|
*/
|
|
load(url, success, fail) {
|
|
if (!url) {
|
|
fail('error: href must be specified');
|
|
} else if (url.match(/^data:/)) {
|
|
// Handle Data URI Scheme
|
|
const pieces = url.split(',');
|
|
const header = pieces[0];
|
|
let resource = pieces[1];
|
|
if (header.indexOf(';base64') > -1) {
|
|
resource = atob(resource);
|
|
} else {
|
|
resource = decodeURIComponent(resource);
|
|
}
|
|
success(resource);
|
|
} else {
|
|
const request = new XMLHttpRequest();
|
|
request.open('GET', url, Xhr.async);
|
|
request.onload = () => {
|
|
// Servers redirecting an import can add a Location header to help us
|
|
// polyfill correctly. Handle relative and full paths.
|
|
let redirectedUrl = request.getResponseHeader('Location');
|
|
if (redirectedUrl && redirectedUrl.indexOf('/') === 0) {
|
|
// In IE location.origin might not work
|
|
// https://connect.microsoft.com/IE/feedback/details/1763802/location-origin-is-undefined-in-ie-11-on-windows-10-but-works-on-windows-7
|
|
const origin = (location.origin || location.protocol + '//' + location.host);
|
|
redirectedUrl = origin + redirectedUrl;
|
|
}
|
|
const resource = /** @type {string} */ (request.response || request.responseText);
|
|
if (request.status === 304 || request.status === 0 ||
|
|
request.status >= 200 && request.status < 300) {
|
|
success(resource, redirectedUrl);
|
|
} else {
|
|
fail(resource);
|
|
}
|
|
};
|
|
request.send();
|
|
}
|
|
}
|
|
};
|
|
|
|
/********************* importer *********************/
|
|
|
|
const isIE = /Trident/.test(navigator.userAgent) ||
|
|
/Edge\/\d./i.test(navigator.userAgent);
|
|
|
|
const importSelector = 'link[rel=import]';
|
|
|
|
// Used to disable loading of resources.
|
|
const importDisableType = 'import-disable';
|
|
|
|
const disabledLinkSelector = `link[rel=stylesheet][href][type=${importDisableType}]`;
|
|
|
|
const importDependenciesSelector = `${importSelector}, ${disabledLinkSelector},
|
|
style:not([type]), link[rel=stylesheet][href]:not([type]),
|
|
script:not([type]), script[type="application/javascript"],
|
|
script[type="text/javascript"]`;
|
|
|
|
const importDependencyAttr = 'import-dependency';
|
|
|
|
const rootImportSelector = `${importSelector}:not(${importDependencyAttr})`;
|
|
|
|
const pendingScriptsSelector = `script[${importDependencyAttr}]`;
|
|
|
|
const pendingStylesSelector = `style[${importDependencyAttr}],
|
|
link[rel=stylesheet][${importDependencyAttr}]`;
|
|
|
|
/**
|
|
* Importer will:
|
|
* - load any linked import documents (with deduping)
|
|
* - whenever an import is loaded, prompt the parser to try to parse
|
|
* - observe imported documents for new elements (these are handled via the
|
|
* dynamic importer)
|
|
*/
|
|
class Importer {
|
|
constructor() {
|
|
this.documents = {};
|
|
// Used to keep track of pending loads, so that flattening and firing of
|
|
// events can be done when all resources are ready.
|
|
this.inflight = 0;
|
|
this.dynamicImportsMO = new MutationObserver(m => this.handleMutations(m));
|
|
// 1. Load imports contents
|
|
// 2. Assign them to first import links on the document
|
|
// 3. Wait for import styles & scripts to be done loading/running
|
|
// 4. Fire load/error events
|
|
whenDocumentReady(() => {
|
|
// Observe changes on <head>.
|
|
this.dynamicImportsMO.observe(document.head, {
|
|
childList: true,
|
|
subtree: true
|
|
});
|
|
this.loadImports(document);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {!(HTMLDocument|DocumentFragment|Element)} doc
|
|
*/
|
|
loadImports(doc) {
|
|
const links = /** @type {!NodeList<!HTMLLinkElement>} */
|
|
(doc.querySelectorAll(importSelector));
|
|
for (let i = 0, l = links.length; i < l; i++) {
|
|
this.loadImport(links[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!HTMLLinkElement} link
|
|
*/
|
|
loadImport(link) {
|
|
const url = link.href;
|
|
// This resource is already being handled by another import.
|
|
if (this.documents[url] !== undefined) {
|
|
// If import is already loaded, we can safely associate it to the link
|
|
// and fire the load/error event.
|
|
const imp = this.documents[url];
|
|
if (imp && imp['__loaded']) {
|
|
link.import = imp;
|
|
this.fireEventIfNeeded(link);
|
|
}
|
|
return;
|
|
}
|
|
this.inflight++;
|
|
// Mark it as pending to notify others this url is being loaded.
|
|
this.documents[url] = 'pending';
|
|
Xhr.load(url, (resource, redirectedUrl) => {
|
|
const doc = this.makeDocument(resource, redirectedUrl || url);
|
|
this.documents[url] = doc;
|
|
this.inflight--;
|
|
// Load subtree.
|
|
this.loadImports(doc);
|
|
this.processImportsIfLoadingDone();
|
|
}, () => {
|
|
// If load fails, handle error.
|
|
this.documents[url] = null;
|
|
this.inflight--;
|
|
this.processImportsIfLoadingDone();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates a new document containing resource and normalizes urls accordingly.
|
|
* @param {string=} resource
|
|
* @param {string=} url
|
|
* @return {!DocumentFragment}
|
|
*/
|
|
makeDocument(resource, url) {
|
|
if (!resource) {
|
|
return document.createDocumentFragment();
|
|
}
|
|
|
|
if (isIE) {
|
|
// <link rel=stylesheet> should be appended to <head>. Not doing so
|
|
// in IE/Edge breaks the cascading order. We disable the loading by
|
|
// setting the type before setting innerHTML to avoid loading
|
|
// resources twice.
|
|
resource = resource.replace(STYLESHEET_REGEXP, (match, p1, p2) => {
|
|
if (match.indexOf('type=') === -1) {
|
|
return `${p1} type=${importDisableType} ${p2}`;
|
|
}
|
|
return match;
|
|
});
|
|
}
|
|
|
|
let content;
|
|
const template = /** @type {!HTMLTemplateElement} */
|
|
(document.createElement('template'));
|
|
template.innerHTML = resource;
|
|
if (template.content) {
|
|
// This creates issues in Safari10 when used with shadydom (see #12).
|
|
content = template.content;
|
|
} else {
|
|
// <template> not supported, create fragment and move content into it.
|
|
content = document.createDocumentFragment();
|
|
while (template.firstChild) {
|
|
content.appendChild(template.firstChild);
|
|
}
|
|
}
|
|
|
|
// Support <base> in imported docs. Resolve url and remove its href.
|
|
const baseEl = content.querySelector('base');
|
|
if (baseEl) {
|
|
url = Path.replaceAttrUrl(baseEl.getAttribute('href'), url);
|
|
baseEl.removeAttribute('href');
|
|
}
|
|
|
|
const n$ = /** @type {!NodeList<!(HTMLLinkElement|HTMLScriptElement|HTMLStyleElement)>} */
|
|
(content.querySelectorAll(importDependenciesSelector));
|
|
// For source map hints.
|
|
let inlineScriptIndex = 0;
|
|
for (let i = 0, l = n$.length, n; i < l && (n = n$[i]); i++) {
|
|
// Listen for load/error events, then fix urls.
|
|
whenElementLoaded(n);
|
|
Path.fixUrls(n, url);
|
|
// Mark for easier selectors.
|
|
n.setAttribute(importDependencyAttr, '');
|
|
// Generate source map hints for inline scripts.
|
|
if (n.localName === 'script' && !n.src && n.textContent) {
|
|
const num = inlineScriptIndex ? `-${inlineScriptIndex}` : '';
|
|
const content = n.textContent + `\n//# sourceURL=${url}${num}.js\n`;
|
|
// We use the src attribute so it triggers load/error events, and it's
|
|
// easier to capture errors (e.g. parsing) like this.
|
|
n.setAttribute('src', 'data:text/javascript;charset=utf-8,' + encodeURIComponent(content));
|
|
n.textContent = '';
|
|
inlineScriptIndex++;
|
|
}
|
|
}
|
|
return content;
|
|
}
|
|
|
|
/**
|
|
* Waits for loaded imports to finish loading scripts and styles, then fires
|
|
* the load/error events.
|
|
*/
|
|
processImportsIfLoadingDone() {
|
|
// Wait until all resources are ready, then load import resources.
|
|
if (this.inflight) {
|
|
return;
|
|
}
|
|
|
|
// Stop observing, flatten & load resource, then restart observing <head>.
|
|
this.dynamicImportsMO.disconnect();
|
|
this.flatten(document);
|
|
// We wait for styles to load, and at the same time we execute the scripts,
|
|
// then fire the load/error events for imports to have faster whenReady
|
|
// callback execution.
|
|
// NOTE: This is different for native behavior where scripts would be
|
|
// executed after the styles before them are loaded.
|
|
// To achieve that, we could select pending styles and scripts in the
|
|
// document and execute them sequentially in their dom order.
|
|
let scriptsOk = false,
|
|
stylesOk = false;
|
|
const onLoadingDone = () => {
|
|
if (stylesOk && scriptsOk) {
|
|
// Restart observing.
|
|
this.dynamicImportsMO.observe(document.head, {
|
|
childList: true,
|
|
subtree: true
|
|
});
|
|
this.fireEvents();
|
|
}
|
|
};
|
|
this.waitForStyles(() => {
|
|
stylesOk = true;
|
|
onLoadingDone();
|
|
});
|
|
this.runScripts(() => {
|
|
scriptsOk = true;
|
|
onLoadingDone();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {!HTMLDocument} doc
|
|
*/
|
|
flatten(doc) {
|
|
const n$ = /** @type {!NodeList<!HTMLLinkElement>} */
|
|
(doc.querySelectorAll(importSelector));
|
|
for (let i = 0, l = n$.length, n; i < l && (n = n$[i]); i++) {
|
|
const imp = this.documents[n.href];
|
|
n.import = /** @type {!Document} */ (imp);
|
|
if (imp && imp.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
// We set the .import to be the link itself, and update its readyState.
|
|
// Other links with the same href will point to this link.
|
|
this.documents[n.href] = n;
|
|
n.readyState = 'loading';
|
|
// Suppress Closure warning about incompatible subtype assignment.
|
|
( /** @type {!HTMLElement} */ (n).import = n);
|
|
// Override baseURI so that link.import.baseURI can be used seemlessly
|
|
// on native or polyfilled html-imports.
|
|
Object.defineProperty(n, 'baseURI', {
|
|
get: () => n.href,
|
|
configurable: true,
|
|
enumerable: true
|
|
});
|
|
this.flatten(imp);
|
|
n.appendChild(imp);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Replaces all the imported scripts with a clone in order to execute them.
|
|
* Updates the `currentScript`.
|
|
* @param {!function()} callback
|
|
*/
|
|
runScripts(callback) {
|
|
const s$ = document.querySelectorAll(pendingScriptsSelector);
|
|
const l = s$.length;
|
|
const cloneScript = i => {
|
|
if (i < l) {
|
|
// The pending scripts have been generated through innerHTML and
|
|
// browsers won't execute them for security reasons. We cannot use
|
|
// s.cloneNode(true) either, the only way to run the script is manually
|
|
// creating a new element and copying its attributes.
|
|
const s = s$[i];
|
|
const clone = /** @type {!HTMLScriptElement} */
|
|
(document.createElement('script'));
|
|
// Remove import-dependency attribute to avoid double cloning.
|
|
s.removeAttribute(importDependencyAttr);
|
|
for (let j = 0, ll = s.attributes.length; j < ll; j++) {
|
|
clone.setAttribute(s.attributes[j].name, s.attributes[j].value);
|
|
}
|
|
// Update currentScript and replace original with clone script.
|
|
currentScript = clone;
|
|
s.parentNode.replaceChild(clone, s);
|
|
whenElementLoaded(clone, () => {
|
|
currentScript = null;
|
|
cloneScript(i + 1);
|
|
});
|
|
} else {
|
|
callback();
|
|
}
|
|
};
|
|
cloneScript(0);
|
|
}
|
|
|
|
/**
|
|
* Waits for all the imported stylesheets/styles to be loaded.
|
|
* @param {!function()} callback
|
|
*/
|
|
waitForStyles(callback) {
|
|
const s$ = /** @type {!NodeList<!(HTMLLinkElement|HTMLStyleElement)>} */
|
|
(document.querySelectorAll(pendingStylesSelector));
|
|
let pending = s$.length;
|
|
if (!pending) {
|
|
callback();
|
|
return;
|
|
}
|
|
// <link rel=stylesheet> should be appended to <head>. Not doing so
|
|
// in IE/Edge breaks the cascading order
|
|
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10472273/
|
|
// If there is one <link rel=stylesheet> imported, we must move all imported
|
|
// links and styles to <head>.
|
|
const needsMove = isIE && !!document.querySelector(disabledLinkSelector);
|
|
for (let i = 0, l = s$.length, s; i < l && (s = s$[i]); i++) {
|
|
// Listen for load/error events, remove selector once is done loading.
|
|
whenElementLoaded(s, () => {
|
|
s.removeAttribute(importDependencyAttr);
|
|
if (--pending === 0) {
|
|
callback();
|
|
}
|
|
});
|
|
// Check if was already moved to head, to handle the case where the element
|
|
// has already been moved but it is still loading.
|
|
if (needsMove && s.parentNode !== document.head) {
|
|
// Replace the element we're about to move with a placeholder.
|
|
const placeholder = document.createElement(s.localName);
|
|
// Add reference of the moved element.
|
|
placeholder['__appliedElement'] = s;
|
|
// Disable this from appearing in document.styleSheets.
|
|
placeholder.setAttribute('type', 'import-placeholder');
|
|
// Append placeholder next to the sibling, and move original to <head>.
|
|
s.parentNode.insertBefore(placeholder, s.nextSibling);
|
|
let newSibling = importForElement(s);
|
|
while (newSibling && importForElement(newSibling)) {
|
|
newSibling = importForElement(newSibling);
|
|
}
|
|
if (newSibling.parentNode !== document.head) {
|
|
newSibling = null;
|
|
}
|
|
document.head.insertBefore(s, newSibling);
|
|
// Enable the loading of <link rel=stylesheet>.
|
|
s.removeAttribute('type');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fires load/error events for imports in the right order .
|
|
*/
|
|
fireEvents() {
|
|
const n$ = /** @type {!NodeList<!HTMLLinkElement>} */
|
|
(document.querySelectorAll(importSelector));
|
|
// Inverse order to have events firing bottom-up.
|
|
for (let i = n$.length - 1, n; i >= 0 && (n = n$[i]); i--) {
|
|
this.fireEventIfNeeded(n);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fires load/error event for the import if this wasn't done already.
|
|
* @param {!HTMLLinkElement} link
|
|
*/
|
|
fireEventIfNeeded(link) {
|
|
// Don't fire twice same event.
|
|
if (!link['__loaded']) {
|
|
link['__loaded'] = true;
|
|
// Update link's import readyState.
|
|
link.import && (link.import.readyState = 'complete');
|
|
const eventType = link.import ? 'load' : 'error';
|
|
link.dispatchEvent(newCustomEvent(eventType, {
|
|
bubbles: false,
|
|
cancelable: false,
|
|
detail: undefined
|
|
}));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Array<MutationRecord>} mutations
|
|
*/
|
|
handleMutations(mutations) {
|
|
for (let i = 0; i < mutations.length; i++) {
|
|
const m = mutations[i];
|
|
if (!m.addedNodes) {
|
|
continue;
|
|
}
|
|
for (let ii = 0; ii < m.addedNodes.length; ii++) {
|
|
const elem = m.addedNodes[ii];
|
|
if (!elem || elem.nodeType !== Node.ELEMENT_NODE) {
|
|
continue;
|
|
}
|
|
// NOTE: added scripts are not updating currentScript in IE.
|
|
if (isImportLink(elem)) {
|
|
this.loadImport( /** @type {!HTMLLinkElement} */ (elem));
|
|
} else {
|
|
this.loadImports( /** @type {!Element} */ (elem));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!Node} node
|
|
* @return {boolean}
|
|
*/
|
|
const isImportLink = node => {
|
|
return node.nodeType === Node.ELEMENT_NODE && node.localName === 'link' &&
|
|
( /** @type {!HTMLLinkElement} */ (node).rel === 'import');
|
|
};
|
|
|
|
/**
|
|
* Waits for an element to finish loading. If already done loading, it will
|
|
* mark the element accordingly.
|
|
* @param {!(HTMLLinkElement|HTMLScriptElement|HTMLStyleElement)} element
|
|
* @param {function()=} callback
|
|
*/
|
|
const whenElementLoaded = (element, callback) => {
|
|
if (element['__loaded']) {
|
|
callback && callback();
|
|
} else if (isImportLink(element) &&
|
|
(!useNative && /** @type {!HTMLLinkElement}*/ (element).import === null) ||
|
|
(element.import && /** @type {!HTMLLinkElement}*/ (element).import.readyState === 'complete')) {
|
|
// This import has already been loaded but its __loaded property got removed. Ensure
|
|
// we set it back!
|
|
element['__loaded'] = true;
|
|
callback && callback();
|
|
} else if (element.localName === 'script' && !element.src) {
|
|
// Inline scripts don't trigger load/error events, consider them already loaded.
|
|
element['__loaded'] = true;
|
|
callback && callback();
|
|
} else {
|
|
const onLoadingDone = event => {
|
|
element.removeEventListener(event.type, onLoadingDone);
|
|
element['__loaded'] = true;
|
|
callback && callback();
|
|
};
|
|
element.addEventListener('load', onLoadingDone);
|
|
// NOTE: We listen only for load events in IE/Edge, because in IE/Edge
|
|
// <style> with @import will fire error events for each failing @import,
|
|
// and finally will trigger the load event when all @import are
|
|
// finished (even if all fail).
|
|
if (!isIE || element.localName !== 'style') {
|
|
element.addEventListener('error', onLoadingDone);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Calls the callback when all imports in the document at call time
|
|
* (or at least document ready) have loaded. Callback is called synchronously
|
|
* if imports are already done loading.
|
|
* @param {function()=} callback
|
|
*/
|
|
const whenReady = callback => {
|
|
// 1. ensure the document is in a ready state (has dom), then
|
|
// 2. watch for loading of imports and call callback when done
|
|
whenDocumentReady(() => whenImportsReady(() => callback && callback()));
|
|
};
|
|
|
|
/**
|
|
* Invokes the callback when document is in ready state. Callback is called
|
|
* synchronously if document is already done loading.
|
|
* @param {!function()} callback
|
|
*/
|
|
const whenDocumentReady = callback => {
|
|
if (document.readyState !== 'loading') {
|
|
callback();
|
|
} else {
|
|
const stateChanged = () => {
|
|
if (document.readyState !== 'loading') {
|
|
document.removeEventListener('readystatechange', stateChanged);
|
|
callback();
|
|
}
|
|
};
|
|
document.addEventListener('readystatechange', stateChanged);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Invokes the callback after all imports are loaded. Callback is called
|
|
* synchronously if imports are already done loading.
|
|
* @param {!function()} callback
|
|
*/
|
|
const whenImportsReady = callback => {
|
|
let imports = /** @type {!NodeList<!HTMLLinkElement>} */
|
|
(document.querySelectorAll(rootImportSelector));
|
|
let pending = imports.length;
|
|
if (!pending) {
|
|
callback();
|
|
return;
|
|
}
|
|
for (let i = 0, l = imports.length, imp; i < l && (imp = imports[i]); i++) {
|
|
whenElementLoaded(imp, () => {
|
|
if (--pending === 0) {
|
|
callback();
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns the import document containing the element.
|
|
* @param {!Node} element
|
|
* @return {HTMLLinkElement|Document|undefined}
|
|
*/
|
|
const importForElement = element => {
|
|
if (useNative) {
|
|
// Return only if not in the main doc!
|
|
return element.ownerDocument !== document ? element.ownerDocument : null;
|
|
}
|
|
let doc = element['__importDoc'];
|
|
if (!doc && element.parentNode) {
|
|
doc = /** @type {!Element} */ (element.parentNode);
|
|
if (typeof doc.closest === 'function') {
|
|
// Element.closest returns the element itself if it matches the selector,
|
|
// so we search the closest import starting from the parent.
|
|
doc = doc.closest(importSelector);
|
|
} else {
|
|
// Walk up the parent tree until we find an import.
|
|
while (!isImportLink(doc) && (doc = doc.parentNode)) {}
|
|
}
|
|
element['__importDoc'] = doc;
|
|
}
|
|
return doc;
|
|
};
|
|
|
|
const newCustomEvent = (type, params) => {
|
|
if (typeof window.CustomEvent === 'function') {
|
|
return new CustomEvent(type, params);
|
|
}
|
|
const event = /** @type {!CustomEvent} */ (document.createEvent('CustomEvent'));
|
|
event.initCustomEvent(type, Boolean(params.bubbles), Boolean(params.cancelable), params.detail);
|
|
return event;
|
|
};
|
|
|
|
if (useNative) {
|
|
// Check for imports that might already be done loading by the time this
|
|
// script is actually executed. Native imports are blocking, so the ones
|
|
// available in the document by this time should already have failed
|
|
// or have .import defined.
|
|
const imps = /** @type {!NodeList<!HTMLLinkElement>} */
|
|
(document.querySelectorAll(importSelector));
|
|
for (let i = 0, l = imps.length, imp; i < l && (imp = imps[i]); i++) {
|
|
if (!imp.import || imp.import.readyState !== 'loading') {
|
|
imp['__loaded'] = true;
|
|
}
|
|
}
|
|
// Listen for load/error events to capture dynamically added scripts.
|
|
/**
|
|
* @type {!function(!Event)}
|
|
*/
|
|
const onLoadingDone = event => {
|
|
const elem = /** @type {!Element} */ (event.target);
|
|
if (isImportLink(elem)) {
|
|
elem['__loaded'] = true;
|
|
}
|
|
};
|
|
document.addEventListener('load', onLoadingDone, true /* useCapture */ );
|
|
document.addEventListener('error', onLoadingDone, true /* useCapture */ );
|
|
} else {
|
|
new Importer();
|
|
}
|
|
|
|
/**
|
|
Add support for the `HTMLImportsLoaded` event and the `HTMLImports.whenReady`
|
|
method. This api is necessary because unlike the native implementation,
|
|
script elements do not force imports to resolve. Instead, users should wrap
|
|
code in either an `HTMLImportsLoaded` handler or after load time in an
|
|
`HTMLImports.whenReady(callback)` call.
|
|
|
|
NOTE: This module also supports these apis under the native implementation.
|
|
Therefore, if this file is loaded, the same code can be used under both
|
|
the polyfill and native implementation.
|
|
*/
|
|
whenReady(() => document.dispatchEvent(newCustomEvent('HTMLImportsLoaded', {
|
|
cancelable: true,
|
|
bubbles: true,
|
|
detail: undefined
|
|
})));
|
|
|
|
// exports
|
|
scope.useNative = useNative;
|
|
scope.whenReady = whenReady;
|
|
scope.importForElement = importForElement;
|
|
|
|
})(window.HTMLImports = (window.HTMLImports || {}));
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2016 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
|
|
*/
|
|
|
|
let settings = window['ShadyDOM'] || {};
|
|
|
|
settings.hasNativeShadowDOM = Boolean(Element.prototype.attachShadow && Node.prototype.getRootNode);
|
|
|
|
let desc = Object.getOwnPropertyDescriptor(Node.prototype, 'firstChild');
|
|
|
|
settings.hasDescriptors = Boolean(desc && desc.configurable && desc.get);
|
|
settings.inUse = settings['force'] || !settings.hasNativeShadowDOM;
|
|
|
|
function isShadyRoot(obj) {
|
|
return Boolean(obj.__localName === 'ShadyRoot');
|
|
}
|
|
|
|
function ownerShadyRootForNode(node) {
|
|
let root = node.getRootNode();
|
|
if (isShadyRoot(root)) {
|
|
return root;
|
|
}
|
|
}
|
|
|
|
let p = Element.prototype;
|
|
let matches = p.matches || p.matchesSelector ||
|
|
p.mozMatchesSelector || p.msMatchesSelector ||
|
|
p.oMatchesSelector || p.webkitMatchesSelector;
|
|
|
|
function matchesSelector(element, selector) {
|
|
return matches.call(element, selector);
|
|
}
|
|
|
|
function copyOwnProperty(name, source, target) {
|
|
let pd = Object.getOwnPropertyDescriptor(source, name);
|
|
if (pd) {
|
|
Object.defineProperty(target, name, pd);
|
|
}
|
|
}
|
|
|
|
function extend(target, source) {
|
|
if (target && source) {
|
|
let n$ = Object.getOwnPropertyNames(source);
|
|
for (let i=0, n; (i<n$.length) && (n=n$[i]); i++) {
|
|
copyOwnProperty(n, source, target);
|
|
}
|
|
}
|
|
return target || source;
|
|
}
|
|
|
|
function extendAll(target, ...sources) {
|
|
for (let i=0; i < sources.length; i++) {
|
|
extend(target, sources[i]);
|
|
}
|
|
return target;
|
|
}
|
|
|
|
function mixin(target, source) {
|
|
for (var i in source) {
|
|
target[i] = source[i];
|
|
}
|
|
return target;
|
|
}
|
|
|
|
function patchPrototype(obj, mixin) {
|
|
let proto = Object.getPrototypeOf(obj);
|
|
if (!proto.hasOwnProperty('__patchProto')) {
|
|
let patchProto = Object.create(proto);
|
|
patchProto.__sourceProto = proto;
|
|
extend(patchProto, mixin);
|
|
proto['__patchProto'] = patchProto;
|
|
}
|
|
// old browsers don't have setPrototypeOf
|
|
obj.__proto__ = proto['__patchProto'];
|
|
}
|
|
|
|
|
|
let twiddle = document.createTextNode('');
|
|
let content = 0;
|
|
let queue = [];
|
|
new MutationObserver(() => {
|
|
while (queue.length) {
|
|
// catch errors in user code...
|
|
try {
|
|
queue.shift()();
|
|
} catch(e) {
|
|
// enqueue another record and throw
|
|
twiddle.textContent = content++;
|
|
throw(e);
|
|
}
|
|
}
|
|
}).observe(twiddle, {characterData: true});
|
|
|
|
// use MutationObserver to get microtask async timing.
|
|
function microtask(callback) {
|
|
queue.push(callback);
|
|
twiddle.textContent = content++;
|
|
}
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2016 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
|
|
*/
|
|
|
|
// render enqueuer/flusher
|
|
let flushList = [];
|
|
let scheduled;
|
|
function enqueue(callback) {
|
|
if (!scheduled) {
|
|
scheduled = true;
|
|
microtask(flush);
|
|
}
|
|
flushList.push(callback);
|
|
}
|
|
|
|
function flush() {
|
|
scheduled = false;
|
|
let didFlush = Boolean(flushList.length);
|
|
while (flushList.length) {
|
|
flushList.shift()();
|
|
}
|
|
return didFlush;
|
|
}
|
|
|
|
flush['list'] = flushList;
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2016 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
|
|
*/
|
|
|
|
class AsyncObserver {
|
|
|
|
constructor() {
|
|
this._scheduled = false;
|
|
this.addedNodes = [];
|
|
this.removedNodes = [];
|
|
this.callbacks = new Set();
|
|
}
|
|
|
|
schedule() {
|
|
if (!this._scheduled) {
|
|
this._scheduled = true;
|
|
microtask(() => {
|
|
this.flush();
|
|
});
|
|
}
|
|
}
|
|
|
|
flush() {
|
|
if (this._scheduled) {
|
|
this._scheduled = false;
|
|
let mutations = this.takeRecords();
|
|
if (mutations.length) {
|
|
this.callbacks.forEach(function(cb) {
|
|
cb(mutations);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
takeRecords() {
|
|
if (this.addedNodes.length || this.removedNodes.length) {
|
|
let mutations = [{
|
|
addedNodes: this.addedNodes,
|
|
removedNodes: this.removedNodes
|
|
}];
|
|
this.addedNodes = [];
|
|
this.removedNodes = [];
|
|
return mutations;
|
|
}
|
|
return [];
|
|
}
|
|
|
|
}
|
|
|
|
// TODO(sorvell): consider instead polyfilling MutationObserver
|
|
// directly so that users do not have to fork their code.
|
|
// Supporting the entire api may be challenging: e.g. filtering out
|
|
// removed nodes in the wrong scope and seeing non-distributing
|
|
// subtree child mutations.
|
|
let observeChildren = function(node, callback) {
|
|
node.__shady = node.__shady || {};
|
|
if (!node.__shady.observer) {
|
|
node.__shady.observer = new AsyncObserver();
|
|
}
|
|
node.__shady.observer.callbacks.add(callback);
|
|
let observer = node.__shady.observer;
|
|
return {
|
|
_callback: callback,
|
|
_observer: observer,
|
|
_node: node,
|
|
takeRecords() {
|
|
return observer.takeRecords()
|
|
}
|
|
};
|
|
};
|
|
|
|
let unobserveChildren = function(handle) {
|
|
let observer = handle && handle._observer;
|
|
if (observer) {
|
|
observer.callbacks.delete(handle._callback);
|
|
if (!observer.callbacks.size) {
|
|
handle._node.__shady.observer = null;
|
|
}
|
|
}
|
|
};
|
|
|
|
function filterMutations(mutations, target) {
|
|
/** @const {Node} */
|
|
const targetRootNode = target.getRootNode();
|
|
return mutations.map(function(mutation) {
|
|
/** @const {boolean} */
|
|
const mutationInScope = (targetRootNode === mutation.target.getRootNode());
|
|
if (mutationInScope && mutation.addedNodes) {
|
|
let nodes = Array.from(mutation.addedNodes).filter(function(n) {
|
|
return (targetRootNode === n.getRootNode());
|
|
});
|
|
if (nodes.length) {
|
|
mutation = Object.create(mutation);
|
|
Object.defineProperty(mutation, 'addedNodes', {
|
|
value: nodes,
|
|
configurable: true
|
|
});
|
|
return mutation;
|
|
}
|
|
} else if (mutationInScope) {
|
|
return mutation;
|
|
}
|
|
}).filter(function(m) { return m});
|
|
}
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2016 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
|
|
*/
|
|
|
|
let appendChild = Element.prototype.appendChild;
|
|
let insertBefore = Element.prototype.insertBefore;
|
|
let removeChild = Element.prototype.removeChild;
|
|
let setAttribute = Element.prototype.setAttribute;
|
|
let removeAttribute = Element.prototype.removeAttribute;
|
|
let cloneNode = Element.prototype.cloneNode;
|
|
let importNode = Document.prototype.importNode;
|
|
let addEventListener = Element.prototype.addEventListener;
|
|
let removeEventListener = Element.prototype.removeEventListener;
|
|
|
|
|
|
var nativeMethods = Object.freeze({
|
|
appendChild: appendChild,
|
|
insertBefore: insertBefore,
|
|
removeChild: removeChild,
|
|
setAttribute: setAttribute,
|
|
removeAttribute: removeAttribute,
|
|
cloneNode: cloneNode,
|
|
importNode: importNode,
|
|
addEventListener: addEventListener,
|
|
removeEventListener: removeEventListener
|
|
});
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2016 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
|
|
*/
|
|
|
|
// Cribbed from ShadowDOM polyfill
|
|
// https://github.com/webcomponents/webcomponentsjs/blob/master/src/ShadowDOM/wrappers/HTMLElement.js#L28
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// innerHTML and outerHTML
|
|
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#escapingString
|
|
let escapeAttrRegExp = /[&\u00A0"]/g;
|
|
let 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) {
|
|
let set = {};
|
|
for (let i = 0; i < arr.length; i++) {
|
|
set[arr[i]] = true;
|
|
}
|
|
return set;
|
|
}
|
|
|
|
// http://www.whatwg.org/specs/web-apps/current-work/#void-elements
|
|
let voidElements = makeSet([
|
|
'area',
|
|
'base',
|
|
'br',
|
|
'col',
|
|
'command',
|
|
'embed',
|
|
'hr',
|
|
'img',
|
|
'input',
|
|
'keygen',
|
|
'link',
|
|
'meta',
|
|
'param',
|
|
'source',
|
|
'track',
|
|
'wbr'
|
|
]);
|
|
|
|
let plaintextParents = makeSet([
|
|
'style',
|
|
'script',
|
|
'xmp',
|
|
'iframe',
|
|
'noembed',
|
|
'noframes',
|
|
'plaintext',
|
|
'noscript'
|
|
]);
|
|
|
|
/**
|
|
* @param {Node} node
|
|
* @param {Node} parentNode
|
|
* @param {Function=} callback
|
|
*/
|
|
function getOuterHTML(node, parentNode, callback) {
|
|
switch (node.nodeType) {
|
|
case Node.ELEMENT_NODE: {
|
|
let tagName = node.localName;
|
|
let s = '<' + tagName;
|
|
let attrs = node.attributes;
|
|
for (let i = 0, attr; (attr = attrs[i]); i++) {
|
|
s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"';
|
|
}
|
|
s += '>';
|
|
if (voidElements[tagName]) {
|
|
return s;
|
|
}
|
|
return s + getInnerHTML(node, callback) + '</' + tagName + '>';
|
|
}
|
|
case Node.TEXT_NODE: {
|
|
let data = /** @type {Text} */ (node).data;
|
|
if (parentNode && plaintextParents[parentNode.localName]) {
|
|
return data;
|
|
}
|
|
return escapeData(data);
|
|
}
|
|
case Node.COMMENT_NODE: {
|
|
return '<!--' + /** @type {Comment} */ (node).data + '-->';
|
|
}
|
|
default: {
|
|
window.console.error(node);
|
|
throw new Error('not implemented');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Node} node
|
|
* @param {Function=} callback
|
|
*/
|
|
function getInnerHTML(node, callback) {
|
|
if (node.localName === 'template') {
|
|
node = /** @type {HTMLTemplateElement} */ (node).content;
|
|
}
|
|
let s = '';
|
|
let c$ = callback ? callback(node) : node.childNodes;
|
|
for (let i=0, l=c$.length, child; (i<l) && (child=c$[i]); i++) {
|
|
s += getOuterHTML(child, node, callback);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2016 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
|
|
*/
|
|
|
|
let nodeWalker = document.createTreeWalker(document, NodeFilter.SHOW_ALL,
|
|
null, false);
|
|
|
|
let elementWalker = document.createTreeWalker(document, NodeFilter.SHOW_ELEMENT,
|
|
null, false);
|
|
|
|
function parentNode(node) {
|
|
nodeWalker.currentNode = node;
|
|
return nodeWalker.parentNode();
|
|
}
|
|
|
|
function firstChild(node) {
|
|
nodeWalker.currentNode = node;
|
|
return nodeWalker.firstChild();
|
|
}
|
|
|
|
function lastChild(node) {
|
|
nodeWalker.currentNode = node;
|
|
return nodeWalker.lastChild();
|
|
}
|
|
|
|
function previousSibling(node) {
|
|
nodeWalker.currentNode = node;
|
|
return nodeWalker.previousSibling();
|
|
}
|
|
|
|
function nextSibling(node) {
|
|
nodeWalker.currentNode = node;
|
|
return nodeWalker.nextSibling();
|
|
}
|
|
|
|
function childNodes(node) {
|
|
let nodes = [];
|
|
nodeWalker.currentNode = node;
|
|
let n = nodeWalker.firstChild();
|
|
while (n) {
|
|
nodes.push(n);
|
|
n = nodeWalker.nextSibling();
|
|
}
|
|
return nodes;
|
|
}
|
|
|
|
function parentElement(node) {
|
|
elementWalker.currentNode = node;
|
|
return elementWalker.parentNode();
|
|
}
|
|
|
|
function firstElementChild(node) {
|
|
elementWalker.currentNode = node;
|
|
return elementWalker.firstChild();
|
|
}
|
|
|
|
function lastElementChild(node) {
|
|
elementWalker.currentNode = node;
|
|
return elementWalker.lastChild();
|
|
}
|
|
|
|
function previousElementSibling(node) {
|
|
elementWalker.currentNode = node;
|
|
return elementWalker.previousSibling();
|
|
}
|
|
|
|
function nextElementSibling(node) {
|
|
elementWalker.currentNode = node;
|
|
return elementWalker.nextSibling();
|
|
}
|
|
|
|
function children(node) {
|
|
let nodes = [];
|
|
elementWalker.currentNode = node;
|
|
let n = elementWalker.firstChild();
|
|
while (n) {
|
|
nodes.push(n);
|
|
n = elementWalker.nextSibling();
|
|
}
|
|
return nodes;
|
|
}
|
|
|
|
function innerHTML(node) {
|
|
return getInnerHTML(node, (n) => childNodes(n));
|
|
}
|
|
|
|
function textContent(node) {
|
|
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
return node.nodeValue;
|
|
}
|
|
let textWalker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT,
|
|
null, false);
|
|
let content = '', n;
|
|
while ( (n = textWalker.nextNode()) ) {
|
|
// TODO(sorvell): can't use textContent since we patch it on Node.prototype!
|
|
// However, should probably patch it only on element.
|
|
content += n.nodeValue;
|
|
}
|
|
return content;
|
|
}
|
|
|
|
var nativeTree = Object.freeze({
|
|
parentNode: parentNode,
|
|
firstChild: firstChild,
|
|
lastChild: lastChild,
|
|
previousSibling: previousSibling,
|
|
nextSibling: nextSibling,
|
|
childNodes: childNodes,
|
|
parentElement: parentElement,
|
|
firstElementChild: firstElementChild,
|
|
lastElementChild: lastElementChild,
|
|
previousElementSibling: previousElementSibling,
|
|
nextElementSibling: nextElementSibling,
|
|
children: children,
|
|
innerHTML: innerHTML,
|
|
textContent: textContent
|
|
});
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2016 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 clearNode(node) {
|
|
while (node.firstChild) {
|
|
node.removeChild(node.firstChild);
|
|
}
|
|
}
|
|
|
|
const nativeInnerHTMLDesc = /** @type {ObjectPropertyDescriptor} */(
|
|
Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML') ||
|
|
Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'innerHTML'));
|
|
|
|
const inertDoc = document.implementation.createHTMLDocument('inert');
|
|
const htmlContainer = inertDoc.createElement('div');
|
|
|
|
const nativeActiveElementDescriptor =
|
|
/** @type {ObjectPropertyDescriptor} */(
|
|
Object.getOwnPropertyDescriptor(Document.prototype, 'activeElement')
|
|
);
|
|
function getDocumentActiveElement() {
|
|
if (nativeActiveElementDescriptor && nativeActiveElementDescriptor.get) {
|
|
return nativeActiveElementDescriptor.get.call(document);
|
|
} else if (!settings.hasDescriptors) {
|
|
return document.activeElement;
|
|
}
|
|
}
|
|
|
|
function activeElementForNode(node) {
|
|
let active = getDocumentActiveElement();
|
|
// In IE11, activeElement might be an empty object if the document is
|
|
// contained in an iframe.
|
|
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/10998788/
|
|
if (!active || !active.nodeType) {
|
|
return null;
|
|
}
|
|
let isShadyRoot$$1 = !!(isShadyRoot(node));
|
|
if (node !== document) {
|
|
// If this node isn't a document or shady root, then it doesn't have
|
|
// an active element.
|
|
if (!isShadyRoot$$1) {
|
|
return null;
|
|
}
|
|
// If this shady root's host is the active element or the active
|
|
// element is not a descendant of the host (in the composed tree),
|
|
// then it doesn't have an active element.
|
|
if (node.host === active ||
|
|
!node.host.contains(active)) {
|
|
return null;
|
|
}
|
|
}
|
|
// This node is either the document or a shady root of which the active
|
|
// element is a (composed) descendant of its host; iterate upwards to
|
|
// find the active element's most shallow host within it.
|
|
let activeRoot = ownerShadyRootForNode(active);
|
|
while (activeRoot && activeRoot !== node) {
|
|
active = activeRoot.host;
|
|
activeRoot = ownerShadyRootForNode(active);
|
|
}
|
|
if (node === document) {
|
|
// This node is the document, so activeRoot should be null.
|
|
return activeRoot ? null : active;
|
|
} else {
|
|
// This node is a non-document shady root, and it should be
|
|
// activeRoot.
|
|
return activeRoot === node ? active : null;
|
|
}
|
|
}
|
|
|
|
let OutsideAccessors = {
|
|
|
|
parentElement: {
|
|
/** @this {Node} */
|
|
get() {
|
|
let l = this.__shady && this.__shady.parentElement;
|
|
return l !== undefined ? l : parentElement(this);
|
|
},
|
|
configurable: true
|
|
},
|
|
|
|
parentNode: {
|
|
/** @this {Node} */
|
|
get() {
|
|
let l = this.__shady && this.__shady.parentNode;
|
|
return l !== undefined ? l : parentNode(this);
|
|
},
|
|
configurable: true
|
|
},
|
|
|
|
nextSibling: {
|
|
/** @this {Node} */
|
|
get() {
|
|
let l = this.__shady && this.__shady.nextSibling;
|
|
return l !== undefined ? l : nextSibling(this);
|
|
},
|
|
configurable: true
|
|
},
|
|
|
|
previousSibling: {
|
|
/** @this {Node} */
|
|
get() {
|
|
let l = this.__shady && this.__shady.previousSibling;
|
|
return l !== undefined ? l : previousSibling(this);
|
|
},
|
|
configurable: true
|
|
},
|
|
|
|
className: {
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
get() {
|
|
return this.getAttribute('class');
|
|
},
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
set(value) {
|
|
this.setAttribute('class', value);
|
|
},
|
|
configurable: true
|
|
},
|
|
|
|
// fragment, element, document
|
|
nextElementSibling: {
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
get() {
|
|
if (this.__shady && this.__shady.nextSibling !== undefined) {
|
|
let n = this.nextSibling;
|
|
while (n && n.nodeType !== Node.ELEMENT_NODE) {
|
|
n = n.nextSibling;
|
|
}
|
|
return n;
|
|
} else {
|
|
return nextElementSibling(this);
|
|
}
|
|
},
|
|
configurable: true
|
|
},
|
|
|
|
previousElementSibling: {
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
get() {
|
|
if (this.__shady && this.__shady.previousSibling !== undefined) {
|
|
let n = this.previousSibling;
|
|
while (n && n.nodeType !== Node.ELEMENT_NODE) {
|
|
n = n.previousSibling;
|
|
}
|
|
return n;
|
|
} else {
|
|
return previousElementSibling(this);
|
|
}
|
|
},
|
|
configurable: true
|
|
}
|
|
|
|
};
|
|
|
|
let InsideAccessors = {
|
|
|
|
childNodes: {
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
get() {
|
|
if (this.__shady && this.__shady.firstChild !== undefined) {
|
|
if (!this.__shady.childNodes) {
|
|
this.__shady.childNodes = [];
|
|
for (let n=this.firstChild; n; n=n.nextSibling) {
|
|
this.__shady.childNodes.push(n);
|
|
}
|
|
}
|
|
return this.__shady.childNodes;
|
|
} else {
|
|
return childNodes(this);
|
|
}
|
|
},
|
|
configurable: true
|
|
},
|
|
|
|
firstChild: {
|
|
/** @this {HTMLElement} */
|
|
get() {
|
|
let l = this.__shady && this.__shady.firstChild;
|
|
return l !== undefined ? l : firstChild(this);
|
|
},
|
|
configurable: true
|
|
},
|
|
|
|
lastChild: {
|
|
/** @this {HTMLElement} */
|
|
get() {
|
|
let l = this.__shady && this.__shady.lastChild;
|
|
return l !== undefined ? l : lastChild(this);
|
|
},
|
|
configurable: true
|
|
},
|
|
|
|
textContent: {
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
get() {
|
|
if (this.__shady && this.__shady.firstChild !== undefined) {
|
|
let tc = [];
|
|
for (let i = 0, cn = this.childNodes, c; (c = cn[i]); i++) {
|
|
if (c.nodeType !== Node.COMMENT_NODE) {
|
|
tc.push(c.textContent);
|
|
}
|
|
}
|
|
return tc.join('');
|
|
} else {
|
|
return textContent(this);
|
|
}
|
|
},
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
set(text) {
|
|
if (this.nodeType !== Node.ELEMENT_NODE) {
|
|
// TODO(sorvell): can't do this if patch nodeValue.
|
|
this.nodeValue = text;
|
|
} else {
|
|
clearNode(this);
|
|
if (text) {
|
|
this.appendChild(document.createTextNode(text));
|
|
}
|
|
}
|
|
},
|
|
configurable: true
|
|
},
|
|
|
|
// fragment, element, document
|
|
firstElementChild: {
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
get() {
|
|
if (this.__shady && this.__shady.firstChild !== undefined) {
|
|
let n = this.firstChild;
|
|
while (n && n.nodeType !== Node.ELEMENT_NODE) {
|
|
n = n.nextSibling;
|
|
}
|
|
return n;
|
|
} else {
|
|
return firstElementChild(this);
|
|
}
|
|
},
|
|
configurable: true
|
|
},
|
|
|
|
lastElementChild: {
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
get() {
|
|
if (this.__shady && this.__shady.lastChild !== undefined) {
|
|
let n = this.lastChild;
|
|
while (n && n.nodeType !== Node.ELEMENT_NODE) {
|
|
n = n.previousSibling;
|
|
}
|
|
return n;
|
|
} else {
|
|
return lastElementChild(this);
|
|
}
|
|
},
|
|
configurable: true
|
|
},
|
|
|
|
children: {
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
get() {
|
|
if (this.__shady && this.__shady.firstChild !== undefined) {
|
|
return Array.prototype.filter.call(this.childNodes, function(n) {
|
|
return (n.nodeType === Node.ELEMENT_NODE);
|
|
});
|
|
} else {
|
|
return children(this);
|
|
}
|
|
},
|
|
configurable: true
|
|
},
|
|
|
|
// element (HTMLElement on IE11)
|
|
innerHTML: {
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
get() {
|
|
let content = this.localName === 'template' ?
|
|
/** @type {HTMLTemplateElement} */(this).content : this;
|
|
if (this.__shady && this.__shady.firstChild !== undefined) {
|
|
return getInnerHTML(content);
|
|
} else {
|
|
return innerHTML(content);
|
|
}
|
|
},
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
set(text) {
|
|
let content = this.localName === 'template' ?
|
|
/** @type {HTMLTemplateElement} */(this).content : this;
|
|
clearNode(content);
|
|
if (nativeInnerHTMLDesc && nativeInnerHTMLDesc.set) {
|
|
nativeInnerHTMLDesc.set.call(htmlContainer, text);
|
|
} else {
|
|
htmlContainer.innerHTML = text;
|
|
}
|
|
while (htmlContainer.firstChild) {
|
|
content.appendChild(htmlContainer.firstChild);
|
|
}
|
|
},
|
|
configurable: true
|
|
}
|
|
|
|
};
|
|
|
|
// Note: Can be patched on element prototype on all browsers.
|
|
// Must be patched on instance on browsers that support native Shadow DOM
|
|
// but do not have builtin accessors (old Chrome).
|
|
let ShadowRootAccessor = {
|
|
|
|
shadowRoot: {
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
get() {
|
|
return this.__shady && this.__shady.root || null;
|
|
},
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
set(value) {
|
|
this.__shady = this.__shady || {};
|
|
this.__shady.root = value;
|
|
},
|
|
configurable: true
|
|
}
|
|
};
|
|
|
|
// Note: Can be patched on document prototype on browsers with builtin accessors.
|
|
// Must be patched separately on simulated ShadowRoot.
|
|
// Must be patched as `_activeElement` on browsers without builtin accessors.
|
|
let ActiveElementAccessor = {
|
|
|
|
activeElement: {
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
get() {
|
|
return activeElementForNode(this);
|
|
},
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
set() {},
|
|
configurable: true
|
|
}
|
|
|
|
};
|
|
|
|
// patch a group of descriptors on an object only if it exists or if the `force`
|
|
// argument is true.
|
|
/**
|
|
* @param {!Object} obj
|
|
* @param {!Object} descriptors
|
|
* @param {boolean=} force
|
|
*/
|
|
function patchAccessorGroup(obj, descriptors, force) {
|
|
for (let p in descriptors) {
|
|
let objDesc = Object.getOwnPropertyDescriptor(obj, p);
|
|
if ((objDesc && objDesc.configurable) ||
|
|
(!objDesc && force)) {
|
|
Object.defineProperty(obj, p, descriptors[p]);
|
|
} else if (force) {
|
|
console.warn('Could not define', p, 'on', obj);
|
|
}
|
|
}
|
|
}
|
|
|
|
// patch dom accessors on proto where they exist
|
|
function patchAccessors(proto) {
|
|
patchAccessorGroup(proto, OutsideAccessors);
|
|
patchAccessorGroup(proto, InsideAccessors);
|
|
patchAccessorGroup(proto, ActiveElementAccessor);
|
|
}
|
|
|
|
// ensure element descriptors (IE/Edge don't have em)
|
|
function patchShadowRootAccessors(proto) {
|
|
patchAccessorGroup(proto, InsideAccessors, true);
|
|
patchAccessorGroup(proto, ActiveElementAccessor, true);
|
|
}
|
|
|
|
// ensure an element has patched "outside" accessors; no-op when not needed
|
|
let patchOutsideElementAccessors = settings.hasDescriptors ?
|
|
function() {} : function(element) {
|
|
if (!(element.__shady && element.__shady.__outsideAccessors)) {
|
|
element.__shady = element.__shady || {};
|
|
element.__shady.__outsideAccessors = true;
|
|
patchAccessorGroup(element, OutsideAccessors, true);
|
|
}
|
|
};
|
|
|
|
// ensure an element has patched "inside" accessors; no-op when not needed
|
|
let patchInsideElementAccessors = settings.hasDescriptors ?
|
|
function() {} : function(element) {
|
|
if (!(element.__shady && element.__shady.__insideAccessors)) {
|
|
element.__shady = element.__shady || {};
|
|
element.__shady.__insideAccessors = true;
|
|
patchAccessorGroup(element, InsideAccessors, true);
|
|
patchAccessorGroup(element, ShadowRootAccessor, true);
|
|
}
|
|
};
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2016 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 recordInsertBefore(node, container, ref_node) {
|
|
patchInsideElementAccessors(container);
|
|
container.__shady = container.__shady || {};
|
|
if (container.__shady.firstChild !== undefined) {
|
|
container.__shady.childNodes = null;
|
|
}
|
|
// handle document fragments
|
|
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
let c$ = node.childNodes;
|
|
for (let i=0; i < c$.length; i++) {
|
|
linkNode(c$[i], container, ref_node);
|
|
}
|
|
// cleanup logical dom in doc fragment.
|
|
node.__shady = node.__shady || {};
|
|
let resetTo = (node.__shady.firstChild !== undefined) ? null : undefined;
|
|
node.__shady.firstChild = node.__shady.lastChild = resetTo;
|
|
node.__shady.childNodes = resetTo;
|
|
} else {
|
|
linkNode(node, container, ref_node);
|
|
}
|
|
}
|
|
|
|
function linkNode(node, container, ref_node) {
|
|
patchOutsideElementAccessors(node);
|
|
ref_node = ref_node || null;
|
|
node.__shady = node.__shady || {};
|
|
container.__shady = container.__shady || {};
|
|
if (ref_node) {
|
|
ref_node.__shady = ref_node.__shady || {};
|
|
}
|
|
// update ref_node.previousSibling <-> node
|
|
node.__shady.previousSibling = ref_node ? ref_node.__shady.previousSibling :
|
|
container.lastChild;
|
|
let ps = node.__shady.previousSibling;
|
|
if (ps && ps.__shady) {
|
|
ps.__shady.nextSibling = node;
|
|
}
|
|
// update node <-> ref_node
|
|
let ns = node.__shady.nextSibling = ref_node;
|
|
if (ns && ns.__shady) {
|
|
ns.__shady.previousSibling = node;
|
|
}
|
|
// update node <-> container
|
|
node.__shady.parentNode = container;
|
|
if (ref_node) {
|
|
if (ref_node === container.__shady.firstChild) {
|
|
container.__shady.firstChild = node;
|
|
}
|
|
} else {
|
|
container.__shady.lastChild = node;
|
|
if (!container.__shady.firstChild) {
|
|
container.__shady.firstChild = node;
|
|
}
|
|
}
|
|
// remove caching of childNodes
|
|
container.__shady.childNodes = null;
|
|
}
|
|
|
|
function recordRemoveChild(node, container) {
|
|
node.__shady = node.__shady || {};
|
|
container.__shady = container.__shady || {};
|
|
if (node === container.__shady.firstChild) {
|
|
container.__shady.firstChild = node.__shady.nextSibling;
|
|
}
|
|
if (node === container.__shady.lastChild) {
|
|
container.__shady.lastChild = node.__shady.previousSibling;
|
|
}
|
|
let p = node.__shady.previousSibling;
|
|
let n = node.__shady.nextSibling;
|
|
if (p) {
|
|
p.__shady = p.__shady || {};
|
|
p.__shady.nextSibling = n;
|
|
}
|
|
if (n) {
|
|
n.__shady = n.__shady || {};
|
|
n.__shady.previousSibling = p;
|
|
}
|
|
// When an element is removed, logical data is no longer tracked.
|
|
// Explicitly set `undefined` here to indicate this. This is disginguished
|
|
// from `null` which is set if info is null.
|
|
node.__shady.parentNode = node.__shady.previousSibling =
|
|
node.__shady.nextSibling = undefined;
|
|
if (container.__shady.childNodes !== undefined) {
|
|
// remove caching of childNodes
|
|
container.__shady.childNodes = null;
|
|
}
|
|
}
|
|
|
|
let recordChildNodes = function(node) {
|
|
if (!node.__shady || node.__shady.firstChild === undefined) {
|
|
node.__shady = node.__shady || {};
|
|
node.__shady.firstChild = firstChild(node);
|
|
node.__shady.lastChild = lastChild(node);
|
|
patchInsideElementAccessors(node);
|
|
let c$ = node.__shady.childNodes = childNodes(node);
|
|
for (let i=0, n; (i<c$.length) && (n=c$[i]); i++) {
|
|
n.__shady = n.__shady || {};
|
|
n.__shady.parentNode = node;
|
|
n.__shady.nextSibling = c$[i+1] || null;
|
|
n.__shady.previousSibling = c$[i-1] || null;
|
|
patchOutsideElementAccessors(n);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2016 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
|
|
*/
|
|
|
|
/**
|
|
* Try to add node. Record logical info, track insertion points, perform
|
|
* distribution iff needed. Return true if the add is handled.
|
|
* @param {Node} container
|
|
* @param {Node} node
|
|
* @param {Node} ref_node
|
|
* @return {boolean}
|
|
*/
|
|
function addNode(container, node, ref_node) {
|
|
let ownerRoot = ownerShadyRootForNode(container);
|
|
let ipAdded;
|
|
if (ownerRoot) {
|
|
// optimization: special insertion point tracking
|
|
// TODO(sorvell): verify that the renderPending check here should not be needed.
|
|
if (node['__noInsertionPoint'] && !ownerRoot._changePending) {
|
|
ownerRoot._skipUpdateInsertionPoints = true;
|
|
}
|
|
// note: we always need to see if an insertion point is added
|
|
// since this saves logical tree info; however, invalidation state
|
|
// needs
|
|
ipAdded = _maybeAddInsertionPoint(node, container, ownerRoot);
|
|
// invalidate insertion points IFF not already invalid!
|
|
if (ipAdded) {
|
|
ownerRoot._skipUpdateInsertionPoints = false;
|
|
}
|
|
}
|
|
if (container.__shady && container.__shady.firstChild !== undefined) {
|
|
recordInsertBefore(node, container, ref_node);
|
|
}
|
|
// if not distributing and not adding to host, do a fast path addition
|
|
// TODO(sorvell): revisit flow since `ipAdded` needed here if
|
|
// node is a fragment that has a patched QSA.
|
|
let handled = _maybeDistribute(node, container, ownerRoot, ipAdded) ||
|
|
container.__shady.root ||
|
|
// TODO(sorvell): we *should* consider the add "handled"
|
|
// if the container or ownerRoot is `_renderPending`.
|
|
// However, this will regress performance right now and is blocked on a
|
|
// fix for https://github.com/webcomponents/shadydom/issues/95
|
|
// handled if ref_node parent is a root that is rendering.
|
|
(ref_node && isShadyRoot(ref_node.parentNode) &&
|
|
ref_node.parentNode._renderPending);
|
|
return handled;
|
|
}
|
|
|
|
|
|
/**
|
|
* Try to remove node: update logical info and perform distribution iff
|
|
* needed. Return true if the removal has been handled.
|
|
* note that it's possible for both the node's host and its parent
|
|
* to require distribution... both cases are handled here.
|
|
* @param {Node} node
|
|
* @return {boolean}
|
|
*/
|
|
function removeNode(node) {
|
|
// important that we want to do this only if the node has a logical parent
|
|
let logicalParent = node.__shady && node.__shady.parentNode;
|
|
let distributed;
|
|
let ownerRoot = ownerShadyRootForNode(node);
|
|
if (logicalParent || ownerRoot) {
|
|
// distribute node's parent iff needed
|
|
distributed = maybeDistributeParent(node);
|
|
if (logicalParent) {
|
|
recordRemoveChild(node, logicalParent);
|
|
}
|
|
// remove node from root and distribute it iff needed
|
|
let removedDistributed = ownerRoot &&
|
|
_removeDistributedChildren(ownerRoot, node);
|
|
let addedInsertionPoint = (logicalParent && ownerRoot &&
|
|
logicalParent.localName === ownerRoot.getInsertionPointTag());
|
|
if (removedDistributed || addedInsertionPoint) {
|
|
ownerRoot._skipUpdateInsertionPoints = false;
|
|
updateRootViaContentChange(ownerRoot);
|
|
}
|
|
}
|
|
_removeOwnerShadyRoot(node);
|
|
return distributed;
|
|
}
|
|
|
|
/**
|
|
* @param {Node} node
|
|
* @param {Node=} addedNode
|
|
* @param {Node=} removedNode
|
|
*/
|
|
function _scheduleObserver(node, addedNode, removedNode) {
|
|
let observer = node.__shady && node.__shady.observer;
|
|
if (observer) {
|
|
if (addedNode) {
|
|
observer.addedNodes.push(addedNode);
|
|
}
|
|
if (removedNode) {
|
|
observer.removedNodes.push(removedNode);
|
|
}
|
|
observer.schedule();
|
|
}
|
|
}
|
|
|
|
function removeNodeFromParent(node, logicalParent) {
|
|
if (logicalParent) {
|
|
_scheduleObserver(logicalParent, null, node);
|
|
return removeNode(node);
|
|
} else {
|
|
// composed but not logical parent
|
|
if (node.parentNode) {
|
|
removeChild.call(node.parentNode, node);
|
|
}
|
|
_removeOwnerShadyRoot(node);
|
|
}
|
|
}
|
|
|
|
function _hasCachedOwnerRoot(node) {
|
|
return Boolean(node.__shady && node.__shady.ownerShadyRoot !== undefined);
|
|
}
|
|
|
|
/**
|
|
* @param {Node} node
|
|
* @param {Object=} options
|
|
*/
|
|
function getRootNode(node, options) { // eslint-disable-line no-unused-vars
|
|
if (!node || !node.nodeType) {
|
|
return;
|
|
}
|
|
node.__shady = node.__shady || {};
|
|
let root = node.__shady.ownerShadyRoot;
|
|
if (root === undefined) {
|
|
if (isShadyRoot(node)) {
|
|
root = node;
|
|
} else {
|
|
let parent = node.parentNode;
|
|
root = parent ? getRootNode(parent) : node;
|
|
}
|
|
// memo-ize result for performance but only memo-ize
|
|
// result if node is in the document. This avoids a problem where a root
|
|
// can be cached while an element is inside a fragment.
|
|
// If this happens and we cache the result, the value can become stale
|
|
// because for perf we avoid processing the subtree of added fragments.
|
|
if (document.documentElement.contains(node)) {
|
|
node.__shady.ownerShadyRoot = root;
|
|
}
|
|
}
|
|
return root;
|
|
}
|
|
|
|
function _maybeDistribute(node, container, ownerRoot, ipAdded) {
|
|
// TODO(sorvell): technically we should check non-fragment nodes for
|
|
// <content> children but since this case is assumed to be exceedingly
|
|
// rare, we avoid the cost and will address with some specific api
|
|
// when the need arises. For now, the user must call
|
|
// distributeContent(true), which updates insertion points manually
|
|
// and forces distribution.
|
|
let insertionPointTag = ownerRoot && ownerRoot.getInsertionPointTag() || '';
|
|
let fragContent = (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) &&
|
|
!node['__noInsertionPoint'] &&
|
|
insertionPointTag && node.querySelector(insertionPointTag);
|
|
let wrappedContent = fragContent &&
|
|
(fragContent.parentNode.nodeType !==
|
|
Node.DOCUMENT_FRAGMENT_NODE);
|
|
let hasContent = fragContent || (node.localName === insertionPointTag);
|
|
// There are 3 possible cases where a distribution may need to occur:
|
|
// 1. <content> being inserted (the host of the shady root where
|
|
// content is inserted needs distribution)
|
|
// 2. children being inserted into parent with a shady root (parent
|
|
// needs distribution)
|
|
// 3. container is an insertionPoint
|
|
if (hasContent || (container.localName === insertionPointTag) || ipAdded) {
|
|
if (ownerRoot) {
|
|
// note, insertion point list update is handled after node
|
|
// mutations are complete
|
|
updateRootViaContentChange(ownerRoot);
|
|
}
|
|
}
|
|
let needsDist = _nodeNeedsDistribution(container);
|
|
if (needsDist) {
|
|
let root = container.__shady && container.__shady.root;
|
|
updateRootViaContentChange(root);
|
|
}
|
|
// Return true when distribution will fully handle the composition
|
|
// Note that if a content was being inserted that was wrapped by a node,
|
|
// and the parent does not need distribution, return false to allow
|
|
// the nodes to be added directly, after which children may be
|
|
// distributed and composed into the wrapping node(s)
|
|
return needsDist || (hasContent && !wrappedContent);
|
|
}
|
|
|
|
/* note: parent argument is required since node may have an out
|
|
of date parent at this point; returns true if a <content> is being added */
|
|
function _maybeAddInsertionPoint(node, parent, root) {
|
|
let added;
|
|
let insertionPointTag = root.getInsertionPointTag();
|
|
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE &&
|
|
!node['__noInsertionPoint']) {
|
|
let c$ = node.querySelectorAll(insertionPointTag);
|
|
for (let i=0, n, np, na; (i<c$.length) && (n=c$[i]); i++) {
|
|
np = n.parentNode;
|
|
// don't allow node's parent to be fragment itself
|
|
if (np === node) {
|
|
np = parent;
|
|
}
|
|
na = _maybeAddInsertionPoint(n, np, root);
|
|
added = added || na;
|
|
}
|
|
} else if (node.localName === insertionPointTag) {
|
|
recordChildNodes(parent);
|
|
recordChildNodes(node);
|
|
added = true;
|
|
}
|
|
return added;
|
|
}
|
|
|
|
function _nodeNeedsDistribution(node) {
|
|
let root = node && node.__shady && node.__shady.root;
|
|
return root && root.hasInsertionPoint();
|
|
}
|
|
|
|
function _removeDistributedChildren(root, container) {
|
|
let hostNeedsDist;
|
|
let ip$ = root._getInsertionPoints();
|
|
for (let i=0; i<ip$.length; i++) {
|
|
let insertionPoint = ip$[i];
|
|
if (_contains(container, insertionPoint)) {
|
|
let dc$ = insertionPoint.assignedNodes({flatten: true});
|
|
for (let j=0; j<dc$.length; j++) {
|
|
hostNeedsDist = true;
|
|
let node = dc$[j];
|
|
let parent = parentNode(node);
|
|
if (parent) {
|
|
removeChild.call(parent, node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return hostNeedsDist;
|
|
}
|
|
|
|
function _contains(container, node) {
|
|
while (node) {
|
|
if (node == container) {
|
|
return true;
|
|
}
|
|
node = node.parentNode;
|
|
}
|
|
}
|
|
|
|
function _removeOwnerShadyRoot(node) {
|
|
// optimization: only reset the tree if node is actually in a root
|
|
if (_hasCachedOwnerRoot(node)) {
|
|
let c$ = node.childNodes;
|
|
for (let i=0, l=c$.length, n; (i<l) && (n=c$[i]); i++) {
|
|
_removeOwnerShadyRoot(n);
|
|
}
|
|
}
|
|
node.__shady = node.__shady || {};
|
|
node.__shady.ownerShadyRoot = undefined;
|
|
}
|
|
|
|
// TODO(sorvell): This will fail if distribution that affects this
|
|
// question is pending; this is expected to be exceedingly rare, but if
|
|
// the issue comes up, we can force a flush in this case.
|
|
function firstComposedNode(insertionPoint) {
|
|
let n$ = insertionPoint.assignedNodes({flatten: true});
|
|
let root = getRootNode(insertionPoint);
|
|
for (let i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) {
|
|
// means that we're composed to this spot.
|
|
if (root.isFinalDestination(insertionPoint, n)) {
|
|
return n;
|
|
}
|
|
}
|
|
}
|
|
|
|
function maybeDistributeParent(node) {
|
|
let parent = node.parentNode;
|
|
if (_nodeNeedsDistribution(parent)) {
|
|
updateRootViaContentChange(parent.__shady.root);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function updateRootViaContentChange(root) {
|
|
// mark root as mutation based on a mutation
|
|
root._changePending = true;
|
|
root.update();
|
|
}
|
|
|
|
function distributeAttributeChange(node, name) {
|
|
if (name === 'slot') {
|
|
maybeDistributeParent(node);
|
|
} else if (node.localName === 'slot' && name === 'name') {
|
|
let root = ownerShadyRootForNode(node);
|
|
if (root) {
|
|
root.update();
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: `query` is used primarily for ShadyDOM's querySelector impl,
|
|
// but it's also generally useful to recurse through the element tree
|
|
// and is used by Polymer's styling system.
|
|
/**
|
|
* @param {Node} node
|
|
* @param {Function} matcher
|
|
* @param {Function=} halter
|
|
*/
|
|
function query(node, matcher, halter) {
|
|
let list = [];
|
|
_queryElements(node.childNodes, matcher,
|
|
halter, list);
|
|
return list;
|
|
}
|
|
|
|
function _queryElements(elements, matcher, halter, list) {
|
|
for (let i=0, l=elements.length, c; (i<l) && (c=elements[i]); i++) {
|
|
if (c.nodeType === Node.ELEMENT_NODE &&
|
|
_queryElement(c, matcher, halter, list)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
function _queryElement(node, matcher, halter, list) {
|
|
let result = matcher(node);
|
|
if (result) {
|
|
list.push(node);
|
|
}
|
|
if (halter && halter(result)) {
|
|
return result;
|
|
}
|
|
_queryElements(node.childNodes, matcher,
|
|
halter, list);
|
|
}
|
|
|
|
function renderRootNode(element) {
|
|
var root = element.getRootNode();
|
|
if (isShadyRoot(root)) {
|
|
root.render();
|
|
}
|
|
}
|
|
|
|
let scopingShim = null;
|
|
|
|
function setAttribute$1(node, attr, value) {
|
|
if (!scopingShim) {
|
|
scopingShim = window['ShadyCSS'] && window['ShadyCSS']['ScopingShim'];
|
|
}
|
|
if (scopingShim && attr === 'class') {
|
|
scopingShim['setElementClass'](node, value);
|
|
} else {
|
|
setAttribute.call(node, attr, value);
|
|
distributeAttributeChange(node, attr);
|
|
}
|
|
}
|
|
|
|
function removeAttribute$1(node, attr) {
|
|
removeAttribute.call(node, attr);
|
|
distributeAttributeChange(node, attr);
|
|
}
|
|
|
|
// cases in which we may not be able to just do standard native call
|
|
// 1. container has a shadyRoot (needsDistribution IFF the shadyRoot
|
|
// has an insertion point)
|
|
// 2. container is a shadyRoot (don't distribute, instead set
|
|
// container to container.host.
|
|
// 3. node is <content> (host of container needs distribution)
|
|
/**
|
|
* @param {Node} parent
|
|
* @param {Node} node
|
|
* @param {Node=} ref_node
|
|
*/
|
|
function insertBefore$1(parent, node, ref_node) {
|
|
if (ref_node) {
|
|
let p = ref_node.__shady && ref_node.__shady.parentNode;
|
|
if ((p !== undefined && p !== parent) ||
|
|
(p === undefined && parentNode(ref_node) !== parent)) {
|
|
throw Error(`Failed to execute 'insertBefore' on 'Node': The node ` +
|
|
`before which the new node is to be inserted is not a child of this node.`);
|
|
}
|
|
}
|
|
if (ref_node === node) {
|
|
return node;
|
|
}
|
|
// remove node from its current position iff it's in a tree.
|
|
if (node.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) {
|
|
let parent = node.__shady && node.__shady.parentNode;
|
|
removeNodeFromParent(node, parent);
|
|
}
|
|
if (!addNode(parent, node, ref_node)) {
|
|
if (ref_node) {
|
|
// if ref_node is an insertion point replace with first distributed node
|
|
let root = ownerShadyRootForNode(ref_node);
|
|
if (root) {
|
|
ref_node = ref_node.localName === root.getInsertionPointTag() ?
|
|
firstComposedNode(/** @type {!HTMLSlotElement} */(ref_node)) : ref_node;
|
|
}
|
|
}
|
|
// if adding to a shadyRoot, add to host instead
|
|
let container = isShadyRoot(parent) ? /** @type {ShadowRoot} */(parent).host : parent;
|
|
if (ref_node) {
|
|
insertBefore.call(container, node, ref_node);
|
|
} else {
|
|
appendChild.call(container, node);
|
|
}
|
|
}
|
|
_scheduleObserver(parent, node);
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
Removes the given `node` from the element's `lightChildren`.
|
|
This method also performs dom composition.
|
|
*/
|
|
function removeChild$1(parent, node) {
|
|
if (node.parentNode !== parent) {
|
|
throw Error('The node to be removed is not a child of this node: ' +
|
|
node);
|
|
}
|
|
if (!removeNode(node)) {
|
|
// if removing from a shadyRoot, remove form host instead
|
|
let container = isShadyRoot(parent) ?
|
|
parent.host :
|
|
parent;
|
|
// not guaranteed to physically be in container; e.g.
|
|
// undistributed nodes.
|
|
let nativeParent = parentNode(node);
|
|
if (container === nativeParent) {
|
|
removeChild.call(container, node);
|
|
}
|
|
}
|
|
_scheduleObserver(parent, null, node);
|
|
return node;
|
|
}
|
|
|
|
function cloneNode$1(node, deep) {
|
|
if (node.localName == 'template') {
|
|
return cloneNode.call(node, deep);
|
|
} else {
|
|
let n = cloneNode.call(node, false);
|
|
if (deep) {
|
|
let c$ = node.childNodes;
|
|
for (let i=0, nc; i < c$.length; i++) {
|
|
nc = c$[i].cloneNode(true);
|
|
n.appendChild(nc);
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
}
|
|
|
|
// note: Though not technically correct, we fast path `importNode`
|
|
// when called on a node not owned by the main document.
|
|
// This allows, for example, elements that cannot
|
|
// contain custom elements and are therefore not likely to contain shadowRoots
|
|
// to cloned natively. This is a fairly significant performance win.
|
|
function importNode$1(node, deep) {
|
|
if (node.ownerDocument !== document) {
|
|
return importNode.call(document, node, deep);
|
|
}
|
|
let n = importNode.call(document, node, false);
|
|
if (deep) {
|
|
let c$ = node.childNodes;
|
|
for (let i=0, nc; i < c$.length; i++) {
|
|
nc = importNode$1(c$[i], true);
|
|
n.appendChild(nc);
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2016 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
|
|
*/
|
|
|
|
// https://github.com/w3c/webcomponents/issues/513#issuecomment-224183937
|
|
let alwaysComposed = {
|
|
'focusin': true,
|
|
'focusout': true,
|
|
'click': true,
|
|
'dblclick': true,
|
|
'mousedown': true,
|
|
'mouseenter': true,
|
|
'mouseleave': true,
|
|
'mousemove': true,
|
|
'mouseout': true,
|
|
'mouseover': true,
|
|
'mouseup': true,
|
|
'wheel': true,
|
|
'beforeinput': true,
|
|
'input': true,
|
|
'keydown': true,
|
|
'keyup': true,
|
|
'compositionstart': true,
|
|
'compositionupdate': true,
|
|
'compositionend': true,
|
|
'touchstart': true,
|
|
'touchend': true,
|
|
'touchmove': true,
|
|
'touchcancel': true,
|
|
'pointerover': true,
|
|
'pointerenter': true,
|
|
'pointerdown': true,
|
|
'pointermove': true,
|
|
'pointerup': true,
|
|
'pointercancel': true,
|
|
'pointerout': true,
|
|
'pointerleave': true,
|
|
'gotpointercapture': true,
|
|
'lostpointercapture': true,
|
|
'dragstart': true,
|
|
'drag': true,
|
|
'dragenter': true,
|
|
'dragleave': true,
|
|
'dragover': true,
|
|
'drop': true,
|
|
'dragend': true,
|
|
'DOMActivate': true,
|
|
'DOMFocusIn': true,
|
|
'DOMFocusOut': true,
|
|
'keypress': true
|
|
};
|
|
|
|
function pathComposer(startNode, composed) {
|
|
let composedPath = [];
|
|
let current = startNode;
|
|
let startRoot = startNode === window ? window : startNode.getRootNode();
|
|
while (current) {
|
|
composedPath.push(current);
|
|
if (current.assignedSlot) {
|
|
current = current.assignedSlot;
|
|
} else if (current.nodeType === Node.DOCUMENT_FRAGMENT_NODE && current.host && (composed || current !== startRoot)) {
|
|
current = current.host;
|
|
} else {
|
|
current = current.parentNode;
|
|
}
|
|
}
|
|
// event composedPath includes window when startNode's ownerRoot is document
|
|
if (composedPath[composedPath.length - 1] === document) {
|
|
composedPath.push(window);
|
|
}
|
|
return composedPath;
|
|
}
|
|
|
|
function retarget(refNode, path) {
|
|
if (!isShadyRoot) {
|
|
return refNode;
|
|
}
|
|
// If ANCESTOR's root is not a shadow root or ANCESTOR's root is BASE's
|
|
// shadow-including inclusive ancestor, return ANCESTOR.
|
|
let refNodePath = pathComposer(refNode, true);
|
|
let p$ = path;
|
|
for (let i=0, ancestor, lastRoot, root, rootIdx; i < p$.length; i++) {
|
|
ancestor = p$[i];
|
|
root = ancestor === window ? window : ancestor.getRootNode();
|
|
if (root !== lastRoot) {
|
|
rootIdx = refNodePath.indexOf(root);
|
|
lastRoot = root;
|
|
}
|
|
if (!isShadyRoot(root) || rootIdx > -1) {
|
|
return ancestor;
|
|
}
|
|
}
|
|
}
|
|
|
|
let eventMixin = {
|
|
|
|
/**
|
|
* @this {Event}
|
|
*/
|
|
get composed() {
|
|
// isTrusted may not exist in this browser, so just check if isTrusted is explicitly false
|
|
if (this.isTrusted !== false && this.__composed === undefined) {
|
|
this.__composed = alwaysComposed[this.type];
|
|
}
|
|
return this.__composed || false;
|
|
},
|
|
|
|
/**
|
|
* @this {Event}
|
|
*/
|
|
composedPath() {
|
|
if (!this.__composedPath) {
|
|
this.__composedPath = pathComposer(this['__target'], this.composed);
|
|
}
|
|
return this.__composedPath;
|
|
},
|
|
|
|
/**
|
|
* @this {Event}
|
|
*/
|
|
get target() {
|
|
return retarget(this.currentTarget, this.composedPath());
|
|
},
|
|
|
|
// http://w3c.github.io/webcomponents/spec/shadow/#event-relatedtarget-retargeting
|
|
/**
|
|
* @this {Event}
|
|
*/
|
|
get relatedTarget() {
|
|
if (!this.__relatedTarget) {
|
|
return null;
|
|
}
|
|
if (!this.__relatedTargetComposedPath) {
|
|
this.__relatedTargetComposedPath = pathComposer(this.__relatedTarget, true);
|
|
}
|
|
// find the deepest node in relatedTarget composed path that is in the same root with the currentTarget
|
|
return retarget(this.currentTarget, this.__relatedTargetComposedPath);
|
|
},
|
|
/**
|
|
* @this {Event}
|
|
*/
|
|
stopPropagation() {
|
|
Event.prototype.stopPropagation.call(this);
|
|
this.__propagationStopped = true;
|
|
},
|
|
/**
|
|
* @this {Event}
|
|
*/
|
|
stopImmediatePropagation() {
|
|
Event.prototype.stopImmediatePropagation.call(this);
|
|
this.__immediatePropagationStopped = true;
|
|
this.__propagationStopped = true;
|
|
}
|
|
|
|
};
|
|
|
|
function mixinComposedFlag(Base) {
|
|
// NOTE: avoiding use of `class` here so that transpiled output does not
|
|
// try to do `Base.call` with a dom construtor.
|
|
let klazz = function(type, options) {
|
|
let event = new Base(type, options);
|
|
event.__composed = options && Boolean(options['composed']);
|
|
return event;
|
|
};
|
|
// put constructor properties on subclass
|
|
mixin(klazz, Base);
|
|
klazz.prototype = Base.prototype;
|
|
return klazz;
|
|
}
|
|
|
|
let nonBubblingEventsToRetarget = {
|
|
'focus': true,
|
|
'blur': true
|
|
};
|
|
|
|
|
|
function fireHandlers(event, node, phase) {
|
|
let hs = node.__handlers && node.__handlers[event.type] &&
|
|
node.__handlers[event.type][phase];
|
|
if (hs) {
|
|
for (let i = 0, fn; (fn = hs[i]); i++) {
|
|
fn.call(node, event);
|
|
if (event.__immediatePropagationStopped) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function retargetNonBubblingEvent(e) {
|
|
let path = e.composedPath();
|
|
let node;
|
|
// override `currentTarget` to let patched `target` calculate correctly
|
|
Object.defineProperty(e, 'currentTarget', {
|
|
get: function() {
|
|
return node;
|
|
},
|
|
configurable: true
|
|
});
|
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
node = path[i];
|
|
// capture phase fires all capture handlers
|
|
fireHandlers(e, node, 'capture');
|
|
if (e.__propagationStopped) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// set the event phase to `AT_TARGET` as in spec
|
|
Object.defineProperty(e, 'eventPhase', {value: Event.AT_TARGET});
|
|
|
|
// the event only needs to be fired when owner roots change when iterating the event path
|
|
// keep track of the last seen owner root
|
|
let lastFiredRoot;
|
|
for (let i = 0; i < path.length; i++) {
|
|
node = path[i];
|
|
if (i === 0 || (node.shadowRoot && node.shadowRoot === lastFiredRoot)) {
|
|
fireHandlers(e, node, 'bubble');
|
|
// don't bother with window, it doesn't have `getRootNode` and will be last in the path anyway
|
|
if (node !== window) {
|
|
lastFiredRoot = node.getRootNode();
|
|
}
|
|
if (e.__propagationStopped) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @this {Event}
|
|
*/
|
|
function addEventListener$1(type, fn, optionsOrCapture) {
|
|
if (!fn) {
|
|
return;
|
|
}
|
|
|
|
// The callback `fn` might be used for multiple nodes/events. Since we generate
|
|
// a wrapper function, we need to keep track of it when we remove the listener.
|
|
// It's more efficient to store the node/type/options information as Array in
|
|
// `fn` itself rather than the node (we assume that the same callback is used
|
|
// for few nodes at most, whereas a node will likely have many event listeners).
|
|
// NOTE(valdrin) invoking external functions is costly, inline has better perf.
|
|
let capture, once, passive;
|
|
if (typeof optionsOrCapture === 'object') {
|
|
capture = Boolean(optionsOrCapture.capture);
|
|
once = Boolean(optionsOrCapture.once);
|
|
passive = Boolean(optionsOrCapture.passive);
|
|
} else {
|
|
capture = Boolean(optionsOrCapture);
|
|
once = false;
|
|
passive = false;
|
|
}
|
|
if (fn.__eventWrappers) {
|
|
// Stop if the wrapper function has already been created.
|
|
for (let i = 0; i < fn.__eventWrappers.length; i++) {
|
|
if (fn.__eventWrappers[i].node === this &&
|
|
fn.__eventWrappers[i].type === type &&
|
|
fn.__eventWrappers[i].capture === capture &&
|
|
fn.__eventWrappers[i].once === once &&
|
|
fn.__eventWrappers[i].passive === passive) {
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
fn.__eventWrappers = [];
|
|
}
|
|
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
const wrapperFn = function(e) {
|
|
// Support `once` option.
|
|
if (once) {
|
|
this.removeEventListener(type, fn, optionsOrCapture);
|
|
}
|
|
if (!e['__target']) {
|
|
patchEvent(e);
|
|
}
|
|
// There are two critera that should stop events from firing on this node
|
|
// 1. the event is not composed and the current node is not in the same root as the target
|
|
// 2. when bubbling, if after retargeting, relatedTarget and target point to the same node
|
|
if (e.composed || e.composedPath().indexOf(this) > -1) {
|
|
if (e.eventPhase === Event.BUBBLING_PHASE) {
|
|
if (e.target === e.relatedTarget) {
|
|
e.stopImmediatePropagation();
|
|
return;
|
|
}
|
|
}
|
|
return fn(e);
|
|
}
|
|
};
|
|
// Store the wrapper information.
|
|
fn.__eventWrappers.push({
|
|
node: this,
|
|
type: type,
|
|
capture: capture,
|
|
once: once,
|
|
passive: passive,
|
|
wrapperFn: wrapperFn
|
|
});
|
|
|
|
if (nonBubblingEventsToRetarget[type]) {
|
|
this.__handlers = this.__handlers || {};
|
|
this.__handlers[type] = this.__handlers[type] ||
|
|
{'capture': [], 'bubble': []};
|
|
this.__handlers[type][capture ? 'capture' : 'bubble'].push(wrapperFn);
|
|
} else {
|
|
addEventListener.call(this, type, wrapperFn, optionsOrCapture);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @this {Event}
|
|
*/
|
|
function removeEventListener$1(type, fn, optionsOrCapture) {
|
|
if (!fn) {
|
|
return;
|
|
}
|
|
|
|
// NOTE(valdrin) invoking external functions is costly, inline has better perf.
|
|
let capture, once, passive;
|
|
if (typeof optionsOrCapture === 'object') {
|
|
capture = Boolean(optionsOrCapture.capture);
|
|
once = Boolean(optionsOrCapture.once);
|
|
passive = Boolean(optionsOrCapture.passive);
|
|
} else {
|
|
capture = Boolean(optionsOrCapture);
|
|
once = false;
|
|
passive = false;
|
|
}
|
|
// Search the wrapped function.
|
|
let wrapperFn = undefined;
|
|
if (fn.__eventWrappers) {
|
|
for (let i = 0; i < fn.__eventWrappers.length; i++) {
|
|
if (fn.__eventWrappers[i].node === this &&
|
|
fn.__eventWrappers[i].type === type &&
|
|
fn.__eventWrappers[i].capture === capture &&
|
|
fn.__eventWrappers[i].once === once &&
|
|
fn.__eventWrappers[i].passive === passive) {
|
|
wrapperFn = fn.__eventWrappers.splice(i, 1)[0].wrapperFn;
|
|
// Cleanup.
|
|
if (!fn.__eventWrappers.length) {
|
|
fn.__eventWrappers = undefined;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
removeEventListener.call(this, type, wrapperFn || fn, optionsOrCapture);
|
|
if (wrapperFn && nonBubblingEventsToRetarget[type] &&
|
|
this.__handlers && this.__handlers[type]) {
|
|
const arr = this.__handlers[type][capture ? 'capture' : 'bubble'];
|
|
const idx = arr.indexOf(wrapperFn);
|
|
if (idx > -1) {
|
|
arr.splice(idx, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
function activateFocusEventOverrides() {
|
|
for (let ev in nonBubblingEventsToRetarget) {
|
|
window.addEventListener(ev, function(e) {
|
|
if (!e['__target']) {
|
|
patchEvent(e);
|
|
retargetNonBubblingEvent(e);
|
|
e.stopImmediatePropagation();
|
|
}
|
|
}, true);
|
|
}
|
|
}
|
|
|
|
function patchEvent(event) {
|
|
event['__target'] = event.target;
|
|
event.__relatedTarget = event.relatedTarget;
|
|
// patch event prototype if we can
|
|
if (settings.hasDescriptors) {
|
|
patchPrototype(event, eventMixin);
|
|
// and fallback to patching instance
|
|
} else {
|
|
extend(event, eventMixin);
|
|
}
|
|
}
|
|
|
|
let PatchedEvent = mixinComposedFlag(window.Event);
|
|
let PatchedCustomEvent = mixinComposedFlag(window.CustomEvent);
|
|
let PatchedMouseEvent = mixinComposedFlag(window.MouseEvent);
|
|
|
|
function patchEvents() {
|
|
window.Event = PatchedEvent;
|
|
window.CustomEvent = PatchedCustomEvent;
|
|
window.MouseEvent = PatchedMouseEvent;
|
|
activateFocusEventOverrides();
|
|
}
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2016 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 newSplice(index, removed, addedCount) {
|
|
return {
|
|
index: index,
|
|
removed: removed,
|
|
addedCount: addedCount
|
|
};
|
|
}
|
|
|
|
const EDIT_LEAVE = 0;
|
|
const EDIT_UPDATE = 1;
|
|
const EDIT_ADD = 2;
|
|
const EDIT_DELETE = 3;
|
|
|
|
// 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.
|
|
function calcEditDistances(current, currentStart, currentEnd,
|
|
old, oldStart, oldEnd) {
|
|
// "Deletion" columns
|
|
let rowCount = oldEnd - oldStart + 1;
|
|
let columnCount = currentEnd - currentStart + 1;
|
|
let distances = new Array(rowCount);
|
|
|
|
// "Addition" rows. Initialize null column.
|
|
for (let i = 0; i < rowCount; i++) {
|
|
distances[i] = new Array(columnCount);
|
|
distances[i][0] = i;
|
|
}
|
|
|
|
// Initialize null row
|
|
for (let j = 0; j < columnCount; j++)
|
|
distances[0][j] = j;
|
|
|
|
for (let i = 1; i < rowCount; i++) {
|
|
for (let j = 1; j < columnCount; j++) {
|
|
if (equals(current[currentStart + j - 1], old[oldStart + i - 1]))
|
|
distances[i][j] = distances[i - 1][j - 1];
|
|
else {
|
|
let north = distances[i - 1][j] + 1;
|
|
let 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.
|
|
function spliceOperationsFromEditDistances(distances) {
|
|
let i = distances.length - 1;
|
|
let j = distances[0].length - 1;
|
|
let current = distances[i][j];
|
|
let edits = [];
|
|
while (i > 0 || j > 0) {
|
|
if (i == 0) {
|
|
edits.push(EDIT_ADD);
|
|
j--;
|
|
continue;
|
|
}
|
|
if (j == 0) {
|
|
edits.push(EDIT_DELETE);
|
|
i--;
|
|
continue;
|
|
}
|
|
let northWest = distances[i - 1][j - 1];
|
|
let west = distances[i - 1][j];
|
|
let north = distances[i][j - 1];
|
|
|
|
let 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
|
|
*/
|
|
function calcSplices(current, currentStart, currentEnd,
|
|
old, oldStart, oldEnd) {
|
|
let prefixCount = 0;
|
|
let suffixCount = 0;
|
|
let splice;
|
|
|
|
let minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart);
|
|
if (currentStart == 0 && oldStart == 0)
|
|
prefixCount = sharedPrefix(current, old, minLength);
|
|
|
|
if (currentEnd == current.length && oldEnd == old.length)
|
|
suffixCount = sharedSuffix(current, old, minLength - prefixCount);
|
|
|
|
currentStart += prefixCount;
|
|
oldStart += prefixCount;
|
|
currentEnd -= suffixCount;
|
|
oldEnd -= suffixCount;
|
|
|
|
if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0)
|
|
return [];
|
|
|
|
if (currentStart == currentEnd) {
|
|
splice = newSplice(currentStart, [], 0);
|
|
while (oldStart < oldEnd)
|
|
splice.removed.push(old[oldStart++]);
|
|
|
|
return [ splice ];
|
|
} else if (oldStart == oldEnd)
|
|
return [ newSplice(currentStart, [], currentEnd - currentStart) ];
|
|
|
|
let ops = spliceOperationsFromEditDistances(
|
|
calcEditDistances(current, currentStart, currentEnd,
|
|
old, oldStart, oldEnd));
|
|
|
|
splice = undefined;
|
|
let splices = [];
|
|
let index = currentStart;
|
|
let oldIndex = oldStart;
|
|
for (let 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;
|
|
}
|
|
|
|
function sharedPrefix(current, old, searchLength) {
|
|
for (let i = 0; i < searchLength; i++)
|
|
if (!equals(current[i], old[i]))
|
|
return i;
|
|
return searchLength;
|
|
}
|
|
|
|
function sharedSuffix(current, old, searchLength) {
|
|
let index1 = current.length;
|
|
let index2 = old.length;
|
|
let count = 0;
|
|
while (count < searchLength && equals(current[--index1], old[--index2]))
|
|
count++;
|
|
|
|
return count;
|
|
}
|
|
|
|
function equals(currentValue, previousValue) {
|
|
return currentValue === previousValue;
|
|
}
|
|
|
|
function calculateSplices(current, previous) {
|
|
return calcSplices(current, 0, current.length, previous, 0,
|
|
previous.length);
|
|
}
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2016 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
|
|
*/
|
|
|
|
// NOTE: normalize event contruction where necessary (IE11)
|
|
let NormalizedEvent = typeof Event === 'function' ? Event :
|
|
function(inType, params) {
|
|
params = params || {};
|
|
var e = document.createEvent('Event');
|
|
e.initEvent(inType, Boolean(params.bubbles), Boolean(params.cancelable));
|
|
return e;
|
|
};
|
|
|
|
var Distributor = class {
|
|
|
|
constructor(root) {
|
|
this.root = root;
|
|
this.insertionPointTag = 'slot';
|
|
}
|
|
|
|
getInsertionPoints() {
|
|
return this.root.querySelectorAll(this.insertionPointTag);
|
|
}
|
|
|
|
isInsertionPoint(node) {
|
|
return node.localName && node.localName == this.insertionPointTag;
|
|
}
|
|
|
|
distribute() {
|
|
if (this.root.hasInsertionPoint()) {
|
|
return this.distributePool(this.root, this.collectPool());
|
|
}
|
|
return [];
|
|
}
|
|
|
|
// Gather the pool of nodes that should be distributed. We will combine
|
|
// these with the "content root" to arrive at the composed tree.
|
|
collectPool() {
|
|
let host = this.root.host;
|
|
let pool=[], i=0;
|
|
for (let n=host.firstChild; n; n=n.nextSibling) {
|
|
pool[i++] = n;
|
|
}
|
|
return pool;
|
|
}
|
|
|
|
// perform "logical" distribution; note, no actual dom is moved here,
|
|
// instead elements are distributed into storage
|
|
// array where applicable.
|
|
distributePool(node, pool) {
|
|
let dirtyRoots = [];
|
|
let p$ = this.root._getInsertionPoints();
|
|
for (let i=0, l=p$.length, p; (i<l) && (p=p$[i]); i++) {
|
|
this.distributeInsertionPoint(p, pool);
|
|
// provoke redistribution on insertion point parents
|
|
// must do this on all candidate hosts since distribution in this
|
|
// scope invalidates their distribution.
|
|
// only get logical parent.
|
|
let parent = p.parentNode;
|
|
let root = parent && parent.__shady && parent.__shady.root;
|
|
if (root && root.hasInsertionPoint()) {
|
|
dirtyRoots.push(root);
|
|
}
|
|
}
|
|
for (let i=0; i < pool.length; i++) {
|
|
let p = pool[i];
|
|
if (p) {
|
|
p.__shady = p.__shady || {};
|
|
p.__shady.assignedSlot = undefined;
|
|
// remove undistributed elements from physical dom.
|
|
let parent = parentNode(p);
|
|
if (parent) {
|
|
removeChild.call(parent, p);
|
|
}
|
|
}
|
|
}
|
|
return dirtyRoots;
|
|
}
|
|
|
|
distributeInsertionPoint(insertionPoint, pool) {
|
|
let prevAssignedNodes = insertionPoint.__shady.assignedNodes;
|
|
if (prevAssignedNodes) {
|
|
this.clearAssignedSlots(insertionPoint, true);
|
|
}
|
|
insertionPoint.__shady.assignedNodes = [];
|
|
let needsSlotChange = false;
|
|
// distribute nodes from the pool that this selector matches
|
|
let anyDistributed = false;
|
|
for (let i=0, l=pool.length, node; i < l; i++) {
|
|
node=pool[i];
|
|
// skip nodes that were already used
|
|
if (!node) {
|
|
continue;
|
|
}
|
|
// distribute this node if it matches
|
|
if (this.matchesInsertionPoint(node, insertionPoint)) {
|
|
if (node.__shady._prevAssignedSlot != insertionPoint) {
|
|
needsSlotChange = true;
|
|
}
|
|
this.distributeNodeInto(node, insertionPoint);
|
|
// remove this node from the pool
|
|
pool[i] = undefined;
|
|
// since at least one node matched, we won't need fallback content
|
|
anyDistributed = true;
|
|
}
|
|
}
|
|
// Fallback content if nothing was distributed here
|
|
if (!anyDistributed) {
|
|
let children$$1 = insertionPoint.childNodes;
|
|
for (let j = 0, node; j < children$$1.length; j++) {
|
|
node = children$$1[j];
|
|
if (node.__shady._prevAssignedSlot != insertionPoint) {
|
|
needsSlotChange = true;
|
|
}
|
|
this.distributeNodeInto(node, insertionPoint);
|
|
}
|
|
}
|
|
// we're already dirty if a node was newly added to the slot
|
|
// and we're also dirty if the assigned count decreased.
|
|
if (prevAssignedNodes) {
|
|
// TODO(sorvell): the tracking of previously assigned slots
|
|
// could instead by done with a Set and then we could
|
|
// avoid needing to iterate here to clear the info.
|
|
for (let i=0; i < prevAssignedNodes.length; i++) {
|
|
prevAssignedNodes[i].__shady._prevAssignedSlot = null;
|
|
}
|
|
if (insertionPoint.__shady.assignedNodes.length < prevAssignedNodes.length) {
|
|
needsSlotChange = true;
|
|
}
|
|
}
|
|
this.setDistributedNodesOnInsertionPoint(insertionPoint);
|
|
if (needsSlotChange) {
|
|
this._fireSlotChange(insertionPoint);
|
|
}
|
|
}
|
|
|
|
clearAssignedSlots(slot, savePrevious) {
|
|
let n$ = slot.__shady.assignedNodes;
|
|
if (n$) {
|
|
for (let i=0; i < n$.length; i++) {
|
|
let n = n$[i];
|
|
if (savePrevious) {
|
|
n.__shady._prevAssignedSlot = n.__shady.assignedSlot;
|
|
}
|
|
// only clear if it was previously set to this slot;
|
|
// this helps ensure that if the node has otherwise been distributed
|
|
// ignore it.
|
|
if (n.__shady.assignedSlot === slot) {
|
|
n.__shady.assignedSlot = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
matchesInsertionPoint(node, insertionPoint) {
|
|
let slotName = insertionPoint.getAttribute('name');
|
|
slotName = slotName ? slotName.trim() : '';
|
|
let slot = node.getAttribute && node.getAttribute('slot');
|
|
slot = slot ? slot.trim() : '';
|
|
return (slot == slotName);
|
|
}
|
|
|
|
distributeNodeInto(child, insertionPoint) {
|
|
insertionPoint.__shady.assignedNodes.push(child);
|
|
child.__shady.assignedSlot = insertionPoint;
|
|
}
|
|
|
|
setDistributedNodesOnInsertionPoint(insertionPoint) {
|
|
let n$ = insertionPoint.__shady.assignedNodes;
|
|
insertionPoint.__shady.distributedNodes = [];
|
|
for (let i=0, n; (i<n$.length) && (n=n$[i]) ; i++) {
|
|
if (this.isInsertionPoint(n)) {
|
|
let d$ = n.__shady.distributedNodes;
|
|
if (d$) {
|
|
for (let j=0; j < d$.length; j++) {
|
|
insertionPoint.__shady.distributedNodes.push(d$[j]);
|
|
}
|
|
}
|
|
} else {
|
|
insertionPoint.__shady.distributedNodes.push(n$[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
_fireSlotChange(insertionPoint) {
|
|
// NOTE: cannot bubble correctly here so not setting bubbles: true
|
|
// Safari tech preview does not bubble but chrome does
|
|
// Spec says it bubbles (https://dom.spec.whatwg.org/#mutation-observers)
|
|
insertionPoint.dispatchEvent(new NormalizedEvent('slotchange'));
|
|
if (insertionPoint.__shady.assignedSlot) {
|
|
this._fireSlotChange(insertionPoint.__shady.assignedSlot);
|
|
}
|
|
}
|
|
|
|
isFinalDestination(insertionPoint) {
|
|
return !(insertionPoint.__shady.assignedSlot);
|
|
}
|
|
|
|
};
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2016 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
|
|
*/
|
|
|
|
// Do not export this object. It must be passed as the first argument to the
|
|
// ShadyRoot constructor in `attachShadow` to prevent the constructor from
|
|
// throwing. This prevents the user from being able to manually construct a
|
|
// ShadyRoot (i.e. `new ShadowRoot()`).
|
|
const ShadyRootConstructionToken = {};
|
|
|
|
/**
|
|
* @constructor
|
|
* @extends {ShadowRoot}
|
|
*/
|
|
let ShadyRoot = function(token, host) {
|
|
if (token !== ShadyRootConstructionToken) {
|
|
throw new TypeError('Illegal constructor');
|
|
}
|
|
// NOTE: this strange construction is necessary because
|
|
// DocumentFragment cannot be subclassed on older browsers.
|
|
let shadowRoot = document.createDocumentFragment();
|
|
shadowRoot.__proto__ = ShadyRoot.prototype;
|
|
/** @type {ShadyRoot} */ (shadowRoot)._init(host);
|
|
return shadowRoot;
|
|
};
|
|
|
|
ShadyRoot.prototype = Object.create(DocumentFragment.prototype);
|
|
|
|
ShadyRoot.prototype._init = function(host) {
|
|
// NOTE: set a fake local name so this element can be
|
|
// distinguished from a DocumentFragment when patching.
|
|
// FF doesn't allow this to be `localName`
|
|
this.__localName = 'ShadyRoot';
|
|
// logical dom setup
|
|
recordChildNodes(host);
|
|
recordChildNodes(this);
|
|
// root <=> host
|
|
host.shadowRoot = this;
|
|
this.host = host;
|
|
// state flags
|
|
this._renderPending = false;
|
|
this._hasRendered = false;
|
|
this._changePending = false;
|
|
this._distributor = new Distributor(this);
|
|
this.update();
|
|
};
|
|
|
|
|
|
// async render
|
|
ShadyRoot.prototype.update = function() {
|
|
if (!this._renderPending) {
|
|
this._renderPending = true;
|
|
enqueue(() => this.render());
|
|
}
|
|
};
|
|
|
|
// returns the oldest renderPending ancestor root.
|
|
ShadyRoot.prototype._getRenderRoot = function() {
|
|
let renderRoot = this;
|
|
let root = this;
|
|
while (root) {
|
|
if (root._renderPending) {
|
|
renderRoot = root;
|
|
}
|
|
root = root._rendererForHost();
|
|
}
|
|
return renderRoot;
|
|
};
|
|
|
|
// Returns the shadyRoot `this.host` if `this.host`
|
|
// has children that require distribution.
|
|
ShadyRoot.prototype._rendererForHost = function() {
|
|
let root = this.host.getRootNode();
|
|
if (isShadyRoot(root)) {
|
|
let c$ = this.host.childNodes;
|
|
for (let i=0, c; i < c$.length; i++) {
|
|
c = c$[i];
|
|
if (this._distributor.isInsertionPoint(c)) {
|
|
return root;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
ShadyRoot.prototype.render = function() {
|
|
if (this._renderPending) {
|
|
this._getRenderRoot()['_render']();
|
|
}
|
|
};
|
|
|
|
// NOTE: avoid renaming to ease testability.
|
|
ShadyRoot.prototype['_render'] = function() {
|
|
this._renderPending = false;
|
|
this._changePending = false;
|
|
if (!this._skipUpdateInsertionPoints) {
|
|
this.updateInsertionPoints();
|
|
} else if (!this._hasRendered) {
|
|
this.__insertionPoints = [];
|
|
}
|
|
this._skipUpdateInsertionPoints = false;
|
|
// TODO(sorvell): can add a first render optimization here
|
|
// to use if there are no insertion points
|
|
// 1. clear host node of composed children
|
|
// 2. appendChild the shadowRoot itself or (more robust) its logical children
|
|
// NOTE: this didn't seem worth it in perf testing
|
|
// but not ready to delete this info.
|
|
// logical
|
|
this.distribute();
|
|
// physical
|
|
this.compose();
|
|
this._hasRendered = true;
|
|
};
|
|
|
|
ShadyRoot.prototype.forceRender = function() {
|
|
this._renderPending = true;
|
|
this.render();
|
|
};
|
|
|
|
ShadyRoot.prototype.distribute = function() {
|
|
let dirtyRoots = this._distributor.distribute();
|
|
for (let i=0; i<dirtyRoots.length; i++) {
|
|
dirtyRoots[i]['_render']();
|
|
}
|
|
};
|
|
|
|
ShadyRoot.prototype.updateInsertionPoints = function() {
|
|
let i$ = this._insertionPoints;
|
|
// if any insertion points have been removed, clear their distribution info
|
|
if (i$) {
|
|
for (let i=0, c; i < i$.length; i++) {
|
|
c = i$[i];
|
|
if (c.getRootNode() !== this) {
|
|
this._distributor.clearAssignedSlots(c);
|
|
}
|
|
}
|
|
}
|
|
i$ = this._insertionPoints = this._distributor.getInsertionPoints();
|
|
// ensure insertionPoints's and their parents have logical dom info.
|
|
// save logical tree info
|
|
// a. for shadyRoot
|
|
// b. for insertion points (fallback)
|
|
// c. for parents of insertion points
|
|
for (let i=0, c; i < i$.length; i++) {
|
|
c = i$[i];
|
|
c.__shady = c.__shady || {};
|
|
recordChildNodes(c);
|
|
recordChildNodes(c.parentNode);
|
|
}
|
|
};
|
|
|
|
ShadyRoot.prototype.compose = function() {
|
|
// compose self
|
|
// note: it's important to mark this clean before distribution
|
|
// so that attachment that provokes additional distribution (e.g.
|
|
// adding something to your parentNode) works
|
|
this._composeTree();
|
|
// TODO(sorvell): See fast paths here in Polymer v1
|
|
// (these seem unnecessary)
|
|
};
|
|
|
|
// Reify dom such that it is at its correct rendering position
|
|
// based on logical distribution.
|
|
ShadyRoot.prototype._composeTree = function() {
|
|
this._updateChildNodes(this.host, this._composeNode(this.host));
|
|
let p$ = this._getInsertionPoints();
|
|
for (let i=0, l=p$.length, p, parent; (i<l) && (p=p$[i]); i++) {
|
|
parent = p.parentNode;
|
|
if ((parent !== this.host) && (parent !== this)) {
|
|
this._updateChildNodes(parent, this._composeNode(parent));
|
|
}
|
|
}
|
|
};
|
|
|
|
// Returns the list of nodes which should be rendered inside `node`.
|
|
ShadyRoot.prototype._composeNode = function(node) {
|
|
let children$$1 = [];
|
|
let c$ = ((node.__shady && node.__shady.root) || node).childNodes;
|
|
for (let i = 0; i < c$.length; i++) {
|
|
let child = c$[i];
|
|
if (this._distributor.isInsertionPoint(child)) {
|
|
let distributedNodes = child.__shady.distributedNodes ||
|
|
(child.__shady.distributedNodes = []);
|
|
for (let j = 0; j < distributedNodes.length; j++) {
|
|
let distributedNode = distributedNodes[j];
|
|
if (this.isFinalDestination(child, distributedNode)) {
|
|
children$$1.push(distributedNode);
|
|
}
|
|
}
|
|
} else {
|
|
children$$1.push(child);
|
|
}
|
|
}
|
|
return children$$1;
|
|
};
|
|
|
|
ShadyRoot.prototype.isFinalDestination = function(insertionPoint, node) {
|
|
return this._distributor.isFinalDestination(
|
|
insertionPoint, node);
|
|
};
|
|
|
|
// Ensures that the rendered node list inside `container` is `children`.
|
|
ShadyRoot.prototype._updateChildNodes = function(container, children$$1) {
|
|
let composed = childNodes(container);
|
|
let splices = calculateSplices(children$$1, composed);
|
|
// process removals
|
|
for (let i=0, d=0, s; (i<splices.length) && (s=splices[i]); i++) {
|
|
for (let j=0, n; (j < s.removed.length) && (n=s.removed[j]); j++) {
|
|
// check if the node is still where we expect it is before trying
|
|
// to remove it; this can happen if we move a node and
|
|
// then schedule its previous host for distribution resulting in
|
|
// the node being removed here.
|
|
if (parentNode(n) === container) {
|
|
removeChild.call(container, n);
|
|
}
|
|
composed.splice(s.index + d, 1);
|
|
}
|
|
d -= s.addedCount;
|
|
}
|
|
// process adds
|
|
for (let i=0, s, next; (i<splices.length) && (s=splices[i]); i++) { //eslint-disable-line no-redeclare
|
|
next = composed[s.index];
|
|
for (let j=s.index, n; j < s.index + s.addedCount; j++) {
|
|
n = children$$1[j];
|
|
insertBefore.call(container, n, next);
|
|
// TODO(sorvell): is this splice strictly needed?
|
|
composed.splice(j, 0, n);
|
|
}
|
|
}
|
|
};
|
|
|
|
ShadyRoot.prototype.getInsertionPointTag = function() {
|
|
return this._distributor.insertionPointTag;
|
|
};
|
|
|
|
ShadyRoot.prototype.hasInsertionPoint = function() {
|
|
return Boolean(this._insertionPoints && this._insertionPoints.length);
|
|
};
|
|
|
|
ShadyRoot.prototype._getInsertionPoints = function() {
|
|
if (!this._insertionPoints) {
|
|
this.updateInsertionPoints();
|
|
}
|
|
return this._insertionPoints;
|
|
};
|
|
|
|
/**
|
|
Implements a pared down version of ShadowDOM's scoping, which is easy to
|
|
polyfill across browsers.
|
|
*/
|
|
function attachShadow(host, options) {
|
|
if (!host) {
|
|
throw 'Must provide a host.';
|
|
}
|
|
if (!options) {
|
|
throw 'Not enough arguments.'
|
|
}
|
|
return new ShadyRoot(ShadyRootConstructionToken, host);
|
|
}
|
|
|
|
patchShadowRootAccessors(ShadyRoot.prototype);
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2016 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 getAssignedSlot(node) {
|
|
renderRootNode(node);
|
|
return node.__shady && node.__shady.assignedSlot || null;
|
|
}
|
|
|
|
let nodeMixin = {
|
|
|
|
addEventListener: addEventListener$1,
|
|
|
|
removeEventListener: removeEventListener$1,
|
|
|
|
appendChild(node) {
|
|
return insertBefore$1(this, node);
|
|
},
|
|
|
|
insertBefore(node, ref_node) {
|
|
return insertBefore$1(this, node, ref_node);
|
|
},
|
|
|
|
removeChild(node) {
|
|
return removeChild$1(this, node);
|
|
},
|
|
|
|
/**
|
|
* @this {Node}
|
|
*/
|
|
replaceChild(node, ref_node) {
|
|
this.insertBefore(node, ref_node);
|
|
this.removeChild(ref_node);
|
|
return node;
|
|
},
|
|
|
|
/**
|
|
* @this {Node}
|
|
*/
|
|
cloneNode(deep) {
|
|
return cloneNode$1(this, deep);
|
|
},
|
|
|
|
/**
|
|
* @this {Node}
|
|
*/
|
|
getRootNode(options) {
|
|
return getRootNode(this, options);
|
|
},
|
|
|
|
/**
|
|
* @this {Node}
|
|
*/
|
|
get isConnected() {
|
|
// Fast path for distributed nodes.
|
|
const ownerDocument = this.ownerDocument;
|
|
if (ownerDocument && ownerDocument.contains && ownerDocument.contains(this)) return true;
|
|
const ownerDocumentElement = ownerDocument.documentElement;
|
|
if (ownerDocumentElement && ownerDocumentElement.contains && ownerDocumentElement.contains(this)) return true;
|
|
|
|
let node = this;
|
|
while (node && !(node instanceof Document)) {
|
|
node = node.parentNode || (node instanceof ShadyRoot ? /** @type {ShadowRoot} */(node).host : undefined);
|
|
}
|
|
return !!(node && node instanceof Document);
|
|
}
|
|
|
|
};
|
|
|
|
// NOTE: For some reason `Text` redefines `assignedSlot`
|
|
let textMixin = {
|
|
/**
|
|
* @this {Text}
|
|
*/
|
|
get assignedSlot() {
|
|
return getAssignedSlot(this);
|
|
}
|
|
};
|
|
|
|
let fragmentMixin = {
|
|
|
|
// TODO(sorvell): consider doing native QSA and filtering results.
|
|
/**
|
|
* @this {DocumentFragment}
|
|
*/
|
|
querySelector(selector) {
|
|
// match selector and halt on first result.
|
|
let result = query(this, function(n) {
|
|
return matchesSelector(n, selector);
|
|
}, function(n) {
|
|
return Boolean(n);
|
|
})[0];
|
|
return result || null;
|
|
},
|
|
|
|
/**
|
|
* @this {DocumentFragment}
|
|
*/
|
|
querySelectorAll(selector) {
|
|
return query(this, function(n) {
|
|
return matchesSelector(n, selector);
|
|
});
|
|
}
|
|
|
|
};
|
|
|
|
let slotMixin = {
|
|
|
|
/**
|
|
* @this {HTMLSlotElement}
|
|
*/
|
|
assignedNodes(options) {
|
|
if (this.localName === 'slot') {
|
|
renderRootNode(this);
|
|
return this.__shady ?
|
|
((options && options.flatten ? this.__shady.distributedNodes :
|
|
this.__shady.assignedNodes) || []) :
|
|
[];
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
let elementMixin = extendAll({
|
|
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
setAttribute(name, value) {
|
|
setAttribute$1(this, name, value);
|
|
},
|
|
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
removeAttribute(name) {
|
|
removeAttribute$1(this, name);
|
|
},
|
|
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
attachShadow(options) {
|
|
return attachShadow(this, options);
|
|
},
|
|
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
get slot() {
|
|
return this.getAttribute('slot');
|
|
},
|
|
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
set slot(value) {
|
|
this.setAttribute('slot', value);
|
|
},
|
|
|
|
/**
|
|
* @this {HTMLElement}
|
|
*/
|
|
get assignedSlot() {
|
|
return getAssignedSlot(this);
|
|
}
|
|
|
|
}, fragmentMixin, slotMixin);
|
|
|
|
Object.defineProperties(elementMixin, ShadowRootAccessor);
|
|
|
|
let documentMixin = extendAll({
|
|
/**
|
|
* @this {Document}
|
|
*/
|
|
importNode(node, deep) {
|
|
return importNode$1(node, deep);
|
|
}
|
|
}, fragmentMixin);
|
|
|
|
Object.defineProperties(documentMixin, {
|
|
'_activeElement': ActiveElementAccessor.activeElement
|
|
});
|
|
|
|
function patchBuiltin(proto, obj) {
|
|
let n$ = Object.getOwnPropertyNames(obj);
|
|
for (let i=0; i < n$.length; i++) {
|
|
let n = n$[i];
|
|
let d = Object.getOwnPropertyDescriptor(obj, n);
|
|
// NOTE: we prefer writing directly here because some browsers
|
|
// have descriptors that are writable but not configurable (e.g.
|
|
// `appendChild` on older browsers)
|
|
if (d.value) {
|
|
proto[n] = d.value;
|
|
} else {
|
|
Object.defineProperty(proto, n, d);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Apply patches to builtins (e.g. Element.prototype). Some of these patches
|
|
// can be done unconditionally (mostly methods like
|
|
// `Element.prototype.appendChild`) and some can only be done when the browser
|
|
// has proper descriptors on the builtin prototype
|
|
// (e.g. `Element.prototype.firstChild`)`. When descriptors are not available,
|
|
// elements are individually patched when needed (see e.g.
|
|
// `patchInside/OutsideElementAccessors` in `patch-accessors.js`).
|
|
function patchBuiltins() {
|
|
// These patches can always be done, for all supported browsers.
|
|
patchBuiltin(window.Node.prototype, nodeMixin);
|
|
patchBuiltin(window.Text.prototype, textMixin);
|
|
patchBuiltin(window.DocumentFragment.prototype, fragmentMixin);
|
|
patchBuiltin(window.Element.prototype, elementMixin);
|
|
patchBuiltin(window.Document.prototype, documentMixin);
|
|
if (window.HTMLSlotElement) {
|
|
patchBuiltin(window.HTMLSlotElement.prototype, slotMixin);
|
|
}
|
|
// These patches can *only* be done
|
|
// on browsers that have proper property descriptors on builtin prototypes.
|
|
// This includes: IE11, Edge, Chrome >= 4?; Safari >= 10, Firefox
|
|
// On older browsers (Chrome <= 4?, Safari 9), a per element patching
|
|
// strategy is used for patching accessors.
|
|
if (settings.hasDescriptors) {
|
|
patchAccessors(window.Node.prototype);
|
|
patchAccessors(window.Text.prototype);
|
|
patchAccessors(window.DocumentFragment.prototype);
|
|
patchAccessors(window.Element.prototype);
|
|
let nativeHTMLElement =
|
|
(window['customElements'] && window['customElements']['nativeHTMLElement']) ||
|
|
HTMLElement;
|
|
patchAccessors(nativeHTMLElement.prototype);
|
|
patchAccessors(window.Document.prototype);
|
|
if (window.HTMLSlotElement) {
|
|
patchAccessors(window.HTMLSlotElement.prototype);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2016 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
|
|
*/
|
|
|
|
/**
|
|
* Patches elements that interacts with ShadyDOM
|
|
* such that tree traversal and mutation apis act like they would under
|
|
* ShadowDOM.
|
|
*
|
|
* This import enables seemless interaction with ShadyDOM powered
|
|
* custom elements, enabling better interoperation with 3rd party code,
|
|
* libraries, and frameworks that use DOM tree manipulation apis.
|
|
*/
|
|
|
|
if (settings.inUse) {
|
|
let ShadyDOM = {
|
|
// TODO(sorvell): remove when Polymer does not depend on this.
|
|
'inUse': settings.inUse,
|
|
// TODO(sorvell): remove when Polymer does not depend on this
|
|
'patch': (node) => node,
|
|
'isShadyRoot': isShadyRoot,
|
|
'enqueue': enqueue,
|
|
'flush': flush,
|
|
'settings': settings,
|
|
'filterMutations': filterMutations,
|
|
'observeChildren': observeChildren,
|
|
'unobserveChildren': unobserveChildren,
|
|
'nativeMethods': nativeMethods,
|
|
'nativeTree': nativeTree
|
|
};
|
|
|
|
window['ShadyDOM'] = ShadyDOM;
|
|
|
|
// Apply patches to events...
|
|
patchEvents();
|
|
// Apply patches to builtins (e.g. Element.prototype) where applicable.
|
|
patchBuiltins();
|
|
|
|
window.ShadowRoot = ShadyRoot;
|
|
}
|
|
|
|
const reservedTagList = new Set([
|
|
'annotation-xml',
|
|
'color-profile',
|
|
'font-face',
|
|
'font-face-src',
|
|
'font-face-uri',
|
|
'font-face-format',
|
|
'font-face-name',
|
|
'missing-glyph',
|
|
]);
|
|
|
|
/**
|
|
* @param {string} localName
|
|
* @returns {boolean}
|
|
*/
|
|
function isValidCustomElementName(localName) {
|
|
const reserved = reservedTagList.has(localName);
|
|
const validForm = /^[a-z][.0-9_a-z]*-[\-.0-9_a-z]*$/.test(localName);
|
|
return !reserved && validForm;
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* @param {!Node} node
|
|
* @return {boolean}
|
|
*/
|
|
function isConnected(node) {
|
|
// Use `Node#isConnected`, if defined.
|
|
const nativeValue = node.isConnected;
|
|
if (nativeValue !== undefined) {
|
|
return nativeValue;
|
|
}
|
|
|
|
/** @type {?Node|undefined} */
|
|
let current = node;
|
|
while (current && !(current.__CE_isImportDocument || current instanceof Document)) {
|
|
current = current.parentNode || (window.ShadowRoot && current instanceof ShadowRoot ? current.host : undefined);
|
|
}
|
|
return !!(current && (current.__CE_isImportDocument || current instanceof Document));
|
|
}
|
|
|
|
/**
|
|
* @param {!Node} root
|
|
* @param {!Node} start
|
|
* @return {?Node}
|
|
*/
|
|
function nextSiblingOrAncestorSibling(root, start) {
|
|
let node = start;
|
|
while (node && node !== root && !node.nextSibling) {
|
|
node = node.parentNode;
|
|
}
|
|
return (!node || node === root) ? null : node.nextSibling;
|
|
}
|
|
|
|
/**
|
|
* @param {!Node} root
|
|
* @param {!Node} start
|
|
* @return {?Node}
|
|
*/
|
|
function nextNode(root, start) {
|
|
return start.firstChild ? start.firstChild : nextSiblingOrAncestorSibling(root, start);
|
|
}
|
|
|
|
/**
|
|
* @param {!Node} root
|
|
* @param {!function(!Element)} callback
|
|
* @param {!Set<Node>=} visitedImports
|
|
*/
|
|
function walkDeepDescendantElements(root, callback, visitedImports = new Set()) {
|
|
let node = root;
|
|
while (node) {
|
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
const element = /** @type {!Element} */(node);
|
|
|
|
callback(element);
|
|
|
|
const localName = element.localName;
|
|
if (localName === 'link' && element.getAttribute('rel') === 'import') {
|
|
// If this import (polyfilled or not) has it's root node available,
|
|
// walk it.
|
|
const importNode = /** @type {!Node} */ (element.import);
|
|
if (importNode instanceof Node && !visitedImports.has(importNode)) {
|
|
// Prevent multiple walks of the same import root.
|
|
visitedImports.add(importNode);
|
|
|
|
for (let child = importNode.firstChild; child; child = child.nextSibling) {
|
|
walkDeepDescendantElements(child, callback, visitedImports);
|
|
}
|
|
}
|
|
|
|
// Ignore descendants of import links to prevent attempting to walk the
|
|
// elements created by the HTML Imports polyfill that we just walked
|
|
// above.
|
|
node = nextSiblingOrAncestorSibling(root, element);
|
|
continue;
|
|
} else if (localName === 'template') {
|
|
// Ignore descendants of templates. There shouldn't be any descendants
|
|
// because they will be moved into `.content` during construction in
|
|
// browsers that support template but, in case they exist and are still
|
|
// waiting to be moved by a polyfill, they will be ignored.
|
|
node = nextSiblingOrAncestorSibling(root, element);
|
|
continue;
|
|
}
|
|
|
|
// Walk shadow roots.
|
|
const shadowRoot = element.__CE_shadowRoot;
|
|
if (shadowRoot) {
|
|
for (let child = shadowRoot.firstChild; child; child = child.nextSibling) {
|
|
walkDeepDescendantElements(child, callback, visitedImports);
|
|
}
|
|
}
|
|
}
|
|
|
|
node = nextNode(root, node);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Used to suppress Closure's "Modifying the prototype is only allowed if the
|
|
* constructor is in the same scope" warning without using
|
|
* `@suppress {newCheckTypes, duplicate}` because `newCheckTypes` is too broad.
|
|
*
|
|
* @param {!Object} destination
|
|
* @param {string} name
|
|
* @param {*} value
|
|
*/
|
|
function setPropertyUnchecked(destination, name, value) {
|
|
destination[name] = value;
|
|
}
|
|
|
|
/**
|
|
* @enum {number}
|
|
*/
|
|
const CustomElementState = {
|
|
custom: 1,
|
|
failed: 2,
|
|
};
|
|
|
|
class CustomElementInternals {
|
|
constructor() {
|
|
/** @type {!Map<string, !CustomElementDefinition>} */
|
|
this._localNameToDefinition = new Map();
|
|
|
|
/** @type {!Map<!Function, !CustomElementDefinition>} */
|
|
this._constructorToDefinition = new Map();
|
|
|
|
/** @type {!Array<!function(!Node)>} */
|
|
this._patches = [];
|
|
|
|
/** @type {boolean} */
|
|
this._hasPatches = false;
|
|
}
|
|
|
|
/**
|
|
* @param {string} localName
|
|
* @param {!CustomElementDefinition} definition
|
|
*/
|
|
setDefinition(localName, definition) {
|
|
this._localNameToDefinition.set(localName, definition);
|
|
this._constructorToDefinition.set(definition.constructor, definition);
|
|
}
|
|
|
|
/**
|
|
* @param {string} localName
|
|
* @return {!CustomElementDefinition|undefined}
|
|
*/
|
|
localNameToDefinition(localName) {
|
|
return this._localNameToDefinition.get(localName);
|
|
}
|
|
|
|
/**
|
|
* @param {!Function} constructor
|
|
* @return {!CustomElementDefinition|undefined}
|
|
*/
|
|
constructorToDefinition(constructor) {
|
|
return this._constructorToDefinition.get(constructor);
|
|
}
|
|
|
|
/**
|
|
* @param {!function(!Node)} listener
|
|
*/
|
|
addPatch(listener) {
|
|
this._hasPatches = true;
|
|
this._patches.push(listener);
|
|
}
|
|
|
|
/**
|
|
* @param {!Node} node
|
|
*/
|
|
patchTree(node) {
|
|
if (!this._hasPatches) return;
|
|
|
|
walkDeepDescendantElements(node, element => this.patch(element));
|
|
}
|
|
|
|
/**
|
|
* @param {!Node} node
|
|
*/
|
|
patch(node) {
|
|
if (!this._hasPatches) return;
|
|
|
|
if (node.__CE_patched) return;
|
|
node.__CE_patched = true;
|
|
|
|
for (let i = 0; i < this._patches.length; i++) {
|
|
this._patches[i](node);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!Node} root
|
|
*/
|
|
connectTree(root) {
|
|
const elements = [];
|
|
|
|
walkDeepDescendantElements(root, element => elements.push(element));
|
|
|
|
for (let i = 0; i < elements.length; i++) {
|
|
const element = elements[i];
|
|
if (element.__CE_state === CustomElementState.custom) {
|
|
this.connectedCallback(element);
|
|
} else {
|
|
this.upgradeElement(element);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!Node} root
|
|
*/
|
|
disconnectTree(root) {
|
|
const elements = [];
|
|
|
|
walkDeepDescendantElements(root, element => elements.push(element));
|
|
|
|
for (let i = 0; i < elements.length; i++) {
|
|
const element = elements[i];
|
|
if (element.__CE_state === CustomElementState.custom) {
|
|
this.disconnectedCallback(element);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Upgrades all uncustomized custom elements at and below a root node for
|
|
* which there is a definition. When custom element reaction callbacks are
|
|
* assumed to be called synchronously (which, by the current DOM / HTML spec
|
|
* definitions, they are *not*), callbacks for both elements customized
|
|
* synchronously by the parser and elements being upgraded occur in the same
|
|
* relative order.
|
|
*
|
|
* NOTE: This function, when used to simulate the construction of a tree that
|
|
* is already created but not customized (i.e. by the parser), does *not*
|
|
* prevent the element from reading the 'final' (true) state of the tree. For
|
|
* example, the element, during truly synchronous parsing / construction would
|
|
* see that it contains no children as they have not yet been inserted.
|
|
* However, this function does not modify the tree, the element will
|
|
* (incorrectly) have children. Additionally, self-modification restrictions
|
|
* for custom element constructors imposed by the DOM spec are *not* enforced.
|
|
*
|
|
*
|
|
* The following nested list shows the steps extending down from the HTML
|
|
* spec's parsing section that cause elements to be synchronously created and
|
|
* upgraded:
|
|
*
|
|
* The "in body" insertion mode:
|
|
* https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody
|
|
* - Switch on token:
|
|
* .. other cases ..
|
|
* -> Any other start tag
|
|
* - [Insert an HTML element](below) for the token.
|
|
*
|
|
* Insert an HTML element:
|
|
* https://html.spec.whatwg.org/multipage/syntax.html#insert-an-html-element
|
|
* - Insert a foreign element for the token in the HTML namespace:
|
|
* https://html.spec.whatwg.org/multipage/syntax.html#insert-a-foreign-element
|
|
* - Create an element for a token:
|
|
* https://html.spec.whatwg.org/multipage/syntax.html#create-an-element-for-the-token
|
|
* - Will execute script flag is true?
|
|
* - (Element queue pushed to the custom element reactions stack.)
|
|
* - Create an element:
|
|
* https://dom.spec.whatwg.org/#concept-create-element
|
|
* - Sync CE flag is true?
|
|
* - Constructor called.
|
|
* - Self-modification restrictions enforced.
|
|
* - Sync CE flag is false?
|
|
* - (Upgrade reaction enqueued.)
|
|
* - Attributes appended to element.
|
|
* (`attributeChangedCallback` reactions enqueued.)
|
|
* - Will execute script flag is true?
|
|
* - (Element queue popped from the custom element reactions stack.
|
|
* Reactions in the popped stack are invoked.)
|
|
* - (Element queue pushed to the custom element reactions stack.)
|
|
* - Insert the element:
|
|
* https://dom.spec.whatwg.org/#concept-node-insert
|
|
* - Shadow-including descendants are connected. During parsing
|
|
* construction, there are no shadow-*excluding* descendants.
|
|
* However, the constructor may have validly attached a shadow
|
|
* tree to itself and added descendants to that shadow tree.
|
|
* (`connectedCallback` reactions enqueued.)
|
|
* - (Element queue popped from the custom element reactions stack.
|
|
* Reactions in the popped stack are invoked.)
|
|
*
|
|
* @param {!Node} root
|
|
* @param {!Set<Node>=} visitedImports
|
|
*/
|
|
patchAndUpgradeTree(root, visitedImports = new Set()) {
|
|
const elements = [];
|
|
|
|
const gatherElements = element => {
|
|
if (element.localName === 'link' && element.getAttribute('rel') === 'import') {
|
|
// The HTML Imports polyfill sets a descendant element of the link to
|
|
// the `import` property, specifically this is *not* a Document.
|
|
const importNode = /** @type {?Node} */ (element.import);
|
|
|
|
if (importNode instanceof Node && importNode.readyState === 'complete') {
|
|
importNode.__CE_isImportDocument = true;
|
|
|
|
// Connected links are associated with the registry.
|
|
importNode.__CE_hasRegistry = true;
|
|
} else {
|
|
// If this link's import root is not available, its contents can't be
|
|
// walked. Wait for 'load' and walk it when it's ready.
|
|
element.addEventListener('load', () => {
|
|
const importNode = /** @type {!Node} */ (element.import);
|
|
|
|
if (importNode.__CE_documentLoadHandled) return;
|
|
importNode.__CE_documentLoadHandled = true;
|
|
|
|
importNode.__CE_isImportDocument = true;
|
|
|
|
// Connected links are associated with the registry.
|
|
importNode.__CE_hasRegistry = true;
|
|
|
|
// Clone the `visitedImports` set that was populated sync during
|
|
// the `patchAndUpgradeTree` call that caused this 'load' handler to
|
|
// be added. Then, remove *this* link's import node so that we can
|
|
// walk that import again, even if it was partially walked later
|
|
// during the same `patchAndUpgradeTree` call.
|
|
const clonedVisitedImports = new Set(visitedImports);
|
|
visitedImports.delete(importNode);
|
|
|
|
this.patchAndUpgradeTree(importNode, visitedImports);
|
|
});
|
|
}
|
|
} else {
|
|
elements.push(element);
|
|
}
|
|
};
|
|
|
|
// `walkDeepDescendantElements` populates (and internally checks against)
|
|
// `visitedImports` when traversing a loaded import.
|
|
walkDeepDescendantElements(root, gatherElements, visitedImports);
|
|
|
|
if (this._hasPatches) {
|
|
for (let i = 0; i < elements.length; i++) {
|
|
this.patch(elements[i]);
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < elements.length; i++) {
|
|
this.upgradeElement(elements[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!Element} element
|
|
*/
|
|
upgradeElement(element) {
|
|
const currentState = element.__CE_state;
|
|
if (currentState !== undefined) return;
|
|
|
|
const definition = this.localNameToDefinition(element.localName);
|
|
if (!definition) return;
|
|
|
|
definition.constructionStack.push(element);
|
|
|
|
const constructor = definition.constructor;
|
|
try {
|
|
try {
|
|
let result = new (constructor)();
|
|
if (result !== element) {
|
|
throw new Error('The custom element constructor did not produce the element being upgraded.');
|
|
}
|
|
} finally {
|
|
definition.constructionStack.pop();
|
|
}
|
|
} catch (e) {
|
|
element.__CE_state = CustomElementState.failed;
|
|
throw e;
|
|
}
|
|
|
|
element.__CE_state = CustomElementState.custom;
|
|
element.__CE_definition = definition;
|
|
|
|
if (definition.attributeChangedCallback) {
|
|
const observedAttributes = definition.observedAttributes;
|
|
for (let i = 0; i < observedAttributes.length; i++) {
|
|
const name = observedAttributes[i];
|
|
const value = element.getAttribute(name);
|
|
if (value !== null) {
|
|
this.attributeChangedCallback(element, name, null, value, null);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isConnected(element)) {
|
|
this.connectedCallback(element);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!Element} element
|
|
*/
|
|
connectedCallback(element) {
|
|
const definition = element.__CE_definition;
|
|
if (definition.connectedCallback) {
|
|
definition.connectedCallback.call(element);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!Element} element
|
|
*/
|
|
disconnectedCallback(element) {
|
|
const definition = element.__CE_definition;
|
|
if (definition.disconnectedCallback) {
|
|
definition.disconnectedCallback.call(element);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!Element} element
|
|
* @param {string} name
|
|
* @param {?string} oldValue
|
|
* @param {?string} newValue
|
|
* @param {?string} namespace
|
|
*/
|
|
attributeChangedCallback(element, name, oldValue, newValue, namespace) {
|
|
const definition = element.__CE_definition;
|
|
if (
|
|
definition.attributeChangedCallback &&
|
|
definition.observedAttributes.indexOf(name) > -1
|
|
) {
|
|
definition.attributeChangedCallback.call(element, name, oldValue, newValue, namespace);
|
|
}
|
|
}
|
|
}
|
|
|
|
class DocumentConstructionObserver {
|
|
constructor(internals, doc) {
|
|
/**
|
|
* @type {!CustomElementInternals}
|
|
*/
|
|
this._internals = internals;
|
|
|
|
/**
|
|
* @type {!Document}
|
|
*/
|
|
this._document = doc;
|
|
|
|
/**
|
|
* @type {MutationObserver|undefined}
|
|
*/
|
|
this._observer = undefined;
|
|
|
|
|
|
// Simulate tree construction for all currently accessible nodes in the
|
|
// document.
|
|
this._internals.patchAndUpgradeTree(this._document);
|
|
|
|
if (this._document.readyState === 'loading') {
|
|
this._observer = new MutationObserver(this._handleMutations.bind(this));
|
|
|
|
// Nodes created by the parser are given to the observer *before* the next
|
|
// task runs. Inline scripts are run in a new task. This means that the
|
|
// observer will be able to handle the newly parsed nodes before the inline
|
|
// script is run.
|
|
this._observer.observe(this._document, {
|
|
childList: true,
|
|
subtree: true,
|
|
});
|
|
}
|
|
}
|
|
|
|
disconnect() {
|
|
if (this._observer) {
|
|
this._observer.disconnect();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!Array<!MutationRecord>} mutations
|
|
*/
|
|
_handleMutations(mutations) {
|
|
// Once the document's `readyState` is 'interactive' or 'complete', all new
|
|
// nodes created within that document will be the result of script and
|
|
// should be handled by patching.
|
|
const readyState = this._document.readyState;
|
|
if (readyState === 'interactive' || readyState === 'complete') {
|
|
this.disconnect();
|
|
}
|
|
|
|
for (let i = 0; i < mutations.length; i++) {
|
|
const addedNodes = mutations[i].addedNodes;
|
|
for (let j = 0; j < addedNodes.length; j++) {
|
|
const node = addedNodes[j];
|
|
this._internals.patchAndUpgradeTree(node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @template T
|
|
*/
|
|
class Deferred {
|
|
constructor() {
|
|
/**
|
|
* @private
|
|
* @type {T|undefined}
|
|
*/
|
|
this._value = undefined;
|
|
|
|
/**
|
|
* @private
|
|
* @type {Function|undefined}
|
|
*/
|
|
this._resolve = undefined;
|
|
|
|
/**
|
|
* @private
|
|
* @type {!Promise<T>}
|
|
*/
|
|
this._promise = new Promise(resolve => {
|
|
this._resolve = resolve;
|
|
|
|
if (this._value) {
|
|
resolve(this._value);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {T} value
|
|
*/
|
|
resolve(value) {
|
|
if (this._value) {
|
|
throw new Error('Already resolved.');
|
|
}
|
|
|
|
this._value = value;
|
|
|
|
if (this._resolve) {
|
|
this._resolve(value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<T>}
|
|
*/
|
|
toPromise() {
|
|
return this._promise;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @unrestricted
|
|
*/
|
|
class CustomElementRegistry {
|
|
|
|
/**
|
|
* @param {!CustomElementInternals} internals
|
|
*/
|
|
constructor(internals) {
|
|
/**
|
|
* @private
|
|
* @type {boolean}
|
|
*/
|
|
this._elementDefinitionIsRunning = false;
|
|
|
|
/**
|
|
* @private
|
|
* @type {!CustomElementInternals}
|
|
*/
|
|
this._internals = internals;
|
|
|
|
/**
|
|
* @private
|
|
* @type {!Map<string, !Deferred<undefined>>}
|
|
*/
|
|
this._whenDefinedDeferred = new Map();
|
|
|
|
/**
|
|
* The default flush callback triggers the document walk synchronously.
|
|
* @private
|
|
* @type {!Function}
|
|
*/
|
|
this._flushCallback = fn => fn();
|
|
|
|
/**
|
|
* @private
|
|
* @type {boolean}
|
|
*/
|
|
this._flushPending = false;
|
|
|
|
/**
|
|
* @private
|
|
* @type {!Array<string>}
|
|
*/
|
|
this._unflushedLocalNames = [];
|
|
|
|
/**
|
|
* @private
|
|
* @type {!DocumentConstructionObserver}
|
|
*/
|
|
this._documentConstructionObserver = new DocumentConstructionObserver(internals, document);
|
|
}
|
|
|
|
/**
|
|
* @param {string} localName
|
|
* @param {!Function} constructor
|
|
*/
|
|
define(localName, constructor) {
|
|
if (!(constructor instanceof Function)) {
|
|
throw new TypeError('Custom element constructors must be functions.');
|
|
}
|
|
|
|
if (!isValidCustomElementName(localName)) {
|
|
throw new SyntaxError(`The element name '${localName}' is not valid.`);
|
|
}
|
|
|
|
if (this._internals.localNameToDefinition(localName)) {
|
|
throw new Error(`A custom element with name '${localName}' has already been defined.`);
|
|
}
|
|
|
|
if (this._elementDefinitionIsRunning) {
|
|
throw new Error('A custom element is already being defined.');
|
|
}
|
|
this._elementDefinitionIsRunning = true;
|
|
|
|
let connectedCallback;
|
|
let disconnectedCallback;
|
|
let adoptedCallback;
|
|
let attributeChangedCallback;
|
|
let observedAttributes;
|
|
try {
|
|
/** @type {!Object} */
|
|
const prototype = constructor.prototype;
|
|
if (!(prototype instanceof Object)) {
|
|
throw new TypeError('The custom element constructor\'s prototype is not an object.');
|
|
}
|
|
|
|
function getCallback(name) {
|
|
const callbackValue = prototype[name];
|
|
if (callbackValue !== undefined && !(callbackValue instanceof Function)) {
|
|
throw new Error(`The '${name}' callback must be a function.`);
|
|
}
|
|
return callbackValue;
|
|
}
|
|
|
|
connectedCallback = getCallback('connectedCallback');
|
|
disconnectedCallback = getCallback('disconnectedCallback');
|
|
adoptedCallback = getCallback('adoptedCallback');
|
|
attributeChangedCallback = getCallback('attributeChangedCallback');
|
|
observedAttributes = constructor['observedAttributes'] || [];
|
|
} catch (e) {
|
|
return;
|
|
} finally {
|
|
this._elementDefinitionIsRunning = false;
|
|
}
|
|
|
|
const definition = {
|
|
localName,
|
|
constructor,
|
|
connectedCallback,
|
|
disconnectedCallback,
|
|
adoptedCallback,
|
|
attributeChangedCallback,
|
|
observedAttributes,
|
|
constructionStack: [],
|
|
};
|
|
|
|
this._internals.setDefinition(localName, definition);
|
|
|
|
this._unflushedLocalNames.push(localName);
|
|
|
|
// If we've already called the flush callback and it hasn't called back yet,
|
|
// don't call it again.
|
|
if (!this._flushPending) {
|
|
this._flushPending = true;
|
|
this._flushCallback(() => this._flush());
|
|
}
|
|
}
|
|
|
|
_flush() {
|
|
// If no new definitions were defined, don't attempt to flush. This could
|
|
// happen if a flush callback keeps the function it is given and calls it
|
|
// multiple times.
|
|
if (this._flushPending === false) return;
|
|
|
|
this._flushPending = false;
|
|
this._internals.patchAndUpgradeTree(document);
|
|
|
|
while (this._unflushedLocalNames.length > 0) {
|
|
const localName = this._unflushedLocalNames.shift();
|
|
const deferred = this._whenDefinedDeferred.get(localName);
|
|
if (deferred) {
|
|
deferred.resolve(undefined);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} localName
|
|
* @return {Function|undefined}
|
|
*/
|
|
get(localName) {
|
|
const definition = this._internals.localNameToDefinition(localName);
|
|
if (definition) {
|
|
return definition.constructor;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* @param {string} localName
|
|
* @return {!Promise<undefined>}
|
|
*/
|
|
whenDefined(localName) {
|
|
if (!isValidCustomElementName(localName)) {
|
|
return Promise.reject(new SyntaxError(`'${localName}' is not a valid custom element name.`));
|
|
}
|
|
|
|
const prior = this._whenDefinedDeferred.get(localName);
|
|
if (prior) {
|
|
return prior.toPromise();
|
|
}
|
|
|
|
const deferred = new Deferred();
|
|
this._whenDefinedDeferred.set(localName, deferred);
|
|
|
|
const definition = this._internals.localNameToDefinition(localName);
|
|
// Resolve immediately only if the given local name has a definition *and*
|
|
// the full document walk to upgrade elements with that local name has
|
|
// already happened.
|
|
if (definition && this._unflushedLocalNames.indexOf(localName) === -1) {
|
|
deferred.resolve(undefined);
|
|
}
|
|
|
|
return deferred.toPromise();
|
|
}
|
|
|
|
polyfillWrapFlushCallback(outer) {
|
|
this._documentConstructionObserver.disconnect();
|
|
const inner = this._flushCallback;
|
|
this._flushCallback = flush => outer(() => inner(flush));
|
|
}
|
|
}
|
|
|
|
// Closure compiler exports.
|
|
window['CustomElementRegistry'] = CustomElementRegistry;
|
|
CustomElementRegistry.prototype['define'] = CustomElementRegistry.prototype.define;
|
|
CustomElementRegistry.prototype['get'] = CustomElementRegistry.prototype.get;
|
|
CustomElementRegistry.prototype['whenDefined'] = CustomElementRegistry.prototype.whenDefined;
|
|
CustomElementRegistry.prototype['polyfillWrapFlushCallback'] = CustomElementRegistry.prototype.polyfillWrapFlushCallback;
|
|
|
|
var Native = {
|
|
Document_createElement: window.Document.prototype.createElement,
|
|
Document_createElementNS: window.Document.prototype.createElementNS,
|
|
Document_importNode: window.Document.prototype.importNode,
|
|
Document_prepend: window.Document.prototype['prepend'],
|
|
Document_append: window.Document.prototype['append'],
|
|
Node_cloneNode: window.Node.prototype.cloneNode,
|
|
Node_appendChild: window.Node.prototype.appendChild,
|
|
Node_insertBefore: window.Node.prototype.insertBefore,
|
|
Node_removeChild: window.Node.prototype.removeChild,
|
|
Node_replaceChild: window.Node.prototype.replaceChild,
|
|
Node_textContent: Object.getOwnPropertyDescriptor(window.Node.prototype, 'textContent'),
|
|
Element_attachShadow: window.Element.prototype['attachShadow'],
|
|
Element_innerHTML: Object.getOwnPropertyDescriptor(window.Element.prototype, 'innerHTML'),
|
|
Element_getAttribute: window.Element.prototype.getAttribute,
|
|
Element_setAttribute: window.Element.prototype.setAttribute,
|
|
Element_removeAttribute: window.Element.prototype.removeAttribute,
|
|
Element_getAttributeNS: window.Element.prototype.getAttributeNS,
|
|
Element_setAttributeNS: window.Element.prototype.setAttributeNS,
|
|
Element_removeAttributeNS: window.Element.prototype.removeAttributeNS,
|
|
Element_insertAdjacentElement: window.Element.prototype['insertAdjacentElement'],
|
|
Element_prepend: window.Element.prototype['prepend'],
|
|
Element_append: window.Element.prototype['append'],
|
|
Element_before: window.Element.prototype['before'],
|
|
Element_after: window.Element.prototype['after'],
|
|
Element_replaceWith: window.Element.prototype['replaceWith'],
|
|
Element_remove: window.Element.prototype['remove'],
|
|
HTMLElement: window.HTMLElement,
|
|
HTMLElement_innerHTML: Object.getOwnPropertyDescriptor(window.HTMLElement.prototype, 'innerHTML'),
|
|
HTMLElement_insertAdjacentElement: window.HTMLElement.prototype['insertAdjacentElement'],
|
|
};
|
|
|
|
/**
|
|
* This class exists only to work around Closure's lack of a way to describe
|
|
* singletons. It represents the 'already constructed marker' used in custom
|
|
* element construction stacks.
|
|
*
|
|
* https://html.spec.whatwg.org/#concept-already-constructed-marker
|
|
*/
|
|
class AlreadyConstructedMarker {}
|
|
|
|
var AlreadyConstructedMarker$1 = new AlreadyConstructedMarker();
|
|
|
|
/**
|
|
* @param {!CustomElementInternals} internals
|
|
*/
|
|
var PatchHTMLElement = function(internals) {
|
|
window['HTMLElement'] = (function() {
|
|
/**
|
|
* @type {function(new: HTMLElement): !HTMLElement}
|
|
*/
|
|
function HTMLElement() {
|
|
// This should really be `new.target` but `new.target` can't be emulated
|
|
// in ES5. Assuming the user keeps the default value of the constructor's
|
|
// prototype's `constructor` property, this is equivalent.
|
|
/** @type {!Function} */
|
|
const constructor = this.constructor;
|
|
|
|
const definition = internals.constructorToDefinition(constructor);
|
|
if (!definition) {
|
|
throw new Error('The custom element being constructed was not registered with `customElements`.');
|
|
}
|
|
|
|
const constructionStack = definition.constructionStack;
|
|
|
|
if (constructionStack.length === 0) {
|
|
const element = Native.Document_createElement.call(document, definition.localName);
|
|
Object.setPrototypeOf(element, constructor.prototype);
|
|
element.__CE_state = CustomElementState.custom;
|
|
element.__CE_definition = definition;
|
|
internals.patch(element);
|
|
return element;
|
|
}
|
|
|
|
const lastIndex = constructionStack.length - 1;
|
|
const element = constructionStack[lastIndex];
|
|
if (element === AlreadyConstructedMarker$1) {
|
|
throw new Error('The HTMLElement constructor was either called reentrantly for this constructor or called multiple times.');
|
|
}
|
|
constructionStack[lastIndex] = AlreadyConstructedMarker$1;
|
|
|
|
Object.setPrototypeOf(element, constructor.prototype);
|
|
internals.patch(/** @type {!HTMLElement} */ (element));
|
|
|
|
return element;
|
|
}
|
|
|
|
HTMLElement.prototype = Native.HTMLElement.prototype;
|
|
|
|
return HTMLElement;
|
|
})();
|
|
};
|
|
|
|
/**
|
|
* @param {!CustomElementInternals} internals
|
|
* @param {!Object} destination
|
|
* @param {!ParentNodeNativeMethods} builtIn
|
|
*/
|
|
var PatchParentNode = function(internals, destination, builtIn) {
|
|
/**
|
|
* @param {...(!Node|string)} nodes
|
|
*/
|
|
destination['prepend'] = function(...nodes) {
|
|
// TODO: Fix this for when one of `nodes` is a DocumentFragment!
|
|
const connectedBefore = /** @type {!Array<!Node>} */ (nodes.filter(node => {
|
|
// DocumentFragments are not connected and will not be added to the list.
|
|
return node instanceof Node && isConnected(node);
|
|
}));
|
|
|
|
builtIn.prepend.apply(this, nodes);
|
|
|
|
for (let i = 0; i < connectedBefore.length; i++) {
|
|
internals.disconnectTree(connectedBefore[i]);
|
|
}
|
|
|
|
if (isConnected(this)) {
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
const node = nodes[i];
|
|
if (node instanceof Element) {
|
|
internals.connectTree(node);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @param {...(!Node|string)} nodes
|
|
*/
|
|
destination['append'] = function(...nodes) {
|
|
// TODO: Fix this for when one of `nodes` is a DocumentFragment!
|
|
const connectedBefore = /** @type {!Array<!Node>} */ (nodes.filter(node => {
|
|
// DocumentFragments are not connected and will not be added to the list.
|
|
return node instanceof Node && isConnected(node);
|
|
}));
|
|
|
|
builtIn.append.apply(this, nodes);
|
|
|
|
for (let i = 0; i < connectedBefore.length; i++) {
|
|
internals.disconnectTree(connectedBefore[i]);
|
|
}
|
|
|
|
if (isConnected(this)) {
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
const node = nodes[i];
|
|
if (node instanceof Element) {
|
|
internals.connectTree(node);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @param {!CustomElementInternals} internals
|
|
*/
|
|
var PatchDocument = function(internals) {
|
|
setPropertyUnchecked(Document.prototype, 'createElement',
|
|
/**
|
|
* @this {Document}
|
|
* @param {string} localName
|
|
* @return {!Element}
|
|
*/
|
|
function(localName) {
|
|
// Only create custom elements if this document is associated with the registry.
|
|
if (this.__CE_hasRegistry) {
|
|
const definition = internals.localNameToDefinition(localName);
|
|
if (definition) {
|
|
return new (definition.constructor)();
|
|
}
|
|
}
|
|
|
|
const result = /** @type {!Element} */
|
|
(Native.Document_createElement.call(this, localName));
|
|
internals.patch(result);
|
|
return result;
|
|
});
|
|
|
|
setPropertyUnchecked(Document.prototype, 'importNode',
|
|
/**
|
|
* @this {Document}
|
|
* @param {!Node} node
|
|
* @param {boolean=} deep
|
|
* @return {!Node}
|
|
*/
|
|
function(node, deep) {
|
|
const clone = Native.Document_importNode.call(this, node, deep);
|
|
// Only create custom elements if this document is associated with the registry.
|
|
if (!this.__CE_hasRegistry) {
|
|
internals.patchTree(clone);
|
|
} else {
|
|
internals.patchAndUpgradeTree(clone);
|
|
}
|
|
return clone;
|
|
});
|
|
|
|
const NS_HTML = "http://www.w3.org/1999/xhtml";
|
|
|
|
setPropertyUnchecked(Document.prototype, 'createElementNS',
|
|
/**
|
|
* @this {Document}
|
|
* @param {?string} namespace
|
|
* @param {string} localName
|
|
* @return {!Element}
|
|
*/
|
|
function(namespace, localName) {
|
|
// Only create custom elements if this document is associated with the registry.
|
|
if (this.__CE_hasRegistry && (namespace === null || namespace === NS_HTML)) {
|
|
const definition = internals.localNameToDefinition(localName);
|
|
if (definition) {
|
|
return new (definition.constructor)();
|
|
}
|
|
}
|
|
|
|
const result = /** @type {!Element} */
|
|
(Native.Document_createElementNS.call(this, namespace, localName));
|
|
internals.patch(result);
|
|
return result;
|
|
});
|
|
|
|
PatchParentNode(internals, Document.prototype, {
|
|
prepend: Native.Document_prepend,
|
|
append: Native.Document_append,
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @param {!CustomElementInternals} internals
|
|
*/
|
|
var PatchNode = function(internals) {
|
|
// `Node#nodeValue` is implemented on `Attr`.
|
|
// `Node#textContent` is implemented on `Attr`, `Element`.
|
|
|
|
setPropertyUnchecked(Node.prototype, 'insertBefore',
|
|
/**
|
|
* @this {Node}
|
|
* @param {!Node} node
|
|
* @param {?Node} refNode
|
|
* @return {!Node}
|
|
*/
|
|
function(node, refNode) {
|
|
if (node instanceof DocumentFragment) {
|
|
const insertedNodes = Array.prototype.slice.apply(node.childNodes);
|
|
const nativeResult = Native.Node_insertBefore.call(this, node, refNode);
|
|
|
|
// DocumentFragments can't be connected, so `disconnectTree` will never
|
|
// need to be called on a DocumentFragment's children after inserting it.
|
|
|
|
if (isConnected(this)) {
|
|
for (let i = 0; i < insertedNodes.length; i++) {
|
|
internals.connectTree(insertedNodes[i]);
|
|
}
|
|
}
|
|
|
|
return nativeResult;
|
|
}
|
|
|
|
const nodeWasConnected = isConnected(node);
|
|
const nativeResult = Native.Node_insertBefore.call(this, node, refNode);
|
|
|
|
if (nodeWasConnected) {
|
|
internals.disconnectTree(node);
|
|
}
|
|
|
|
if (isConnected(this)) {
|
|
internals.connectTree(node);
|
|
}
|
|
|
|
return nativeResult;
|
|
});
|
|
|
|
setPropertyUnchecked(Node.prototype, 'appendChild',
|
|
/**
|
|
* @this {Node}
|
|
* @param {!Node} node
|
|
* @return {!Node}
|
|
*/
|
|
function(node) {
|
|
if (node instanceof DocumentFragment) {
|
|
const insertedNodes = Array.prototype.slice.apply(node.childNodes);
|
|
const nativeResult = Native.Node_appendChild.call(this, node);
|
|
|
|
// DocumentFragments can't be connected, so `disconnectTree` will never
|
|
// need to be called on a DocumentFragment's children after inserting it.
|
|
|
|
if (isConnected(this)) {
|
|
for (let i = 0; i < insertedNodes.length; i++) {
|
|
internals.connectTree(insertedNodes[i]);
|
|
}
|
|
}
|
|
|
|
return nativeResult;
|
|
}
|
|
|
|
const nodeWasConnected = isConnected(node);
|
|
const nativeResult = Native.Node_appendChild.call(this, node);
|
|
|
|
if (nodeWasConnected) {
|
|
internals.disconnectTree(node);
|
|
}
|
|
|
|
if (isConnected(this)) {
|
|
internals.connectTree(node);
|
|
}
|
|
|
|
return nativeResult;
|
|
});
|
|
|
|
setPropertyUnchecked(Node.prototype, 'cloneNode',
|
|
/**
|
|
* @this {Node}
|
|
* @param {boolean=} deep
|
|
* @return {!Node}
|
|
*/
|
|
function(deep) {
|
|
const clone = Native.Node_cloneNode.call(this, deep);
|
|
// Only create custom elements if this element's owner document is
|
|
// associated with the registry.
|
|
if (!this.ownerDocument.__CE_hasRegistry) {
|
|
internals.patchTree(clone);
|
|
} else {
|
|
internals.patchAndUpgradeTree(clone);
|
|
}
|
|
return clone;
|
|
});
|
|
|
|
setPropertyUnchecked(Node.prototype, 'removeChild',
|
|
/**
|
|
* @this {Node}
|
|
* @param {!Node} node
|
|
* @return {!Node}
|
|
*/
|
|
function(node) {
|
|
const nodeWasConnected = isConnected(node);
|
|
const nativeResult = Native.Node_removeChild.call(this, node);
|
|
|
|
if (nodeWasConnected) {
|
|
internals.disconnectTree(node);
|
|
}
|
|
|
|
return nativeResult;
|
|
});
|
|
|
|
setPropertyUnchecked(Node.prototype, 'replaceChild',
|
|
/**
|
|
* @this {Node}
|
|
* @param {!Node} nodeToInsert
|
|
* @param {!Node} nodeToRemove
|
|
* @return {!Node}
|
|
*/
|
|
function(nodeToInsert, nodeToRemove) {
|
|
if (nodeToInsert instanceof DocumentFragment) {
|
|
const insertedNodes = Array.prototype.slice.apply(nodeToInsert.childNodes);
|
|
const nativeResult = Native.Node_replaceChild.call(this, nodeToInsert, nodeToRemove);
|
|
|
|
// DocumentFragments can't be connected, so `disconnectTree` will never
|
|
// need to be called on a DocumentFragment's children after inserting it.
|
|
|
|
if (isConnected(this)) {
|
|
internals.disconnectTree(nodeToRemove);
|
|
for (let i = 0; i < insertedNodes.length; i++) {
|
|
internals.connectTree(insertedNodes[i]);
|
|
}
|
|
}
|
|
|
|
return nativeResult;
|
|
}
|
|
|
|
const nodeToInsertWasConnected = isConnected(nodeToInsert);
|
|
const nativeResult = Native.Node_replaceChild.call(this, nodeToInsert, nodeToRemove);
|
|
const thisIsConnected = isConnected(this);
|
|
|
|
if (thisIsConnected) {
|
|
internals.disconnectTree(nodeToRemove);
|
|
}
|
|
|
|
if (nodeToInsertWasConnected) {
|
|
internals.disconnectTree(nodeToInsert);
|
|
}
|
|
|
|
if (thisIsConnected) {
|
|
internals.connectTree(nodeToInsert);
|
|
}
|
|
|
|
return nativeResult;
|
|
});
|
|
|
|
|
|
function patch_textContent(destination, baseDescriptor) {
|
|
Object.defineProperty(destination, 'textContent', {
|
|
enumerable: baseDescriptor.enumerable,
|
|
configurable: true,
|
|
get: baseDescriptor.get,
|
|
set: /** @this {Node} */ function(assignedValue) {
|
|
// If this is a text node then there are no nodes to disconnect.
|
|
if (this.nodeType === Node.TEXT_NODE) {
|
|
baseDescriptor.set.call(this, assignedValue);
|
|
return;
|
|
}
|
|
|
|
let removedNodes = undefined;
|
|
// Checking for `firstChild` is faster than reading `childNodes.length`
|
|
// to compare with 0.
|
|
if (this.firstChild) {
|
|
// Using `childNodes` is faster than `children`, even though we only
|
|
// care about elements.
|
|
const childNodes = this.childNodes;
|
|
const childNodesLength = childNodes.length;
|
|
if (childNodesLength > 0 && isConnected(this)) {
|
|
// Copying an array by iterating is faster than using slice.
|
|
removedNodes = new Array(childNodesLength);
|
|
for (let i = 0; i < childNodesLength; i++) {
|
|
removedNodes[i] = childNodes[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
baseDescriptor.set.call(this, assignedValue);
|
|
|
|
if (removedNodes) {
|
|
for (let i = 0; i < removedNodes.length; i++) {
|
|
internals.disconnectTree(removedNodes[i]);
|
|
}
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
if (Native.Node_textContent && Native.Node_textContent.get) {
|
|
patch_textContent(Node.prototype, Native.Node_textContent);
|
|
} else {
|
|
internals.addPatch(function(element) {
|
|
patch_textContent(element, {
|
|
enumerable: true,
|
|
configurable: true,
|
|
// NOTE: This implementation of the `textContent` getter assumes that
|
|
// text nodes' `textContent` getter will not be patched.
|
|
get: /** @this {Node} */ function() {
|
|
/** @type {!Array<string>} */
|
|
const parts = [];
|
|
|
|
for (let i = 0; i < this.childNodes.length; i++) {
|
|
parts.push(this.childNodes[i].textContent);
|
|
}
|
|
|
|
return parts.join('');
|
|
},
|
|
set: /** @this {Node} */ function(assignedValue) {
|
|
while (this.firstChild) {
|
|
Native.Node_removeChild.call(this, this.firstChild);
|
|
}
|
|
Native.Node_appendChild.call(this, document.createTextNode(assignedValue));
|
|
},
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @param {!CustomElementInternals} internals
|
|
* @param {!Object} destination
|
|
* @param {!ChildNodeNativeMethods} builtIn
|
|
*/
|
|
var PatchChildNode = function(internals, destination, builtIn) {
|
|
/**
|
|
* @param {...(!Node|string)} nodes
|
|
*/
|
|
destination['before'] = function(...nodes) {
|
|
// TODO: Fix this for when one of `nodes` is a DocumentFragment!
|
|
const connectedBefore = /** @type {!Array<!Node>} */ (nodes.filter(node => {
|
|
// DocumentFragments are not connected and will not be added to the list.
|
|
return node instanceof Node && isConnected(node);
|
|
}));
|
|
|
|
builtIn.before.apply(this, nodes);
|
|
|
|
for (let i = 0; i < connectedBefore.length; i++) {
|
|
internals.disconnectTree(connectedBefore[i]);
|
|
}
|
|
|
|
if (isConnected(this)) {
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
const node = nodes[i];
|
|
if (node instanceof Element) {
|
|
internals.connectTree(node);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @param {...(!Node|string)} nodes
|
|
*/
|
|
destination['after'] = function(...nodes) {
|
|
// TODO: Fix this for when one of `nodes` is a DocumentFragment!
|
|
const connectedBefore = /** @type {!Array<!Node>} */ (nodes.filter(node => {
|
|
// DocumentFragments are not connected and will not be added to the list.
|
|
return node instanceof Node && isConnected(node);
|
|
}));
|
|
|
|
builtIn.after.apply(this, nodes);
|
|
|
|
for (let i = 0; i < connectedBefore.length; i++) {
|
|
internals.disconnectTree(connectedBefore[i]);
|
|
}
|
|
|
|
if (isConnected(this)) {
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
const node = nodes[i];
|
|
if (node instanceof Element) {
|
|
internals.connectTree(node);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @param {...(!Node|string)} nodes
|
|
*/
|
|
destination['replaceWith'] = function(...nodes) {
|
|
// TODO: Fix this for when one of `nodes` is a DocumentFragment!
|
|
const connectedBefore = /** @type {!Array<!Node>} */ (nodes.filter(node => {
|
|
// DocumentFragments are not connected and will not be added to the list.
|
|
return node instanceof Node && isConnected(node);
|
|
}));
|
|
|
|
const wasConnected = isConnected(this);
|
|
|
|
builtIn.replaceWith.apply(this, nodes);
|
|
|
|
for (let i = 0; i < connectedBefore.length; i++) {
|
|
internals.disconnectTree(connectedBefore[i]);
|
|
}
|
|
|
|
if (wasConnected) {
|
|
internals.disconnectTree(this);
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
const node = nodes[i];
|
|
if (node instanceof Element) {
|
|
internals.connectTree(node);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
destination['remove'] = function() {
|
|
const wasConnected = isConnected(this);
|
|
|
|
builtIn.remove.call(this);
|
|
|
|
if (wasConnected) {
|
|
internals.disconnectTree(this);
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @param {!CustomElementInternals} internals
|
|
*/
|
|
var PatchElement = function(internals) {
|
|
if (Native.Element_attachShadow) {
|
|
setPropertyUnchecked(Element.prototype, 'attachShadow',
|
|
/**
|
|
* @this {Element}
|
|
* @param {!{mode: string}} init
|
|
* @return {ShadowRoot}
|
|
*/
|
|
function(init) {
|
|
const shadowRoot = Native.Element_attachShadow.call(this, init);
|
|
this.__CE_shadowRoot = shadowRoot;
|
|
return shadowRoot;
|
|
});
|
|
} else {
|
|
console.warn('Custom Elements: `Element#attachShadow` was not patched.');
|
|
}
|
|
|
|
|
|
function patch_innerHTML(destination, baseDescriptor) {
|
|
Object.defineProperty(destination, 'innerHTML', {
|
|
enumerable: baseDescriptor.enumerable,
|
|
configurable: true,
|
|
get: baseDescriptor.get,
|
|
set: /** @this {Element} */ function(htmlString) {
|
|
const isConnected$$1 = isConnected(this);
|
|
|
|
// NOTE: In IE11, when using the native `innerHTML` setter, all nodes
|
|
// that were previously descendants of the context element have all of
|
|
// their children removed as part of the set - the entire subtree is
|
|
// 'disassembled'. This work around walks the subtree *before* using the
|
|
// native setter.
|
|
/** @type {!Array<!Element>|undefined} */
|
|
let removedElements = undefined;
|
|
if (isConnected$$1) {
|
|
removedElements = [];
|
|
walkDeepDescendantElements(this, element => {
|
|
if (element !== this) {
|
|
removedElements.push(element);
|
|
}
|
|
});
|
|
}
|
|
|
|
baseDescriptor.set.call(this, htmlString);
|
|
|
|
if (removedElements) {
|
|
for (let i = 0; i < removedElements.length; i++) {
|
|
const element = removedElements[i];
|
|
if (element.__CE_state === CustomElementState.custom) {
|
|
internals.disconnectedCallback(element);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only create custom elements if this element's owner document is
|
|
// associated with the registry.
|
|
if (!this.ownerDocument.__CE_hasRegistry) {
|
|
internals.patchTree(this);
|
|
} else {
|
|
internals.patchAndUpgradeTree(this);
|
|
}
|
|
return htmlString;
|
|
},
|
|
});
|
|
}
|
|
|
|
if (Native.Element_innerHTML && Native.Element_innerHTML.get) {
|
|
patch_innerHTML(Element.prototype, Native.Element_innerHTML);
|
|
} else if (Native.HTMLElement_innerHTML && Native.HTMLElement_innerHTML.get) {
|
|
patch_innerHTML(HTMLElement.prototype, Native.HTMLElement_innerHTML);
|
|
} else {
|
|
|
|
/** @type {HTMLDivElement} */
|
|
const rawDiv = Native.Document_createElement.call(document, 'div');
|
|
|
|
internals.addPatch(function(element) {
|
|
patch_innerHTML(element, {
|
|
enumerable: true,
|
|
configurable: true,
|
|
// Implements getting `innerHTML` by performing an unpatched `cloneNode`
|
|
// of the element and returning the resulting element's `innerHTML`.
|
|
// TODO: Is this too expensive?
|
|
get: /** @this {Element} */ function() {
|
|
return Native.Node_cloneNode.call(this, true).innerHTML;
|
|
},
|
|
// Implements setting `innerHTML` by creating an unpatched element,
|
|
// setting `innerHTML` of that element and replacing the target
|
|
// element's children with those of the unpatched element.
|
|
set: /** @this {Element} */ function(assignedValue) {
|
|
// NOTE: re-route to `content` for `template` elements.
|
|
// We need to do this because `template.appendChild` does not
|
|
// route into `template.content`.
|
|
/** @type {!Node} */
|
|
const content = this.localName === 'template' ? (/** @type {!HTMLTemplateElement} */ (this)).content : this;
|
|
rawDiv.innerHTML = assignedValue;
|
|
|
|
while (content.childNodes.length > 0) {
|
|
Native.Node_removeChild.call(content, content.childNodes[0]);
|
|
}
|
|
while (rawDiv.childNodes.length > 0) {
|
|
Native.Node_appendChild.call(content, rawDiv.childNodes[0]);
|
|
}
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
setPropertyUnchecked(Element.prototype, 'setAttribute',
|
|
/**
|
|
* @this {Element}
|
|
* @param {string} name
|
|
* @param {string} newValue
|
|
*/
|
|
function(name, newValue) {
|
|
// Fast path for non-custom elements.
|
|
if (this.__CE_state !== CustomElementState.custom) {
|
|
return Native.Element_setAttribute.call(this, name, newValue);
|
|
}
|
|
|
|
const oldValue = Native.Element_getAttribute.call(this, name);
|
|
Native.Element_setAttribute.call(this, name, newValue);
|
|
newValue = Native.Element_getAttribute.call(this, name);
|
|
if (oldValue !== newValue) {
|
|
internals.attributeChangedCallback(this, name, oldValue, newValue, null);
|
|
}
|
|
});
|
|
|
|
setPropertyUnchecked(Element.prototype, 'setAttributeNS',
|
|
/**
|
|
* @this {Element}
|
|
* @param {?string} namespace
|
|
* @param {string} name
|
|
* @param {string} newValue
|
|
*/
|
|
function(namespace, name, newValue) {
|
|
// Fast path for non-custom elements.
|
|
if (this.__CE_state !== CustomElementState.custom) {
|
|
return Native.Element_setAttributeNS.call(this, namespace, name, newValue);
|
|
}
|
|
|
|
const oldValue = Native.Element_getAttributeNS.call(this, namespace, name);
|
|
Native.Element_setAttributeNS.call(this, namespace, name, newValue);
|
|
newValue = Native.Element_getAttributeNS.call(this, namespace, name);
|
|
if (oldValue !== newValue) {
|
|
internals.attributeChangedCallback(this, name, oldValue, newValue, namespace);
|
|
}
|
|
});
|
|
|
|
setPropertyUnchecked(Element.prototype, 'removeAttribute',
|
|
/**
|
|
* @this {Element}
|
|
* @param {string} name
|
|
*/
|
|
function(name) {
|
|
// Fast path for non-custom elements.
|
|
if (this.__CE_state !== CustomElementState.custom) {
|
|
return Native.Element_removeAttribute.call(this, name);
|
|
}
|
|
|
|
const oldValue = Native.Element_getAttribute.call(this, name);
|
|
Native.Element_removeAttribute.call(this, name);
|
|
if (oldValue !== null) {
|
|
internals.attributeChangedCallback(this, name, oldValue, null, null);
|
|
}
|
|
});
|
|
|
|
setPropertyUnchecked(Element.prototype, 'removeAttributeNS',
|
|
/**
|
|
* @this {Element}
|
|
* @param {?string} namespace
|
|
* @param {string} name
|
|
*/
|
|
function(namespace, name) {
|
|
// Fast path for non-custom elements.
|
|
if (this.__CE_state !== CustomElementState.custom) {
|
|
return Native.Element_removeAttributeNS.call(this, namespace, name);
|
|
}
|
|
|
|
const oldValue = Native.Element_getAttributeNS.call(this, namespace, name);
|
|
Native.Element_removeAttributeNS.call(this, namespace, name);
|
|
// In older browsers, `Element#getAttributeNS` may return the empty string
|
|
// instead of null if the attribute does not exist. For details, see;
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttributeNS#Notes
|
|
const newValue = Native.Element_getAttributeNS.call(this, namespace, name);
|
|
if (oldValue !== newValue) {
|
|
internals.attributeChangedCallback(this, name, oldValue, newValue, namespace);
|
|
}
|
|
});
|
|
|
|
|
|
function patch_insertAdjacentElement(destination, baseMethod) {
|
|
setPropertyUnchecked(destination, 'insertAdjacentElement',
|
|
/**
|
|
* @this {Element}
|
|
* @param {string} where
|
|
* @param {!Element} element
|
|
* @return {?Element}
|
|
*/
|
|
function(where, element) {
|
|
const wasConnected = isConnected(element);
|
|
const insertedElement = /** @type {!Element} */
|
|
(baseMethod.call(this, where, element));
|
|
|
|
if (wasConnected) {
|
|
internals.disconnectTree(element);
|
|
}
|
|
|
|
if (isConnected(insertedElement)) {
|
|
internals.connectTree(element);
|
|
}
|
|
return insertedElement;
|
|
});
|
|
}
|
|
|
|
if (Native.HTMLElement_insertAdjacentElement) {
|
|
patch_insertAdjacentElement(HTMLElement.prototype, Native.HTMLElement_insertAdjacentElement);
|
|
} else if (Native.Element_insertAdjacentElement) {
|
|
patch_insertAdjacentElement(Element.prototype, Native.Element_insertAdjacentElement);
|
|
} else {
|
|
console.warn('Custom Elements: `Element#insertAdjacentElement` was not patched.');
|
|
}
|
|
|
|
|
|
PatchParentNode(internals, Element.prototype, {
|
|
prepend: Native.Element_prepend,
|
|
append: Native.Element_append,
|
|
});
|
|
|
|
PatchChildNode(internals, Element.prototype, {
|
|
before: Native.Element_before,
|
|
after: Native.Element_after,
|
|
replaceWith: Native.Element_replaceWith,
|
|
remove: Native.Element_remove,
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @license
|
|
* Copyright (c) 2016 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
|
|
*/
|
|
|
|
const priorCustomElements = window['customElements'];
|
|
|
|
if (!priorCustomElements ||
|
|
priorCustomElements['forcePolyfill'] ||
|
|
(typeof priorCustomElements['define'] != 'function') ||
|
|
(typeof priorCustomElements['get'] != 'function')) {
|
|
/** @type {!CustomElementInternals} */
|
|
const internals = new CustomElementInternals();
|
|
|
|
PatchHTMLElement(internals);
|
|
PatchDocument(internals);
|
|
PatchNode(internals);
|
|
PatchElement(internals);
|
|
|
|
// The main document is always associated with the registry.
|
|
document.__CE_hasRegistry = true;
|
|
|
|
/** @type {!CustomElementRegistry} */
|
|
const customElements = new CustomElementRegistry(internals);
|
|
|
|
Object.defineProperty(window, 'customElements', {
|
|
configurable: true,
|
|
enumerable: true,
|
|
value: customElements,
|
|
});
|
|
}
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2017 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
|
|
*/
|
|
|
|
/*
|
|
Extremely simple css parser. Intended to be not more than what we need
|
|
and definitely not necessarily correct =).
|
|
*/
|
|
|
|
/** @unrestricted */
|
|
class StyleNode {
|
|
constructor() {
|
|
/** @type {number} */
|
|
this['start'] = 0;
|
|
/** @type {number} */
|
|
this['end'] = 0;
|
|
/** @type {StyleNode} */
|
|
this['previous'] = null;
|
|
/** @type {StyleNode} */
|
|
this['parent'] = null;
|
|
/** @type {Array<StyleNode>} */
|
|
this['rules'] = null;
|
|
/** @type {string} */
|
|
this['parsedCssText'] = '';
|
|
/** @type {string} */
|
|
this['cssText'] = '';
|
|
/** @type {boolean} */
|
|
this['atRule'] = false;
|
|
/** @type {number} */
|
|
this['type'] = 0;
|
|
/** @type {string} */
|
|
this['keyframesName'] = '';
|
|
/** @type {string} */
|
|
this['selector'] = '';
|
|
/** @type {string} */
|
|
this['parsedSelector'] = '';
|
|
}
|
|
}
|
|
|
|
// given a string of css, return a simple rule tree
|
|
/**
|
|
* @param {string} text
|
|
* @return {StyleNode}
|
|
*/
|
|
function parse(text) {
|
|
text = clean(text);
|
|
return parseCss(lex(text), text);
|
|
}
|
|
|
|
// remove stuff we don't care about that may hinder parsing
|
|
/**
|
|
* @param {string} cssText
|
|
* @return {string}
|
|
*/
|
|
function clean(cssText) {
|
|
return cssText.replace(RX.comments, '').replace(RX.port, '');
|
|
}
|
|
|
|
// super simple {...} lexer that returns a node tree
|
|
/**
|
|
* @param {string} text
|
|
* @return {StyleNode}
|
|
*/
|
|
function lex(text) {
|
|
let root = new StyleNode();
|
|
root['start'] = 0;
|
|
root['end'] = text.length;
|
|
let n = root;
|
|
for (let i = 0, l = text.length; i < l; i++) {
|
|
if (text[i] === OPEN_BRACE) {
|
|
if (!n['rules']) {
|
|
n['rules'] = [];
|
|
}
|
|
let p = n;
|
|
let previous = p['rules'][p['rules'].length - 1] || null;
|
|
n = new StyleNode();
|
|
n['start'] = i + 1;
|
|
n['parent'] = p;
|
|
n['previous'] = previous;
|
|
p['rules'].push(n);
|
|
} else if (text[i] === CLOSE_BRACE) {
|
|
n['end'] = i + 1;
|
|
n = n['parent'] || root;
|
|
}
|
|
}
|
|
return root;
|
|
}
|
|
|
|
// add selectors/cssText to node tree
|
|
/**
|
|
* @param {StyleNode} node
|
|
* @param {string} text
|
|
* @return {StyleNode}
|
|
*/
|
|
function parseCss(node, text) {
|
|
let t = text.substring(node['start'], node['end'] - 1);
|
|
node['parsedCssText'] = node['cssText'] = t.trim();
|
|
if (node['parent']) {
|
|
let ss = node['previous'] ? node['previous']['end'] : node['parent']['start'];
|
|
t = text.substring(ss, node['start'] - 1);
|
|
t = _expandUnicodeEscapes(t);
|
|
t = t.replace(RX.multipleSpaces, ' ');
|
|
// TODO(sorvell): ad hoc; make selector include only after last ;
|
|
// helps with mixin syntax
|
|
t = t.substring(t.lastIndexOf(';') + 1);
|
|
let s = node['parsedSelector'] = node['selector'] = t.trim();
|
|
node['atRule'] = (s.indexOf(AT_START) === 0);
|
|
// note, support a subset of rule types...
|
|
if (node['atRule']) {
|
|
if (s.indexOf(MEDIA_START) === 0) {
|
|
node['type'] = types.MEDIA_RULE;
|
|
} else if (s.match(RX.keyframesRule)) {
|
|
node['type'] = types.KEYFRAMES_RULE;
|
|
node['keyframesName'] =
|
|
node['selector'].split(RX.multipleSpaces).pop();
|
|
}
|
|
} else {
|
|
if (s.indexOf(VAR_START) === 0) {
|
|
node['type'] = types.MIXIN_RULE;
|
|
} else {
|
|
node['type'] = types.STYLE_RULE;
|
|
}
|
|
}
|
|
}
|
|
let r$ = node['rules'];
|
|
if (r$) {
|
|
for (let i = 0, l = r$.length, r;
|
|
(i < l) && (r = r$[i]); i++) {
|
|
parseCss(r, text);
|
|
}
|
|
}
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* conversion of sort unicode escapes with spaces like `\33 ` (and longer) into
|
|
* expanded form that doesn't require trailing space `\000033`
|
|
* @param {string} s
|
|
* @return {string}
|
|
*/
|
|
function _expandUnicodeEscapes(s) {
|
|
return s.replace(/\\([0-9a-f]{1,6})\s/gi, function() {
|
|
let code = arguments[1],
|
|
repeat = 6 - code.length;
|
|
while (repeat--) {
|
|
code = '0' + code;
|
|
}
|
|
return '\\' + code;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* stringify parsed css.
|
|
* @param {StyleNode} node
|
|
* @param {boolean=} preserveProperties
|
|
* @param {string=} text
|
|
* @return {string}
|
|
*/
|
|
function stringify(node, preserveProperties, text = '') {
|
|
// calc rule cssText
|
|
let cssText = '';
|
|
if (node['cssText'] || node['rules']) {
|
|
let r$ = node['rules'];
|
|
if (r$ && !_hasMixinRules(r$)) {
|
|
for (let i = 0, l = r$.length, r;
|
|
(i < l) && (r = r$[i]); i++) {
|
|
cssText = stringify(r, preserveProperties, cssText);
|
|
}
|
|
} else {
|
|
cssText = preserveProperties ? node['cssText'] :
|
|
removeCustomProps(node['cssText']);
|
|
cssText = cssText.trim();
|
|
if (cssText) {
|
|
cssText = ' ' + cssText + '\n';
|
|
}
|
|
}
|
|
}
|
|
// emit rule if there is cssText
|
|
if (cssText) {
|
|
if (node['selector']) {
|
|
text += node['selector'] + ' ' + OPEN_BRACE + '\n';
|
|
}
|
|
text += cssText;
|
|
if (node['selector']) {
|
|
text += CLOSE_BRACE + '\n\n';
|
|
}
|
|
}
|
|
return text;
|
|
}
|
|
|
|
/**
|
|
* @param {Array<StyleNode>} rules
|
|
* @return {boolean}
|
|
*/
|
|
function _hasMixinRules(rules) {
|
|
let r = rules[0];
|
|
return Boolean(r) && Boolean(r['selector']) && r['selector'].indexOf(VAR_START) === 0;
|
|
}
|
|
|
|
/**
|
|
* @param {string} cssText
|
|
* @return {string}
|
|
*/
|
|
function removeCustomProps(cssText) {
|
|
cssText = removeCustomPropAssignment(cssText);
|
|
return removeCustomPropApply(cssText);
|
|
}
|
|
|
|
/**
|
|
* @param {string} cssText
|
|
* @return {string}
|
|
*/
|
|
function removeCustomPropAssignment(cssText) {
|
|
return cssText
|
|
.replace(RX.customProp, '')
|
|
.replace(RX.mixinProp, '');
|
|
}
|
|
|
|
/**
|
|
* @param {string} cssText
|
|
* @return {string}
|
|
*/
|
|
function removeCustomPropApply(cssText) {
|
|
return cssText
|
|
.replace(RX.mixinApply, '')
|
|
.replace(RX.varApply, '');
|
|
}
|
|
|
|
/** @enum {number} */
|
|
const types = {
|
|
STYLE_RULE: 1,
|
|
KEYFRAMES_RULE: 7,
|
|
MEDIA_RULE: 4,
|
|
MIXIN_RULE: 1000
|
|
};
|
|
|
|
const OPEN_BRACE = '{';
|
|
const CLOSE_BRACE = '}';
|
|
|
|
// helper regexp's
|
|
const RX = {
|
|
comments: /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,
|
|
port: /@import[^;]*;/gim,
|
|
customProp: /(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?(?:[;\n]|$)/gim,
|
|
mixinProp: /(?:^[^;\-\s}]+)?--[^;{}]*?:[^{};]*?{[^}]*?}(?:[;\n]|$)?/gim,
|
|
mixinApply: /@apply\s*\(?[^);]*\)?\s*(?:[;\n]|$)?/gim,
|
|
varApply: /[^;:]*?:[^;]*?var\([^;]*\)(?:[;\n]|$)?/gim,
|
|
keyframesRule: /^@[^\s]*keyframes/,
|
|
multipleSpaces: /\s+/g
|
|
};
|
|
|
|
const VAR_START = '--';
|
|
const MEDIA_START = '@media';
|
|
const AT_START = '@';
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2017 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
|
|
*/
|
|
|
|
let nativeShadow = !(window['ShadyDOM'] && window['ShadyDOM']['inUse']);
|
|
// chrome 49 has semi-working css vars, check if box-shadow works
|
|
// safari 9.1 has a recalc bug: https://bugs.webkit.org/show_bug.cgi?id=155782
|
|
let nativeCssVariables = (!navigator.userAgent.match('AppleWebKit/601') &&
|
|
window.CSS && CSS.supports && CSS.supports('box-shadow', '0 0 0 var(--foo)'));
|
|
|
|
/**
|
|
* @param {ShadyCSSOptions | ShadyCSSInterface | undefined} settings
|
|
*/
|
|
function parseSettings(settings) {
|
|
if (settings) {
|
|
nativeCssVariables = nativeCssVariables && !settings['nativeCss'] && !settings['shimcssproperties'];
|
|
nativeShadow = nativeShadow && !settings['nativeShadow'] && !settings['shimshadow'];
|
|
}
|
|
}
|
|
|
|
if (window.ShadyCSS) {
|
|
parseSettings(window.ShadyCSS);
|
|
} else if (window['WebComponents']) {
|
|
parseSettings(window['WebComponents']['flags']);
|
|
}
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2017 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
|
|
*/
|
|
|
|
const VAR_ASSIGN = /(?:^|[;\s{]\s*)(--[\w-]*?)\s*:\s*(?:([^;{]*)|{([^}]*)})(?:(?=[;\s}])|$)/gi;
|
|
const MIXIN_MATCH = /(?:^|\W+)@apply\s*\(?([^);\n]*)\)?/gi;
|
|
const VAR_CONSUMED = /(--[\w-]+)\s*([:,;)]|$)/gi;
|
|
const ANIMATION_MATCH = /(animation\s*:)|(animation-name\s*:)/;
|
|
const MEDIA_MATCH = /@media[^(]*(\([^)]*\))/;
|
|
|
|
const BRACKETED = /\{[^}]*\}/g;
|
|
const HOST_PREFIX = '(?:^|[^.#[:])';
|
|
const HOST_SUFFIX = '($|[.:[\\s>+~])';
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2017 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
|
|
*/
|
|
|
|
/**
|
|
* @param {string|StyleNode} rules
|
|
* @param {function(StyleNode)=} callback
|
|
* @return {string}
|
|
*/
|
|
function toCssText (rules, callback) {
|
|
if (!rules) {
|
|
return '';
|
|
}
|
|
if (typeof rules === 'string') {
|
|
rules = parse(rules);
|
|
}
|
|
if (callback) {
|
|
forEachRule(rules, callback);
|
|
}
|
|
return stringify(rules, nativeCssVariables);
|
|
}
|
|
|
|
/**
|
|
* @param {HTMLStyleElement} style
|
|
* @return {StyleNode}
|
|
*/
|
|
function rulesForStyle(style) {
|
|
if (!style['__cssRules'] && style.textContent) {
|
|
style['__cssRules'] = parse(style.textContent);
|
|
}
|
|
return style['__cssRules'] || null;
|
|
}
|
|
|
|
// Tests if a rule is a keyframes selector, which looks almost exactly
|
|
// like a normal selector but is not (it has nothing to do with scoping
|
|
// for example).
|
|
/**
|
|
* @param {StyleNode} rule
|
|
* @return {boolean}
|
|
*/
|
|
function isKeyframesSelector(rule) {
|
|
return Boolean(rule['parent']) &&
|
|
rule['parent']['type'] === types.KEYFRAMES_RULE;
|
|
}
|
|
|
|
/**
|
|
* @param {StyleNode} node
|
|
* @param {Function=} styleRuleCallback
|
|
* @param {Function=} keyframesRuleCallback
|
|
* @param {boolean=} onlyActiveRules
|
|
*/
|
|
function forEachRule(node, styleRuleCallback, keyframesRuleCallback, onlyActiveRules) {
|
|
if (!node) {
|
|
return;
|
|
}
|
|
let skipRules = false;
|
|
let type = node['type'];
|
|
if (onlyActiveRules) {
|
|
if (type === types.MEDIA_RULE) {
|
|
let matchMedia = node['selector'].match(MEDIA_MATCH);
|
|
if (matchMedia) {
|
|
// if rule is a non matching @media rule, skip subrules
|
|
if (!window.matchMedia(matchMedia[1]).matches) {
|
|
skipRules = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (type === types.STYLE_RULE) {
|
|
styleRuleCallback(node);
|
|
} else if (keyframesRuleCallback &&
|
|
type === types.KEYFRAMES_RULE) {
|
|
keyframesRuleCallback(node);
|
|
} else if (type === types.MIXIN_RULE) {
|
|
skipRules = true;
|
|
}
|
|
let r$ = node['rules'];
|
|
if (r$ && !skipRules) {
|
|
for (let i=0, l=r$.length, r; (i<l) && (r=r$[i]); i++) {
|
|
forEachRule(r, styleRuleCallback, keyframesRuleCallback, onlyActiveRules);
|
|
}
|
|
}
|
|
}
|
|
|
|
// add a string of cssText to the document.
|
|
/**
|
|
* @param {string} cssText
|
|
* @param {string} moniker
|
|
* @param {Node} target
|
|
* @param {Node} contextNode
|
|
* @return {HTMLStyleElement}
|
|
*/
|
|
function applyCss(cssText, moniker, target, contextNode) {
|
|
let style = createScopeStyle(cssText, moniker);
|
|
applyStyle(style, target, contextNode);
|
|
return style;
|
|
}
|
|
|
|
/**
|
|
* @param {string} cssText
|
|
* @param {string} moniker
|
|
* @return {HTMLStyleElement}
|
|
*/
|
|
function createScopeStyle(cssText, moniker) {
|
|
let style = /** @type {HTMLStyleElement} */(document.createElement('style'));
|
|
if (moniker) {
|
|
style.setAttribute('scope', moniker);
|
|
}
|
|
style.textContent = cssText;
|
|
return style;
|
|
}
|
|
|
|
/**
|
|
* Track the position of the last added style for placing placeholders
|
|
* @type {Node}
|
|
*/
|
|
let lastHeadApplyNode = null;
|
|
|
|
// insert a comment node as a styling position placeholder.
|
|
/**
|
|
* @param {string} moniker
|
|
* @return {!Comment}
|
|
*/
|
|
function applyStylePlaceHolder(moniker) {
|
|
let placeHolder = document.createComment(' Shady DOM styles for ' +
|
|
moniker + ' ');
|
|
let after = lastHeadApplyNode ?
|
|
lastHeadApplyNode['nextSibling'] : null;
|
|
let scope = document.head;
|
|
scope.insertBefore(placeHolder, after || scope.firstChild);
|
|
lastHeadApplyNode = placeHolder;
|
|
return placeHolder;
|
|
}
|
|
|
|
/**
|
|
* @param {HTMLStyleElement} style
|
|
* @param {?Node} target
|
|
* @param {?Node} contextNode
|
|
*/
|
|
function applyStyle(style, target, contextNode) {
|
|
target = target || document.head;
|
|
let after = (contextNode && contextNode.nextSibling) ||
|
|
target.firstChild;
|
|
target.insertBefore(style, after);
|
|
if (!lastHeadApplyNode) {
|
|
lastHeadApplyNode = style;
|
|
} else {
|
|
// only update lastHeadApplyNode if the new style is inserted after the old lastHeadApplyNode
|
|
let position = style.compareDocumentPosition(lastHeadApplyNode);
|
|
if (position === Node.DOCUMENT_POSITION_PRECEDING) {
|
|
lastHeadApplyNode = style;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} buildType
|
|
* @return {boolean}
|
|
*/
|
|
|
|
|
|
/**
|
|
* @param {Element} element
|
|
* @return {?string}
|
|
*/
|
|
|
|
|
|
/**
|
|
* Walk from text[start] matching parens and
|
|
* returns position of the outer end paren
|
|
* @param {string} text
|
|
* @param {number} start
|
|
* @return {number}
|
|
*/
|
|
function findMatchingParen(text, start) {
|
|
let level = 0;
|
|
for (let i=start, l=text.length; i < l; i++) {
|
|
if (text[i] === '(') {
|
|
level++;
|
|
} else if (text[i] === ')') {
|
|
if (--level === 0) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* @param {string} str
|
|
* @param {function(string, string, string, string)} callback
|
|
*/
|
|
function processVariableAndFallback(str, callback) {
|
|
// find 'var('
|
|
let start = str.indexOf('var(');
|
|
if (start === -1) {
|
|
// no var?, everything is prefix
|
|
return callback(str, '', '', '');
|
|
}
|
|
//${prefix}var(${inner})${suffix}
|
|
let end = findMatchingParen(str, start + 3);
|
|
let inner = str.substring(start + 4, end);
|
|
let prefix = str.substring(0, start);
|
|
// suffix may have other variables
|
|
let suffix = processVariableAndFallback(str.substring(end + 1), callback);
|
|
let comma = inner.indexOf(',');
|
|
// value and fallback args should be trimmed to match in property lookup
|
|
if (comma === -1) {
|
|
// variable, no fallback
|
|
return callback(prefix, inner.trim(), '', suffix);
|
|
}
|
|
// var(${value},${fallback})
|
|
let value = inner.substring(0, comma).trim();
|
|
let fallback = inner.substring(comma + 1).trim();
|
|
return callback(prefix, value, fallback, suffix);
|
|
}
|
|
|
|
/**
|
|
* @param {Element} element
|
|
* @param {string} value
|
|
*/
|
|
function setElementClassRaw(element, value) {
|
|
// use native setAttribute provided by ShadyDOM when setAttribute is patched
|
|
if (nativeShadow) {
|
|
element.setAttribute('class', value);
|
|
} else {
|
|
window['ShadyDOM']['nativeMethods']['setAttribute'].call(element, 'class', value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Element | {is: string, extends: string}} element
|
|
* @return {{is: string, typeExtension: string}}
|
|
*/
|
|
function getIsExtends(element) {
|
|
let localName = element['localName'];
|
|
let is = '', typeExtension = '';
|
|
/*
|
|
NOTE: technically, this can be wrong for certain svg elements
|
|
with `-` in the name like `<font-face>`
|
|
*/
|
|
if (localName) {
|
|
if (localName.indexOf('-') > -1) {
|
|
is = localName;
|
|
} else {
|
|
typeExtension = localName;
|
|
is = (element.getAttribute && element.getAttribute('is')) || '';
|
|
}
|
|
} else {
|
|
is = /** @type {?} */(element).is;
|
|
typeExtension = /** @type {?} */(element).extends;
|
|
}
|
|
return {is, typeExtension};
|
|
}
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2017 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
|
|
*/
|
|
|
|
/* Transforms ShadowDOM styling into ShadyDOM styling
|
|
|
|
* scoping:
|
|
|
|
* elements in scope get scoping selector class="x-foo-scope"
|
|
* selectors re-written as follows:
|
|
|
|
div button -> div.x-foo-scope button.x-foo-scope
|
|
|
|
* :host -> scopeName
|
|
|
|
* :host(...) -> scopeName...
|
|
|
|
* ::slotted(...) -> scopeName > ...
|
|
|
|
* ...:dir(ltr|rtl) -> [dir="ltr|rtl"] ..., ...[dir="ltr|rtl"]
|
|
|
|
* :host(:dir[rtl]) -> scopeName:dir(rtl) -> [dir="rtl"] scopeName, scopeName[dir="rtl"]
|
|
|
|
*/
|
|
const SCOPE_NAME = 'style-scope';
|
|
|
|
class StyleTransformer {
|
|
get SCOPE_NAME() {
|
|
return SCOPE_NAME;
|
|
}
|
|
// Given a node and scope name, add a scoping class to each node
|
|
// in the tree. This facilitates transforming css into scoped rules.
|
|
dom(node, scope, shouldRemoveScope) {
|
|
// one time optimization to skip scoping...
|
|
if (node['__styleScoped']) {
|
|
node['__styleScoped'] = null;
|
|
} else {
|
|
this._transformDom(node, scope || '', shouldRemoveScope);
|
|
}
|
|
}
|
|
|
|
_transformDom(node, selector, shouldRemoveScope) {
|
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
this.element(node, selector, shouldRemoveScope);
|
|
}
|
|
let c$ = (node.localName === 'template') ?
|
|
(node.content || node._content).childNodes :
|
|
node.children || node.childNodes;
|
|
if (c$) {
|
|
for (let i=0; i<c$.length; i++) {
|
|
this._transformDom(c$[i], selector, shouldRemoveScope);
|
|
}
|
|
}
|
|
}
|
|
|
|
element(element, scope, shouldRemoveScope) {
|
|
// note: if using classes, we add both the general 'style-scope' class
|
|
// as well as the specific scope. This enables easy filtering of all
|
|
// `style-scope` elements
|
|
if (scope) {
|
|
// note: svg on IE does not have classList so fallback to class
|
|
if (element.classList) {
|
|
if (shouldRemoveScope) {
|
|
element.classList.remove(SCOPE_NAME);
|
|
element.classList.remove(scope);
|
|
} else {
|
|
element.classList.add(SCOPE_NAME);
|
|
element.classList.add(scope);
|
|
}
|
|
} else if (element.getAttribute) {
|
|
let c = element.getAttribute(CLASS);
|
|
if (shouldRemoveScope) {
|
|
if (c) {
|
|
let newValue = c.replace(SCOPE_NAME, '').replace(scope, '');
|
|
setElementClassRaw(element, newValue);
|
|
}
|
|
} else {
|
|
let newValue = (c ? c + ' ' : '') + SCOPE_NAME + ' ' + scope;
|
|
setElementClassRaw(element, newValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
elementStyles(element, styleRules, callback) {
|
|
let cssBuildType = element['__cssBuild'];
|
|
// no need to shim selectors if settings.useNativeShadow, also
|
|
// a shady css build will already have transformed selectors
|
|
// NOTE: This method may be called as part of static or property shimming.
|
|
// When there is a targeted build it will not be called for static shimming,
|
|
// but when the property shim is used it is called and should opt out of
|
|
// static shimming work when a proper build exists.
|
|
let cssText = '';
|
|
if (nativeShadow || cssBuildType === 'shady') {
|
|
cssText = toCssText(styleRules, callback);
|
|
} else {
|
|
let {is, typeExtension} = getIsExtends(element);
|
|
cssText = this.css(styleRules, is, typeExtension, callback) + '\n\n';
|
|
}
|
|
return cssText.trim();
|
|
}
|
|
|
|
// Given a string of cssText and a scoping string (scope), returns
|
|
// a string of scoped css where each selector is transformed to include
|
|
// a class created from the scope. ShadowDOM selectors are also transformed
|
|
// (e.g. :host) to use the scoping selector.
|
|
css(rules, scope, ext, callback) {
|
|
let hostScope = this._calcHostScope(scope, ext);
|
|
scope = this._calcElementScope(scope);
|
|
let self = this;
|
|
return toCssText(rules, function(/** StyleNode */rule) {
|
|
if (!rule.isScoped) {
|
|
self.rule(rule, scope, hostScope);
|
|
rule.isScoped = true;
|
|
}
|
|
if (callback) {
|
|
callback(rule, scope, hostScope);
|
|
}
|
|
});
|
|
}
|
|
|
|
_calcElementScope(scope) {
|
|
if (scope) {
|
|
return CSS_CLASS_PREFIX + scope;
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
_calcHostScope(scope, ext) {
|
|
return ext ? `[is=${scope}]` : scope;
|
|
}
|
|
|
|
rule(rule, scope, hostScope) {
|
|
this._transformRule(rule, this._transformComplexSelector,
|
|
scope, hostScope);
|
|
}
|
|
|
|
/**
|
|
* transforms a css rule to a scoped rule.
|
|
*
|
|
* @param {StyleNode} rule
|
|
* @param {Function} transformer
|
|
* @param {string=} scope
|
|
* @param {string=} hostScope
|
|
*/
|
|
_transformRule(rule, transformer, scope, hostScope) {
|
|
// NOTE: save transformedSelector for subsequent matching of elements
|
|
// against selectors (e.g. when calculating style properties)
|
|
rule['selector'] = rule.transformedSelector =
|
|
this._transformRuleCss(rule, transformer, scope, hostScope);
|
|
}
|
|
|
|
/**
|
|
* @param {StyleNode} rule
|
|
* @param {Function} transformer
|
|
* @param {string=} scope
|
|
* @param {string=} hostScope
|
|
*/
|
|
_transformRuleCss(rule, transformer, scope, hostScope) {
|
|
let p$ = rule['selector'].split(COMPLEX_SELECTOR_SEP);
|
|
// we want to skip transformation of rules that appear in keyframes,
|
|
// because they are keyframe selectors, not element selectors.
|
|
if (!isKeyframesSelector(rule)) {
|
|
for (let i=0, l=p$.length, p; (i<l) && (p=p$[i]); i++) {
|
|
p$[i] = transformer.call(this, p, scope, hostScope);
|
|
}
|
|
}
|
|
return p$.join(COMPLEX_SELECTOR_SEP);
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
* @param {string} scope
|
|
* @param {string=} hostScope
|
|
*/
|
|
_transformComplexSelector(selector, scope, hostScope) {
|
|
let stop = false;
|
|
selector = selector.trim();
|
|
// Remove spaces inside of selectors like `:nth-of-type` because it confuses SIMPLE_SELECTOR_SEP
|
|
selector = selector.replace(NTH, (m, type, inner) => `:${type}(${inner.replace(/\s/g, '')})`);
|
|
selector = selector.replace(SLOTTED_START, `${HOST} $1`);
|
|
selector = selector.replace(SIMPLE_SELECTOR_SEP, (m, c, s) => {
|
|
if (!stop) {
|
|
let info = this._transformCompoundSelector(s, c, scope, hostScope);
|
|
stop = stop || info.stop;
|
|
c = info.combinator;
|
|
s = info.value;
|
|
}
|
|
return c + s;
|
|
});
|
|
return selector;
|
|
}
|
|
|
|
_transformCompoundSelector(selector, combinator, scope, hostScope) {
|
|
// replace :host with host scoping class
|
|
let slottedIndex = selector.indexOf(SLOTTED);
|
|
if (selector.indexOf(HOST) >= 0) {
|
|
selector = this._transformHostSelector(selector, hostScope);
|
|
// replace other selectors with scoping class
|
|
} else if (slottedIndex !== 0) {
|
|
selector = scope ? this._transformSimpleSelector(selector, scope) :
|
|
selector;
|
|
}
|
|
// mark ::slotted() scope jump to replace with descendant selector + arg
|
|
// also ignore left-side combinator
|
|
let slotted = false;
|
|
if (slottedIndex >= 0) {
|
|
combinator = '';
|
|
slotted = true;
|
|
}
|
|
// process scope jumping selectors up to the scope jump and then stop
|
|
let stop;
|
|
if (slotted) {
|
|
stop = true;
|
|
if (slotted) {
|
|
// .zonk ::slotted(.foo) -> .zonk.scope > .foo
|
|
selector = selector.replace(SLOTTED_PAREN, (m, paren) => ` > ${paren}`);
|
|
}
|
|
}
|
|
selector = selector.replace(DIR_PAREN, (m, before, dir) =>
|
|
`[dir="${dir}"] ${before}, ${before}[dir="${dir}"]`);
|
|
return {value: selector, combinator, stop};
|
|
}
|
|
|
|
_transformSimpleSelector(selector, scope) {
|
|
let p$ = selector.split(PSEUDO_PREFIX);
|
|
p$[0] += scope;
|
|
return p$.join(PSEUDO_PREFIX);
|
|
}
|
|
|
|
// :host(...) -> scopeName...
|
|
_transformHostSelector(selector, hostScope) {
|
|
let m = selector.match(HOST_PAREN);
|
|
let paren = m && m[2].trim() || '';
|
|
if (paren) {
|
|
if (!paren[0].match(SIMPLE_SELECTOR_PREFIX)) {
|
|
// paren starts with a type selector
|
|
let typeSelector = paren.split(SIMPLE_SELECTOR_PREFIX)[0];
|
|
// if the type selector is our hostScope then avoid pre-pending it
|
|
if (typeSelector === hostScope) {
|
|
return paren;
|
|
// otherwise, this selector should not match in this scope so
|
|
// output a bogus selector.
|
|
} else {
|
|
return SELECTOR_NO_MATCH;
|
|
}
|
|
} else {
|
|
// make sure to do a replace here to catch selectors like:
|
|
// `:host(.foo)::before`
|
|
return selector.replace(HOST_PAREN, function(m, host, paren) {
|
|
return hostScope + paren;
|
|
});
|
|
}
|
|
// if no paren, do a straight :host replacement.
|
|
// TODO(sorvell): this should not strictly be necessary but
|
|
// it's needed to maintain support for `:host[foo]` type selectors
|
|
// which have been improperly used under Shady DOM. This should be
|
|
// deprecated.
|
|
} else {
|
|
return selector.replace(HOST, hostScope);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {StyleNode} rule
|
|
*/
|
|
documentRule(rule) {
|
|
// reset selector in case this is redone.
|
|
rule['selector'] = rule['parsedSelector'];
|
|
this.normalizeRootSelector(rule);
|
|
this._transformRule(rule, this._transformDocumentSelector);
|
|
}
|
|
|
|
/**
|
|
* @param {StyleNode} rule
|
|
*/
|
|
normalizeRootSelector(rule) {
|
|
if (rule['selector'] === ROOT) {
|
|
rule['selector'] = 'html';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
*/
|
|
_transformDocumentSelector(selector) {
|
|
return selector.match(SLOTTED) ?
|
|
this._transformComplexSelector(selector, SCOPE_DOC_SELECTOR) :
|
|
this._transformSimpleSelector(selector.trim(), SCOPE_DOC_SELECTOR);
|
|
}
|
|
}
|
|
|
|
let NTH = /:(nth[-\w]+)\(([^)]+)\)/;
|
|
let SCOPE_DOC_SELECTOR = `:not(.${SCOPE_NAME})`;
|
|
let COMPLEX_SELECTOR_SEP = ',';
|
|
let SIMPLE_SELECTOR_SEP = /(^|[\s>+~]+)((?:\[.+?\]|[^\s>+~=\[])+)/g;
|
|
let SIMPLE_SELECTOR_PREFIX = /[[.:#*]/;
|
|
let HOST = ':host';
|
|
let ROOT = ':root';
|
|
let SLOTTED = '::slotted';
|
|
let SLOTTED_START = new RegExp(`^(${SLOTTED})`);
|
|
// NOTE: this supports 1 nested () pair for things like
|
|
// :host(:not([selected]), more general support requires
|
|
// parsing which seems like overkill
|
|
let HOST_PAREN = /(:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/;
|
|
// similar to HOST_PAREN
|
|
let SLOTTED_PAREN = /(?:::slotted)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/;
|
|
let DIR_PAREN = /(.*):dir\((?:(ltr|rtl))\)/;
|
|
let CSS_CLASS_PREFIX = '.';
|
|
let PSEUDO_PREFIX = ':';
|
|
let CLASS = 'class';
|
|
let SELECTOR_NO_MATCH = 'should_not_match';
|
|
|
|
var StyleTransformer$1 = new StyleTransformer();
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2017 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
|
|
*/
|
|
|
|
/** @const {string} */
|
|
const infoKey = '__styleInfo';
|
|
|
|
class StyleInfo {
|
|
/**
|
|
* @param {Element} node
|
|
* @return {StyleInfo}
|
|
*/
|
|
static get(node) {
|
|
if (node) {
|
|
return node[infoKey];
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
/**
|
|
* @param {!Element} node
|
|
* @param {StyleInfo} styleInfo
|
|
* @return {StyleInfo}
|
|
*/
|
|
static set(node, styleInfo) {
|
|
node[infoKey] = styleInfo;
|
|
return styleInfo;
|
|
}
|
|
/**
|
|
* @param {StyleNode} ast
|
|
* @param {Node=} placeholder
|
|
* @param {Array<string>=} ownStylePropertyNames
|
|
* @param {string=} elementName
|
|
* @param {string=} typeExtension
|
|
* @param {string=} cssBuild
|
|
*/
|
|
constructor(ast, placeholder, ownStylePropertyNames, elementName, typeExtension, cssBuild) {
|
|
/** @type {StyleNode} */
|
|
this.styleRules = ast || null;
|
|
/** @type {Node} */
|
|
this.placeholder = placeholder || null;
|
|
/** @type {!Array<string>} */
|
|
this.ownStylePropertyNames = ownStylePropertyNames || [];
|
|
/** @type {Array<Object>} */
|
|
this.overrideStyleProperties = null;
|
|
/** @type {string} */
|
|
this.elementName = elementName || '';
|
|
/** @type {string} */
|
|
this.cssBuild = cssBuild || '';
|
|
/** @type {string} */
|
|
this.typeExtension = typeExtension || '';
|
|
/** @type {Object<string, string>} */
|
|
this.styleProperties = null;
|
|
/** @type {?string} */
|
|
this.scopeSelector = null;
|
|
/** @type {HTMLStyleElement} */
|
|
this.customStyle = null;
|
|
}
|
|
_getStyleRules() {
|
|
return this.styleRules;
|
|
}
|
|
}
|
|
|
|
StyleInfo.prototype['_getStyleRules'] = StyleInfo.prototype._getStyleRules;
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2017 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
|
|
*/
|
|
|
|
// TODO: dedupe with shady
|
|
/**
|
|
* @const {function(string):boolean}
|
|
*/
|
|
const matchesSelector$1 = ((p) => p.matches || p.matchesSelector ||
|
|
p.mozMatchesSelector || p.msMatchesSelector ||
|
|
p.oMatchesSelector || p.webkitMatchesSelector)(window.Element.prototype);
|
|
|
|
const IS_IE = navigator.userAgent.match('Trident');
|
|
|
|
const XSCOPE_NAME = 'x-scope';
|
|
|
|
class StyleProperties {
|
|
get XSCOPE_NAME() {
|
|
return XSCOPE_NAME;
|
|
}
|
|
/**
|
|
* decorates styles with rule info and returns an array of used style property names
|
|
*
|
|
* @param {StyleNode} rules
|
|
* @return {Array<string>}
|
|
*/
|
|
decorateStyles(rules) {
|
|
let self = this, props = {}, keyframes = [], ruleIndex = 0;
|
|
forEachRule(rules, function(rule) {
|
|
self.decorateRule(rule);
|
|
// mark in-order position of ast rule in styles block, used for cache key
|
|
rule.index = ruleIndex++;
|
|
self.collectPropertiesInCssText(rule.propertyInfo.cssText, props);
|
|
}, function onKeyframesRule(rule) {
|
|
keyframes.push(rule);
|
|
});
|
|
// Cache all found keyframes rules for later reference:
|
|
rules._keyframes = keyframes;
|
|
// return this list of property names *consumes* in these styles.
|
|
let names = [];
|
|
for (let i in props) {
|
|
names.push(i);
|
|
}
|
|
return names;
|
|
}
|
|
|
|
// decorate a single rule with property info
|
|
decorateRule(rule) {
|
|
if (rule.propertyInfo) {
|
|
return rule.propertyInfo;
|
|
}
|
|
let info = {}, properties = {};
|
|
let hasProperties = this.collectProperties(rule, properties);
|
|
if (hasProperties) {
|
|
info.properties = properties;
|
|
// TODO(sorvell): workaround parser seeing mixins as additional rules
|
|
rule['rules'] = null;
|
|
}
|
|
info.cssText = this.collectCssText(rule);
|
|
rule.propertyInfo = info;
|
|
return info;
|
|
}
|
|
|
|
// collects the custom properties from a rule's cssText
|
|
collectProperties(rule, properties) {
|
|
let info = rule.propertyInfo;
|
|
if (info) {
|
|
if (info.properties) {
|
|
Object.assign(properties, info.properties);
|
|
return true;
|
|
}
|
|
} else {
|
|
let m, rx = VAR_ASSIGN;
|
|
let cssText = rule['parsedCssText'];
|
|
let value;
|
|
let any;
|
|
while ((m = rx.exec(cssText))) {
|
|
// note: group 2 is var, 3 is mixin
|
|
value = (m[2] || m[3]).trim();
|
|
// value of 'inherit' or 'unset' is equivalent to not setting the property here
|
|
if (value !== 'inherit' || value !== 'unset') {
|
|
properties[m[1].trim()] = value;
|
|
}
|
|
any = true;
|
|
}
|
|
return any;
|
|
}
|
|
|
|
}
|
|
|
|
// returns cssText of properties that consume variables/mixins
|
|
collectCssText(rule) {
|
|
return this.collectConsumingCssText(rule['parsedCssText']);
|
|
}
|
|
|
|
// NOTE: we support consumption inside mixin assignment
|
|
// but not production, so strip out {...}
|
|
collectConsumingCssText(cssText) {
|
|
return cssText.replace(BRACKETED, '')
|
|
.replace(VAR_ASSIGN, '');
|
|
}
|
|
|
|
collectPropertiesInCssText(cssText, props) {
|
|
let m;
|
|
while ((m = VAR_CONSUMED.exec(cssText))) {
|
|
let name = m[1];
|
|
// This regex catches all variable names, and following non-whitespace char
|
|
// If next char is not ':', then variable is a consumer
|
|
if (m[2] !== ':') {
|
|
props[name] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// turns custom properties into realized values.
|
|
reify(props) {
|
|
// big perf optimization here: reify only *own* properties
|
|
// since this object has __proto__ of the element's scope properties
|
|
let names = Object.getOwnPropertyNames(props);
|
|
for (let i=0, n; i < names.length; i++) {
|
|
n = names[i];
|
|
props[n] = this.valueForProperty(props[n], props);
|
|
}
|
|
}
|
|
|
|
// given a property value, returns the reified value
|
|
// a property value may be:
|
|
// (1) a literal value like: red or 5px;
|
|
// (2) a variable value like: var(--a), var(--a, red), or var(--a, --b) or
|
|
// var(--a, var(--b));
|
|
// (3) a literal mixin value like { properties }. Each of these properties
|
|
// can have values that are: (a) literal, (b) variables, (c) @apply mixins.
|
|
valueForProperty(property, props) {
|
|
// case (1) default
|
|
// case (3) defines a mixin and we have to reify the internals
|
|
if (property) {
|
|
if (property.indexOf(';') >=0) {
|
|
property = this.valueForProperties(property, props);
|
|
} else {
|
|
// case (2) variable
|
|
let self = this;
|
|
let fn = function(prefix, value, fallback, suffix) {
|
|
if (!value) {
|
|
return prefix + suffix;
|
|
}
|
|
let propertyValue = self.valueForProperty(props[value], props);
|
|
// if value is "initial", then the variable should be treated as unset
|
|
if (!propertyValue || propertyValue === 'initial') {
|
|
// fallback may be --a or var(--a) or literal
|
|
propertyValue = self.valueForProperty(props[fallback] || fallback, props) ||
|
|
fallback;
|
|
} else if (propertyValue === 'apply-shim-inherit') {
|
|
// CSS build will replace `inherit` with `apply-shim-inherit`
|
|
// for use with native css variables.
|
|
// Since we have full control, we can use `inherit` directly.
|
|
propertyValue = 'inherit';
|
|
}
|
|
return prefix + (propertyValue || '') + suffix;
|
|
};
|
|
property = processVariableAndFallback(property, fn);
|
|
}
|
|
}
|
|
return property && property.trim() || '';
|
|
}
|
|
|
|
// note: we do not yet support mixin within mixin
|
|
valueForProperties(property, props) {
|
|
let parts = property.split(';');
|
|
for (let i=0, p, m; i<parts.length; i++) {
|
|
if ((p = parts[i])) {
|
|
MIXIN_MATCH.lastIndex = 0;
|
|
m = MIXIN_MATCH.exec(p);
|
|
if (m) {
|
|
p = this.valueForProperty(props[m[1]], props);
|
|
} else {
|
|
let colon = p.indexOf(':');
|
|
if (colon !== -1) {
|
|
let pp = p.substring(colon);
|
|
pp = pp.trim();
|
|
pp = this.valueForProperty(pp, props) || pp;
|
|
p = p.substring(0, colon) + pp;
|
|
}
|
|
}
|
|
parts[i] = (p && p.lastIndexOf(';') === p.length - 1) ?
|
|
// strip trailing ;
|
|
p.slice(0, -1) :
|
|
p || '';
|
|
}
|
|
}
|
|
return parts.join(';');
|
|
}
|
|
|
|
applyProperties(rule, props) {
|
|
let output = '';
|
|
// dynamically added sheets may not be decorated so ensure they are.
|
|
if (!rule.propertyInfo) {
|
|
this.decorateRule(rule);
|
|
}
|
|
if (rule.propertyInfo.cssText) {
|
|
output = this.valueForProperties(rule.propertyInfo.cssText, props);
|
|
}
|
|
rule['cssText'] = output;
|
|
}
|
|
|
|
// Apply keyframe transformations to the cssText of a given rule. The
|
|
// keyframeTransforms object is a map of keyframe names to transformer
|
|
// functions which take in cssText and spit out transformed cssText.
|
|
applyKeyframeTransforms(rule, keyframeTransforms) {
|
|
let input = rule['cssText'];
|
|
let output = rule['cssText'];
|
|
if (rule.hasAnimations == null) {
|
|
// Cache whether or not the rule has any animations to begin with:
|
|
rule.hasAnimations = ANIMATION_MATCH.test(input);
|
|
}
|
|
// If there are no animations referenced, we can skip transforms:
|
|
if (rule.hasAnimations) {
|
|
let transform;
|
|
// If we haven't transformed this rule before, we iterate over all
|
|
// transforms:
|
|
if (rule.keyframeNamesToTransform == null) {
|
|
rule.keyframeNamesToTransform = [];
|
|
for (let keyframe in keyframeTransforms) {
|
|
transform = keyframeTransforms[keyframe];
|
|
output = transform(input);
|
|
// If the transform actually changed the CSS text, we cache the
|
|
// transform name for future use:
|
|
if (input !== output) {
|
|
input = output;
|
|
rule.keyframeNamesToTransform.push(keyframe);
|
|
}
|
|
}
|
|
} else {
|
|
// If we already have a list of keyframe names that apply to this
|
|
// rule, we apply only those keyframe name transforms:
|
|
for (let i = 0; i < rule.keyframeNamesToTransform.length; ++i) {
|
|
transform = keyframeTransforms[rule.keyframeNamesToTransform[i]];
|
|
input = transform(input);
|
|
}
|
|
output = input;
|
|
}
|
|
}
|
|
rule['cssText'] = output;
|
|
}
|
|
|
|
// Test if the rules in these styles matches the given `element` and if so,
|
|
// collect any custom properties into `props`.
|
|
/**
|
|
* @param {StyleNode} rules
|
|
* @param {Element} element
|
|
*/
|
|
propertyDataFromStyles(rules, element) {
|
|
let props = {}, self = this;
|
|
// generates a unique key for these matches
|
|
let o = [];
|
|
// note: active rules excludes non-matching @media rules
|
|
forEachRule(rules, function(rule) {
|
|
// TODO(sorvell): we could trim the set of rules at declaration
|
|
// time to only include ones that have properties
|
|
if (!rule.propertyInfo) {
|
|
self.decorateRule(rule);
|
|
}
|
|
// match element against transformedSelector: selector may contain
|
|
// unwanted uniquification and parsedSelector does not directly match
|
|
// for :host selectors.
|
|
let selectorToMatch = rule.transformedSelector || rule['parsedSelector'];
|
|
if (element && rule.propertyInfo.properties && selectorToMatch) {
|
|
if (matchesSelector$1.call(element, selectorToMatch)) {
|
|
self.collectProperties(rule, props);
|
|
// produce numeric key for these matches for lookup
|
|
addToBitMask(rule.index, o);
|
|
}
|
|
}
|
|
}, null, true);
|
|
return {properties: props, key: o};
|
|
}
|
|
|
|
/**
|
|
* @param {Element} scope
|
|
* @param {StyleNode} rule
|
|
* @param {string|undefined} cssBuild
|
|
* @param {function(Object)} callback
|
|
*/
|
|
whenHostOrRootRule(scope, rule, cssBuild, callback) {
|
|
if (!rule.propertyInfo) {
|
|
this.decorateRule(rule);
|
|
}
|
|
if (!rule.propertyInfo.properties) {
|
|
return;
|
|
}
|
|
let {is, typeExtension} = getIsExtends(scope);
|
|
let hostScope = is ?
|
|
StyleTransformer$1._calcHostScope(is, typeExtension) :
|
|
'html';
|
|
let parsedSelector = rule['parsedSelector'];
|
|
let isRoot = (parsedSelector === ':host > *' || parsedSelector === 'html');
|
|
let isHost = parsedSelector.indexOf(':host') === 0 && !isRoot;
|
|
// build info is either in scope (when scope is an element) or in the style
|
|
// when scope is the default scope; note: this allows default scope to have
|
|
// mixed mode built and unbuilt styles.
|
|
if (cssBuild === 'shady') {
|
|
// :root -> x-foo > *.x-foo for elements and html for custom-style
|
|
isRoot = parsedSelector === (hostScope + ' > *.' + hostScope) || parsedSelector.indexOf('html') !== -1;
|
|
// :host -> x-foo for elements, but sub-rules have .x-foo in them
|
|
isHost = !isRoot && parsedSelector.indexOf(hostScope) === 0;
|
|
}
|
|
if (cssBuild === 'shadow') {
|
|
isRoot = parsedSelector === ':host > *' || parsedSelector === 'html';
|
|
isHost = isHost && !isRoot;
|
|
}
|
|
if (!isRoot && !isHost) {
|
|
return;
|
|
}
|
|
let selectorToMatch = hostScope;
|
|
if (isHost) {
|
|
// need to transform :host under ShadowDOM because `:host` does not work with `matches`
|
|
if (nativeShadow && !rule.transformedSelector) {
|
|
// transform :host into a matchable selector
|
|
rule.transformedSelector =
|
|
StyleTransformer$1._transformRuleCss(
|
|
rule,
|
|
StyleTransformer$1._transformComplexSelector,
|
|
StyleTransformer$1._calcElementScope(is),
|
|
hostScope
|
|
);
|
|
}
|
|
selectorToMatch = rule.transformedSelector || hostScope;
|
|
}
|
|
callback({
|
|
selector: selectorToMatch,
|
|
isHost: isHost,
|
|
isRoot: isRoot
|
|
});
|
|
}
|
|
/**
|
|
* @param {Element} scope
|
|
* @param {StyleNode} rules
|
|
* @return {Object}
|
|
*/
|
|
hostAndRootPropertiesForScope(scope, rules) {
|
|
let hostProps = {}, rootProps = {}, self = this;
|
|
// note: active rules excludes non-matching @media rules
|
|
let cssBuild = rules && rules['__cssBuild'];
|
|
forEachRule(rules, function(rule) {
|
|
// if scope is StyleDefaults, use _element for matchesSelector
|
|
self.whenHostOrRootRule(scope, rule, cssBuild, function(info) {
|
|
let element = scope._element || scope;
|
|
if (matchesSelector$1.call(element, info.selector)) {
|
|
if (info.isHost) {
|
|
self.collectProperties(rule, hostProps);
|
|
} else {
|
|
self.collectProperties(rule, rootProps);
|
|
}
|
|
}
|
|
});
|
|
}, null, true);
|
|
return {rootProps: rootProps, hostProps: hostProps};
|
|
}
|
|
|
|
/**
|
|
* @param {Element} element
|
|
* @param {Object} properties
|
|
* @param {string} scopeSelector
|
|
*/
|
|
transformStyles(element, properties, scopeSelector) {
|
|
let self = this;
|
|
let {is, typeExtension} = getIsExtends(element);
|
|
let hostSelector = StyleTransformer$1
|
|
._calcHostScope(is, typeExtension);
|
|
let rxHostSelector = element.extends ?
|
|
'\\' + hostSelector.slice(0, -1) + '\\]' :
|
|
hostSelector;
|
|
let hostRx = new RegExp(HOST_PREFIX + rxHostSelector +
|
|
HOST_SUFFIX);
|
|
let rules = StyleInfo.get(element).styleRules;
|
|
let keyframeTransforms =
|
|
this._elementKeyframeTransforms(element, rules, scopeSelector);
|
|
return StyleTransformer$1.elementStyles(element, rules, function(rule) {
|
|
self.applyProperties(rule, properties);
|
|
if (!nativeShadow &&
|
|
!isKeyframesSelector(rule) &&
|
|
rule['cssText']) {
|
|
// NOTE: keyframe transforms only scope munge animation names, so it
|
|
// is not necessary to apply them in ShadowDOM.
|
|
self.applyKeyframeTransforms(rule, keyframeTransforms);
|
|
self._scopeSelector(rule, hostRx, hostSelector, scopeSelector);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {Element} element
|
|
* @param {StyleNode} rules
|
|
* @param {string} scopeSelector
|
|
* @return {Object}
|
|
*/
|
|
_elementKeyframeTransforms(element, rules, scopeSelector) {
|
|
let keyframesRules = rules._keyframes;
|
|
let keyframeTransforms = {};
|
|
if (!nativeShadow && keyframesRules) {
|
|
// For non-ShadowDOM, we transform all known keyframes rules in
|
|
// advance for the current scope. This allows us to catch keyframes
|
|
// rules that appear anywhere in the stylesheet:
|
|
for (let i = 0, keyframesRule = keyframesRules[i];
|
|
i < keyframesRules.length;
|
|
keyframesRule = keyframesRules[++i]) {
|
|
this._scopeKeyframes(keyframesRule, scopeSelector);
|
|
keyframeTransforms[keyframesRule['keyframesName']] =
|
|
this._keyframesRuleTransformer(keyframesRule);
|
|
}
|
|
}
|
|
return keyframeTransforms;
|
|
}
|
|
|
|
// Generate a factory for transforming a chunk of CSS text to handle a
|
|
// particular scoped keyframes rule.
|
|
/**
|
|
* @param {StyleNode} keyframesRule
|
|
* @return {function(string):string}
|
|
*/
|
|
_keyframesRuleTransformer(keyframesRule) {
|
|
return function(cssText) {
|
|
return cssText.replace(
|
|
keyframesRule.keyframesNameRx,
|
|
keyframesRule.transformedKeyframesName);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Transforms `@keyframes` names to be unique for the current host.
|
|
* Example: @keyframes foo-anim -> @keyframes foo-anim-x-foo-0
|
|
*
|
|
* @param {StyleNode} rule
|
|
* @param {string} scopeId
|
|
*/
|
|
_scopeKeyframes(rule, scopeId) {
|
|
rule.keyframesNameRx = new RegExp(rule['keyframesName'], 'g');
|
|
rule.transformedKeyframesName = rule['keyframesName'] + '-' + scopeId;
|
|
rule.transformedSelector = rule.transformedSelector || rule['selector'];
|
|
rule['selector'] = rule.transformedSelector.replace(
|
|
rule['keyframesName'], rule.transformedKeyframesName);
|
|
}
|
|
|
|
// Strategy: x scope shim a selector e.g. to scope `.x-foo-42` (via classes):
|
|
// non-host selector: .a.x-foo -> .x-foo-42 .a.x-foo
|
|
// host selector: x-foo.wide -> .x-foo-42.wide
|
|
// note: we use only the scope class (.x-foo-42) and not the hostSelector
|
|
// (x-foo) to scope :host rules; this helps make property host rules
|
|
// have low specificity. They are overrideable by class selectors but,
|
|
// unfortunately, not by type selectors (e.g. overriding via
|
|
// `.special` is ok, but not by `x-foo`).
|
|
/**
|
|
* @param {StyleNode} rule
|
|
* @param {RegExp} hostRx
|
|
* @param {string} hostSelector
|
|
* @param {string} scopeId
|
|
*/
|
|
_scopeSelector(rule, hostRx, hostSelector, scopeId) {
|
|
rule.transformedSelector = rule.transformedSelector || rule['selector'];
|
|
let selector = rule.transformedSelector;
|
|
let scope = '.' + scopeId;
|
|
let parts = selector.split(',');
|
|
for (let i=0, l=parts.length, p; (i<l) && (p=parts[i]); i++) {
|
|
parts[i] = p.match(hostRx) ?
|
|
p.replace(hostSelector, scope) :
|
|
scope + ' ' + p;
|
|
}
|
|
rule['selector'] = parts.join(',');
|
|
}
|
|
|
|
/**
|
|
* @param {Element} element
|
|
* @param {string} selector
|
|
* @param {string} old
|
|
*/
|
|
applyElementScopeSelector(element, selector, old) {
|
|
let c = element.getAttribute('class') || '';
|
|
let v = c;
|
|
if (old) {
|
|
v = c.replace(
|
|
new RegExp('\\s*' + XSCOPE_NAME + '\\s*' + old + '\\s*', 'g'), ' ');
|
|
}
|
|
v += (v ? ' ' : '') + XSCOPE_NAME + ' ' + selector;
|
|
if (c !== v) {
|
|
setElementClassRaw(element, v);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {HTMLElement} element
|
|
* @param {Object} properties
|
|
* @param {string} selector
|
|
* @param {HTMLStyleElement} style
|
|
* @return {HTMLStyleElement}
|
|
*/
|
|
applyElementStyle(element, properties, selector, style) {
|
|
// calculate cssText to apply
|
|
let cssText = style ? style.textContent || '' :
|
|
this.transformStyles(element, properties, selector);
|
|
// if shady and we have a cached style that is not style, decrement
|
|
let styleInfo = StyleInfo.get(element);
|
|
let s = styleInfo.customStyle;
|
|
if (s && !nativeShadow && (s !== style)) {
|
|
s['_useCount']--;
|
|
if (s['_useCount'] <= 0 && s.parentNode) {
|
|
s.parentNode.removeChild(s);
|
|
}
|
|
}
|
|
// apply styling always under native or if we generated style
|
|
// or the cached style is not in document(!)
|
|
if (nativeShadow) {
|
|
// update existing style only under native
|
|
if (styleInfo.customStyle) {
|
|
styleInfo.customStyle.textContent = cssText;
|
|
style = styleInfo.customStyle;
|
|
// otherwise, if we have css to apply, do so
|
|
} else if (cssText) {
|
|
// apply css after the scope style of the element to help with
|
|
// style precedence rules.
|
|
style = applyCss(cssText, selector, element.shadowRoot,
|
|
styleInfo.placeholder);
|
|
}
|
|
} else {
|
|
// shady and no cache hit
|
|
if (!style) {
|
|
// apply css after the scope style of the element to help with
|
|
// style precedence rules.
|
|
if (cssText) {
|
|
style = applyCss(cssText, selector, null,
|
|
styleInfo.placeholder);
|
|
}
|
|
// shady and cache hit but not in document
|
|
} else if (!style.parentNode) {
|
|
applyStyle(style, null, styleInfo.placeholder);
|
|
}
|
|
|
|
}
|
|
// ensure this style is our custom style and increment its use count.
|
|
if (style) {
|
|
style['_useCount'] = style['_useCount'] || 0;
|
|
// increment use count if we changed styles
|
|
if (styleInfo.customStyle != style) {
|
|
style['_useCount']++;
|
|
}
|
|
styleInfo.customStyle = style;
|
|
}
|
|
// @media rules may be stale in IE 10 and 11
|
|
if (IS_IE) {
|
|
style.textContent = style.textContent;
|
|
}
|
|
return style;
|
|
}
|
|
|
|
/**
|
|
* @param {Element} style
|
|
* @param {Object} properties
|
|
*/
|
|
applyCustomStyle(style, properties) {
|
|
let rules = rulesForStyle(/** @type {HTMLStyleElement} */(style));
|
|
let self = this;
|
|
style.textContent = toCssText(rules, function(/** StyleNode */rule) {
|
|
let css = rule['cssText'] = rule['parsedCssText'];
|
|
if (rule.propertyInfo && rule.propertyInfo.cssText) {
|
|
// remove property assignments
|
|
// so next function isn't confused
|
|
// NOTE: we have 3 categories of css:
|
|
// (1) normal properties,
|
|
// (2) custom property assignments (--foo: red;),
|
|
// (3) custom property usage: border: var(--foo); @apply(--foo);
|
|
// In elements, 1 and 3 are separated for efficiency; here they
|
|
// are not and this makes this case unique.
|
|
css = removeCustomPropAssignment(/** @type {string} */(css));
|
|
// replace with reified properties, scenario is same as mixin
|
|
rule['cssText'] = self.valueForProperties(css, properties);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {number} n
|
|
* @param {Array<number>} bits
|
|
*/
|
|
function addToBitMask(n, bits) {
|
|
let o = parseInt(n / 32, 10);
|
|
let v = 1 << (n % 32);
|
|
bits[o] = (bits[o] || 0) | v;
|
|
}
|
|
|
|
var StyleProperties$1 = new StyleProperties();
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2017 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
|
|
*/
|
|
|
|
/** @type {Object<string, !Node>} */
|
|
let placeholderMap = {};
|
|
|
|
/**
|
|
* @const {CustomElementRegistry}
|
|
*/
|
|
const ce = window['customElements'];
|
|
if (ce && !nativeShadow) {
|
|
/**
|
|
* @const {function(this:CustomElementRegistry, string,function(new:HTMLElement),{extends: string}=)}
|
|
*/
|
|
const origDefine = ce['define'];
|
|
/**
|
|
* @param {string} name
|
|
* @param {function(new:HTMLElement)} clazz
|
|
* @param {{extends: string}=} options
|
|
* @return {function(new:HTMLElement)}
|
|
*/
|
|
const wrappedDefine = (name, clazz, options) => {
|
|
placeholderMap[name] = applyStylePlaceHolder(name);
|
|
return origDefine.call(/** @type {!CustomElementRegistry} */(ce), name, clazz, options);
|
|
};
|
|
ce['define'] = wrappedDefine;
|
|
}
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2017 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
|
|
*/
|
|
class StyleCache {
|
|
constructor(typeMax = 100) {
|
|
// map element name -> [{properties, styleElement, scopeSelector}]
|
|
this.cache = {};
|
|
this.typeMax = typeMax;
|
|
}
|
|
|
|
_validate(cacheEntry, properties, ownPropertyNames) {
|
|
for (let idx = 0; idx < ownPropertyNames.length; idx++) {
|
|
let pn = ownPropertyNames[idx];
|
|
if (cacheEntry.properties[pn] !== properties[pn]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
store(tagname, properties, styleElement, scopeSelector) {
|
|
let list = this.cache[tagname] || [];
|
|
list.push({properties, styleElement, scopeSelector});
|
|
if (list.length > this.typeMax) {
|
|
list.shift();
|
|
}
|
|
this.cache[tagname] = list;
|
|
}
|
|
|
|
fetch(tagname, properties, ownPropertyNames) {
|
|
let list = this.cache[tagname];
|
|
if (!list) {
|
|
return;
|
|
}
|
|
// reverse list for most-recent lookups
|
|
for (let idx = list.length - 1; idx >= 0; idx--) {
|
|
let entry = list[idx];
|
|
if (this._validate(entry, properties, ownPropertyNames)) {
|
|
return entry;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2017 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
|
|
*/
|
|
|
|
let flush$1 = function() {};
|
|
|
|
if (!nativeShadow) {
|
|
let elementNeedsScoping = (element) => {
|
|
return (element.classList &&
|
|
!element.classList.contains(StyleTransformer$1.SCOPE_NAME) ||
|
|
// note: necessary for IE11
|
|
(element instanceof window['SVGElement'] && (!element.hasAttribute('class') ||
|
|
element.getAttribute('class').indexOf(StyleTransformer$1.SCOPE_NAME) < 0)));
|
|
};
|
|
|
|
/**
|
|
* @param {Array<MutationRecord|null>|null} mxns
|
|
*/
|
|
let handler = (mxns) => {
|
|
for (let x=0; x < mxns.length; x++) {
|
|
let mxn = mxns[x];
|
|
if (mxn.target === document.documentElement ||
|
|
mxn.target === document.head) {
|
|
continue;
|
|
}
|
|
for (let i=0; i < mxn.addedNodes.length; i++) {
|
|
let n = mxn.addedNodes[i];
|
|
if (elementNeedsScoping(n)) {
|
|
let root = n.getRootNode();
|
|
if (root.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
// may no longer be in a shadowroot
|
|
let host = /** @type {ShadowRoot} */(root).host;
|
|
if (host) {
|
|
let {is: scope} = getIsExtends(host);
|
|
StyleTransformer$1.dom(n, scope);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (let i=0; i < mxn.removedNodes.length; i++) {
|
|
let n = /** @type {HTMLElement} */(mxn.removedNodes[i]);
|
|
if (n.nodeType === Node.ELEMENT_NODE) {
|
|
let classes = undefined;
|
|
if (n.classList) {
|
|
classes = Array.from(n.classList);
|
|
} else if (n.hasAttribute('class')) {
|
|
classes = n.getAttribute('class').split(/\s+/);
|
|
}
|
|
if (classes !== undefined) {
|
|
// NOTE: relies on the scoping class always being adjacent to the
|
|
// SCOPE_NAME class.
|
|
let classIdx = classes.indexOf(StyleTransformer$1.SCOPE_NAME);
|
|
if (classIdx >= 0) {
|
|
let scope = classes[classIdx + 1];
|
|
if (scope) {
|
|
StyleTransformer$1.dom(n, scope, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
let observer = new MutationObserver(handler);
|
|
let start = (node) => {
|
|
observer.observe(node, {childList: true, subtree: true});
|
|
};
|
|
let nativeCustomElements = (window.customElements &&
|
|
!window['customElements']['flush']);
|
|
// need to start immediately with native custom elements
|
|
// TODO(dfreedm): with polyfilled HTMLImports and native custom elements
|
|
// excessive mutations may be observed; this can be optimized via cooperation
|
|
// with the HTMLImports polyfill.
|
|
if (nativeCustomElements) {
|
|
start(document);
|
|
} else {
|
|
let delayedStart = () => {
|
|
start(document.body);
|
|
};
|
|
// use polyfill timing if it's available
|
|
if (window['HTMLImports']) {
|
|
window['HTMLImports']['whenReady'](delayedStart);
|
|
// otherwise push beyond native imports being ready
|
|
// which requires RAF + readystate interactive.
|
|
} else {
|
|
requestAnimationFrame(function() {
|
|
if (document.readyState === 'loading') {
|
|
let listener = function() {
|
|
delayedStart();
|
|
document.removeEventListener('readystatechange', listener);
|
|
};
|
|
document.addEventListener('readystatechange', listener);
|
|
} else {
|
|
delayedStart();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
flush$1 = function() {
|
|
handler(observer.takeRecords());
|
|
};
|
|
}
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2017 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
|
|
*/
|
|
|
|
/**
|
|
* @const {!Object<string, !HTMLTemplateElement>}
|
|
*/
|
|
const templateMap = {};
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2017 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
|
|
*/
|
|
|
|
/**
|
|
* @const {Promise<void>}
|
|
*/
|
|
const promise = Promise.resolve();
|
|
|
|
/**
|
|
* @param {string} elementName
|
|
*/
|
|
function invalidate(elementName){
|
|
let template = templateMap[elementName];
|
|
if (template) {
|
|
invalidateTemplate(template);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {HTMLTemplateElement} template
|
|
*/
|
|
function invalidateTemplate(template) {
|
|
template['_applyShimInvalid'] = true;
|
|
}
|
|
|
|
/**
|
|
* @param {string} elementName
|
|
* @return {boolean}
|
|
*/
|
|
|
|
|
|
/**
|
|
* @param {HTMLTemplateElement} template
|
|
* @return {boolean}
|
|
*/
|
|
function templateIsValid(template) {
|
|
return !template['_applyShimInvalid'];
|
|
}
|
|
|
|
/**
|
|
* @param {string} elementName
|
|
* @return {boolean}
|
|
*/
|
|
|
|
|
|
/**
|
|
* @param {HTMLTemplateElement} template
|
|
* @return {boolean}
|
|
*/
|
|
function templateIsValidating(template) {
|
|
return template._validating;
|
|
}
|
|
|
|
/**
|
|
* the template is marked as `validating` for one microtask so that all instances
|
|
* found in the tree crawl of `applyStyle` will update themselves,
|
|
* but the template will only be updated once.
|
|
* @param {string} elementName
|
|
*/
|
|
|
|
|
|
/**
|
|
* @param {HTMLTemplateElement} template
|
|
*/
|
|
function startValidatingTemplate(template) {
|
|
if (!template._validating) {
|
|
template._validating = true;
|
|
promise.then(function() {
|
|
template['_applyShimInvalid'] = false;
|
|
template._validating = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return {boolean}
|
|
*/
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2017 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
|
|
*/
|
|
|
|
/** @type {Promise<void>} */
|
|
let readyPromise = null;
|
|
|
|
/** @type {?function(?function())} */
|
|
let whenReady = window['HTMLImports'] && window['HTMLImports']['whenReady'] || null;
|
|
|
|
/** @type {function()} */
|
|
let resolveFn;
|
|
|
|
/**
|
|
* @param {?function()} callback
|
|
*/
|
|
function documentWait(callback) {
|
|
if (whenReady) {
|
|
whenReady(callback);
|
|
} else {
|
|
if (!readyPromise) {
|
|
readyPromise = new Promise((resolve) => {resolveFn = resolve;});
|
|
if (document.readyState === 'complete') {
|
|
resolveFn();
|
|
} else {
|
|
document.addEventListener('readystatechange', () => {
|
|
if (document.readyState === 'complete') {
|
|
resolveFn();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
readyPromise.then(function(){ callback && callback(); });
|
|
}
|
|
}
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2017 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
|
|
*/
|
|
|
|
/**
|
|
* @param {Element} element
|
|
* @param {Object=} properties
|
|
*/
|
|
function updateNativeProperties(element, properties) {
|
|
// remove previous properties
|
|
for (let p in properties) {
|
|
// NOTE: for bc with shim, don't apply null values.
|
|
if (p === null) {
|
|
element.style.removeProperty(p);
|
|
} else {
|
|
element.style.setProperty(p, properties[p]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Element} element
|
|
* @param {string} property
|
|
* @return {string}
|
|
*/
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2017 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
|
|
*/
|
|
|
|
/**
|
|
* @typedef {HTMLStyleElement | {getStyle: function():HTMLStyleElement}}
|
|
*/
|
|
|
|
|
|
const SEEN_MARKER = '__seenByShadyCSS';
|
|
const CACHED_STYLE = '__shadyCSSCachedStyle';
|
|
|
|
/** @type {?function(!HTMLStyleElement)} */
|
|
let transformFn = null;
|
|
|
|
/** @type {?function()} */
|
|
let validateFn = null;
|
|
|
|
/**
|
|
This interface is provided to add document-level <style> elements to ShadyCSS for processing.
|
|
These styles must be processed by ShadyCSS to simulate ShadowRoot upper-bound encapsulation from outside styles
|
|
In addition, these styles may also need to be processed for @apply rules and CSS Custom Properties
|
|
|
|
To add document-level styles to ShadyCSS, one can call `ShadyCSS.addDocumentStyle(styleElement)` or `ShadyCSS.addDocumentStyle({getStyle: () => styleElement})`
|
|
|
|
In addition, if the process used to discover document-level styles can be synchronously flushed, one should set `ShadyCSS.documentStyleFlush`.
|
|
This function will be called when calculating styles.
|
|
|
|
An example usage of the document-level styling api can be found in `examples/document-style-lib.js`
|
|
|
|
@unrestricted
|
|
*/
|
|
class CustomStyleInterface$1 {
|
|
constructor() {
|
|
/** @type {!Array<!CustomStyleProvider>} */
|
|
this['customStyles'] = [];
|
|
this['enqueued'] = false;
|
|
}
|
|
/**
|
|
* Queue a validation for new custom styles to batch style recalculations
|
|
*/
|
|
enqueueDocumentValidation() {
|
|
if (this['enqueued'] || !validateFn) {
|
|
return;
|
|
}
|
|
this['enqueued'] = true;
|
|
documentWait(validateFn);
|
|
}
|
|
/**
|
|
* @param {!HTMLStyleElement} style
|
|
*/
|
|
addCustomStyle(style) {
|
|
if (!style[SEEN_MARKER]) {
|
|
style[SEEN_MARKER] = true;
|
|
this['customStyles'].push(style);
|
|
this.enqueueDocumentValidation();
|
|
}
|
|
}
|
|
/**
|
|
* @param {!CustomStyleProvider} customStyle
|
|
* @return {HTMLStyleElement}
|
|
*/
|
|
getStyleForCustomStyle(customStyle) {
|
|
if (customStyle[CACHED_STYLE]) {
|
|
return customStyle[CACHED_STYLE];
|
|
}
|
|
let style;
|
|
if (customStyle['getStyle']) {
|
|
style = customStyle['getStyle']();
|
|
} else {
|
|
style = customStyle;
|
|
}
|
|
return style;
|
|
}
|
|
/**
|
|
* @return {!Array<!CustomStyleProvider>}
|
|
*/
|
|
processStyles() {
|
|
let cs = this['customStyles'];
|
|
for (let i = 0; i < cs.length; i++) {
|
|
let customStyle = cs[i];
|
|
if (customStyle[CACHED_STYLE]) {
|
|
continue;
|
|
}
|
|
let style = this.getStyleForCustomStyle(customStyle);
|
|
if (style) {
|
|
// HTMLImports polyfill may have cloned the style into the main document,
|
|
// which is referenced with __appliedElement.
|
|
// Also, we must copy over the attributes.
|
|
let appliedStyle = /** @type {HTMLStyleElement} */(style['__appliedElement']);
|
|
if (appliedStyle) {
|
|
for (let i = 0; i < style.attributes.length; i++) {
|
|
let attr = style.attributes[i];
|
|
appliedStyle.setAttribute(attr.name, attr.value);
|
|
}
|
|
}
|
|
let styleToTransform = appliedStyle || style;
|
|
if (transformFn) {
|
|
transformFn(styleToTransform);
|
|
}
|
|
customStyle[CACHED_STYLE] = styleToTransform;
|
|
}
|
|
}
|
|
return cs;
|
|
}
|
|
}
|
|
|
|
CustomStyleInterface$1.prototype['addCustomStyle'] = CustomStyleInterface$1.prototype.addCustomStyle;
|
|
CustomStyleInterface$1.prototype['getStyleForCustomStyle'] = CustomStyleInterface$1.prototype.getStyleForCustomStyle;
|
|
CustomStyleInterface$1.prototype['processStyles'] = CustomStyleInterface$1.prototype.processStyles;
|
|
|
|
Object.defineProperties(CustomStyleInterface$1.prototype, {
|
|
'transformCallback': {
|
|
/** @return {?function(!HTMLStyleElement)} */
|
|
get() {
|
|
return transformFn;
|
|
},
|
|
/** @param {?function(!HTMLStyleElement)} fn */
|
|
set(fn) {
|
|
transformFn = fn;
|
|
}
|
|
},
|
|
'validateCallback': {
|
|
/** @return {?function()} */
|
|
get() {
|
|
return validateFn;
|
|
},
|
|
/**
|
|
* @param {?function()} fn
|
|
* @this {CustomStyleInterface}
|
|
*/
|
|
set(fn) {
|
|
let needsEnqueue = false;
|
|
if (!validateFn) {
|
|
needsEnqueue = true;
|
|
}
|
|
validateFn = fn;
|
|
if (needsEnqueue) {
|
|
this.enqueueDocumentValidation();
|
|
}
|
|
},
|
|
}
|
|
});
|
|
|
|
/** @typedef {{
|
|
* customStyles: !Array<!CustomStyleProvider>,
|
|
* addCustomStyle: function(!CustomStyleProvider),
|
|
* getStyleForCustomStyle: function(!CustomStyleProvider): HTMLStyleElement,
|
|
* findStyles: function(),
|
|
* transformCallback: ?function(!HTMLStyleElement),
|
|
* validateCallback: ?function()
|
|
* }}
|
|
*/
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2017 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
|
|
*/
|
|
|
|
/**
|
|
* @const {StyleCache}
|
|
*/
|
|
const styleCache = new StyleCache();
|
|
|
|
class ScopingShim {
|
|
constructor() {
|
|
this._scopeCounter = {};
|
|
this._documentOwner = document.documentElement;
|
|
let ast = new StyleNode();
|
|
ast['rules'] = [];
|
|
this._documentOwnerStyleInfo = StyleInfo.set(this._documentOwner, new StyleInfo(ast));
|
|
this._elementsHaveApplied = false;
|
|
this._applyShim = null;
|
|
/** @type {?CustomStyleInterfaceInterface} */
|
|
this._customStyleInterface = null;
|
|
documentWait(() => {
|
|
this._ensure();
|
|
});
|
|
}
|
|
flush() {
|
|
flush$1();
|
|
}
|
|
_generateScopeSelector(name) {
|
|
let id = this._scopeCounter[name] = (this._scopeCounter[name] || 0) + 1;
|
|
return `${name}-${id}`;
|
|
}
|
|
getStyleAst(style) {
|
|
return rulesForStyle(style);
|
|
}
|
|
styleAstToString(ast) {
|
|
return toCssText(ast);
|
|
}
|
|
_gatherStyles(template) {
|
|
let styles = template.content.querySelectorAll('style');
|
|
let cssText = [];
|
|
for (let i = 0; i < styles.length; i++) {
|
|
let s = styles[i];
|
|
cssText.push(s.textContent);
|
|
s.parentNode.removeChild(s);
|
|
}
|
|
return cssText.join('').trim();
|
|
}
|
|
_getCssBuild(template) {
|
|
let style = template.content.querySelector('style');
|
|
if (!style) {
|
|
return '';
|
|
}
|
|
return style.getAttribute('css-build') || '';
|
|
}
|
|
/**
|
|
* Prepare the styling and template for the given element type
|
|
*
|
|
* @param {HTMLTemplateElement} template
|
|
* @param {string} elementName
|
|
* @param {string=} typeExtension
|
|
*/
|
|
prepareTemplate(template, elementName, typeExtension) {
|
|
if (template._prepared) {
|
|
return;
|
|
}
|
|
template._prepared = true;
|
|
template.name = elementName;
|
|
template.extends = typeExtension;
|
|
templateMap[elementName] = template;
|
|
let cssBuild = this._getCssBuild(template);
|
|
let cssText = this._gatherStyles(template);
|
|
let info = {
|
|
is: elementName,
|
|
extends: typeExtension,
|
|
__cssBuild: cssBuild,
|
|
};
|
|
if (!nativeShadow) {
|
|
StyleTransformer$1.dom(template.content, elementName);
|
|
}
|
|
// check if the styling has mixin definitions or uses
|
|
this._ensure();
|
|
let hasMixins = this._applyShim['detectMixin'](cssText);
|
|
let ast = parse(cssText);
|
|
// only run the applyshim transforms if there is a mixin involved
|
|
if (hasMixins && nativeCssVariables) {
|
|
this._applyShim['transformRules'](ast, elementName);
|
|
}
|
|
template['_styleAst'] = ast;
|
|
template._cssBuild = cssBuild;
|
|
|
|
let ownPropertyNames = [];
|
|
if (!nativeCssVariables) {
|
|
ownPropertyNames = StyleProperties$1.decorateStyles(template['_styleAst'], info);
|
|
}
|
|
if (!ownPropertyNames.length || nativeCssVariables) {
|
|
let root = nativeShadow ? template.content : null;
|
|
let placeholder = placeholderMap[elementName];
|
|
let style = this._generateStaticStyle(info, template['_styleAst'], root, placeholder);
|
|
template._style = style;
|
|
}
|
|
template._ownPropertyNames = ownPropertyNames;
|
|
}
|
|
_generateStaticStyle(info, rules, shadowroot, placeholder) {
|
|
let cssText = StyleTransformer$1.elementStyles(info, rules);
|
|
if (cssText.length) {
|
|
return applyCss(cssText, info.is, shadowroot, placeholder);
|
|
}
|
|
}
|
|
_prepareHost(host) {
|
|
let {is, typeExtension} = getIsExtends(host);
|
|
let placeholder = placeholderMap[is];
|
|
let template = templateMap[is];
|
|
let ast;
|
|
let ownStylePropertyNames;
|
|
let cssBuild;
|
|
if (template) {
|
|
ast = template['_styleAst'];
|
|
ownStylePropertyNames = template._ownPropertyNames;
|
|
cssBuild = template._cssBuild;
|
|
}
|
|
return StyleInfo.set(host,
|
|
new StyleInfo(
|
|
ast,
|
|
placeholder,
|
|
ownStylePropertyNames,
|
|
is,
|
|
typeExtension,
|
|
cssBuild
|
|
)
|
|
);
|
|
}
|
|
_ensureApplyShim() {
|
|
if (this._applyShim) {
|
|
return;
|
|
} else if (window.ShadyCSS.ApplyShim) {
|
|
this._applyShim = window.ShadyCSS.ApplyShim;
|
|
this._applyShim['invalidCallback'] = invalidate;
|
|
} else {
|
|
this._applyShim = {
|
|
/* eslint-disable no-unused-vars */
|
|
['detectMixin'](str){return false},
|
|
['transformRule'](ast){},
|
|
['transformRules'](ast, name){},
|
|
/* eslint-enable no-unused-vars */
|
|
};
|
|
}
|
|
}
|
|
_ensureCustomStyleInterface() {
|
|
if (this._customStyleInterface) {
|
|
return;
|
|
} else if (window.ShadyCSS.CustomStyleInterface) {
|
|
this._customStyleInterface = /** @type {!CustomStyleInterfaceInterface} */(window.ShadyCSS.CustomStyleInterface);
|
|
/** @type {function(!HTMLStyleElement)} */
|
|
this._customStyleInterface['transformCallback'] = (style) => {this.transformCustomStyleForDocument(style);};
|
|
this._customStyleInterface['validateCallback'] = () => {
|
|
requestAnimationFrame(() => {
|
|
if (this._customStyleInterface['enqueued'] || this._elementsHaveApplied) {
|
|
this.flushCustomStyles();
|
|
}
|
|
});
|
|
};
|
|
} else {
|
|
this._customStyleInterface = /** @type {!CustomStyleInterfaceInterface} */({
|
|
['processStyles']() {},
|
|
['enqueued']: false,
|
|
['getStyleForCustomStyle'](s) { return null } // eslint-disable-line no-unused-vars
|
|
});
|
|
}
|
|
}
|
|
_ensure() {
|
|
this._ensureApplyShim();
|
|
this._ensureCustomStyleInterface();
|
|
}
|
|
/**
|
|
* Flush and apply custom styles to document
|
|
*/
|
|
flushCustomStyles() {
|
|
this._ensure();
|
|
let customStyles = this._customStyleInterface['processStyles']();
|
|
// early return if custom-styles don't need validation
|
|
if (!this._customStyleInterface['enqueued']) {
|
|
return;
|
|
}
|
|
if (!nativeCssVariables) {
|
|
this._updateProperties(this._documentOwner, this._documentOwnerStyleInfo);
|
|
this._applyCustomStyles(customStyles);
|
|
} else {
|
|
this._revalidateCustomStyleApplyShim(customStyles);
|
|
}
|
|
this._customStyleInterface['enqueued'] = false;
|
|
// if custom elements have upgraded and there are no native css variables, we must recalculate the whole tree
|
|
if (this._elementsHaveApplied && !nativeCssVariables) {
|
|
this.styleDocument();
|
|
}
|
|
}
|
|
/**
|
|
* Apply styles for the given element
|
|
*
|
|
* @param {!HTMLElement} host
|
|
* @param {Object=} overrideProps
|
|
*/
|
|
styleElement(host, overrideProps) {
|
|
let {is} = getIsExtends(host);
|
|
let styleInfo = StyleInfo.get(host);
|
|
if (!styleInfo) {
|
|
styleInfo = this._prepareHost(host);
|
|
}
|
|
// Only trip the `elementsHaveApplied` flag if a node other that the root document has `applyStyle` called
|
|
if (!this._isRootOwner(host)) {
|
|
this._elementsHaveApplied = true;
|
|
}
|
|
if (overrideProps) {
|
|
styleInfo.overrideStyleProperties =
|
|
styleInfo.overrideStyleProperties || {};
|
|
Object.assign(styleInfo.overrideStyleProperties, overrideProps);
|
|
}
|
|
if (!nativeCssVariables) {
|
|
this._updateProperties(host, styleInfo);
|
|
if (styleInfo.ownStylePropertyNames && styleInfo.ownStylePropertyNames.length) {
|
|
this._applyStyleProperties(host, styleInfo);
|
|
}
|
|
} else {
|
|
if (styleInfo.overrideStyleProperties) {
|
|
updateNativeProperties(host, styleInfo.overrideStyleProperties);
|
|
}
|
|
let template = templateMap[is];
|
|
// bail early if there is no shadowroot for this element
|
|
if (!template && !this._isRootOwner(host)) {
|
|
return;
|
|
}
|
|
if (template && template._style && !templateIsValid(template)) {
|
|
// update template
|
|
if (!templateIsValidating(template)) {
|
|
this._ensure();
|
|
this._applyShim['transformRules'](template['_styleAst'], is);
|
|
template._style.textContent = StyleTransformer$1.elementStyles(host, styleInfo.styleRules);
|
|
startValidatingTemplate(template);
|
|
}
|
|
// update instance if native shadowdom
|
|
if (nativeShadow) {
|
|
let root = host.shadowRoot;
|
|
if (root) {
|
|
let style = root.querySelector('style');
|
|
style.textContent = StyleTransformer$1.elementStyles(host, styleInfo.styleRules);
|
|
}
|
|
}
|
|
styleInfo.styleRules = template['_styleAst'];
|
|
}
|
|
}
|
|
}
|
|
_styleOwnerForNode(node) {
|
|
let root = node.getRootNode();
|
|
let host = root.host;
|
|
if (host) {
|
|
if (StyleInfo.get(host)) {
|
|
return host;
|
|
} else {
|
|
return this._styleOwnerForNode(host);
|
|
}
|
|
}
|
|
return this._documentOwner;
|
|
}
|
|
_isRootOwner(node) {
|
|
return (node === this._documentOwner);
|
|
}
|
|
_applyStyleProperties(host, styleInfo) {
|
|
let is = getIsExtends(host).is;
|
|
let cacheEntry = styleCache.fetch(is, styleInfo.styleProperties, styleInfo.ownStylePropertyNames);
|
|
let cachedScopeSelector = cacheEntry && cacheEntry.scopeSelector;
|
|
let cachedStyle = cacheEntry ? cacheEntry.styleElement : null;
|
|
let oldScopeSelector = styleInfo.scopeSelector;
|
|
// only generate new scope if cached style is not found
|
|
styleInfo.scopeSelector = cachedScopeSelector || this._generateScopeSelector(is);
|
|
let style = StyleProperties$1.applyElementStyle(host, styleInfo.styleProperties, styleInfo.scopeSelector, cachedStyle);
|
|
if (!nativeShadow) {
|
|
StyleProperties$1.applyElementScopeSelector(host, styleInfo.scopeSelector, oldScopeSelector);
|
|
}
|
|
if (!cacheEntry) {
|
|
styleCache.store(is, styleInfo.styleProperties, style, styleInfo.scopeSelector);
|
|
}
|
|
return style;
|
|
}
|
|
_updateProperties(host, styleInfo) {
|
|
let owner = this._styleOwnerForNode(host);
|
|
let ownerStyleInfo = StyleInfo.get(owner);
|
|
let ownerProperties = ownerStyleInfo.styleProperties;
|
|
let props = Object.create(ownerProperties || null);
|
|
let hostAndRootProps = StyleProperties$1.hostAndRootPropertiesForScope(host, styleInfo.styleRules);
|
|
let propertyData = StyleProperties$1.propertyDataFromStyles(ownerStyleInfo.styleRules, host);
|
|
let propertiesMatchingHost = propertyData.properties;
|
|
Object.assign(
|
|
props,
|
|
hostAndRootProps.hostProps,
|
|
propertiesMatchingHost,
|
|
hostAndRootProps.rootProps
|
|
);
|
|
this._mixinOverrideStyles(props, styleInfo.overrideStyleProperties);
|
|
StyleProperties$1.reify(props);
|
|
styleInfo.styleProperties = props;
|
|
}
|
|
_mixinOverrideStyles(props, overrides) {
|
|
for (let p in overrides) {
|
|
let v = overrides[p];
|
|
// skip override props if they are not truthy or 0
|
|
// in order to fall back to inherited values
|
|
if (v || v === 0) {
|
|
props[p] = v;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Update styles of the whole document
|
|
*
|
|
* @param {Object=} properties
|
|
*/
|
|
styleDocument(properties) {
|
|
this.styleSubtree(this._documentOwner, properties);
|
|
}
|
|
/**
|
|
* Update styles of a subtree
|
|
*
|
|
* @param {!HTMLElement} host
|
|
* @param {Object=} properties
|
|
*/
|
|
styleSubtree(host, properties) {
|
|
let root = host.shadowRoot;
|
|
if (root || this._isRootOwner(host)) {
|
|
this.styleElement(host, properties);
|
|
}
|
|
// process the shadowdom children of `host`
|
|
let shadowChildren = root && (root.children || root.childNodes);
|
|
if (shadowChildren) {
|
|
for (let i = 0; i < shadowChildren.length; i++) {
|
|
let c = /** @type {!HTMLElement} */(shadowChildren[i]);
|
|
this.styleSubtree(c);
|
|
}
|
|
} else {
|
|
// process the lightdom children of `host`
|
|
let children = host.children || host.childNodes;
|
|
if (children) {
|
|
for (let i = 0; i < children.length; i++) {
|
|
let c = /** @type {!HTMLElement} */(children[i]);
|
|
this.styleSubtree(c);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* Custom Style operations */
|
|
_revalidateCustomStyleApplyShim(customStyles) {
|
|
for (let i = 0; i < customStyles.length; i++) {
|
|
let c = customStyles[i];
|
|
let s = this._customStyleInterface['getStyleForCustomStyle'](c);
|
|
if (s) {
|
|
this._revalidateApplyShim(s);
|
|
}
|
|
}
|
|
}
|
|
_applyCustomStyles(customStyles) {
|
|
for (let i = 0; i < customStyles.length; i++) {
|
|
let c = customStyles[i];
|
|
let s = this._customStyleInterface['getStyleForCustomStyle'](c);
|
|
if (s) {
|
|
StyleProperties$1.applyCustomStyle(s, this._documentOwnerStyleInfo.styleProperties);
|
|
}
|
|
}
|
|
}
|
|
transformCustomStyleForDocument(style) {
|
|
let ast = rulesForStyle(style);
|
|
forEachRule(ast, (rule) => {
|
|
if (nativeShadow) {
|
|
StyleTransformer$1.normalizeRootSelector(rule);
|
|
} else {
|
|
StyleTransformer$1.documentRule(rule);
|
|
}
|
|
if (nativeCssVariables) {
|
|
this._ensure();
|
|
this._applyShim['transformRule'](rule);
|
|
}
|
|
});
|
|
if (nativeCssVariables) {
|
|
style.textContent = toCssText(ast);
|
|
} else {
|
|
this._documentOwnerStyleInfo.styleRules.rules.push(ast);
|
|
}
|
|
}
|
|
_revalidateApplyShim(style) {
|
|
if (nativeCssVariables) {
|
|
let ast = rulesForStyle(style);
|
|
this._ensure();
|
|
this._applyShim['transformRules'](ast);
|
|
style.textContent = toCssText(ast);
|
|
}
|
|
}
|
|
getComputedStyleValue(element, property) {
|
|
let value;
|
|
if (!nativeCssVariables) {
|
|
// element is either a style host, or an ancestor of a style host
|
|
let styleInfo = StyleInfo.get(element) || StyleInfo.get(this._styleOwnerForNode(element));
|
|
value = styleInfo.styleProperties[property];
|
|
}
|
|
// fall back to the property value from the computed styling
|
|
value = value || window.getComputedStyle(element).getPropertyValue(property);
|
|
// trim whitespace that can come after the `:` in css
|
|
// example: padding: 2px -> " 2px"
|
|
return value ? value.trim() : '';
|
|
}
|
|
// given an element and a classString, replaces
|
|
// the element's class with the provided classString and adds
|
|
// any necessary ShadyCSS static and property based scoping selectors
|
|
setElementClass(element, classString) {
|
|
let root = element.getRootNode();
|
|
let classes = classString ? classString.split(/\s/) : [];
|
|
let scopeName = root.host && root.host.localName;
|
|
// If no scope, try to discover scope name from existing class.
|
|
// This can occur if, for example, a template stamped element that
|
|
// has been scoped is manipulated when not in a root.
|
|
if (!scopeName) {
|
|
var classAttr = element.getAttribute('class');
|
|
if (classAttr) {
|
|
let k$ = classAttr.split(/\s/);
|
|
for (let i=0; i < k$.length; i++) {
|
|
if (k$[i] === StyleTransformer$1.SCOPE_NAME) {
|
|
scopeName = k$[i+1];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (scopeName) {
|
|
classes.push(StyleTransformer$1.SCOPE_NAME, scopeName);
|
|
}
|
|
if (!nativeCssVariables) {
|
|
let styleInfo = StyleInfo.get(element);
|
|
if (styleInfo && styleInfo.scopeSelector) {
|
|
classes.push(StyleProperties$1.XSCOPE_NAME, styleInfo.scopeSelector);
|
|
}
|
|
}
|
|
setElementClassRaw(element, classes.join(' '));
|
|
}
|
|
_styleInfoForNode(node) {
|
|
return StyleInfo.get(node);
|
|
}
|
|
}
|
|
|
|
/* exports */
|
|
ScopingShim.prototype['flush'] = ScopingShim.prototype.flush;
|
|
ScopingShim.prototype['prepareTemplate'] = ScopingShim.prototype.prepareTemplate;
|
|
ScopingShim.prototype['styleElement'] = ScopingShim.prototype.styleElement;
|
|
ScopingShim.prototype['styleDocument'] = ScopingShim.prototype.styleDocument;
|
|
ScopingShim.prototype['styleSubtree'] = ScopingShim.prototype.styleSubtree;
|
|
ScopingShim.prototype['getComputedStyleValue'] = ScopingShim.prototype.getComputedStyleValue;
|
|
ScopingShim.prototype['setElementClass'] = ScopingShim.prototype.setElementClass;
|
|
ScopingShim.prototype['_styleInfoForNode'] = ScopingShim.prototype._styleInfoForNode;
|
|
ScopingShim.prototype['transformCustomStyleForDocument'] = ScopingShim.prototype.transformCustomStyleForDocument;
|
|
ScopingShim.prototype['getStyleAst'] = ScopingShim.prototype.getStyleAst;
|
|
ScopingShim.prototype['styleAstToString'] = ScopingShim.prototype.styleAstToString;
|
|
ScopingShim.prototype['flushCustomStyles'] = ScopingShim.prototype.flushCustomStyles;
|
|
Object.defineProperties(ScopingShim.prototype, {
|
|
'nativeShadow': {
|
|
get() {
|
|
return nativeShadow;
|
|
}
|
|
},
|
|
'nativeCss': {
|
|
get() {
|
|
return nativeCssVariables;
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2017 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
|
|
*/
|
|
|
|
/** @const {ScopingShim} */
|
|
const scopingShim$1 = new ScopingShim();
|
|
|
|
let ApplyShim;
|
|
let CustomStyleInterface;
|
|
|
|
if (window['ShadyCSS']) {
|
|
ApplyShim = window['ShadyCSS']['ApplyShim'];
|
|
CustomStyleInterface = window['ShadyCSS']['CustomStyleInterface'];
|
|
}
|
|
|
|
window.ShadyCSS = {
|
|
ScopingShim: scopingShim$1,
|
|
/**
|
|
* @param {!HTMLTemplateElement} template
|
|
* @param {string} elementName
|
|
* @param {string=} elementExtends
|
|
*/
|
|
prepareTemplate(template, elementName, elementExtends) {
|
|
scopingShim$1.flushCustomStyles();
|
|
scopingShim$1.prepareTemplate(template, elementName, elementExtends);
|
|
},
|
|
|
|
/**
|
|
* @param {!HTMLElement} element
|
|
* @param {Object=} properties
|
|
*/
|
|
styleSubtree(element, properties) {
|
|
scopingShim$1.flushCustomStyles();
|
|
scopingShim$1.styleSubtree(element, properties);
|
|
},
|
|
|
|
/**
|
|
* @param {!HTMLElement} element
|
|
*/
|
|
styleElement(element) {
|
|
scopingShim$1.flushCustomStyles();
|
|
scopingShim$1.styleElement(element);
|
|
},
|
|
|
|
/**
|
|
* @param {Object=} properties
|
|
*/
|
|
styleDocument(properties) {
|
|
scopingShim$1.flushCustomStyles();
|
|
scopingShim$1.styleDocument(properties);
|
|
},
|
|
|
|
/**
|
|
* @param {Element} element
|
|
* @param {string} property
|
|
* @return {string}
|
|
*/
|
|
getComputedStyleValue(element, property) {
|
|
return scopingShim$1.getComputedStyleValue(element, property);
|
|
},
|
|
|
|
nativeCss: nativeCssVariables,
|
|
|
|
nativeShadow: nativeShadow
|
|
};
|
|
|
|
if (ApplyShim) {
|
|
window.ShadyCSS.ApplyShim = ApplyShim;
|
|
}
|
|
|
|
if (CustomStyleInterface) {
|
|
window.ShadyCSS.CustomStyleInterface = CustomStyleInterface;
|
|
}
|
|
|
|
/**
|
|
* @license
|
|
* 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() {
|
|
|
|
'use strict';
|
|
|
|
var customElements = window['customElements'];
|
|
var HTMLImports = window['HTMLImports'];
|
|
|
|
if (customElements && customElements['polyfillWrapFlushCallback']) {
|
|
// Here we ensure that the public `HTMLImports.whenReady`
|
|
// always comes *after* custom elements have upgraded.
|
|
var flushCallback;
|
|
var runAndClearCallback = function runAndClearCallback() {
|
|
if (flushCallback) {
|
|
var cb = flushCallback;
|
|
flushCallback = null;
|
|
cb();
|
|
return true;
|
|
}
|
|
};
|
|
var origWhenReady = HTMLImports['whenReady'];
|
|
customElements['polyfillWrapFlushCallback'](function(cb) {
|
|
flushCallback = cb;
|
|
origWhenReady(runAndClearCallback);
|
|
});
|
|
|
|
HTMLImports['whenReady'] = function(cb) {
|
|
origWhenReady(function() {
|
|
// custom element code may add dynamic imports
|
|
// to match processing of native custom elements before
|
|
// domContentLoaded, we wait for these imports to resolve first.
|
|
if (runAndClearCallback()) {
|
|
HTMLImports['whenReady'](cb);
|
|
} else {
|
|
cb();
|
|
}
|
|
});
|
|
};
|
|
|
|
}
|
|
|
|
HTMLImports['whenReady'](function() {
|
|
requestAnimationFrame(function() {
|
|
window.dispatchEvent(new CustomEvent('WebComponentsReady'));
|
|
});
|
|
});
|
|
|
|
})();
|
|
|
|
/**
|
|
* @license
|
|
* 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() {
|
|
'use strict';
|
|
// 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.
|
|
//
|
|
// NOTE: position: relative fixes IE's failure to inherit opacity
|
|
// when a child is not statically positioned.
|
|
var style = document.createElement('style');
|
|
style.textContent = ''
|
|
+ 'body {'
|
|
+ 'transition: opacity ease-in 0.2s;'
|
|
+ ' } \n'
|
|
+ 'body[unresolved] {'
|
|
+ 'opacity: 0; display: block; overflow: hidden; position: relative;'
|
|
+ ' } \n'
|
|
;
|
|
var head = document.querySelector('head');
|
|
head.insertBefore(style, head.firstChild);
|
|
|
|
})();
|
|
|
|
/**
|
|
@license
|
|
Copyright (c) 2017 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
|
|
*/
|
|
/*
|
|
* Polyfills loaded: HTML Imports, Custom Elements, Shady DOM/Shady CSS
|
|
* Used in: Safari 9, Firefox, Edge
|
|
*/
|
|
|
|
}());
|