Bug 867143 - Adapt testsuite to cached session restore. r=ttaubert
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Fri, 26 Jul 2013 12:15:25 -0400
changeset 140106 6a62ce6a092bd57a28f0139b4b7cf5bd7863fb27
parent 140105 6f8cc9245141fb1cdf7131981d87ab8426ec973c
child 140107 6fba91b781eeb3e9b4a72f7d0ac4a161580fbbf1
push id1941
push userryanvm@gmail.com
push dateFri, 26 Jul 2013 16:15:50 +0000
treeherderfx-team@6a62ce6a092b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersttaubert
bugs867143
milestone25.0a1
Bug 867143 - Adapt testsuite to cached session restore. r=ttaubert
browser/components/sessionstore/test/Makefile.in
browser/components/sessionstore/test/browser_625257.js
browser/components/sessionstore/test/browser_capabilities.js
browser/components/sessionstore/test/browser_sessionStorage.js
browser/components/sessionstore/test/head.js
--- a/browser/components/sessionstore/test/Makefile.in
+++ b/browser/components/sessionstore/test/Makefile.in
@@ -22,16 +22,17 @@ MOCHITEST_BROWSER_FILES = \
 	browser_dying_cache.js \
 	browser_form_restore_events.js \
 	browser_form_restore_events_sample.html \
 	browser_formdata_format.js \
 	browser_formdata_format_sample.html \
 	browser_input.js \
 	browser_input_sample.html \
 	browser_pageshow.js \
+	browser_sessionStorage.js \
         browser_upgrade_backup.js \
 	browser_windowRestore_perwindowpb.js \
 	browser_248970_b_perwindowpb.js \
 	browser_248970_b_sample.html \
 	browser_339445.js \
 	browser_339445_sample.html \
 	browser_345898.js \
 	browser_346337.js \
--- a/browser/components/sessionstore/test/browser_625257.js
+++ b/browser/components/sessionstore/test/browser_625257.js
@@ -1,85 +1,86 @@
 /* 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/. */
 
+let Scope = {};
+Cu.import("resource://gre/modules/Task.jsm", Scope);
+Cu.import("resource://gre/modules/Promise.jsm", Scope);
+let {Task, Promise} = Scope;
+
+
 // This tests that a tab which is closed while loading is not lost.
 // Specifically, that session store does not rely on an invalid cache when
 // constructing data for a tab which is loading.
 
-// The newly created tab which we load a URL into and try closing/undoing.
-let tab;
-
 // This test steps through the following parts:
 //  1. Tab has been created is loading URI_TO_LOAD.
 //  2. Before URI_TO_LOAD finishes loading, browser.currentURI has changed and
 //     tab is scheduled to be removed.
 //  3. After the tab has been closed, undoCloseTab() has been called and the tab
 //     should fully load.
 const URI_TO_LOAD = "about:mozilla";
 
+function waitForLoadStarted(aTab) {
+  let deferred = Promise.defer();
+  waitForContentMessage(aTab.linkedBrowser,
+    "SessionStore:loadStart",
+    1000,
+    deferred.resolve);
+  return deferred.promise;
+}
+
+function waitForTabLoaded(aTab) {
+  let deferred = Promise.defer();
+  whenBrowserLoaded(aTab.linkedBrowser, deferred.resolve);
+  return deferred.promise;
+}
+
+function waitForTabClosed() {
+  let deferred = Promise.defer();
+  let observer = function() {
+    gBrowser.tabContainer.removeEventListener("TabClose", observer, true);
+    deferred.resolve();
+  };
+  gBrowser.tabContainer.addEventListener("TabClose", observer, true);
+  return deferred.promise;
+}
+
 function test() {
   waitForExplicitFinish();
 
-  gBrowser.addTabsProgressListener(tabsListener);
-
-  tab = gBrowser.addTab();
-
-  tab.linkedBrowser.addEventListener("load", firstOnLoad, true);
-
-  gBrowser.tabContainer.addEventListener("TabClose", onTabClose, true);
-}
-
-function firstOnLoad(aEvent) {
-  tab.linkedBrowser.removeEventListener("load", firstOnLoad, true);
+  Task.spawn(function() {
+    try {
+      // Open a new tab
+      let tab = gBrowser.addTab("about:blank");
+      yield waitForTabLoaded(tab);
 
-  let uri = aEvent.target.location;
-  is(uri, "about:blank", "first load should be for about:blank");
-
-  // Trigger a save state.
-  ss.getBrowserState();
+      // Trigger a save state, to initialize any caches
+      ss.getBrowserState();
 
-  is(gBrowser.tabs[1], tab, "newly created tab should exist by now");
-  ok(tab.linkedBrowser.__SS_data, "newly created tab should be in save state");
+      is(gBrowser.tabs[1], tab, "newly created tab should exist by now");
+      ok(tab.linkedBrowser.__SS_data, "newly created tab should be in save state");
 
-  tab.linkedBrowser.loadURI(URI_TO_LOAD);
-}
+      // Start a load and interrupt it by closing the tab
+      tab.linkedBrowser.loadURI(URI_TO_LOAD);
+      let loaded = yield waitForLoadStarted(tab);
+      ok(loaded, "Load started");
 
-let tabsListener = {
-  onLocationChange: function onLocationChange(aBrowser) {
-    gBrowser.removeTabsProgressListener(tabsListener);
-
-    is(aBrowser.currentURI.spec, URI_TO_LOAD,
-       "should occur after about:blank load and be loading next page");
-
-    // Since we are running in the context of tabs listeners, we do not
-    // want to disrupt other tabs listeners.
-    executeSoon(function() {
+      let tabClosing = waitForTabClosed();
       gBrowser.removeTab(tab);
-    });
-  }
-};
+      info("Now waiting for TabClose to close");
+      yield tabClosing;
 
-function onTabClose(aEvent) {
-  gBrowser.tabContainer.removeEventListener("TabClose", onTabClose, true);
+      // Undo the tab, ensure that it proceeds with loading
+      tab = ss.undoCloseTab(window, 0);
+      yield waitForTabLoaded(tab);
+      is(tab.linkedBrowser.currentURI.spec, URI_TO_LOAD, "loading proceeded as expected");
 
-  is(tab.linkedBrowser.currentURI.spec, URI_TO_LOAD,
-     "should only remove when loading page");
+      gBrowser.removeTab(tab);
 
-  executeSoon(function() {
-    tab = ss.undoCloseTab(window, 0);
-    tab.linkedBrowser.addEventListener("load", secondOnLoad, true);
+      executeSoon(finish);
+    } catch (ex) {
+      ok(false, ex);
+      info(ex.stack);
+    }
   });
 }
-
-function secondOnLoad(aEvent) {
-  let uri = aEvent.target.location;
-  is(uri, URI_TO_LOAD, "should load page from undoCloseTab");
-  done();
-}
-
-function done() {
-  tab.linkedBrowser.removeEventListener("load", secondOnLoad, true);
-  gBrowser.removeTab(tab);
-
-  executeSoon(finish);
-}
--- a/browser/components/sessionstore/test/browser_capabilities.js
+++ b/browser/components/sessionstore/test/browser_capabilities.js
@@ -25,16 +25,21 @@ function runTests() {
   let state = JSON.parse(ss.getTabState(tab));
   ok(!("disallow" in state), "everything allowed by default");
   ok(flags.every(f => docShell[f]), "all flags set to true");
 
   // Flip a couple of allow* flags.
   docShell.allowImages = false;
   docShell.allowMetaRedirects = false;
 
+  // Now reload the document to ensure that these capabilities
+  // are taken into account
+  browser.reload();
+  yield whenBrowserLoaded(browser);
+
   // Check that we correctly save disallowed features.
   let disallowedState = JSON.parse(ss.getTabState(tab));
   let disallow = new Set(disallowedState.disallow.split(","));
   ok(disallow.has("Images"), "images not allowed");
   ok(disallow.has("MetaRedirects"), "meta redirects not allowed");
   is(disallow.size, 2, "two capabilities disallowed");
 
   // Reuse the tab to restore a new, clean state into it.
@@ -47,17 +52,17 @@ function runTests() {
   ok(flags.every(f => docShell[f]), "all flags set to true");
 
   // Restore the state with disallowed features.
   ss.setTabState(tab, JSON.stringify(disallowedState));
   yield waitForLoad(browser);
 
   // Check that docShell flags are set.
   ok(!docShell.allowImages, "images not allowed");
-  ok(!docShell.allowMetaRedirects, "meta redirects not allowed")
+  ok(!docShell.allowMetaRedirects, "meta redirects not allowed");
 
   // Check that we correctly restored features as disabled.
   state = JSON.parse(ss.getTabState(tab));
   disallow = new Set(state.disallow.split(","));
   ok(disallow.has("Images"), "images not allowed anymore");
   ok(disallow.has("MetaRedirects"), "meta redirects not allowed anymore");
   is(disallow.size, 2, "two capabilities disallowed");
 
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_sessionStorage.js
@@ -0,0 +1,91 @@
+/* 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/. */
+
+let Scope = {};
+Cu.import("resource://gre/modules/Task.jsm", Scope);
+Cu.import("resource://gre/modules/Promise.jsm", Scope);
+let {Task, Promise} = Scope;
+
+function promiseBrowserLoaded(aBrowser) {
+  let deferred = Promise.defer();
+  whenBrowserLoaded(aBrowser, () => deferred.resolve());
+  return deferred.promise;
+}
+
+function forceWriteState() {
+  let deferred = Promise.defer();
+  const PREF = "browser.sessionstore.interval";
+  const TOPIC = "sessionstore-state-write";
+
+  Services.obs.addObserver(function observe() {
+    Services.obs.removeObserver(observe, TOPIC);
+    Services.prefs.clearUserPref(PREF);
+    deferred.resolve();
+  }, TOPIC, false);
+
+  Services.prefs.setIntPref(PREF, 0);
+  return deferred.promise;
+}
+
+function waitForStorageChange(aTab) {
+  let deferred = Promise.defer();
+  waitForContentMessage(aTab.linkedBrowser,
+    "SessionStore:MozStorageChanged",
+    1000,
+    deferred.resolve);
+  return deferred.promise;
+}
+
+function test() {
+
+  waitForExplicitFinish();
+
+  let tab;
+  Task.spawn(function() {
+    try {
+      tab = gBrowser.addTab("http://example.com");
+      // about:home supports sessionStorage and localStorage
+
+      let win = tab.linkedBrowser.contentWindow;
+
+      // Flush loading and next save, call getBrowserState()
+      // a few times to ensure that everything is cached.
+      yield promiseBrowserLoaded(tab.linkedBrowser);
+      yield forceWriteState();
+      info("Calling getBrowserState() to populate cache");
+      ss.getBrowserState();
+
+      info("Change sessionStorage, ensure that state is saved");
+      win.sessionStorage["SESSION_STORAGE_KEY"] = "SESSION_STORAGE_VALUE";
+      let storageChanged = yield waitForStorageChange(tab);
+      ok(storageChanged, "Changing sessionStorage triggered the right message");
+      yield forceWriteState();
+
+      let state = ss.getBrowserState();
+      ok(state.indexOf("SESSION_STORAGE_KEY") != -1, "Key appears in state");
+      ok(state.indexOf("SESSION_STORAGE_VALUE") != -1, "Value appears in state");
+
+
+      info("Change localStorage, ensure that state is not saved");
+      win.localStorage["LOCAL_STORAGE_KEY"] = "LOCAL_STORAGE_VALUE";
+      storageChanged = yield waitForStorageChange(tab);
+      ok(!storageChanged, "Changing localStorage did not trigger a message");
+      yield forceWriteState();
+
+      state = ss.getBrowserState();
+      ok(state.indexOf("LOCAL_STORAGE_KEY") == -1, "Key does not appear in state");
+      ok(state.indexOf("LOCAL_STORAGE_VALUE") == -1, "Value does not appear in state");
+    } catch (ex) {
+      ok(false, ex);
+      info(ex.stack);
+    } finally {
+      // clean up
+      if (tab) {
+        gBrowser.removeTab(tab);
+      }
+
+      executeSoon(finish);
+    }
+  });
+}
--- a/browser/components/sessionstore/test/head.js
+++ b/browser/components/sessionstore/test/head.js
@@ -151,53 +151,100 @@ function waitForTabState(aTab, aState, a
   registerCleanupFunction(function() {
     if (listening) {
       aTab.removeEventListener("SSTabRestored", onSSTabRestored, false);
     }
   });
   ss.setTabState(aTab, JSON.stringify(aState));
 }
 
-// waitForSaveState waits for a state write but not necessarily for the state to
-// turn dirty.
-function waitForSaveState(aSaveStateCallback) {
+/**
+ * Wait for a content -> chrome message.
+ */
+function waitForContentMessage(aBrowser, aTopic, aTimeout, aCallback) {
+  let mm = aBrowser.messageManager;
   let observing = false;
-  let topic = "sessionstore-state-write";
-
-  let sessionSaveTimeout = 1000 +
-    Services.prefs.getIntPref("browser.sessionstore.interval");
-
   function removeObserver() {
     if (!observing)
       return;
-    Services.obs.removeObserver(observer, topic);
+    mm.removeMessageListener(aTopic, observer);
     observing = false;
   }
 
   let timeout = setTimeout(function () {
     removeObserver();
-    aSaveStateCallback();
-  }, sessionSaveTimeout);
+    aCallback(false);
+  }, aTimeout);
 
   function observer(aSubject, aTopic, aData) {
     removeObserver();
     timeout = clearTimeout(timeout);
-    executeSoon(aSaveStateCallback);
+    executeSoon(() => aCallback(true));
   }
 
   registerCleanupFunction(function() {
     removeObserver();
     if (timeout) {
       clearTimeout(timeout);
     }
   });
 
   observing = true;
-  Services.obs.addObserver(observer, topic, false);
-};
+  mm.addMessageListener(aTopic, observer);
+}
+
+function waitForTopic(aTopic, aTimeout, aCallback) {
+  let observing = false;
+  function removeObserver() {
+    if (!observing)
+      return;
+    Services.obs.removeObserver(observer, aTopic);
+    observing = false;
+  }
+
+  let timeout = setTimeout(function () {
+    removeObserver();
+    aCallback(false);
+  }, aTimeout);
+
+  function observer(aSubject, aTopic, aData) {
+    removeObserver();
+    timeout = clearTimeout(timeout);
+    executeSoon(() => aCallback(true));
+  }
+
+  registerCleanupFunction(function() {
+    removeObserver();
+    if (timeout) {
+      clearTimeout(timeout);
+    }
+  });
+
+  observing = true;
+  Services.obs.addObserver(observer, aTopic, false);
+}
+
+/**
+ * Wait until session restore has finished collecting its data and is
+ * getting ready to write that data ("sessionstore-state-write").
+ *
+ * This function is meant to be called immediately after the code
+ * that will trigger the saving.
+ *
+ * Note that this does not wait for the disk write to be complete.
+ *
+ * @param {function} aCallback If sessionstore-state-write is sent
+ * within buffering interval + 100 ms, the callback is passed |true|,
+ * otherwise, it is passed |false|.
+ */
+function waitForSaveState(aCallback) {
+  let timeout = 100 +
+    Services.prefs.getIntPref("browser.sessionstore.interval");
+  return waitForTopic("sessionstore-state-write", timeout, aCallback);
+}
 
 function whenBrowserLoaded(aBrowser, aCallback = next) {
   aBrowser.addEventListener("load", function onLoad() {
     aBrowser.removeEventListener("load", onLoad, true);
     executeSoon(aCallback);
   }, true);
 }