Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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/. */
5 const TAB_STATE_NEEDS_RESTORE = 1;
6 const TAB_STATE_RESTORING = 2;
8 const ROOT = getRootDirectory(gTestPath);
9 const FRAME_SCRIPTS = [
10 ROOT + "content.js",
11 ROOT + "content-forms.js"
12 ];
14 let mm = Cc["@mozilla.org/globalmessagemanager;1"]
15 .getService(Ci.nsIMessageListenerManager);
17 for (let script of FRAME_SCRIPTS) {
18 mm.loadFrameScript(script, true);
19 }
21 mm.addMessageListener("SessionStore:setupSyncHandler", onSetupSyncHandler);
23 /**
24 * This keeps track of all SyncHandlers passed to chrome from frame scripts.
25 * We need this to let tests communicate with frame scripts and cause (a)sync
26 * flushes.
27 */
28 let SyncHandlers = new WeakMap();
29 function onSetupSyncHandler(msg) {
30 SyncHandlers.set(msg.target, msg.objects.handler);
31 }
33 registerCleanupFunction(() => {
34 for (let script of FRAME_SCRIPTS) {
35 mm.removeDelayedFrameScript(script, true);
36 }
37 mm.removeMessageListener("SessionStore:setupSyncHandler", onSetupSyncHandler);
38 });
40 let tmp = {};
41 Cu.import("resource://gre/modules/Promise.jsm", tmp);
42 Cu.import("resource:///modules/sessionstore/SessionStore.jsm", tmp);
43 Cu.import("resource:///modules/sessionstore/SessionSaver.jsm", tmp);
44 let {Promise, SessionStore, SessionSaver} = tmp;
46 let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
48 // Some tests here assume that all restored tabs are loaded without waiting for
49 // the user to bring them to the foreground. We ensure this by resetting the
50 // related preference (see the "firefox.js" defaults file for details).
51 Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", false);
52 registerCleanupFunction(function () {
53 Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
54 });
56 // Obtain access to internals
57 Services.prefs.setBoolPref("browser.sessionstore.debug", true);
58 registerCleanupFunction(function () {
59 Services.prefs.clearUserPref("browser.sessionstore.debug");
60 });
63 // This kicks off the search service used on about:home and allows the
64 // session restore tests to be run standalone without triggering errors.
65 Cc["@mozilla.org/browser/clh;1"].getService(Ci.nsIBrowserHandler).defaultArgs;
67 function provideWindow(aCallback, aURL, aFeatures) {
68 function callbackSoon(aWindow) {
69 executeSoon(function executeCallbackSoon() {
70 aCallback(aWindow);
71 });
72 }
74 let win = openDialog(getBrowserURL(), "", aFeatures || "chrome,all,dialog=no", aURL);
75 whenWindowLoaded(win, function onWindowLoaded(aWin) {
76 if (!aURL) {
77 info("Loaded a blank window.");
78 callbackSoon(aWin);
79 return;
80 }
82 aWin.gBrowser.selectedBrowser.addEventListener("load", function selectedBrowserLoadListener() {
83 aWin.gBrowser.selectedBrowser.removeEventListener("load", selectedBrowserLoadListener, true);
84 callbackSoon(aWin);
85 }, true);
86 });
87 }
89 // This assumes that tests will at least have some state/entries
90 function waitForBrowserState(aState, aSetStateCallback) {
91 let windows = [window];
92 let tabsRestored = 0;
93 let expectedTabsRestored = 0;
94 let expectedWindows = aState.windows.length;
95 let windowsOpen = 1;
96 let listening = false;
97 let windowObserving = false;
98 let restoreHiddenTabs = Services.prefs.getBoolPref(
99 "browser.sessionstore.restore_hidden_tabs");
101 aState.windows.forEach(function (winState) {
102 winState.tabs.forEach(function (tabState) {
103 if (restoreHiddenTabs || !tabState.hidden)
104 expectedTabsRestored++;
105 });
106 });
108 // There must be only hidden tabs and restoreHiddenTabs = false. We still
109 // expect one of them to be restored because it gets shown automatically.
110 if (!expectedTabsRestored)
111 expectedTabsRestored = 1;
113 function onSSTabRestored(aEvent) {
114 if (++tabsRestored == expectedTabsRestored) {
115 // Remove the event listener from each window
116 windows.forEach(function(win) {
117 win.gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored, true);
118 });
119 listening = false;
120 info("running " + aSetStateCallback.name);
121 executeSoon(aSetStateCallback);
122 }
123 }
125 // Used to add our listener to further windows so we can catch SSTabRestored
126 // coming from them when creating a multi-window state.
127 function windowObserver(aSubject, aTopic, aData) {
128 if (aTopic == "domwindowopened") {
129 let newWindow = aSubject.QueryInterface(Ci.nsIDOMWindow);
130 newWindow.addEventListener("load", function() {
131 newWindow.removeEventListener("load", arguments.callee, false);
133 if (++windowsOpen == expectedWindows) {
134 Services.ww.unregisterNotification(windowObserver);
135 windowObserving = false;
136 }
138 // Track this window so we can remove the progress listener later
139 windows.push(newWindow);
140 // Add the progress listener
141 newWindow.gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored, true);
142 }, false);
143 }
144 }
146 // We only want to register the notification if we expect more than 1 window
147 if (expectedWindows > 1) {
148 registerCleanupFunction(function() {
149 if (windowObserving) {
150 Services.ww.unregisterNotification(windowObserver);
151 }
152 });
153 windowObserving = true;
154 Services.ww.registerNotification(windowObserver);
155 }
157 registerCleanupFunction(function() {
158 if (listening) {
159 windows.forEach(function(win) {
160 win.gBrowser.tabContainer.removeEventListener("SSTabRestored", onSSTabRestored, true);
161 });
162 }
163 });
164 // Add the event listener for this window as well.
165 listening = true;
166 gBrowser.tabContainer.addEventListener("SSTabRestored", onSSTabRestored, true);
168 // Ensure setBrowserState() doesn't remove the initial tab.
169 gBrowser.selectedTab = gBrowser.tabs[0];
171 // Finally, call setBrowserState
172 ss.setBrowserState(JSON.stringify(aState));
173 }
175 // Doesn't assume that the tab needs to be closed in a cleanup function.
176 // If that's the case, the test author should handle that in the test.
177 function waitForTabState(aTab, aState, aCallback) {
178 let listening = true;
180 function onSSTabRestored() {
181 aTab.removeEventListener("SSTabRestored", onSSTabRestored, false);
182 listening = false;
183 aCallback();
184 }
186 aTab.addEventListener("SSTabRestored", onSSTabRestored, false);
188 registerCleanupFunction(function() {
189 if (listening) {
190 aTab.removeEventListener("SSTabRestored", onSSTabRestored, false);
191 }
192 });
193 ss.setTabState(aTab, JSON.stringify(aState));
194 }
196 /**
197 * Wait for a content -> chrome message.
198 */
199 function promiseContentMessage(browser, name) {
200 let deferred = Promise.defer();
201 let mm = browser.messageManager;
203 function removeListener() {
204 mm.removeMessageListener(name, listener);
205 }
207 function listener(msg) {
208 removeListener();
209 deferred.resolve(msg.data);
210 }
212 mm.addMessageListener(name, listener);
213 registerCleanupFunction(removeListener);
214 return deferred.promise;
215 }
217 function waitForTopic(aTopic, aTimeout, aCallback) {
218 let observing = false;
219 function removeObserver() {
220 if (!observing)
221 return;
222 Services.obs.removeObserver(observer, aTopic);
223 observing = false;
224 }
226 let timeout = setTimeout(function () {
227 removeObserver();
228 aCallback(false);
229 }, aTimeout);
231 function observer(aSubject, aTopic, aData) {
232 removeObserver();
233 timeout = clearTimeout(timeout);
234 executeSoon(() => aCallback(true));
235 }
237 registerCleanupFunction(function() {
238 removeObserver();
239 if (timeout) {
240 clearTimeout(timeout);
241 }
242 });
244 observing = true;
245 Services.obs.addObserver(observer, aTopic, false);
246 }
248 /**
249 * Wait until session restore has finished collecting its data and is
250 * has written that data ("sessionstore-state-write-complete").
251 *
252 * @param {function} aCallback If sessionstore-state-write is sent
253 * within buffering interval + 100 ms, the callback is passed |true|,
254 * otherwise, it is passed |false|.
255 */
256 function waitForSaveState(aCallback) {
257 let timeout = 100 +
258 Services.prefs.getIntPref("browser.sessionstore.interval");
259 return waitForTopic("sessionstore-state-write-complete", timeout, aCallback);
260 }
261 function promiseSaveState() {
262 let deferred = Promise.defer();
263 waitForSaveState(isSuccessful => {
264 if (isSuccessful) {
265 deferred.resolve();
266 } else {
267 deferred.reject(new Error("timeout"));
268 }});
269 return deferred.promise;
270 }
271 function forceSaveState() {
272 return SessionSaver.run();
273 }
275 function promiseSaveFileContents() {
276 let promise = forceSaveState();
277 return promise.then(function() {
278 return OS.File.read(OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"), { encoding: "utf-8" });
279 });
280 }
282 function whenBrowserLoaded(aBrowser, aCallback = next, ignoreSubFrames = true) {
283 aBrowser.addEventListener("load", function onLoad(event) {
284 if (!ignoreSubFrames || event.target == aBrowser.contentDocument) {
285 aBrowser.removeEventListener("load", onLoad, true);
286 executeSoon(aCallback);
287 }
288 }, true);
289 }
290 function promiseBrowserLoaded(aBrowser, ignoreSubFrames = true) {
291 let deferred = Promise.defer();
292 whenBrowserLoaded(aBrowser, deferred.resolve, ignoreSubFrames);
293 return deferred.promise;
294 }
295 function whenBrowserUnloaded(aBrowser, aContainer, aCallback = next) {
296 aBrowser.addEventListener("unload", function onUnload() {
297 aBrowser.removeEventListener("unload", onUnload, true);
298 executeSoon(aCallback);
299 }, true);
300 }
301 function promiseBrowserUnloaded(aBrowser, aContainer) {
302 let deferred = Promise.defer();
303 whenBrowserUnloaded(aBrowser, aContainer, deferred.resolve);
304 return deferred.promise;
305 }
307 function whenWindowLoaded(aWindow, aCallback = next) {
308 aWindow.addEventListener("load", function windowLoadListener() {
309 aWindow.removeEventListener("load", windowLoadListener, false);
310 executeSoon(function executeWhenWindowLoaded() {
311 aCallback(aWindow);
312 });
313 }, false);
314 }
315 function promiseWindowLoaded(aWindow) {
316 let deferred = Promise.defer();
317 whenWindowLoaded(aWindow, deferred.resolve);
318 return deferred.promise;
319 }
321 function whenTabRestored(aTab, aCallback = next) {
322 aTab.addEventListener("SSTabRestored", function onRestored(aEvent) {
323 aTab.removeEventListener("SSTabRestored", onRestored, true);
324 executeSoon(function executeWhenTabRestored() {
325 aCallback();
326 });
327 }, true);
328 }
330 var gUniqueCounter = 0;
331 function r() {
332 return Date.now() + "-" + (++gUniqueCounter);
333 }
335 function BrowserWindowIterator() {
336 let windowsEnum = Services.wm.getEnumerator("navigator:browser");
337 while (windowsEnum.hasMoreElements()) {
338 let currentWindow = windowsEnum.getNext();
339 if (!currentWindow.closed) {
340 yield currentWindow;
341 }
342 }
343 }
345 let gWebProgressListener = {
346 _callback: null,
348 setCallback: function (aCallback) {
349 if (!this._callback) {
350 window.gBrowser.addTabsProgressListener(this);
351 }
352 this._callback = aCallback;
353 },
355 unsetCallback: function () {
356 if (this._callback) {
357 this._callback = null;
358 window.gBrowser.removeTabsProgressListener(this);
359 }
360 },
362 onStateChange: function (aBrowser, aWebProgress, aRequest,
363 aStateFlags, aStatus) {
364 if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
365 aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
366 aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
367 this._callback(aBrowser);
368 }
369 }
370 };
372 registerCleanupFunction(function () {
373 gWebProgressListener.unsetCallback();
374 });
376 let gProgressListener = {
377 _callback: null,
379 setCallback: function (callback) {
380 Services.obs.addObserver(this, "sessionstore-debug-tab-restored", false);
381 this._callback = callback;
382 },
384 unsetCallback: function () {
385 if (this._callback) {
386 this._callback = null;
387 Services.obs.removeObserver(this, "sessionstore-debug-tab-restored");
388 }
389 },
391 observe: function (browser, topic, data) {
392 gProgressListener.onRestored(browser);
393 },
395 onRestored: function (browser) {
396 if (browser.__SS_restoreState == TAB_STATE_RESTORING) {
397 let args = [browser].concat(gProgressListener._countTabs());
398 gProgressListener._callback.apply(gProgressListener, args);
399 }
400 },
402 _countTabs: function () {
403 let needsRestore = 0, isRestoring = 0, wasRestored = 0;
405 for (let win in BrowserWindowIterator()) {
406 for (let i = 0; i < win.gBrowser.tabs.length; i++) {
407 let browser = win.gBrowser.tabs[i].linkedBrowser;
408 if (!browser.__SS_restoreState)
409 wasRestored++;
410 else if (browser.__SS_restoreState == TAB_STATE_RESTORING)
411 isRestoring++;
412 else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
413 needsRestore++;
414 }
415 }
416 return [needsRestore, isRestoring, wasRestored];
417 }
418 };
420 registerCleanupFunction(function () {
421 gProgressListener.unsetCallback();
422 });
424 // Close everything but our primary window. We can't use waitForFocus()
425 // because apparently it's buggy. See bug 599253.
426 function closeAllButPrimaryWindow() {
427 for (let win in BrowserWindowIterator()) {
428 if (win != window) {
429 win.close();
430 }
431 }
432 }
434 /**
435 * When opening a new window it is not sufficient to wait for its load event.
436 * We need to use whenDelayedStartupFinshed() here as the browser window's
437 * delayedStartup() routine is executed one tick after the window's load event
438 * has been dispatched. browser-delayed-startup-finished might be deferred even
439 * further if parts of the window's initialization process take more time than
440 * expected (e.g. reading a big session state from disk).
441 */
442 function whenNewWindowLoaded(aOptions, aCallback) {
443 let win = OpenBrowserWindow(aOptions);
444 whenDelayedStartupFinished(win, () => aCallback(win));
445 return win;
446 }
447 function promiseNewWindowLoaded(aOptions) {
448 let deferred = Promise.defer();
449 whenNewWindowLoaded(aOptions, deferred.resolve);
450 return deferred.promise;
451 }
453 /**
454 * Chrome windows aren't closed synchronously. Provide a helper method to close
455 * a window and wait until we received the "domwindowclosed" notification for it.
456 */
457 function promiseWindowClosed(win) {
458 let deferred = Promise.defer();
460 Services.obs.addObserver(function obs(subject, topic) {
461 if (subject == win) {
462 Services.obs.removeObserver(obs, topic);
463 deferred.resolve();
464 }
465 }, "domwindowclosed", false);
467 win.close();
468 return deferred.promise;
469 }
471 /**
472 * This waits for the browser-delayed-startup-finished notification of a given
473 * window. It indicates that the windows has loaded completely and is ready to
474 * be used for testing.
475 */
476 function whenDelayedStartupFinished(aWindow, aCallback) {
477 Services.obs.addObserver(function observer(aSubject, aTopic) {
478 if (aWindow == aSubject) {
479 Services.obs.removeObserver(observer, aTopic);
480 executeSoon(aCallback);
481 }
482 }, "browser-delayed-startup-finished", false);
483 }
485 /**
486 * The test runner that controls the execution flow of our tests.
487 */
488 let TestRunner = {
489 _iter: null,
491 /**
492 * Holds the browser state from before we started so
493 * that we can restore it after all tests ran.
494 */
495 backupState: {},
497 /**
498 * Starts the test runner.
499 */
500 run: function () {
501 waitForExplicitFinish();
503 SessionStore.promiseInitialized.then(() => {
504 this.backupState = JSON.parse(ss.getBrowserState());
505 this._iter = runTests();
506 this.next();
507 });
508 },
510 /**
511 * Runs the next available test or finishes if there's no test left.
512 */
513 next: function () {
514 try {
515 TestRunner._iter.next();
516 } catch (e if e instanceof StopIteration) {
517 TestRunner.finish();
518 }
519 },
521 /**
522 * Finishes all tests and cleans up.
523 */
524 finish: function () {
525 closeAllButPrimaryWindow();
526 gBrowser.selectedTab = gBrowser.tabs[0];
527 waitForBrowserState(this.backupState, finish);
528 }
529 };
531 function next() {
532 TestRunner.next();
533 }
535 function promiseTabRestored(tab) {
536 let deferred = Promise.defer();
538 tab.addEventListener("SSTabRestored", function onRestored() {
539 tab.removeEventListener("SSTabRestored", onRestored);
540 deferred.resolve();
541 });
543 return deferred.promise;
544 }
546 function sendMessage(browser, name, data = {}) {
547 browser.messageManager.sendAsyncMessage(name, data);
548 return promiseContentMessage(browser, name);
549 }
551 // This creates list of functions that we will map to their corresponding
552 // ss-test:* messages names. Those will be sent to the frame script and
553 // be used to read and modify form data.
554 const FORM_HELPERS = [
555 "getTextContent",
556 "getInputValue", "setInputValue",
557 "getInputChecked", "setInputChecked",
558 "getSelectedIndex", "setSelectedIndex",
559 "getMultipleSelected", "setMultipleSelected",
560 "getFileNameArray", "setFileNameArray",
561 ];
563 for (let name of FORM_HELPERS) {
564 let msg = "ss-test:" + name;
565 this[name] = (browser, data) => sendMessage(browser, msg, data);
566 }