browser_589246.js (8539B)
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 // Mirrors WINDOW_ATTRIBUTES IN SessionStore.sys.mjs 6 const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"]; 7 8 var stateBackup = ss.getBrowserState(); 9 10 var originalWarnOnClose = Services.prefs.getBoolPref( 11 "browser.tabs.warnOnClose" 12 ); 13 var originalStartupPage = Services.prefs.getIntPref("browser.startup.page"); 14 var originalWindowType = document.documentElement.getAttribute("windowtype"); 15 16 var gotLastWindowClosedTopic = false; 17 var shouldPinTab = false; 18 var shouldOpenTabs = false; 19 var shouldCloseTab = false; 20 var testNum = 0; 21 var afterTestCallback; 22 23 // Set state so we know the closed windows content 24 var testState = { 25 windows: [{ tabs: [{ entries: [{ url: "http://example.org" }] }] }], 26 _closedWindows: [], 27 }; 28 29 // We'll push a set of conditions and callbacks into this array 30 // Ideally we would also test win/linux under a complete set of conditions, but 31 // the tests for osx mirror the other set of conditions possible on win/linux. 32 var tests = []; 33 34 // the third & fourth test share a condition check, keep it DRY 35 function checkOSX34Generator(num) { 36 return function (aPreviousState, aCurState) { 37 // In here, we should have restored the pinned tab, so only the unpinned tab 38 // should be in aCurState. So let's shape our expectations. 39 let expectedState = JSON.parse(aPreviousState); 40 expectedState[0].tabs.shift(); 41 // size attributes are stripped out in _prepDataForDeferredRestore in SessionStore.sys.mjs. 42 // This isn't the best approach, but neither is comparing JSON strings 43 WINDOW_ATTRIBUTES.forEach(attr => delete expectedState[0][attr]); 44 45 is( 46 aCurState, 47 JSON.stringify(expectedState), 48 "test #" + num + ": closedWindowState is as expected" 49 ); 50 }; 51 } 52 function checkNoWindowsGenerator(num) { 53 return function (aPreviousState, aCurState) { 54 is( 55 aCurState, 56 "[]", 57 "test #" + num + ": there should be no closedWindowsLeft" 58 ); 59 }; 60 } 61 62 // The first test has 0 pinned tabs and 1 unpinned tab 63 tests.push({ 64 pinned: false, 65 extra: false, 66 close: false, 67 checkWinLin: checkNoWindowsGenerator(1), 68 checkOSX(aPreviousState, aCurState) { 69 is(aCurState, aPreviousState, "test #1: closed window state is unchanged"); 70 }, 71 }); 72 73 // The second test has 1 pinned tab and 0 unpinned tabs. 74 tests.push({ 75 pinned: true, 76 extra: false, 77 close: false, 78 checkWinLin: checkNoWindowsGenerator(2), 79 checkOSX: checkNoWindowsGenerator(2), 80 }); 81 82 // The third test has 1 pinned tab and 2 unpinned tabs. 83 tests.push({ 84 pinned: true, 85 extra: true, 86 close: false, 87 checkWinLin: checkNoWindowsGenerator(3), 88 checkOSX: checkOSX34Generator(3), 89 }); 90 91 // The fourth test has 1 pinned tab, 2 unpinned tabs, and closes one unpinned tab. 92 tests.push({ 93 pinned: true, 94 extra: true, 95 close: "one", 96 checkWinLin: checkNoWindowsGenerator(4), 97 checkOSX: checkOSX34Generator(4), 98 }); 99 100 // The fifth test has 1 pinned tab, 2 unpinned tabs, and closes both unpinned tabs. 101 tests.push({ 102 pinned: true, 103 extra: true, 104 close: "both", 105 checkWinLin: checkNoWindowsGenerator(5), 106 checkOSX: checkNoWindowsGenerator(5), 107 }); 108 109 function test() { 110 /** 111 * Test for Bug 589246 - Closed window state getting corrupted when closing 112 * and reopening last browser window without exiting browser 113 */ 114 waitForExplicitFinish(); 115 // windows opening & closing, so extending the timeout 116 requestLongerTimeout(2); 117 118 // We don't want the quit dialog pref 119 Services.prefs.setBoolPref("browser.tabs.warnOnClose", false); 120 // Ensure that we would restore the session (important for Windows) 121 Services.prefs.setIntPref("browser.startup.page", 3); 122 123 runNextTestOrFinish(); 124 } 125 126 function runNextTestOrFinish() { 127 if (tests.length) { 128 setupForTest(tests.shift()); 129 } else { 130 // some state is cleaned up at the end of each test, but not all 131 ["browser.tabs.warnOnClose", "browser.startup.page"].forEach(function (p) { 132 if (Services.prefs.prefHasUserValue(p)) { 133 Services.prefs.clearUserPref(p); 134 } 135 }); 136 137 ss.setBrowserState(stateBackup); 138 executeSoon(finish); 139 } 140 } 141 142 function setupForTest(aConditions) { 143 // reset some checks 144 gotLastWindowClosedTopic = false; 145 shouldPinTab = aConditions.pinned; 146 shouldOpenTabs = aConditions.extra; 147 shouldCloseTab = aConditions.close; 148 testNum++; 149 150 // set our test callback 151 afterTestCallback = /Mac/.test(navigator.platform) 152 ? aConditions.checkOSX 153 : aConditions.checkWinLin; 154 155 // Add observers 156 Services.obs.addObserver( 157 onLastWindowClosed, 158 "browser-lastwindow-close-granted" 159 ); 160 161 // Set the state 162 Services.obs.addObserver( 163 onStateRestored, 164 "sessionstore-browser-state-restored" 165 ); 166 ss.setBrowserState(JSON.stringify(testState)); 167 } 168 169 function onStateRestored() { 170 info("test #" + testNum + ": onStateRestored"); 171 Services.obs.removeObserver( 172 onStateRestored, 173 "sessionstore-browser-state-restored" 174 ); 175 176 // change this window's windowtype so that closing a new window will trigger 177 // browser-lastwindow-close-granted. 178 document.documentElement.setAttribute("windowtype", "navigator:testrunner"); 179 180 let newWin = openDialog( 181 location, 182 "_blank", 183 "chrome,all,dialog=no", 184 "http://example.com" 185 ); 186 newWin.addEventListener( 187 "load", 188 function () { 189 promiseBrowserLoaded(newWin.gBrowser.selectedBrowser).then(() => { 190 // pin this tab 191 if (shouldPinTab) { 192 newWin.gBrowser.pinTab(newWin.gBrowser.selectedTab); 193 } 194 195 newWin.addEventListener( 196 "unload", 197 function () { 198 onWindowUnloaded(); 199 }, 200 { once: true } 201 ); 202 // Open a new tab as well. On Windows/Linux this will be restored when the 203 // new window is opened below (in onWindowUnloaded). On OS X we'll just 204 // restore the pinned tabs, leaving the unpinned tab in the closedWindowsData. 205 if (shouldOpenTabs) { 206 let newTab = BrowserTestUtils.addTab(newWin.gBrowser, "about:config"); 207 let newTab2 = BrowserTestUtils.addTab( 208 newWin.gBrowser, 209 "about:buildconfig" 210 ); 211 212 newTab.linkedBrowser.addEventListener( 213 "load", 214 function () { 215 if (shouldCloseTab == "one") { 216 newWin.gBrowser.removeTab(newTab2); 217 } else if (shouldCloseTab == "both") { 218 newWin.gBrowser.removeTab(newTab); 219 newWin.gBrowser.removeTab(newTab2); 220 } 221 newWin.BrowserCommands.tryToCloseWindow(); 222 }, 223 { capture: true, once: true } 224 ); 225 } else { 226 newWin.BrowserCommands.tryToCloseWindow(); 227 } 228 }); 229 }, 230 { once: true } 231 ); 232 } 233 234 // This will be called before the window is actually closed 235 function onLastWindowClosed() { 236 info("test #" + testNum + ": onLastWindowClosed"); 237 Services.obs.removeObserver( 238 onLastWindowClosed, 239 "browser-lastwindow-close-granted" 240 ); 241 gotLastWindowClosedTopic = true; 242 } 243 244 // This is the unload event listener on the new window (from onStateRestored). 245 // Unload is fired after the window is closed, so sessionstore has already 246 // updated _closedWindows (which is important). We'll open a new window here 247 // which should actually trigger the bug. 248 function onWindowUnloaded() { 249 info("test #" + testNum + ": onWindowClosed"); 250 ok( 251 gotLastWindowClosedTopic, 252 "test #" + testNum + ": browser-lastwindow-close-granted was notified prior" 253 ); 254 255 let previousClosedWindowData = ss.getClosedWindowData(); 256 257 // Now we want to open a new window 258 let newWin = openDialog( 259 location, 260 "_blank", 261 "chrome,all,dialog=no", 262 "about:mozilla" 263 ); 264 newWin.addEventListener( 265 "load", 266 function () { 267 newWin.gBrowser.selectedBrowser.addEventListener( 268 "load", 269 function () { 270 // Good enough for checking the state 271 afterTestCallback(previousClosedWindowData, ss.getClosedWindowData()); 272 afterTestCleanup(newWin); 273 }, 274 { capture: true, once: true } 275 ); 276 }, 277 { once: true } 278 ); 279 } 280 281 function afterTestCleanup(aNewWin) { 282 executeSoon(function () { 283 BrowserTestUtils.closeWindow(aNewWin).then(() => { 284 document.documentElement.setAttribute("windowtype", originalWindowType); 285 runNextTestOrFinish(); 286 }); 287 }); 288 }