summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/captivePortal
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base/content/test/captivePortal')
-rw-r--r--browser/base/content/test/captivePortal/browser.ini12
-rw-r--r--browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js125
-rw-r--r--browser/base/content/test/captivePortal/browser_CaptivePortalWatcher_1.js108
-rw-r--r--browser/base/content/test/captivePortal/browser_captivePortalTabReference.js65
-rw-r--r--browser/base/content/test/captivePortal/browser_captivePortal_certErrorUI.js221
-rw-r--r--browser/base/content/test/captivePortal/browser_captivePortal_https_only.js73
-rw-r--r--browser/base/content/test/captivePortal/browser_closeCapPortalTabCanonicalURL.js152
-rw-r--r--browser/base/content/test/captivePortal/head.js260
8 files changed, 1016 insertions, 0 deletions
diff --git a/browser/base/content/test/captivePortal/browser.ini b/browser/base/content/test/captivePortal/browser.ini
new file mode 100644
index 0000000000..37b72ab758
--- /dev/null
+++ b/browser/base/content/test/captivePortal/browser.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+support-files =
+ head.js
+
+[browser_CaptivePortalWatcher.js]
+skip-if = os == "win" # Bug 1313894
+[browser_CaptivePortalWatcher_1.js]
+skip-if = os == "win" # Bug 1313894
+[browser_captivePortalTabReference.js]
+[browser_captivePortal_certErrorUI.js]
+[browser_captivePortal_https_only.js]
+[browser_closeCapPortalTabCanonicalURL.js]
diff --git a/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js
new file mode 100644
index 0000000000..aeafae21d8
--- /dev/null
+++ b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher.js
@@ -0,0 +1,125 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+"use strict";
+
+// Bug 1318389 - This test does a lot of window and tab manipulation,
+// causing it to take a long time on debug.
+requestLongerTimeout(2);
+
+add_task(setupPrefsAndRecentWindowBehavior);
+
+// Each of the test cases below is run twice: once for login-success and once
+// for login-abort (aSuccess set to true and false respectively).
+let testCasesForBothSuccessAndAbort = [
+ /**
+ * A portal is detected when there's no browser window, then a browser
+ * window is opened, then the portal is freed.
+ * The portal tab should be added and focused when the window is
+ * opened, and closed automatically when the success event is fired.
+ * The captive portal notification should be shown when the window is
+ * opened, and closed automatically when the success event is fired.
+ */
+ async function test_detectedWithNoBrowserWindow_Open(aSuccess) {
+ await portalDetected();
+ let win = await focusWindowAndWaitForPortalUI();
+ await freePortal(aSuccess);
+ ensureNoPortalTab(win);
+ ensureNoPortalNotification(win);
+ await closeWindowAndWaitForWindowActivate(win);
+ },
+
+ /**
+ * A portal is detected when multiple browser windows are open but none
+ * have focus. A browser window is focused, then the portal is freed.
+ * The portal tab should be added and focused when the window is
+ * focused, and closed automatically when the success event is fired.
+ * The captive portal notification should be shown in all windows upon
+ * detection, and closed automatically when the success event is fired.
+ */
+ async function test_detectedWithNoBrowserWindow_Focused(aSuccess) {
+ let win1 = await openWindowAndWaitForFocus();
+ let win2 = await openWindowAndWaitForFocus();
+ // Defocus both windows.
+ await SimpleTest.promiseFocus(window);
+
+ await portalDetected();
+
+ // Notification should be shown in both windows.
+ ensurePortalNotification(win1);
+ ensureNoPortalTab(win1);
+ ensurePortalNotification(win2);
+ ensureNoPortalTab(win2);
+
+ await focusWindowAndWaitForPortalUI(false, win2);
+
+ await freePortal(aSuccess);
+
+ ensureNoPortalNotification(win1);
+ ensureNoPortalTab(win2);
+ ensureNoPortalNotification(win2);
+
+ await closeWindowAndWaitForWindowActivate(win2);
+ // No need to wait for xul-window-visible: after win2 is closed, focus
+ // is restored to the default window and win1 remains in the background.
+ await BrowserTestUtils.closeWindow(win1);
+ },
+
+ /**
+ * A portal is detected when there's no browser window, then a browser
+ * window is opened, then the portal is freed.
+ * The recheck triggered when the browser window is opened takes a
+ * long time. No portal tab should be added.
+ * The captive portal notification should be shown when the window is
+ * opened, and closed automatically when the success event is fired.
+ */
+ async function test_detectedWithNoBrowserWindow_LongRecheck(aSuccess) {
+ await portalDetected();
+ let win = await focusWindowAndWaitForPortalUI(true);
+ await freePortal(aSuccess);
+ ensureNoPortalTab(win);
+ ensureNoPortalNotification(win);
+ await closeWindowAndWaitForWindowActivate(win);
+ },
+
+ /**
+ * A portal is detected when there's no browser window, and the
+ * portal is freed before a browser window is opened. No portal
+ * UI should be shown when a browser window is opened.
+ */
+ async function test_detectedWithNoBrowserWindow_GoneBeforeOpen(aSuccess) {
+ await portalDetected();
+ await freePortal(aSuccess);
+ let win = await openWindowAndWaitForFocus();
+ // Wait for a while to make sure no UI is shown.
+ await new Promise(resolve => {
+ setTimeout(resolve, 1000);
+ });
+ ensureNoPortalTab(win);
+ ensureNoPortalNotification(win);
+ await closeWindowAndWaitForWindowActivate(win);
+ },
+
+ /**
+ * A portal is detected when a browser window has focus. No portal tab should
+ * be opened. A notification bar should be displayed in all browser windows.
+ */
+ async function test_detectedWithFocus(aSuccess) {
+ let win1 = await openWindowAndWaitForFocus();
+ let win2 = await openWindowAndWaitForFocus();
+ await portalDetected();
+ ensureNoPortalTab(win1);
+ ensureNoPortalTab(win2);
+ ensurePortalNotification(win1);
+ ensurePortalNotification(win2);
+ await freePortal(aSuccess);
+ ensureNoPortalNotification(win1);
+ ensureNoPortalNotification(win2);
+ await BrowserTestUtils.closeWindow(win2);
+ await BrowserTestUtils.closeWindow(win1);
+ await waitForBrowserWindowActive(window);
+ },
+];
+
+for (let testcase of testCasesForBothSuccessAndAbort) {
+ add_task(testcase.bind(null, true));
+ add_task(testcase.bind(null, false));
+}
diff --git a/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher_1.js b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher_1.js
new file mode 100644
index 0000000000..6c6cc5f438
--- /dev/null
+++ b/browser/base/content/test/captivePortal/browser_CaptivePortalWatcher_1.js
@@ -0,0 +1,108 @@
+"use strict";
+
+add_task(setupPrefsAndRecentWindowBehavior);
+
+let testcases = [
+ /**
+ * A portal is detected when there's no browser window,
+ * then a browser window is opened, and the portal is logged into
+ * and redirects to a different page. The portal tab should be added
+ * and focused when the window is opened, and left open after login
+ * since it redirected.
+ */
+ async function test_detectedWithNoBrowserWindow_Redirect() {
+ await portalDetected();
+ let win = await focusWindowAndWaitForPortalUI();
+ let browser = win.gBrowser.selectedTab.linkedBrowser;
+ let loadPromise = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ CANONICAL_URL_REDIRECTED
+ );
+ BrowserTestUtils.loadURIString(browser, CANONICAL_URL_REDIRECTED);
+ await loadPromise;
+ await freePortal(true);
+ ensurePortalTab(win);
+ ensureNoPortalNotification(win);
+ await closeWindowAndWaitForWindowActivate(win);
+ },
+
+ /**
+ * Test the various expected behaviors of the "Show Login Page" button
+ * in the captive portal notification. The button should be visible for
+ * all tabs except the captive portal tab, and when clicked, should
+ * ensure a captive portal tab is open and select it.
+ */
+ async function test_showLoginPageButton() {
+ let win = await openWindowAndWaitForFocus();
+ await portalDetected();
+ let notification = ensurePortalNotification(win);
+ testShowLoginPageButtonVisibility(notification, "visible");
+
+ function testPortalTabSelectedAndButtonNotVisible() {
+ is(
+ win.gBrowser.selectedTab,
+ tab,
+ "The captive portal tab should be selected."
+ );
+ testShowLoginPageButtonVisibility(notification, "hidden");
+ }
+
+ let button = notification.buttonContainer.querySelector(
+ "button.notification-button"
+ );
+ async function clickButtonAndExpectNewPortalTab() {
+ let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
+ button.click();
+ let tab = await p;
+ is(
+ win.gBrowser.selectedTab,
+ tab,
+ "The captive portal tab should be selected."
+ );
+ return tab;
+ }
+
+ // Simulate clicking the button. The portal tab should be opened and
+ // selected and the button should hide.
+ let tab = await clickButtonAndExpectNewPortalTab();
+ testPortalTabSelectedAndButtonNotVisible();
+
+ // Close the tab. The button should become visible.
+ BrowserTestUtils.removeTab(tab);
+ ensureNoPortalTab(win);
+ testShowLoginPageButtonVisibility(notification, "visible");
+
+ // When the button is clicked, a new portal tab should be opened and
+ // selected.
+ tab = await clickButtonAndExpectNewPortalTab();
+
+ // Open another arbitrary tab. The button should become visible. When it's clicked,
+ // the portal tab should be selected.
+ let anotherTab = await BrowserTestUtils.openNewForegroundTab(win.gBrowser);
+ testShowLoginPageButtonVisibility(notification, "visible");
+ button.click();
+ is(
+ win.gBrowser.selectedTab,
+ tab,
+ "The captive portal tab should be selected."
+ );
+
+ // Close the portal tab and select the arbitrary tab. The button should become
+ // visible and when it's clicked, a new portal tab should be opened.
+ BrowserTestUtils.removeTab(tab);
+ win.gBrowser.selectedTab = anotherTab;
+ testShowLoginPageButtonVisibility(notification, "visible");
+ tab = await clickButtonAndExpectNewPortalTab();
+
+ BrowserTestUtils.removeTab(anotherTab);
+ await freePortal(true);
+ ensureNoPortalTab(win);
+ ensureNoPortalNotification(win);
+ await closeWindowAndWaitForWindowActivate(win);
+ },
+];
+
+for (let testcase of testcases) {
+ add_task(testcase);
+}
diff --git a/browser/base/content/test/captivePortal/browser_captivePortalTabReference.js b/browser/base/content/test/captivePortal/browser_captivePortalTabReference.js
new file mode 100644
index 0000000000..b630f35149
--- /dev/null
+++ b/browser/base/content/test/captivePortal/browser_captivePortalTabReference.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const CPS = Cc["@mozilla.org/network/captive-portal-service;1"].getService(
+ Ci.nsICaptivePortalService
+);
+
+async function checkCaptivePortalTabReference(evt, currState) {
+ await portalDetected();
+ let errorTab = await openCaptivePortalErrorTab();
+ let portalTab = await openCaptivePortalLoginTab(errorTab);
+
+ // Release the reference held to the portal tab by sending success/abort events.
+ Services.obs.notifyObservers(null, evt);
+ await TestUtils.waitForCondition(
+ () => CPS.state == currState,
+ "Captive portal has been released"
+ );
+ gBrowser.removeTab(errorTab);
+
+ await portalDetected();
+ ok(CPS.state == CPS.LOCKED_PORTAL, "Captive portal is locked again");
+ errorTab = await openCaptivePortalErrorTab();
+ let portalTab2 = await openCaptivePortalLoginTab(errorTab);
+ ok(
+ portalTab != portalTab2,
+ "waitForNewTab in openCaptivePortalLoginTab should not have completed at this point if references were held to the old captive portal tab after login/abort."
+ );
+ gBrowser.removeTab(portalTab);
+ gBrowser.removeTab(portalTab2);
+
+ let errorTabReloaded = BrowserTestUtils.waitForErrorPage(
+ errorTab.linkedBrowser
+ );
+ Services.obs.notifyObservers(null, "captive-portal-login-success");
+ await errorTabReloaded;
+
+ gBrowser.removeTab(errorTab);
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["captivedetect.canonicalURL", CANONICAL_URL],
+ ["captivedetect.canonicalContent", CANONICAL_CONTENT],
+ ],
+ });
+});
+
+let capPortalStates = [
+ {
+ evt: "captive-portal-login-success",
+ state: CPS.UNLOCKED_PORTAL,
+ },
+ {
+ evt: "captive-portal-login-abort",
+ state: CPS.UNKNOWN,
+ },
+];
+
+for (let elem of capPortalStates) {
+ add_task(checkCaptivePortalTabReference.bind(null, elem.evt, elem.state));
+}
diff --git a/browser/base/content/test/captivePortal/browser_captivePortal_certErrorUI.js b/browser/base/content/test/captivePortal/browser_captivePortal_certErrorUI.js
new file mode 100644
index 0000000000..d23125a627
--- /dev/null
+++ b/browser/base/content/test/captivePortal/browser_captivePortal_certErrorUI.js
@@ -0,0 +1,221 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["captivedetect.canonicalURL", CANONICAL_URL],
+ ["captivedetect.canonicalContent", CANONICAL_CONTENT],
+ ],
+ });
+});
+
+// This tests the alternate cert error UI when we are behind a captive portal.
+add_task(async function checkCaptivePortalCertErrorUI() {
+ info(
+ "Checking that the alternate cert error UI is shown when we are behind a captive portal"
+ );
+
+ // Open a second window in the background. Later, we'll check that
+ // when we click the button to open the captive portal tab, the tab
+ // only opens in the active window and not in the background one.
+ let secondWindow = await openWindowAndWaitForFocus();
+ await SimpleTest.promiseFocus(window);
+
+ await portalDetected();
+
+ // Check that we didn't open anything in the background window.
+ ensureNoPortalTab(secondWindow);
+
+ let tab = await openCaptivePortalErrorTab();
+ let browser = tab.linkedBrowser;
+ let portalTabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ CANONICAL_URL
+ );
+
+ await SpecialPowers.spawn(browser, [], async () => {
+ let doc = content.document;
+ let loginButton = doc.getElementById("openPortalLoginPageButton");
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.is_visible(loginButton),
+ "Captive portal error page UI is visible"
+ );
+
+ if (!Services.focus.focusedElement == loginButton) {
+ await ContentTaskUtils.waitForEvent(loginButton, "focus");
+ }
+
+ Assert.ok(true, "openPortalLoginPageButton has focus");
+ info("Clicking the Open Login Page button");
+ await EventUtils.synthesizeMouseAtCenter(loginButton, {}, content);
+ });
+
+ let portalTab = await portalTabPromise;
+ is(
+ gBrowser.selectedTab,
+ portalTab,
+ "Login page should be open in a new foreground tab."
+ );
+
+ // Check that we didn't open anything in the background window.
+ ensureNoPortalTab(secondWindow);
+
+ // Make sure clicking the "Open Login Page" button again focuses the existing portal tab.
+ await BrowserTestUtils.switchTab(gBrowser, tab);
+ // Passing an empty function to BrowserTestUtils.switchTab lets us wait for an arbitrary
+ // tab switch.
+ portalTabPromise = BrowserTestUtils.switchTab(gBrowser, () => {});
+ await SpecialPowers.spawn(browser, [], async () => {
+ info("Clicking the Open Login Page button.");
+ let loginButton = content.document.getElementById(
+ "openPortalLoginPageButton"
+ );
+ await EventUtils.synthesizeMouseAtCenter(loginButton, {}, content);
+ });
+
+ info("Opening captive portal login page");
+ let portalTab2 = await portalTabPromise;
+ is(portalTab2, portalTab, "The existing portal tab should be focused.");
+
+ // Check that we didn't open anything in the background window.
+ ensureNoPortalTab(secondWindow);
+
+ let portalTabClosing = BrowserTestUtils.waitForTabClosing(portalTab);
+ let errorTabReloaded = BrowserTestUtils.waitForErrorPage(browser);
+
+ Services.obs.notifyObservers(null, "captive-portal-login-success");
+ await portalTabClosing;
+
+ info(
+ "Waiting for error tab to be reloaded after the captive portal was freed."
+ );
+ await errorTabReloaded;
+ await SpecialPowers.spawn(browser, [], () => {
+ let doc = content.document;
+ ok(
+ !doc.body.classList.contains("captiveportal"),
+ "Captive portal error page UI is not visible."
+ );
+ });
+
+ await BrowserTestUtils.removeTab(tab);
+ await BrowserTestUtils.closeWindow(secondWindow);
+});
+
+add_task(async function testCaptivePortalAdvancedPanel() {
+ info(
+ "Checking that the advanced section of the about:certerror UI is shown when we are behind a captive portal."
+ );
+ await portalDetected();
+ let tab = await openCaptivePortalErrorTab();
+ let browser = tab.linkedBrowser;
+
+ const waitForLocationChange = (async () => {
+ await BrowserTestUtils.waitForLocationChange(gBrowser, BAD_CERT_PAGE);
+ info("(waitForLocationChange resolved)");
+ })();
+ await SpecialPowers.spawn(browser, [BAD_CERT_PAGE], async expectedURL => {
+ const doc = content.document;
+ let advancedButton = doc.getElementById("advancedButton");
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.is_visible(advancedButton),
+ "Captive portal UI is visible"
+ );
+
+ info("Clicking on the advanced button");
+ const advPanel = doc.getElementById("badCertAdvancedPanel");
+ ok(
+ !ContentTaskUtils.is_visible(advPanel),
+ "Advanced panel is not yet visible"
+ );
+ await EventUtils.synthesizeMouseAtCenter(advancedButton, {}, content);
+ ok(ContentTaskUtils.is_visible(advPanel), "Advanced panel is now visible");
+
+ let advPanelContent = doc.getElementById("badCertTechnicalInfo");
+ ok(
+ ContentTaskUtils.is_visible(advPanelContent) &&
+ advPanelContent.textContent.includes("expired.example.com"),
+ "Advanced panel text content is visible"
+ );
+
+ let advPanelErrorCode = doc.getElementById("errorCode");
+ ok(
+ advPanelErrorCode.textContent,
+ "Cert error code is visible in the advanced panel"
+ );
+
+ // -
+
+ const advPanelExceptionButton = doc.getElementById("exceptionDialogButton");
+
+ function isOnCertErrorPage() {
+ return ContentTaskUtils.is_visible(advPanel);
+ }
+
+ ok(isOnCertErrorPage(), "On cert error page before adding exception");
+ ok(
+ advPanelExceptionButton.disabled,
+ "Exception button should start disabled"
+ );
+ await EventUtils.synthesizeMouseAtCenter(
+ advPanelExceptionButton,
+ {},
+ content
+ ); // Click
+ const clickTime = content.performance.now();
+ ok(
+ isOnCertErrorPage(),
+ "Still on cert error page because clicked too early"
+ );
+
+ // Now waitForCondition now that it's possible.
+ try {
+ await ContentTaskUtils.waitForCondition(
+ () => !advPanelExceptionButton.disabled,
+ "Wait for exception button enabled"
+ );
+ } catch (rejected) {
+ ok(false, rejected);
+ return;
+ }
+ ok(
+ !advPanelExceptionButton.disabled,
+ "Exception button should be enabled after waiting"
+ );
+ const msSinceClick = content.performance.now() - clickTime;
+ const expr = `${msSinceClick} > 1000`;
+ /* eslint-disable no-eval */
+ ok(eval(expr), `Exception button should stay disabled for ${expr} ms`);
+
+ await EventUtils.synthesizeMouseAtCenter(
+ advPanelExceptionButton,
+ {},
+ content
+ ); // Click
+ info("Clicked");
+ });
+ await waitForLocationChange;
+ info("Page reloaded after adding cert exception");
+
+ // Clear the certificate exception.
+ let certOverrideService = Cc[
+ "@mozilla.org/security/certoverride;1"
+ ].getService(Ci.nsICertOverrideService);
+ certOverrideService.clearValidityOverride("expired.example.com", -1, {});
+
+ info("After clearing cert override, asking for reload...");
+ const waitForErrorPage = BrowserTestUtils.waitForErrorPage(browser);
+ await SpecialPowers.spawn(browser, [], async () => {
+ info("reload...");
+ content.location.reload();
+ });
+ info("waitForErrorPage...");
+ await waitForErrorPage;
+
+ info("removeTab...");
+ await BrowserTestUtils.removeTab(tab);
+ info("Done!");
+});
diff --git a/browser/base/content/test/captivePortal/browser_captivePortal_https_only.js b/browser/base/content/test/captivePortal/browser_captivePortal_https_only.js
new file mode 100644
index 0000000000..789d392107
--- /dev/null
+++ b/browser/base/content/test/captivePortal/browser_captivePortal_https_only.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+const { PermissionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PermissionTestUtils.sys.mjs"
+);
+const testPath = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com"
+);
+const CANONICAL_URI = Services.io.newURI(testPath);
+const PERMISSION_NAME = "https-only-load-insecure";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ // That changes the canoncicalURL from "http://{server}/captive-detect/success.txt"
+ // to http://example.com
+ set: [
+ ["captivedetect.canonicalURL", testPath],
+ ["dom.security.https_only_mode", true],
+ ],
+ });
+});
+
+// This test checks if https-only exempts the canoncial uri.
+add_task(async function checkCaptivePortalExempt() {
+ await portalDetected();
+ info("Checking that the canonical uri is exempt by https-only mode");
+ let tab = await openCaptivePortalErrorTab();
+ let browser = tab.linkedBrowser;
+ let portalTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, testPath);
+
+ await SpecialPowers.spawn(browser, [], async () => {
+ let doc = content.document;
+ let loginButton = doc.getElementById("openPortalLoginPageButton");
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.is_visible(loginButton),
+ "Captive portal error page UI is visible"
+ );
+
+ if (!Services.focus.focusedElement == loginButton) {
+ await ContentTaskUtils.waitForEvent(loginButton, "focus");
+ }
+
+ Assert.ok(true, "openPortalLoginPageButton has focus");
+ info("Clicking the Open Login Page button");
+ await EventUtils.synthesizeMouseAtCenter(loginButton, {}, content);
+ });
+ is(
+ PermissionTestUtils.testPermission(CANONICAL_URI, PERMISSION_NAME),
+ Services.perms.ALLOW_ACTION,
+ "Check permission in perm. manager if canoncial uri is set as exempt."
+ );
+ let portalTab = await portalTabPromise;
+ is(
+ gBrowser.selectedTab,
+ portalTab,
+ "Login page should be open in a new foreground tab."
+ );
+ is(
+ gBrowser.currentURI.spec,
+ testPath,
+ "Opened the right URL without upgrading it."
+ );
+ // Close all tabs
+ await BrowserTestUtils.removeTab(portalTab);
+ let tabReloaded = BrowserTestUtils.waitForErrorPage(tab.linkedBrowser);
+ Services.obs.notifyObservers(null, "captive-portal-login-success");
+ await tabReloaded;
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/base/content/test/captivePortal/browser_closeCapPortalTabCanonicalURL.js b/browser/base/content/test/captivePortal/browser_closeCapPortalTabCanonicalURL.js
new file mode 100644
index 0000000000..0457dab1c0
--- /dev/null
+++ b/browser/base/content/test/captivePortal/browser_closeCapPortalTabCanonicalURL.js
@@ -0,0 +1,152 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+const LOGIN_LINK = `<html><body><a href="/unlock">login</a></body></html>`;
+const LOGIN_URL = "http://localhost:8080/login";
+const CANONICAL_SUCCESS_URL = "http://localhost:8080/success";
+const CPS = Cc["@mozilla.org/network/captive-portal-service;1"].getService(
+ Ci.nsICaptivePortalService
+);
+
+let server;
+let loginPageShown = false;
+
+function redirectHandler(request, response) {
+ if (loginPageShown) {
+ return;
+ }
+ response.setStatusLine(request.httpVersion, 302, "captive");
+ response.setHeader("Content-Type", "text/html");
+ response.setHeader("Location", LOGIN_URL);
+}
+
+function loginHandler(request, response) {
+ response.setHeader("Content-Type", "text/html");
+ response.bodyOutputStream.write(LOGIN_LINK, LOGIN_LINK.length);
+ loginPageShown = true;
+}
+
+function unlockHandler(request, response) {
+ response.setStatusLine(request.httpVersion, 302, "login complete");
+ response.setHeader("Content-Type", "text/html");
+ response.setHeader("Location", CANONICAL_SUCCESS_URL);
+}
+
+add_setup(async function () {
+ // Set up a mock server for handling captive portal redirect.
+ server = new HttpServer();
+ server.registerPathHandler("/success", redirectHandler);
+ server.registerPathHandler("/login", loginHandler);
+ server.registerPathHandler("/unlock", unlockHandler);
+ server.start(8080);
+ info("Mock server is now set up for captive portal redirect");
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["captivedetect.canonicalURL", CANONICAL_SUCCESS_URL],
+ ["captivedetect.canonicalContent", CANONICAL_CONTENT],
+ ],
+ });
+});
+
+// This test checks if the captive portal tab is removed after the
+// sucess/abort events are fired, assuming the tab has already redirected
+// to the canonical URL before they are fired.
+add_task(async function checkCaptivePortalTabCloseOnCanonicalURL_one() {
+ await portalDetected();
+ let errorTab = await openCaptivePortalErrorTab();
+ let tab = await openCaptivePortalLoginTab(errorTab, LOGIN_URL);
+ let browser = tab.linkedBrowser;
+
+ let redirectedToCanonicalURL = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ CANONICAL_SUCCESS_URL
+ );
+ let errorPageReloaded = BrowserTestUtils.waitForErrorPage(
+ errorTab.linkedBrowser
+ );
+
+ await SpecialPowers.spawn(browser, [], async () => {
+ let doc = content.document;
+ let loginButton = doc.querySelector("a");
+ await ContentTaskUtils.waitForCondition(
+ () => loginButton,
+ "Login button on the captive portal tab is visible"
+ );
+ info("Clicking the login button on the captive portal tab page");
+ await EventUtils.synthesizeMouseAtCenter(loginButton, {}, content);
+ });
+
+ await redirectedToCanonicalURL;
+ info(
+ "Re-direct to canonical URL in the captive portal tab was succcessful after login"
+ );
+
+ let tabClosed = BrowserTestUtils.waitForTabClosing(tab);
+ Services.obs.notifyObservers(null, "captive-portal-login-success");
+ await tabClosed;
+ info(
+ "Captive portal tab was closed on re-direct to canonical URL after login as expected"
+ );
+
+ await errorPageReloaded;
+ info("Captive portal error page was reloaded");
+ gBrowser.removeTab(errorTab);
+});
+
+// This test checks if the captive portal tab is removed on location change
+// i.e. when it is re-directed to the canonical URL long after success/abort
+// event handlers are executed.
+add_task(async function checkCaptivePortalTabCloseOnCanonicalURL_two() {
+ loginPageShown = false;
+ await portalDetected();
+ let errorTab = await openCaptivePortalErrorTab();
+ let tab = await openCaptivePortalLoginTab(errorTab, LOGIN_URL);
+ let browser = tab.linkedBrowser;
+
+ let redirectedToCanonicalURL = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ CANONICAL_SUCCESS_URL
+ );
+ let errorPageReloaded = BrowserTestUtils.waitForErrorPage(
+ errorTab.linkedBrowser
+ );
+
+ Services.obs.notifyObservers(null, "captive-portal-login-success");
+ await TestUtils.waitForCondition(
+ () => CPS.state == CPS.UNLOCKED_PORTAL,
+ "Captive portal is released"
+ );
+
+ let tabClosed = BrowserTestUtils.waitForTabClosing(tab);
+ await SpecialPowers.spawn(browser, [], async () => {
+ let doc = content.document;
+ let loginButton = doc.querySelector("a");
+ await ContentTaskUtils.waitForCondition(
+ () => loginButton,
+ "Login button on the captive portal tab is visible"
+ );
+ info("Clicking the login button on the captive portal tab page");
+ await EventUtils.synthesizeMouseAtCenter(loginButton, {}, content);
+ });
+
+ await redirectedToCanonicalURL;
+ info(
+ "Re-direct to canonical URL in the captive portal tab was succcessful after login"
+ );
+ await tabClosed;
+ info(
+ "Captive portal tab was closed on re-direct to canonical URL after login as expected"
+ );
+
+ await errorPageReloaded;
+ info("Captive portal error page was reloaded");
+ gBrowser.removeTab(errorTab);
+
+ // Stop the server.
+ await new Promise(r => server.stop(r));
+});
diff --git a/browser/base/content/test/captivePortal/head.js b/browser/base/content/test/captivePortal/head.js
new file mode 100644
index 0000000000..4e47c3012a
--- /dev/null
+++ b/browser/base/content/test/captivePortal/head.js
@@ -0,0 +1,260 @@
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "cps",
+ "@mozilla.org/network/captive-portal-service;1",
+ "nsICaptivePortalService"
+);
+
+const CANONICAL_CONTENT = "success";
+const CANONICAL_URL = "data:text/plain;charset=utf-8," + CANONICAL_CONTENT;
+const CANONICAL_URL_REDIRECTED = "data:text/plain;charset=utf-8,redirected";
+const PORTAL_NOTIFICATION_VALUE = "captive-portal-detected";
+const BAD_CERT_PAGE = "https://expired.example.com/";
+
+async function setupPrefsAndRecentWindowBehavior() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["captivedetect.canonicalURL", CANONICAL_URL],
+ ["captivedetect.canonicalContent", CANONICAL_CONTENT],
+ ],
+ });
+ // We need to test behavior when a portal is detected when there is no browser
+ // window, but we can't close the default window opened by the test harness.
+ // Instead, we deactivate CaptivePortalWatcher in the default window and
+ // exclude it using an attribute to mask its presence.
+ window.CaptivePortalWatcher.uninit();
+ window.document.documentElement.setAttribute("ignorecaptiveportal", "true");
+
+ registerCleanupFunction(function cleanUp() {
+ window.CaptivePortalWatcher.init();
+ window.document.documentElement.removeAttribute("ignorecaptiveportal");
+ });
+}
+
+async function portalDetected() {
+ Services.obs.notifyObservers(null, "captive-portal-login");
+ await TestUtils.waitForCondition(() => {
+ return cps.state == cps.LOCKED_PORTAL;
+ }, "Waiting for Captive Portal Service to update state after portal detected.");
+}
+
+async function freePortal(aSuccess) {
+ Services.obs.notifyObservers(
+ null,
+ "captive-portal-login-" + (aSuccess ? "success" : "abort")
+ );
+ await TestUtils.waitForCondition(() => {
+ return cps.state != cps.LOCKED_PORTAL;
+ }, "Waiting for Captive Portal Service to update state after portal freed.");
+}
+
+// If a window is provided, it will be focused. Otherwise, a new window
+// will be opened and focused.
+async function focusWindowAndWaitForPortalUI(aLongRecheck, win) {
+ // CaptivePortalWatcher triggers a recheck when a window gains focus. If
+ // the time taken for the check to complete is under PORTAL_RECHECK_DELAY_MS,
+ // a tab with the login page is opened and selected. If it took longer,
+ // no tab is opened. It's not reliable to time things in an async test,
+ // so use a delay threshold of -1 to simulate a long recheck (so that any
+ // amount of time is considered excessive), and a very large threshold to
+ // simulate a short recheck.
+ Services.prefs.setIntPref(
+ "captivedetect.portalRecheckDelayMS",
+ aLongRecheck ? -1 : 1000000
+ );
+
+ if (!win) {
+ win = await BrowserTestUtils.openNewBrowserWindow();
+ }
+ let windowActivePromise = waitForBrowserWindowActive(win);
+ win.focus();
+ await windowActivePromise;
+
+ // After a new window is opened, CaptivePortalWatcher asks for a recheck, and
+ // waits for it to complete. We need to manually tell it a recheck completed.
+ await TestUtils.waitForCondition(() => {
+ return win.CaptivePortalWatcher._waitingForRecheck;
+ }, "Waiting for CaptivePortalWatcher to trigger a recheck.");
+ Services.obs.notifyObservers(null, "captive-portal-check-complete");
+
+ let notification = ensurePortalNotification(win);
+
+ if (aLongRecheck) {
+ ensureNoPortalTab(win);
+ testShowLoginPageButtonVisibility(notification, "visible");
+ return win;
+ }
+
+ let tab = win.gBrowser.tabs[1];
+ if (tab.linkedBrowser.currentURI.spec != CANONICAL_URL) {
+ // The tab should load the canonical URL, wait for it.
+ await BrowserTestUtils.waitForLocationChange(win.gBrowser, CANONICAL_URL);
+ }
+ is(
+ win.gBrowser.selectedTab,
+ tab,
+ "The captive portal tab should be open and selected in the new window."
+ );
+ testShowLoginPageButtonVisibility(notification, "hidden");
+ return win;
+}
+
+function ensurePortalTab(win) {
+ // For the tests that call this function, it's enough to ensure there
+ // are two tabs in the window - the default tab and the portal tab.
+ is(
+ win.gBrowser.tabs.length,
+ 2,
+ "There should be a captive portal tab in the window."
+ );
+}
+
+function ensurePortalNotification(win) {
+ let notification = win.gNotificationBox.getNotificationWithValue(
+ PORTAL_NOTIFICATION_VALUE
+ );
+ isnot(
+ notification,
+ null,
+ "There should be a captive portal notification in the window."
+ );
+ return notification;
+}
+
+// Helper to test whether the "Show Login Page" is visible in the captive portal
+// notification (it should be hidden when the portal tab is selected).
+function testShowLoginPageButtonVisibility(notification, visibility) {
+ let showLoginPageButton = notification.buttonContainer.querySelector(
+ "button.notification-button"
+ );
+ // If the visibility property was never changed from default, it will be
+ // an empty string, so we pretend it's "visible" (effectively the same).
+ is(
+ showLoginPageButton.style.visibility || "visible",
+ visibility,
+ 'The "Show Login Page" button should be ' + visibility + "."
+ );
+}
+
+function ensureNoPortalTab(win) {
+ is(
+ win.gBrowser.tabs.length,
+ 1,
+ "There should be no captive portal tab in the window."
+ );
+}
+
+function ensureNoPortalNotification(win) {
+ is(
+ win.gNotificationBox.getNotificationWithValue(PORTAL_NOTIFICATION_VALUE),
+ null,
+ "There should be no captive portal notification in the window."
+ );
+}
+
+/**
+ * Some tests open a new window and close it later. When the window is closed,
+ * the original window opened by mochitest gains focus, generating an
+ * activate event. If the next test also opens a new window
+ * before this event has a chance to fire, CaptivePortalWatcher picks
+ * up the first one instead of the one from the new window. To avoid this
+ * unfortunate intermittent timing issue, we wait for the event from
+ * the original window every time we close a window that we opened.
+ */
+function waitForBrowserWindowActive(win) {
+ return new Promise(resolve => {
+ if (Services.focus.activeWindow == win) {
+ resolve();
+ } else {
+ win.addEventListener(
+ "activate",
+ () => {
+ resolve();
+ },
+ { once: true }
+ );
+ }
+ });
+}
+
+async function closeWindowAndWaitForWindowActivate(win) {
+ let activationPromises = [];
+ for (let w of BrowserWindowTracker.orderedWindows) {
+ if (
+ w != win &&
+ !win.document.documentElement.getAttribute("ignorecaptiveportal")
+ ) {
+ activationPromises.push(waitForBrowserWindowActive(win));
+ }
+ }
+ await BrowserTestUtils.closeWindow(win);
+ await Promise.race(activationPromises);
+}
+
+/**
+ * BrowserTestUtils.openNewBrowserWindow() does not guarantee the newly
+ * opened window has received focus when the promise resolves, so we
+ * have to manually wait every time.
+ */
+async function openWindowAndWaitForFocus() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ await waitForBrowserWindowActive(win);
+ return win;
+}
+
+async function openCaptivePortalErrorTab() {
+ // Open a page with a cert error.
+ let browser;
+ let certErrorLoaded;
+ let errorTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ () => {
+ let tab = BrowserTestUtils.addTab(gBrowser, BAD_CERT_PAGE);
+ gBrowser.selectedTab = tab;
+ browser = gBrowser.selectedBrowser;
+ certErrorLoaded = BrowserTestUtils.waitForErrorPage(browser);
+ return tab;
+ },
+ false
+ );
+ await certErrorLoaded;
+ info("A cert error page was opened");
+ await SpecialPowers.spawn(errorTab.linkedBrowser, [], async () => {
+ let doc = content.document;
+ let loginButton = doc.getElementById("openPortalLoginPageButton");
+ await ContentTaskUtils.waitForCondition(
+ () => loginButton && doc.body.className == "captiveportal",
+ "Captive portal error page UI is visible"
+ );
+ });
+ info("Captive portal error page UI is visible");
+
+ return errorTab;
+}
+
+async function openCaptivePortalLoginTab(
+ errorTab,
+ LOGIN_PAGE_URL = CANONICAL_URL
+) {
+ let portalTabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ LOGIN_PAGE_URL,
+ true
+ );
+
+ await SpecialPowers.spawn(errorTab.linkedBrowser, [], async () => {
+ let doc = content.document;
+ let loginButton = doc.getElementById("openPortalLoginPageButton");
+ info("Click on the login button on the captive portal error page");
+ await EventUtils.synthesizeMouseAtCenter(loginButton, {}, content);
+ });
+
+ let portalTab = await portalTabPromise;
+ is(
+ gBrowser.selectedTab,
+ portalTab,
+ "Captive Portal login page is now open in a new foreground tab."
+ );
+
+ return portalTab;
+}