aboutsummaryrefslogtreecommitdiff
path: root/catapult/third_party/polymer/components/shadycss/src/document-watcher.js
diff options
context:
space:
mode:
Diffstat (limited to 'catapult/third_party/polymer/components/shadycss/src/document-watcher.js')
-rw-r--r--catapult/third_party/polymer/components/shadycss/src/document-watcher.js198
1 files changed, 198 insertions, 0 deletions
diff --git a/catapult/third_party/polymer/components/shadycss/src/document-watcher.js b/catapult/third_party/polymer/components/shadycss/src/document-watcher.js
new file mode 100644
index 00000000..9cf34f05
--- /dev/null
+++ b/catapult/third_party/polymer/components/shadycss/src/document-watcher.js
@@ -0,0 +1,198 @@
+/**
+@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
+*/
+
+'use strict';
+
+import {nativeShadow} from './style-settings.js';
+import StyleTransformer from './style-transformer.js';
+import {getIsExtends, elementHasBuiltCss, wrap} from './style-util.js';
+
+export let flush = function() {};
+
+/**
+ * @param {!Element} element
+ * @return {string}
+ */
+function getClasses(element) {
+ if (element.classList && element.classList.value) {
+ return element.classList.value;
+ } else {
+ // NOTE: className is patched to remove scoping classes in ShadyDOM
+ // use getAttribute('class') instead, which is unpatched
+ return element.getAttribute('class') || '';
+ }
+}
+
+const scopeRegExp = new RegExp(`${StyleTransformer.SCOPE_NAME}\\s*([^\\s]*)`);
+
+/**
+ * @param {!Element} element
+ * @return {string}
+ */
+export function getCurrentScope(element) {
+ const match = getClasses(element).match(scopeRegExp);
+ if (match) {
+ return match[1];
+ } else {
+ return '';
+ }
+}
+
+/**
+ * @param {!Node} node
+ */
+export function getOwnerScope(node) {
+ const ownerRoot = wrap(node).getRootNode();
+ if (ownerRoot === node || ownerRoot === node.ownerDocument) {
+ return '';
+ }
+ const host = /** @type {!ShadowRoot} */(ownerRoot).host;
+ if (!host) {
+ // this may actually be a document fragment
+ return '';
+ }
+ return getIsExtends(host).is;
+}
+
+/**
+ * @param {!Element} element
+ */
+export function ensureCorrectScope(element) {
+ const currentScope = getCurrentScope(element);
+ const ownerRoot = wrap(element).getRootNode();
+ if (ownerRoot === element) {
+ return;
+ }
+ if (currentScope && ownerRoot === element.ownerDocument) {
+ // node was scoped, but now is in document
+ StyleTransformer.domRemoveScope(element, currentScope);
+ } else if (ownerRoot instanceof ShadowRoot) {
+ const ownerScope = getOwnerScope(element);
+ if (ownerScope !== currentScope) {
+ // node was scoped, but not by its current owner
+ StyleTransformer.domReplaceScope(element, currentScope, ownerScope);
+ }
+ }
+}
+
+/**
+ * @param {!HTMLElement|!HTMLDocument} element
+ */
+export function ensureCorrectSubtreeScoping(element) {
+ // find unscoped subtree nodes
+ const unscopedNodes = window['ShadyDOM']['nativeMethods']['querySelectorAll'].call(
+ element, `:not(.${StyleTransformer.SCOPE_NAME})`);
+
+ for (let j = 0; j < unscopedNodes.length; j++) {
+ // it's possible, during large batch inserts, that nodes that aren't
+ // scoped within the current scope were added.
+ // To make sure that any unscoped nodes that were inserted in the current batch are correctly styled,
+ // query all unscoped nodes and force their style-scope to be applied.
+ // This could happen if a sub-element appended an unscoped node in its shadowroot and this function
+ // runs on a parent element of the host of that unscoped node:
+ // parent-element -> element -> unscoped node
+ // Here unscoped node should have the style-scope element, not parent-element.
+ const unscopedNode = unscopedNodes[j];
+ const scopeForPreviouslyUnscopedNode = getOwnerScope(unscopedNode);
+ if (scopeForPreviouslyUnscopedNode) {
+ StyleTransformer.element(unscopedNode, scopeForPreviouslyUnscopedNode);
+ }
+ }
+}
+
+/**
+ * @param {HTMLElement} el
+ * @return {boolean}
+ */
+function isElementWithBuiltCss(el) {
+ if (el.localName === 'style' || el.localName === 'template') {
+ return elementHasBuiltCss(el);
+ }
+ return false;
+}
+
+/**
+ * @param {Array<MutationRecord|null>|null} mxns
+ */
+function 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 (n.nodeType !== Node.ELEMENT_NODE) {
+ continue;
+ }
+ n = /** @type {HTMLElement} */(n); // eslint-disable-line no-self-assign
+ let root = n.getRootNode();
+ let currentScope = getCurrentScope(n);
+ // node was scoped, but now is in document
+ // If this element has built css, we must not remove scoping as this node
+ // will be used as a template or style without re - applying scoping as an optimization
+ if (currentScope && root === n.ownerDocument && !isElementWithBuiltCss(n)) {
+ StyleTransformer.domRemoveScope(n, currentScope);
+ } else if (root instanceof ShadowRoot) {
+ const newScope = getOwnerScope(n);
+ // rescope current node and subtree if necessary
+ if (newScope !== currentScope) {
+ StyleTransformer.domReplaceScope(n, currentScope, newScope);
+ }
+ // make sure all the subtree elements are scoped correctly
+ ensureCorrectSubtreeScoping(n);
+ }
+ }
+ }
+}
+
+// if native Shadow DOM is being used, or ShadyDOM handles dynamic scoiping, do not activate the MutationObserver
+if (!nativeShadow && !(window['ShadyDOM'] && window['ShadyDOM']['handlesDynamicScoping'])) {
+ let observer = new MutationObserver(handler);
+ let start = (node) => {
+ observer.observe(node, {childList: true, subtree: true});
+ }
+ let nativeCustomElements = (window['customElements'] &&
+ !window['customElements']['polyfillWrapFlushCallback']);
+ // 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 = function() {
+ handler(observer.takeRecords());
+ }
+}