summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/tabdialogs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /browser/base/content/test/tabdialogs
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/base/content/test/tabdialogs')
-rw-r--r--browser/base/content/test/tabdialogs/browser.toml20
-rw-r--r--browser/base/content/test/tabdialogs/browser_multiple_dialog_navigation.js64
-rw-r--r--browser/base/content/test/tabdialogs/browser_subdialog_esc.js122
-rw-r--r--browser/base/content/test/tabdialogs/browser_tabdialogbox_content_prompts.js179
-rw-r--r--browser/base/content/test/tabdialogs/browser_tabdialogbox_focus.js212
-rw-r--r--browser/base/content/test/tabdialogs/browser_tabdialogbox_navigation.js174
-rw-r--r--browser/base/content/test/tabdialogs/loadDelayedReply.sjs22
-rw-r--r--browser/base/content/test/tabdialogs/subdialog.xhtml46
-rw-r--r--browser/base/content/test/tabdialogs/test_page.html10
9 files changed, 849 insertions, 0 deletions
diff --git a/browser/base/content/test/tabdialogs/browser.toml b/browser/base/content/test/tabdialogs/browser.toml
new file mode 100644
index 0000000000..2029209cbd
--- /dev/null
+++ b/browser/base/content/test/tabdialogs/browser.toml
@@ -0,0 +1,20 @@
+[DEFAULT]
+support-files = ["subdialog.xhtml"]
+
+["browser_multiple_dialog_navigation.js"]
+
+["browser_subdialog_esc.js"]
+support-files = ["loadDelayedReply.sjs"]
+
+["browser_tabdialogbox_content_prompts.js"]
+skip-if = [
+ "apple_silicon && !debug", # Bug 1786514
+ "apple_catalina && !debug", # Bug 1786514
+]
+support-files = ["test_page.html"]
+
+["browser_tabdialogbox_focus.js"]
+https_first_disabled = true
+
+["browser_tabdialogbox_navigation.js"]
+https_first_disabled = true
diff --git a/browser/base/content/test/tabdialogs/browser_multiple_dialog_navigation.js b/browser/base/content/test/tabdialogs/browser_multiple_dialog_navigation.js
new file mode 100644
index 0000000000..4d41a76482
--- /dev/null
+++ b/browser/base/content/test/tabdialogs/browser_multiple_dialog_navigation.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_multiple_dialog_navigation() {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com/gone",
+ async browser => {
+ let firstDialogPromise = BrowserTestUtils.promiseAlertDialogOpen();
+ // We're gonna queue up some dialogs, and navigate. The tasks queueing the dialog
+ // are going to get aborted when the navigation happened, but that's OK because by
+ // that time they will have done their job. Detect and swallow that specific
+ // exception:
+ let navigationCatcher = e => {
+ if (e.name == "AbortError" && e.message.includes("destroyed before")) {
+ return;
+ }
+ throw e;
+ };
+ // Queue up 2 dialogs
+ let firstTask = SpecialPowers.spawn(browser, [], async function () {
+ content.eval(`alert('hi');`);
+ }).catch(navigationCatcher);
+ let secondTask = SpecialPowers.spawn(browser, [], async function () {
+ content.eval(`alert('hi again');`);
+ }).catch(navigationCatcher);
+ info("Waiting for first dialog.");
+ let dialogWin = await firstDialogPromise;
+
+ let secondDialogPromise = BrowserTestUtils.promiseAlertDialogOpen();
+ dialogWin.document
+ .getElementById("commonDialog")
+ .getButton("accept")
+ .click();
+ dialogWin = null;
+
+ info("Wait for second dialog to appear.");
+ let secondDialogWin = await secondDialogPromise;
+ let closedPromise = BrowserTestUtils.waitForEvent(
+ secondDialogWin,
+ "unload"
+ );
+ let loadedOtherPage = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ "https://example.org/gone"
+ );
+ BrowserTestUtils.startLoadingURIString(
+ browser,
+ "https://example.org/gone"
+ );
+ info("Waiting for the next page to load.");
+ await loadedOtherPage;
+ info(
+ "Waiting for second dialog to close. If we time out here that's a bug!"
+ );
+ await closedPromise;
+ is(secondDialogWin.closed, true, "Should have closed second dialog.");
+ info("Ensure content tasks are done");
+ await secondTask;
+ await firstTask;
+ }
+ );
+});
diff --git a/browser/base/content/test/tabdialogs/browser_subdialog_esc.js b/browser/base/content/test/tabdialogs/browser_subdialog_esc.js
new file mode 100644
index 0000000000..20d8496681
--- /dev/null
+++ b/browser/base/content/test/tabdialogs/browser_subdialog_esc.js
@@ -0,0 +1,122 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_ROOT_CHROME = getRootDirectory(gTestPath);
+const TEST_DIALOG_PATH = TEST_ROOT_CHROME + "subdialog.xhtml";
+
+const WEB_ROOT = TEST_ROOT_CHROME.replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com"
+);
+const TEST_LOAD_PAGE = WEB_ROOT + "loadDelayedReply.sjs";
+
+/**
+ * Tests that ESC on a SubDialog does not cancel ongoing loads in the parent.
+ */
+add_task(async function test_subdialog_esc_does_not_cancel_load() {
+ await BrowserTestUtils.withNewTab(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com",
+ async function (browser) {
+ // Start loading a page
+ let loadStartedPromise = BrowserTestUtils.startLoadingURIString(
+ browser,
+ TEST_LOAD_PAGE
+ );
+ let loadedPromise = BrowserTestUtils.browserLoaded(browser);
+ await loadStartedPromise;
+
+ // Open a dialog
+ let dialogBox = gBrowser.getTabDialogBox(browser);
+ let dialogClose = dialogBox.open(TEST_DIALOG_PATH, {
+ keepOpenSameOriginNav: true,
+ }).closedPromise;
+
+ let dialogs = dialogBox.getTabDialogManager()._dialogs;
+
+ is(dialogs.length, 1, "Dialog manager has a dialog.");
+
+ info("Waiting for dialogs to open.");
+ await dialogs[0]._dialogReady;
+
+ // Close the dialog with esc key
+ EventUtils.synthesizeKey("KEY_Escape");
+
+ info("Waiting for dialog to close.");
+ await dialogClose;
+
+ info("Triggering load complete");
+ fetch(TEST_LOAD_PAGE, {
+ method: "POST",
+ });
+
+ // Load must complete
+ info("Waiting for load to complete");
+ await loadedPromise;
+ ok(true, "Load completed");
+ }
+ );
+});
+
+/**
+ * Tests that ESC on a SubDialog with an open dropdown doesn't close the dialog.
+ */
+add_task(async function test_subdialog_esc_on_dropdown_does_not_close_dialog() {
+ await BrowserTestUtils.withNewTab(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com",
+ async function (browser) {
+ // Open the test dialog
+ let dialogBox = gBrowser.getTabDialogBox(browser);
+ let dialogClose = dialogBox.open(TEST_DIALOG_PATH, {
+ keepOpenSameOriginNav: true,
+ }).closedPromise;
+
+ let dialogs = dialogBox.getTabDialogManager()._dialogs;
+
+ is(dialogs.length, 1, "Dialog manager has a dialog.");
+
+ let dialog = dialogs[0];
+
+ info("Waiting for dialog to open.");
+ await dialog._dialogReady;
+
+ // Open dropdown
+ let select = dialog._frame.contentDocument.getElementById("select");
+ let shownPromise = BrowserTestUtils.waitForSelectPopupShown(window);
+
+ info("Opening dropdown");
+ select.focus();
+ EventUtils.synthesizeKey("VK_SPACE", {}, dialog._frame.contentWindow);
+
+ let selectPopup = await shownPromise;
+
+ let hiddenPromise = BrowserTestUtils.waitForEvent(
+ selectPopup,
+ "popuphiding",
+ true
+ );
+
+ // Race dropdown closing vs SubDialog close
+ let race = Promise.race([
+ hiddenPromise.then(() => true),
+ dialogClose.then(() => false),
+ ]);
+
+ // Close the dropdown with esc key
+ info("Hitting escape key.");
+ await EventUtils.synthesizeKey("KEY_Escape");
+
+ let result = await race;
+ ok(result, "Select closed first");
+
+ await new Promise(resolve => executeSoon(resolve));
+
+ ok(!dialog._isClosing, "Dialog is not closing");
+ ok(dialog._openedURL, "Dialog is open");
+ }
+ );
+});
diff --git a/browser/base/content/test/tabdialogs/browser_tabdialogbox_content_prompts.js b/browser/base/content/test/tabdialogs/browser_tabdialogbox_content_prompts.js
new file mode 100644
index 0000000000..50b94e1a36
--- /dev/null
+++ b/browser/base/content/test/tabdialogs/browser_tabdialogbox_content_prompts.js
@@ -0,0 +1,179 @@
+/* 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";
+
+const TEST_DATA_URI = "data:text/html,<body onload='alert(1)'>";
+const TEST_EXTENSION_DATA = {
+ background() {
+ // eslint-disable-next-line no-undef
+ browser.test.sendMessage("url", browser.runtime.getURL("alert.html"));
+ },
+ manifest: {
+ name: "Test Extension",
+ },
+ files: {
+ "alert.html": `<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>TabDialogBox Content Modal Test page</title>
+ <script src="./alert.js"></script>
+ </head>
+ <body>
+ <h1>TabDialogBox Content Modal</h1>
+ </body>
+</html>`,
+ "alert.js": `window.addEventListener("load", () => alert("Hi"));`,
+ },
+};
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+const TEST_ORIGIN = "http://example.com";
+const TEST_PAGE =
+ TEST_ROOT_CHROME.replace("chrome://mochitests/content", TEST_ORIGIN) +
+ "test_page.html";
+
+var commonDialogsBundle = Services.strings.createBundle(
+ "chrome://global/locale/commonDialogs.properties"
+);
+
+// Setup.
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [[CONTENT_PROMPT_PREF, true]],
+ });
+});
+
+/**
+ * Test that a manager for content prompts is added to tab dialog box.
+ */
+add_task(async function test_tabdialog_content_prompts() {
+ await BrowserTestUtils.withNewTab(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com",
+ async function (browser) {
+ 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."
+ );
+ }
+ );
+});
+
+/**
+ * Test origin text for a null principal.
+ */
+add_task(async function test_tabdialog_null_principal_title() {
+ let dialogShown = BrowserTestUtils.waitForEvent(
+ gBrowser,
+ "DOMWillOpenModalDialog"
+ );
+
+ await BrowserTestUtils.withNewTab(TEST_DATA_URI, async function (browser) {
+ info("Waiting for dialog to open.");
+ await dialogShown;
+ await checkOriginText(browser);
+ });
+});
+
+/**
+ * Test origin text for an extension page.
+ */
+add_task(async function test_tabdialog_extension_title() {
+ let extension = ExtensionTestUtils.loadExtension(TEST_EXTENSION_DATA);
+
+ await extension.startup();
+ let url = await extension.awaitMessage("url");
+ let dialogShown = BrowserTestUtils.waitForEvent(
+ gBrowser,
+ "DOMWillOpenModalDialog"
+ );
+
+ await BrowserTestUtils.withNewTab(url, async function (browser) {
+ info("Waiting for dialog to open.");
+ await dialogShown;
+ await checkOriginText(browser, "Test Extension");
+ });
+
+ await extension.unload();
+});
+
+/**
+ * Test origin text for a regular page.
+ */
+add_task(async function test_tabdialog_page_title() {
+ let dialogShown = BrowserTestUtils.waitForEvent(
+ gBrowser,
+ "DOMWillOpenModalDialog"
+ );
+
+ await BrowserTestUtils.withNewTab(TEST_PAGE, async function (browser) {
+ info("Waiting for dialog to open.");
+ await dialogShown;
+ await checkOriginText(browser, TEST_ORIGIN);
+ });
+});
+
+/**
+ * Test helper for checking the origin header of a dialog.
+ *
+ * @param {Object} browser
+ * The browser the dialog was opened from.
+ * @param {String|null} origin
+ * The page origin that should be displayed in the header, if any.
+ */
+async function checkOriginText(browser, origin = null) {
+ info("Check the title is visible.");
+ let dialogBox = gBrowser.getTabDialogBox(browser);
+ let contentPromptManager = dialogBox.getContentDialogManager();
+ let dialog = contentPromptManager._dialogs[0];
+
+ info("Waiting for dialog frame to be ready.");
+ await dialog._dialogReady;
+
+ let dialogDoc = dialog._frame.contentWindow.document;
+ let titleSelector = "#titleText";
+ let infoTitle = dialogDoc.querySelector(titleSelector);
+ ok(BrowserTestUtils.isVisible(infoTitle), "Title text is visible");
+
+ info("Check the displayed origin text is correct.");
+ if (origin) {
+ let host = origin;
+ try {
+ host = new URL(origin).host;
+ } catch (ex) {
+ /* will fail for the extension case. */
+ }
+ is(infoTitle.textContent, host, "Origin should be in header.");
+ } else {
+ is(
+ infoTitle.dataset.l10nId,
+ "common-dialog-title-null",
+ "Null principal string should be in header."
+ );
+ }
+}
diff --git a/browser/base/content/test/tabdialogs/browser_tabdialogbox_focus.js b/browser/base/content/test/tabdialogs/browser_tabdialogbox_focus.js
new file mode 100644
index 0000000000..db3ac81aee
--- /dev/null
+++ b/browser/base/content/test/tabdialogs/browser_tabdialogbox_focus.js
@@ -0,0 +1,212 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_ROOT_CHROME = getRootDirectory(gTestPath);
+const TEST_DIALOG_PATH = TEST_ROOT_CHROME + "subdialog.xhtml";
+
+/**
+ * Tests that tab dialogs 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,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com",
+ true
+ )
+ );
+ }
+
+ // Wait for tabs to be ready
+ let tabs = await Promise.all(tabPromises);
+
+ // Open subdialog in first two tabs
+ let dialogs = [];
+ for (let i = 0; i < 2; i += 1) {
+ let dialogBox = gBrowser.getTabDialogBox(tabs[i].linkedBrowser);
+ dialogBox.open(TEST_DIALOG_PATH);
+ dialogs.push(dialogBox.getTabDialogManager()._topDialog);
+ }
+
+ // Wait for dialogs to be ready
+ await Promise.all([dialogs[0]._dialogReady, dialogs[1]._dialogReady]);
+
+ // Switch to first tab which has dialog
+ await BrowserTestUtils.switchTab(gBrowser, tabs[0]);
+
+ // The textbox in the dialogs content window should be focused
+ let dialogTextbox =
+ dialogs[0]._frame.contentDocument.querySelector("#textbox");
+ is(Services.focus.focusedElement, dialogTextbox, "Dialog textbox is focused");
+
+ // Switch to second tab which has dialog
+ await BrowserTestUtils.switchTab(gBrowser, tabs[1]);
+
+ // The textbox in the dialogs content window should be focused
+ let dialogTextbox2 =
+ dialogs[1]._frame.contentDocument.querySelector("#textbox");
+ is(
+ Services.focus.focusedElement,
+ dialogTextbox2,
+ "Dialog2 textbox is focused"
+ );
+
+ // Switch to third tab which does not have a dialog
+ await BrowserTestUtils.switchTab(gBrowser, tabs[2]);
+
+ // Test that content is focused
+ is(
+ Services.focus.focusedElement,
+ tabs[2].linkedBrowser,
+ "Top level browser is focused"
+ );
+
+ // Cleanup
+ tabs.forEach(tab => {
+ BrowserTestUtils.removeTab(tab);
+ });
+});
+
+/**
+ * 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,
+ }
+ ).closedPromise;
+ let dialogBClose = dialogBox.open(TEST_DIALOG_PATH).closedPromise;
+ let dialogCClose = dialogBox.open(
+ TEST_DIALOG_PATH,
+ {},
+ {
+ testCustomFocusHandler: true,
+ }
+ ).closedPromise;
+
+ let dialogs = dialogBox._tabDialogManager._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._tabDialogManager._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.
+ */
+add_task(async function test_tabdialogbox_tab_switch_hidden() {
+ // Open 2 tabs
+ let tabPromises = [];
+ for (let i = 0; i < 2; i += 1) {
+ tabPromises.push(
+ BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com",
+ true
+ )
+ );
+ }
+
+ // Wait for tabs to be ready
+ let tabs = await Promise.all(tabPromises);
+
+ // Open subdialog in tabs
+ let dialogs = [];
+ let dialogBox, dialogBoxManager, browser;
+ for (let i = 0; i < 2; i += 1) {
+ dialogBox = gBrowser.getTabDialogBox(tabs[i].linkedBrowser);
+ browser = tabs[i].linkedBrowser;
+ dialogBox.open(TEST_DIALOG_PATH);
+ dialogBoxManager = dialogBox.getTabDialogManager();
+ dialogs.push(dialogBoxManager._topDialog);
+ }
+
+ // Wait for dialogs to be ready
+ await Promise.all([dialogs[0]._dialogReady, dialogs[1]._dialogReady]);
+
+ // Hide the top dialog
+ dialogBoxManager.hideDialog(browser);
+
+ ok(
+ BrowserTestUtils.isHidden(dialogBoxManager._dialogStack),
+ "Dialog stack is hidden"
+ );
+
+ // Switch to first tab
+ await BrowserTestUtils.switchTab(gBrowser, tabs[0]);
+
+ // Check the dialog stack is showing in first tab
+ dialogBoxManager = gBrowser
+ .getTabDialogBox(tabs[0].linkedBrowser)
+ .getTabDialogManager();
+ is(dialogBoxManager._dialogStack.hidden, false, "Dialog stack is showing");
+
+ // Cleanup
+ tabs.forEach(tab => {
+ BrowserTestUtils.removeTab(tab);
+ });
+});
diff --git a/browser/base/content/test/tabdialogs/browser_tabdialogbox_navigation.js b/browser/base/content/test/tabdialogs/browser_tabdialogbox_navigation.js
new file mode 100644
index 0000000000..fd75ed4dcf
--- /dev/null
+++ b/browser/base/content/test/tabdialogs/browser_tabdialogbox_navigation.js
@@ -0,0 +1,174 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_ROOT_CHROME = getRootDirectory(gTestPath);
+const TEST_DIALOG_PATH = TEST_ROOT_CHROME + "subdialog.xhtml";
+
+/**
+ * Tests that all tab dialogs are closed on navigation.
+ */
+add_task(async function test_tabdialogbox_multiple_close_on_nav() {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com",
+ async function (browser) {
+ // Open two dialogs and wait for them to be ready.
+ let dialogBox = gBrowser.getTabDialogBox(browser);
+ let closedPromises = [
+ dialogBox.open(TEST_DIALOG_PATH).closedPromise,
+ dialogBox.open(TEST_DIALOG_PATH).closedPromise,
+ ];
+
+ let dialogs = dialogBox.getTabDialogManager()._dialogs;
+
+ is(dialogs.length, 2, "Dialog manager has two dialogs.");
+
+ info("Waiting for dialogs to open.");
+ await Promise.all(dialogs.map(dialog => dialog._dialogReady));
+
+ // Navigate to a different page
+ BrowserTestUtils.startLoadingURIString(browser, "https://example.org");
+
+ info("Waiting for dialogs to close.");
+ await closedPromises;
+
+ ok(true, "All open dialogs should close on navigation");
+ }
+ );
+});
+
+/**
+ * Tests dialog close on navigation triggered by web content.
+ */
+add_task(async function test_tabdialogbox_close_on_content_nav() {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com",
+ async function (browser) {
+ // Open a dialog and wait for it to be ready
+ let dialogBox = gBrowser.getTabDialogBox(browser);
+ let { closedPromise } = dialogBox.open(TEST_DIALOG_PATH);
+
+ let dialog = dialogBox.getTabDialogManager()._topDialog;
+
+ is(
+ dialogBox.getTabDialogManager()._dialogs.length,
+ 1,
+ "Dialog manager has one dialog."
+ );
+
+ info("Waiting for dialog to open.");
+ await dialog._dialogReady;
+
+ // Trigger a same origin navigation by the content
+ await ContentTask.spawn(browser, {}, () => {
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ content.location = "http://example.com/1";
+ });
+
+ info("Waiting for dialog to close.");
+ await closedPromise;
+ ok(
+ true,
+ "Dialog should close for same origin navigation by the content."
+ );
+
+ // Open a new dialog
+ closedPromise = dialogBox.open(TEST_DIALOG_PATH, {
+ keepOpenSameOriginNav: true,
+ }).closedPromise;
+
+ info("Waiting for dialog to open.");
+ await dialog._dialogReady;
+
+ SimpleTest.requestFlakyTimeout("Waiting to ensure dialog does not close");
+ let race = Promise.race([
+ closedPromise,
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ new Promise(resolve => setTimeout(() => resolve("success"), 1000)),
+ ]);
+
+ // Trigger a same origin navigation by the content
+ await ContentTask.spawn(browser, {}, () => {
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ content.location = "http://example.com/test";
+ });
+
+ is(
+ await race,
+ "success",
+ "Dialog should not close for same origin navigation by the content."
+ );
+
+ // Trigger a cross origin navigation by the content
+ await ContentTask.spawn(browser, {}, () => {
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ content.location = "http://example.org/test2";
+ });
+
+ info("Waiting for dialog to close");
+ await closedPromise;
+
+ ok(
+ true,
+ "Dialog should close for cross origin navigation by the content."
+ );
+ }
+ );
+});
+
+/**
+ * Hides a dialog stack and tests that behavior doesn't change. Ensures
+ * navigation triggered by web content still closes all dialogs.
+ */
+add_task(async function test_tabdialogbox_hide() {
+ await BrowserTestUtils.withNewTab(
+ "https://example.com",
+ async function (browser) {
+ // Open a dialog and wait for it to be ready
+ let dialogBox = gBrowser.getTabDialogBox(browser);
+ let dialogBoxManager = dialogBox.getTabDialogManager();
+ let closedPromises = [
+ dialogBox.open(TEST_DIALOG_PATH).closedPromise,
+ dialogBox.open(TEST_DIALOG_PATH).closedPromise,
+ ];
+
+ let dialogs = dialogBox.getTabDialogManager()._dialogs;
+
+ is(
+ dialogBox.getTabDialogManager()._dialogs.length,
+ 2,
+ "Dialog manager has two dialogs."
+ );
+
+ info("Waiting for dialogs to open.");
+ await Promise.all(dialogs.map(dialog => dialog._dialogReady));
+
+ ok(
+ !BrowserTestUtils.isHidden(dialogBoxManager._dialogStack),
+ "Dialog stack is showing"
+ );
+
+ dialogBoxManager.hideDialog(browser);
+
+ is(
+ dialogBoxManager._dialogs.length,
+ 2,
+ "Dialog manager still has two dialogs."
+ );
+
+ ok(
+ BrowserTestUtils.isHidden(dialogBoxManager._dialogStack),
+ "Dialog stack is hidden"
+ );
+
+ // Navigate to a different page
+ BrowserTestUtils.startLoadingURIString(browser, "https://example.org");
+
+ info("Waiting for dialogs to close.");
+ await closedPromises;
+
+ ok(true, "All open dialogs should still close on navigation");
+ }
+ );
+});
diff --git a/browser/base/content/test/tabdialogs/loadDelayedReply.sjs b/browser/base/content/test/tabdialogs/loadDelayedReply.sjs
new file mode 100644
index 0000000000..cf046967bf
--- /dev/null
+++ b/browser/base/content/test/tabdialogs/loadDelayedReply.sjs
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(request, response) {
+ response.processAsync();
+ if (request.method === "POST") {
+ getObjectState("wait", queryResponse => {
+ if (!queryResponse) {
+ throw new Error("Wrong call order");
+ }
+ queryResponse.finish();
+
+ response.setStatusLine(request.httpVersion, 200);
+ response.write("OK");
+ response.finish();
+ });
+ return;
+ }
+ response.setStatusLine(request.httpVersion, 200);
+ response.write("OK");
+ setObjectState("wait", response);
+}
diff --git a/browser/base/content/test/tabdialogs/subdialog.xhtml b/browser/base/content/test/tabdialogs/subdialog.xhtml
new file mode 100644
index 0000000000..03b2b76d49
--- /dev/null
+++ b/browser/base/content/test/tabdialogs/subdialog.xhtml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Sample sub-dialog">
+<dialog id="subDialog">
+ <script>
+ document.addEventListener("dialogaccept", acceptSubdialog);
+ 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>
+
+ <html:input id="textbox" value="Default text" />
+
+ <html:select id="select">
+ <html:option>Foo</html:option>
+ <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" />
+
+</dialog>
+</window>
diff --git a/browser/base/content/test/tabdialogs/test_page.html b/browser/base/content/test/tabdialogs/test_page.html
new file mode 100644
index 0000000000..c5f17062cf
--- /dev/null
+++ b/browser/base/content/test/tabdialogs/test_page.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>TabDialogBox Content Modal Test page</title>
+</head>
+<body onload='alert("Hi");'>
+ <h1>TabDialogBox Content Modal</h1>
+</body>
+</html>