зеркало из https://github.com/mozilla/gecko-dev.git
Backed out 2 changesets (bug 1686989, bug 1684469) for auth and dialog box failures. CLOSED TREE
Backed out changeset 111af4c2bf6b (bug 1684469) Backed out changeset 81794f8a220b (bug 1686989)
This commit is contained in:
Родитель
1f192d6edb
Коммит
086251db75
|
@ -1302,9 +1302,7 @@
|
|||
|
||||
if (newBrowser.hasAttribute("tabDialogShowing")) {
|
||||
newBrowser.tabDialogBox.focus();
|
||||
return;
|
||||
}
|
||||
if (newBrowser.hasAttribute("tabmodalPromptShowing")) {
|
||||
} else if (newBrowser.hasAttribute("tabmodalPromptShowing")) {
|
||||
// If there's a tabmodal prompt showing, focus it.
|
||||
let prompts = newBrowser.tabModalPromptBox.listPrompts();
|
||||
let prompt = prompts[prompts.length - 1];
|
||||
|
|
|
@ -6,4 +6,3 @@ skip-if = verify && debug && (os == 'linux')
|
|||
[browser_multiplePrompts.js]
|
||||
[browser_openPromptInBackgroundTab.js]
|
||||
support-files = openPromptOffTimeout.html
|
||||
[browser_promptFocus.js]
|
||||
|
|
|
@ -1,169 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { PromptTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/PromptTestUtils.jsm"
|
||||
);
|
||||
|
||||
// MacOS has different default focus behavior for prompts.
|
||||
const isMacOS = Services.appinfo.OS === "Darwin";
|
||||
|
||||
/**
|
||||
* Tests that prompts are focused when switching tabs.
|
||||
*/
|
||||
add_task(async function test_tabdialogbox_tab_switch_focus() {
|
||||
// Open 3 tabs
|
||||
let tabPromises = [];
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
tabPromises.push(
|
||||
BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
"http://example.com",
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
// Wait for tabs to be ready
|
||||
let tabs = await Promise.all(tabPromises);
|
||||
let [tabA, tabB, tabC] = tabs;
|
||||
|
||||
// Spawn two prompts, which have different default focus as determined by
|
||||
// CommonDialog#setDefaultFocus.
|
||||
let openPromise = PromptTestUtils.waitForPrompt(tabA.linkedBrowser, {
|
||||
modalType: Services.prompt.MODAL_TYPE_TAB,
|
||||
promptType: "confirm",
|
||||
});
|
||||
Services.prompt.asyncConfirm(
|
||||
tabA.linkedBrowser.browsingContext,
|
||||
Services.prompt.MODAL_TYPE_TAB,
|
||||
null,
|
||||
"prompt A"
|
||||
);
|
||||
let promptA = await openPromise;
|
||||
|
||||
openPromise = PromptTestUtils.waitForPrompt(tabB.linkedBrowser, {
|
||||
modalType: Services.prompt.MODAL_TYPE_TAB,
|
||||
promptType: "promptPassword",
|
||||
});
|
||||
Services.prompt.asyncPromptPassword(
|
||||
tabB.linkedBrowser.browsingContext,
|
||||
Services.prompt.MODAL_TYPE_TAB,
|
||||
null,
|
||||
"prompt B",
|
||||
"",
|
||||
null,
|
||||
false
|
||||
);
|
||||
let promptB = await openPromise;
|
||||
|
||||
// Switch tabs and check if the correct element was focused.
|
||||
|
||||
// Switch back to the third tab which doesn't have a prompt.
|
||||
await BrowserTestUtils.switchTab(gBrowser, tabC);
|
||||
is(
|
||||
Services.focus.focusedElement,
|
||||
tabC.linkedBrowser,
|
||||
"Tab without prompt should have focus on browser."
|
||||
);
|
||||
|
||||
// Switch to first tab which has prompt
|
||||
await BrowserTestUtils.switchTab(gBrowser, tabA);
|
||||
|
||||
if (isMacOS) {
|
||||
is(
|
||||
Services.focus.focusedElement,
|
||||
promptA.ui.infoBody,
|
||||
"Tab with prompt should have focus on body."
|
||||
);
|
||||
} else {
|
||||
is(
|
||||
Services.focus.focusedElement,
|
||||
promptA.ui.button0,
|
||||
"Tab with prompt should have focus on default button."
|
||||
);
|
||||
}
|
||||
|
||||
await PromptTestUtils.handlePrompt(promptA);
|
||||
|
||||
// Switch to second tab which has prompt
|
||||
await BrowserTestUtils.switchTab(gBrowser, tabB);
|
||||
is(
|
||||
Services.focus.focusedElement,
|
||||
promptB.ui.password1Textbox,
|
||||
"Tab with password prompt should have focus on password field."
|
||||
);
|
||||
await PromptTestUtils.handlePrompt(promptB);
|
||||
|
||||
// Cleanup
|
||||
tabs.forEach(tab => {
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests that an alert prompt has focus on the default element.
|
||||
* @param {CommonDialog} prompt - Prompt to test focus for.
|
||||
* @param {number} index - Index of the prompt to log.
|
||||
*/
|
||||
function testAlertPromptFocus(prompt, index) {
|
||||
if (isMacOS) {
|
||||
is(
|
||||
Services.focus.focusedElement,
|
||||
prompt.ui.infoBody,
|
||||
`Prompt #${index} should have focus on body.`
|
||||
);
|
||||
} else {
|
||||
is(
|
||||
Services.focus.focusedElement,
|
||||
prompt.ui.button0,
|
||||
`Prompt #${index} should have focus on default button.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that we set the correct focus when queuing multiple prompts.
|
||||
*/
|
||||
add_task(async function test_tabdialogbox_prompt_queue_focus() {
|
||||
await BrowserTestUtils.withNewTab(gBrowser, async browser => {
|
||||
const PROMPT_COUNT = 10;
|
||||
|
||||
let firstPromptPromise = PromptTestUtils.waitForPrompt(browser, {
|
||||
modalType: Services.prompt.MODAL_TYPE_TAB,
|
||||
promptType: "alert",
|
||||
});
|
||||
|
||||
for (let i = 0; i < PROMPT_COUNT; i += 1) {
|
||||
Services.prompt.asyncAlert(
|
||||
browser.browsingContext,
|
||||
Services.prompt.MODAL_TYPE_TAB,
|
||||
null,
|
||||
"prompt " + i
|
||||
);
|
||||
}
|
||||
|
||||
// Close prompts one by one and check focus.
|
||||
let nextPromptPromise = firstPromptPromise;
|
||||
for (let i = 0; i < PROMPT_COUNT; i += 1) {
|
||||
let p = await nextPromptPromise;
|
||||
testAlertPromptFocus(p, i);
|
||||
|
||||
if (i < PROMPT_COUNT - 1) {
|
||||
nextPromptPromise = PromptTestUtils.waitForPrompt(browser, {
|
||||
modalType: Services.prompt.MODAL_TYPE_TAB,
|
||||
promptType: "alert",
|
||||
});
|
||||
}
|
||||
await PromptTestUtils.handlePrompt(p);
|
||||
}
|
||||
|
||||
// All prompts are closed, focus should be back on the browser.
|
||||
is(
|
||||
Services.focus.focusedElement,
|
||||
browser,
|
||||
"Tab without prompts should have focus on browser."
|
||||
);
|
||||
});
|
||||
});
|
|
@ -4,7 +4,7 @@ support-files =
|
|||
|
||||
[browser_tabdialogbox_content_prompts.js]
|
||||
[browser_tabdialogbox_navigation.js]
|
||||
[browser_tabdialogbox_focus.js]
|
||||
[browser_tabdialogbox_tab_switch_focus.js]
|
||||
[browser_subdialog_esc.js]
|
||||
support-files =
|
||||
loadDelayedReply.sjs
|
||||
|
|
|
@ -74,90 +74,6 @@ add_task(async function test_tabdialogbox_tab_switch_focus() {
|
|||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests that if we're showing multiple tab dialogs they are focused in the
|
||||
* correct order and custom focus handlers are called.
|
||||
*/
|
||||
add_task(async function test_tabdialogbox_multiple_focus() {
|
||||
await BrowserTestUtils.withNewTab(gBrowser, async browser => {
|
||||
let dialogBox = gBrowser.getTabDialogBox(browser);
|
||||
let dialogAClose = dialogBox.open(
|
||||
TEST_DIALOG_PATH,
|
||||
{},
|
||||
{
|
||||
testCustomFocusHandler: true,
|
||||
}
|
||||
);
|
||||
let dialogBClose = dialogBox.open(TEST_DIALOG_PATH);
|
||||
let dialogCClose = dialogBox.open(
|
||||
TEST_DIALOG_PATH,
|
||||
{},
|
||||
{
|
||||
testCustomFocusHandler: true,
|
||||
}
|
||||
);
|
||||
|
||||
let dialogs = dialogBox._dialogManager._dialogs;
|
||||
let [dialogA, dialogB, dialogC] = dialogs;
|
||||
|
||||
// Wait until all dialogs are ready
|
||||
await Promise.all(dialogs.map(dialog => dialog._dialogReady));
|
||||
|
||||
// Dialog A's custom focus target should be focused
|
||||
let dialogElementA = dialogA._frame.contentDocument.querySelector(
|
||||
"#custom-focus-el"
|
||||
);
|
||||
is(
|
||||
Services.focus.focusedElement,
|
||||
dialogElementA,
|
||||
"Dialog A custom focus target is focused"
|
||||
);
|
||||
|
||||
// Close top dialog
|
||||
dialogA.close();
|
||||
await dialogAClose;
|
||||
|
||||
// Dialog B's first focus target should be focused
|
||||
let dialogElementB = dialogB._frame.contentDocument.querySelector(
|
||||
"#textbox"
|
||||
);
|
||||
is(
|
||||
Services.focus.focusedElement,
|
||||
dialogElementB,
|
||||
"Dialog B default focus target is focused"
|
||||
);
|
||||
|
||||
// close top dialog
|
||||
dialogB.close();
|
||||
await dialogBClose;
|
||||
|
||||
// Dialog C's custom focus target should be focused
|
||||
let dialogElementC = dialogC._frame.contentDocument.querySelector(
|
||||
"#custom-focus-el"
|
||||
);
|
||||
is(
|
||||
Services.focus.focusedElement,
|
||||
dialogElementC,
|
||||
"Dialog C custom focus target is focused"
|
||||
);
|
||||
|
||||
// Close last dialog
|
||||
dialogC.close();
|
||||
await dialogCClose;
|
||||
|
||||
is(
|
||||
dialogBox._dialogManager._dialogs.length,
|
||||
0,
|
||||
"All dialogs should be closed"
|
||||
);
|
||||
is(
|
||||
Services.focus.focusedElement,
|
||||
browser,
|
||||
"Focus should be back on the browser"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests that other dialogs are still visible if one dialog is hidden.
|
||||
*/
|
|
@ -14,17 +14,6 @@
|
|||
function acceptSubdialog() {
|
||||
window.arguments[0].acceptCount++;
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
if (!window.arguments) {
|
||||
return;
|
||||
}
|
||||
let [options] = window.arguments;
|
||||
if (options?.testCustomFocusHandler) {
|
||||
document.subDialogSetDefaultFocus = () => {
|
||||
document.getElementById("custom-focus-el").focus();
|
||||
}
|
||||
}
|
||||
}, {once: true})
|
||||
</script>
|
||||
|
||||
<description id="desc">A sample sub-dialog for testing</description>
|
||||
|
@ -36,8 +25,6 @@
|
|||
<html:option>Bar</html:option>
|
||||
</html:select>
|
||||
|
||||
<html:input id="custom-focus-el" value="Custom Focus Test" />
|
||||
|
||||
<separator class="thin"/>
|
||||
|
||||
<button oncommand="window.close();" label="Close" />
|
||||
|
|
|
@ -440,6 +440,9 @@ class PromptFactory {
|
|||
asyncPromptAuth() {
|
||||
return this.callProxy("asyncPromptAuth", arguments);
|
||||
}
|
||||
asyncPromptAuthBC() {
|
||||
return this.callProxy("asyncPromptAuth", arguments);
|
||||
}
|
||||
}
|
||||
|
||||
PromptFactory.prototype.classID = Components.ID(
|
||||
|
@ -755,21 +758,48 @@ class PromptDelegate {
|
|||
return this._fillAuthInfo(aAuthInfo, aCheckState, result);
|
||||
}
|
||||
|
||||
async asyncPromptAuth(aChannel, aLevel, aAuthInfo, aCheckMsg, aCheckState) {
|
||||
const check = {
|
||||
value: aCheckState,
|
||||
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);
|
||||
}
|
||||
};
|
||||
const result = await this._prompter.asyncShowPromptPromise(
|
||||
this._prompter.asyncShowPrompt(
|
||||
this._addCheck(
|
||||
aCheckMsg,
|
||||
check,
|
||||
aCheckState,
|
||||
this._getAuthMsg(aChannel, aLevel, aAuthInfo)
|
||||
)
|
||||
),
|
||||
callback
|
||||
);
|
||||
// OK: result && result.password !== undefined
|
||||
// Cancel: result && result.password === undefined
|
||||
// Error: !result
|
||||
return this._fillAuthInfo(aAuthInfo, check, result);
|
||||
return {
|
||||
QueryInterface: ChromeUtils.generateQI(["nsICancelable"]),
|
||||
cancel() {
|
||||
if (responded) {
|
||||
return;
|
||||
}
|
||||
responded = true;
|
||||
aCallback.onAuthCancelled(aContext, /* userCancel */ false);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_getAuthText(aChannel, aAuthInfo) {
|
||||
|
|
|
@ -94,12 +94,6 @@ class GeckoViewPrompter {
|
|||
return result;
|
||||
}
|
||||
|
||||
asyncShowPromptPromise(aMsg) {
|
||||
return new Promise(resolve => {
|
||||
this.asyncShowPrompt(aMsg, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
asyncShowPrompt(aMsg, aCallback) {
|
||||
let handled = false;
|
||||
const onResponse = response => {
|
||||
|
|
|
@ -59,7 +59,6 @@ interface nsIAuthPrompt2 : nsISupports
|
|||
*
|
||||
* @note Exceptions thrown from this function will be treated like a
|
||||
* return value of false.
|
||||
* @deprecated use asyncPromptAuth
|
||||
*/
|
||||
boolean promptAuth(in nsIChannel aChannel,
|
||||
in uint32_t level,
|
||||
|
|
|
@ -94,7 +94,9 @@ XPCOMUtils.defineLazyPreferenceGetter(
|
|||
* Invoked by [toolkit/components/prompts/src/Prompter.jsm]
|
||||
*/
|
||||
function LoginManagerAuthPromptFactory() {
|
||||
Services.obs.addObserver(this, "quit-application-granted", true);
|
||||
Services.obs.addObserver(this, "passwordmgr-crypto-login", true);
|
||||
Services.obs.addObserver(this, "passwordmgr-crypto-loginCanceled", true);
|
||||
}
|
||||
|
||||
LoginManagerAuthPromptFactory.prototype = {
|
||||
|
@ -105,23 +107,20 @@ LoginManagerAuthPromptFactory.prototype = {
|
|||
"nsISupportsWeakReference",
|
||||
]),
|
||||
|
||||
// Tracks pending auth prompts per top level browser and hash key.
|
||||
// browser -> hashkey -> prompt
|
||||
// This enables us to consolidate auth prompts with the same browser and
|
||||
// hashkey (level, origin, realm).
|
||||
_pendingPrompts: new WeakMap(),
|
||||
// We use a separate bucket for when we don't have a browser.
|
||||
// _noBrowser -> hashkey -> prompt
|
||||
_noBrowser: {},
|
||||
// Promise used to defer prompts if the password manager isn't ready when
|
||||
// they're called.
|
||||
_uiBusyPromise: null,
|
||||
_asyncPrompts: {},
|
||||
_asyncPromptInProgress: false,
|
||||
|
||||
observe(subject, topic, data) {
|
||||
this.log("Observed: " + topic);
|
||||
if (topic == "passwordmgr-crypto-login") {
|
||||
// Show the deferred prompters.
|
||||
this._uiBusyPromise?.resolve();
|
||||
if (topic == "quit-application-granted") {
|
||||
this._cancelPendingPrompts();
|
||||
} else if (topic == "passwordmgr-crypto-login") {
|
||||
// Start processing the deferred prompters.
|
||||
this._doAsyncPrompt();
|
||||
} else if (topic == "passwordmgr-crypto-loginCanceled") {
|
||||
// User canceled a Master Password prompt, so go ahead and cancel
|
||||
// all pending auth prompts to avoid nagging over and over.
|
||||
this._cancelPendingPrompts();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -131,114 +130,143 @@ LoginManagerAuthPromptFactory.prototype = {
|
|||
return prompt;
|
||||
},
|
||||
|
||||
getPendingPrompt(browser, hashKey) {
|
||||
return this._pendingPrompts.get(browser || this._noBrowser)?.get(hashKey);
|
||||
},
|
||||
|
||||
_setPendingPrompt(prompt, hashKey) {
|
||||
let browser = prompt.prompter.browser || this._noBrowser;
|
||||
let hashToPrompt = this._pendingPrompts.get(browser);
|
||||
if (!hashToPrompt) {
|
||||
hashToPrompt = new Map();
|
||||
this._pendingPrompts.set(browser, hashToPrompt);
|
||||
}
|
||||
hashToPrompt.set(hashKey, prompt);
|
||||
},
|
||||
|
||||
_removePendingPrompt(prompt, hashKey) {
|
||||
let browser = prompt.prompter.browser || this._noBrowser;
|
||||
let hashToPrompt = this._pendingPrompts.get(browser);
|
||||
if (!hashToPrompt) {
|
||||
_doAsyncPrompt() {
|
||||
if (this._asyncPromptInProgress) {
|
||||
this.log("_doAsyncPrompt bypassed, already in progress");
|
||||
return;
|
||||
}
|
||||
hashToPrompt.delete(hashKey);
|
||||
if (!hashToPrompt.size) {
|
||||
this._pendingPrompts.delete(browser);
|
||||
|
||||
// Find the first prompt key we have in the queue
|
||||
var hashKey = null;
|
||||
for (hashKey in this._asyncPrompts) {
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
async _waitForLoginsUI(prompt) {
|
||||
await this._uiBusyPromise;
|
||||
if (!hashKey) {
|
||||
this.log("_doAsyncPrompt:run bypassed, no prompts in the queue");
|
||||
return;
|
||||
}
|
||||
|
||||
let [origin, httpRealm] = prompt.prompter._getAuthTarget(
|
||||
// If login manger has logins for this host, defer prompting if we're
|
||||
// already waiting on a master password entry.
|
||||
var prompt = this._asyncPrompts[hashKey];
|
||||
var prompter = prompt.prompter;
|
||||
var [origin, httpRealm] = prompter._getAuthTarget(
|
||||
prompt.channel,
|
||||
prompt.authInfo
|
||||
);
|
||||
|
||||
// No UI to wait for.
|
||||
if (!Services.logins.uiBusy) {
|
||||
return;
|
||||
}
|
||||
|
||||
let hasLogins = Services.logins.countLogins(origin, null, httpRealm) > 0;
|
||||
if (
|
||||
!hasLogins &&
|
||||
LoginHelper.schemeUpgrades &&
|
||||
origin.startsWith("https://")
|
||||
) {
|
||||
let httpOrigin = origin.replace(/^https:\/\//, "http://");
|
||||
hasLogins = Services.logins.countLogins(httpOrigin, null, httpRealm) > 0;
|
||||
}
|
||||
// We don't depend on saved logins.
|
||||
if (!hasLogins) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.log("Waiting for master password UI");
|
||||
|
||||
this._uiBusyPromise = new Promise();
|
||||
await this._uiBusyPromise;
|
||||
},
|
||||
|
||||
async _doAsyncPrompt(prompt, hashKey) {
|
||||
this._setPendingPrompt(prompt, hashKey);
|
||||
|
||||
// UI might be busy due to the master password dialog. Wait for it to close.
|
||||
await this._waitForLoginsUI(prompt);
|
||||
|
||||
let ok = false;
|
||||
let promptAborted = false;
|
||||
try {
|
||||
this.log("_doAsyncPrompt - performing the prompt for '" + hashKey + "'");
|
||||
ok = await prompt.prompter.promptAuthInternal(
|
||||
prompt.channel,
|
||||
prompt.level,
|
||||
prompt.authInfo
|
||||
);
|
||||
} catch (e) {
|
||||
if (Services.logins.uiBusy) {
|
||||
let hasLogins = Services.logins.countLogins(origin, null, httpRealm) > 0;
|
||||
if (
|
||||
e instanceof Components.Exception &&
|
||||
e.result == Cr.NS_ERROR_NOT_AVAILABLE
|
||||
!hasLogins &&
|
||||
LoginHelper.schemeUpgrades &&
|
||||
origin.startsWith("https://")
|
||||
) {
|
||||
this.log(
|
||||
"_doAsyncPrompt bypassed, UI is not available in this context"
|
||||
);
|
||||
// Prompts throw NS_ERROR_NOT_AVAILABLE if they're aborted.
|
||||
promptAborted = true;
|
||||
} else {
|
||||
Cu.reportError("LoginManagerAuthPrompter: _doAsyncPrompt " + e + "\n");
|
||||
let httpOrigin = origin.replace(/^https:\/\//, "http://");
|
||||
hasLogins =
|
||||
Services.logins.countLogins(httpOrigin, null, httpRealm) > 0;
|
||||
}
|
||||
if (hasLogins) {
|
||||
this.log("_doAsyncPrompt:run bypassed, master password UI busy");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._removePendingPrompt(prompt, hashKey);
|
||||
var self = this;
|
||||
|
||||
// Handle callbacks
|
||||
for (var consumer of prompt.consumers) {
|
||||
if (!consumer.callback) {
|
||||
// Not having a callback means that consumer didn't provide it
|
||||
// or canceled the notification
|
||||
var runnable = {
|
||||
cancel: false,
|
||||
run() {
|
||||
var ok = false;
|
||||
if (!this.cancel) {
|
||||
try {
|
||||
self.log(
|
||||
"_doAsyncPrompt:run - performing the prompt for '" + hashKey + "'"
|
||||
);
|
||||
ok = prompter.promptAuth(
|
||||
prompt.channel,
|
||||
prompt.level,
|
||||
prompt.authInfo
|
||||
);
|
||||
} catch (e) {
|
||||
if (
|
||||
e instanceof Components.Exception &&
|
||||
e.result == Cr.NS_ERROR_NOT_AVAILABLE
|
||||
) {
|
||||
self.log(
|
||||
"_doAsyncPrompt:run bypassed, UI is not available in this context"
|
||||
);
|
||||
} else {
|
||||
Cu.reportError(
|
||||
"LoginManagerAuthPrompter: _doAsyncPrompt:run: " + e + "\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
delete self._asyncPrompts[hashKey];
|
||||
prompt.inProgress = false;
|
||||
self._asyncPromptInProgress = false;
|
||||
}
|
||||
|
||||
for (var consumer of prompt.consumers) {
|
||||
if (!consumer.callback) {
|
||||
// Not having a callback means that consumer didn't provide it
|
||||
// or canceled the notification
|
||||
continue;
|
||||
}
|
||||
|
||||
self.log("Calling back to " + consumer.callback + " ok=" + ok);
|
||||
try {
|
||||
if (ok) {
|
||||
consumer.callback.onAuthAvailable(
|
||||
consumer.context,
|
||||
prompt.authInfo
|
||||
);
|
||||
} else {
|
||||
consumer.callback.onAuthCancelled(consumer.context, !this.cancel);
|
||||
}
|
||||
} catch (e) {
|
||||
/* Throw away exceptions caused by callback */
|
||||
}
|
||||
}
|
||||
self._doAsyncPrompt();
|
||||
},
|
||||
};
|
||||
|
||||
this._asyncPromptInProgress = true;
|
||||
prompt.inProgress = true;
|
||||
|
||||
Services.tm.dispatchToMainThread(runnable);
|
||||
this.log("_doAsyncPrompt:run dispatched");
|
||||
},
|
||||
|
||||
_cancelPendingPrompts() {
|
||||
this.log("Canceling all pending prompts...");
|
||||
var asyncPrompts = this._asyncPrompts;
|
||||
this.__proto__._asyncPrompts = {};
|
||||
|
||||
for (var hashKey in asyncPrompts) {
|
||||
let prompt = asyncPrompts[hashKey];
|
||||
// Watch out! If this prompt is currently prompting, let it handle
|
||||
// notifying the callbacks of success/failure, since it's already
|
||||
// asking the user for input. Reusing a callback can be crashy.
|
||||
if (prompt.inProgress) {
|
||||
this.log("skipping a prompt in progress");
|
||||
continue;
|
||||
}
|
||||
|
||||
this.log("Calling back to " + consumer.callback + " ok=" + ok);
|
||||
try {
|
||||
if (ok) {
|
||||
consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo);
|
||||
} else {
|
||||
consumer.callback.onAuthCancelled(consumer.context, !promptAborted);
|
||||
for (var consumer of prompt.consumers) {
|
||||
if (!consumer.callback) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.log("Canceling async auth prompt callback " + consumer.callback);
|
||||
try {
|
||||
consumer.callback.onAuthCancelled(consumer.context, true);
|
||||
} catch (e) {
|
||||
/* Just ignore exceptions from the callback */
|
||||
}
|
||||
} catch (e) {
|
||||
/* Throw away exceptions caused by callback */
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -609,7 +637,16 @@ LoginManagerAuthPrompter.prototype = {
|
|||
return [formattedOrigin, formattedOrigin + pathname, uri.username];
|
||||
},
|
||||
|
||||
async promptAuthInternal(aChannel, aLevel, aAuthInfo) {
|
||||
/* ---------- nsIAuthPrompt2 prompts ---------- */
|
||||
|
||||
/**
|
||||
* Implementation of nsIAuthPrompt2.
|
||||
*
|
||||
* @param {nsIChannel} aChannel
|
||||
* @param {int} aLevel
|
||||
* @param {nsIAuthInformation} aAuthInfo
|
||||
*/
|
||||
promptAuth(aChannel, aLevel, aAuthInfo) {
|
||||
var selectedLogin = null;
|
||||
var checkbox = { value: false };
|
||||
var checkboxLabel = null;
|
||||
|
@ -719,16 +756,29 @@ LoginManagerAuthPrompter.prototype = {
|
|||
this._browser
|
||||
);
|
||||
}
|
||||
|
||||
ok = await Services.prompt.asyncPromptAuth(
|
||||
this._browser?.browsingContext,
|
||||
LoginManagerAuthPrompter.promptAuthModalType,
|
||||
aChannel,
|
||||
aLevel,
|
||||
aAuthInfo,
|
||||
checkboxLabel,
|
||||
checkbox
|
||||
);
|
||||
if (this._browser) {
|
||||
ok = Services.prompt.promptAuthBC(
|
||||
this._browser.browsingContext,
|
||||
LoginManagerAuthPrompter.promptAuthModalType,
|
||||
aChannel,
|
||||
aLevel,
|
||||
aAuthInfo,
|
||||
checkboxLabel,
|
||||
checkbox
|
||||
);
|
||||
} else {
|
||||
// Can't tab prompt without browser. Fallback to window prompt.
|
||||
// For cases where this._chromeWindow is defined, this will keep our
|
||||
// parent relationship intact as opposed to passing null above.
|
||||
ok = Services.prompt.promptAuth(
|
||||
this._chromeWindow,
|
||||
aChannel,
|
||||
aLevel,
|
||||
aAuthInfo,
|
||||
checkboxLabel,
|
||||
checkbox
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let [username, password] = this._GetAuthInfo(aAuthInfo);
|
||||
|
@ -818,25 +868,6 @@ LoginManagerAuthPrompter.prototype = {
|
|||
return ok;
|
||||
},
|
||||
|
||||
/* ---------- nsIAuthPrompt2 prompts ---------- */
|
||||
|
||||
/**
|
||||
* Implementation of nsIAuthPrompt2.
|
||||
*
|
||||
* @param {nsIChannel} aChannel
|
||||
* @param {int} aLevel
|
||||
* @param {nsIAuthInformation} aAuthInfo
|
||||
*/
|
||||
promptAuth(aChannel, aLevel, aAuthInfo) {
|
||||
let closed = false;
|
||||
let result = false;
|
||||
this.promptAuthInternal(aChannel, aLevel, aAuthInfo)
|
||||
.then(ok => (result = ok))
|
||||
.finally(() => (closed = true));
|
||||
Services.tm.spinEventLoopUntilOrShutdown(() => closed);
|
||||
return result;
|
||||
},
|
||||
|
||||
asyncPromptAuth(aChannel, aCallback, aContext, aLevel, aAuthInfo) {
|
||||
var cancelable = null;
|
||||
|
||||
|
@ -850,33 +881,32 @@ LoginManagerAuthPrompter.prototype = {
|
|||
|
||||
cancelable = this._newAsyncPromptConsumer(aCallback, aContext);
|
||||
|
||||
let [origin, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo);
|
||||
var [origin, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo);
|
||||
|
||||
let hashKey = aLevel + "|" + origin + "|" + httpRealm;
|
||||
var hashKey = aLevel + "|" + origin + "|" + httpRealm;
|
||||
this.log("Async prompt key = " + hashKey);
|
||||
let pendingPrompt = this._factory.getPendingPrompt(
|
||||
this._browser,
|
||||
hashKey
|
||||
);
|
||||
if (pendingPrompt) {
|
||||
var asyncPrompt = this._factory._asyncPrompts[hashKey];
|
||||
if (asyncPrompt) {
|
||||
this.log(
|
||||
"Prompt bound to an existing one in the queue, callback = " +
|
||||
aCallback
|
||||
);
|
||||
pendingPrompt.consumers.push(cancelable);
|
||||
asyncPrompt.consumers.push(cancelable);
|
||||
return cancelable;
|
||||
}
|
||||
|
||||
this.log("Adding new async prompt, callback = " + aCallback);
|
||||
let asyncPrompt = {
|
||||
this.log("Adding new prompt to the queue, callback = " + aCallback);
|
||||
asyncPrompt = {
|
||||
consumers: [cancelable],
|
||||
channel: aChannel,
|
||||
authInfo: aAuthInfo,
|
||||
level: aLevel,
|
||||
inProgress: false,
|
||||
prompter: this,
|
||||
};
|
||||
|
||||
this._factory._doAsyncPrompt(asyncPrompt, hashKey);
|
||||
this._factory._asyncPrompts[hashKey] = asyncPrompt;
|
||||
this._factory._doAsyncPrompt();
|
||||
} catch (e) {
|
||||
Cu.reportError(
|
||||
"LoginManagerAuthPrompter: " +
|
||||
|
@ -918,10 +948,6 @@ LoginManagerAuthPrompter.prototype = {
|
|||
this._browser = aBrowser;
|
||||
},
|
||||
|
||||
get browser() {
|
||||
return this._browser;
|
||||
},
|
||||
|
||||
set openerBrowser(aOpenerBrowser) {
|
||||
this._openerBrowser = aOpenerBrowser;
|
||||
},
|
||||
|
|
|
@ -11,22 +11,6 @@ function handleRequest(request, response)
|
|||
|
||||
var expected_user = "test", expected_pass = "testpass", realm = "mochitest";
|
||||
|
||||
// user=xxx
|
||||
match = /[^_]user=([^&]*)/.exec(query);
|
||||
if (match)
|
||||
expected_user = match[1];
|
||||
|
||||
// pass=xxx
|
||||
match = /[^_]pass=([^&]*)/.exec(query);
|
||||
if (match)
|
||||
expected_pass = match[1];
|
||||
|
||||
// realm=xxx
|
||||
match = /[^_]realm=([^&]*)/.exec(query);
|
||||
if (match)
|
||||
realm = match[1];
|
||||
|
||||
|
||||
// Look for an authentication header, if any, in the request.
|
||||
//
|
||||
// EG: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
|
||||
|
|
|
@ -116,5 +116,3 @@ support-files =
|
|||
[browser_username_select_dialog.js]
|
||||
support-files =
|
||||
subtst_notifications_change_p.html
|
||||
[browser_basicAuth_multiTab.js]
|
||||
skip-if = os == "android"
|
||||
|
|
|
@ -1,141 +0,0 @@
|
|||
/* 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";
|
||||
|
||||
/**
|
||||
* Tests that can show multiple auth prompts in different tabs and handle them
|
||||
* correctly.
|
||||
*/
|
||||
|
||||
const ORIGIN1 = "https://example.com";
|
||||
const ORIGIN2 = "https://example.org";
|
||||
|
||||
const AUTH_PATH =
|
||||
"/browser/toolkit/components/passwordmgr/test/browser/authenticate.sjs";
|
||||
|
||||
/**
|
||||
* Opens a tab and navigates to the auth test path.
|
||||
* @param {String} origin - Origin to open with test path.
|
||||
* @param {Object} authOptions - Authentication options to pass to server and
|
||||
* test for.
|
||||
* @param {String} authOptions.user - Expected username.
|
||||
* @param {String} authOptions.pass - Expected password.
|
||||
* @param {String} authOptions.realm - Realm to return on auth request.
|
||||
* @returns {Object} - An object containing passed origin and authOptions,
|
||||
* opened tab, a promise which resolves once the tab loads, a promise which
|
||||
* resolves once the prompt has been opened.
|
||||
*/
|
||||
function openTabWithAuthPrompt(origin, authOptions) {
|
||||
let tab = BrowserTestUtils.addTab(gBrowser);
|
||||
let promptPromise = PromptTestUtils.waitForPrompt(tab.linkedBrowser, {
|
||||
modalType: Services.prompt.MODAL_TYPE_TAB,
|
||||
promptType: "promptUserAndPass",
|
||||
});
|
||||
let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
|
||||
let url = new URL(origin + AUTH_PATH);
|
||||
Object.entries(authOptions).forEach(([key, value]) =>
|
||||
url.searchParams.append(key, value)
|
||||
);
|
||||
info("Loading " + url.toString());
|
||||
BrowserTestUtils.loadURI(tab.linkedBrowser, url.toString());
|
||||
return { origin, tab, authOptions, loadPromise, promptPromise };
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for tab to load and tests for expected auth state.
|
||||
* @param {boolean} expectAuthed - true: auth success, false otherwise.
|
||||
* @param {Object} tabInfo - Information about the tab as generated by
|
||||
* openTabWithAuthPrompt.
|
||||
*/
|
||||
async function testTabAuthed(expectAuthed, { tab, loadPromise, authOptions }) {
|
||||
// Wait for tab to load after auth.
|
||||
await loadPromise;
|
||||
ok(true, "Tab loads after auth");
|
||||
|
||||
// Fetch auth results from body (set by authenticate.sjs).
|
||||
let { loginResult, user, pass } = await SpecialPowers.spawn(
|
||||
tab.linkedBrowser,
|
||||
[],
|
||||
() => {
|
||||
let result = {};
|
||||
result.loginResult = content.document.getElementById("ok").innerText;
|
||||
result.user = content.document.getElementById("user").innerText;
|
||||
result.pass = content.document.getElementById("pass").innerText;
|
||||
return result;
|
||||
}
|
||||
);
|
||||
|
||||
is(loginResult == "PASS", expectAuthed, "Site has expected auth state");
|
||||
is(user, expectAuthed ? authOptions.user : "", "Sent correct user");
|
||||
is(pass, expectAuthed ? authOptions.pass : "", "Sent correct password");
|
||||
}
|
||||
|
||||
add_task(async function setup() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
// This test relies on tab auth prompts.
|
||||
set: [["prompts.modalType.httpAuth", Services.prompt.MODAL_TYPE_TAB]],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test() {
|
||||
let tabA = openTabWithAuthPrompt(ORIGIN1, {
|
||||
user: "userA",
|
||||
pass: "passA",
|
||||
realm: "realmA",
|
||||
});
|
||||
// Tab B and C share realm and credentials.
|
||||
// However, since the auth happens in separate tabs we should get two prompts.
|
||||
let tabB = openTabWithAuthPrompt(ORIGIN2, {
|
||||
user: "userB",
|
||||
pass: "passB",
|
||||
realm: "realmB",
|
||||
});
|
||||
let tabC = openTabWithAuthPrompt(ORIGIN2, {
|
||||
user: "userB",
|
||||
pass: "passB",
|
||||
realm: "realmB",
|
||||
});
|
||||
let tabs = [tabA, tabB, tabC];
|
||||
|
||||
info(`Opening ${tabs.length} tabs with auth prompts`);
|
||||
let prompts = await Promise.all(tabs.map(tab => tab.promptPromise));
|
||||
|
||||
is(prompts.length, tabs.length, "Should have one prompt per tab");
|
||||
|
||||
for (let i = 0; i < prompts.length; i++) {
|
||||
ok(
|
||||
prompts[i].args.text.startsWith(tabs[i].origin),
|
||||
"Prompt matches the tabs origin"
|
||||
);
|
||||
}
|
||||
|
||||
// Interact with the prompts. This is deliberately done out of order
|
||||
// (no FIFO, LIFO).
|
||||
let [promptA, promptB, promptC] = prompts;
|
||||
|
||||
// Accept prompt B with correct login details.
|
||||
await PromptTestUtils.handlePrompt(promptB, {
|
||||
loginInput: tabB.authOptions.user,
|
||||
passwordInput: tabB.authOptions.pass,
|
||||
});
|
||||
await testTabAuthed(true, tabB);
|
||||
|
||||
// Accept prompt A with correct login details
|
||||
await PromptTestUtils.handlePrompt(promptA, {
|
||||
loginInput: tabA.authOptions.user,
|
||||
passwordInput: tabA.authOptions.pass,
|
||||
});
|
||||
await testTabAuthed(true, tabA);
|
||||
|
||||
// Cancel prompt C
|
||||
await PromptTestUtils.handlePrompt(promptC, {
|
||||
buttonNumClick: 1,
|
||||
});
|
||||
await testTabAuthed(false, tabC);
|
||||
|
||||
// Cleanup tabs
|
||||
tabs.forEach(({ tab }) => BrowserTestUtils.removeTab(tab));
|
||||
});
|
|
@ -60,8 +60,6 @@ function commonDialogOnLoad() {
|
|||
Dialog.onButton3();
|
||||
window.close();
|
||||
});
|
||||
document.subDialogSetDefaultFocus = isInitialFocus =>
|
||||
Dialog.setDefaultFocus(isInitialFocus);
|
||||
Dialog.onLoad(dialog);
|
||||
|
||||
// resize the window to the content
|
||||
|
|
|
@ -18,9 +18,6 @@ const { AppConstants } = ChromeUtils.import(
|
|||
function CommonDialog(args, ui) {
|
||||
this.args = args;
|
||||
this.ui = ui;
|
||||
this.initialFocusPromise = new Promise(resolve => {
|
||||
this.initialFocusResolver = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
CommonDialog.prototype = {
|
||||
|
@ -32,8 +29,6 @@ CommonDialog.prototype = {
|
|||
iconClass: undefined,
|
||||
soundID: undefined,
|
||||
focusTimer: null,
|
||||
initialFocusPromise: null,
|
||||
initialFocusResolver: null,
|
||||
|
||||
/**
|
||||
* @param [commonDialogEl] - Dialog element from commonDialog.xhtml,
|
||||
|
@ -207,12 +202,21 @@ CommonDialog.prototype = {
|
|||
button.setAttribute("default", "true");
|
||||
}
|
||||
|
||||
let isEmbedded =
|
||||
commonDialogEl && this.ui.prompt.docShell.chromeEventHandler;
|
||||
if (!isEmbedded && !this.ui.promptContainer?.hidden) {
|
||||
// Set default focus and select textbox contents if applicable. If we're
|
||||
// embedded SubDialogManager will call setDefaultFocus for us.
|
||||
this.setDefaultFocus(true);
|
||||
let focusReady;
|
||||
if (!this.ui.promptContainer || !this.ui.promptContainer.hidden) {
|
||||
// Set default focus and select textbox contents if applicable.
|
||||
|
||||
if (commonDialogEl && this.ui.prompt.docShell.chromeEventHandler) {
|
||||
// We're embedded. Delay focus until onload, to after when our embedder
|
||||
// (SubDialog) has focused the frame.
|
||||
focusReady = new Promise(resolve =>
|
||||
this.ui.prompt.addEventListener("load", resolve, { once: true })
|
||||
).then(() => {
|
||||
this.setDefaultFocus(true);
|
||||
});
|
||||
} else {
|
||||
this.setDefaultFocus(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.args.enableDelay) {
|
||||
|
@ -236,11 +240,10 @@ CommonDialog.prototype = {
|
|||
}
|
||||
|
||||
if (commonDialogEl) {
|
||||
if (isEmbedded) {
|
||||
// If we delayed default focus above, wait for it to be ready before
|
||||
// sending the notification.
|
||||
await this.initialFocusPromise;
|
||||
}
|
||||
// If we delayed default focus above, wait for it to be ready before
|
||||
// sending the notification.
|
||||
await focusReady;
|
||||
// ui.prompt is the window object of the dialog.
|
||||
Services.obs.notifyObservers(this.ui.prompt, "common-dialog-loaded");
|
||||
} else {
|
||||
// ui.promptContainer is the <tabmodalprompt> element.
|
||||
|
@ -320,10 +323,6 @@ CommonDialog.prototype = {
|
|||
} else {
|
||||
this.ui.loginTextbox.focus();
|
||||
}
|
||||
|
||||
if (isInitialLoad) {
|
||||
this.initialFocusResolver();
|
||||
}
|
||||
},
|
||||
|
||||
onCheckbox() {
|
||||
|
|
|
@ -691,25 +691,75 @@ Prompter.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Requests a username and a password. Shows a dialog with username and
|
||||
* password field, depending on flags also a domain field.
|
||||
* Asynchronously prompt the user for a username and password.
|
||||
* This has largely the same semantics as promptUsernameAndPassword(),
|
||||
* but returns immediately after calling and returns the entered
|
||||
* data in a callback.
|
||||
*
|
||||
* @param {mozIDOMWindowProxy} domWin - The parent window or null.
|
||||
* @param {nsIChannel} channel - The channel that requires authentication.
|
||||
* @param {nsIAuthPromptCallback} callback - Called once the prompt has been
|
||||
* closed.
|
||||
* @param {nsISupports} context
|
||||
* @param {Number} level - Security level of the credential transmission.
|
||||
* Any of nsIAuthPrompt2.<LEVEL_NONE|LEVEL_PW_ENCRYPTED|LEVEL_SECURE>
|
||||
* @param {nsIAuthInformation} authInfo
|
||||
* @param {String} checkLabel - Text to appear with the checkbox.
|
||||
* If null, check box will not be shown.
|
||||
* @param {Object} checkValue - Contains the initial checked state of the
|
||||
* checkbox when this method is called and the final checked state
|
||||
* after the callback.
|
||||
* @returns {nsICancelable} Interface to cancel prompt.
|
||||
*/
|
||||
asyncPromptAuth(
|
||||
domWin,
|
||||
channel,
|
||||
callback,
|
||||
context,
|
||||
level,
|
||||
authInfo,
|
||||
checkLabel,
|
||||
checkValue
|
||||
) {
|
||||
let p = this.pickPrompter({ domWin });
|
||||
return p.asyncPromptAuth(
|
||||
channel,
|
||||
callback,
|
||||
context,
|
||||
level,
|
||||
authInfo,
|
||||
checkLabel,
|
||||
checkValue
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Asynchronously prompt the user for a username and password.
|
||||
* This has largely the same semantics as promptUsernameAndPassword(),
|
||||
* but returns immediately after calling and returns the entered
|
||||
* data in a callback.
|
||||
*
|
||||
* @param {BrowsingContext} browsingContext - The browsing context the
|
||||
* prompt should be opened for.
|
||||
* @param {Number} modalType - The modal type of the prompt.
|
||||
* nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
|
||||
* @param {nsIChannel} channel - The channel that requires authentication.
|
||||
* @param {nsIAuthPromptCallback} callback - Called once the prompt has been
|
||||
* closed.
|
||||
* @param {nsISupports} context
|
||||
* @param {Number} level - Security level of the credential transmission.
|
||||
* Any of nsIAuthPrompt2.<LEVEL_NONE|LEVEL_PW_ENCRYPTED|LEVEL_SECURE>
|
||||
* @param {nsIAuthInformation} authInfo - Authentication information object.
|
||||
* @param {nsIAuthInformation} authInfo
|
||||
* @param {String} checkLabel - Text to appear with the checkbox.
|
||||
* If null, check box will not be shown.
|
||||
* @param {Object} checkValue - Initial checked state of the checkbox.
|
||||
* @returns {Promise<nsIPropertyBag<{ ok: Boolean }>>}
|
||||
* A promise which resolves when the prompt is dismissed.
|
||||
* @param {Object} checkValue - Contains the initial checked state of the
|
||||
* checkbox when this method is called and the final checked state
|
||||
* after the callback.
|
||||
* @returns {nsICancelable} Interface to cancel prompt.
|
||||
*/
|
||||
asyncPromptAuth(browsingContext, modalType, ...promptArgs) {
|
||||
let p = this.pickPrompter({ browsingContext, modalType, async: true });
|
||||
return p.promptAuth(...promptArgs);
|
||||
asyncPromptAuthBC(browsingContext, modalType, ...promptArgs) {
|
||||
let p = this.pickPrompter({ browsingContext, modalType });
|
||||
return p.asyncPromptAuth(...promptArgs);
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -1596,12 +1646,12 @@ class ModalPrompter {
|
|||
|
||||
let [username, password] = PromptUtils.getAuthInfo(authInfo);
|
||||
|
||||
let userParam = this.async ? username : { value: username };
|
||||
let passParam = this.async ? password : { value: password };
|
||||
let userParam = { value: username };
|
||||
let passParam = { value: password };
|
||||
|
||||
let result;
|
||||
let ok;
|
||||
if (authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD) {
|
||||
result = this.nsIPrompt_promptPassword(
|
||||
ok = this.nsIPrompt_promptPassword(
|
||||
null,
|
||||
message,
|
||||
passParam,
|
||||
|
@ -1609,7 +1659,7 @@ class ModalPrompter {
|
|||
checkValue
|
||||
);
|
||||
} else {
|
||||
result = this.nsIPrompt_promptUsernameAndPassword(
|
||||
ok = this.nsIPrompt_promptUsernameAndPassword(
|
||||
null,
|
||||
message,
|
||||
userParam,
|
||||
|
@ -1619,25 +1669,10 @@ class ModalPrompter {
|
|||
);
|
||||
}
|
||||
|
||||
// For the async case result is an nsIPropertyBag with prompt results.
|
||||
if (this.async) {
|
||||
return result.then(bag => {
|
||||
let ok = bag.getProperty("ok");
|
||||
if (ok) {
|
||||
let username = bag.getProperty("user");
|
||||
let password = bag.getProperty("pass");
|
||||
PromptUtils.setAuthInfo(authInfo, username, password);
|
||||
}
|
||||
return ok;
|
||||
});
|
||||
}
|
||||
|
||||
// For the sync case result is the "ok" boolean which indicates whether
|
||||
// the user has confirmed the dialog.
|
||||
if (result) {
|
||||
if (ok) {
|
||||
PromptUtils.setAuthInfo(authInfo, userParam.value, passParam.value);
|
||||
}
|
||||
return result;
|
||||
return ok;
|
||||
}
|
||||
|
||||
asyncPromptAuth(
|
||||
|
|
|
@ -160,30 +160,19 @@ let PromptTestUtils = {
|
|||
|
||||
let dialog;
|
||||
await TestUtils.topicObserved(topic, subject => {
|
||||
// If we are not given a browser, use the currently selected browser of the window
|
||||
let browser =
|
||||
parentBrowser || subject.ownerGlobal.gBrowser.selectedBrowser;
|
||||
if (isCommonDialog(modalType)) {
|
||||
// Is not associated with given parent window, skip
|
||||
if (parentWindow && subject.opener !== parentWindow) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For tab prompts, ensure that the associated browser matches.
|
||||
if (modalType == Services.prompt.MODAL_TYPE_TAB) {
|
||||
let dialogBox = parentWindow.gBrowser.getTabDialogBox(browser);
|
||||
let hasMatchingDialog = dialogBox._dialogManager._dialogs.some(
|
||||
d => d._frame?.browsingContext == subject.browsingContext
|
||||
);
|
||||
if (!hasMatchingDialog) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// subject is the window object of the prompt which has a Dialog object
|
||||
// attached.
|
||||
dialog = subject.Dialog;
|
||||
} else {
|
||||
// If we are not given a browser, use the currently selected browser of the window
|
||||
let browser =
|
||||
parentBrowser || subject.ownerGlobal.gBrowser.selectedBrowser;
|
||||
|
||||
// subject is the tabprompt dom node
|
||||
// Get the full prompt object which has the dialog object
|
||||
let prompt = browser.tabModalPromptBox.getPrompt(subject);
|
||||
|
|
|
@ -627,18 +627,29 @@ interface nsIPromptService : nsISupports
|
|||
in nsIAuthInformation authInfo,
|
||||
in wstring checkboxLabel,
|
||||
inout boolean checkValue);
|
||||
nsICancelable asyncPromptAuth(in mozIDOMWindowProxy aParent,
|
||||
in nsIChannel aChannel,
|
||||
in nsIAuthPromptCallback aCallback,
|
||||
in nsISupports aContext,
|
||||
in uint32_t level,
|
||||
in nsIAuthInformation authInfo,
|
||||
in wstring checkboxLabel,
|
||||
inout boolean checkValue);
|
||||
/**
|
||||
* Async version of promptAuthBC
|
||||
* Like asyncPromptAuth, but with a BrowsingContext as parent.
|
||||
*
|
||||
* @return A promise which resolves when the prompt is dismissed.
|
||||
*
|
||||
* @resolves nsIPropertyBag { ok: boolean }
|
||||
* @param aBrowsingContext
|
||||
* The browsing context the prompt should be opened for.
|
||||
* @param modalType
|
||||
* Whether the prompt should be window, tab or content modal.
|
||||
*/
|
||||
Promise asyncPromptAuth(in BrowsingContext aBrowsingContext,
|
||||
in unsigned long modalType,
|
||||
in nsIChannel aChannel,
|
||||
in uint32_t level,
|
||||
in nsIAuthInformation authInfo,
|
||||
in wstring checkboxLabel,
|
||||
in boolean checkValue);
|
||||
nsICancelable asyncPromptAuthBC(in BrowsingContext aBrowsingContext,
|
||||
in unsigned long modalType,
|
||||
in nsIChannel aChannel,
|
||||
in nsIAuthPromptCallback aCallback,
|
||||
in nsISupports aContext,
|
||||
in uint32_t level,
|
||||
in nsIAuthInformation authInfo,
|
||||
in wstring checkboxLabel,
|
||||
inout boolean checkValue);
|
||||
};
|
||||
|
|
|
@ -755,22 +755,7 @@ SubDialog.prototype = {
|
|||
this._untrapFocus();
|
||||
},
|
||||
|
||||
/**
|
||||
* Focus the dialog content.
|
||||
* If the embedded document defines a custom focus handler it will be called.
|
||||
* Otherwise we will focus the first focusable element in the content window.
|
||||
* @param {boolean} [isInitialFocus] - Whether the dialog is focused for the
|
||||
* first time after opening.
|
||||
*/
|
||||
focus(isInitialFocus = false) {
|
||||
// If the content window has its own focus logic, hand off the focus call.
|
||||
let focusHandler = this._frame?.contentDocument?.subDialogSetDefaultFocus;
|
||||
if (focusHandler) {
|
||||
focusHandler(isInitialFocus);
|
||||
return;
|
||||
}
|
||||
// Handle focus ourselves. Try to move the focus to the first element in
|
||||
// the content window.
|
||||
focus() {
|
||||
let fm = Services.focus;
|
||||
let focusedElement = fm.moveFocus(
|
||||
this._frame.contentWindow,
|
||||
|
@ -786,6 +771,7 @@ SubDialog.prototype = {
|
|||
},
|
||||
|
||||
_trapFocus() {
|
||||
this.focus();
|
||||
// Attach a system event listener so the dialog can cancel keydown events.
|
||||
// See Bug 1669990.
|
||||
this._box.addEventListener("keydown", this, { mozSystemGroup: true });
|
||||
|
@ -904,19 +890,22 @@ class SubDialogManager {
|
|||
this._dialogStack.hidden = false;
|
||||
this._dialogStack.classList.remove("temporarilyHidden");
|
||||
this._topLevelPrevActiveElement = doc.activeElement;
|
||||
|
||||
// Mark the top dialog according to the array insertion order.
|
||||
// This is needed because when multiple dialog are opening, their callbacks
|
||||
// don't necessarily arrive in order.
|
||||
this._preloadDialog.isTop = true;
|
||||
} else if (this._orderType === SubDialogManager.ORDER_STACK) {
|
||||
this._preloadDialog.isTop = true;
|
||||
this._dialogs[this._dialogs.length - 1].isTop = false;
|
||||
}
|
||||
|
||||
this._dialogs.push(this._preloadDialog);
|
||||
this._preloadDialog.open(
|
||||
aURL,
|
||||
{
|
||||
features,
|
||||
closingCallback,
|
||||
closedCallback,
|
||||
sizeTo,
|
||||
},
|
||||
{ features, closingCallback, closedCallback, sizeTo },
|
||||
...aParams
|
||||
);
|
||||
this._dialogs.push(this._preloadDialog);
|
||||
|
||||
let openedDialog = this._preloadDialog;
|
||||
|
||||
|
@ -984,19 +973,20 @@ class SubDialogManager {
|
|||
}
|
||||
|
||||
_onDialogOpen(dialog) {
|
||||
// The first dialog is always on top
|
||||
if (this._dialogs.length === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
let lowerDialogs = [];
|
||||
if (dialog == this._topDialog) {
|
||||
dialog.focus(true);
|
||||
} else {
|
||||
|
||||
if (!dialog.isTop) {
|
||||
// Opening dialog is not on top, hide it
|
||||
lowerDialogs.push(dialog);
|
||||
}
|
||||
|
||||
// For stack order, hide the previous top
|
||||
if (
|
||||
this._dialogs.length &&
|
||||
this._orderType === SubDialogManager.ORDER_STACK
|
||||
) {
|
||||
if (this._orderType === SubDialogManager.ORDER_STACK) {
|
||||
let index = this._dialogs.indexOf(dialog);
|
||||
if (index > 0) {
|
||||
lowerDialogs.push(this._dialogs[index - 1]);
|
||||
|
@ -1016,11 +1006,7 @@ class SubDialogManager {
|
|||
|
||||
if (this._topDialog) {
|
||||
// The prevActiveElement is only set for stacked dialogs
|
||||
if (this._topDialog._prevActiveElement) {
|
||||
this._topDialog._prevActiveElement.focus();
|
||||
} else {
|
||||
this._topDialog.focus(true);
|
||||
}
|
||||
this._topDialog._prevActiveElement?.focus();
|
||||
this._topDialog._overlay.setAttribute("topmost", true);
|
||||
this._topDialog._addDialogEventListeners(false);
|
||||
this._dialogStack.hidden = false;
|
||||
|
|
Загрузка…
Ссылка в новой задаче