Bug 1680637 - Add a dialog manager for content prompts in TabDialogBox r=jaws,Gijs
The TabDialogBox will manage two separate SubDialog managers at the tab and content level. Dialogs managed at the tab level will always be on top of content ones and should always receive focus first when tab switching or refocusing the window. Differential Revision: https://phabricator.services.mozilla.com/D100066
This commit is contained in:
@@ -29,6 +29,13 @@ XPCOMUtils.defineLazyPreferenceGetter(
|
||||
false
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"contentPromptSubDialog",
|
||||
"prompts.contentPromptSubDialog",
|
||||
false
|
||||
);
|
||||
|
||||
/**
|
||||
* @typedef {Object} Prompt
|
||||
* @property {Function} resolver
|
||||
@@ -118,13 +125,14 @@ class PromptParent extends JSWindowActorParent {
|
||||
switch (message.name) {
|
||||
case "Prompt:Open": {
|
||||
if (
|
||||
args.modalType === Ci.nsIPrompt.MODAL_TYPE_CONTENT ||
|
||||
(args.modalType === Ci.nsIPrompt.MODAL_TYPE_CONTENT &&
|
||||
!contentPromptSubDialog) ||
|
||||
(args.modalType === Ci.nsIPrompt.MODAL_TYPE_TAB &&
|
||||
!tabChromePromptSubDialog)
|
||||
) {
|
||||
return this.openContentPrompt(args, id);
|
||||
}
|
||||
return this.openChromePrompt(args);
|
||||
return this.openPromptWithTabDialogBox(args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,18 +233,19 @@ class PromptParent extends JSWindowActorParent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a window prompt for a BrowsingContext, and puts the associated
|
||||
* browser in the modal state until the prompt is closed.
|
||||
* Opens either a window prompt or TabDialogBox at the content or tab level
|
||||
* for a BrowsingContext, and puts the associated browser in the modal state
|
||||
* until the prompt is closed.
|
||||
*
|
||||
* @param {Object} args
|
||||
* The arguments passed up from the BrowsingContext to be passed
|
||||
* directly to the modal window.
|
||||
* directly to the modal prompt.
|
||||
* @return {Promise}
|
||||
* Resolves when the window prompt is dismissed.
|
||||
* Resolves when the modal prompt is dismissed.
|
||||
* @resolves {Object}
|
||||
* The arguments returned from the window prompt.
|
||||
* The arguments returned from the modal prompt.
|
||||
*/
|
||||
async openChromePrompt(args) {
|
||||
async openPromptWithTabDialogBox(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;
|
||||
@@ -271,15 +280,29 @@ class PromptParent extends JSWindowActorParent {
|
||||
|
||||
let bag = PromptUtils.objectToPropBag(args);
|
||||
|
||||
if (args.modalType === Services.prompt.MODAL_TYPE_TAB) {
|
||||
if (
|
||||
args.modalType === Services.prompt.MODAL_TYPE_TAB ||
|
||||
args.modalType === Services.prompt.MODAL_TYPE_CONTENT
|
||||
) {
|
||||
if (!browser) {
|
||||
throw new Error("Cannot tab-prompt without a browser!");
|
||||
let modal_type =
|
||||
args.modalType === Services.prompt.MODAL_TYPE_TAB
|
||||
? "tab"
|
||||
: "content";
|
||||
throw new Error(`Cannot ${modal_type}-prompt without a browser!`);
|
||||
}
|
||||
// Tab
|
||||
// Tab or content level prompt
|
||||
let dialogBox = win.gBrowser.getTabDialogBox(browser);
|
||||
await dialogBox.open(uri, { features: "resizable=no" }, bag);
|
||||
await dialogBox.open(
|
||||
uri,
|
||||
{
|
||||
features: "resizable=no",
|
||||
modalType: args.modalType,
|
||||
},
|
||||
bag
|
||||
);
|
||||
} else {
|
||||
// Window
|
||||
// Window prompt
|
||||
Services.ww.openWindow(
|
||||
win,
|
||||
uri,
|
||||
|
||||
@@ -1353,6 +1353,14 @@ pref("browser.partnerlink.campaign.topsites", "amzn_2020_a1");
|
||||
// TabModalPromptBox (false).
|
||||
pref("prompts.tabChromePromptSubDialog", true);
|
||||
|
||||
// Whether to show the dialogs opened at the content level, such as
|
||||
// alert() or prompt(), using a SubDialogManager in the TabDialogBox.
|
||||
#ifdef EARLY_BETA_OR_EARLIER
|
||||
pref("prompts.contentPromptSubDialog", true);
|
||||
#else
|
||||
pref("prompts.contentPromptSubDialog", false);
|
||||
#endif
|
||||
|
||||
// Activates preloading of the new tab url.
|
||||
pref("browser.newtab.preload", true);
|
||||
|
||||
|
||||
@@ -8907,6 +8907,8 @@ class TabDialogBox {
|
||||
// Create parent element for dialogs
|
||||
let template = document.getElementById("dialogStackTemplate");
|
||||
let dialogStack = template.content.cloneNode(true).firstElementChild;
|
||||
dialogStack.classList.add("tab-prompt-dialog");
|
||||
|
||||
this.browser.parentNode.insertBefore(
|
||||
dialogStack,
|
||||
this.browser.nextElementSibling
|
||||
@@ -8927,7 +8929,7 @@ class TabDialogBox {
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a dialog on tab level.
|
||||
* Open a dialog on tab or content level.
|
||||
* @param {String} aURL - URL of the dialog to load in the tab box.
|
||||
* @param {Object} [aOptions]
|
||||
* @param {String} [aOptions.features] - Comma separated list of window
|
||||
@@ -8940,6 +8942,8 @@ class TabDialogBox {
|
||||
* @param {Boolean} [aOptions.keepOpenSameOriginNav] - By default dialogs are
|
||||
* aborted on any navigation.
|
||||
* Set to true to keep the dialog open for same origin navigation.
|
||||
* @param {Number} [aOptions.modalType] - The modal type to create the dialog for.
|
||||
* By default, we show the dialog for tab prompts.
|
||||
* @returns {Promise} - Resolves once the dialog has been closed.
|
||||
*/
|
||||
open(
|
||||
@@ -8949,22 +8953,32 @@ class TabDialogBox {
|
||||
allowDuplicateDialogs = true,
|
||||
sizeTo,
|
||||
keepOpenSameOriginNav,
|
||||
modalType = null,
|
||||
} = {},
|
||||
...aParams
|
||||
) {
|
||||
return new Promise(resolve => {
|
||||
if (!this._dialogManager.hasDialogs) {
|
||||
// Get the dialog manager to open the prompt with.
|
||||
let dialogManager =
|
||||
modalType === Ci.nsIPrompt.MODAL_TYPE_CONTENT
|
||||
? this.getContentDialogManager()
|
||||
: this._dialogManager;
|
||||
let hasDialogs =
|
||||
this._dialogManager.hasDialogs ||
|
||||
this._contentDialogManager?.hasDialogs;
|
||||
|
||||
if (!hasDialogs) {
|
||||
this._onFirstDialogOpen();
|
||||
}
|
||||
|
||||
let closingCallback = () => {
|
||||
if (!this._dialogManager.hasDialogs) {
|
||||
if (!hasDialogs) {
|
||||
this._onLastDialogClose();
|
||||
}
|
||||
};
|
||||
|
||||
// Open dialog and resolve once it has been closed
|
||||
let dialog = this._dialogManager.open(
|
||||
let dialog = dialogManager.open(
|
||||
aURL,
|
||||
{
|
||||
features,
|
||||
@@ -9009,6 +9023,29 @@ class TabDialogBox {
|
||||
this.tab?.removeEventListener("TabClose", this);
|
||||
}
|
||||
|
||||
_buildContentPromptDialog() {
|
||||
let template = document.getElementById("dialogStackTemplate");
|
||||
let contentDialogStack = template.content.cloneNode(true).firstElementChild;
|
||||
contentDialogStack.classList.add("content-prompt-dialog");
|
||||
|
||||
// Create a dialog manager for content prompts.
|
||||
let tabPromptDialog = this.browser.parentNode.querySelector(
|
||||
".tab-prompt-dialog"
|
||||
);
|
||||
this.browser.parentNode.insertBefore(contentDialogStack, tabPromptDialog);
|
||||
|
||||
let contentDialogTemplate = contentDialogStack.firstElementChild;
|
||||
this._contentDialogManager = new SubDialogManager({
|
||||
dialogStack: contentDialogStack,
|
||||
dialogTemplate: contentDialogTemplate,
|
||||
orderType: SubDialogManager.ORDER_QUEUE,
|
||||
allowDuplicateDialogs: true,
|
||||
dialogOptions: {
|
||||
consumeOutsideClicks: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
if (event.type !== "TabClose") {
|
||||
return;
|
||||
@@ -9018,10 +9055,16 @@ class TabDialogBox {
|
||||
|
||||
abortAllDialogs() {
|
||||
this._dialogManager.abortDialogs();
|
||||
this._contentDialogManager?.abortDialogs();
|
||||
}
|
||||
|
||||
focus() {
|
||||
// Prioritize focusing the dialog manager for tab prompts
|
||||
if (this._dialogManager._dialogs.length) {
|
||||
this._dialogManager.focusTopDialog();
|
||||
return;
|
||||
}
|
||||
this._contentDialogManager?.focusTopDialog();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -9052,6 +9095,7 @@ class TabDialogBox {
|
||||
this._lastPrincipal = this.browser.contentPrincipal;
|
||||
|
||||
this._dialogManager.abortDialogs(filterFn);
|
||||
this._contentDialogManager?.abortDialogs(filterFn);
|
||||
}
|
||||
|
||||
get tab() {
|
||||
@@ -9069,6 +9113,13 @@ class TabDialogBox {
|
||||
getManager() {
|
||||
return this._dialogManager;
|
||||
}
|
||||
|
||||
getContentDialogManager() {
|
||||
if (!this._contentDialogManager) {
|
||||
this._buildContentPromptDialog();
|
||||
}
|
||||
return this._contentDialogManager;
|
||||
}
|
||||
}
|
||||
|
||||
TabDialogBox.prototype.QueryInterface = ChromeUtils.generateQI([
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
support-files =
|
||||
subdialog.xhtml
|
||||
|
||||
[browser_tabdialogbox_content_prompts.js]
|
||||
[browser_tabdialogbox_navigation.js]
|
||||
[browser_tabdialogbox_tab_switch_focus.js]
|
||||
[browser_subdialog_esc.js]
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const CONTENT_PROMPT_PREF = "prompts.contentPromptSubDialog";
|
||||
const TEST_ROOT_CHROME = getRootDirectory(gTestPath);
|
||||
const TEST_DIALOG_PATH = TEST_ROOT_CHROME + "subdialog.xhtml";
|
||||
|
||||
/**
|
||||
* Test that a manager for content prompts is added to tab dialog box.
|
||||
*/
|
||||
add_task(async function test_tabdialog_content_prompts() {
|
||||
await BrowserTestUtils.withNewTab("http://example.com", async function(
|
||||
browser
|
||||
) {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[CONTENT_PROMPT_PREF, true]],
|
||||
});
|
||||
|
||||
info("Open a tab prompt.");
|
||||
let dialogBox = gBrowser.getTabDialogBox(browser);
|
||||
dialogBox.open(TEST_DIALOG_PATH);
|
||||
|
||||
info("Check the content prompt dialog is only created when needed.");
|
||||
let contentPromptDialog = document.querySelector(".content-prompt-dialog");
|
||||
ok(!contentPromptDialog, "Content prompt dialog should not be created.");
|
||||
|
||||
info("Open a content prompt");
|
||||
dialogBox.open(TEST_DIALOG_PATH, {
|
||||
modalType: Ci.nsIPrompt.MODAL_TYPE_CONTENT,
|
||||
});
|
||||
|
||||
contentPromptDialog = document.querySelector(".content-prompt-dialog");
|
||||
ok(contentPromptDialog, "Content prompt dialog should be created.");
|
||||
let contentPromptManager = dialogBox.getContentDialogManager();
|
||||
|
||||
is(
|
||||
contentPromptManager._dialogs.length,
|
||||
1,
|
||||
"Content prompt manager should have 1 dialog box."
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user