Bug 1615588 - Extended nsIPromptService to support tab modal prompts. r=johannh,MattN,necko-reviewers,dragana

This patch introduces a new tab modal system prompt type. It can be opened via the nsIPromptService
with a destination BrowsingContext. These tab system prompts overlap slightly with the upper
chrome UI to differentiate them from content prompts (previously called tab prompts).

- Extended nsIPromptService and nsIPrompt to accept 3 types of modal prompts:
  - Window prompts
  - Tab (system) prompts
  - Content prompts (the old tab prompts)
- Removed prompt code from Prompter.jsm, always call PromptParent window actor instead
- Added PromptChild window actor to forward pagehide events to parent actor
- Created additional prompt methods in nsIPromptService to prompt by browsingContext and modalType
- Backwards compatibility is maintained, consumers can still open content prompts calling nsIPrompt with a content window

Differential Revision: https://phabricator.services.mozilla.com/D66446
This commit is contained in:
pbz 2020-04-16 14:43:50 +00:00
Родитель 74bd4471ab
Коммит 3075dbd453
13 изменённых файлов: 496 добавлений и 340 удалений

Просмотреть файл

@ -0,0 +1,20 @@
/* 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 = ["PromptChild"];
class PromptChild extends JSWindowActorChild {
constructor(dispatcher) {
super(dispatcher);
}
handleEvent(aEvent) {
if (aEvent.type !== "pagehide") {
return;
}
this.sendAsyncMessage("Prompt:OnPageHide", {});
}
}

Просмотреть файл

@ -120,40 +120,35 @@ class PromptParent extends JSWindowActorParent {
forceClosePrompts(browsingContext) {
let prompts = gBrowserPrompts.get(browsingContext) || [];
for (let prompt of prompts) {
if (prompt.tabModalPrompt) {
prompt.tabModalPrompt.abortPrompt();
}
for (let [, prompt] of prompts) {
prompt.tabModalPrompt && prompt.tabModalPrompt.abortPrompt();
}
}
receiveMessage(message) {
let browsingContext = this.browsingContext;
let args = message.data;
let browsingContext = args.browsingContext || this.browsingContext;
let id = args._remoteId;
switch (message.name) {
case "Prompt:Open": {
const COMMON_DIALOG = "chrome://global/content/commonDialog.xhtml";
const SELECT_DIALOG = "chrome://global/content/selectDialog.xhtml";
let topPrincipal =
browsingContext.top.currentWindowGlobal.documentPrincipal;
args.showAlertOrigin = topPrincipal.equals(args.promptPrincipal);
if (message.data.tabPrompt) {
return this.openTabPrompt(message.data, browsingContext, id);
if (args.modalType === Ci.nsIPrompt.MODAL_TYPE_WINDOW) {
return this.openWindowPrompt(args, browsingContext);
}
let uri =
message.data.promptType == "select" ? SELECT_DIALOG : COMMON_DIALOG;
let browser = browsingContext.top.embedderElement;
return this.openModalWindow(uri, message.data, browser);
return this.openTabPrompt(args, browsingContext, id);
}
case "Prompt:ForceClose": {
this.forceClosePrompt(browsingContext, id);
break;
}
case "Prompt:OnPageHide": {
// User navigates away, close all non window prompts
this.forceClosePrompts(browsingContext);
break;
}
}
return undefined;
@ -172,12 +167,15 @@ class PromptParent extends JSWindowActorParent {
* A unique ID to differentiate multiple Prompts coming from the same
* BrowsingContext.
* @return {Promise}
* Resolves when the TabModalPrompt is dismissed.
* @resolves {Object}
* Resolves with the arguments returned from the TabModalPrompt when it
* is dismissed.
* The arguments returned from the TabModalPrompt.
*/
openTabPrompt(args, browsingContext, id) {
openTabPrompt(args, browsingContext = this.browsingContext, id) {
let browser = browsingContext.top.embedderElement;
if (!browser) {
throw new Error("Cannot tab-prompt without a browser!");
}
let window = browser.ownerGlobal;
let tabPrompt = window.gBrowser.getTabModalPromptBox(browser);
let newPrompt;
@ -206,7 +204,7 @@ class PromptParent extends JSWindowActorParent {
PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed", browser);
resolver(args);
browser.leaveModalState();
browser.maybeLeaveModalState();
};
try {
@ -242,31 +240,49 @@ class PromptParent extends JSWindowActorParent {
}
/**
* Opens a window-modal prompt for a BrowsingContext, and puts the associated
* Opens a window prompt for a BrowsingContext, and puts the associated
* browser in the modal state until the prompt is closed.
*
* @param {string} uri
* The URI to a XUL document to be loaded in a modal window.
* @param {Object} args
* The arguments passed up from the BrowsingContext to be passed
* directly to the modal window.
* @param {Element} browser
* The <xul:browser> from which the request to open the window-modal
* @param {BrowsingContext} browsingContext
* The BrowsingContext from which the request to open the window-modal
* prompt came.
* @return {Promise}
* Resolves when the window prompt is dismissed.
* @resolves {Object}
* Resolves with the arguments returned from the window-modal
* prompt when it is dismissed.
* The arguments returned from the window prompt.
*/
openModalWindow(uri, args, browser) {
let window = browser.ownerGlobal;
openWindowPrompt(args, browsingContext = this.browsingContext) {
const COMMON_DIALOG = "chrome://global/content/commonDialog.xhtml";
const SELECT_DIALOG = "chrome://global/content/selectDialog.xhtml";
let uri = args.promptType == "select" ? SELECT_DIALOG : COMMON_DIALOG;
let browser = browsingContext.top.embedderElement;
// If can't get the browser, because the BC does not have an embedder element,
// use window associated with the BC.
// This happens if we are passed a browsingContext of a chrome window.
let win = (browser && browser.ownerGlobal) || browsingContext.top.window;
// There's a requirement for prompts to be blocked if a window is
// passed and that window is hidden (eg, auth prompts are suppressed if the
// passed window is the hidden window).
// See bug 875157 comment 30 for more..
if (win && win.winUtils && !win.winUtils.isParentWindowMainWidgetVisible) {
throw new Error("Cannot call openModalWindow on a hidden window");
}
try {
browser.enterModalState();
PromptUtils.fireDialogEvent(window, "DOMWillOpenModalDialog", browser);
if (browser) {
browser.enterModalState();
PromptUtils.fireDialogEvent(win, "DOMWillOpenModalDialog", browser);
}
let bag = PromptUtils.objectToPropBag(args);
Services.ww.openWindow(
window,
win,
uri,
"_blank",
"centerscreen,chrome,modal,titlebar",
@ -275,8 +291,10 @@ class PromptParent extends JSWindowActorParent {
PromptUtils.propBagToObject(bag, args);
} finally {
browser.leaveModalState();
PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed", browser);
if (browser) {
browser.leaveModalState();
PromptUtils.fireDialogEvent(win, "DOMModalDialogClosed", browser);
}
}
return Promise.resolve(args);
}

Просмотреть файл

@ -53,6 +53,7 @@ FINAL_TARGET_FILES.actors += [
'PageStyleParent.jsm',
'PluginChild.jsm',
'PluginParent.jsm',
'PromptChild.jsm',
'PromptParent.jsm',
'RFPHelperChild.jsm',
'RFPHelperParent.jsm',

Просмотреть файл

@ -1252,6 +1252,10 @@ pref("browser.menu.showCharacterEncoding", "chrome://browser/locale/browser.prop
// Allow using tab-modal prompts when possible.
pref("prompts.tab_modal.enabled", true);
// Whether prompts should be content modal (1) tab modal (2) or window modal(3) by default
// This is a fallback value for when prompt callers do not specify a modalType.
pref("prompts.defaultModalType", 3);
// Activates preloading of the new tab url.
pref("browser.newtab.preload", true);

Просмотреть файл

@ -324,7 +324,15 @@ let ACTORS = {
parent: {
moduleURI: "resource:///actors/PromptParent.jsm",
},
child: {
moduleURI: "resource:///actors/PromptChild.jsm",
events: {
pagehide: {
capture: true,
},
},
},
includeChrome: true,
allFrames: true,
},

Просмотреть файл

@ -11567,11 +11567,12 @@ nsresult nsDocShell::ConfirmRepost(bool* aRepost) {
return rv;
}
// Make the repost prompt tab modal to prevent malicious pages from locking
// up the browser, see bug 1412559 for an example.
// Make the repost prompt content modal to prevent malicious pages from
// locking up the browser, see bug 1412559 for an example.
if (nsCOMPtr<nsIWritablePropertyBag2> promptBag =
do_QueryInterface(prompter)) {
promptBag->SetPropertyAsBool(NS_LITERAL_STRING("allowTabModal"), true);
promptBag->SetPropertyAsUint32(NS_LITERAL_STRING("modalType"),
nsIPrompt::MODAL_TYPE_CONTENT);
}
int32_t buttonPressed;

Просмотреть файл

@ -4939,9 +4939,10 @@ bool nsGlobalWindowOuter::AlertOrConfirm(bool aAlert, const nsAString& aMessage,
return false;
}
// Always allow tab modal prompts for alert and confirm.
// Always allow content modal prompts for alert and confirm.
if (nsCOMPtr<nsIWritablePropertyBag2> promptBag = do_QueryInterface(prompt)) {
promptBag->SetPropertyAsBool(NS_LITERAL_STRING("allowTabModal"), true);
promptBag->SetPropertyAsUint32(NS_LITERAL_STRING("modalType"),
nsIPrompt::MODAL_TYPE_CONTENT);
}
bool result = false;
@ -5028,9 +5029,10 @@ void nsGlobalWindowOuter::PromptOuter(const nsAString& aMessage,
return;
}
// Always allow tab modal prompts for prompt.
// Always allow content modal prompts for prompt.
if (nsCOMPtr<nsIWritablePropertyBag2> promptBag = do_QueryInterface(prompt)) {
promptBag->SetPropertyAsBool(NS_LITERAL_STRING("allowTabModal"), true);
promptBag->SetPropertyAsUint32(NS_LITERAL_STRING("modalType"),
nsIPrompt::MODAL_TYPE_CONTENT);
}
// Pass in the default value, if any.

Просмотреть файл

@ -1297,7 +1297,7 @@ interface nsIDOMWindowUtils : nsISupports {
/**
* Is the window is in a modal state? [See enterModalState()]
*/
[noscript] boolean isInModalState();
boolean isInModalState();
/**
* Request set internal desktopMode flag change.

Просмотреть файл

@ -61,6 +61,12 @@ interface nsIPrompt : nsISupports
const unsigned long STD_YES_NO_BUTTONS = (BUTTON_TITLE_YES * BUTTON_POS_0) +
(BUTTON_TITLE_NO * BUTTON_POS_1);
// Indicates whether a prompt should be shown in-content, on tab level or as a separate window
const unsigned long MODAL_TYPE_CONTENT = 1;
const unsigned long MODAL_TYPE_TAB = 2;
const unsigned long MODAL_TYPE_WINDOW = 3;
int32_t confirmEx(in wstring dialogTitle,
in wstring text,
in unsigned long buttonFlags,

Просмотреть файл

@ -41,6 +41,12 @@ class BrowserElementChild extends JSWindowActorChild {
}
case "LeaveModalState": {
if (
!message.data.forceLeave &&
!this.contentWindow.windowUtils.isInModalState()
) {
break;
}
this.contentWindow.windowUtils.leaveModalState();
break;
}

Просмотреть файл

@ -26,8 +26,8 @@ Prompter.prototype = {
/* ---------- private members ---------- */
pickPrompter(domWin) {
return new ModalPrompter(domWin);
pickPrompter(options) {
return new ModalPrompter(options);
},
/* ---------- nsIPromptFactory ---------- */
@ -48,7 +48,7 @@ Prompter.prototype = {
}
}
let p = new ModalPrompter(domWin);
let p = new ModalPrompter({ domWin });
p.QueryInterface(iid);
return p;
},
@ -56,25 +56,45 @@ Prompter.prototype = {
/* ---------- nsIPromptService ---------- */
alert(domWin, title, text) {
let p = this.pickPrompter(domWin);
let p = this.pickPrompter({ domWin });
p.alert(title, text);
},
alertBC(browsingContext, modalType, ...promptArgs) {
let p = this.pickPrompter({ browsingContext, modalType });
p.alert(...promptArgs);
},
alertCheck(domWin, title, text, checkLabel, checkValue) {
let p = this.pickPrompter(domWin);
let p = this.pickPrompter({ domWin });
p.alertCheck(title, text, checkLabel, checkValue);
},
alertCheckBC(browsingContext, modalType, ...promptArgs) {
let p = this.pickPrompter({ browsingContext, modalType });
p.alertCheck(...promptArgs);
},
confirm(domWin, title, text) {
let p = this.pickPrompter(domWin);
let p = this.pickPrompter({ domWin });
return p.confirm(title, text);
},
confirmBC(browsingContext, modalType, ...promptArgs) {
let p = this.pickPrompter({ browsingContext, modalType });
return p.confirm(...promptArgs);
},
confirmCheck(domWin, title, text, checkLabel, checkValue) {
let p = this.pickPrompter(domWin);
let p = this.pickPrompter({ domWin });
return p.confirmCheck(title, text, checkLabel, checkValue);
},
confirmCheckBC(browsingContext, modalType, ...promptArgs) {
let p = this.pickPrompter({ browsingContext, modalType });
return p.confirmCheck(...promptArgs);
},
confirmEx(
domWin,
title,
@ -86,7 +106,32 @@ Prompter.prototype = {
checkLabel,
checkValue
) {
let p = this.pickPrompter(domWin);
let p = this.pickPrompter({ domWin });
return p.confirmEx(
title,
text,
flags,
button0,
button1,
button2,
checkLabel,
checkValue
);
},
confirmExBC(
browsingContext,
modalType,
title,
text,
flags,
button0,
button1,
button2,
checkLabel,
checkValue
) {
let p = this.pickPrompter({ browsingContext, modalType });
return p.confirmEx(
title,
text,
@ -100,10 +145,15 @@ Prompter.prototype = {
},
prompt(domWin, title, text, value, checkLabel, checkValue) {
let p = this.pickPrompter(domWin);
let p = this.pickPrompter({ domWin });
return p.nsIPrompt_prompt(title, text, value, checkLabel, checkValue);
},
promptBC(browsingContext, modalType, ...promptArgs) {
let p = this.pickPrompter({ browsingContext, modalType });
return p.nsIPrompt_prompt(...promptArgs);
},
promptUsernameAndPassword(
domWin,
title,
@ -113,7 +163,7 @@ Prompter.prototype = {
checkLabel,
checkValue
) {
let p = this.pickPrompter(domWin);
let p = this.pickPrompter({ domWin });
return p.nsIPrompt_promptUsernameAndPassword(
title,
text,
@ -124,8 +174,13 @@ Prompter.prototype = {
);
},
promptUsernameAndPasswordBC(browsingContext, modalType, ...promptArgs) {
let p = this.pickPrompter({ browsingContext, modalType });
return p.nsIPrompt_promptUsernameAndPassword(...promptArgs);
},
promptPassword(domWin, title, text, pass, checkLabel, checkValue) {
let p = this.pickPrompter(domWin);
let p = this.pickPrompter({ domWin });
return p.nsIPrompt_promptPassword(
title,
text,
@ -135,16 +190,31 @@ Prompter.prototype = {
);
},
promptPasswordBC(browsingContext, modalType, ...promptArgs) {
let p = this.pickPrompter({ browsingContext, modalType });
return p.nsIPrompt_promptPassword(...promptArgs);
},
select(domWin, title, text, list, selected) {
let p = this.pickPrompter(domWin);
let p = this.pickPrompter({ domWin });
return p.select(title, text, list, selected);
},
selectBC(browsingContext, modalType, ...promptArgs) {
let p = this.pickPrompter({ browsingContext, modalType });
return p.select(...promptArgs);
},
promptAuth(domWin, channel, level, authInfo, checkLabel, checkValue) {
let p = this.pickPrompter(domWin);
let p = this.pickPrompter({ domWin });
return p.promptAuth(channel, level, authInfo, checkLabel, checkValue);
},
promptAuthBC(browsingContext, modalType, ...promptArgs) {
let p = this.pickPrompter({ browsingContext, modalType });
return p.promptAuth(...promptArgs);
},
asyncPromptAuth(
domWin,
channel,
@ -155,7 +225,7 @@ Prompter.prototype = {
checkLabel,
checkValue
) {
let p = this.pickPrompter(domWin);
let p = this.pickPrompter({ domWin });
return p.asyncPromptAuth(
channel,
callback,
@ -166,6 +236,11 @@ Prompter.prototype = {
checkValue
);
},
asyncPromptAuthBC(browsingContext, modalType, ...promptArgs) {
let p = this.pickPrompter({ browsingContext, modalType });
return p.asyncPromptAuth(...promptArgs);
},
};
// Common utils not specific to a particular prompter style.
@ -369,33 +444,6 @@ var PromptUtilsTemp = {
return text;
},
getTabModalPrompt(domWin) {
var promptBox = null;
try {
// Get the topmost window, in case we're in a frame.
var promptWin = domWin.top;
// Get the chrome window for the content window we're using.
// (Unwrap because we need a non-IDL property below.)
var chromeWin =
promptWin.docShell.chromeEventHandler.ownerGlobal.wrappedJSObject;
if (chromeWin) {
if (chromeWin.gBrowser && chromeWin.gBrowser.getTabModalPromptBox) {
let browser = promptWin.docShell.chromeEventHandler;
promptBox = chromeWin.gBrowser.getTabModalPromptBox(browser);
} else if (chromeWin.getTabModalPromptBox) {
chromeWin.getTabModalPromptBox(promptWin);
}
}
} catch (e) {
// If any errors happen, just assume no tabmodal prompter.
}
return promptBox;
},
getBrandFullName() {
return this.brandBundle.GetStringFromName("brandFullName");
},
@ -434,190 +482,169 @@ XPCOMUtils.defineLazyGetter(PromptUtils, "ellipsis", function() {
return ellipsis;
});
function openModalWindow(domWin, uri, args) {
// There's an implied contract that says modal prompts should still work
// when no "parent" window is passed for the dialog (eg, the "Master
// Password" dialog does this). These prompts must be shown even if there
// are *no* visible windows at all.
// There's also a requirement for prompts to be blocked if a window is
// passed and that window is hidden (eg, auth prompts are supressed if the
// passed window is the hidden window).
// See bug 875157 comment 30 for more...
if (domWin) {
// a domWin was passed, so we can apply the check for it being hidden.
let winUtils = domWin.windowUtils;
class ModalPrompter {
constructor({ browsingContext = null, domWin = null, modalType = null }) {
if (browsingContext && domWin) {
throw new Error("Pass either browsingContext or domWin");
}
this.browsingContext = browsingContext;
this._domWin = domWin;
if (winUtils && !winUtils.isParentWindowMainWidgetVisible) {
throw Components.Exception(
"Cannot call openModalWindow on a hidden window",
Cr.NS_ERROR_NOT_AVAILABLE
if (this._domWin) {
// We have a domWin, get the associated browsing context
this.browsingContext = BrowsingContext.getFromWindow(this._domWin);
} else if (this.browsingContext) {
// We have a browsingContext, get the associated dom window
if (this.browsingContext.window) {
this._domWin = this.browsingContext.window;
} else {
this._domWin =
this.browsingContext.embedderElement &&
this.browsingContext.embedderElement.ownerGlobal;
}
}
// Use given modal type or fallback to default
this.modalType = modalType || ModalPrompter.defaultModalType;
this.QueryInterface = ChromeUtils.generateQI([
Ci.nsIPrompt,
Ci.nsIAuthPrompt,
Ci.nsIAuthPrompt2,
Ci.nsIWritablePropertyBag2,
]);
}
set modalType(modalType) {
// Setting modal type window is always allowed
if (modalType == Ci.nsIPrompt.MODAL_TYPE_WINDOW) {
this._modalType = modalType;
return;
}
// If we have a chrome window and the browsing context isn't embedded
// in a browser, we can't use tab/content prompts.
// Or if we don't allow tab or content prompts, override modalType
// argument to use window prompts
if (
!this.browsingContext ||
!this._domWin ||
(this._domWin.isChromeWindow &&
!this.browsingContext.top.embedderElement) ||
!ModalPrompter.tabModalEnabled
) {
modalType = Ci.nsIPrompt.MODAL_TYPE_WINDOW;
Cu.reportError(
"Prompter: Browser not available or tab modal prompts disabled. Falling back to window prompt."
);
}
} else {
// We try and find a window to use as the parent, but don't consider
// if that is visible before showing the prompt.
domWin = Services.ww.activeWindow;
// domWin may still be null here if there are _no_ windows open.
this._modalType = modalType;
}
// Note that we don't need to fire DOMWillOpenModalDialog and
// DOMModalDialogClosed events here, wwatcher's OpenWindowInternal
// will do that. Similarly for enterModalState / leaveModalState.
Services.ww.openWindow(
domWin && domWin.docShell.rootTreeItem.domWindow,
uri,
"_blank",
"centerscreen,chrome,modal,titlebar",
args
);
}
get modalType() {
return this._modalType;
}
function openTabPrompt(domWin, tabPrompt, args) {
let docShell = domWin.docShell;
let inPermitUnload =
docShell.contentViewer && docShell.contentViewer.inPermitUnload;
let eventDetail = Cu.cloneInto({ tabPrompt: true, inPermitUnload }, domWin);
PromptUtils.fireDialogEvent(
domWin,
"DOMWillOpenModalDialog",
null,
eventDetail
);
/* ---------- internal methods ---------- */
let winUtils = domWin.windowUtils;
winUtils.enterModalState();
openPrompt(args) {
if (!this.browsingContext) {
// We don't have a browsing context, fallback to a window prompt
let frameMM = docShell.messageManager;
// There's an implied contract that says modal prompts should still work
// when no "parent" window is passed for the dialog (eg, the "Master
// Password" dialog does this). These prompts must be shown even if there
// are *no* visible windows at all.
// We provide a callback so the prompt can close itself. We don't want to
// wait for this event loop to return... Otherwise the presence of other
// prompts on the call stack would in this dialog appearing unresponsive
// until the other prompts had been closed.
let callbackInvoked = false;
let newPrompt;
function onPromptClose(forceCleanup) {
if (!newPrompt && !forceCleanup) {
// We try and find a window to use as the parent, but don't consider
// if that is visible before showing the prompt.
let parentWindow = Services.ww.activeWindow;
// parentWindow may still be null here if there are _no_ windows open.
this.openWindowPrompt(parentWindow, args);
return;
}
callbackInvoked = true;
if (newPrompt) {
tabPrompt.removePrompt(newPrompt);
}
frameMM.removeEventListener("pagehide", pagehide, true);
winUtils.leaveModalState();
PromptUtils.fireDialogEvent(domWin, "DOMModalDialogClosed");
}
frameMM.addEventListener("pagehide", pagehide, true);
function pagehide(e) {
// Check whether the event relates to our window or its ancestors
let window = domWin;
let eventWindow = e.target.defaultView;
while (window != eventWindow && window.parent != window) {
window = window.parent;
}
if (window != eventWindow) {
return;
}
frameMM.removeEventListener("pagehide", pagehide, true);
if (newPrompt) {
newPrompt.abortPrompt();
}
}
try {
let topPrincipal = domWin.top.document.nodePrincipal;
let promptPrincipal = domWin.document.nodePrincipal;
args.showAlertOrigin = topPrincipal.equals(promptPrincipal);
args.promptActive = true;
newPrompt = tabPrompt.appendPrompt(args, onPromptClose);
// TODO since we don't actually open a window, need to check if
// there's other stuff in nsWindowWatcher::OpenWindowInternal
// that we might need to do here as well.
Services.tm.spinEventLoopUntil(() => !args.promptActive);
delete args.promptActive;
if (args.promptAborted) {
throw Components.Exception(
"prompt aborted by user",
Cr.NS_ERROR_NOT_AVAILABLE
// Select prompts are not part of CommonDialog
// and thus not supported as tab or content prompts yet. See Bug 1622817.
// Once they are integrated this override should be removed.
if (
args.promptType == "select" &&
this.modalType !== Ci.nsIPrompt.MODAL_TYPE_WINDOW
) {
Cu.reportError(
"Prompter: 'select' prompts do not support tab/content prompting. Falling back to window prompt."
);
}
} finally {
// If the prompt unexpectedly failed to invoke the callback, do so here.
if (!callbackInvoked) {
onPromptClose(true);
}
}
}
function openRemotePrompt(domWin, args) {
let actor = domWin.windowGlobalChild.getActor("Prompt");
let docShell = domWin.docShell;
let inPermitUnload =
docShell.contentViewer && docShell.contentViewer.inPermitUnload;
let eventDetail = Cu.cloneInto(
{ tabPrompt: args.tabPrompt, inPermitUnload },
domWin
);
PromptUtils.fireDialogEvent(
domWin,
"DOMWillOpenModalDialog",
null,
eventDetail
);
let windowUtils = domWin.windowUtils;
windowUtils.enterModalState();
// It is technically possible for multiple prompts to be sent from a single
// BrowsingContext. See bug 1266353. We use a randomly generated UUID to
// differentiate between the different prompts.
let id =
"id" +
Cc["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator)
.generateUUID()
.toString();
let frameMM = docShell.messageManager;
let closed = false;
let onPageHide = e => {
let window = domWin;
let eventWindow = e.target.defaultView;
while (window != eventWindow && window.parent != window) {
window = window.parent;
}
if (window != eventWindow) {
return;
args.modalType = Ci.nsIPrompt.MODAL_TYPE_WINDOW;
} else {
args.modalType = this.modalType;
}
actor.sendAsyncMessage("Prompt:ForceClose", { _remoteId: id });
closed = true;
};
args.browsingContext = this.browsingContext;
frameMM.addEventListener("pagehide", onPageHide, true);
let actor = this._domWin.windowGlobalChild.getActor("Prompt");
try {
let promptPrincipal = domWin.document.nodePrincipal;
args.promptPrincipal = promptPrincipal;
let docShell =
(this.browsingContext && this.browsingContext.docShell) ||
this._domWin.docShell;
let inPermitUnload =
docShell.contentViewer && docShell.contentViewer.inPermitUnload;
let eventDetail = Cu.cloneInto(
{
tabPrompt: this.modalType != Ci.nsIPrompt.MODAL_TYPE_WINDOW,
inPermitUnload,
},
this._domWin
);
PromptUtils.fireDialogEvent(
this._domWin,
"DOMWillOpenModalDialog",
null,
eventDetail
);
let windowUtils =
Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT &&
this._domWin.windowUtils;
// Put content windows in the modal state while the prompt is open.
if (windowUtils) {
windowUtils.enterModalState();
}
// It is technically possible for multiple prompts to be sent from a single
// BrowsingContext. See bug 1266353. We use a randomly generated UUID to
// differentiate between the different prompts.
let id =
"id" +
Cc["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator)
.generateUUID()
.toString();
let closed = false;
args.promptPrincipal = this._domWin.document.nodePrincipal;
args.inPermitUnload = inPermitUnload;
args._remoteId = id;
let promise = actor.sendQuery("Prompt:Open", args);
promise.then(returnedArgs => {
// Copy the response from the closed prompt into our args, it will be
// read by our caller.
if (returnedArgs) {
actor
.sendQuery("Prompt:Open", args)
.then(returnedArgs => {
// Copy the response from the closed prompt into our args, it will be
// read by our caller.
if (!returnedArgs) {
return;
}
if (returnedArgs.promptAborted) {
throw Components.Exception(
"prompt aborted by user",
Cr.NS_ERROR_NOT_AVAILABLE
);
}
if (returnedArgs._remoteId !== id) {
return;
}
@ -625,82 +652,34 @@ function openRemotePrompt(domWin, args) {
for (let key in returnedArgs) {
args[key] = returnedArgs[key];
}
}
closed = true;
});
})
.finally(() => {
closed = true;
});
Services.tm.spinEventLoopUntilOrShutdown(() => closed);
} finally {
frameMM.removeEventListener("pagehide", onPageHide, true);
windowUtils.leaveModalState();
PromptUtils.fireDialogEvent(domWin, "DOMModalDialogClosed");
if (windowUtils) {
windowUtils.leaveModalState();
}
PromptUtils.fireDialogEvent(this._domWin, "DOMModalDialogClosed");
}
}
function ModalPrompter(domWin) {
this.domWin = domWin;
}
ModalPrompter.prototype = {
domWin: null,
/*
* Default to not using a tab-modal prompt, unless the caller opts in by
* QIing to nsIWritablePropertyBag and setting the value of this property
* to true.
*/
allowTabModal: false,
QueryInterface: ChromeUtils.generateQI([
Ci.nsIPrompt,
Ci.nsIAuthPrompt,
Ci.nsIAuthPrompt2,
Ci.nsIWritablePropertyBag2,
]),
/* ---------- internal methods ---------- */
openPrompt(args) {
// Check pref, if false/missing do not ever allow tab-modal prompts.
const prefName = "prompts.tab_modal.enabled";
let prefValue = false;
if (Services.prefs.getPrefType(prefName) == Services.prefs.PREF_BOOL) {
prefValue = Services.prefs.getBoolPref(prefName);
}
let allowTabModal = this.allowTabModal && prefValue;
if (allowTabModal && this.domWin) {
if (
Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT
) {
args.tabPrompt = true;
openRemotePrompt(this.domWin, args);
return;
}
let tabPrompt = PromptUtils.getTabModalPrompt(this.domWin);
if (tabPrompt) {
openTabPrompt(this.domWin, tabPrompt, args);
return;
}
}
// If we can't do a tab modal prompt, fallback to using a window-modal dialog.
if (
Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT
) {
args.tabPrompt = false;
openRemotePrompt(this.domWin, args);
return;
}
openWindowPrompt(parentWindow, args) {
const COMMON_DIALOG = "chrome://global/content/commonDialog.xhtml";
const SELECT_DIALOG = "chrome://global/content/selectDialog.xhtml";
let uri = args.promptType == "select" ? SELECT_DIALOG : COMMON_DIALOG;
let propBag = PromptUtils.objectToPropBag(args);
openModalWindow(this.domWin, uri, propBag);
Services.ww.openWindow(
parentWindow,
uri,
"_blank",
"centerscreen,chrome,modal,titlebar",
propBag
);
PromptUtils.propBagToObject(propBag, args);
},
}
/*
* ---------- interface disambiguation ----------
@ -716,7 +695,7 @@ ModalPrompter.prototype = {
return this.nsIPrompt_prompt.apply(this, arguments);
}
return this.nsIAuthPrompt_prompt.apply(this, arguments);
},
}
promptUsernameAndPassword() {
// Both have 6 args, so use types.
@ -724,7 +703,7 @@ ModalPrompter.prototype = {
return this.nsIPrompt_promptUsernameAndPassword.apply(this, arguments);
}
return this.nsIAuthPrompt_promptUsernameAndPassword.apply(this, arguments);
},
}
promptPassword() {
// Both have 5 args, so use types.
@ -732,7 +711,7 @@ ModalPrompter.prototype = {
return this.nsIPrompt_promptPassword.apply(this, arguments);
}
return this.nsIAuthPrompt_promptPassword.apply(this, arguments);
},
}
/* ---------- nsIPrompt ---------- */
@ -748,7 +727,7 @@ ModalPrompter.prototype = {
};
this.openPrompt(args);
},
}
alertCheck(title, text, checkLabel, checkValue) {
if (!title) {
@ -767,7 +746,7 @@ ModalPrompter.prototype = {
// Checkbox state always returned, even if cancel clicked.
checkValue.value = args.checked;
},
}
confirm(title, text) {
if (!title) {
@ -785,7 +764,7 @@ ModalPrompter.prototype = {
// Did user click Ok or Cancel?
return args.ok;
},
}
confirmCheck(title, text, checkLabel, checkValue) {
if (!title) {
@ -808,7 +787,7 @@ ModalPrompter.prototype = {
// Did user click Ok or Cancel?
return args.ok;
},
}
confirmEx(
title,
@ -862,7 +841,7 @@ ModalPrompter.prototype = {
// Get the number of the button the user clicked.
return args.buttonNumClicked;
},
}
nsIPrompt_prompt(title, text, value, checkLabel, checkValue) {
if (!title) {
@ -889,7 +868,7 @@ ModalPrompter.prototype = {
}
return ok;
},
}
nsIPrompt_promptUsernameAndPassword(
title,
@ -927,7 +906,7 @@ ModalPrompter.prototype = {
}
return ok;
},
}
nsIPrompt_promptPassword(title, text, pass, checkLabel, checkValue) {
if (!title) {
@ -956,7 +935,7 @@ ModalPrompter.prototype = {
}
return ok;
},
}
select(title, text, list, selected) {
if (!title) {
@ -981,7 +960,7 @@ ModalPrompter.prototype = {
}
return ok;
},
}
/* ---------- nsIAuthPrompt ---------- */
@ -998,7 +977,7 @@ ModalPrompter.prototype = {
result.value = defaultText;
}
return this.nsIPrompt_prompt(title, text, result, null, {});
},
}
nsIAuthPrompt_promptUsernameAndPassword(
title,
@ -1017,12 +996,12 @@ ModalPrompter.prototype = {
null,
{}
);
},
}
nsIAuthPrompt_promptPassword(title, text, passwordRealm, savePassword, pass) {
// The passwordRealm and savePassword args were ignored by nsPrompt.cpp
return this.nsIPrompt_promptPassword(title, text, pass, null, {});
},
}
/* ---------- nsIAuthPrompt2 ---------- */
@ -1058,7 +1037,7 @@ ModalPrompter.prototype = {
PromptUtils.setAuthInfo(authInfo, userParam.value, passParam.value);
}
return ok;
},
}
asyncPromptAuth(
channel,
@ -1076,20 +1055,33 @@ ModalPrompter.prototype = {
//
// Bug 565582 will change this.
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
}
/* ---------- nsIWritablePropertyBag2 ---------- */
// Only a partial implementation, for one specific use case...
setPropertyAsBool(name, value) {
if (name == "allowTabModal") {
this.allowTabModal = value;
// Legacy way to set modal type when prompting via nsIPrompt.
// Please prompt via nsIPromptService. This will be removed in the future.
setPropertyAsUint32(name, value) {
if (name == "modalType") {
this.modalType = value;
} else {
throw Cr.NS_ERROR_ILLEGAL_VALUE;
}
},
};
}
}
XPCOMUtils.defineLazyPreferenceGetter(
ModalPrompter,
"defaultModalType",
"prompts.defaultModalType",
Ci.nsIPrompt.MODAL_TYPE_WINDOW
);
XPCOMUtils.defineLazyPreferenceGetter(
ModalPrompter,
"tabModalEnabled",
"prompts.tab_modal.enabled",
true
);
function AuthPromptAdapterFactory() {}
AuthPromptAdapterFactory.prototype = {

Просмотреть файл

@ -11,6 +11,8 @@ interface nsIAuthInformation;
interface nsICancelable;
interface nsIChannel;
webidl BrowsingContext;
/**
* This is the interface to the embeddable prompt service; the service that
* implements nsIPrompt. Its interface is designed to be just nsIPrompt, each
@ -57,6 +59,10 @@ interface nsIPromptService : nsISupports
void alert(in mozIDOMWindowProxy aParent,
in wstring aDialogTitle,
in wstring aText);
void alertBC(in BrowsingContext aBrowsingContext,
in unsigned long modalType,
in wstring aDialogTitle,
in wstring aText);
/**
* Puts up an alert dialog with an OK button and a labeled checkbox.
@ -78,6 +84,12 @@ interface nsIPromptService : nsISupports
in wstring aText,
in wstring aCheckMsg,
inout boolean aCheckState);
void alertCheckBC(in BrowsingContext aBrowsingContext,
in unsigned long modalType,
in wstring aDialogTitle,
in wstring aText,
in wstring aCheckMsg,
inout boolean aCheckState);
/**
* Puts up a dialog with OK and Cancel buttons.
@ -94,6 +106,10 @@ interface nsIPromptService : nsISupports
boolean confirm(in mozIDOMWindowProxy aParent,
in wstring aDialogTitle,
in wstring aText);
boolean confirmBC(in BrowsingContext aBrowsingContext,
in unsigned long modalType,
in wstring aDialogTitle,
in wstring aText);
/**
* Puts up a dialog with OK and Cancel buttons and a labeled checkbox.
@ -117,6 +133,12 @@ interface nsIPromptService : nsISupports
in wstring aText,
in wstring aCheckMsg,
inout boolean aCheckState);
boolean confirmCheckBC(in BrowsingContext aBrowsingContext,
in unsigned long modalType,
in wstring aDialogTitle,
in wstring aText,
in wstring aCheckMsg,
inout boolean aCheckState);
/**
* Button Flags
@ -173,6 +195,10 @@ interface nsIPromptService : nsISupports
const unsigned long STD_YES_NO_BUTTONS = (BUTTON_TITLE_YES * BUTTON_POS_0) +
(BUTTON_TITLE_NO * BUTTON_POS_1);
// Indicates whether a prompt should be shown in-content, on tab level or as a separate window
const unsigned long MODAL_TYPE_CONTENT = 1;
const unsigned long MODAL_TYPE_TAB = 2;
const unsigned long MODAL_TYPE_WINDOW = 3;
/**
* Puts up a dialog with up to 3 buttons and an optional, labeled checkbox.
@ -226,6 +252,16 @@ interface nsIPromptService : nsISupports
in wstring aButton2Title,
in wstring aCheckMsg,
inout boolean aCheckState);
int32_t confirmExBC(in BrowsingContext aBrowsingContext,
in unsigned long modalType,
in wstring aDialogTitle,
in wstring aText,
in unsigned long aButtonFlags,
in wstring aButton0Title,
in wstring aButton1Title,
in wstring aButton2Title,
in wstring aCheckMsg,
inout boolean aCheckState);
/**
* Puts up a dialog with an edit field and an optional, labeled checkbox.
@ -255,6 +291,13 @@ interface nsIPromptService : nsISupports
inout wstring aValue,
in wstring aCheckMsg,
inout boolean aCheckState);
boolean promptBC(in BrowsingContext aBrowsingContext,
in unsigned long modalType,
in wstring aDialogTitle,
in wstring aText,
inout wstring aValue,
in wstring aCheckMsg,
inout boolean aCheckState);
/**
* Puts up a dialog with an edit field, a password field, and an optional,
@ -291,6 +334,14 @@ interface nsIPromptService : nsISupports
inout wstring aPassword,
in wstring aCheckMsg,
inout boolean aCheckState);
boolean promptUsernameAndPasswordBC(in BrowsingContext aBrowsingContext,
in unsigned long modalType,
in wstring aDialogTitle,
in wstring aText,
inout wstring aUsername,
inout wstring aPassword,
in wstring aCheckMsg,
inout boolean aCheckState);
/**
* Puts up a dialog with a password field and an optional, labeled checkbox.
@ -320,6 +371,13 @@ interface nsIPromptService : nsISupports
inout wstring aPassword,
in wstring aCheckMsg,
inout boolean aCheckState);
boolean promptPasswordBC(in BrowsingContext aBrowsingContext,
in unsigned long modalType,
in wstring aDialogTitle,
in wstring aText,
inout wstring aPassword,
in wstring aCheckMsg,
inout boolean aCheckState);
/**
* Puts up a dialog box which has a list box of strings from which the user
@ -344,6 +402,12 @@ interface nsIPromptService : nsISupports
in wstring aText,
in Array<AString> aSelectList,
out long aOutSelection);
boolean selectBC(in BrowsingContext aBrowsingContext,
in unsigned long modalType,
in wstring aDialogTitle,
in wstring aText,
in Array<AString> aSelectList,
out long aOutSelection);
// NOTE: These functions differ from their nsIAuthPrompt counterparts by
// having additional checkbox parameters
@ -359,6 +423,13 @@ interface nsIPromptService : nsISupports
in nsIAuthInformation authInfo,
in wstring checkboxLabel,
inout boolean checkValue);
boolean promptAuthBC(in BrowsingContext aBrowsingContext,
in unsigned long modalType,
in nsIChannel aChannel,
in uint32_t level,
in nsIAuthInformation authInfo,
in wstring checkboxLabel,
inout boolean checkValue);
nsICancelable asyncPromptAuth(in mozIDOMWindowProxy aParent,
in nsIChannel aChannel,
@ -368,4 +439,13 @@ interface nsIPromptService : nsISupports
in nsIAuthInformation authInfo,
in wstring checkboxLabel,
inout 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);
};

Просмотреть файл

@ -2017,7 +2017,25 @@
}
leaveModalState() {
this.sendMessageToActor("LeaveModalState", {}, "BrowserElement", "roots");
this.sendMessageToActor(
"LeaveModalState",
{ forceLeave: true },
"BrowserElement",
"roots"
);
}
/**
* Can be called for a window with or without modal state.
* If the window is not in modal state, this is a no-op.
*/
maybeLeaveModalState() {
this.sendMessageToActor(
"LeaveModalState",
{ forceLeave: false },
"BrowserElement",
"roots"
);
}
getDevicePermissionOrigins(key) {