text: /**
  @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} 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 = 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 = 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());
    }
  }
  