browser/components/sessionstore/test/head.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

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 }

mercurial