Bug 1034036 - Part 5: restore windows in reverse z-order, so that the last focused window is restored first and stays in front. r?dao draft
authorMike de Boer <mdeboer@mozilla.com>
Fri, 02 Mar 2018 13:35:34 +0100
changeset 762478 d585926a92e14514c8191d92f1193bb45027b3a6
parent 762477 1a4427ae35b82ddcaea7cae88ce4fcd4af02c488
child 762479 bfe2aa1efd1dafd4e76a1607fb7deae610ec35fe
push id101173
push usermdeboer@mozilla.com
push dateFri, 02 Mar 2018 13:07:51 +0000
reviewersdao
bugs1034036
milestone60.0a1
Bug 1034036 - Part 5: restore windows in reverse z-order, so that the last focused window is restored first and stays in front. r?dao MozReview-Commit-ID: G7dYFzqKIGm
browser/components/sessionstore/SessionStore.jsm
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -11,16 +11,17 @@ const FORMAT_VERSION = 1;
 
 const TAB_STATE_NEEDS_RESTORE = 1;
 const TAB_STATE_RESTORING = 2;
 const TAB_STATE_WILL_RESTORE = 3;
 const TAB_STATES = new WeakMap();
 const TAB_LAZY_STATES = new WeakMap();
 const BROWSER_STATES = new WeakMap();
 const WINDOW_RESTORE_IDS = new WeakMap();
+const WINDOW_RESTORE_ZINDICES = new WeakMap();
 
 // A new window has just been restored. At this stage, tabs are generally
 // not restored.
 const NOTIFY_SINGLE_WINDOW_RESTORED = "sessionstore-single-window-restored";
 const NOTIFY_WINDOWS_RESTORED = "sessionstore-windows-restored";
 const NOTIFY_BROWSER_STATE_RESTORED = "sessionstore-browser-state-restored";
 const NOTIFY_LAST_SESSION_CLEARED = "sessionstore-last-session-cleared";
 const NOTIFY_RESTORING_ON_STARTUP = "sessionstore-restoring-on-startup";
@@ -203,16 +204,25 @@ function debug(aMsg) {
 /**
  * A global value to tell that fingerprinting resistance is enabled or not.
  * If it's enabled, the session restore won't restore the window's size and
  * size mode.
  * This value is controlled by preference privacy.resistFingerprinting.
  */
 var gResistFingerprintingEnabled = false;
 
+/**
+ * Return a promise that will be resolved once it receives the 'SSBrowserWindowShowing'
+ * event, which is dispatched in `onBeforeBrowserWindowShown`.
+ */
+function promiseWindowShowing(window) {
+  return new Promise(resolve =>
+    window.addEventListener("SSBrowserWindowShowing", () => resolve(window), {once: true}));
+}
+
 var SessionStore = {
   get promiseInitialized() {
     return SessionStoreInternal.promiseInitialized;
   },
 
   get promiseAllWindowsRestored() {
     return SessionStoreInternal.promiseAllWindowsRestored;
   },
@@ -691,18 +701,19 @@ var SessionStoreInternal = {
 
           // Update the session start time using the restored session state.
           this._updateSessionStartTime(state);
 
           // make sure that at least the first window doesn't have anything hidden
           delete state.windows[0].hidden;
           // Since nothing is hidden in the first window, it cannot be a popup
           delete state.windows[0].isPopup;
-          // We don't want to minimize and then open a window at startup.
-          if (state.windows[0].sizemode == "minimized")
+          // We don't want to minimize and then open a window at startup. However,
+          // when we're restoring, we should preserve previous windows sizemode.
+          if (state.windows[0].sizemode == "minimized" && !ss.doRestore())
             state.windows[0].sizemode = "normal";
           // clear any lastSessionWindowID attributes since those don't matter
           // during normal restore
           state.windows.forEach(function(aWindow) {
             delete aWindow.__lastSessionWindowID;
           });
         }
       } catch (ex) { debug("The session file is invalid: " + ex); }
@@ -1188,19 +1199,19 @@ var SessionStoreInternal = {
       } else {
         // Nothing to restore, notify observers things are complete.
         Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED);
         Services.obs.notifyObservers(null, "sessionstore-one-or-no-tab-restored");
         this._deferredAllWindowsRestored.resolve();
       }
     // this window was opened by _openWindowWithState
     } else if (!this._isWindowLoaded(aWindow)) {
-      let state = this._statesToRestore[WINDOW_RESTORE_IDS.get(aWindow)];
-      let options = {overwriteTabs: true, isFollowUp: state.windows.length == 1};
-      this.restoreWindow(aWindow, state.windows[0], options);
+      // We used to restore window when it is opened. However, we want to restore
+      // windows after all windows have opened (bug 1034036). Thus, the restoration
+      // process has been moved to `restoreWindowsFeaturesAndTabs`.
     // The user opened another, non-private window after starting up with
     // a single private one. Let's restore the session we actually wanted to
     // restore at startup.
     } else if (this._deferredInitialState && !isPrivateWindow &&
              aWindow.toolbar.visible) {
 
       // global data must be restored before restoreWindow is called so that
       // it happens before observers are notified
@@ -1282,16 +1293,20 @@ var SessionStoreInternal = {
    * Called right before a new browser window is shown.
    * @param aWindow
    *        Window reference
    */
   onBeforeBrowserWindowShown(aWindow) {
     // Register the window.
     this.onLoad(aWindow);
 
+    // Dispatch a custom event to tell that a new window is about to be shown.
+    let evt = new aWindow.CustomEvent("SSBrowserWindowShowing");
+    aWindow.dispatchEvent(evt);
+
     // Just call initializeWindow() directly if we're initialized already.
     if (this._sessionInitialized) {
       this.initializeWindow(aWindow);
       return;
     }
 
     // The very first window that is opened creates a promise that is then
     // re-used by all subsequent windows. The promise will be used to tell
@@ -1357,17 +1372,17 @@ var SessionStoreInternal = {
     // this window was about to be restored - conserve its original data, if any
     let isFullyLoaded = this._isWindowLoaded(aWindow);
     if (!isFullyLoaded) {
       if (!aWindow.__SSi) {
         aWindow.__SSi = this._generateWindowID();
       }
 
       let restoreID = WINDOW_RESTORE_IDS.get(aWindow);
-      this._windows[aWindow.__SSi] = this._statesToRestore[restoreID];
+      this._windows[aWindow.__SSi] = this._statesToRestore[restoreID].windows[0];
       delete this._statesToRestore[restoreID];
       WINDOW_RESTORE_IDS.delete(aWindow);
     }
 
     // ignore windows not tracked by SessionStore
     if (!aWindow.__SSi || !this._windows[aWindow.__SSi]) {
       return completionPromise;
     }
@@ -1584,19 +1599,20 @@ var SessionStoreInternal = {
     }
   },
 
   /**
    * On quit application granted
    */
   onQuitApplicationGranted: function ssi_onQuitApplicationGranted(syncShutdown = false) {
     // Collect an initial snapshot of window data before we do the flush
-    this._forEachBrowserWindow((win) => {
-      this._collectWindowData(win);
-    });
+    for (let window of this._browserWindows) {
+      this._collectWindowData(window);
+      this._windows[window.__SSi].zIndex = BrowserWindowTracker.getZIndex(window);
+    }
 
     // Now add an AsyncShutdown blocker that'll spin the event loop
     // until the windows have all been flushed.
 
     // This progress object will track the state of async window flushing
     // and will help us debug things that go wrong with our AsyncShutdown
     // blocker.
     let progress = { total: -1, current: -1 };
@@ -1671,29 +1687,29 @@ var SessionStoreInternal = {
    *
    * @return Promise
    */
   async flushAllWindowsAsync(progress = {}) {
     let windowPromises = new Map();
     // We collect flush promises and close each window immediately so that
     // the user can't start changing any window state while we're waiting
     // for the flushes to finish.
-    this._forEachBrowserWindow((win) => {
-      windowPromises.set(win, TabStateFlusher.flushWindow(win));
+    for (let window of this._browserWindows) {
+      windowPromises.set(window, TabStateFlusher.flushWindow(window));
 
       // We have to wait for these messages to come up from
       // each window and each browser. In the meantime, hide
       // the windows to improve perceived shutdown speed.
-      let baseWin = win.QueryInterface(Ci.nsIInterfaceRequestor)
-                       .getInterface(Ci.nsIDocShell)
-                       .QueryInterface(Ci.nsIDocShellTreeItem)
-                       .treeOwner
-                       .QueryInterface(Ci.nsIBaseWindow);
+      let baseWin = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDocShell)
+                          .QueryInterface(Ci.nsIDocShellTreeItem)
+                          .treeOwner
+                          .QueryInterface(Ci.nsIBaseWindow);
       baseWin.visibility = false;
-    });
+    }
 
     progress.total = windowPromises.size;
     progress.current = 0;
 
     // We'll iterate through the Promise array, yielding each one, so as to
     // provide useful progress information to AsyncShutdown.
     for (let [win, promise] of windowPromises) {
       await promise;
@@ -1757,17 +1773,19 @@ var SessionStoreInternal = {
     // session data on disk as this notification fires after the
     // quit-application notification so the browser is about to exit.
     if (RunState.isQuitting)
       return;
     LastSession.clear();
 
     let openWindows = {};
     // Collect open windows.
-    this._forEachBrowserWindow(({__SSi: id}) => openWindows[id] = true);
+    for (let window of this._browserWindows) {
+      openWindows[window.__SSi] = true;
+    }
 
     // also clear all data about closed tabs and windows
     for (let ix in this._windows) {
       if (ix in openWindows) {
         if (this._windows[ix]._closedTabs.length) {
           this._windows[ix]._closedTabs = [];
           this._closedObjectsChanged = true;
         }
@@ -2281,22 +2299,22 @@ var SessionStoreInternal = {
     var window = this._getTopWindow();
     if (!window) {
       this._restoreCount = 1;
       this._openWindowWithState(state);
       return;
     }
 
     // close all other browser windows
-    this._forEachBrowserWindow(function(aWindow) {
-      if (aWindow != window) {
-        aWindow.close();
-        this.onClose(aWindow);
+    for (let otherWin of this._browserWindows) {
+      if (otherWin != window) {
+        otherWin.close();
+        this.onClose(otherWin);
       }
-    });
+    }
 
     // make sure closed window data isn't kept
     if (this._closedWindows.length) {
       this._closedWindows = [];
       this._closedObjectsChanged = true;
     }
 
     // determine how many windows are meant to be restored
@@ -2539,16 +2557,17 @@ var SessionStoreInternal = {
     }
 
     // reopen the window
     let state = { windows: this._removeClosedWindow(aIndex) };
     delete state.windows[0].closedAt; // Window is now open.
 
     let window = this._openWindowWithState(state);
     this.windowToFocus = window;
+    promiseWindowShowing(window).then(win => this.restoreWindows(win, state, {overwriteTabs: true}));
 
     // Notify of changes to closed objects.
     this._notifyOfClosedObjectsChange();
 
     return window;
   },
 
   forgetClosedWindow: function ssi_forgetClosedWindow(aIndex) {
@@ -2788,20 +2807,20 @@ var SessionStoreInternal = {
     if (!this.canRestoreLastSession) {
       throw Components.Exception("Last session can not be restored");
     }
 
     Services.obs.notifyObservers(null, NOTIFY_INITIATING_MANUAL_RESTORE);
 
     // First collect each window with its id...
     let windows = {};
-    this._forEachBrowserWindow(function(aWindow) {
-      if (aWindow.__SS_lastSessionWindowID)
-        windows[aWindow.__SS_lastSessionWindowID] = aWindow;
-    });
+    for (let window of this._browserWindows) {
+      if (window.__SS_lastSessionWindowID)
+        windows[window.__SS_lastSessionWindowID] = window;
+    }
 
     let lastSessionState = LastSession.getState();
 
     // This shouldn't ever be the case...
     if (!lastSessionState.windows.length) {
       throw Components.Exception("lastSessionState has no windows", Cr.NS_ERROR_UNEXPECTED);
     }
 
@@ -2817,16 +2836,19 @@ var SessionStoreInternal = {
     let lastWindow = this._getTopWindow();
     let canUseLastWindow = lastWindow &&
                            !lastWindow.__SS_lastSessionWindowID;
 
     // global data must be restored before restoreWindow is called so that
     // it happens before observers are notified
     this._globalState.setFromState(lastSessionState);
 
+    let openWindows = [];
+    let windowsToOpen = [];
+
     // Restore session cookies.
     SessionCookies.restore(lastSessionState.cookies || []);
 
     // Restore into windows or open new ones as needed.
     for (let i = 0; i < lastSessionState.windows.length; i++) {
       let winState = lastSessionState.windows[i];
       let lastSessionWindowID = winState.__lastSessionWindowID;
       // delete lastSessionWindowID so we don't add that to the window again
@@ -2848,27 +2870,34 @@ var SessionStoreInternal = {
         // Since we're not overwriting existing tabs, we want to merge _closedTabs,
         // putting existing ones first. Then make sure we're respecting the max pref.
         if (winState._closedTabs && winState._closedTabs.length) {
           let curWinState = this._windows[windowToUse.__SSi];
           curWinState._closedTabs = curWinState._closedTabs.concat(winState._closedTabs);
           curWinState._closedTabs.splice(this._max_tabs_undo, curWinState._closedTabs.length);
         }
 
-        // Restore into that window - pretend it's a followup since we'll already
-        // have a focused window.
         // XXXzpao This is going to merge extData together (taking what was in
         //        winState over what is in the window already.
-        let options = {overwriteTabs: canOverwriteTabs, isFollowUp: true};
-        this.restoreWindow(windowToUse, winState, options);
+        // We don't restore window right away, just store its data.
+        // Later, these windows will be restored with newly opened windows.
+        this._updateWindowRestoreState(windowToUse, {
+          windows: [winState],
+          options: {overwriteTabs: canOverwriteTabs}
+        });
+        openWindows.push(windowToUse);
       } else {
-        this._openWindowWithState({ windows: [winState] });
+        windowsToOpen.push(winState);
       }
     }
 
+    // Actually restore windows in reversed z-order.
+    this.openWindows({windows: windowsToOpen}).then(openedWindows =>
+      this.restoreWindowsInReversedZOrder(openWindows.concat(openedWindows)));
+
     // Merge closed windows from this session with ones from last session
     if (lastSessionState._closedWindows) {
       this._closedWindows = this._closedWindows.concat(lastSessionState._closedWindows);
       this._capClosedWindows();
       this._closedObjectsChanged = true;
     }
 
     DevToolsShim.restoreDevToolsSession(lastSessionState);
@@ -3153,25 +3182,26 @@ var SessionStoreInternal = {
       this._notifyOfClosedObjectsChange();
     });
 
     var activeWindow = this._getTopWindow();
 
     TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_ALL_WINDOWS_DATA_MS");
     if (RunState.isRunning) {
       // update the data for all windows with activities since the last save operation
-      this._forEachBrowserWindow(function(aWindow) {
-        if (!this._isWindowLoaded(aWindow)) // window data is still in _statesToRestore
-          return;
-        if (aUpdateAll || DirtyWindows.has(aWindow) || aWindow == activeWindow) {
-          this._collectWindowData(aWindow);
+      for (let window of this._browserWindows) {
+        if (!this._isWindowLoaded(window)) // window data is still in _statesToRestore
+          continue;
+        if (aUpdateAll || DirtyWindows.has(window) || window == activeWindow) {
+          this._collectWindowData(window);
         } else { // always update the window features (whose change alone never triggers a save operation)
-          this._updateWindowFeatures(aWindow);
+          this._updateWindowFeatures(window);
         }
-      });
+        this._windows[window.__SSi].zIndex = BrowserWindowTracker.getZIndex(window);
+      }
       DirtyWindows.clear();
     }
     TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_ALL_WINDOWS_DATA_MS");
 
     // An array that at the end will hold all current window data.
     var total = [];
     // The ids of all windows contained in 'total' in the same order.
     var ids = [];
@@ -3316,37 +3346,49 @@ var SessionStoreInternal = {
 
     DirtyWindows.remove(aWindow);
     return tabMap;
   },
 
   /* ........ Restoring Functionality .............. */
 
   /**
+   * Open windows with data
+   *
+   * @param root
+   *        Windows data
+   * @returns a promise resolved when all windows have been opened
+   */
+  openWindows(root) {
+    let openedWindowPromises = [];
+    for (let winData of root.windows) {
+      if (winData && winData.tabs && winData.tabs[0]) {
+        let window = this._openWindowWithState({ windows: [winData] });
+        openedWindowPromises.push(promiseWindowShowing(window));
+      }
+    }
+    return Promise.all(openedWindowPromises);
+  },
+
+  /**
    * restore features to a single window
    * @param aWindow
    *        Window reference to the window to use for restoration
    * @param winData
    *        JS object
    * @param aOptions
    *        {overwriteTabs: true} to overwrite existing tabs w/ new ones
-   *        {isFollowUp: true} if this is not the restoration of the 1st window
    *        {firstWindow: true} if this is the first non-private window we're
    *                            restoring in this session, that might open an
    *                            external link as well
    */
   restoreWindow: function ssi_restoreWindow(aWindow, winData, aOptions = {}) {
     let overwriteTabs = aOptions && aOptions.overwriteTabs;
-    let isFollowUp = aOptions && aOptions.isFollowUp;
     let firstWindow = aOptions && aOptions.firstWindow;
 
-    if (isFollowUp) {
-      this.windowToFocus = aWindow;
-    }
-
     // initialize window if necessary
     if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
       this.onLoad(aWindow);
 
     TelemetryStopwatch.start("FX_SESSION_RESTORE_RESTORE_WINDOW_MS");
 
     // We're not returning from this before we end up calling restoreTabs
     // for this window, so make sure we send the SSWindowStateBusy event.
@@ -3473,17 +3515,16 @@ var SessionStoreInternal = {
     // We want to correlate the window with data from the last session, so
     // assign another id if we have one. Otherwise clear so we don't do
     // anything with it.
     delete aWindow.__SS_lastSessionWindowID;
     if (winData.__lastSessionWindowID)
       aWindow.__SS_lastSessionWindowID = winData.__lastSessionWindowID;
 
     if (overwriteTabs) {
-      this.restoreWindowFeatures(aWindow, winData);
       delete this._windows[aWindow.__SSi].extData;
     }
 
     // Restore cookies from legacy sessions, i.e. before bug 912717.
     SessionCookies.restore(winData.cookies || []);
 
     if (winData.extData) {
       if (!this._windows[aWindow.__SSi].extData) {
@@ -3572,36 +3613,66 @@ var SessionStoreInternal = {
       }
       // A flag indicate that we've prepared a connection for this tab and
       // if is called again, we shouldn't prepare another connection.
       tab.__SS_connectionPrepared = true;
     }
   },
 
   /**
+   * This function will restore window features and then retore window data.
+   *
+   * @param windows
+   *        ordered array of windows to restore
+   */
+  restoreWindowsFeaturesAndTabs(windows) {
+    // First, we restore window features, so that when users start interacting
+    // with a window, we don't steal the window focus.
+    for (let window of windows) {
+      let state = this._statesToRestore[WINDOW_RESTORE_IDS.get(window)];
+      this.restoreWindowFeatures(window, state.windows[0]);
+    }
+
+    // Then we restore data into windows.
+    for (let window of windows) {
+      let state = this._statesToRestore[WINDOW_RESTORE_IDS.get(window)];
+      this.restoreWindow(window, state.windows[0], state.options || {overwriteTabs: true});
+      WINDOW_RESTORE_ZINDICES.delete(window);
+    }
+  },
+
+  /**
+   * This function will restore window in reversed z-index, so that users will
+   * be presented with most recently used window first.
+   *
+   * @param windows
+   *        unordered array of windows to restore
+   */
+  restoreWindowsInReversedZOrder(windows) {
+    windows.sort((a, b) =>
+      (WINDOW_RESTORE_ZINDICES.get(a) || 0) - (WINDOW_RESTORE_ZINDICES.get(b) || 0));
+
+    this.windowToFocus = windows[0];
+    this.restoreWindowsFeaturesAndTabs(windows);
+  },
+
+  /**
    * Restore multiple windows using the provided state.
    * @param aWindow
    *        Window reference to the first window to use for restoration.
    *        Additionally required windows will be opened.
    * @param aState
    *        JS object or JSON string
    * @param aOptions
    *        {overwriteTabs: true} to overwrite existing tabs w/ new ones
-   *        {isFollowUp: true} if this is not the restoration of the 1st window
    *        {firstWindow: true} if this is the first non-private window we're
    *                            restoring in this session, that might open an
    *                            external link as well
    */
   restoreWindows: function ssi_restoreWindows(aWindow, aState, aOptions = {}) {
-    let isFollowUp = aOptions && aOptions.isFollowUp;
-
-    if (isFollowUp) {
-      this.windowToFocus = aWindow;
-    }
-
     // initialize window if necessary
     if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
       this.onLoad(aWindow);
 
     let root;
     try {
       root = (typeof aState == "string") ? JSON.parse(aState) : aState;
     } catch (ex) { // invalid state object - don't restore anything
@@ -3617,34 +3688,31 @@ var SessionStoreInternal = {
     }
 
     // We're done here if there are no windows.
     if (!root.windows || !root.windows.length) {
       this._sendRestoreCompletedNotifications();
       return;
     }
 
-    if (!root.selectedWindow || root.selectedWindow > root.windows.length) {
-      root.selectedWindow = 0;
-    }
-
-    // open new windows for all further window entries of a multi-window session
-    // (unless they don't contain any tab data)
-    let winData;
-    for (var w = 1; w < root.windows.length; w++) {
-      winData = root.windows[w];
-      if (winData && winData.tabs && winData.tabs[0]) {
-        var window = this._openWindowWithState({ windows: [winData] });
-        if (w == root.selectedWindow - 1) {
-          this.windowToFocus = window;
-        }
-      }
-    }
-
-    this.restoreWindow(aWindow, root.windows[0], aOptions);
+    let firstWindowData = root.windows.splice(0, 1);
+    // Store the restore state and restore option of the current window,
+    // so that the window can be restored in reversed z-order.
+    this._updateWindowRestoreState(aWindow, {windows: firstWindowData, options: aOptions});
+
+    // Begin the restoration: First open all windows in creation order. After all
+    // windows have opened, we restore states to windows in reversed z-order.
+    this.openWindows(root).then(windows => {
+      // We want to add current window to opened window, so that this window will be
+      // restored in reversed z-order. (We add the window to first position, in case
+      // no z-indices are found, that window will be restored first.)
+      windows.unshift(aWindow);
+
+      this.restoreWindowsInReversedZOrder(windows);
+    });
 
     DevToolsShim.restoreDevToolsSession(aState);
   },
 
   /**
    * Manage history restoration for a window
    * @param aWindow
    *        Window to restore the tabs into
@@ -4217,28 +4285,24 @@ var SessionStoreInternal = {
   _updateSessionStartTime: function ssi_updateSessionStartTime(state) {
     // Attempt to load the session start time from the session state
     if (state.session && state.session.startTime) {
       this._sessionStartTime = state.session.startTime;
     }
   },
 
   /**
-   * call a callback for all currently opened browser windows
-   * (might miss the most recent one)
-   * @param aFunc
-   *        Callback each window is passed to
+   * Iterator that yields all currently opened browser windows, in order.
+   * (Might miss the most recent one.)
    */
-  _forEachBrowserWindow: function ssi_forEachBrowserWindow(aFunc) {
-    var windowsEnum = Services.wm.getEnumerator("navigator:browser");
-
-    while (windowsEnum.hasMoreElements()) {
-      var window = windowsEnum.getNext();
-      if (window.__SSi && !window.closed) {
-        aFunc.call(this, window);
+  _browserWindows: {
+    * [Symbol.iterator]() {
+      for (let window of BrowserWindowTracker.orderedWindows) {
+        if (window.__SSi && !window.closed)
+          yield window;
       }
     }
   },
 
   /**
    * Returns most recent window
    * @returns Window reference
    */
@@ -4260,16 +4324,38 @@ var SessionStoreInternal = {
       if (window.closed) {
         promises.push(this.onClose(window));
       }
     }
     return Promise.all(promises);
   },
 
   /**
+   * Store a restore state of a window to this._statesToRestore. The window
+   * will be given an id that can be used to get the restore state from
+   * this._statesToRestore.
+   *
+   * @param window
+   *        a reference to a window that has a state to restore
+   * @param state
+   *        an object containing session data
+   */
+  _updateWindowRestoreState(window, state) {
+    // Store z-index, so that windows can be restored in reversed z-order.
+    if ("zIndex" in state.windows[0]) {
+      WINDOW_RESTORE_ZINDICES.set(window, state.windows[0].zIndex);
+    }
+    do {
+      var ID = "window" + Math.random();
+    } while (ID in this._statesToRestore);
+    WINDOW_RESTORE_IDS.set(window, ID);
+    this._statesToRestore[ID] = state;
+  },
+
+  /**
    * open a new browser window for a given session state
    * called when restoring a multi-window session
    * @param aState
    *        Object containing session data
    */
   _openWindowWithState: function ssi_openWindowWithState(aState) {
     var argString = Cc["@mozilla.org/supports-string;1"].
                     createInstance(Ci.nsISupportsString);
@@ -4287,21 +4373,17 @@ var SessionStoreInternal = {
     if (winState.isPrivate) {
       features += ",private";
     }
 
     var window =
       Services.ww.openWindow(null, this._prefBranch.getCharPref("chromeURL"),
                              "_blank", features, argString);
 
-    do {
-      var ID = "window" + Math.random();
-    } while (ID in this._statesToRestore);
-    WINDOW_RESTORE_IDS.set(window, ID);
-    this._statesToRestore[ID] = aState;
+    this._updateWindowRestoreState(window, aState);
 
     return window;
   },
 
   /**
    * Whether or not to resume session, if not recovering from a crash.
    * @returns bool
    */