/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** * Checks that restoring the last browser window in session is actually * working. * * @see https://bugzilla.mozilla.org/show_bug.cgi?id=354894 * @note It is implicitly tested that restoring the last window works when * non-browser windows are around. The "Run Tests" window as well as the main * browser window (wherein the test code gets executed) won't be considered * browser windows. To achiveve this said main browser window has it's windowtype * attribute modified so that it's not considered a browser window any longer. * This is crucial, because otherwise there would be two browser windows around, * said main test window and the one opened by the tests, and hence the new * logic wouldn't be executed at all. * @note Mac only tests the new notifications, as restoring the last window is * not enabled on that platform (platform shim; the application is kept running * although there are no windows left) * @note There is a difference when closing a browser window with * BrowserTryToCloseWindow() as opposed to close(). The former will make * nsSessionStore restore a window next time it gets a chance and will post * notifications. The latter won't. */ // Some urls that might be opened in tabs and/or popups // Do not use about:blank: // That one is reserved for special purposes in the tests const TEST_URLS = ["about:mozilla", "about:buildconfig"]; // Number of -request notifications to except // remember to adjust when adding new tests const NOTIFICATIONS_EXPECTED = 6; // Window features of popup windows const POPUP_FEATURES = "toolbar=no,resizable=no,status=no"; // Window features of browser windows const CHROME_FEATURES = "chrome,all,dialog=no"; const IS_MAC = navigator.platform.match(/Mac/); /** * Returns an Object with two properties: * open (int): * A count of how many non-closed navigator:browser windows there are. * winstates (int): * A count of how many windows there are in the SessionStore state. */ function getBrowserWindowsCount() { let open = 0; let e = Services.wm.getEnumerator("navigator:browser"); while (e.hasMoreElements()) { if (!e.getNext().closed) ++open; } let winstates = JSON.parse(ss.getBrowserState()).windows.length; return { open, winstates }; } add_task(function* setup() { // Make sure we've only got one browser window to start with let { open, winstates } = getBrowserWindowsCount(); is(open, 1, "Should only be one open window"); is(winstates, 1, "Should only be one window state in SessionStore"); // This test takes some time to run, and it could timeout randomly. // So we require a longer timeout. See bug 528219. requestLongerTimeout(3); // Make the main test window not count as a browser window any longer let oldWinType = document.documentElement.getAttribute("windowtype"); document.documentElement.setAttribute("windowtype", "navigator:testrunner"); registerCleanupFunction(() => { document.documentElement.setAttribute("windowtype", "navigator:browser"); }); }); /** * Sets up one of our tests by setting the right preferences, and * then opening up a browser window preloaded with some tabs. * * @param options (Object) * An object that can contain the following properties: * * private: * Whether or not the opened window should be private. * * denyFirst: * Whether or not the first window that attempts to close * via closeWindowForRestoration should be denied. * * @param testFunction (Function*) * A generator function that yields Promises to be run * once the test has been set up. * * @returns Promise * Resolves once the test has been cleaned up. */ let setupTest = Task.async(function*(options, testFunction) { yield pushPrefs(["browser.startup.page", 3], ["browser.tabs.warnOnClose", false]); // Observe these, and also use to count the number of hits let observing = { "browser-lastwindow-close-requested": 0, "browser-lastwindow-close-granted": 0 }; /** * Helper: Will observe and handle the notifications for us */ let hitCount = 0; function observer(aCancel, aTopic, aData) { // count so that we later may compare observing[aTopic]++; // handle some tests if (options.denyFirst && ++hitCount == 1) { aCancel.QueryInterface(Ci.nsISupportsPRBool).data = true; } } for (let o in observing) { Services.obs.addObserver(observer, o, false); } let private = options.private || false; let newWin = yield promiseNewWindowLoaded({ private }); injectTestTabs(newWin); yield testFunction(newWin, observing); let count = getBrowserWindowsCount(); is(count.open, 0, "Got right number of open windows"); is(count.winstates, 1, "Got right number of stored window states"); for (let o in observing) { Services.obs.removeObserver(observer, o); } yield popPrefs(); }); /** * Loads a TEST_URLS into a browser window. * * @param win (Window) * The browser window to load the tabs in */ function injectTestTabs(win) { TEST_URLS.forEach(function (url) { win.gBrowser.addTab(url); }); } /** * Attempts to close a window via BrowserTryToCloseWindow so that * we get the browser-lastwindow-close-requested and * browser-lastwindow-close-granted observer notifications. * * @param win (Window) * The window to try to close * @returns Promise * Resolves to true if the window closed, or false if the window * was denied the ability to close. */ function closeWindowForRestoration(win) { return new Promise((resolve) => { let closePromise = BrowserTestUtils.windowClosed(win); win.BrowserTryToCloseWindow(); if (!win.closed) { resolve(false); return; } closePromise.then(() => { resolve(true); }); }); } /** * Normal in-session restore * * @note: Non-Mac only * * Should do the following: * 1. Open a new browser window * 2. Add some tabs * 3. Close that window * 4. Opening another window * 5. Checks that state is restored */ add_task(function* test_open_close_normal() { if (IS_MAC) { return; } yield setupTest({ denyFirst: true }, function*(newWin, obs) { let closed = yield closeWindowForRestoration(newWin); ok(!closed, "First close request should have been denied"); closed = yield closeWindowForRestoration(newWin); ok(closed, "Second close request should be accepted"); newWin = yield promiseNewWindowLoaded(); is(newWin.gBrowser.browsers.length, TEST_URLS.length + 2, "Restored window in-session with otherpopup windows around"); // Note that this will not result in the the browser-lastwindow-close // notifications firing for this other newWin. yield BrowserTestUtils.closeWindow(newWin); // setupTest gave us a window which was denied for closing once, and then // closed. is(obs["browser-lastwindow-close-requested"], 2, "Got expected browser-lastwindow-close-requested notifications"); is(obs["browser-lastwindow-close-granted"], 1, "Got expected browser-lastwindow-close-granted notifications"); }); }); /** * PrivateBrowsing in-session restore * * @note: Non-Mac only * * Should do the following: * 1. Open a new browser window A * 2. Add some tabs * 3. Close the window A as the last window * 4. Open a private browsing window B * 5. Make sure that B didn't restore the tabs from A * 6. Close private browsing window B * 7. Open a new window C * 8. Make sure that new window C has restored tabs from A */ add_task(function* test_open_close_private_browsing() { if (IS_MAC) { return; } yield setupTest({}, function*(newWin, obs) { let closed = yield closeWindowForRestoration(newWin); ok(closed, "Should be able to close the window"); newWin = yield promiseNewWindowLoaded({private: true}); is(newWin.gBrowser.browsers.length, 1, "Did not restore in private browing mode"); closed = yield closeWindowForRestoration(newWin); ok(closed, "Should be able to close the window"); newWin = yield promiseNewWindowLoaded(); is(newWin.gBrowser.browsers.length, TEST_URLS.length + 2, "Restored tabs in a new non-private window"); // Note that this will not result in the the browser-lastwindow-close // notifications firing for this other newWin. yield BrowserTestUtils.closeWindow(newWin); // We closed two windows with closeWindowForRestoration, and both // should have been successful. is(obs["browser-lastwindow-close-requested"], 2, "Got expected browser-lastwindow-close-requested notifications"); is(obs["browser-lastwindow-close-granted"], 2, "Got expected browser-lastwindow-close-granted notifications"); }); }); /** * Open some popup windows to check those aren't restored, but the browser * window is. * * @note: Non-Mac only * * Should do the following: * 1. Open a new browser window * 2. Add some tabs * 3. Open some popups * 4. Add another tab to one popup (so that it gets stored) and close it again * 5. Close the browser window * 6. Open another browser window * 7. Make sure that the tabs of the closed browser window, but not the popup, * are restored */ add_task(function* test_open_close_window_and_popup() { if (IS_MAC) { return; } yield setupTest({}, function*(newWin, obs) { let popupPromise = BrowserTestUtils.waitForNewWindow(); openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[0]); let popup = yield popupPromise; let popup2Promise = BrowserTestUtils.waitForNewWindow(); openDialog(location, "popup2", POPUP_FEATURES, TEST_URLS[1]); let popup2 = yield popup2Promise; popup2.gBrowser.addTab(TEST_URLS[0]); let closed = yield closeWindowForRestoration(newWin); ok(closed, "Should be able to close the window"); yield BrowserTestUtils.closeWindow(popup2); newWin = yield promiseNewWindowLoaded(); is(newWin.gBrowser.browsers.length, TEST_URLS.length + 2, "Restored window and associated tabs in session"); yield BrowserTestUtils.closeWindow(popup); yield BrowserTestUtils.closeWindow(newWin); // We closed one window with closeWindowForRestoration, and it should // have been successful. is(obs["browser-lastwindow-close-requested"], 1, "Got expected browser-lastwindow-close-requested notifications"); is(obs["browser-lastwindow-close-granted"], 1, "Got expected browser-lastwindow-close-granted notifications"); }); }); /** * Open some popup window to check it isn't restored. Instead nothing at all * should be restored * * @note: Non-Mac only * * Should do the following: * 1. Open a popup * 2. Add another tab to the popup (so that it gets stored) and close it again * 3. Open a window * 4. Check that nothing at all is restored * 5. Open two browser windows and close them again * 6. undoCloseWindow() one * 7. Open another browser window * 8. Check that nothing at all is restored */ add_task(function* test_open_close_only_popup() { if (IS_MAC) { return; } yield setupTest({}, function*(newWin, obs) { // We actually don't care about the initial window in this test. yield BrowserTestUtils.closeWindow(newWin); // This will cause nsSessionStore to restore a window the next time it // gets a chance. let popupPromise = BrowserTestUtils.waitForNewWindow(); openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[1]); let popup = yield popupPromise; is(popup.gBrowser.browsers.length, 1, "Did not restore the popup window (1)"); let closed = yield closeWindowForRestoration(popup); ok(closed, "Should be able to close the window"); popupPromise = BrowserTestUtils.waitForNewWindow(); openDialog(location, "popup", POPUP_FEATURES, TEST_URLS[1]); popup = yield popupPromise; popup.gBrowser.addTab(TEST_URLS[0]); is(popup.gBrowser.browsers.length, 2, "Did not restore to the popup window (2)"); yield BrowserTestUtils.closeWindow(popup); newWin = yield promiseNewWindowLoaded(); isnot(newWin.gBrowser.browsers.length, 2, "Did not restore the popup window"); is(TEST_URLS.indexOf(newWin.gBrowser.browsers[0].currentURI.spec), -1, "Did not restore the popup window (2)"); yield BrowserTestUtils.closeWindow(newWin); // We closed one popup window with closeWindowForRestoration, and popup // windows should never fire the browser-lastwindow notifications. is(obs["browser-lastwindow-close-requested"], 0, "Got expected browser-lastwindow-close-requested notifications"); is(obs["browser-lastwindow-close-granted"], 0, "Got expected browser-lastwindow-close-granted notifications"); }); }); /** * Open some windows and do undoCloseWindow. This should prevent any * restoring later in the test * * @note: Non-Mac only * * Should do the following: * 1. Open two browser windows and close them again * 2. undoCloseWindow() one * 3. Open another browser window * 4. Make sure nothing at all is restored */ add_task(function* test_open_close_restore_from_popup() { if (IS_MAC) { return; } yield setupTest({}, function*(newWin, obs) { let newWin2 = yield promiseNewWindowLoaded(); yield injectTestTabs(newWin2); let closed = yield closeWindowForRestoration(newWin); ok(closed, "Should be able to close the window"); closed = yield closeWindowForRestoration(newWin2); ok(closed, "Should be able to close the window"); let counts = getBrowserWindowsCount(); is(counts.open, 0, "Got right number of open windows"); is(counts.winstates, 1, "Got right number of window states"); newWin = undoCloseWindow(0); yield BrowserTestUtils.waitForEvent(newWin, "load"); // Make sure we wait until this window is restored. yield BrowserTestUtils.waitForEvent(newWin.gBrowser.tabContainer, "SSTabRestored"); newWin2 = yield promiseNewWindowLoaded(); is(newWin2.gBrowser.browsers.length, 1, "Did not restore, as undoCloseWindow() was last called"); is(TEST_URLS.indexOf(newWin2.gBrowser.browsers[0].currentURI.spec), -1, "Did not restore, as undoCloseWindow() was last called (2)"); counts = getBrowserWindowsCount(); is(counts.open, 2, "Got right number of open windows"); is(counts.winstates, 3, "Got right number of window states"); yield BrowserTestUtils.closeWindow(newWin); yield BrowserTestUtils.closeWindow(newWin2); counts = getBrowserWindowsCount(); is(counts.open, 0, "Got right number of open windows"); is(counts.winstates, 1, "Got right number of window states"); }); }); /** * Test if closing can be denied on Mac. * @note: Mac only */ add_task(function* test_mac_notifications() { if (!IS_MAC) { return; } yield setupTest({ denyFirst: true }, function*(newWin, obs) { let closed = yield closeWindowForRestoration(newWin); ok(!closed, "First close attempt should be denied"); closed = yield closeWindowForRestoration(newWin); ok(closed, "Second close attempt should be granted"); // We tried closing once, and got denied. Then we tried again and // succeeded. That means 2 close requests, and 1 close granted. is(obs["browser-lastwindow-close-requested"], 2, "Got expected browser-lastwindow-close-requested notifications"); is(obs["browser-lastwindow-close-granted"], 1, "Got expected browser-lastwindow-close-granted notifications"); }); });