summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/tabPrompts
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base/content/test/tabPrompts')
-rw-r--r--browser/base/content/test/tabPrompts/auth-route.sjs28
-rw-r--r--browser/base/content/test/tabPrompts/browser.ini30
-rw-r--r--browser/base/content/test/tabPrompts/browser_abort_when_in_modal_state.js60
-rw-r--r--browser/base/content/test/tabPrompts/browser_auth_spoofing_protection.js232
-rw-r--r--browser/base/content/test/tabPrompts/browser_auth_spoofing_url_copy.js95
-rw-r--r--browser/base/content/test/tabPrompts/browser_auth_spoofing_url_drag_and_drop.js93
-rw-r--r--browser/base/content/test/tabPrompts/browser_beforeunload_urlbar.js75
-rw-r--r--browser/base/content/test/tabPrompts/browser_closeTabSpecificPanels.js53
-rw-r--r--browser/base/content/test/tabPrompts/browser_confirmFolderUpload.js141
-rw-r--r--browser/base/content/test/tabPrompts/browser_contentOrigins.js217
-rw-r--r--browser/base/content/test/tabPrompts/browser_multiplePrompts.js171
-rw-r--r--browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js262
-rw-r--r--browser/base/content/test/tabPrompts/browser_promptFocus.js170
-rw-r--r--browser/base/content/test/tabPrompts/browser_prompt_closed_window.js40
-rw-r--r--browser/base/content/test/tabPrompts/browser_switchTabPermissionPrompt.js41
-rw-r--r--browser/base/content/test/tabPrompts/browser_windowPrompt.js259
-rw-r--r--browser/base/content/test/tabPrompts/file_beforeunload_stop.html8
-rw-r--r--browser/base/content/test/tabPrompts/openPromptOffTimeout.html10
-rw-r--r--browser/base/content/test/tabPrompts/redirect-crossDomain-tabTitle-update.html15
-rw-r--r--browser/base/content/test/tabPrompts/redirect-crossDomain.html13
-rw-r--r--browser/base/content/test/tabPrompts/redirect-sameDomain.html13
21 files changed, 2026 insertions, 0 deletions
diff --git a/browser/base/content/test/tabPrompts/auth-route.sjs b/browser/base/content/test/tabPrompts/auth-route.sjs
new file mode 100644
index 0000000000..4f113a8add
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/auth-route.sjs
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function handleRequest(request, response) {
+ let body;
+ // guest:guest
+ let expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
+ // correct login credentials provided
+ if (
+ request.hasHeader("Authorization") &&
+ request.getHeader("Authorization") == expectedHeader
+ ) {
+ response.setStatusLine(request.httpVersion, 200, "OK, authorized");
+ response.setHeader("Content-Type", "text", false);
+
+ body = "success";
+ } else {
+ // incorrect credentials
+ response.setStatusLine(request.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+ response.setHeader("Content-Type", "text", false);
+
+ body = "failed";
+ }
+ response.bodyOutputStream.write(body, body.length);
+}
diff --git a/browser/base/content/test/tabPrompts/browser.ini b/browser/base/content/test/tabPrompts/browser.ini
new file mode 100644
index 0000000000..2834b6d2af
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser.ini
@@ -0,0 +1,30 @@
+[browser_abort_when_in_modal_state.js]
+[browser_auth_spoofing_protection.js]
+support-files =
+ redirect-crossDomain.html
+ redirect-sameDomain.html
+ auth-route.sjs
+[browser_auth_spoofing_url_copy.js]
+support-files =
+ redirect-crossDomain.html
+ auth-route.sjs
+[browser_auth_spoofing_url_drag_and_drop.js]
+support-files =
+ redirect-crossDomain.html
+ redirect-sameDomain.html
+ auth-route.sjs
+[browser_beforeunload_urlbar.js]
+support-files = file_beforeunload_stop.html
+[browser_closeTabSpecificPanels.js]
+skip-if = verify && debug && (os == 'linux')
+[browser_confirmFolderUpload.js]
+[browser_contentOrigins.js]
+support-files = file_beforeunload_stop.html
+[browser_multiplePrompts.js]
+[browser_openPromptInBackgroundTab.js]
+https_first_disabled = true
+support-files = openPromptOffTimeout.html
+[browser_promptFocus.js]
+[browser_prompt_closed_window.js]
+[browser_switchTabPermissionPrompt.js]
+[browser_windowPrompt.js]
diff --git a/browser/base/content/test/tabPrompts/browser_abort_when_in_modal_state.js b/browser/base/content/test/tabPrompts/browser_abort_when_in_modal_state.js
new file mode 100644
index 0000000000..cb3a1f72d6
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_abort_when_in_modal_state.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { PromiseTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromiseTestUtils.sys.mjs"
+);
+
+/**
+ * Check that if we're using a window-modal prompt,
+ * the next synchronous window-internal modal prompt aborts rather than
+ * leaving us in a deadlock about how to enter modal state.
+ */
+add_task(async function test_check_multiple_prompts() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["prompts.windowPromptSubDialog", true]],
+ });
+ let container = document.getElementById("window-modal-dialog");
+ let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen();
+
+ let firstDialogClosedPromise = new Promise(resolve => {
+ // Avoid blocking the test on the (sync) alert by sticking it in a timeout:
+ setTimeout(() => {
+ Services.prompt.alertBC(
+ window.browsingContext,
+ Ci.nsIPrompt.MODAL_TYPE_WINDOW,
+ "Some title",
+ "some message"
+ );
+ resolve();
+ }, 0);
+ });
+ let dialogWin = await dialogPromise;
+
+ // Check circumstances of opening.
+ ok(
+ !dialogWin.docShell.chromeEventHandler,
+ "Should not have embedded the dialog."
+ );
+
+ PromiseTestUtils.expectUncaughtRejection(/could not be shown/);
+ let rv = Services.prompt.confirm(
+ window,
+ "I should not appear",
+ "because another prompt was open"
+ );
+ is(rv, false, "Prompt should have been canceled.");
+
+ info("Accepting dialog");
+ dialogWin.document.querySelector("dialog").acceptDialog();
+
+ await firstDialogClosedPromise;
+
+ await BrowserTestUtils.waitForMutationCondition(
+ container,
+ { childList: true, attributes: true },
+ () => !container.hasChildNodes() && !container.open
+ );
+});
diff --git a/browser/base/content/test/tabPrompts/browser_auth_spoofing_protection.js b/browser/base/content/test/tabPrompts/browser_auth_spoofing_protection.js
new file mode 100644
index 0000000000..ca139df8e4
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_auth_spoofing_protection.js
@@ -0,0 +1,232 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+let TEST_PATH_AUTH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.org"
+);
+
+const CROSS_DOMAIN_URL = TEST_PATH + "redirect-crossDomain.html";
+
+const SAME_DOMAIN_URL = TEST_PATH + "redirect-sameDomain.html";
+
+const AUTH_URL = TEST_PATH_AUTH + "auth-route.sjs";
+
+/**
+ * Opens a new tab with a url that ether redirects us cross or same domain
+ *
+ * @param {Boolean} doConfirmPrompt - true if we want to test the case when the user accepts the prompt,
+ * false if we want to test the case when the user cancels the prompt.
+ * @param {Boolean} crossDomain - if true we will open a url that redirects us to a cross domain url,
+ * if false, we will open a url that redirects us to a same domain url
+ * @param {Boolean} prefEnabled true will enable "privacy.authPromptSpoofingProtection",
+ * false will disable the pref
+ */
+async function trigger401AndHandle(doConfirmPrompt, crossDomain, prefEnabled) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.authPromptSpoofingProtection", prefEnabled]],
+ });
+ let url = crossDomain ? CROSS_DOMAIN_URL : SAME_DOMAIN_URL;
+ let dialogShown = waitForDialog(doConfirmPrompt, crossDomain, prefEnabled);
+ await BrowserTestUtils.withNewTab(url, async function () {
+ await dialogShown;
+ });
+ await new Promise(resolve => {
+ Services.clearData.deleteData(
+ Ci.nsIClearDataService.CLEAR_AUTH_CACHE,
+ resolve
+ );
+ });
+}
+
+async function waitForDialog(doConfirmPrompt, crossDomain, prefEnabled) {
+ await TestUtils.topicObserved("common-dialog-loaded");
+ let dialog = gBrowser.getTabDialogBox(gBrowser.selectedBrowser)
+ ._tabDialogManager._topDialog;
+ let dialogDocument = dialog._frame.contentDocument;
+ if (crossDomain) {
+ if (prefEnabled) {
+ Assert.equal(
+ dialog._overlay.getAttribute("hideContent"),
+ "true",
+ "Dialog overlay hides the current sites content"
+ );
+ Assert.equal(
+ window.gURLBar.value,
+ AUTH_URL,
+ "Correct location is provided by the prompt"
+ );
+ Assert.equal(
+ window.gBrowser.selectedTab.label,
+ "example.org",
+ "Tab title is manipulated"
+ );
+ // switch to another tab and make sure we dont mess up this new tabs url bar and tab title
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.org:443"
+ );
+ Assert.equal(
+ window.gURLBar.value,
+ "https://example.org",
+ "No location is provided by the prompt, correct location is displayed"
+ );
+ Assert.equal(
+ window.gBrowser.selectedTab.label,
+ "mochitest index /",
+ "Tab title is not manipulated"
+ );
+ // switch back to our tab with the prompt and make sure the url bar state and tab title is still there
+ BrowserTestUtils.removeTab(tab);
+ Assert.equal(
+ window.gURLBar.value,
+ AUTH_URL,
+ "Correct location is provided by the prompt"
+ );
+ Assert.equal(
+ window.gBrowser.selectedTab.label,
+ "example.org",
+ "Tab title is manipulated"
+ );
+ // make sure a value that the user types in has a higher priority than our prompts location
+ gBrowser.selectedBrowser.userTypedValue = "user value";
+ gURLBar.setURI();
+ Assert.equal(
+ window.gURLBar.value,
+ "user value",
+ "User typed value is shown"
+ );
+ // if the user clears the url bar we again fall back to the location of the prompt if we trigger setURI by a tab switch
+ gBrowser.selectedBrowser.userTypedValue = "";
+ gURLBar.setURI(null, true);
+ Assert.equal(
+ window.gURLBar.value,
+ AUTH_URL,
+ "Correct location is provided by the prompt"
+ );
+ // Cross domain and pref is not enabled
+ } else {
+ Assert.equal(
+ dialog._overlay.getAttribute("hideContent"),
+ "",
+ "Dialog overlay does not hide the current sites content"
+ );
+ Assert.equal(
+ window.gURLBar.value,
+ CROSS_DOMAIN_URL,
+ "No location is provided by the prompt, correct location is displayed"
+ );
+ Assert.equal(
+ window.gBrowser.selectedTab.label,
+ "example.com",
+ "Tab title is not manipulated"
+ );
+ }
+ // same domain
+ } else {
+ Assert.equal(
+ dialog._overlay.getAttribute("hideContent"),
+ "",
+ "Dialog overlay does not hide the current sites content"
+ );
+ Assert.equal(
+ window.gURLBar.value,
+ SAME_DOMAIN_URL,
+ "No location is provided by the prompt, correct location is displayed"
+ );
+ Assert.equal(
+ window.gBrowser.selectedTab.label,
+ "example.com",
+ "Tab title is not manipulated"
+ );
+ }
+
+ let onDialogClosed = BrowserTestUtils.waitForEvent(
+ window,
+ "DOMModalDialogClosed"
+ );
+ if (doConfirmPrompt) {
+ dialogDocument.getElementById("loginTextbox").value = "guest";
+ dialogDocument.getElementById("password1Textbox").value = "guest";
+ dialogDocument.getElementById("commonDialog").acceptDialog();
+ } else {
+ dialogDocument.getElementById("commonDialog").cancelDialog();
+ }
+
+ // wait for the dialog to be closed to check that the URLBar state is reset
+ await onDialogClosed;
+ // Due to bug 1812014, the url bar will be clear if we have set its value to "" while the prompt was open
+ // so we trigger a tab switch again to have the uri displayed to be able to check its value
+ gURLBar.setURI(null, true);
+ Assert.equal(
+ window.gURLBar.value,
+ crossDomain ? CROSS_DOMAIN_URL : SAME_DOMAIN_URL,
+ "No location is provided by the prompt"
+ );
+ Assert.equal(
+ window.gBrowser.selectedTab.label,
+ "example.com",
+ "Tab title is not manipulated"
+ );
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.authPromptSpoofingProtection", true]],
+ });
+});
+
+/**
+ * Tests that the 401 auth spoofing mechanisms apply if the 401 is from a different base domain than the current sites,
+ * canceling the prompt
+ */
+add_task(async function testCrossDomainCancelPrefEnabled() {
+ await trigger401AndHandle(false, true, true);
+});
+
+/**
+ * Tests that the 401 auth spoofing mechanisms apply if the 401 is from a different base domain than the current sites,
+ * accepting the prompt
+ */
+add_task(async function testCrossDomainAcceptPrefEnabled() {
+ await trigger401AndHandle(true, true, true);
+});
+
+/**
+ * Tests that the 401 auth spoofing mechanisms do not apply if "privacy.authPromptSpoofingProtection" is not set to true
+ * canceling the prompt
+ */
+add_task(async function testCrossDomainCancelPrefDisabled() {
+ await trigger401AndHandle(false, true, false);
+});
+
+/**
+ * Tests that the 401 auth spoofing mechanisms do not apply if "privacy.authPromptSpoofingProtection" is not set to true,
+ * accepting the prompt
+ */
+add_task(async function testCrossDomainAcceptPrefDisabled() {
+ await trigger401AndHandle(true, true, false);
+});
+
+/**
+ * Tests that the 401 auth spoofing mechanisms are not triggered by a 401 within the same base domain as the current sites,
+ * canceling the prompt
+ */
+add_task(async function testSameDomainCancelPrefEnabled() {
+ await trigger401AndHandle(false, false, true);
+});
+
+/**
+ * Tests that the 401 auth spoofing mechanisms are not triggered by a 401 within the same base domain as the current sites,
+ * accepting the prompt
+ */
+add_task(async function testSameDomainAcceptPrefEnabled() {
+ await trigger401AndHandle(true, false, true);
+});
diff --git a/browser/base/content/test/tabPrompts/browser_auth_spoofing_url_copy.js b/browser/base/content/test/tabPrompts/browser_auth_spoofing_url_copy.js
new file mode 100644
index 0000000000..5bea05020e
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_auth_spoofing_url_copy.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+let TEST_PATH_AUTH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.org"
+);
+
+const CROSS_DOMAIN_URL = TEST_PATH + "redirect-crossDomain.html";
+
+const AUTH_URL = TEST_PATH_AUTH + "auth-route.sjs";
+
+/**
+ * Opens a new tab with a url that redirects us cross domain
+ * tests that auth anti-spoofing mechanisms cover url copy while prompt is open
+ *
+ */
+async function trigger401AndHandle() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.authPromptSpoofingProtection", true]],
+ });
+ let dialogShown = waitForDialogAndCopyURL();
+ await BrowserTestUtils.withNewTab(CROSS_DOMAIN_URL, async function () {
+ await dialogShown;
+ });
+ await new Promise(resolve => {
+ Services.clearData.deleteData(
+ Ci.nsIClearDataService.CLEAR_AUTH_CACHE,
+ resolve
+ );
+ });
+}
+
+async function waitForDialogAndCopyURL() {
+ await TestUtils.topicObserved("common-dialog-loaded");
+ let dialog = gBrowser.getTabDialogBox(gBrowser.selectedBrowser)
+ ._tabDialogManager._topDialog;
+ let dialogDocument = dialog._frame.contentDocument;
+
+ //select the whole URL
+ gURLBar.focus();
+ await SimpleTest.promiseClipboardChange(AUTH_URL, () => {
+ Assert.equal(gURLBar.value, AUTH_URL, "url bar copy value set");
+ gURLBar.select();
+ goDoCommand("cmd_copy");
+ });
+
+ // select only part of the URL
+ gURLBar.focus();
+ let endOfSelectionRange = AUTH_URL.indexOf("/auth-route.sjs");
+ await SimpleTest.promiseClipboardChange(
+ AUTH_URL.substring(0, endOfSelectionRange),
+ () => {
+ Assert.equal(gURLBar.value, AUTH_URL, "url bar copy value set");
+ gURLBar.selectionStart = 0;
+ gURLBar.selectionEnd = endOfSelectionRange;
+ goDoCommand("cmd_copy");
+ }
+ );
+ let onDialogClosed = BrowserTestUtils.waitForEvent(
+ window,
+ "DOMModalDialogClosed"
+ );
+ dialogDocument.getElementById("commonDialog").cancelDialog();
+
+ await onDialogClosed;
+ Assert.equal(
+ window.gURLBar.value,
+ CROSS_DOMAIN_URL,
+ "No location is provided by the prompt"
+ );
+
+ //select the whole URL after URL is reset to normal
+ gURLBar.focus();
+ await SimpleTest.promiseClipboardChange(CROSS_DOMAIN_URL, () => {
+ Assert.equal(gURLBar.value, CROSS_DOMAIN_URL, "url bar copy value set");
+ gURLBar.select();
+ goDoCommand("cmd_copy");
+ });
+}
+
+/**
+ * Tests that the 401 auth spoofing mechanisms covers the url bar copy action properly,
+ * canceling the prompt
+ */
+add_task(async function testUrlCopy() {
+ await trigger401AndHandle();
+});
diff --git a/browser/base/content/test/tabPrompts/browser_auth_spoofing_url_drag_and_drop.js b/browser/base/content/test/tabPrompts/browser_auth_spoofing_url_drag_and_drop.js
new file mode 100644
index 0000000000..564f1fa9a7
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_auth_spoofing_url_drag_and_drop.js
@@ -0,0 +1,93 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+let TEST_PATH_AUTH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.org"
+);
+
+const CROSS_DOMAIN_URL = TEST_PATH + "redirect-crossDomain.html";
+
+const SAME_DOMAIN_URL = TEST_PATH + "redirect-sameDomain.html";
+
+const AUTH_URL = TEST_PATH_AUTH + "auth-route.sjs";
+
+/**
+ * Opens a new tab with a url that ether redirects us cross or same domain
+ *
+ * @param {Boolean} crossDomain - if true we will open a url that redirects us to a cross domain url,
+ * if false, we will open a url that redirects us to a same domain url
+ */
+async function trigger401AndHandle(crossDomain) {
+ let dialogShown = waitForDialogAndDragNDropURL(crossDomain);
+ await BrowserTestUtils.withNewTab(
+ crossDomain ? CROSS_DOMAIN_URL : SAME_DOMAIN_URL,
+ async function () {
+ await dialogShown;
+ }
+ );
+ await new Promise(resolve => {
+ Services.clearData.deleteData(
+ Ci.nsIClearDataService.CLEAR_AUTH_CACHE,
+ resolve
+ );
+ });
+}
+
+async function waitForDialogAndDragNDropURL(crossDomain) {
+ await TestUtils.topicObserved("common-dialog-loaded");
+ let dialog = gBrowser.getTabDialogBox(gBrowser.selectedBrowser)
+ ._tabDialogManager._topDialog;
+ let dialogDocument = dialog._frame.contentDocument;
+
+ let urlbar = document.getElementById("urlbar-input");
+ let dataTran = new DataTransfer();
+ let urlEvent = new DragEvent("dragstart", { dataTransfer: dataTran });
+ let urlBarContainer = document.getElementById("urlbar-input-container");
+ urlBarContainer.click();
+ // trigger a drag event in the gUrlBar
+ urlbar.dispatchEvent(urlEvent);
+ // this should set some propperties on our event, like the url we are dragging
+ if (crossDomain) {
+ is(
+ urlEvent.dataTransfer.getData("text/plain"),
+ AUTH_URL,
+ "correct cross Domain URL is dragged over"
+ );
+ } else {
+ is(
+ urlEvent.dataTransfer.getData("text/plain"),
+ SAME_DOMAIN_URL,
+ "correct same domain URL is dragged over"
+ );
+ }
+
+ let onDialogClosed = BrowserTestUtils.waitForEvent(
+ window,
+ "DOMModalDialogClosed"
+ );
+ dialogDocument.getElementById("commonDialog").cancelDialog();
+
+ await onDialogClosed;
+}
+
+/**
+ * Tests that the 401 auth spoofing mechanisms covers the url bar drag and drop action propperly,
+ */
+add_task(async function testUrlDragAndDrop() {
+ await trigger401AndHandle(true);
+});
+
+/**
+ * Tests that the 401 auth spoofing mechanisms do not apply to the url bar drag and drop action if the 401 is not from a different base domain,
+ */
+add_task(async function testUrlDragAndDrop() {
+ await trigger401AndHandle(false);
+});
diff --git a/browser/base/content/test/tabPrompts/browser_beforeunload_urlbar.js b/browser/base/content/test/tabPrompts/browser_beforeunload_urlbar.js
new file mode 100644
index 0000000000..cb53783c99
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_beforeunload_urlbar.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com"
+);
+
+const CONTENT_PROMPT_SUBDIALOG = Services.prefs.getBoolPref(
+ "prompts.contentPromptSubDialog",
+ false
+);
+
+add_task(async function test_beforeunload_stay_clears_urlbar() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.require_user_interaction_for_beforeunload", false]],
+ });
+
+ const TEST_URL = TEST_ROOT + "file_beforeunload_stop.html";
+ await BrowserTestUtils.withNewTab(TEST_URL, async function (browser) {
+ gURLBar.focus();
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ const inputValue = "http://example.org/?q=typed";
+ gURLBar.inputField.value = inputValue.slice(0, -1);
+ EventUtils.sendString(inputValue.slice(-1));
+
+ if (CONTENT_PROMPT_SUBDIALOG) {
+ let promptOpenedPromise =
+ BrowserTestUtils.promiseAlertDialogOpen("cancel");
+ EventUtils.synthesizeKey("VK_RETURN");
+ await promptOpenedPromise;
+ await TestUtils.waitForTick();
+ } else {
+ let promptOpenedPromise = TestUtils.topicObserved(
+ "tabmodal-dialog-loaded"
+ );
+ EventUtils.synthesizeKey("VK_RETURN");
+ await promptOpenedPromise;
+ let promptElement = browser.parentNode.querySelector("tabmodalprompt");
+
+ // Click the cancel button
+ promptElement.querySelector(".tabmodalprompt-button1").click();
+ await TestUtils.waitForCondition(
+ () => promptElement.parentNode == null,
+ "tabprompt should be removed"
+ );
+ }
+
+ // Can't just compare directly with TEST_URL because the URL may be trimmed.
+ // Just need it to not be the example.org thing we typed in.
+ ok(
+ gURLBar.value.endsWith("_stop.html"),
+ "Url bar should be reset to point to the stop html file"
+ );
+ ok(
+ gURLBar.value.includes("example.com"),
+ "Url bar should be reset to example.com"
+ );
+ // Check the lock/identity icons are back:
+ is(
+ gURLBar.textbox.getAttribute("pageproxystate"),
+ "valid",
+ "Should be in valid pageproxy state."
+ );
+
+ // Now we need to get rid of the handler to avoid the prompt coming up when trying to close the
+ // tab when we exit `withNewTab`. :-)
+ await SpecialPowers.spawn(browser, [], function () {
+ content.window.onbeforeunload = null;
+ });
+ });
+});
diff --git a/browser/base/content/test/tabPrompts/browser_closeTabSpecificPanels.js b/browser/base/content/test/tabPrompts/browser_closeTabSpecificPanels.js
new file mode 100644
index 0000000000..8d789ad512
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_closeTabSpecificPanels.js
@@ -0,0 +1,53 @@
+"use strict";
+
+/*
+ * This test creates multiple panels, one that has been tagged as specific to its tab's content
+ * and one that isn't. When a tab loses focus, panel specific to that tab should close.
+ * The non-specific panel should remain open.
+ *
+ */
+
+add_task(async function () {
+ let tab1 = BrowserTestUtils.addTab(gBrowser, "http://mochi.test:8888/#0");
+ let tab2 = BrowserTestUtils.addTab(gBrowser, "http://mochi.test:8888/#1");
+ let specificPanel = document.createXULElement("panel");
+ specificPanel.setAttribute("tabspecific", "true");
+ specificPanel.setAttribute("noautohide", "true");
+ let generalPanel = document.createXULElement("panel");
+ generalPanel.setAttribute("noautohide", "true");
+ let anchor = document.getElementById(CustomizableUI.AREA_NAVBAR);
+
+ anchor.appendChild(specificPanel);
+ anchor.appendChild(generalPanel);
+ is(specificPanel.state, "closed", "specificPanel starts as closed");
+ is(generalPanel.state, "closed", "generalPanel starts as closed");
+
+ let specificPanelPromise = BrowserTestUtils.waitForEvent(
+ specificPanel,
+ "popupshown"
+ );
+ specificPanel.openPopupAtScreen(210, 210);
+ await specificPanelPromise;
+ is(specificPanel.state, "open", "specificPanel has been opened");
+
+ let generalPanelPromise = BrowserTestUtils.waitForEvent(
+ generalPanel,
+ "popupshown"
+ );
+ generalPanel.openPopupAtScreen(510, 510);
+ await generalPanelPromise;
+ is(generalPanel.state, "open", "generalPanel has been opened");
+
+ gBrowser.tabContainer.advanceSelectedTab(-1, true);
+ is(
+ specificPanel.state,
+ "closed",
+ "specificPanel panel is closed after its tab loses focus"
+ );
+ is(generalPanel.state, "open", "generalPanel is still open after tab switch");
+
+ specificPanel.remove();
+ generalPanel.remove();
+ gBrowser.removeTab(tab1);
+ gBrowser.removeTab(tab2);
+});
diff --git a/browser/base/content/test/tabPrompts/browser_confirmFolderUpload.js b/browser/base/content/test/tabPrompts/browser_confirmFolderUpload.js
new file mode 100644
index 0000000000..62b0ed4f2b
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_confirmFolderUpload.js
@@ -0,0 +1,141 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+/**
+ * Create a temporary test directory that will be cleaned up on test shutdown.
+ * @returns {String} - absolute directory path.
+ */
+function getTestDirectory() {
+ let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ tmpDir.append("testdir");
+ if (!tmpDir.exists()) {
+ tmpDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ registerCleanupFunction(() => {
+ tmpDir.remove(true);
+ });
+ }
+
+ let file1 = tmpDir.clone();
+ file1.append("foo.txt");
+ if (!file1.exists()) {
+ file1.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+ }
+
+ let file2 = tmpDir.clone();
+ file2.append("bar.txt");
+ if (!file2.exists()) {
+ file2.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+ }
+
+ return tmpDir.path;
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Allow using our MockFilePicker in the content process.
+ ["dom.filesystem.pathcheck.disabled", true],
+ ["dom.webkitBlink.dirPicker.enabled", true],
+ ],
+ });
+});
+
+/**
+ * Create a file input, select a folder and wait for the upload confirmation
+ * prompt to open.
+ * @param {boolean} confirmUpload - Whether to accept (true) or cancel the
+ * prompt (false).
+ * @returns {Promise} - Resolves once the prompt has been closed.
+ */
+async function testUploadPrompt(confirmUpload) {
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ await BrowserTestUtils.withNewTab("http://example.com", async browser => {
+ // Create file input element
+ await ContentTask.spawn(browser, null, () => {
+ let input = content.document.createElement("input");
+ input.id = "filepicker";
+ input.setAttribute("type", "file");
+ input.setAttribute("webkitdirectory", "");
+ content.document.body.appendChild(input);
+ });
+
+ // If we're confirming the dialog, register a "change" listener on the
+ // file input.
+ let changePromise;
+ if (confirmUpload) {
+ changePromise = ContentTask.spawn(browser, null, async () => {
+ let input = content.document.getElementById("filepicker");
+ return ContentTaskUtils.waitForEvent(input, "change").then(
+ e => e.target.files.length
+ );
+ });
+ }
+
+ // Register prompt promise
+ let promptPromise = PromptTestUtils.waitForPrompt(browser, {
+ modalType: Services.prompt.MODAL_TYPE_TAB,
+ promptType: "confirmEx",
+ });
+
+ // Open filepicker
+ let path = getTestDirectory();
+ await ContentTask.spawn(browser, { path }, args => {
+ let MockFilePicker = content.SpecialPowers.MockFilePicker;
+ MockFilePicker.init(
+ content,
+ "A Mock File Picker",
+ content.SpecialPowers.Ci.nsIFilePicker.modeGetFolder
+ );
+ MockFilePicker.useDirectory(args.path);
+
+ let input = content.document.getElementById("filepicker");
+ input.click();
+ });
+
+ // Wait for confirmation prompt
+ let prompt = await promptPromise;
+ ok(prompt, "Shown upload confirmation prompt");
+ is(prompt.ui.button0.label, "Upload", "Accept button label");
+ ok(prompt.ui.button1.hasAttribute("default"), "Cancel is default button");
+
+ // Close confirmation prompt
+ await PromptTestUtils.handlePrompt(prompt, {
+ buttonNumClick: confirmUpload ? 0 : 1,
+ });
+
+ // If we accepted, wait for the input elements "change" event
+ if (changePromise) {
+ let fileCount = await changePromise;
+ is(fileCount, 2, "Should have selected 2 files");
+ } else {
+ let fileCount = await ContentTask.spawn(browser, null, () => {
+ return content.document.getElementById("filepicker").files.length;
+ });
+
+ is(fileCount, 0, "Should not have selected any files");
+ }
+
+ // Cleanup
+ await ContentTask.spawn(browser, null, () => {
+ content.SpecialPowers.MockFilePicker.cleanup();
+ });
+ });
+}
+
+// Tests the confirmation prompt that shows after the user picked a folder.
+
+// Confirm the prompt
+add_task(async function test_confirm() {
+ await testUploadPrompt(true);
+});
+
+// Cancel the prompt
+add_task(async function test_cancel() {
+ await testUploadPrompt(false);
+});
diff --git a/browser/base/content/test/tabPrompts/browser_contentOrigins.js b/browser/base/content/test/tabPrompts/browser_contentOrigins.js
new file mode 100644
index 0000000000..d39c8a3f67
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_contentOrigins.js
@@ -0,0 +1,217 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+let { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+const TEST_ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+async function checkAlert(
+ pageToLoad,
+ expectedTitle,
+ expectedIcon = "chrome://global/skin/icons/defaultFavicon.svg"
+) {
+ function openFn(browser) {
+ return SpecialPowers.spawn(browser, [], () => {
+ if (content.document.nodePrincipal.isSystemPrincipal) {
+ // Can't eval in privileged contexts due to CSP, just call directly:
+ content.alert("Test");
+ } else {
+ // Eval everywhere else so it gets the principal of the loaded page.
+ content.eval("alert('Test')");
+ }
+ });
+ }
+ return checkDialog(pageToLoad, openFn, expectedTitle, expectedIcon);
+}
+
+async function checkBeforeunload(
+ pageToLoad,
+ expectedTitle,
+ expectedIcon = "chrome://global/skin/icons/defaultFavicon.svg"
+) {
+ async function openFn(browser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "body",
+ {},
+ browser.browsingContext
+ );
+ return gBrowser.removeTab(tab); // trigger beforeunload.
+ }
+ return checkDialog(pageToLoad, openFn, expectedTitle, expectedIcon);
+}
+
+async function checkDialog(
+ pageToLoad,
+ openFn,
+ expectedTitle,
+ expectedIcon,
+ modalType = Ci.nsIPrompt.MODAL_TYPE_CONTENT
+) {
+ return BrowserTestUtils.withNewTab(pageToLoad, async browser => {
+ let promptPromise = PromptTestUtils.waitForPrompt(browser, {
+ modalType,
+ });
+ let spawnPromise = openFn(browser);
+ let dialog = await promptPromise;
+
+ let doc = dialog.ui.prompt.document;
+ let titleEl = doc.getElementById("titleText");
+ if (expectedTitle.value) {
+ is(titleEl.textContent, expectedTitle.value, "Title should match.");
+ } else {
+ is(
+ titleEl.dataset.l10nId,
+ expectedTitle.l10nId,
+ "Title l10n id should match."
+ );
+ }
+ ok(
+ !titleEl.parentNode.hasAttribute("overflown"),
+ "Title should fit without overflowing."
+ );
+
+ ok(BrowserTestUtils.is_visible(titleEl), "New title should be shown.");
+ ok(
+ BrowserTestUtils.is_hidden(doc.getElementById("infoTitle")),
+ "Old title should be hidden."
+ );
+ let iconCS = doc.ownerGlobal.getComputedStyle(
+ doc.querySelector(".titleIcon")
+ );
+ is(
+ iconCS.backgroundImage,
+ `url("${expectedIcon}")`,
+ "Icon is as expected."
+ );
+
+ // This is not particularly neat, but we want to also test overflow
+ // Our test systems don't have hosts that long, so just fake it:
+ if (browser.currentURI.asciiHost == "example.com") {
+ let longerDomain = "extravagantly.long.".repeat(10) + "example.com";
+ doc.documentElement.setAttribute(
+ "headertitle",
+ JSON.stringify({ raw: longerDomain, shouldUseMaskFade: true })
+ );
+ info("Wait for the prompt title to update.");
+ await BrowserTestUtils.waitForMutationCondition(
+ titleEl,
+ { characterData: true, attributes: true },
+ () =>
+ titleEl.textContent == longerDomain &&
+ titleEl.parentNode.hasAttribute("overflown")
+ );
+ is(titleEl.textContent, longerDomain, "The longer domain is reflected.");
+ ok(
+ titleEl.parentNode.hasAttribute("overflown"),
+ "The domain should overflow."
+ );
+ }
+
+ // Close the prompt again.
+ await PromptTestUtils.handlePrompt(dialog);
+ // The alert in the content process was sync, we need to make sure it gets
+ // cleaned up, but couldn't await it above because that'd hang the test!
+ await spawnPromise;
+ });
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["prompts.contentPromptSubDialog", true],
+ ["prompts.modalType.httpAuth", Ci.nsIPrompt.MODAL_TYPE_TAB],
+ ["prompts.tabChromePromptSubDialog", true],
+ ],
+ });
+});
+
+add_task(async function test_check_prompt_origin_display() {
+ await checkAlert("https://example.com/", { value: "example.com" });
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ await checkAlert("http://example.com/", { value: "example.com" });
+ await checkAlert("data:text/html,<body>", {
+ l10nId: "common-dialog-title-null",
+ });
+
+ let homeDir = Services.dirsvc.get("Home", Ci.nsIFile);
+ let fileURI = Services.io.newFileURI(homeDir).spec;
+ await checkAlert(fileURI, { value: "file://" });
+
+ await checkAlert(
+ "about:config",
+ { l10nId: "common-dialog-title-system" },
+ "chrome://branding/content/icon32.png"
+ );
+
+ await checkBeforeunload(TEST_ROOT + "file_beforeunload_stop.html", {
+ value: "example.com",
+ });
+});
+
+add_task(async function test_check_auth() {
+ let server = new HttpServer();
+ registerCleanupFunction(() => {
+ return new Promise(resolve => {
+ server.stop(() => {
+ server = null;
+ resolve();
+ });
+ });
+ });
+
+ function forbiddenHandler(meta, res) {
+ res.setStatusLine(meta.httpVersion, 401, "Unauthorized");
+ res.setHeader("WWW-Authenticate", 'Basic realm="Realm"');
+ }
+ function pageHandler(meta, res) {
+ res.setStatusLine(meta.httpVersion, 200, "OK");
+ res.setHeader("Content-Type", "text/html");
+ let body = "<html><body></body></html>";
+ res.bodyOutputStream.write(body, body.length);
+ }
+ server.registerPathHandler("/forbidden", forbiddenHandler);
+ server.registerPathHandler("/page", pageHandler);
+ server.start(-1);
+
+ const HOST = `localhost:${server.identity.primaryPort}`;
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ const AUTH_URI = `http://${HOST}/forbidden`;
+
+ // Try a simple load:
+ await checkDialog(
+ "https://example.com/",
+ browser => BrowserTestUtils.loadURIString(browser, AUTH_URI),
+ HOST,
+ "chrome://global/skin/icons/defaultFavicon.svg",
+ Ci.nsIPrompt.MODAL_TYPE_TAB
+ );
+
+ let subframeLoad = function (browser) {
+ return SpecialPowers.spawn(browser, [AUTH_URI], uri => {
+ let f = content.document.createElement("iframe");
+ f.src = uri;
+ content.document.body.appendChild(f);
+ });
+ };
+
+ // Try x-origin subframe:
+ await checkDialog(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.org/1",
+ subframeLoad,
+ HOST,
+ /* Because this is x-origin, we expect a different icon: */
+ "chrome://global/skin/icons/security-broken.svg",
+ Ci.nsIPrompt.MODAL_TYPE_TAB
+ );
+});
diff --git a/browser/base/content/test/tabPrompts/browser_multiplePrompts.js b/browser/base/content/test/tabPrompts/browser_multiplePrompts.js
new file mode 100644
index 0000000000..597b7dfd6f
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_multiplePrompts.js
@@ -0,0 +1,171 @@
+"use strict";
+
+const CONTENT_PROMPT_SUBDIALOG = Services.prefs.getBoolPref(
+ "prompts.contentPromptSubDialog",
+ false
+);
+
+/**
+ * Goes through a stacked series of dialogs opened with
+ * CONTENT_PROMPT_SUBDIALOG set to true, and ensures that
+ * the oldest one is front-most and has the right type. It
+ * then closes the oldest to newest dialog.
+ *
+ * @param {Element} tab The <tab> that has had content dialogs opened
+ * for it.
+ * @param {Number} promptCount How many dialogs we expected to have been
+ * opened.
+ *
+ * @return {Promise}
+ * @resolves {undefined} Once the dialogs have all been closed.
+ */
+async function closeDialogs(tab, dialogCount) {
+ let dialogElementsCount = dialogCount;
+ let dialogs =
+ tab.linkedBrowser.tabDialogBox.getContentDialogManager().dialogs;
+
+ is(
+ dialogs.length,
+ dialogElementsCount,
+ "There should be " + dialogElementsCount + " dialog(s)."
+ );
+
+ let i = dialogElementsCount - 1;
+ for (let dialog of dialogs) {
+ dialog.focus(true);
+ await dialog._dialogReady;
+
+ let dialogWindow = dialog.frameContentWindow;
+ let expectedType = ["alert", "prompt", "confirm"][i % 3];
+
+ is(
+ dialogWindow.Dialog.args.text,
+ expectedType + " countdown #" + i,
+ "The #" + i + " alert should be labelled as such."
+ );
+ i--;
+
+ dialogWindow.Dialog.ui.button0.click();
+
+ // The click is handled async; wait for an event loop turn for that to
+ // happen.
+ await new Promise(function (resolve) {
+ Services.tm.dispatchToMainThread(resolve);
+ });
+ }
+
+ dialogs = tab.linkedBrowser.tabDialogBox.getContentDialogManager().dialogs;
+ is(dialogs.length, 0, "Dialogs should all be dismissed.");
+}
+
+/**
+ * Goes through a stacked series of tabprompt modals opened with
+ * CONTENT_PROMPT_SUBDIALOG set to false, and ensures that
+ * the oldest one is front-most and has the right type. It also
+ * ensures that the other tabprompt modals are hidden. It
+ * then closes the oldest to newest dialog.
+ *
+ * @param {Element} tab The <tab> that has had tabprompt modals opened
+ * for it.
+ * @param {Number} promptCount How many modals we expected to have been
+ * opened.
+ *
+ * @return {Promise}
+ * @resolves {undefined} Once the modals have all been closed.
+ */
+async function closeTabModals(tab, promptCount) {
+ let promptElementsCount = promptCount;
+ while (promptElementsCount--) {
+ let promptElements =
+ tab.linkedBrowser.parentNode.querySelectorAll("tabmodalprompt");
+ is(
+ promptElements.length,
+ promptElementsCount + 1,
+ "There should be " + (promptElementsCount + 1) + " prompt(s)."
+ );
+ // The oldest should be the first.
+ let i = 0;
+
+ for (let promptElement of promptElements) {
+ let prompt = tab.linkedBrowser.tabModalPromptBox.getPrompt(promptElement);
+ let expectedType = ["alert", "prompt", "confirm"][i % 3];
+ is(
+ prompt.Dialog.args.text,
+ expectedType + " countdown #" + i,
+ "The #" + i + " alert should be labelled as such."
+ );
+ if (i !== promptElementsCount) {
+ is(prompt.element.hidden, true, "This prompt should be hidden.");
+ i++;
+ continue;
+ }
+
+ is(prompt.element.hidden, false, "The last prompt should not be hidden.");
+ prompt.onButtonClick(0);
+
+ // The click is handled async; wait for an event loop turn for that to
+ // happen.
+ await new Promise(function (resolve) {
+ Services.tm.dispatchToMainThread(resolve);
+ });
+ }
+ }
+
+ let promptElements =
+ tab.linkedBrowser.parentNode.querySelectorAll("tabmodalprompt");
+ is(promptElements.length, 0, "Prompts should all be dismissed.");
+}
+
+/*
+ * This test triggers multiple alerts on one single tab, because it"s possible
+ * for web content to do so. The behavior is described in bug 1266353.
+ *
+ * We assert the presentation of the multiple alerts, ensuring we show only
+ * the oldest one.
+ */
+add_task(async function () {
+ const PROMPTCOUNT = 9;
+
+ let unopenedPromptCount = PROMPTCOUNT;
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com",
+ true
+ );
+ info("Tab loaded");
+
+ let promptsOpenedPromise = BrowserTestUtils.waitForEvent(
+ tab.linkedBrowser,
+ "DOMWillOpenModalDialog",
+ false,
+ () => {
+ unopenedPromptCount--;
+ return unopenedPromptCount == 0;
+ }
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [PROMPTCOUNT], maxPrompts => {
+ var i = maxPrompts;
+ let fns = ["alert", "prompt", "confirm"];
+ function openDialog() {
+ i--;
+ if (i) {
+ SpecialPowers.Services.tm.dispatchToMainThread(openDialog);
+ }
+ content[fns[i % 3]](fns[i % 3] + " countdown #" + i);
+ }
+ SpecialPowers.Services.tm.dispatchToMainThread(openDialog);
+ });
+
+ await promptsOpenedPromise;
+
+ if (CONTENT_PROMPT_SUBDIALOG) {
+ await closeDialogs(tab, PROMPTCOUNT);
+ } else {
+ await closeTabModals(tab, PROMPTCOUNT);
+ }
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js b/browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js
new file mode 100644
index 0000000000..d95faa9665
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_openPromptInBackgroundTab.js
@@ -0,0 +1,262 @@
+"use strict";
+
+const { PermissionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PermissionTestUtils.sys.mjs"
+);
+
+const ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/"
+);
+let pageWithAlert = ROOT + "openPromptOffTimeout.html";
+
+registerCleanupFunction(function () {
+ Services.perms.removeAll();
+});
+
+/*
+ * This test opens a tab that alerts when it is hidden. We then switch away
+ * from the tab, and check that by default the tab is not automatically
+ * re-selected. We also check that a checkbox appears in the alert that allows
+ * the user to enable this automatically re-selecting. We then check that
+ * checking the checkbox does actually enable that behaviour.
+ */
+add_task(async function test_old_modal_ui() {
+ // We're intentionally testing the old modal mechanism, so disable the new one.
+ await SpecialPowers.pushPrefEnv({
+ set: [["prompts.contentPromptSubDialog", false]],
+ });
+
+ let firstTab = gBrowser.selectedTab;
+ // load page that opens prompt when page is hidden
+ let openedTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ pageWithAlert,
+ true
+ );
+ let openedTabGotAttentionPromise = BrowserTestUtils.waitForAttribute(
+ "attention",
+ openedTab
+ );
+ // switch away from that tab again - this triggers the alert.
+ await BrowserTestUtils.switchTab(gBrowser, firstTab);
+ // ... but that's async on e10s...
+ await openedTabGotAttentionPromise;
+ // check for attention attribute
+ is(
+ openedTab.hasAttribute("attention"),
+ true,
+ "Tab with alert should have 'attention' attribute."
+ );
+ ok(!openedTab.selected, "Tab with alert should not be selected");
+
+ // switch tab back, and check the checkbox is displayed:
+ await BrowserTestUtils.switchTab(gBrowser, openedTab);
+ // check the prompt is there, and the extra row is present
+ let promptElements =
+ openedTab.linkedBrowser.parentNode.querySelectorAll("tabmodalprompt");
+ is(promptElements.length, 1, "There should be 1 prompt");
+ let ourPromptElement = promptElements[0];
+ let checkbox = ourPromptElement.querySelector(
+ "checkbox[label*='example.com']"
+ );
+ ok(checkbox, "The checkbox should be there");
+ ok(!checkbox.checked, "Checkbox shouldn't be checked");
+ // tick box and accept dialog
+ checkbox.checked = true;
+ let ourPrompt =
+ openedTab.linkedBrowser.tabModalPromptBox.getPrompt(ourPromptElement);
+ ourPrompt.onButtonClick(0);
+ // Wait for that click to actually be handled completely.
+ await new Promise(function (resolve) {
+ Services.tm.dispatchToMainThread(resolve);
+ });
+ // check permission is set
+ is(
+ Services.perms.ALLOW_ACTION,
+ PermissionTestUtils.testPermission(pageWithAlert, "focus-tab-by-prompt"),
+ "Tab switching should now be allowed"
+ );
+
+ // Check if the control center shows the correct permission.
+ let shown = BrowserTestUtils.waitForEvent(
+ window,
+ "popupshown",
+ true,
+ event => event.target == gPermissionPanel._permissionPopup
+ );
+ gPermissionPanel._identityPermissionBox.click();
+ await shown;
+ let labelText = SitePermissions.getPermissionLabel("focus-tab-by-prompt");
+ let permissionsList = document.getElementById(
+ "permission-popup-permission-list"
+ );
+ let label = permissionsList.querySelector(
+ ".permission-popup-permission-label"
+ );
+ is(label.textContent, labelText);
+ gPermissionPanel._permissionPopup.hidePopup();
+
+ // Check if the identity icon signals granted permission.
+ ok(
+ gPermissionPanel._identityPermissionBox.hasAttribute("hasPermissions"),
+ "identity-box signals granted permissions"
+ );
+
+ let openedTabSelectedPromise = BrowserTestUtils.waitForAttribute(
+ "selected",
+ openedTab,
+ "true"
+ );
+ // switch to other tab again
+ await BrowserTestUtils.switchTab(gBrowser, firstTab);
+
+ // This is sync in non-e10s, but in e10s we need to wait for this, so yield anyway.
+ // Note that the switchTab promise doesn't actually guarantee anything about *which*
+ // tab ends up as selected when its event fires, so using that here wouldn't work.
+ await openedTabSelectedPromise;
+ // should be switched back
+ ok(openedTab.selected, "Ta-dah, the other tab should now be selected again!");
+
+ // In e10s, with the conformant promise scheduling, we have to wait for next tick
+ // to ensure that the prompt is open before removing the opened tab, because the
+ // promise callback of 'openedTabSelectedPromise' could be done at the middle of
+ // RemotePrompt.openTabPrompt() while 'DOMModalDialogClosed' event is fired.
+ await TestUtils.waitForTick();
+
+ BrowserTestUtils.removeTab(openedTab);
+});
+
+add_task(async function test_new_modal_ui() {
+ // We're intentionally testing the new modal mechanism, so make sure it's enabled.
+ await SpecialPowers.pushPrefEnv({
+ set: [["prompts.contentPromptSubDialog", true]],
+ });
+
+ // Make sure we clear the focus tab permission set in the previous test
+ PermissionTestUtils.remove(pageWithAlert, "focus-tab-by-prompt");
+
+ let firstTab = gBrowser.selectedTab;
+ // load page that opens prompt when page is hidden
+ let openedTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ pageWithAlert,
+ true
+ );
+ let openedTabGotAttentionPromise = BrowserTestUtils.waitForAttribute(
+ "attention",
+ openedTab
+ );
+ // switch away from that tab again - this triggers the alert.
+ await BrowserTestUtils.switchTab(gBrowser, firstTab);
+ // ... but that's async on e10s...
+ await openedTabGotAttentionPromise;
+ // check for attention attribute
+ is(
+ openedTab.hasAttribute("attention"),
+ true,
+ "Tab with alert should have 'attention' attribute."
+ );
+ ok(!openedTab.selected, "Tab with alert should not be selected");
+
+ // switch tab back, and check the checkbox is displayed:
+ await BrowserTestUtils.switchTab(gBrowser, openedTab);
+ // check the prompt is there
+ let promptElements = openedTab.linkedBrowser.parentNode.querySelectorAll(
+ ".content-prompt-dialog"
+ );
+
+ let dialogBox = gBrowser.getTabDialogBox(openedTab.linkedBrowser);
+ let contentPromptManager = dialogBox.getContentDialogManager();
+ is(promptElements.length, 1, "There should be 1 prompt");
+ is(
+ contentPromptManager._dialogs.length,
+ 1,
+ "Content prompt manager should have 1 dialog box."
+ );
+
+ // make sure the checkbox appears and that the permission for allowing tab switching
+ // is set when the checkbox is tickted and the dialog is accepted
+ let dialog = contentPromptManager._dialogs[0];
+
+ await dialog._dialogReady;
+
+ let dialogDoc = dialog._frame.contentWindow.document;
+ let checkbox = dialogDoc.querySelector("checkbox[label*='example.com']");
+ let button = dialogDoc.querySelector("#commonDialog").getButton("accept");
+
+ ok(checkbox, "The checkbox should be there");
+ ok(!checkbox.checked, "Checkbox shouldn't be checked");
+
+ // tick box and accept dialog
+ checkbox.checked = true;
+ button.click();
+ // Wait for that click to actually be handled completely.
+ await new Promise(function (resolve) {
+ Services.tm.dispatchToMainThread(resolve);
+ });
+
+ ok(!contentPromptManager._dialogs.length, "Dialog should be closed");
+
+ // check permission is set
+ is(
+ Services.perms.ALLOW_ACTION,
+ PermissionTestUtils.testPermission(pageWithAlert, "focus-tab-by-prompt"),
+ "Tab switching should now be allowed"
+ );
+
+ // Check if the control center shows the correct permission.
+ let shown = BrowserTestUtils.waitForEvent(
+ window,
+ "popupshown",
+ true,
+ event => event.target == gPermissionPanel._permissionPopup
+ );
+ gPermissionPanel._identityPermissionBox.click();
+ await shown;
+ let labelText = SitePermissions.getPermissionLabel("focus-tab-by-prompt");
+ let permissionsList = document.getElementById(
+ "permission-popup-permission-list"
+ );
+ let label = permissionsList.querySelector(
+ ".permission-popup-permission-label"
+ );
+ is(label.textContent, labelText);
+ gPermissionPanel.hidePopup();
+
+ // Check if the identity icon signals granted permission.
+ ok(
+ gPermissionPanel._identityPermissionBox.hasAttribute("hasPermissions"),
+ "identity-permission-box signals granted permissions"
+ );
+
+ let openedTabSelectedPromise = BrowserTestUtils.waitForAttribute(
+ "selected",
+ openedTab,
+ "true"
+ );
+
+ // switch to other tab again
+ await BrowserTestUtils.switchTab(gBrowser, firstTab);
+
+ // This is sync in non-e10s, but in e10s we need to wait for this, so yield anyway.
+ // Note that the switchTab promise doesn't actually guarantee anything about *which*
+ // tab ends up as selected when its event fires, so using that here wouldn't work.
+ await openedTabSelectedPromise;
+
+ ok(contentPromptManager._dialogs.length === 1, "Dialog opened.");
+ dialog = contentPromptManager._dialogs[0];
+ await dialog._dialogReady;
+
+ // should be switched back
+ ok(openedTab.selected, "Ta-dah, the other tab should now be selected again!");
+
+ // In e10s, with the conformant promise scheduling, we have to wait for next tick
+ // to ensure that the prompt is open before removing the opened tab, because the
+ // promise callback of 'openedTabSelectedPromise' could be done at the middle of
+ // RemotePrompt.openTabPrompt() while 'DOMModalDialogClosed' event is fired.
+ // await TestUtils.waitForTick();
+
+ await BrowserTestUtils.removeTab(openedTab);
+});
diff --git a/browser/base/content/test/tabPrompts/browser_promptFocus.js b/browser/base/content/test/tabPrompts/browser_promptFocus.js
new file mode 100644
index 0000000000..89ca064c10
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_promptFocus.js
@@ -0,0 +1,170 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+// 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,
+ // 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);
+ 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."
+ );
+ });
+});
diff --git a/browser/base/content/test/tabPrompts/browser_prompt_closed_window.js b/browser/base/content/test/tabPrompts/browser_prompt_closed_window.js
new file mode 100644
index 0000000000..4db3286691
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_prompt_closed_window.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Check that if we loop prompts from a closed tab, they don't
+ * start showing up as window prompts.
+ */
+add_task(async function test_closed_tab_doesnt_show_prompt() {
+ let newWin = await BrowserTestUtils.openNewBrowserWindow();
+
+ // Get a promise for the initial, in-tab prompt:
+ let promptPromise = BrowserTestUtils.promiseAlertDialogOpen();
+ await ContentTask.spawn(newWin.gBrowser.selectedBrowser, [], function () {
+ // Don't want to block, so use setTimeout with 0 timeout:
+ content.setTimeout(
+ () =>
+ content.eval(
+ 'let i = 0; while (!prompt("Prompts a lot!") && i++ < 10);'
+ ),
+ 0
+ );
+ });
+ // wait for the first prompt to have appeared:
+ await promptPromise;
+
+ // Now close the containing tab, and check for windowed prompts appearing.
+ let opened = false;
+ let obs = () => {
+ opened = true;
+ };
+ Services.obs.addObserver(obs, "domwindowopened");
+ registerCleanupFunction(() =>
+ Services.obs.removeObserver(obs, "domwindowopened")
+ );
+ await BrowserTestUtils.closeWindow(newWin);
+
+ ok(!opened, "Should not have opened a prompt when closing the main window.");
+});
diff --git a/browser/base/content/test/tabPrompts/browser_switchTabPermissionPrompt.js b/browser/base/content/test/tabPrompts/browser_switchTabPermissionPrompt.js
new file mode 100644
index 0000000000..e803869b92
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_switchTabPermissionPrompt.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_check_file_prompt() {
+ let initialTab = gBrowser.selectedTab;
+ await BrowserTestUtils.withNewTab("about:blank", async browser => {
+ await BrowserTestUtils.switchTab(gBrowser, initialTab);
+
+ let testHelper = async function (uri, expectedValue) {
+ BrowserTestUtils.loadURIString(browser, uri);
+ await BrowserTestUtils.browserLoaded(browser, false, uri);
+ let dialogFinishedShowing = TestUtils.topicObserved(
+ "common-dialog-loaded"
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.setTimeout(() => {
+ content.alert("Hello");
+ }, 0);
+ });
+
+ let [dialogWin] = await dialogFinishedShowing;
+ let checkbox = dialogWin.document.getElementById("checkbox");
+ info("Got: " + checkbox.label);
+ ok(
+ checkbox.label.includes(expectedValue),
+ `Checkbox label should mention domain (${expectedValue}).`
+ );
+
+ dialogWin.document.querySelector("dialog").acceptDialog();
+ };
+
+ await testHelper("https://example.com/1", "example.com");
+ await testHelper("about:robots", "about:");
+ let file = Services.io.newFileURI(
+ Services.dirsvc.get("Desk", Ci.nsIFile)
+ ).spec;
+ await testHelper(file, "file://");
+ });
+});
diff --git a/browser/base/content/test/tabPrompts/browser_windowPrompt.js b/browser/base/content/test/tabPrompts/browser_windowPrompt.js
new file mode 100644
index 0000000000..535142f485
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/browser_windowPrompt.js
@@ -0,0 +1,259 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Check that the in-window modal dialogs work correctly.
+ */
+add_task(async function test_check_window_modal_prompt_service() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["prompts.windowPromptSubDialog", true]],
+ });
+ let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen();
+ // Avoid blocking the test on the (sync) alert by sticking it in a timeout:
+ setTimeout(
+ () => Services.prompt.alert(window, "Some title", "some message"),
+ 0
+ );
+ let dialogWin = await dialogPromise;
+
+ // Check dialog content:
+ is(
+ dialogWin.document.getElementById("infoTitle").textContent,
+ "Some title",
+ "Title should be correct."
+ );
+ ok(
+ !dialogWin.document.getElementById("infoTitle").hidden,
+ "Title should be shown."
+ );
+ is(
+ dialogWin.document.getElementById("infoBody").textContent,
+ "some message",
+ "Body text should be correct."
+ );
+
+ // Check circumstances of opening.
+ ok(
+ dialogWin?.docShell?.chromeEventHandler,
+ "Should have embedded the dialog."
+ );
+ for (let menu of document.querySelectorAll("menubar > menu")) {
+ ok(menu.disabled, `Menu ${menu.id} should be disabled.`);
+ }
+
+ let container = dialogWin.docShell.chromeEventHandler.closest("dialog");
+ let closedPromise = BrowserTestUtils.waitForMutationCondition(
+ container,
+ { childList: true, attributes: true },
+ () => !container.hasChildNodes() && !container.open
+ );
+
+ EventUtils.sendKey("ESCAPE");
+ await closedPromise;
+
+ await BrowserTestUtils.waitForMutationCondition(
+ document.querySelector("menubar > menu"),
+ { attributes: true },
+ () => !document.querySelector("menubar > menu").disabled
+ );
+
+ // Check we cleaned up:
+ for (let menu of document.querySelectorAll("menubar > menu")) {
+ ok(!menu.disabled, `Menu ${menu.id} should not be disabled anymore.`);
+ }
+});
+
+/**
+ * Check that the dialog's own closing methods being invoked don't break things.
+ */
+add_task(async function test_check_window_modal_prompt_service() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["prompts.windowPromptSubDialog", true]],
+ });
+ let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen();
+ // Avoid blocking the test on the (sync) alert by sticking it in a timeout:
+ setTimeout(
+ () => Services.prompt.alert(window, "Some title", "some message"),
+ 0
+ );
+ let dialogWin = await dialogPromise;
+
+ // Check circumstances of opening.
+ ok(
+ dialogWin?.docShell?.chromeEventHandler,
+ "Should have embedded the dialog."
+ );
+
+ let container = dialogWin.docShell.chromeEventHandler.closest("dialog");
+ let closedPromise = BrowserTestUtils.waitForMutationCondition(
+ container,
+ { childList: true, attributes: true },
+ () => !container.hasChildNodes() && !container.open
+ );
+
+ // This can also be invoked by the user if the escape key is handled
+ // outside of our embedded dialog.
+ container.close();
+ await closedPromise;
+
+ // Check we cleaned up:
+ for (let menu of document.querySelectorAll("menubar > menu")) {
+ ok(!menu.disabled, `Menu ${menu.id} should not be disabled anymore.`);
+ }
+});
+
+add_task(async function test_check_multiple_prompts() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["prompts.windowPromptSubDialog", true]],
+ });
+ let container = document.getElementById("window-modal-dialog");
+ let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen();
+
+ let firstDialogClosedPromise = new Promise(resolve => {
+ // Avoid blocking the test on the (sync) alert by sticking it in a timeout:
+ setTimeout(() => {
+ Services.prompt.alert(window, "Some title", "some message");
+ resolve();
+ }, 0);
+ });
+ let dialogWin = await dialogPromise;
+
+ // Check circumstances of opening.
+ ok(
+ dialogWin?.docShell?.chromeEventHandler,
+ "Should have embedded the dialog."
+ );
+ is(container.childElementCount, 1, "Should only have 1 dialog in the DOM.");
+
+ let secondDialogClosedPromise = new Promise(resolve => {
+ // Avoid blocking the test on the (sync) alert by sticking it in a timeout:
+ setTimeout(() => {
+ Services.prompt.alert(window, "Another title", "another message");
+ resolve();
+ }, 0);
+ });
+
+ dialogPromise = BrowserTestUtils.promiseAlertDialogOpen();
+
+ info("Accepting dialog");
+ dialogWin.document.querySelector("dialog").acceptDialog();
+
+ let oldWin = dialogWin;
+
+ info("Second dialog should automatically come up.");
+ dialogWin = await dialogPromise;
+
+ isnot(oldWin, dialogWin, "Opened a new dialog.");
+ ok(container.open, "Dialog should be open.");
+
+ info("Now close the second dialog.");
+ dialogWin.document.querySelector("dialog").acceptDialog();
+
+ await firstDialogClosedPromise;
+ await secondDialogClosedPromise;
+
+ await BrowserTestUtils.waitForMutationCondition(
+ container,
+ { childList: true, attributes: true },
+ () => !container.hasChildNodes() && !container.open
+ );
+ // Check we cleaned up:
+ for (let menu of document.querySelectorAll("menubar > menu")) {
+ ok(!menu.disabled, `Menu ${menu.id} should not be disabled anymore.`);
+ }
+});
+
+/**
+ * Check that the in-window modal dialogs un-minimizes windows when necessary.
+ */
+add_task(async function test_check_minimize_response() {
+ // Window minimization doesn't necessarily work on Linux...
+ if (AppConstants.platform == "linux") {
+ return;
+ }
+ await SpecialPowers.pushPrefEnv({
+ set: [["prompts.windowPromptSubDialog", true]],
+ });
+
+ let promiseSizeModeChange = BrowserTestUtils.waitForEvent(
+ window,
+ "sizemodechange"
+ );
+ window.minimize();
+ await promiseSizeModeChange;
+ is(window.windowState, window.STATE_MINIMIZED, "Should be minimized.");
+
+ promiseSizeModeChange = BrowserTestUtils.waitForEvent(
+ window,
+ "sizemodechange"
+ );
+ let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen();
+ // Use an async alert to avoid blocking.
+ Services.prompt.asyncAlert(
+ window.browsingContext,
+ Ci.nsIPrompt.MODAL_TYPE_INTERNAL_WINDOW,
+ "Some title",
+ "some message"
+ );
+ let dialogWin = await dialogPromise;
+ await promiseSizeModeChange;
+
+ isnot(
+ window.windowState,
+ window.STATE_MINIMIZED,
+ "Should no longer be minimized."
+ );
+
+ // Check dialog content:
+ is(
+ dialogWin.document.getElementById("infoTitle").textContent,
+ "Some title",
+ "Title should be correct."
+ );
+
+ let container = dialogWin.docShell.chromeEventHandler.closest("dialog");
+ let closedPromise = BrowserTestUtils.waitForMutationCondition(
+ container,
+ { childList: true, attributes: true },
+ () => !container.hasChildNodes() && !container.open
+ );
+
+ EventUtils.sendKey("ESCAPE");
+ await closedPromise;
+
+ await BrowserTestUtils.waitForMutationCondition(
+ document.querySelector("menubar > menu"),
+ { attributes: true },
+ () => !document.querySelector("menubar > menu").disabled
+ );
+});
+
+/**
+ * Tests that we get a closed callback even when closing the prompt before the
+ * underlying SubDialog has fully opened.
+ */
+add_task(async function test_closed_callback() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["prompts.windowPromptSubDialog", true]],
+ });
+
+ let promptClosedPromise = Services.prompt.asyncAlert(
+ window.browsingContext,
+ Services.prompt.MODAL_TYPE_INTERNAL_WINDOW,
+ "Hello",
+ "Hello, World!"
+ );
+
+ let dialog = gDialogBox._dialog;
+ ok(dialog, "gDialogBox should have a dialog");
+
+ // Directly close the dialog without waiting for it to initialize.
+ dialog.close();
+
+ info("Waiting for prompt close");
+ await promptClosedPromise;
+
+ ok(!gDialogBox._dialog, "gDialogBox should no longer have a dialog");
+});
diff --git a/browser/base/content/test/tabPrompts/file_beforeunload_stop.html b/browser/base/content/test/tabPrompts/file_beforeunload_stop.html
new file mode 100644
index 0000000000..7273e60c65
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/file_beforeunload_stop.html
@@ -0,0 +1,8 @@
+<body>
+ <p>I will ask not to be closed.</p>
+ <script>
+ window.onbeforeunload = function() {
+ return "true";
+ };
+ </script>
+</body>
diff --git a/browser/base/content/test/tabPrompts/openPromptOffTimeout.html b/browser/base/content/test/tabPrompts/openPromptOffTimeout.html
new file mode 100644
index 0000000000..5dfd8cbeff
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/openPromptOffTimeout.html
@@ -0,0 +1,10 @@
+<body>
+This page opens an alert box when the page is hidden.
+<script>
+document.addEventListener("visibilitychange", () => {
+ if (document.hidden) {
+ alert("You hid my page!");
+ }
+});
+</script>
+</body>
diff --git a/browser/base/content/test/tabPrompts/redirect-crossDomain-tabTitle-update.html b/browser/base/content/test/tabPrompts/redirect-crossDomain-tabTitle-update.html
new file mode 100644
index 0000000000..773b3e47d9
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/redirect-crossDomain-tabTitle-update.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8">
+ <title>example.com</title>
+ </head>
+ <body>
+ I am a friendly test page!
+ <script>
+ document.title="tab title update 1";
+ window.location.href="https://example.org:443/browser/browser/base/content/test/tabPrompts/auth-route.sjs";
+ document.title ="tab title update 2";
+ </script>
+ </body>
+</html>
diff --git a/browser/base/content/test/tabPrompts/redirect-crossDomain.html b/browser/base/content/test/tabPrompts/redirect-crossDomain.html
new file mode 100644
index 0000000000..ebae2c060a
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/redirect-crossDomain.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8">
+ <title>example.com</title>
+ </head>
+ <body>
+ I am a friendly test page!
+ <script>
+ window.location.href="https://example.org:443/browser/browser/base/content/test/tabPrompts/auth-route.sjs";
+ </script>
+ </body>
+</html>
diff --git a/browser/base/content/test/tabPrompts/redirect-sameDomain.html b/browser/base/content/test/tabPrompts/redirect-sameDomain.html
new file mode 100644
index 0000000000..2e50689d1e
--- /dev/null
+++ b/browser/base/content/test/tabPrompts/redirect-sameDomain.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8">
+ <title>example.com</title>
+ </head>
+ <body>
+ I am a friendly test page!
+ <script>
+ window.location.href="https://test1.example.com:443/browser/browser/base/content/test/tabPrompts/auth-route.sjs";
+ </script>
+ </body>
+</html>