summaryrefslogtreecommitdiffstats
path: root/mobile/android/components/geckoview/GeckoViewPrompt.jsm
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /mobile/android/components/geckoview/GeckoViewPrompt.jsm
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--mobile/android/components/geckoview/GeckoViewPrompt.jsm899
1 files changed, 899 insertions, 0 deletions
diff --git a/mobile/android/components/geckoview/GeckoViewPrompt.jsm b/mobile/android/components/geckoview/GeckoViewPrompt.jsm
new file mode 100644
index 0000000000..3dc5d0daf5
--- /dev/null
+++ b/mobile/android/components/geckoview/GeckoViewPrompt.jsm
@@ -0,0 +1,899 @@
+/* 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/. */
+"use strict";
+
+var EXPORTED_SYMBOLS = ["PromptFactory"];
+
+const { GeckoViewUtils } = ChromeUtils.import(
+ "resource://gre/modules/GeckoViewUtils.jsm"
+);
+
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ GeckoViewPrompter: "resource://gre/modules/GeckoViewPrompter.jsm",
+ Services: "resource://gre/modules/Services.jsm",
+});
+
+const { debug, warn } = GeckoViewUtils.initLogging("GeckoViewPrompt");
+
+class PromptFactory {
+ constructor() {
+ this.wrappedJSObject = this;
+ }
+
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "click":
+ this._handleClick(aEvent);
+ break;
+ case "contextmenu":
+ this._handleContextMenu(aEvent);
+ break;
+ case "DOMPopupBlocked":
+ this._handlePopupBlocked(aEvent);
+ break;
+ }
+ }
+
+ _handleClick(aEvent) {
+ const target = aEvent.composedTarget;
+ 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;
+ }
+
+ const win = target.ownerGlobal;
+ if (target instanceof win.HTMLSelectElement) {
+ this._handleSelect(target);
+ aEvent.preventDefault();
+ } else if (target instanceof win.HTMLInputElement) {
+ const type = target.type;
+ if (
+ type === "date" ||
+ type === "month" ||
+ type === "week" ||
+ type === "time" ||
+ type === "datetime-local"
+ ) {
+ this._handleDateTime(target, type);
+ aEvent.preventDefault();
+ }
+ }
+ }
+
+ _handleSelect(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 (child instanceof win.HTMLOptGroupElement) {
+ item.label = child.label;
+ item.items = enumList(child, item.disabled);
+ } else if (child instanceof win.HTMLOptionElement) {
+ item.label = child.label || child.text;
+ item.selected = child.selected;
+ } else {
+ continue;
+ }
+ items.push(item);
+ map[id++] = child;
+ }
+ return items;
+ })(aElement);
+
+ const prompt = new GeckoViewPrompter(win);
+ prompt.asyncShowPrompt(
+ {
+ type: "choice",
+ mode: aElement.multiple ? "multiple" : "single",
+ choices: items,
+ },
+ result => {
+ // OK: result
+ // Cancel: !result
+ if (!result || result.choices === undefined) {
+ return;
+ }
+
+ let dispatchEvents = false;
+ if (!aElement.multiple) {
+ const elem = map[result.choices[0]];
+ if (elem && elem instanceof win.HTMLOptionElement) {
+ dispatchEvents = !elem.selected;
+ elem.selected = true;
+ } else {
+ Cu.reportError(
+ "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 (
+ elem instanceof win.HTMLOptionElement &&
+ 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) {
+ Cu.reportError(
+ "Invalid id for select result: " + result.choices[i]
+ );
+ break;
+ }
+ }
+ }
+
+ if (dispatchEvents) {
+ this._dispatchEvents(aElement);
+ }
+ }
+ );
+ }
+
+ _handleDateTime(aElement, aType) {
+ const prompt = new GeckoViewPrompter(aElement.ownerGlobal);
+ prompt.asyncShowPrompt(
+ {
+ type: "datetime",
+ mode: aType,
+ value: aElement.value,
+ min: aElement.min,
+ max: aElement.max,
+ },
+ result => {
+ // 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 })
+ );
+ aElement.dispatchEvent(
+ new aElement.ownerGlobal.Event("change", { bubbles: true })
+ );
+ }
+
+ _handleContextMenu(aEvent) {
+ const target = aEvent.composedTarget;
+ if (aEvent.defaultPrevented || target.isContentEditable) {
+ return;
+ }
+
+ // Look through all ancestors for a context menu per spec.
+ let parent = target;
+ let menu = target.contextMenu;
+ while (!menu && parent) {
+ menu = parent.contextMenu;
+ parent = parent.parentElement;
+ }
+ if (!menu) {
+ return;
+ }
+
+ const builder = {
+ _cursor: undefined,
+ _id: 0,
+ _map: {},
+ _stack: [],
+ items: [],
+
+ // nsIMenuBuilder
+ openContainer(aLabel) {
+ if (!this._cursor) {
+ // Top-level
+ this._cursor = this;
+ return;
+ }
+ const newCursor = {
+ id: String(this._id++),
+ items: [],
+ label: aLabel,
+ };
+ this._cursor.items.push(newCursor);
+ this._stack.push(this._cursor);
+ this._cursor = newCursor;
+ },
+
+ addItemFor(aElement, aCanLoadIcon) {
+ this._cursor.items.push({
+ disabled: aElement.disabled,
+ icon:
+ aCanLoadIcon && aElement.icon && aElement.icon.length
+ ? aElement.icon
+ : null,
+ id: String(this._id),
+ label: aElement.label,
+ selected: aElement.checked,
+ });
+ this._map[this._id++] = aElement;
+ },
+
+ addSeparator() {
+ this._cursor.items.push({
+ disabled: true,
+ id: String(this._id++),
+ separator: true,
+ });
+ },
+
+ undoAddSeparator() {
+ const sep = this._cursor.items[this._cursor.items.length - 1];
+ if (sep && sep.separator) {
+ this._cursor.items.pop();
+ }
+ },
+
+ closeContainer() {
+ const childItems =
+ this._cursor.label === "" ? this._cursor.items : null;
+ this._cursor = this._stack.pop();
+
+ if (
+ childItems !== null &&
+ this._cursor &&
+ this._cursor.items.length === 1
+ ) {
+ // Merge a single nameless child container into the parent container.
+ // This lets us build an HTML contextmenu within a submenu.
+ this._cursor.items = childItems;
+ }
+ },
+
+ toJSONString() {
+ return JSON.stringify(this.items);
+ },
+
+ click(aId) {
+ const item = this._map[aId];
+ if (item) {
+ item.click();
+ }
+ },
+ };
+
+ // XXX the "show" event is not cancelable but spec says it should be.
+ menu.sendShowEvent();
+ menu.build(builder);
+
+ const prompt = new GeckoViewPrompter(target.ownerGlobal);
+ prompt.asyncShowPrompt(
+ {
+ type: "choice",
+ mode: "menu",
+ choices: builder.items,
+ },
+ result => {
+ // OK: result
+ // Cancel: !result
+ if (result && result.choices !== undefined) {
+ builder.click(result.choices[0]);
+ }
+ }
+ );
+
+ aEvent.preventDefault();
+ }
+
+ _handlePopupBlocked(aEvent) {
+ const dwi = aEvent.requestingWindow;
+ const popupWindowURISpec = aEvent.popupWindowURI
+ ? aEvent.popupWindowURI.displaySpec
+ : "about:blank";
+
+ const prompt = new GeckoViewPrompter(aEvent.requestingWindow);
+ prompt.asyncShowPrompt(
+ {
+ type: "popup",
+ targetUri: popupWindowURISpec,
+ },
+ ({ response }) => {
+ if (response && dwi) {
+ dwi.open(
+ popupWindowURISpec,
+ aEvent.popupWindowName,
+ aEvent.popupWindowFeatures
+ );
+ }
+ }
+ );
+ }
+
+ /* ---------- 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) {
+ Cu.reportError("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 (aArguments[0] instanceof BrowsingContext) {
+ // 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);
+ }
+ asyncPromptAuthBC() {
+ return this.callProxy("asyncPromptAuth", 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 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, aCheckMsg, aCheckState) {
+ return this._promptUsernameAndPassword(
+ aTitle,
+ aText,
+ /* aUsername */ undefined,
+ aPassword,
+ aCheckMsg,
+ aCheckState
+ );
+ }
+
+ promptUsernameAndPassword(
+ aTitle,
+ aText,
+ aUsername,
+ aPassword,
+ aCheckMsg,
+ aCheckState
+ ) {
+ 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, this._addCheck(aCheckMsg, aCheckState, msg))
+ );
+ // OK: result && result.password !== undefined
+ // Cancel: result && result.password === undefined
+ // Error: !result
+ if (result && aCheckState) {
+ aCheckState.value = !!result.checkValue;
+ }
+ 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, aCheckState, aResult) {
+ if (aResult && aCheckState) {
+ aCheckState.value = !!aResult.checkValue;
+ }
+ 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, aCheckMsg, aCheckState) {
+ const result = this._prompter.showPrompt(
+ this._addCheck(
+ aCheckMsg,
+ aCheckState,
+ this._getAuthMsg(aChannel, aLevel, aAuthInfo)
+ )
+ );
+ // OK: result && result.password !== undefined
+ // Cancel: result && result.password === undefined
+ // Error: !result
+ return this._fillAuthInfo(aAuthInfo, aCheckState, result);
+ }
+
+ asyncPromptAuth(
+ aChannel,
+ aCallback,
+ aContext,
+ aLevel,
+ aAuthInfo,
+ aCheckMsg,
+ aCheckState
+ ) {
+ let responded = false;
+ const callback = result => {
+ // OK: result && result.password !== undefined
+ // Cancel: result && result.password === undefined
+ // Error: !result
+ if (responded) {
+ return;
+ }
+ responded = true;
+ if (this._fillAuthInfo(aAuthInfo, aCheckState, result)) {
+ aCallback.onAuthAvailable(aContext, aAuthInfo);
+ } else {
+ aCallback.onAuthCancelled(aContext, /* userCancel */ true);
+ }
+ };
+ this._prompter.asyncShowPrompt(
+ this._addCheck(
+ aCheckMsg,
+ aCheckState,
+ this._getAuthMsg(aChannel, aLevel, aAuthInfo)
+ ),
+ callback
+ );
+ return {
+ QueryInterface: ChromeUtils.generateQI(["nsICancelable"]),
+ cancel() {
+ if (responded) {
+ return;
+ }
+ responded = true;
+ aCallback.onAuthCancelled(aContext, /* userCancel */ false);
+ },
+ };
+ }
+
+ _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"]);