/* 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/. */ // Mirrors WINDOW_ATTRIBUTES IN nsSessionStore.js const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"]; var stateBackup = ss.getBrowserState(); var originalWarnOnClose = gPrefService.getBoolPref("browser.tabs.warnOnClose"); var originalStartupPage = gPrefService.getIntPref("browser.startup.page"); var originalWindowType = document.documentElement.getAttribute("windowtype"); var gotLastWindowClosedTopic = false; var shouldPinTab = false; var shouldOpenTabs = false; var shouldCloseTab = false; var testNum = 0; var afterTestCallback; // Set state so we know the closed windows content var testState = { windows: [ { tabs: [{ entries: [{ url: "http://example.org" }] }] } ], _closedWindows: [] }; // We'll push a set of conditions and callbacks into this array // Ideally we would also test win/linux under a complete set of conditions, but // the tests for osx mirror the other set of conditions possible on win/linux. var tests = []; // the third & fourth test share a condition check, keep it DRY function checkOSX34Generator(num) { return function(aPreviousState, aCurState) { // In here, we should have restored the pinned tab, so only the unpinned tab // should be in aCurState. So let's shape our expectations. let expectedState = JSON.parse(aPreviousState); expectedState[0].tabs.shift(); // size attributes are stripped out in _prepDataForDeferredRestore in nsSessionStore. // This isn't the best approach, but neither is comparing JSON strings WINDOW_ATTRIBUTES.forEach(attr => delete expectedState[0][attr]); is(aCurState, JSON.stringify(expectedState), "test #" + num + ": closedWindowState is as expected"); }; } function checkNoWindowsGenerator(num) { return function(aPreviousState, aCurState) { is(aCurState, "[]", "test #" + num + ": there should be no closedWindowsLeft"); }; } // The first test has 0 pinned tabs and 1 unpinned tab tests.push({ pinned: false, extra: false, close: false, checkWinLin: checkNoWindowsGenerator(1), checkOSX: function(aPreviousState, aCurState) { is(aCurState, aPreviousState, "test #1: closed window state is unchanged"); } }); // The second test has 1 pinned tab and 0 unpinned tabs. tests.push({ pinned: true, extra: false, close: false, checkWinLin: checkNoWindowsGenerator(2), checkOSX: checkNoWindowsGenerator(2) }); // The third test has 1 pinned tab and 2 unpinned tabs. tests.push({ pinned: true, extra: true, close: false, checkWinLin: checkNoWindowsGenerator(3), checkOSX: checkOSX34Generator(3) }); // The fourth test has 1 pinned tab, 2 unpinned tabs, and closes one unpinned tab. tests.push({ pinned: true, extra: true, close: "one", checkWinLin: checkNoWindowsGenerator(4), checkOSX: checkOSX34Generator(4) }); // The fifth test has 1 pinned tab, 2 unpinned tabs, and closes both unpinned tabs. tests.push({ pinned: true, extra: true, close: "both", checkWinLin: checkNoWindowsGenerator(5), checkOSX: checkNoWindowsGenerator(5) }); function test() { /** Test for Bug 589246 - Closed window state getting corrupted when closing and reopening last browser window without exiting browser **/ waitForExplicitFinish(); // windows opening & closing, so extending the timeout requestLongerTimeout(2); // We don't want the quit dialog pref gPrefService.setBoolPref("browser.tabs.warnOnClose", false); // Ensure that we would restore the session (important for Windows) gPrefService.setIntPref("browser.startup.page", 3); runNextTestOrFinish(); } function runNextTestOrFinish() { if (tests.length) { setupForTest(tests.shift()) } else { // some state is cleaned up at the end of each test, but not all ["browser.tabs.warnOnClose", "browser.startup.page"].forEach(function(p) { if (gPrefService.prefHasUserValue(p)) gPrefService.clearUserPref(p); }); ss.setBrowserState(stateBackup); executeSoon(finish); } } function setupForTest(aConditions) { // reset some checks gotLastWindowClosedTopic = false; shouldPinTab = aConditions.pinned; shouldOpenTabs = aConditions.extra; shouldCloseTab = aConditions.close; testNum++; // set our test callback afterTestCallback = /Mac/.test(navigator.platform) ? aConditions.checkOSX : aConditions.checkWinLin; // Add observers Services.obs.addObserver(onLastWindowClosed, "browser-lastwindow-close-granted", false); // Set the state Services.obs.addObserver(onStateRestored, "sessionstore-browser-state-restored", false); ss.setBrowserState(JSON.stringify(testState)); } function onStateRestored(aSubject, aTopic, aData) { info("test #" + testNum + ": onStateRestored"); Services.obs.removeObserver(onStateRestored, "sessionstore-browser-state-restored"); // change this window's windowtype so that closing a new window will trigger // browser-lastwindow-close-granted. document.documentElement.setAttribute("windowtype", "navigator:testrunner"); let newWin = openDialog(location, "_blank", "chrome,all,dialog=no", "http://example.com"); newWin.addEventListener("load", function(aEvent) { newWin.removeEventListener("load", arguments.callee, false); promiseBrowserLoaded(newWin.gBrowser.selectedBrowser).then(() => { // pin this tab if (shouldPinTab) newWin.gBrowser.pinTab(newWin.gBrowser.selectedTab); newWin.addEventListener("unload", function () { newWin.removeEventListener("unload", arguments.callee, false); onWindowUnloaded(); }, false); // Open a new tab as well. On Windows/Linux this will be restored when the // new window is opened below (in onWindowUnloaded). On OS X we'll just // restore the pinned tabs, leaving the unpinned tab in the closedWindowsData. if (shouldOpenTabs) { let newTab = newWin.gBrowser.addTab("about:config"); let newTab2 = newWin.gBrowser.addTab("about:buildconfig"); newTab.linkedBrowser.addEventListener("load", function() { newTab.linkedBrowser.removeEventListener("load", arguments.callee, true); if (shouldCloseTab == "one") { newWin.gBrowser.removeTab(newTab2); } else if (shouldCloseTab == "both") { newWin.gBrowser.removeTab(newTab); newWin.gBrowser.removeTab(newTab2); } newWin.BrowserTryToCloseWindow(); }, true); } else { newWin.BrowserTryToCloseWindow(); } }); }, false); } // This will be called before the window is actually closed function onLastWindowClosed(aSubject, aTopic, aData) { info("test #" + testNum + ": onLastWindowClosed"); Services.obs.removeObserver(onLastWindowClosed, "browser-lastwindow-close-granted"); gotLastWindowClosedTopic = true; } // This is the unload event listener on the new window (from onStateRestored). // Unload is fired after the window is closed, so sessionstore has already // updated _closedWindows (which is important). We'll open a new window here // which should actually trigger the bug. function onWindowUnloaded() { info("test #" + testNum + ": onWindowClosed"); ok(gotLastWindowClosedTopic, "test #" + testNum + ": browser-lastwindow-close-granted was notified prior"); let previousClosedWindowData = ss.getClosedWindowData(); // Now we want to open a new window let newWin = openDialog(location, "_blank", "chrome,all,dialog=no", "about:mozilla"); newWin.addEventListener("load", function(aEvent) { newWin.removeEventListener("load", arguments.callee, false); newWin.gBrowser.selectedBrowser.addEventListener("load", function () { newWin.gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); // Good enough for checking the state afterTestCallback(previousClosedWindowData, ss.getClosedWindowData()); afterTestCleanup(newWin); }, true); }, false); } function afterTestCleanup(aNewWin) { executeSoon(function() { BrowserTestUtils.closeWindow(aNewWin).then(() => { document.documentElement.setAttribute("windowtype", originalWindowType); runNextTestOrFinish(); }); }); }