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; }