head.js (8872B)
1 XPCOMUtils.defineLazyServiceGetter( 2 this, 3 "cps", 4 "@mozilla.org/network/captive-portal-service;1", 5 Ci.nsICaptivePortalService 6 ); 7 8 const CANONICAL_CONTENT = "success"; 9 const CANONICAL_URL = "data:text/plain;charset=utf-8," + CANONICAL_CONTENT; 10 const CANONICAL_URL_REDIRECTED = "data:text/plain;charset=utf-8,redirected"; 11 const PORTAL_NOTIFICATION_VALUE = "captive-portal-detected"; 12 const BAD_CERT_PAGE = "https://expired.example.com/"; 13 14 async function setupPrefsAndRecentWindowBehavior() { 15 await SpecialPowers.pushPrefEnv({ 16 set: [ 17 ["captivedetect.canonicalURL", CANONICAL_URL], 18 ["captivedetect.canonicalContent", CANONICAL_CONTENT], 19 ], 20 }); 21 // We need to test behavior when a portal is detected when there is no browser 22 // window, but we can't close the default window opened by the test harness. 23 // Instead, we deactivate CaptivePortalWatcher in the default window and 24 // exclude it using an attribute to mask its presence. 25 window.CaptivePortalWatcher.uninit(); 26 window.document.documentElement.setAttribute("ignorecaptiveportal", "true"); 27 28 registerCleanupFunction(function cleanUp() { 29 window.CaptivePortalWatcher.init(); 30 window.document.documentElement.removeAttribute("ignorecaptiveportal"); 31 }); 32 } 33 34 async function portalDetected() { 35 Services.obs.notifyObservers(null, "captive-portal-login"); 36 await TestUtils.waitForCondition(() => { 37 return cps.state == cps.LOCKED_PORTAL; 38 }, "Waiting for Captive Portal Service to update state after portal detected."); 39 } 40 41 async function freePortal(aSuccess) { 42 Services.obs.notifyObservers( 43 null, 44 "captive-portal-login-" + (aSuccess ? "success" : "abort") 45 ); 46 await TestUtils.waitForCondition(() => { 47 return cps.state != cps.LOCKED_PORTAL; 48 }, "Waiting for Captive Portal Service to update state after portal freed."); 49 } 50 51 // If a window is provided, it will be focused. Otherwise, a new window 52 // will be opened and focused. 53 async function focusWindowAndWaitForPortalUI(aLongRecheck, win) { 54 // CaptivePortalWatcher triggers a recheck when a window gains focus. If 55 // the time taken for the check to complete is under PORTAL_RECHECK_DELAY_MS, 56 // a tab with the login page is opened and selected. If it took longer, 57 // no tab is opened. It's not reliable to time things in an async test, 58 // so use a delay threshold of -1 to simulate a long recheck (so that any 59 // amount of time is considered excessive), and a very large threshold to 60 // simulate a short recheck. 61 Services.prefs.setIntPref( 62 "captivedetect.portalRecheckDelayMS", 63 aLongRecheck ? -1 : 1000000 64 ); 65 66 if (!win) { 67 win = await BrowserTestUtils.openNewBrowserWindow(); 68 } 69 let windowActivePromise = waitForBrowserWindowActive(win); 70 win.focus(); 71 await windowActivePromise; 72 73 // After a new window is opened, CaptivePortalWatcher asks for a recheck, and 74 // waits for it to complete. We need to manually tell it a recheck completed. 75 await TestUtils.waitForCondition(() => { 76 return win.CaptivePortalWatcher._waitingForRecheck; 77 }, "Waiting for CaptivePortalWatcher to trigger a recheck."); 78 Services.obs.notifyObservers(null, "captive-portal-check-complete"); 79 80 let notification = await ensurePortalNotification(win); 81 82 if (aLongRecheck) { 83 ensureNoPortalTab(win); 84 await testShowLoginPageButtonVisibility(notification, "visible"); 85 return win; 86 } 87 88 let tab = win.gBrowser.tabs[1]; 89 if (tab.linkedBrowser.currentURI.spec != CANONICAL_URL) { 90 // The tab should load the canonical URL, wait for it. 91 await BrowserTestUtils.waitForLocationChange(win.gBrowser, CANONICAL_URL); 92 } 93 is( 94 win.gBrowser.selectedTab, 95 tab, 96 "The captive portal tab should be open and selected in the new window." 97 ); 98 await testShowLoginPageButtonVisibility(notification, "hidden"); 99 return win; 100 } 101 102 function ensurePortalTab(win) { 103 // For the tests that call this function, it's enough to ensure there 104 // are two tabs in the window - the default tab and the portal tab. 105 is( 106 win.gBrowser.tabs.length, 107 2, 108 "There should be a captive portal tab in the window." 109 ); 110 } 111 112 async function ensurePortalNotification(win) { 113 await BrowserTestUtils.waitForMutationCondition( 114 win.gNavToolbox, 115 { childList: true }, 116 () => 117 win.gNavToolbox 118 .querySelector("notification-message") 119 ?.getAttribute("value") == PORTAL_NOTIFICATION_VALUE 120 ); 121 122 let notification = win.gNotificationBox.getNotificationWithValue( 123 PORTAL_NOTIFICATION_VALUE 124 ); 125 isnot( 126 notification, 127 null, 128 "There should be a captive portal notification in the window." 129 ); 130 return notification; 131 } 132 133 // Helper to test whether the "Show Login Page" is visible in the captive portal 134 // notification (it should be hidden when the portal tab is selected). 135 async function testShowLoginPageButtonVisibility(notification, visibility) { 136 await notification.updateComplete; 137 let showLoginPageButton = notification.buttonContainer.querySelector( 138 "button.notification-button" 139 ); 140 // If the visibility property was never changed from default, it will be 141 // an empty string, so we pretend it's "visible" (effectively the same). 142 is( 143 showLoginPageButton.style.visibility || "visible", 144 visibility, 145 'The "Show Login Page" button should be ' + visibility + "." 146 ); 147 } 148 149 function ensureNoPortalTab(win) { 150 is( 151 win.gBrowser.tabs.length, 152 1, 153 "There should be no captive portal tab in the window." 154 ); 155 } 156 157 function ensureNoPortalNotification(win) { 158 is( 159 win.gNotificationBox.getNotificationWithValue(PORTAL_NOTIFICATION_VALUE), 160 null, 161 "There should be no captive portal notification in the window." 162 ); 163 } 164 165 /** 166 * Some tests open a new window and close it later. When the window is closed, 167 * the original window opened by mochitest gains focus, generating an 168 * activate event. If the next test also opens a new window 169 * before this event has a chance to fire, CaptivePortalWatcher picks 170 * up the first one instead of the one from the new window. To avoid this 171 * unfortunate intermittent timing issue, we wait for the event from 172 * the original window every time we close a window that we opened. 173 */ 174 function waitForBrowserWindowActive(win) { 175 return new Promise(resolve => { 176 if (Services.focus.activeWindow == win) { 177 resolve(); 178 } else { 179 win.addEventListener( 180 "activate", 181 () => { 182 resolve(); 183 }, 184 { once: true } 185 ); 186 } 187 }); 188 } 189 190 async function closeWindowAndWaitForWindowActivate(win) { 191 let activationPromises = []; 192 for (let w of BrowserWindowTracker.orderedWindows) { 193 if ( 194 w != win && 195 !win.document.documentElement.getAttribute("ignorecaptiveportal") 196 ) { 197 activationPromises.push(waitForBrowserWindowActive(win)); 198 } 199 } 200 await BrowserTestUtils.closeWindow(win); 201 await Promise.race(activationPromises); 202 } 203 204 /** 205 * BrowserTestUtils.openNewBrowserWindow() does not guarantee the newly 206 * opened window has received focus when the promise resolves, so we 207 * have to manually wait every time. 208 */ 209 async function openWindowAndWaitForFocus() { 210 let win = await BrowserTestUtils.openNewBrowserWindow(); 211 await waitForBrowserWindowActive(win); 212 return win; 213 } 214 215 async function openCaptivePortalErrorTab() { 216 // Open a page with a cert error. 217 let browser; 218 let certErrorLoaded; 219 let errorTab = await BrowserTestUtils.openNewForegroundTab( 220 gBrowser, 221 () => { 222 let tab = BrowserTestUtils.addTab(gBrowser, BAD_CERT_PAGE); 223 gBrowser.selectedTab = tab; 224 browser = gBrowser.selectedBrowser; 225 certErrorLoaded = BrowserTestUtils.waitForErrorPage(browser); 226 return tab; 227 }, 228 false 229 ); 230 await certErrorLoaded; 231 info("A cert error page was opened"); 232 await SpecialPowers.spawn(errorTab.linkedBrowser, [], async () => { 233 let doc = content.document; 234 let loginButton = doc.getElementById("openPortalLoginPageButton"); 235 await ContentTaskUtils.waitForCondition( 236 () => loginButton && doc.body.className == "captiveportal", 237 "Captive portal error page UI is visible" 238 ); 239 }); 240 info("Captive portal error page UI is visible"); 241 242 return errorTab; 243 } 244 245 async function openCaptivePortalLoginTab( 246 errorTab, 247 LOGIN_PAGE_URL = CANONICAL_URL 248 ) { 249 let portalTabPromise = BrowserTestUtils.waitForNewTab( 250 gBrowser, 251 LOGIN_PAGE_URL, 252 true 253 ); 254 255 await SpecialPowers.spawn(errorTab.linkedBrowser, [], async () => { 256 let doc = content.document; 257 let loginButton = doc.getElementById("openPortalLoginPageButton"); 258 info("Click on the login button on the captive portal error page"); 259 await EventUtils.synthesizeMouseAtCenter(loginButton, {}, content); 260 }); 261 262 let portalTab = await portalTabPromise; 263 is( 264 gBrowser.selectedTab, 265 portalTab, 266 "Captive Portal login page is now open in a new foreground tab." 267 ); 268 269 return portalTab; 270 }