/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  GeckoViewPrompter: "resource://gre/modules/GeckoViewPrompter.sys.mjs",
  GeckoViewClipboardPermission:
    "resource://gre/modules/GeckoViewClipboardPermission.sys.mjs",
});

const { debug, warn } = GeckoViewUtils.initLogging("GeckoViewPrompt");

export class PromptFactory {
  constructor() {
    this.wrappedJSObject = this;
  }

  handleEvent(aEvent) {
    switch (aEvent.type) {
      case "mozshowdropdown":
      case "mozshowdropdown-sourcetouch":
        this._handleSelect(
          aEvent.composedTarget,
          aEvent.composedTarget.isCombobox
        );
        break;
      case "MozOpenDateTimePicker":
        this._handleDateTime(aEvent.composedTarget);
        break;
      case "click":
        this._handleClick(aEvent);
        break;
      case "DOMPopupBlocked":
        this._handlePopupBlocked(aEvent);
        break;
      case "DOMRedirectBlocked":
        this._handleRedirectBlocked(aEvent);
        break;
    }
  }

  _handleClick(aEvent) {
    const target = aEvent.composedTarget;
    const className = ChromeUtils.getClassName(target);
    if (className !== "HTMLInputElement" && className !== "HTMLSelectElement") {
      return;
    }

    if (
      target.isContentEditable ||
      target.disabled ||
      target.readOnly ||
      !target.willValidate
    ) {
      // target.willValidate is false when any associated fieldset is disabled,
      // in which case this element is treated as disabled per spec.
      return;
    }

    if (className === "HTMLSelectElement") {
      if (!target.isCombobox) {
        this._handleSelect(target, /* aIsDropDown = */ false);
        return;
      }
      // combobox select is handled by mozshowdropdown.
      return;
    }

    const type = target.type;
    if (type === "month" || type === "week") {
      // If there's a shadow root, the MozOpenDateTimePicker event takes care
      // of this. Right now for these input types there's never a shadow root.
      // Once we support UA widgets for month/week inputs (see bug 888320), we
      // can remove this.
      if (!target.openOrClosedShadowRoot) {
        this._handleDateTime(target);
        aEvent.preventDefault();
      }
    }
  }

  _generateSelectItems(aElement) {
    const win = aElement.ownerGlobal;
    let id = 0;
    const map = {};

    const items = (function enumList(elem, disabled) {
      const items = [];
      const children = elem.children;
      for (let i = 0; i < children.length; i++) {
        const child = children[i];
        if (win.getComputedStyle(child).display === "none") {
          continue;
        }
        const item = {
          id: String(id),
          disabled: disabled || child.disabled,
        };
        if (win.HTMLOptGroupElement.isInstance(child)) {
          item.label = child.label;
          item.items = enumList(child, item.disabled);
        } else if (win.HTMLOptionElement.isInstance(child)) {
          item.label = child.label || child.text;
          item.selected = child.selected;
        } else if (win.HTMLHRElement.isInstance(child)) {
          item.separator = true;
        } else {
          continue;
        }
        items.push(item);
        map[id++] = child;
      }
      return items;
    })(aElement);

    return [items, map, id];
  }

  _handleSelect(aElement, aIsDropDown) {
    const win = aElement.ownerGlobal;
    const [items] = this._generateSelectItems(aElement);

    if (aIsDropDown) {
      aElement.openInParentProcess = true;
    }

    const prompt = new lazy.GeckoViewPrompter(win);

    // Something changed the <select> while it was open.
    const deferredUpdate = new lazy.DeferredTask(() => {
      // Inner contents in choice prompt are updated.
      const [newItems] = this._generateSelectItems(aElement);
      prompt.update({
        type: "choice",
        mode: aElement.multiple ? "multiple" : "single",
        choices: newItems,
      });
    }, 0);
    const mut = new win.MutationObserver(() => {
      deferredUpdate.arm();
    });
    mut.observe(aElement, {
      childList: true,
      subtree: true,
      attributes: true,
    });

    const dismissPrompt = () => prompt.dismiss();
    aElement.addEventListener("blur", dismissPrompt, { mozSystemGroup: true });
    const hidedropdown = event => {
      if (aElement === event.target) {
        prompt.dismiss();
      }
    };
    const chromeEventHandler = aElement.ownerGlobal.docShell.chromeEventHandler;
    chromeEventHandler.addEventListener("mozhidedropdown", hidedropdown, {
      mozSystemGroup: true,
    });

    prompt.asyncShowPrompt(
      {
        type: "choice",
        mode: aElement.multiple ? "multiple" : "single",
        choices: items,
      },
      result => {
        deferredUpdate.disarm();
        mut.disconnect();
        aElement.removeEventListener("blur", dismissPrompt, {
          mozSystemGroup: true,
        });
        chromeEventHandler.removeEventListener(
          "mozhidedropdown",
          hidedropdown,
          { mozSystemGroup: true }
        );

        if (aIsDropDown) {
          aElement.openInParentProcess = false;
        }
        // OK: result
        // Cancel: !result
        if (!result || result.choices === undefined) {
          return;
        }

        const [, map, id] = this._generateSelectItems(aElement);
        let dispatchEvents = false;
        if (!aElement.multiple) {
          const elem = map[result.choices[0]];
          if (elem && win.HTMLOptionElement.isInstance(elem)) {
            dispatchEvents = !elem.selected;
            elem.selected = true;
          } else {
            console.error("Invalid id for select result: " + result.choices[0]);
          }
        } else {
          for (let i = 0; i < id; i++) {
            const elem = map[i];
            const index = result.choices.indexOf(String(i));
            if (
              win.HTMLOptionElement.isInstance(elem) &&
              elem.selected !== index >= 0
            ) {
              // Current selected is not the same as the new selected state.
              dispatchEvents = true;
              elem.selected = !elem.selected;
            }
            result.choices[index] = undefined;
          }
          for (let i = 0; i < result.choices.length; i++) {
            if (result.choices[i] !== undefined && result.choices[i] !== null) {
              console.error(
                "Invalid id for select result: " + result.choices[i]
              );
              break;
            }
          }
        }

        if (dispatchEvents) {
          this._dispatchEvents(aElement);
        }
      }
    );
  }

  _handleDateTime(aElement) {
    const win = aElement.ownerGlobal;
    const prompt = new lazy.GeckoViewPrompter(win);

    const chromeEventHandler = aElement.ownerGlobal.docShell.chromeEventHandler;
    const dismissPrompt = () => prompt.dismiss();
    // Some controls don't have UA widget (bug 888320)
    {
      const dateTimeBoxElement = aElement.dateTimeBoxElement;
      if (["month", "week"].includes(aElement.type) && !dateTimeBoxElement) {
        aElement.addEventListener("blur", dismissPrompt, {
          mozSystemGroup: true,
        });
      } else {
        chromeEventHandler.addEventListener(
          "MozCloseDateTimePicker",
          dismissPrompt
        );

        dateTimeBoxElement.dispatchEvent(
          new win.CustomEvent("MozSetDateTimePickerState", { detail: true })
        );
      }
    }

    prompt.asyncShowPrompt(
      {
        type: "datetime",
        mode: aElement.type,
        value: aElement.value,
        min: aElement.min,
        max: aElement.max,
        step: aElement.step,
      },
      result => {
        // Some controls don't have UA widget (bug 888320)
        const dateTimeBoxElement = aElement.dateTimeBoxElement;
        if (["month", "week"].includes(aElement.type) && !dateTimeBoxElement) {
          aElement.removeEventListener("blur", dismissPrompt, {
            mozSystemGroup: true,
          });
        } else {
          chromeEventHandler.removeEventListener(
            "MozCloseDateTimePicker",
            dismissPrompt
          );
          dateTimeBoxElement.dispatchEvent(
            new win.CustomEvent("MozSetDateTimePickerState", { detail: false })
          );
        }

        // OK: result
        // Cancel: !result
        if (
          !result ||
          result.datetime === undefined ||
          result.datetime === aElement.value
        ) {
          return;
        }
        aElement.value = result.datetime;
        this._dispatchEvents(aElement);
      }
    );
  }

  _dispatchEvents(aElement) {
    // Fire both "input" and "change" events for <select> and <input> for
    // date/time.
    aElement.dispatchEvent(
      new aElement.ownerGlobal.Event("input", { bubbles: true, composed: true })
    );
    aElement.dispatchEvent(
      new aElement.ownerGlobal.Event("change", { bubbles: true })
    );
  }

  _handlePopupBlocked(aEvent) {
    const dwi = aEvent.requestingWindow;
    const popupWindowURISpec = aEvent.popupWindowURI
      ? aEvent.popupWindowURI.displaySpec
      : "about:blank";

    const prompt = new lazy.GeckoViewPrompter(aEvent.requestingWindow);
    prompt.asyncShowPrompt(
      {
        type: "popup",
        targetUri: popupWindowURISpec,
      },
      ({ response }) => {
        if (response && dwi) {
          dwi.open(
            popupWindowURISpec,
            aEvent.popupWindowName,
            aEvent.popupWindowFeatures
          );
        }
      }
    );
  }

  _handleRedirectBlocked(aEvent) {
    const dwi = aEvent.requestingWindow;
    const redirectURISpec = aEvent.redirectURI.spec;

    const prompt = new lazy.GeckoViewPrompter(aEvent.requestingWindow);
    prompt.asyncShowPrompt(
      {
        type: "redirect",
        targetUri: redirectURISpec,
      },
      ({ response }) => {
        if (response && dwi) {
          dwi.top.location.href = redirectURISpec;
        }
      }
    );
  }

  /* ----------  nsIPromptFactory  ---------- */
  getPrompt(aDOMWin, aIID) {
    // Delegated to login manager here, which in turn calls back into us via nsIPromptService.
    if (aIID.equals(Ci.nsIAuthPrompt2) || aIID.equals(Ci.nsIAuthPrompt)) {
      try {
        const pwmgr = Cc[
          "@mozilla.org/passwordmanager/authpromptfactory;1"
        ].getService(Ci.nsIPromptFactory);
        return pwmgr.getPrompt(aDOMWin, aIID);
      } catch (e) {
        console.error("Delegation to password manager failed:", e);
      }
    }

    const p = new PromptDelegate(aDOMWin);
    p.QueryInterface(aIID);
    return p;
  }

  /* ----------  private memebers  ---------- */

  // nsIPromptService methods proxy to our Prompt class
  callProxy(aMethod, aArguments) {
    const prompt = new PromptDelegate(aArguments[0]);
    let promptArgs;
    if (BrowsingContext.isInstance(aArguments[0])) {
      // Called by BrowsingContext prompt method, strip modalType.
      [, , /*browsingContext*/ /*modalType*/ ...promptArgs] = aArguments;
    } else {
      [, /*domWindow*/ ...promptArgs] = aArguments;
    }
    return prompt[aMethod].apply(prompt, promptArgs);
  }

  /* ----------  nsIPromptService  ---------- */

  alert() {
    return this.callProxy("alert", arguments);
  }
  alertBC() {
    return this.callProxy("alert", arguments);
  }
  alertCheck() {
    return this.callProxy("alertCheck", arguments);
  }
  alertCheckBC() {
    return this.callProxy("alertCheck", arguments);
  }
  confirm() {
    return this.callProxy("confirm", arguments);
  }
  confirmBC() {
    return this.callProxy("confirm", arguments);
  }
  confirmCheck() {
    return this.callProxy("confirmCheck", arguments);
  }
  confirmCheckBC() {
    return this.callProxy("confirmCheck", arguments);
  }
  confirmEx() {
    return this.callProxy("confirmEx", arguments);
  }
  confirmExBC() {
    return this.callProxy("confirmEx", arguments);
  }
  prompt() {
    return this.callProxy("prompt", arguments);
  }
  promptBC() {
    return this.callProxy("prompt", arguments);
  }
  promptUsernameAndPassword() {
    return this.callProxy("promptUsernameAndPassword", arguments);
  }
  promptUsernameAndPasswordBC() {
    return this.callProxy("promptUsernameAndPassword", arguments);
  }
  promptPassword() {
    return this.callProxy("promptPassword", arguments);
  }
  promptPasswordBC() {
    return this.callProxy("promptPassword", arguments);
  }
  select() {
    return this.callProxy("select", arguments);
  }
  selectBC() {
    return this.callProxy("select", arguments);
  }
  promptAuth() {
    return this.callProxy("promptAuth", arguments);
  }
  promptAuthBC() {
    return this.callProxy("promptAuth", arguments);
  }
  asyncPromptAuth() {
    return this.callProxy("asyncPromptAuth", arguments);
  }
  confirmUserPaste() {
    return lazy.GeckoViewClipboardPermission.confirmUserPaste(...arguments);
  }
}

PromptFactory.prototype.classID = Components.ID(
  "{076ac188-23c1-4390-aa08-7ef1f78ca5d9}"
);
PromptFactory.prototype.QueryInterface = ChromeUtils.generateQI([
  "nsIPromptFactory",
  "nsIPromptService",
]);

class PromptDelegate {
  constructor(aParent) {
    this._prompter = new lazy.GeckoViewPrompter(aParent);
  }

  BUTTON_TYPE_POSITIVE = 0;
  BUTTON_TYPE_NEUTRAL = 1;
  BUTTON_TYPE_NEGATIVE = 2;

  /* ---------- internal methods ---------- */

  _addText(aTitle, aText, aMsg) {
    return Object.assign(aMsg, {
      title: aTitle,
      msg: aText,
    });
  }

  _addCheck(aCheckMsg, aCheckState, aMsg) {
    return Object.assign(aMsg, {
      hasCheck: !!aCheckMsg,
      checkMsg: aCheckMsg,
      checkValue: aCheckState && aCheckState.value,
    });
  }

  /* ----------  nsIPrompt  ---------- */

  alert(aTitle, aText) {
    this.alertCheck(aTitle, aText);
  }

  alertCheck(aTitle, aText, aCheckMsg, aCheckState) {
    const result = this._prompter.showPrompt(
      this._addText(
        aTitle,
        aText,
        this._addCheck(aCheckMsg, aCheckState, {
          type: "alert",
        })
      )
    );
    if (result && aCheckState) {
      aCheckState.value = !!result.checkValue;
    }
  }

  confirm(aTitle, aText) {
    // Button 0 is OK.
    return this.confirmCheck(aTitle, aText);
  }

  confirmCheck(aTitle, aText, aCheckMsg, aCheckState) {
    // Button 0 is OK.
    return (
      this.confirmEx(
        aTitle,
        aText,
        Ci.nsIPrompt.STD_OK_CANCEL_BUTTONS,
        /* aButton0 */ null,
        /* aButton1 */ null,
        /* aButton2 */ null,
        aCheckMsg,
        aCheckState
      ) == 0
    );
  }

  confirmEx(
    aTitle,
    aText,
    aButtonFlags,
    aButton0,
    aButton1,
    aButton2,
    aCheckMsg,
    aCheckState
  ) {
    const btnMap = Array(3).fill(null);
    const btnTitle = Array(3).fill(null);
    const btnCustomTitle = Array(3).fill(null);
    const savedButtonId = [];
    for (let i = 0; i < 3; i++) {
      const btnFlags = aButtonFlags >> (i * 8);
      switch (btnFlags & 0xff) {
        case Ci.nsIPrompt.BUTTON_TITLE_OK:
          btnMap[this.BUTTON_TYPE_POSITIVE] = i;
          btnTitle[this.BUTTON_TYPE_POSITIVE] = "ok";
          break;
        case Ci.nsIPrompt.BUTTON_TITLE_CANCEL:
          btnMap[this.BUTTON_TYPE_NEGATIVE] = i;
          btnTitle[this.BUTTON_TYPE_NEGATIVE] = "cancel";
          break;
        case Ci.nsIPrompt.BUTTON_TITLE_YES:
          btnMap[this.BUTTON_TYPE_POSITIVE] = i;
          btnTitle[this.BUTTON_TYPE_POSITIVE] = "yes";
          break;
        case Ci.nsIPrompt.BUTTON_TITLE_NO:
          btnMap[this.BUTTON_TYPE_NEGATIVE] = i;
          btnTitle[this.BUTTON_TYPE_NEGATIVE] = "no";
          break;
        case Ci.nsIPrompt.BUTTON_TITLE_IS_STRING:
          // We don't know if this is positive/negative/neutral, so save for later.
          savedButtonId.push(i);
          break;
        case Ci.nsIPrompt.BUTTON_TITLE_SAVE:
        case Ci.nsIPrompt.BUTTON_TITLE_DONT_SAVE:
        case Ci.nsIPrompt.BUTTON_TITLE_REVERT:
        // Not supported; fall-through.
        default:
          break;
      }
    }

    // Put saved buttons into available slots.
    for (let i = 0; i < 3 && savedButtonId.length; i++) {
      if (btnMap[i] === null) {
        btnMap[i] = savedButtonId.shift();
        btnTitle[i] = "custom";
        btnCustomTitle[i] = [aButton0, aButton1, aButton2][btnMap[i]];
      }
    }

    const result = this._prompter.showPrompt(
      this._addText(
        aTitle,
        aText,
        this._addCheck(aCheckMsg, aCheckState, {
          type: "button",
          btnTitle,
          btnCustomTitle,
        })
      )
    );
    if (result && aCheckState) {
      aCheckState.value = !!result.checkValue;
    }
    return result && result.button in btnMap ? btnMap[result.button] : -1;
  }

  prompt(aTitle, aText, aValue, aCheckMsg, aCheckState) {
    const result = this._prompter.showPrompt(
      this._addText(
        aTitle,
        aText,
        this._addCheck(aCheckMsg, aCheckState, {
          type: "text",
          value: aValue.value,
        })
      )
    );
    // OK: result && result.text !== undefined
    // Cancel: result && result.text === undefined
    // Error: !result
    if (result && aCheckState) {
      aCheckState.value = !!result.checkValue;
    }
    if (!result || result.text === undefined) {
      return false;
    }
    aValue.value = result.text || "";
    return true;
  }

  promptPassword(aTitle, aText, aPassword) {
    return this._promptUsernameAndPassword(
      aTitle,
      aText,
      /* aUsername */ undefined,
      aPassword
    );
  }

  promptUsernameAndPassword(aTitle, aText, aUsername, aPassword) {
    const msg = {
      type: "auth",
      mode: aUsername ? "auth" : "password",
      options: {
        flags: aUsername ? 0 : Ci.nsIAuthInformation.ONLY_PASSWORD,
        username: aUsername ? aUsername.value : undefined,
        password: aPassword.value,
      },
    };
    const result = this._prompter.showPrompt(this._addText(aTitle, aText, msg));
    // OK: result && result.password !== undefined
    // Cancel: result && result.password === undefined
    // Error: !result
    if (!result || result.password === undefined) {
      return false;
    }
    if (aUsername) {
      aUsername.value = result.username || "";
    }
    aPassword.value = result.password || "";
    return true;
  }

  select(aTitle, aText, aSelectList, aOutSelection) {
    const choices = Array.prototype.map.call(aSelectList, (item, index) => ({
      id: String(index),
      label: item,
      disabled: false,
      selected: false,
    }));
    const result = this._prompter.showPrompt(
      this._addText(aTitle, aText, {
        type: "choice",
        mode: "single",
        choices,
      })
    );
    // OK: result
    // Cancel: !result
    if (!result || result.choices === undefined) {
      return false;
    }
    aOutSelection.value = Number(result.choices[0]);
    return true;
  }

  _getAuthMsg(aChannel, aLevel, aAuthInfo) {
    let username;
    if (
      aAuthInfo.flags & Ci.nsIAuthInformation.NEED_DOMAIN &&
      aAuthInfo.domain
    ) {
      username = aAuthInfo.domain + "\\" + aAuthInfo.username;
    } else {
      username = aAuthInfo.username;
    }
    return this._addText(
      /* title */ null,
      this._getAuthText(aChannel, aAuthInfo),
      {
        type: "auth",
        mode:
          aAuthInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD
            ? "password"
            : "auth",
        options: {
          flags: aAuthInfo.flags,
          uri: aChannel && aChannel.URI.displaySpec,
          level: aLevel,
          username,
          password: aAuthInfo.password,
        },
      }
    );
  }

  _fillAuthInfo(aAuthInfo, aResult) {
    if (!aResult || aResult.password === undefined) {
      return false;
    }

    aAuthInfo.password = aResult.password || "";
    if (aAuthInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD) {
      return true;
    }

    const username = aResult.username || "";
    if (aAuthInfo.flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
      // Domain is separated from username by a backslash
      var idx = username.indexOf("\\");
      if (idx >= 0) {
        aAuthInfo.domain = username.substring(0, idx);
        aAuthInfo.username = username.substring(idx + 1);
        return true;
      }
    }
    aAuthInfo.username = username;
    return true;
  }

  promptAuth(aChannel, aLevel, aAuthInfo) {
    const result = this._prompter.showPrompt(
      this._getAuthMsg(aChannel, aLevel, aAuthInfo)
    );
    // OK: result && result.password !== undefined
    // Cancel: result && result.password === undefined
    // Error: !result
    return this._fillAuthInfo(aAuthInfo, result);
  }

  async asyncPromptAuth(aChannel, aLevel, aAuthInfo) {
    const result = await this._prompter.asyncShowPromptPromise(
      this._getAuthMsg(aChannel, aLevel, aAuthInfo)
    );
    // OK: result && result.password !== undefined
    // Cancel: result && result.password === undefined
    // Error: !result
    return this._fillAuthInfo(aAuthInfo, result);
  }

  _getAuthText(aChannel, aAuthInfo) {
    const isProxy = aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY;
    const isPassOnly = aAuthInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD;
    const isCrossOrig =
      aAuthInfo.flags & Ci.nsIAuthInformation.CROSS_ORIGIN_SUB_RESOURCE;

    const username = aAuthInfo.username;
    const authTarget = this._getAuthTarget(aChannel, aAuthInfo);
    const { displayHost } = authTarget;
    let { realm } = authTarget;

    // Suppress "the site says: $realm" when we synthesized a missing realm.
    if (!aAuthInfo.realm && !isProxy) {
      realm = "";
    }

    // Trim obnoxiously long realms.
    if (realm.length > 50) {
      realm = realm.substring(0, 50) + "\u2026";
    }

    const bundle = Services.strings.createBundle(
      "chrome://global/locale/commonDialogs.properties"
    );
    let text;
    if (isProxy) {
      text = bundle.formatStringFromName("EnterLoginForProxy3", [
        realm,
        displayHost,
      ]);
    } else if (isPassOnly) {
      text = bundle.formatStringFromName("EnterPasswordFor", [
        username,
        displayHost,
      ]);
    } else if (isCrossOrig) {
      text = bundle.formatStringFromName("EnterUserPasswordForCrossOrigin2", [
        displayHost,
      ]);
    } else if (!realm) {
      text = bundle.formatStringFromName("EnterUserPasswordFor2", [
        displayHost,
      ]);
    } else {
      text = bundle.formatStringFromName("EnterLoginForRealm3", [
        realm,
        displayHost,
      ]);
    }

    return text;
  }

  _getAuthTarget(aChannel, aAuthInfo) {
    // If our proxy is demanding authentication, don't use the
    // channel's actual destination.
    if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
      if (!(aChannel instanceof Ci.nsIProxiedChannel)) {
        throw new Error("proxy auth needs nsIProxiedChannel");
      }
      const info = aChannel.proxyInfo;
      if (!info) {
        throw new Error("proxy auth needs nsIProxyInfo");
      }
      // Proxies don't have a scheme, but we'll use "moz-proxy://"
      // so that it's more obvious what the login is for.
      const idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
        Ci.nsIIDNService
      );
      const displayHost =
        "moz-proxy://" +
        idnService.convertUTF8toACE(info.host) +
        ":" +
        info.port;
      let realm = aAuthInfo.realm;
      if (!realm) {
        realm = displayHost;
      }
      return { displayHost, realm };
    }

    const displayHost =
      aChannel.URI.scheme + "://" + aChannel.URI.displayHostPort;
    // If a HTTP WWW-Authenticate header specified a realm, that value
    // will be available here. If it wasn't set or wasn't HTTP, we'll use
    // the formatted hostname instead.
    let realm = aAuthInfo.realm;
    if (!realm) {
      realm = displayHost;
    }
    return { displayHost, realm };
  }
}

PromptDelegate.prototype.QueryInterface = ChromeUtils.generateQI(["nsIPrompt"]);
