Bug 662812 - Panorama isn't aware of the current SSWindowState when being initialized; r=zpao
authorTim Taubert <tim.taubert@gmx.de>
Tue, 16 Aug 2011 11:06:14 +0200
changeset 75623 c687cfaa9a68a3be8246df8abd13fd1c45b3cd7c
parent 75622 399aba688a193fe6523f672a1ea7ed04d1963956
child 75624 8579b0386cee3b7fc821a5d73f7020537382828c
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
reviewerszpao
bugs662812
milestone9.0a1
Bug 662812 - Panorama isn't aware of the current SSWindowState when being initialized; r=zpao
browser/base/content/tabview/storage.js
browser/base/content/tabview/ui.js
browser/base/content/test/tabview/browser_tabview_bug597248.js
browser/base/content/test/tabview/browser_tabview_privatebrowsing.js
browser/base/content/test/tabview/head.js
browser/components/sessionstore/src/nsSessionStore.js
browser/components/sessionstore/test/browser/Makefile.in
browser/components/sessionstore/test/browser/browser_595601-restore_hidden.js
browser/components/sessionstore/test/browser/browser_662812.js
--- a/browser/base/content/tabview/storage.js
+++ b/browser/base/content/tabview/storage.js
@@ -177,16 +177,33 @@ let Storage = {
     } catch (e) {
       // getWindowValue will fail if the property doesn't exist
       Utils.log("Error in readGroupItemData: "+e, data);
     }
     return existingData;
   },
 
   // ----------
+  // Function: readWindowBusyState
+  // Returns the current busyState for the given window.
+  readWindowBusyState: function Storage_readWindowBusyState(win) {
+    let state;
+
+    try {
+      let data = this._sessionStore.getWindowState(win);
+      if (data)
+        state = JSON.parse(data);
+    } catch (e) {
+      Utils.log("Error while parsing window state");
+    }
+
+    return (state && state.windows[0].busy);
+  },
+
+  // ----------
   // Function: saveGroupItemsData
   // Saves the global data for the <GroupItems> singleton for the given window.
   saveGroupItemsData: function Storage_saveGroupItemsData(win, data) {
     this.saveData(win, this.GROUPS_DATA_IDENTIFIER, data);
   },
 
   // ----------
   // Function: readGroupItemsData
--- a/browser/base/content/tabview/ui.js
+++ b/browser/base/content/tabview/ui.js
@@ -118,19 +118,19 @@ let UI = {
   // Keeps track of info related to private browsing, including: 
   //   transitionMode - whether we're entering or exiting PB
   //   wasInTabView - whether TabView was visible before we went into PB
   _privateBrowsing: {
     transitionMode: "",
     wasInTabView: false 
   },
   
-  // Variable: _storageBusyCount
-  // Used to keep track of how many calls to storageBusy vs storageReady.
-  _storageBusyCount: 0,
+  // Variable: _storageBusy
+  // Tells whether the storage is currently busy or not.
+  _storageBusy: false,
 
   // Variable: isDOMWindowClosing
   // Tells wether the parent window is about to close
   isDOMWindowClosing: false,
 
   // Variable: _browserKeys
   // Used to keep track of allowed browser keys.
   _browserKeys: null,
@@ -164,16 +164,20 @@ let UI = {
       // initialize the direction of the page
       this._initPageDirection();
 
       // ___ thumbnail storage
       ThumbnailStorage.init();
 
       // ___ storage
       Storage.init();
+
+      if (Storage.readWindowBusyState(gWindow))
+        this.storageBusy();
+
       let data = Storage.readUIData(gWindow);
       this._storageSanity(data);
       this._pageBounds = data.pageBounds;
 
       // ___ currentTab
       this._currentTab = gBrowser.selectedTab;
 
       // ___ exit button
@@ -607,39 +611,42 @@ let UI = {
   },
 #endif
 
   // ----------
   // Function: storageBusy
   // Pauses the storage activity that conflicts with sessionstore updates and 
   // private browsing mode switches. Calls can be nested. 
   storageBusy: function UI_storageBusy() {
-    if (!this._storageBusyCount) {
-      TabItems.pauseReconnecting();
-      GroupItems.pauseAutoclose();
-    }
-    
-    this._storageBusyCount++;
+    if (this._storageBusy)
+      return;
+
+    this._storageBusy = true;
+
+    TabItems.pauseReconnecting();
+    GroupItems.pauseAutoclose();
   },
   
   // ----------
   // Function: storageReady
   // Resumes the activity paused by storageBusy, and updates for any new group
   // information in sessionstore. Calls can be nested. 
   storageReady: function UI_storageReady() {
-    this._storageBusyCount--;
-    if (!this._storageBusyCount) {
-      let hasGroupItemsData = GroupItems.load();
-      if (!hasGroupItemsData)
-        this.reset();
-  
-      TabItems.resumeReconnecting();
-      GroupItems._updateTabBar();
-      GroupItems.resumeAutoclose();
-    }
+    if (!this._storageBusy)
+      return;
+
+    this._storageBusy = false;
+
+    let hasGroupItemsData = GroupItems.load();
+    if (!hasGroupItemsData)
+      this.reset();
+
+    TabItems.resumeReconnecting();
+    GroupItems._updateTabBar();
+    GroupItems.resumeAutoclose();
   },
 
   // ----------
   // Function: _addTabActionHandlers
   // Adds handlers to handle tab actions.
   _addTabActionHandlers: function UI__addTabActionHandlers() {
     var self = this;
 
@@ -723,17 +730,17 @@ let UI = {
         
       if (self.isTabViewVisible()) {
         // just closed the selected tab in the TabView interface.
         if (self._currentTab == tab)
           self._closedSelectedTabInTabView = true;
       } else {
         // If we're currently in the process of entering private browsing,
         // we don't want to go to the Tab View UI. 
-        if (self._storageBusyCount)
+        if (self._storageBusy)
           return;
 
         // if not closing the last tab
         if (gBrowser.tabs.length > 1) {
           // Don't return to TabView if there are any app tabs
           for (let a = 0; a < gBrowser._numPinnedTabs; a++) {
             if (!gBrowser.tabs[a].closing)
               return;
--- a/browser/base/content/test/tabview/browser_tabview_bug597248.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug597248.js
@@ -32,36 +32,30 @@ function setupTwo(win) {
 
   let numTabsToSave = tabItems.length;
 
   // force all canvases to update, and hook in imageData save detection
   tabItems.forEach(function(tabItem) {
     contentWindow.TabItems.update(tabItem.tab);
     tabItem.addSubscriber("savedCachedImageData", function onSaved(item) {
       item.removeSubscriber("savedCachedImageData", onSaved);
-      --numTabsToSave;
+
+      if (!--numTabsToSave)
+        restoreWindow();
     });
   });
 
   // after the window is closed, restore it.
-  let xulWindowDestory = function() {
-    Services.obs.removeObserver(
-       xulWindowDestory, "xul-window-destroyed", false);
-
-    // "xul-window-destroyed" is just fired just before a XUL window is
-    // destroyed so restore window and test it after a delay
+  let restoreWindow = function() {
     executeSoon(function() {
       restoredWin = undoCloseWindow();
       restoredWin.addEventListener("load", function onLoad(event) {
         restoredWin.removeEventListener("load", onLoad, false);
 
         registerCleanupFunction(function() restoredWin.close());
-
-        // ensure that closed tabs have been saved
-        is(numTabsToSave, 0, "All tabs were saved when window was closed.");
         is(restoredWin.gBrowser.tabs.length, 3, "The total number of tabs is 3");
 
         // setup tab variables and listen to the tabs load progress
         newTabOne = restoredWin.gBrowser.tabs[0];
         newTabTwo = restoredWin.gBrowser.tabs[1];
         newTabThree = restoredWin.gBrowser.tabs[2];
         restoredWin.gBrowser.addTabsProgressListener(gTabsProgressListener);
 
@@ -98,17 +92,16 @@ function setupTwo(win) {
           });
         }
 
         restoredWin.addEventListener(
           "tabviewframeinitialized", onTabViewFrameInitialized, false);
       }, false);
     });
   };
-  Services.obs.addObserver(xulWindowDestory, "xul-window-destroyed", false);
 
   win.close();
 }
 
 let gTabsProgressListener = {
   onStateChange: function(browser, webProgress, request, stateFlags, status) {
     // ensure about:blank doesn't trigger the code
     if ((stateFlags & Ci.nsIWebProgressListener.STATE_STOP) &&
--- a/browser/base/content/test/tabview/browser_tabview_privatebrowsing.js
+++ b/browser/base/content/test/tabview/browser_tabview_privatebrowsing.js
@@ -44,21 +44,25 @@ function onTabViewLoadedAndShown() {
 
   // collect the group titles
   let count = contentWindow.GroupItems.groupItems.length;
   for (let a = 0; a < count; a++) {
     let gi = contentWindow.GroupItems.groupItems[a];
     groupTitles[a] = gi.getTitle();
   }
 
+  contentWindow.gPrefBranch.setBoolPref("animate_zoom", false);
+
   // Create a second tab
   gBrowser.addTab("about:robots");
   is(gBrowser.tabs.length, 2, "we now have 2 tabs");
+
   registerCleanupFunction(function() {
     gBrowser.removeTab(gBrowser.tabs[1]);
+    contentWindow.gPrefBranch.clearUserPref("animate_zoom");
   });
 
   afterAllTabsLoaded(function() {
     // Get normal tab urls
     for (let a = 0; a < gBrowser.tabs.length; a++)
       normalURLs.push(gBrowser.tabs[a].linkedBrowser.currentURI.spec);
 
     // verify that we're all set up for our test
--- a/browser/base/content/test/tabview/head.js
+++ b/browser/base/content/test/tabview/head.js
@@ -356,16 +356,19 @@ function restoreTab(callback, index, win
 }
 
 // ----------
 function togglePrivateBrowsing(callback) {
   let topic = "private-browsing-transition-complete";
 
   Services.obs.addObserver(function observe() {
     Services.obs.removeObserver(observe, topic);
-    afterAllTabsLoaded(callback);
+
+    // use executeSoon() to let Panorama load its group data from the session
+    // before we call afterAllTabsLoaded()
+    executeSoon(function () afterAllTabsLoaded(callback));
   }, topic, false);
 
   let pb = Cc["@mozilla.org/privatebrowsing;1"].
            getService(Ci.nsIPrivateBrowsingService);
 
   pb.privateBrowsingEnabled = !pb.privateBrowsingEnabled;
 }
--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -780,17 +780,17 @@ SessionStoreService.prototype = {
     if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser" ||
         this._loadState == STATE_QUITTING)
       return;
 
     // assign it a unique identifier (timestamp)
     aWindow.__SSi = "window" + Date.now();
 
     // and create its data object
-    this._windows[aWindow.__SSi] = { tabs: [], selected: 0, _closedTabs: [] };
+    this._windows[aWindow.__SSi] = { tabs: [], selected: 0, _closedTabs: [], busy: false };
     if (!this._isWindowLoaded(aWindow))
       this._windows[aWindow.__SSi]._restoring = true;
     if (!aWindow.toolbar.visible)
       this._windows[aWindow.__SSi].isPopup = true;
     
     // perform additional initialization when the first window is loading
     if (this._loadState == STATE_STOPPED) {
       this._loadState = STATE_RUNNING;
@@ -964,16 +964,19 @@ SessionStoreService.prototype = {
       // Until we decide otherwise elsewhere, this window is part of a series
       // of closing windows to quit.
       winData._shouldRestore = true;
 #endif
 
       // save the window if it has multiple tabs or a single saveable tab
       if (winData.tabs.length > 1 ||
           (winData.tabs.length == 1 && this._shouldSaveTabState(winData.tabs[0]))) {
+        // we don't want to save the busy state
+        delete winData.busy;
+
         this._closedWindows.unshift(winData);
         this._capClosedWindows();
       }
       
       // clear this window from the list
       delete this._windows[aWindow.__SSi];
       
       // save the state without this window to disk
@@ -1260,33 +1263,33 @@ SessionStoreService.prototype = {
   },
 
   setTabState: function sss_setTabState(aTab, aState) {
     var tabState = JSON.parse(aState);
     if (!tabState.entries || !aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi)
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
     
     var window = aTab.ownerDocument.defaultView;
-    this._sendWindowStateEvent(window, "Busy");
+    this._setWindowStateBusy(window);
     this.restoreHistoryPrecursor(window, [aTab], [tabState], 0, 0, 0);
   },
 
   duplicateTab: function sss_duplicateTab(aWindow, aTab, aDelta) {
     if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi ||
         !aWindow.getBrowser)
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
 
     var tabState = this._collectTabData(aTab, true);
     var sourceWindow = aTab.ownerDocument.defaultView;
     this._updateTextAndScrollDataForTab(sourceWindow, aTab.linkedBrowser, tabState, true);
     tabState.index += aDelta;
     tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
     tabState.pinned = false;
 
-    this._sendWindowStateEvent(aWindow, "Busy");
+    this._setWindowStateBusy(aWindow);
     let newTab = aTab == aWindow.gBrowser.selectedTab ?
       aWindow.gBrowser.addTab(null, {relatedToCurrent: true, ownerTab: aTab}) :
       aWindow.gBrowser.addTab();
     this.restoreHistoryPrecursor(aWindow, [newTab], [tabState], 0, 0, 0);
 
     return newTab;
   },
 
@@ -1319,17 +1322,17 @@ SessionStoreService.prototype = {
     aIndex = aIndex || 0;
     if (!(aIndex in closedTabs))
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
     
     // fetch the data of closed tab, while removing it from the array
     let closedTab = closedTabs.splice(aIndex, 1).shift();
     let closedTabState = closedTab.state;
 
-    this._sendWindowStateEvent(aWindow, "Busy");
+    this._setWindowStateBusy(aWindow);
     // create a new tab
     let browser = aWindow.gBrowser;
     let tab = browser.addTab();
 
     // restore tab content
     this.restoreHistoryPrecursor(aWindow, [tab], [closedTabState], 1, 0, 0);
       
     // restore the tab's position
@@ -2529,17 +2532,17 @@ SessionStoreService.prototype = {
     catch (ex) { // invalid state object - don't restore anything 
       debug(ex);
       this._sendRestoreCompletedNotifications();
       return;
     }
 
     // We're not returning from this before we end up calling restoreHistoryPrecursor
     // for this window, so make sure we send the SSWindowStateBusy event.
-    this._sendWindowStateEvent(aWindow, "Busy");
+    this._setWindowStateBusy(aWindow);
 
     if (root._closedWindows)
       this._closedWindows = root._closedWindows;
 
     var winData;
     if (!aState.selectedWindow) {
       aState.selectedWindow = 0;
     }
@@ -2716,17 +2719,17 @@ SessionStoreService.prototype = {
       delete this._statesToRestore[aWindow.__SS_restoreID];
       delete aWindow.__SS_restoreID;
       delete this._windows[aWindow.__SSi]._restoring;
     }
 
     if (aTabs.length == 0) {
       // this is normally done in restoreHistory() but as we're returning early
       // here we need to take care of it.
-      this._sendWindowStateEvent(aWindow, "Ready");
+      this._setWindowStateReady(aWindow);
       return;
     }
 
     let unhiddenTabs = aTabData.filter(function (aData) !aData.hidden).length;
 
     // if all tabs to be restored are hidden, make the first one visible
     if (unhiddenTabs == 0) {
       aTabData[0].hidden = false;
@@ -2861,17 +2864,17 @@ SessionStoreService.prototype = {
     var _this = this;
     while (aTabs.length > 0 && (!aTabData[0]._tabStillLoading || !aTabs[0].parentNode)) {
       aTabs.shift(); // this tab got removed before being completely restored
       aTabData.shift();
     }
     if (aTabs.length == 0) {
       // At this point we're essentially ready for consumers to read/write data
       // via the sessionstore API so we'll send the SSWindowStateReady event.
-      this._sendWindowStateEvent(aWindow, "Ready");
+      this._setWindowStateReady(aWindow);
       return; // no more tabs to restore
     }
     
     var tab = aTabs.shift();
     var tabData = aTabData.shift();
 
     var browser = aWindow.gBrowser.getBrowserForTab(tab);
     var history = browser.webNavigation.sessionHistory;
@@ -4006,16 +4009,52 @@ SessionStoreService.prototype = {
     Services.obs.notifyObservers(null,
       this._browserSetState ? NOTIFY_BROWSER_STATE_RESTORED : NOTIFY_WINDOWS_RESTORED,
       "");
 
     this._browserSetState = false;
     this._restoreCount = -1;
   },
 
+   /**
+   * Set the given window's busy state
+   * @param aWindow the window
+   * @param aValue the window's busy state
+   */
+  _setWindowStateBusyValue:
+    function sss__changeWindowStateBusyValue(aWindow, aValue) {
+
+    this._windows[aWindow.__SSi].busy = aValue;
+
+    // Keep the to-be-restored state in sync because that is returned by
+    // getWindowState() as long as the window isn't loaded, yet.
+    if (!this._isWindowLoaded(aWindow)) {
+      let stateToRestore = this._statesToRestore[aWindow.__SS_restoreID].windows[0];
+      stateToRestore.busy = aValue;
+    }
+  },
+
+  /**
+   * Set the given window's state to 'not busy'.
+   * @param aWindow the window
+   */
+  _setWindowStateReady: function sss__setWindowStateReady(aWindow) {
+    this._setWindowStateBusyValue(aWindow, false);
+    this._sendWindowStateEvent(aWindow, "Ready");
+  },
+
+  /**
+   * Set the given window's state to 'busy'.
+   * @param aWindow the window
+   */
+  _setWindowStateBusy: function sss__setWindowStateBusy(aWindow) {
+    this._setWindowStateBusyValue(aWindow, true);
+    this._sendWindowStateEvent(aWindow, "Busy");
+  },
+
   /**
    * Dispatch an SSWindowState_____ event for the given window.
    * @param aWindow the window
    * @param aType the type of event, SSWindowState will be prepended to this string
    */
   _sendWindowStateEvent: function sss__sendWindowStateEvent(aWindow, aType) {
     let event = aWindow.document.createEvent("Events");
     event.initEvent("SSWindowState" + aType, true, false);
--- a/browser/components/sessionstore/test/browser/Makefile.in
+++ b/browser/components/sessionstore/test/browser/Makefile.in
@@ -146,16 +146,17 @@ include $(topsrcdir)/config/rules.mk
 	browser_623779.js \
 	browser_624727.js \
 	browser_625257.js \
 	browser_628270.js \
 	browser_635418.js \
 	browser_636279.js \
 	browser_645428.js \
 	browser_659591.js \
+	browser_662812.js \
 	$(NULL)
 
 ifneq ($(OS_ARCH),Darwin)
 _BROWSER_TEST_FILES += \
 	browser_597071.js \
 	browser_625016.js \
 	$(NULL)
 endif
--- a/browser/components/sessionstore/test/browser/browser_595601-restore_hidden.js
+++ b/browser/components/sessionstore/test/browser/browser_595601-restore_hidden.js
@@ -1,113 +1,117 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TAB_STATE_NEEDS_RESTORE = 1;
 const TAB_STATE_RESTORING = 2;
 
-let stateBackup = ss.getBrowserState();
-
 let state = {windows:[{tabs:[
   {entries:[{url:"http://example.com#1"}]},
   {entries:[{url:"http://example.com#2"}]},
   {entries:[{url:"http://example.com#3"}]},
   {entries:[{url:"http://example.com#4"}]},
   {entries:[{url:"http://example.com#5"}], hidden: true},
   {entries:[{url:"http://example.com#6"}], hidden: true},
   {entries:[{url:"http://example.com#7"}], hidden: true},
   {entries:[{url:"http://example.com#8"}], hidden: true}
 ]}]};
 
 function test() {
   waitForExplicitFinish();
 
   registerCleanupFunction(function () {
     Services.prefs.clearUserPref("browser.sessionstore.restore_hidden_tabs");
-
-    TabsProgressListener.uninit();
-
-    ss.setBrowserState(stateBackup);
   });
 
-  TabsProgressListener.init();
-
   // First stage: restoreHiddenTabs = true
   // Second stage: restoreHiddenTabs = false
   test_loadTabs(true, function () {
-    test_loadTabs(false, function () {
-      waitForFocus(finish);
-    });
+    test_loadTabs(false, finish);
   });
 }
 
 function test_loadTabs(restoreHiddenTabs, callback) {
   Services.prefs.setBoolPref("browser.sessionstore.restore_hidden_tabs", restoreHiddenTabs);
 
   let expectedTabs = restoreHiddenTabs ? 8 : 4;
-
   let firstProgress = true;
 
-  TabsProgressListener.setCallback(function (needsRestore, isRestoring) {
+  newWindowWithState(state, function (win, needsRestore, isRestoring) {
     if (firstProgress) {
       firstProgress = false;
       is(isRestoring, 3, "restoring 3 tabs concurrently");
     } else {
       ok(isRestoring < 4, "restoring max. 3 tabs concurrently");
     }
 
-    if (gBrowser.tabs.length - needsRestore == expectedTabs) {
-      TabsProgressListener.unsetCallback();
-      is(gBrowser.visibleTabs.length, 4, "only 4 visible tabs");
+    if (win.gBrowser.tabs.length - needsRestore == expectedTabs) {
+      is(win.gBrowser.visibleTabs.length, 4, "only 4 visible tabs");
+
+      TabsProgressListener.uninit();
       callback();
     }
   });
-
-  ss.setBrowserState(JSON.stringify(state));
-}
-
-function countTabs() {
-  let needsRestore = 0, isRestoring = 0;
-  let windowsEnum = Services.wm.getEnumerator("navigator:browser");
-
-  while (windowsEnum.hasMoreElements()) {
-    let window = windowsEnum.getNext();
-    if (window.closed)
-      continue;
-
-    for (let i = 0; i < window.gBrowser.tabs.length; i++) {
-      let browser = window.gBrowser.tabs[i].linkedBrowser;
-      if (browser.__SS_restoreState == TAB_STATE_RESTORING)
-        isRestoring++;
-      else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
-        needsRestore++;
-    }
-  }
-
-  return [needsRestore, isRestoring];
 }
 
 let TabsProgressListener = {
-  init: function () {
-    gBrowser.addTabsProgressListener(this);
+  init: function (win) {
+    this.window = win;
+
+    this.window.gBrowser.addTabsProgressListener(this);
   },
 
   uninit: function () {
-    this.unsetCallback();
-    gBrowser.removeTabsProgressListener(this);
+    this.window.gBrowser.removeTabsProgressListener(this);
+
+    delete this.window;
+    delete this.callback;
   },
 
   setCallback: function (callback) {
     this.callback = callback;
   },
 
-  unsetCallback: function () {
-    delete this.callback;
-  },
-
   onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
     if (this.callback && aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
         aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
         aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
         aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
-      this.callback.apply(null, countTabs());
+      this.callback.apply(null, [this.window].concat(this.countTabs()));
+  },
+
+  countTabs: function () {
+    let needsRestore = 0, isRestoring = 0;
+
+    for (let i = 0; i < this.window.gBrowser.tabs.length; i++) {
+      let browser = this.window.gBrowser.tabs[i].linkedBrowser;
+      if (browser.__SS_restoreState == TAB_STATE_RESTORING)
+        isRestoring++;
+      else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
+        needsRestore++;
+    }
+
+    return [needsRestore, isRestoring];
   }
 }
+
+// ----------
+function whenWindowLoaded(win, callback) {
+  win.addEventListener("load", function onLoad() {
+    win.removeEventListener("load", onLoad, false);
+    executeSoon(callback);
+  }, false);
+}
+
+// ----------
+function newWindowWithState(state, callback) {
+  let opts = "chrome,all,dialog=no,height=800,width=800";
+  let win = window.openDialog(getBrowserURL(), "_blank", opts);
+
+  registerCleanupFunction(function () win.close());
+
+  whenWindowLoaded(win, function () {
+    TabsProgressListener.init(win);
+    TabsProgressListener.setCallback(callback);
+
+    ss.setWindowState(win, JSON.stringify(state), true);
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser/browser_662812.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+  waitForExplicitFinish();
+
+  window.addEventListener("SSWindowStateBusy", function onBusy() {
+    window.removeEventListener("SSWindowStateBusy", onBusy, false);
+
+    let state = JSON.parse(ss.getWindowState(window));
+    ok(state.windows[0].busy, "window is busy");
+
+    window.addEventListener("SSWindowStateReady", function onReady() {
+      window.removeEventListener("SSWindowStateReady", onReady, false);
+
+      let state = JSON.parse(ss.getWindowState(window));
+      ok(!state.windows[0].busy, "window is not busy");
+
+      gBrowser.removeTab(gBrowser.tabs[1]);
+      executeSoon(finish);
+    }, false);
+  }, false);
+
+  // create a new tab
+  let tab = gBrowser.addTab("about:mozilla");
+  let browser = tab.linkedBrowser;
+
+  // close and restore it
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    gBrowser.removeTab(tab);
+    ss.undoCloseTab(window, 0);
+  }, true);
+}