text: "use strict";
  
  const webIDLConversions = require("webidl-conversions");
  const DOMException = require("domexception/webidl2js-wrapper");
  
  const NODE_TYPE = require("../node-type");
  
  const { HTML_NS } = require("../helpers/namespaces");
  const { getHTMLElementInterface } = require("../helpers/create-element");
  const { shadowIncludingInclusiveDescendantsIterator } = require("../helpers/shadow-dom");
  const { isValidCustomElementName, tryUpgradeElement, enqueueCEUpgradeReaction } = require("../helpers/custom-elements");
  
  const idlUtils = require("../generated/utils");
  const IDLFunction = require("../generated/Function.js");
  const HTMLUnknownElement = require("../generated/HTMLUnknownElement");
  
  const LIFECYCLE_CALLBACKS = [
    "connectedCallback",
    "disconnectedCallback",
    "adoptedCallback",
    "attributeChangedCallback"
  ];
  
  function convertToSequenceDOMString(obj) {
    if (!obj || !obj[Symbol.iterator]) {
      throw new TypeError("Invalid Sequence");
    }
  
    return Array.from(obj).map(webIDLConversions.DOMString);
  }
  
  // Returns true is the passed value is a valid constructor.
  // Borrowed from: https://stackoverflow.com/a/39336206/3832710
  function isConstructor(value) {
    if (typeof value !== "function") {
      return false;
    }
  
    try {
      const P = new Proxy(value, {
        construct() {
          return {};
        }
      });
  
      // eslint-disable-next-line no-new
      new P();
  
      return true;
    } catch {
      return false;
    }
  }
  
  // https://html.spec.whatwg.org/#customelementregistry
  class CustomElementRegistryImpl {
    constructor(globalObject) {
      this._customElementDefinitions = [];
      this._elementDefinitionIsRunning = false;
      this._whenDefinedPromiseMap = Object.create(null);
  
      this._globalObject = globalObject;
    }
  
    // https://html.spec.whatwg.org/#dom-customelementregistry-define
    define(name, constructor, options) {
      const { _globalObject } = this;
      const ctor = constructor.objectReference;
  
      if (!isConstructor(ctor)) {
        throw new TypeError("Constructor argument is not a constructor.");
      }
  
      if (!isValidCustomElementName(name)) {
        throw DOMException.create(_globalObject, ["Name argument is not a valid custom element name.", "SyntaxError"]);
      }
  
      const nameAlreadyRegistered = this._customElementDefinitions.some(entry => entry.name === name);
      if (nameAlreadyRegistered) {
        throw DOMException.create(_globalObject, [
          "This name has already been registered in the registry.",
          "NotSupportedError"
        ]);
      }
  
      const ctorAlreadyRegistered = this._customElementDefinitions.some(entry => entry.objectReference === ctor);
      if (ctorAlreadyRegistered) {
        throw DOMException.create(_globalObject, [
          "This constructor has already been registered in the registry.",
          "NotSupportedError"
        ]);
      }
  
      let localName = name;
  
      let extendsOption = null;
      if (options !== undefined && options.extends) {
        extendsOption = options.extends;
      }
  
      if (extendsOption !== null) {
        if (isValidCustomElementName(extendsOption)) {
          throw DOMException.create(_globalObject, [
            "Option extends value can't be a valid custom element name.",
            "NotSupportedError"
          ]);
        }
  
        const extendsInterface = getHTMLElementInterface(extendsOption);
        if (extendsInterface === HTMLUnknownElement) {
          throw DOMException.create(_globalObject, [
            `${extendsOption} is an HTMLUnknownElement.`,
            "NotSupportedError"
          ]);
        }
  
        localName = extendsOption;
      }
  
      if (this._elementDefinitionIsRunning) {
        throw DOMException.create(_globalObject, [
          "Invalid nested custom element definition.",
          "NotSupportedError"
        ]);
      }
  
      this._elementDefinitionIsRunning = true;
  
      let disableShadow = false;
      let observedAttributes = [];
      const lifecycleCallbacks = {
        connectedCallback: null,
        disconnectedCallback: null,
        adoptedCallback: null,
        attributeChangedCallback: null
      };
  
      let caughtError;
      try {
        const { prototype } = ctor;
  
        if (typeof prototype !== "object") {
          throw new TypeError("Invalid constructor prototype.");
        }
  
        for (const callbackName of LIFECYCLE_CALLBACKS) {
          const callbackValue = prototype[callbackName];
  
          if (callbackValue !== undefined) {
            lifecycleCallbacks[callbackName] = IDLFunction.convert(callbackValue, {
              context: `The lifecycle callback "${callbackName}"`
            });
          }
        }
  
        if (lifecycleCallbacks.attributeChangedCallback !== null) {
          const observedAttributesIterable = ctor.observedAttributes;
  
          if (observedAttributesIterable !== undefined) {
            observedAttributes = convertToSequenceDOMString(observedAttributesIterable);
          }
        }
  
        let disabledFeatures = [];
        const disabledFeaturesIterable = ctor.disabledFeatures;
        if (disabledFeaturesIterable) {
          disabledFeatures = convertToSequenceDOMString(disabledFeaturesIterable);
        }
  
        disableShadow = disabledFeatures.includes("shadow");
      } catch (err) {
        caughtError = err;
      } finally {
        this._elementDefinitionIsRunning = false;
      }
  
      if (caughtError !== undefined) {
        throw caughtError;
      }
  
      const definition = {
        name,
        localName,
        constructor,
        objectReference: ctor,
        observedAttributes,
        lifecycleCallbacks,
        disableShadow,
        constructionStack: []
      };
  
      this._customElementDefinitions.push(definition);
  
      const document = idlUtils.implForWrapper(this._globalObject._document);
  
      const upgradeCandidates = [];
      for (const candidate of shadowIncludingInclusiveDescendantsIterator(document)) {
        if (
          (candidate._namespaceURI === HTML_NS && candidate._localName === localName) &&
          (extendsOption === null || candidate._isValue === name)
        ) {
          upgradeCandidates.push(candidate);
        }
      }
  
      for (const upgradeCandidate of upgradeCandidates) {
        enqueueCEUpgradeReaction(upgradeCandidate, definition);
      }
  
      if (this._whenDefinedPromiseMap[name] !== undefined) {
        this._whenDefinedPromiseMap[name].resolve(undefined);
        delete this._whenDefinedPromiseMap[name];
      }
    }
  
    // https://html.spec.whatwg.org/#dom-customelementregistry-get
    get(name) {
      const definition = this._customElementDefinitions.find(entry => entry.name === name);
      return definition && definition.objectReference;
    }
  
    // https://html.spec.whatwg.org/#dom-customelementregistry-whendefined
    whenDefined(name) {
      if (!isValidCustomElementName(name)) {
        return Promise.reject(DOMException.create(
          this._globalObject,
          ["Name argument is not a valid custom element name.", "SyntaxError"]
        ));
      }
  
      const alreadyRegistered = this._customElementDefinitions.some(entry => entry.name === name);
      if (alreadyRegistered) {
        return Promise.resolve();
      }
  
      if (this._whenDefinedPromiseMap[name] === undefined) {
        let resolve;
        const promise = new Promise(r => {
          resolve = r;
        });
  
        // Store the pending Promise along with the extracted resolve callback to actually resolve the returned Promise,
        // once the custom element is registered.
        this._whenDefinedPromiseMap[name] = {
          promise,
          resolve
        };
      }
  
      return this._whenDefinedPromiseMap[name].promise;
    }
  
    // https://html.spec.whatwg.org/#dom-customelementregistry-upgrade
    upgrade(root) {
      for (const candidate of shadowIncludingInclusiveDescendantsIterator(root)) {
        if (candidate.nodeType === NODE_TYPE.ELEMENT_NODE) {
          tryUpgradeElement(candidate);
        }
      }
    }
  }
  
  module.exports = {
    implementation: CustomElementRegistryImpl
  };
  