browser_354894_perwindowpb.js (15159B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 /** 8 * Checks that restoring the last browser window in session is actually 9 * working. 10 * 11 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=354894 12 * Note: It is implicitly tested that restoring the last window works when 13 * non-browser windows are around. The "Run Tests" window as well as the main 14 * browser window (wherein the test code gets executed) won't be considered 15 * browser windows. To achiveve this said main browser window has its windowtype 16 * attribute modified so that it's not considered a browser window any longer. 17 * This is crucial, because otherwise there would be two browser windows around, 18 * said main test window and the one opened by the tests, and hence the new 19 * logic wouldn't be executed at all. 20 * Note: Mac only tests the new notifications, as restoring the last window is 21 * not enabled on that platform (platform shim; the application is kept running 22 * although there are no windows left) 23 * Note: There is a difference when closing a browser window with 24 * BrowserCommands.tryToCloseWindow() as opposed to close(). The former will make 25 * nsSessionStore restore a window next time it gets a chance and will post 26 * notifications. The latter won't. 27 */ 28 29 // The rejection "BrowserWindowTracker.getTopWindow(...) is null" is left 30 // unhandled in some cases. This bug should be fixed, but for the moment this 31 // file allows a class of rejections. 32 // 33 // NOTE: Allowing a whole class of rejections should be avoided. Normally you 34 // should use "expectUncaughtRejection" to flag individual failures. 35 const { PromiseTestUtils } = ChromeUtils.importESModule( 36 "resource://testing-common/PromiseTestUtils.sys.mjs" 37 ); 38 PromiseTestUtils.allowMatchingRejectionsGlobally(/getTopWindow/); 39 40 // Some urls that might be opened in tabs and/or popups 41 // Do not use about:blank: 42 // That one is reserved for special purposes in the tests 43 const TEST_URLS = ["about:mozilla", "about:buildconfig"]; 44 45 // Number of -request notifications to except 46 // remember to adjust when adding new tests 47 const NOTIFICATIONS_EXPECTED = 6; 48 49 // Window features of popup windows 50 const POPUP_FEATURES = "toolbar=no,resizable=no,status=no"; 51 52 // Window features of browser windows 53 const CHROME_FEATURES = "chrome,all,dialog=no"; 54 55 const IS_MAC = navigator.platform.match(/Mac/); 56 57 /** 58 * Returns an Object with two properties: 59 * open (int): 60 * A count of how many non-closed navigator:browser windows there are. 61 * winstates (int): 62 * A count of how many windows there are in the SessionStore state. 63 */ 64 function getBrowserWindowsCount() { 65 let open = 0; 66 for (let win of Services.wm.getEnumerator("navigator:browser")) { 67 if (!win.closed) { 68 ++open; 69 } 70 } 71 72 let winstates = JSON.parse(ss.getBrowserState()).windows.length; 73 74 return { open, winstates }; 75 } 76 77 add_setup(async function () { 78 // Make sure we've only got one browser window to start with 79 let { open, winstates } = getBrowserWindowsCount(); 80 is(open, 1, "Should only be one open window"); 81 is(winstates, 1, "Should only be one window state in SessionStore"); 82 83 // This test takes some time to run, and it could timeout randomly. 84 // So we require a longer timeout. See bug 528219. 85 requestLongerTimeout(3); 86 87 // Make the main test window not count as a browser window any longer 88 let oldWinType = document.documentElement.getAttribute("windowtype"); 89 document.documentElement.setAttribute("windowtype", "navigator:testrunner"); 90 91 registerCleanupFunction(() => { 92 document.documentElement.setAttribute("windowtype", oldWinType); 93 }); 94 }); 95 96 /** 97 * Sets up one of our tests by setting the right preferences, and 98 * then opening up a browser window preloaded with some tabs. 99 * 100 * @param options (Object) 101 * An object that can contain the following properties: 102 * 103 * private: 104 * Whether or not the opened window should be private. 105 * 106 * denyFirst: 107 * Whether or not the first window that attempts to close 108 * via closeWindowForRestoration should be denied. 109 * 110 * @param testFunction (Function*) 111 * A generator function that yields Promises to be run 112 * once the test has been set up. 113 * 114 * @returns Promise 115 * Resolves once the test has been cleaned up. 116 */ 117 let setupTest = async function (options, testFunction) { 118 await pushPrefs( 119 ["browser.startup.page", 3], 120 ["browser.tabs.warnOnClose", false] 121 ); 122 // SessionStartup caches pref values, but as this test tries to simulate a 123 // startup scenario, we'll reset them here. 124 SessionStartup.resetForTest(); 125 126 // Observe these, and also use to count the number of hits 127 let observing = { 128 "browser-lastwindow-close-requested": 0, 129 "browser-lastwindow-close-granted": 0, 130 }; 131 132 /** 133 * Helper: Will observe and handle the notifications for us 134 */ 135 let hitCount = 0; 136 function observer(aCancel, aTopic) { 137 // count so that we later may compare 138 observing[aTopic]++; 139 140 // handle some tests 141 if (options.denyFirst && ++hitCount == 1) { 142 aCancel.QueryInterface(Ci.nsISupportsPRBool).data = true; 143 } 144 } 145 146 for (let o in observing) { 147 Services.obs.addObserver(observer, o); 148 } 149 150 let newWin = await promiseNewWindowLoaded({ 151 private: options.private || false, 152 }); 153 154 await injectTestTabs(newWin); 155 156 await testFunction(newWin, observing); 157 158 let count = getBrowserWindowsCount(); 159 is(count.open, 0, "Got right number of open windows"); 160 is(count.winstates, 1, "Got right number of stored window states"); 161 162 for (let o in observing) { 163 Services.obs.removeObserver(observer, o); 164 } 165 166 await popPrefs(); 167 // Act like nothing ever happened. 168 SessionStartup.resetForTest(); 169 }; 170 171 /** 172 * Loads a TEST_URLS into a browser window. 173 * 174 * @param win (Window) 175 * The browser window to load the tabs in 176 */ 177 function injectTestTabs(win) { 178 let promises = TEST_URLS.map(url => 179 BrowserTestUtils.addTab(win.gBrowser, url) 180 ).map(tab => BrowserTestUtils.browserLoaded(tab.linkedBrowser)); 181 return Promise.all(promises); 182 } 183 184 /** 185 * Attempts to close a window via BrowserCommands.tryToCloseWindow so that 186 * we get the browser-lastwindow-close-requested and 187 * browser-lastwindow-close-granted observer notifications. 188 * 189 * @param win (Window) 190 * The window to try to close 191 * @returns Promise 192 * Resolves to true if the window closed, or false if the window 193 * was denied the ability to close. 194 */ 195 function closeWindowForRestoration(win) { 196 return new Promise(resolve => { 197 let closePromise = BrowserTestUtils.windowClosed(win); 198 win.BrowserCommands.tryToCloseWindow(); 199 if (!win.closed) { 200 resolve(false); 201 return; 202 } 203 204 closePromise.then(() => { 205 resolve(true); 206 }); 207 }); 208 } 209 210 /** 211 * Normal in-session restore 212 * 213 * Note: Non-Mac only 214 * 215 * Should do the following: 216 * 1. Open a new browser window 217 * 2. Add some tabs 218 * 3. Close that window 219 * 4. Opening another window 220 * 5. Checks that state is restored 221 */ 222 add_task(async function test_open_close_normal() { 223 if (IS_MAC) { 224 return; 225 } 226 227 await setupTest({ denyFirst: true }, async function (newWin, obs) { 228 let closed = await closeWindowForRestoration(newWin); 229 ok(!closed, "First close request should have been denied"); 230 231 closed = await closeWindowForRestoration(newWin); 232 ok(closed, "Second close request should be accepted"); 233 234 newWin = await promiseNewWindowLoaded(); 235 is( 236 newWin.gBrowser.browsers.length, 237 TEST_URLS.length + 2, 238 "Restored window in-session with otherpopup windows around" 239 ); 240 241 // Note that this will not result in the the browser-lastwindow-close 242 // notifications firing for this other newWin. 243 await BrowserTestUtils.closeWindow(newWin); 244 245 // setupTest gave us a window which was denied for closing once, and then 246 // closed. 247 is( 248 obs["browser-lastwindow-close-requested"], 249 2, 250 "Got expected browser-lastwindow-close-requested notifications" 251 ); 252 is( 253 obs["browser-lastwindow-close-granted"], 254 1, 255 "Got expected browser-lastwindow-close-granted notifications" 256 ); 257 }); 258 }); 259 260 /** 261 * PrivateBrowsing in-session restore 262 * 263 * Note: Non-Mac only 264 * 265 * Should do the following: 266 * 1. Open a new browser window A 267 * 2. Add some tabs 268 * 3. Close the window A as the last window 269 * 4. Open a private browsing window B 270 * 5. Make sure that B didn't restore the tabs from A 271 * 6. Close private browsing window B 272 * 7. Open a new window C 273 * 8. Make sure that new window C has restored tabs from A 274 */ 275 add_task(async function test_open_close_private_browsing() { 276 if (IS_MAC) { 277 return; 278 } 279 280 await setupTest({}, async function (newWin, obs) { 281 let closed = await closeWindowForRestoration(newWin); 282 ok(closed, "Should be able to close the window"); 283 284 newWin = await promiseNewWindowLoaded({ private: true }); 285 is( 286 newWin.gBrowser.browsers.length, 287 1, 288 "Did not restore in private browsing mode" 289 ); 290 291 closed = await closeWindowForRestoration(newWin); 292 ok(closed, "Should be able to close the window"); 293 294 newWin = await promiseNewWindowLoaded(); 295 is( 296 newWin.gBrowser.browsers.length, 297 TEST_URLS.length + 2, 298 "Restored tabs in a new non-private window" 299 ); 300 301 // Note that this will not result in the the browser-lastwindow-close 302 // notifications firing for this other newWin. 303 await BrowserTestUtils.closeWindow(newWin); 304 305 // We closed two windows with closeWindowForRestoration, and both 306 // should have been successful. 307 is( 308 obs["browser-lastwindow-close-requested"], 309 2, 310 "Got expected browser-lastwindow-close-requested notifications" 311 ); 312 is( 313 obs["browser-lastwindow-close-granted"], 314 2, 315 "Got expected browser-lastwindow-close-granted notifications" 316 ); 317 }); 318 }); 319 320 /** 321 * Open some popup window to check it isn't restored. Instead nothing at all 322 * should be restored 323 * 324 * Note: Non-Mac only 325 * 326 * Should do the following: 327 * 1. Open a popup 328 * 2. Add another tab to the popup (so that it gets stored) and close it again 329 * 3. Open a window 330 * 4. Check that nothing at all is restored 331 * 5. Open two browser windows and close them again 332 * 6. undoCloseWindow() one 333 * 7. Open another browser window 334 * 8. Check that nothing at all is restored 335 */ 336 add_task(async function test_open_close_only_popup() { 337 if (IS_MAC) { 338 return; 339 } 340 341 await setupTest({}, async function (newWin, obs) { 342 // We actually don't care about the initial window in this test. 343 await BrowserTestUtils.closeWindow(newWin); 344 345 // This will cause nsSessionStore to restore a window the next time it 346 // gets a chance. 347 let popupPromise = BrowserTestUtils.waitForNewWindow(); 348 openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[1]); 349 let popup = await popupPromise; 350 351 is( 352 popup.gBrowser.browsers.length, 353 1, 354 "Did not restore the popup window (1)" 355 ); 356 357 let closed = await closeWindowForRestoration(popup); 358 ok(closed, "Should be able to close the window"); 359 360 popupPromise = BrowserTestUtils.waitForNewWindow(); 361 openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[1]); 362 popup = await popupPromise; 363 364 BrowserTestUtils.addTab(popup.gBrowser, TEST_URLS[0]); 365 is( 366 popup.gBrowser.browsers.length, 367 2, 368 "Did not restore to the popup window (2)" 369 ); 370 371 await BrowserTestUtils.closeWindow(popup); 372 373 newWin = await promiseNewWindowLoaded(); 374 isnot( 375 newWin.gBrowser.browsers.length, 376 2, 377 "Did not restore the popup window" 378 ); 379 is( 380 TEST_URLS.indexOf(newWin.gBrowser.browsers[0].currentURI.spec), 381 -1, 382 "Did not restore the popup window (2)" 383 ); 384 await BrowserTestUtils.closeWindow(newWin); 385 386 // We closed one popup window with closeWindowForRestoration, and popup 387 // windows should never fire the browser-lastwindow notifications. 388 is( 389 obs["browser-lastwindow-close-requested"], 390 0, 391 "Got expected browser-lastwindow-close-requested notifications" 392 ); 393 is( 394 obs["browser-lastwindow-close-granted"], 395 0, 396 "Got expected browser-lastwindow-close-granted notifications" 397 ); 398 }); 399 }); 400 401 /** 402 * Open some windows and do undoCloseWindow. This should prevent any 403 * restoring later in the test 404 * 405 * Note: Non-Mac only 406 * 407 * Should do the following: 408 * 1. Open two browser windows and close them again 409 * 2. undoCloseWindow() one 410 * 3. Open another browser window 411 * 4. Make sure nothing at all is restored 412 */ 413 add_task(async function test_open_close_restore_from_popup() { 414 if (IS_MAC) { 415 return; 416 } 417 418 await setupTest({}, async function (newWin) { 419 let newWin2 = await promiseNewWindowLoaded(); 420 await injectTestTabs(newWin2); 421 422 let closed = await closeWindowForRestoration(newWin); 423 ok(closed, "Should be able to close the window"); 424 closed = await closeWindowForRestoration(newWin2); 425 ok(closed, "Should be able to close the window"); 426 427 let counts = getBrowserWindowsCount(); 428 is(counts.open, 0, "Got right number of open windows"); 429 is(counts.winstates, 1, "Got right number of window states"); 430 431 newWin = SessionWindowUI.undoCloseWindow(0); 432 await BrowserTestUtils.waitForEvent(newWin, "load"); 433 434 // Make sure we wait until this window is restored. 435 await BrowserTestUtils.waitForEvent( 436 newWin.gBrowser.tabContainer, 437 "SSTabRestored" 438 ); 439 440 newWin2 = await promiseNewWindowLoaded(); 441 442 is( 443 TEST_URLS.indexOf(newWin2.gBrowser.browsers[0].currentURI.spec), 444 -1, 445 "Did not restore, as undoCloseWindow() was last called (2)" 446 ); 447 448 counts = getBrowserWindowsCount(); 449 is(counts.open, 2, "Got right number of open windows"); 450 is(counts.winstates, 3, "Got right number of window states"); 451 452 await BrowserTestUtils.closeWindow(newWin); 453 await BrowserTestUtils.closeWindow(newWin2); 454 455 counts = getBrowserWindowsCount(); 456 is(counts.open, 0, "Got right number of open windows"); 457 is(counts.winstates, 1, "Got right number of window states"); 458 }); 459 }); 460 461 /** 462 * Test if closing can be denied on Mac. 463 * 464 * Note: Mac only 465 */ 466 add_task(async function test_mac_notifications() { 467 if (!IS_MAC) { 468 return; 469 } 470 471 await setupTest({ denyFirst: true }, async function (newWin, obs) { 472 let closed = await closeWindowForRestoration(newWin); 473 ok(!closed, "First close attempt should be denied"); 474 closed = await closeWindowForRestoration(newWin); 475 ok(closed, "Second close attempt should be granted"); 476 477 // We tried closing once, and got denied. Then we tried again and 478 // succeeded. That means 2 close requests, and 1 close granted. 479 is( 480 obs["browser-lastwindow-close-requested"], 481 2, 482 "Got expected browser-lastwindow-close-requested notifications" 483 ); 484 is( 485 obs["browser-lastwindow-close-granted"], 486 1, 487 "Got expected browser-lastwindow-close-granted notifications" 488 ); 489 }); 490 });