/*!

Copyright (C) 2014-2016 by Andrea Giammarchi - @WebReflection

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/
// global window Object
// optional polyfill info
//    'auto' used by default, everything is feature detected
//    'force' use the polyfill even if not fully needed
function installCustomElements(window, polyfill) {
  'use strict'; // DO NOT USE THIS FILE DIRECTLY, IT WON'T WORK
  // THIS IS A PROJECT BASED ON A BUILD SYSTEM
  // THIS FILE IS JUST WRAPPED UP RESULTING IN
  // build/document-register-element.node.js

  var document = window.document,
      Object = window.Object;

  var htmlClass = function (info) {
    // (C) Andrea Giammarchi - @WebReflection - MIT Style
    var catchClass = /^[A-Z]+[a-z]/,
        filterBy = function filterBy(re) {
      var arr = [],
          tag;

      for (tag in register) {
        if (re.test(tag)) arr.push(tag);
      }

      return arr;
    },
        add = function add(Class, tag) {
      tag = tag.toLowerCase();

      if (!(tag in register)) {
        register[Class] = (register[Class] || []).concat(tag);
        register[tag] = register[tag.toUpperCase()] = Class;
      }
    },
        register = (Object.create || Object)(null),
        htmlClass = {},
        i,
        section,
        tags,
        Class;

    for (section in info) {
      for (Class in info[section]) {
        tags = info[section][Class];
        register[Class] = tags;

        for (i = 0; i < tags.length; i++) {
          register[tags[i].toLowerCase()] = register[tags[i].toUpperCase()] = Class;
        }
      }
    }

    htmlClass.get = function get(tagOrClass) {
      return typeof tagOrClass === 'string' ? register[tagOrClass] || (catchClass.test(tagOrClass) ? [] : '') : filterBy(tagOrClass);
    };

    htmlClass.set = function set(tag, Class) {
      return catchClass.test(tag) ? add(tag, Class) : add(Class, tag), htmlClass;
    };

    return htmlClass;
  }({
    collections: {
      HTMLAllCollection: ['all'],
      HTMLCollection: ['forms'],
      HTMLFormControlsCollection: ['elements'],
      HTMLOptionsCollection: ['options']
    },
    elements: {
      Element: ['element'],
      HTMLAnchorElement: ['a'],
      HTMLAppletElement: ['applet'],
      HTMLAreaElement: ['area'],
      HTMLAttachmentElement: ['attachment'],
      HTMLAudioElement: ['audio'],
      HTMLBRElement: ['br'],
      HTMLBaseElement: ['base'],
      HTMLBodyElement: ['body'],
      HTMLButtonElement: ['button'],
      HTMLCanvasElement: ['canvas'],
      HTMLContentElement: ['content'],
      HTMLDListElement: ['dl'],
      HTMLDataElement: ['data'],
      HTMLDataListElement: ['datalist'],
      HTMLDetailsElement: ['details'],
      HTMLDialogElement: ['dialog'],
      HTMLDirectoryElement: ['dir'],
      HTMLDivElement: ['div'],
      HTMLDocument: ['document'],
      HTMLElement: ['element', 'abbr', 'address', 'article', 'aside', 'b', 'bdi', 'bdo', 'cite', 'code', 'command', 'dd', 'dfn', 'dt', 'em', 'figcaption', 'figure', 'footer', 'header', 'i', 'kbd', 'mark', 'nav', 'noscript', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'small', 'strong', 'sub', 'summary', 'sup', 'u', 'var', 'wbr'],
      HTMLEmbedElement: ['embed'],
      HTMLFieldSetElement: ['fieldset'],
      HTMLFontElement: ['font'],
      HTMLFormElement: ['form'],
      HTMLFrameElement: ['frame'],
      HTMLFrameSetElement: ['frameset'],
      HTMLHRElement: ['hr'],
      HTMLHeadElement: ['head'],
      HTMLHeadingElement: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
      HTMLHtmlElement: ['html'],
      HTMLIFrameElement: ['iframe'],
      HTMLImageElement: ['img'],
      HTMLInputElement: ['input'],
      HTMLKeygenElement: ['keygen'],
      HTMLLIElement: ['li'],
      HTMLLabelElement: ['label'],
      HTMLLegendElement: ['legend'],
      HTMLLinkElement: ['link'],
      HTMLMapElement: ['map'],
      HTMLMarqueeElement: ['marquee'],
      HTMLMediaElement: ['media'],
      HTMLMenuElement: ['menu'],
      HTMLMenuItemElement: ['menuitem'],
      HTMLMetaElement: ['meta'],
      HTMLMeterElement: ['meter'],
      HTMLModElement: ['del', 'ins'],
      HTMLOListElement: ['ol'],
      HTMLObjectElement: ['object'],
      HTMLOptGroupElement: ['optgroup'],
      HTMLOptionElement: ['option'],
      HTMLOutputElement: ['output'],
      HTMLParagraphElement: ['p'],
      HTMLParamElement: ['param'],
      HTMLPictureElement: ['picture'],
      HTMLPreElement: ['pre'],
      HTMLProgressElement: ['progress'],
      HTMLQuoteElement: ['blockquote', 'q', 'quote'],
      HTMLScriptElement: ['script'],
      HTMLSelectElement: ['select'],
      HTMLShadowElement: ['shadow'],
      HTMLSlotElement: ['slot'],
      HTMLSourceElement: ['source'],
      HTMLSpanElement: ['span'],
      HTMLStyleElement: ['style'],
      HTMLTableCaptionElement: ['caption'],
      HTMLTableCellElement: ['td', 'th'],
      HTMLTableColElement: ['col', 'colgroup'],
      HTMLTableElement: ['table'],
      HTMLTableRowElement: ['tr'],
      HTMLTableSectionElement: ['thead', 'tbody', 'tfoot'],
      HTMLTemplateElement: ['template'],
      HTMLTextAreaElement: ['textarea'],
      HTMLTimeElement: ['time'],
      HTMLTitleElement: ['title'],
      HTMLTrackElement: ['track'],
      HTMLUListElement: ['ul'],
      HTMLUnknownElement: ['unknown', 'vhgroupv', 'vkeygen'],
      HTMLVideoElement: ['video']
    },
    nodes: {
      Attr: ['node'],
      Audio: ['audio'],
      CDATASection: ['node'],
      CharacterData: ['node'],
      Comment: ['#comment'],
      Document: ['#document'],
      DocumentFragment: ['#document-fragment'],
      DocumentType: ['node'],
      HTMLDocument: ['#document'],
      Image: ['img'],
      Option: ['option'],
      ProcessingInstruction: ['node'],
      ShadowRoot: ['#shadow-root'],
      Text: ['#text'],
      XMLDocument: ['xml']
    }
  }); // passed at runtime, configurable via nodejs module


  if (typeof polyfill !== 'object') polyfill = {
    type: polyfill || 'auto'
  };

  var // V0 polyfill entry
  REGISTER_ELEMENT = 'registerElement',
      // IE < 11 only + old WebKit for attributes + feature detection
  EXPANDO_UID = '__' + REGISTER_ELEMENT + (window.Math.random() * 10e4 >> 0),
      // shortcuts and costants
  ADD_EVENT_LISTENER = 'addEventListener',
      ATTACHED = 'attached',
      CALLBACK = 'Callback',
      DETACHED = 'detached',
      EXTENDS = 'extends',
      ATTRIBUTE_CHANGED_CALLBACK = 'attributeChanged' + CALLBACK,
      ATTACHED_CALLBACK = ATTACHED + CALLBACK,
      CONNECTED_CALLBACK = 'connected' + CALLBACK,
      DISCONNECTED_CALLBACK = 'disconnected' + CALLBACK,
      CREATED_CALLBACK = 'created' + CALLBACK,
      DETACHED_CALLBACK = DETACHED + CALLBACK,
      ADDITION = 'ADDITION',
      MODIFICATION = 'MODIFICATION',
      REMOVAL = 'REMOVAL',
      DOM_ATTR_MODIFIED = 'DOMAttrModified',
      DOM_CONTENT_LOADED = 'DOMContentLoaded',
      DOM_SUBTREE_MODIFIED = 'DOMSubtreeModified',
      PREFIX_TAG = '<',
      PREFIX_IS = '=',
      // valid and invalid node names
  validName = /^[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+$/,
      invalidNames = ['ANNOTATION-XML', 'COLOR-PROFILE', 'FONT-FACE', 'FONT-FACE-SRC', 'FONT-FACE-URI', 'FONT-FACE-FORMAT', 'FONT-FACE-NAME', 'MISSING-GLYPH'],
      // registered types and their prototypes
  types = [],
      protos = [],
      // to query subnodes
  query = '',
      // html shortcut used to feature detect
  documentElement = document.documentElement,
      // ES5 inline helpers || basic patches
  indexOf = types.indexOf || function (v) {
    for (var i = this.length; i-- && this[i] !== v;) {}

    return i;
  },
      // other helpers / shortcuts
  OP = Object.prototype,
      hOP = OP.hasOwnProperty,
      iPO = OP.isPrototypeOf,
      defineProperty = Object.defineProperty,
      empty = [],
      gOPD = Object.getOwnPropertyDescriptor,
      gOPN = Object.getOwnPropertyNames,
      gPO = Object.getPrototypeOf,
      sPO = Object.setPrototypeOf,
      // jshint proto: true
  hasProto = !!Object.__proto__,
      // V1 helpers
  fixGetClass = false,
      DRECEV1 = '__dreCEv1',
      customElements = window.customElements,
      usableCustomElements = !/^force/.test(polyfill.type) && !!(customElements && customElements.define && customElements.get && customElements.whenDefined),
      Dict = Object.create || Object,
      Map = window.Map || function Map() {
    var K = [],
        V = [],
        i;
    return {
      get: function get(k) {
        return V[indexOf.call(K, k)];
      },
      set: function set(k, v) {
        i = indexOf.call(K, k);
        if (i < 0) V[K.push(k) - 1] = v;else V[i] = v;
      }
    };
  },
      Promise = window.Promise || function (fn) {
    var notify = [],
        done = false,
        p = {
      catch: function _catch() {
        return p;
      },
      then: function then(cb) {
        notify.push(cb);
        if (done) setTimeout(resolve, 1);
        return p;
      }
    };

    function resolve(value) {
      done = true;

      while (notify.length) notify.shift()(value);
    }

    fn(resolve);
    return p;
  },
      justCreated = false,
      constructors = Dict(null),
      waitingList = Dict(null),
      nodeNames = new Map(),
      secondArgument = function secondArgument(is) {
    return is.toLowerCase();
  },
      // used to create unique instances
  create = Object.create || function Bridge(proto) {
    // silly broken polyfill probably ever used but short enough to work
    return proto ? (Bridge.prototype = proto, new Bridge()) : this;
  },
      // will set the prototype if possible
  // or copy over all properties
  setPrototype = sPO || (hasProto ? function (o, p) {
    o.__proto__ = p;
    return o;
  } : gOPN && gOPD ? function () {
    function setProperties(o, p) {
      for (var key, names = gOPN(p), i = 0, length = names.length; i < length; i++) {
        key = names[i];

        if (!hOP.call(o, key)) {
          defineProperty(o, key, gOPD(p, key));
        }
      }
    }

    return function (o, p) {
      do {
        setProperties(o, p);
      } while ((p = gPO(p)) && !iPO.call(p, o));

      return o;
    };
  }() : function (o, p) {
    for (var key in p) {
      o[key] = p[key];
    }

    return o;
  }),
      // DOM shortcuts and helpers, if any
  MutationObserver = window.MutationObserver || window.WebKitMutationObserver,
      HTMLElementPrototype = (window.HTMLElement || window.Element || window.Node).prototype,
      IE8 = !iPO.call(HTMLElementPrototype, documentElement),
      safeProperty = IE8 ? function (o, k, d) {
    o[k] = d.value;
    return o;
  } : defineProperty,
      isValidNode = IE8 ? function (node) {
    return node.nodeType === 1;
  } : function (node) {
    return iPO.call(HTMLElementPrototype, node);
  },
      targets = IE8 && [],
      attachShadow = HTMLElementPrototype.attachShadow,
      cloneNode = HTMLElementPrototype.cloneNode,
      dispatchEvent = HTMLElementPrototype.dispatchEvent,
      getAttribute = HTMLElementPrototype.getAttribute,
      hasAttribute = HTMLElementPrototype.hasAttribute,
      removeAttribute = HTMLElementPrototype.removeAttribute,
      setAttribute = HTMLElementPrototype.setAttribute,
      // replaced later on
  createElement = document.createElement,
      patchedCreateElement = createElement,
      // shared observer for all attributes
  attributesObserver = MutationObserver && {
    attributes: true,
    characterData: true,
    attributeOldValue: true
  },
      // useful to detect only if there's no MutationObserver
  DOMAttrModified = MutationObserver || function (e) {
    doesNotSupportDOMAttrModified = false;
    documentElement.removeEventListener(DOM_ATTR_MODIFIED, DOMAttrModified);
  },
      // will both be used to make DOMNodeInserted asynchronous
  asapQueue,
      asapTimer = 0,
      // internal flags
  V0 = REGISTER_ELEMENT in document && !/^force-all/.test(polyfill.type),
      setListener = true,
      justSetup = false,
      doesNotSupportDOMAttrModified = true,
      dropDomContentLoaded = true,
      // needed for the innerHTML helper
  notFromInnerHTMLHelper = true,
      // optionally defined later on
  onSubtreeModified,
      callDOMAttrModified,
      getAttributesMirror,
      observer,
      observe,
      // based on setting prototype capability
  // will check proto or the expando attribute
  // in order to setup the node once
  patchIfNotAlready,
      patch,
      // used for tests
  tmp; // IE11 disconnectedCallback issue #
  // to be tested before any createElement patch


  if (MutationObserver) {
    tmp = document.createElement('div');
    tmp.innerHTML = '<div><div></div></div>';
    new MutationObserver(function (mutations, observer) {
      if (mutations[0] && mutations[0].type == 'childList' && !mutations[0].removedNodes[0].childNodes.length) {
        tmp = gOPD(HTMLElementPrototype, 'innerHTML');

        var _set = tmp && tmp.set;

        if (_set) defineProperty(HTMLElementPrototype, 'innerHTML', {
          set: function set(value) {
            while (this.lastChild) this.removeChild(this.lastChild);

            _set.call(this, value);
          }
        });
      }

      observer.disconnect();
      tmp = null;
    }).observe(tmp, {
      childList: true,
      subtree: true
    });
    tmp.innerHTML = '';
  } // only if needed


  if (!V0) {
    if (sPO || hasProto) {
      patchIfNotAlready = function patchIfNotAlready(node, proto) {
        if (!iPO.call(proto, node)) {
          setupNode(node, proto);
        }
      };

      patch = setupNode;
    } else {
      patchIfNotAlready = function patchIfNotAlready(node, proto) {
        if (!node[EXPANDO_UID]) {
          node[EXPANDO_UID] = Object(true);
          setupNode(node, proto);
        }
      };

      patch = patchIfNotAlready;
    }

    if (IE8) {
      doesNotSupportDOMAttrModified = false;

      (function () {
        var descriptor = gOPD(HTMLElementPrototype, ADD_EVENT_LISTENER),
            addEventListener = descriptor.value,
            patchedRemoveAttribute = function patchedRemoveAttribute(name) {
          var e = new CustomEvent(DOM_ATTR_MODIFIED, {
            bubbles: true
          });
          e.attrName = name;
          e.prevValue = getAttribute.call(this, name);
          e.newValue = null;
          e[REMOVAL] = e.attrChange = 2;
          removeAttribute.call(this, name);
          dispatchEvent.call(this, e);
        },
            patchedSetAttribute = function patchedSetAttribute(name, value) {
          var had = hasAttribute.call(this, name),
              old = had && getAttribute.call(this, name),
              e = new CustomEvent(DOM_ATTR_MODIFIED, {
            bubbles: true
          });
          setAttribute.call(this, name, value);
          e.attrName = name;
          e.prevValue = had ? old : null;
          e.newValue = value;

          if (had) {
            e[MODIFICATION] = e.attrChange = 1;
          } else {
            e[ADDITION] = e.attrChange = 0;
          }

          dispatchEvent.call(this, e);
        },
            onPropertyChange = function onPropertyChange(e) {
          // jshint eqnull:true
          var node = e.currentTarget,
              superSecret = node[EXPANDO_UID],
              propertyName = e.propertyName,
              event;

          if (superSecret.hasOwnProperty(propertyName)) {
            superSecret = superSecret[propertyName];
            event = new CustomEvent(DOM_ATTR_MODIFIED, {
              bubbles: true
            });
            event.attrName = superSecret.name;
            event.prevValue = superSecret.value || null;
            event.newValue = superSecret.value = node[propertyName] || null;

            if (event.prevValue == null) {
              event[ADDITION] = event.attrChange = 0;
            } else {
              event[MODIFICATION] = event.attrChange = 1;
            }

            dispatchEvent.call(node, event);
          }
        };

        descriptor.value = function (type, handler, capture) {
          if (type === DOM_ATTR_MODIFIED && this[ATTRIBUTE_CHANGED_CALLBACK] && this.setAttribute !== patchedSetAttribute) {
            this[EXPANDO_UID] = {
              className: {
                name: 'class',
                value: this.className
              }
            };
            this.setAttribute = patchedSetAttribute;
            this.removeAttribute = patchedRemoveAttribute;
            addEventListener.call(this, 'propertychange', onPropertyChange);
          }

          addEventListener.call(this, type, handler, capture);
        };

        defineProperty(HTMLElementPrototype, ADD_EVENT_LISTENER, descriptor);
      })();
    } else if (!MutationObserver) {
      documentElement[ADD_EVENT_LISTENER](DOM_ATTR_MODIFIED, DOMAttrModified);
      documentElement.setAttribute(EXPANDO_UID, 1);
      documentElement.removeAttribute(EXPANDO_UID);

      if (doesNotSupportDOMAttrModified) {
        onSubtreeModified = function onSubtreeModified(e) {
          var node = this,
              oldAttributes,
              newAttributes,
              key;

          if (node === e.target) {
            oldAttributes = node[EXPANDO_UID];
            node[EXPANDO_UID] = newAttributes = getAttributesMirror(node);

            for (key in newAttributes) {
              if (!(key in oldAttributes)) {
                // attribute was added
                return callDOMAttrModified(0, node, key, oldAttributes[key], newAttributes[key], ADDITION);
              } else if (newAttributes[key] !== oldAttributes[key]) {
                // attribute was changed
                return callDOMAttrModified(1, node, key, oldAttributes[key], newAttributes[key], MODIFICATION);
              }
            } // checking if it has been removed


            for (key in oldAttributes) {
              if (!(key in newAttributes)) {
                // attribute removed
                return callDOMAttrModified(2, node, key, oldAttributes[key], newAttributes[key], REMOVAL);
              }
            }
          }
        };

        callDOMAttrModified = function callDOMAttrModified(attrChange, currentTarget, attrName, prevValue, newValue, action) {
          var e = {
            attrChange: attrChange,
            currentTarget: currentTarget,
            attrName: attrName,
            prevValue: prevValue,
            newValue: newValue
          };
          e[action] = attrChange;
          onDOMAttrModified(e);
        };

        getAttributesMirror = function getAttributesMirror(node) {
          for (var attr, name, result = {}, attributes = node.attributes, i = 0, length = attributes.length; i < length; i++) {
            attr = attributes[i];
            name = attr.name;

            if (name !== 'setAttribute') {
              result[name] = attr.value;
            }
          }

          return result;
        };
      }
    } // set as enumerable, writable and configurable


    document[REGISTER_ELEMENT] = function registerElement(type, options) {
      upperType = type.toUpperCase();

      if (setListener) {
        // only first time document.registerElement is used
        // we need to set this listener
        // setting it by default might slow down for no reason
        setListener = false;

        if (MutationObserver) {
          observer = function (attached, detached) {
            function checkEmAll(list, callback) {
              for (var i = 0, length = list.length; i < length; callback(list[i++])) {}
            }

            return new MutationObserver(function (records) {
              for (var current, node, newValue, i = 0, length = records.length; i < length; i++) {
                current = records[i];

                if (current.type === 'childList') {
                  checkEmAll(current.addedNodes, attached);
                  checkEmAll(current.removedNodes, detached);
                } else {
                  node = current.target;

                  if (notFromInnerHTMLHelper && node[ATTRIBUTE_CHANGED_CALLBACK] && current.attributeName !== 'style') {
                    newValue = getAttribute.call(node, current.attributeName);

                    if (newValue !== current.oldValue) {
                      node[ATTRIBUTE_CHANGED_CALLBACK](current.attributeName, current.oldValue, newValue);
                    }
                  }
                }
              }
            });
          }(executeAction(ATTACHED), executeAction(DETACHED));

          observe = function observe(node) {
            observer.observe(node, {
              childList: true,
              subtree: true
            });
            return node;
          };

          observe(document);

          if (attachShadow) {
            HTMLElementPrototype.attachShadow = function () {
              return observe(attachShadow.apply(this, arguments));
            };
          }
        } else {
          asapQueue = [];
          document[ADD_EVENT_LISTENER]('DOMNodeInserted', onDOMNode(ATTACHED));
          document[ADD_EVENT_LISTENER]('DOMNodeRemoved', onDOMNode(DETACHED));
        }

        document[ADD_EVENT_LISTENER](DOM_CONTENT_LOADED, onReadyStateChange);
        document[ADD_EVENT_LISTENER]('readystatechange', onReadyStateChange);

        HTMLElementPrototype.cloneNode = function (deep) {
          var node = cloneNode.call(this, !!deep),
              i = getTypeIndex(node);
          if (-1 < i) patch(node, protos[i]);
          if (deep && query.length) loopAndSetup(node.querySelectorAll(query));
          return node;
        };
      }

      if (justSetup) return justSetup = false;

      if (-2 < indexOf.call(types, PREFIX_IS + upperType) + indexOf.call(types, PREFIX_TAG + upperType)) {
        throwTypeError(type);
      }

      if (!validName.test(upperType) || -1 < indexOf.call(invalidNames, upperType)) {
        throw new Error('The type ' + type + ' is invalid');
      }

      var constructor = function constructor() {
        return extending ? document.createElement(nodeName, upperType) : document.createElement(nodeName);
      },
          opt = options || OP,
          extending = hOP.call(opt, EXTENDS),
          nodeName = extending ? options[EXTENDS].toUpperCase() : upperType,
          upperType,
          i;

      if (extending && -1 < indexOf.call(types, PREFIX_TAG + nodeName)) {
        throwTypeError(nodeName);
      }

      i = types.push((extending ? PREFIX_IS : PREFIX_TAG) + upperType) - 1;
      query = query.concat(query.length ? ',' : '', extending ? nodeName + '[is="' + type.toLowerCase() + '"]' : nodeName);
      constructor.prototype = protos[i] = hOP.call(opt, 'prototype') ? opt.prototype : create(HTMLElementPrototype);
      if (query.length) loopAndVerify(document.querySelectorAll(query), ATTACHED);
      return constructor;
    };

    document.createElement = patchedCreateElement = function patchedCreateElement(localName, typeExtension) {
      var is = getIs(typeExtension),
          node = is ? createElement.call(document, localName, secondArgument(is)) : createElement.call(document, localName),
          name = '' + localName,
          i = indexOf.call(types, (is ? PREFIX_IS : PREFIX_TAG) + (is || name).toUpperCase()),
          setup = -1 < i;

      if (is) {
        node.setAttribute('is', is = is.toLowerCase());

        if (setup) {
          setup = isInQSA(name.toUpperCase(), is);
        }
      }

      notFromInnerHTMLHelper = !document.createElement.innerHTMLHelper;
      if (setup) patch(node, protos[i]);
      return node;
    };
  }

  function ASAP() {
    var queue = asapQueue.splice(0, asapQueue.length);
    asapTimer = 0;

    while (queue.length) {
      queue.shift().call(null, queue.shift());
    }
  }

  function loopAndVerify(list, action) {
    for (var i = 0, length = list.length; i < length; i++) {
      verifyAndSetupAndAction(list[i], action);
    }
  }

  function loopAndSetup(list) {
    for (var i = 0, length = list.length, node; i < length; i++) {
      node = list[i];
      patch(node, protos[getTypeIndex(node)]);
    }
  }

  function executeAction(action) {
    return function (node) {
      if (isValidNode(node)) {
        verifyAndSetupAndAction(node, action);
        if (query.length) loopAndVerify(node.querySelectorAll(query), action);
      }
    };
  }

  function getTypeIndex(target) {
    var is = getAttribute.call(target, 'is'),
        nodeName = target.nodeName.toUpperCase(),
        i = indexOf.call(types, is ? PREFIX_IS + is.toUpperCase() : PREFIX_TAG + nodeName);
    return is && -1 < i && !isInQSA(nodeName, is) ? -1 : i;
  }

  function isInQSA(name, type) {
    return -1 < query.indexOf(name + '[is="' + type + '"]');
  }

  function onDOMAttrModified(e) {
    var node = e.currentTarget,
        attrChange = e.attrChange,
        attrName = e.attrName,
        target = e.target,
        addition = e[ADDITION] || 2,
        removal = e[REMOVAL] || 3;

    if (notFromInnerHTMLHelper && (!target || target === node) && node[ATTRIBUTE_CHANGED_CALLBACK] && attrName !== 'style' && (e.prevValue !== e.newValue || // IE9, IE10, and Opera 12 gotcha
    e.newValue === '' && (attrChange === addition || attrChange === removal))) {
      node[ATTRIBUTE_CHANGED_CALLBACK](attrName, attrChange === addition ? null : e.prevValue, attrChange === removal ? null : e.newValue);
    }
  }

  function onDOMNode(action) {
    var executor = executeAction(action);
    return function (e) {
      asapQueue.push(executor, e.target);
      if (asapTimer) clearTimeout(asapTimer);
      asapTimer = setTimeout(ASAP, 1);
    };
  }

  function onReadyStateChange(e) {
    if (dropDomContentLoaded) {
      dropDomContentLoaded = false;
      e.currentTarget.removeEventListener(DOM_CONTENT_LOADED, onReadyStateChange);
    }

    if (query.length) loopAndVerify((e.target || document).querySelectorAll(query), e.detail === DETACHED ? DETACHED : ATTACHED);
    if (IE8) purge();
  }

  function patchedSetAttribute(name, value) {
    // jshint validthis:true
    var self = this;
    setAttribute.call(self, name, value);
    onSubtreeModified.call(self, {
      target: self
    });
  }

  function setupNode(node, proto) {
    setPrototype(node, proto);

    if (observer) {
      observer.observe(node, attributesObserver);
    } else {
      if (doesNotSupportDOMAttrModified) {
        node.setAttribute = patchedSetAttribute;
        node[EXPANDO_UID] = getAttributesMirror(node);
        node[ADD_EVENT_LISTENER](DOM_SUBTREE_MODIFIED, onSubtreeModified);
      }

      node[ADD_EVENT_LISTENER](DOM_ATTR_MODIFIED, onDOMAttrModified);
    }

    if (node[CREATED_CALLBACK] && notFromInnerHTMLHelper) {
      node.created = true;
      node[CREATED_CALLBACK]();
      node.created = false;
    }
  }

  function purge() {
    for (var node, i = 0, length = targets.length; i < length; i++) {
      node = targets[i];

      if (!documentElement.contains(node)) {
        length--;
        targets.splice(i--, 1);
        verifyAndSetupAndAction(node, DETACHED);
      }
    }
  }

  function throwTypeError(type) {
    throw new Error('A ' + type + ' type is already registered');
  }

  function verifyAndSetupAndAction(node, action) {
    var fn,
        i = getTypeIndex(node),
        counterAction;

    if (-1 < i) {
      patchIfNotAlready(node, protos[i]);
      i = 0;

      if (action === ATTACHED && !node[ATTACHED]) {
        node[DETACHED] = false;
        node[ATTACHED] = true;
        counterAction = 'connected';
        i = 1;

        if (IE8 && indexOf.call(targets, node) < 0) {
          targets.push(node);
        }
      } else if (action === DETACHED && !node[DETACHED]) {
        node[ATTACHED] = false;
        node[DETACHED] = true;
        counterAction = 'disconnected';
        i = 1;
      }

      if (i && (fn = node[action + CALLBACK] || node[counterAction + CALLBACK])) fn.call(node);
    }
  } // V1 in da House!


  function CustomElementRegistry() {}

  CustomElementRegistry.prototype = {
    constructor: CustomElementRegistry,
    // a workaround for the stubborn WebKit
    define: usableCustomElements ? function (name, Class, options) {
      if (options) {
        CERDefine(name, Class, options);
      } else {
        var NAME = name.toUpperCase();
        constructors[NAME] = {
          constructor: Class,
          create: [NAME]
        };
        nodeNames.set(Class, NAME);
        customElements.define(name, Class);
      }
    } : CERDefine,
    get: usableCustomElements ? function (name) {
      return customElements.get(name) || get(name);
    } : get,
    whenDefined: usableCustomElements ? function (name) {
      return Promise.race([customElements.whenDefined(name), whenDefined(name)]);
    } : whenDefined
  };

  function CERDefine(name, Class, options) {
    var is = options && options[EXTENDS] || '',
        CProto = Class.prototype,
        proto = create(CProto),
        attributes = Class.observedAttributes || empty,
        definition = {
      prototype: proto // TODO: is this needed at all since it's inherited?
      // defineProperty(proto, 'constructor', {value: Class});

    };
    safeProperty(proto, CREATED_CALLBACK, {
      value: function value() {
        if (justCreated) justCreated = false;else if (!this[DRECEV1]) {
          this[DRECEV1] = true;
          new Class(this);
          if (CProto[CREATED_CALLBACK]) CProto[CREATED_CALLBACK].call(this);
          var info = constructors[nodeNames.get(Class)];

          if (!usableCustomElements || info.create.length > 1) {
            notifyAttributes(this);
          }
        }
      }
    });
    safeProperty(proto, ATTRIBUTE_CHANGED_CALLBACK, {
      value: function value(name) {
        if (-1 < indexOf.call(attributes, name)) CProto[ATTRIBUTE_CHANGED_CALLBACK].apply(this, arguments);
      }
    });

    if (CProto[CONNECTED_CALLBACK]) {
      safeProperty(proto, ATTACHED_CALLBACK, {
        value: CProto[CONNECTED_CALLBACK]
      });
    }

    if (CProto[DISCONNECTED_CALLBACK]) {
      safeProperty(proto, DETACHED_CALLBACK, {
        value: CProto[DISCONNECTED_CALLBACK]
      });
    }

    if (is) definition[EXTENDS] = is;
    name = name.toUpperCase();
    constructors[name] = {
      constructor: Class,
      create: is ? [is, secondArgument(name)] : [name]
    };
    nodeNames.set(Class, name);
    document[REGISTER_ELEMENT](name.toLowerCase(), definition);
    whenDefined(name);
    waitingList[name].r();
  }

  function get(name) {
    var info = constructors[name.toUpperCase()];
    return info && info.constructor;
  }

  function getIs(options) {
    return typeof options === 'string' ? options : options && options.is || '';
  }

  function notifyAttributes(self) {
    var callback = self[ATTRIBUTE_CHANGED_CALLBACK],
        attributes = callback ? self.attributes : empty,
        i = attributes.length,
        attribute;

    while (i--) {
      attribute = attributes[i]; // || attributes.item(i);

      callback.call(self, attribute.name || attribute.nodeName, null, attribute.value || attribute.nodeValue);
    }
  }

  function whenDefined(name) {
    name = name.toUpperCase();

    if (!(name in waitingList)) {
      waitingList[name] = {};
      waitingList[name].p = new Promise(function (resolve) {
        waitingList[name].r = resolve;
      });
    }

    return waitingList[name].p;
  }

  function polyfillV1() {
    if (customElements) delete window.customElements;
    defineProperty(window, 'customElements', {
      configurable: true,
      value: new CustomElementRegistry()
    });
    defineProperty(window, 'CustomElementRegistry', {
      configurable: true,
      value: CustomElementRegistry
    });

    for (var patchClass = function patchClass(name) {
      var Class = window[name];

      if (Class) {
        window[name] = function CustomElementsV1(self) {
          var info, isNative;
          if (!self) self = this;

          if (!self[DRECEV1]) {
            justCreated = true;
            info = constructors[nodeNames.get(self.constructor)];
            isNative = usableCustomElements && info.create.length === 1;
            self = isNative ? Reflect.construct(Class, empty, info.constructor) : document.createElement.apply(document, info.create);
            self[DRECEV1] = true;
            justCreated = false;
            if (!isNative) notifyAttributes(self);
          }

          return self;
        };

        window[name].prototype = Class.prototype;

        try {
          Class.prototype.constructor = window[name];
        } catch (WebKit) {
          fixGetClass = true;
          defineProperty(Class, DRECEV1, {
            value: window[name]
          });
        }
      }
    }, Classes = htmlClass.get(/^HTML[A-Z]*[a-z]/), i = Classes.length; i--; patchClass(Classes[i])) {}

    document.createElement = function (name, options) {
      var is = getIs(options);
      return is ? patchedCreateElement.call(this, name, secondArgument(is)) : patchedCreateElement.call(this, name);
    };

    if (!V0) {
      justSetup = true;
      document[REGISTER_ELEMENT]('');
    }
  } // if customElements is not there at all


  if (!customElements || /^force/.test(polyfill.type)) polyfillV1();else if (!polyfill.noBuiltIn) {
    // if available test extends work as expected
    try {
      ;

      (function (DRE, options, name) {
        options[EXTENDS] = 'a';
        DRE.prototype = create(HTMLAnchorElement.prototype);
        DRE.prototype.constructor = DRE;
        window.customElements.define(name, DRE, options);

        if (getAttribute.call(document.createElement('a', {
          is: name
        }), 'is') !== name || usableCustomElements && getAttribute.call(new DRE(), 'is') !== name) {
          throw options;
        }
      })(function DRE() {
        return Reflect.construct(HTMLAnchorElement, [], DRE);
      }, {}, 'document-register-element-a');
    } catch (o_O) {
      // or force the polyfill if not
      // and keep internal original reference
      polyfillV1();
    }
  } // FireFox only issue

  if (!polyfill.noBuiltIn) {
    try {
      createElement.call(document, 'a', 'a');
    } catch (FireFox) {
      secondArgument = function secondArgument(is) {
        return {
          is: is.toLowerCase()
        };
      };
    }
  }
}

export default installCustomElements;