1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
|
/* 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 SessionStore.sys.mjs
const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"];
var stateBackup = ss.getBrowserState();
var originalWarnOnClose = Services.prefs.getBoolPref(
"browser.tabs.warnOnClose"
);
var originalStartupPage = Services.prefs.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 SessionStore.sys.mjs.
// 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(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
Services.prefs.setBoolPref("browser.tabs.warnOnClose", false);
// Ensure that we would restore the session (important for Windows)
Services.prefs.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 (Services.prefs.prefHasUserValue(p)) {
Services.prefs.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"
);
// Set the state
Services.obs.addObserver(
onStateRestored,
"sessionstore-browser-state-restored"
);
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) {
promiseBrowserLoaded(newWin.gBrowser.selectedBrowser).then(() => {
// pin this tab
if (shouldPinTab) {
newWin.gBrowser.pinTab(newWin.gBrowser.selectedTab);
}
newWin.addEventListener(
"unload",
function () {
onWindowUnloaded();
},
{ once: true }
);
// 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 = BrowserTestUtils.addTab(newWin.gBrowser, "about:config");
let newTab2 = BrowserTestUtils.addTab(
newWin.gBrowser,
"about:buildconfig"
);
newTab.linkedBrowser.addEventListener(
"load",
function () {
if (shouldCloseTab == "one") {
newWin.gBrowser.removeTab(newTab2);
} else if (shouldCloseTab == "both") {
newWin.gBrowser.removeTab(newTab);
newWin.gBrowser.removeTab(newTab2);
}
newWin.BrowserTryToCloseWindow();
},
{ capture: true, once: true }
);
} else {
newWin.BrowserTryToCloseWindow();
}
});
},
{ once: true }
);
}
// 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.gBrowser.selectedBrowser.addEventListener(
"load",
function () {
// Good enough for checking the state
afterTestCallback(previousClosedWindowData, ss.getClosedWindowData());
afterTestCleanup(newWin);
},
{ capture: true, once: true }
);
},
{ once: true }
);
}
function afterTestCleanup(aNewWin) {
executeSoon(function () {
BrowserTestUtils.closeWindow(aNewWin).then(() => {
document.documentElement.setAttribute("windowtype", originalWindowType);
runNextTestOrFinish();
});
});
}
|