browser/components/sessionstore/src/SessionStore.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/components/sessionstore/src/SessionStore.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,3687 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +this.EXPORTED_SYMBOLS = ["SessionStore"];
    1.11 +
    1.12 +const Cu = Components.utils;
    1.13 +const Cc = Components.classes;
    1.14 +const Ci = Components.interfaces;
    1.15 +const Cr = Components.results;
    1.16 +
    1.17 +const STATE_STOPPED = 0;
    1.18 +const STATE_RUNNING = 1;
    1.19 +const STATE_QUITTING = -1;
    1.20 +
    1.21 +const TAB_STATE_NEEDS_RESTORE = 1;
    1.22 +const TAB_STATE_RESTORING = 2;
    1.23 +
    1.24 +const NOTIFY_WINDOWS_RESTORED = "sessionstore-windows-restored";
    1.25 +const NOTIFY_BROWSER_STATE_RESTORED = "sessionstore-browser-state-restored";
    1.26 +const NOTIFY_LAST_SESSION_CLEARED = "sessionstore-last-session-cleared";
    1.27 +
    1.28 +const NOTIFY_TAB_RESTORED = "sessionstore-debug-tab-restored"; // WARNING: debug-only
    1.29 +
    1.30 +// Maximum number of tabs to restore simultaneously. Previously controlled by
    1.31 +// the browser.sessionstore.max_concurrent_tabs pref.
    1.32 +const MAX_CONCURRENT_TAB_RESTORES = 3;
    1.33 +
    1.34 +// global notifications observed
    1.35 +const OBSERVING = [
    1.36 +  "domwindowopened", "domwindowclosed",
    1.37 +  "quit-application-requested", "quit-application-granted",
    1.38 +  "browser-lastwindow-close-granted",
    1.39 +  "quit-application", "browser:purge-session-history",
    1.40 +  "browser:purge-domain-data",
    1.41 +  "gather-telemetry",
    1.42 +];
    1.43 +
    1.44 +// XUL Window properties to (re)store
    1.45 +// Restored in restoreDimensions()
    1.46 +const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"];
    1.47 +
    1.48 +// Hideable window features to (re)store
    1.49 +// Restored in restoreWindowFeatures()
    1.50 +const WINDOW_HIDEABLE_FEATURES = [
    1.51 +  "menubar", "toolbar", "locationbar", "personalbar", "statusbar", "scrollbars"
    1.52 +];
    1.53 +
    1.54 +const MESSAGES = [
    1.55 +  // The content script gives us a reference to an object that performs
    1.56 +  // synchronous collection of session data.
    1.57 +  "SessionStore:setupSyncHandler",
    1.58 +
    1.59 +  // The content script sends us data that has been invalidated and needs to
    1.60 +  // be saved to disk.
    1.61 +  "SessionStore:update",
    1.62 +
    1.63 +  // The restoreHistory code has run. This is a good time to run SSTabRestoring.
    1.64 +  "SessionStore:restoreHistoryComplete",
    1.65 +
    1.66 +  // The load for the restoring tab has begun. We update the URL bar at this
    1.67 +  // time; if we did it before, the load would overwrite it.
    1.68 +  "SessionStore:restoreTabContentStarted",
    1.69 +
    1.70 +  // All network loads for a restoring tab are done, so we should consider
    1.71 +  // restoring another tab in the queue.
    1.72 +  "SessionStore:restoreTabContentComplete",
    1.73 +
    1.74 +  // The document has been restored, so the restore is done. We trigger
    1.75 +  // SSTabRestored at this time.
    1.76 +  "SessionStore:restoreDocumentComplete",
    1.77 +
    1.78 +  // A tab that is being restored was reloaded. We call restoreTabContent to
    1.79 +  // finish restoring it right away.
    1.80 +  "SessionStore:reloadPendingTab",
    1.81 +];
    1.82 +
    1.83 +// These are tab events that we listen to.
    1.84 +const TAB_EVENTS = [
    1.85 +  "TabOpen", "TabClose", "TabSelect", "TabShow", "TabHide", "TabPinned",
    1.86 +  "TabUnpinned"
    1.87 +];
    1.88 +
    1.89 +// The number of milliseconds in a day
    1.90 +const MS_PER_DAY = 1000.0 * 60.0 * 60.0 * 24.0;
    1.91 +
    1.92 +Cu.import("resource://gre/modules/Services.jsm", this);
    1.93 +Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
    1.94 +Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", this);
    1.95 +Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
    1.96 +Cu.import("resource://gre/modules/osfile.jsm", this);
    1.97 +Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm", this);
    1.98 +Cu.import("resource://gre/modules/Promise.jsm", this);
    1.99 +Cu.import("resource://gre/modules/Task.jsm", this);
   1.100 +
   1.101 +XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
   1.102 +  "@mozilla.org/browser/sessionstartup;1", "nsISessionStartup");
   1.103 +XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager",
   1.104 +  "@mozilla.org/gfx/screenmanager;1", "nsIScreenManager");
   1.105 +XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
   1.106 +  "@mozilla.org/base/telemetry;1", "nsITelemetry");
   1.107 +
   1.108 +XPCOMUtils.defineLazyModuleGetter(this, "console",
   1.109 +  "resource://gre/modules/devtools/Console.jsm");
   1.110 +XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
   1.111 +  "resource:///modules/RecentWindow.jsm");
   1.112 +
   1.113 +XPCOMUtils.defineLazyModuleGetter(this, "GlobalState",
   1.114 +  "resource:///modules/sessionstore/GlobalState.jsm");
   1.115 +XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
   1.116 +  "resource:///modules/sessionstore/PrivacyFilter.jsm");
   1.117 +XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
   1.118 +  "resource:///modules/devtools/scratchpad-manager.jsm");
   1.119 +XPCOMUtils.defineLazyModuleGetter(this, "SessionSaver",
   1.120 +  "resource:///modules/sessionstore/SessionSaver.jsm");
   1.121 +XPCOMUtils.defineLazyModuleGetter(this, "SessionCookies",
   1.122 +  "resource:///modules/sessionstore/SessionCookies.jsm");
   1.123 +XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
   1.124 +  "resource:///modules/sessionstore/SessionFile.jsm");
   1.125 +XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
   1.126 +  "resource:///modules/sessionstore/TabAttributes.jsm");
   1.127 +XPCOMUtils.defineLazyModuleGetter(this, "TabState",
   1.128 +  "resource:///modules/sessionstore/TabState.jsm");
   1.129 +XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
   1.130 +  "resource:///modules/sessionstore/TabStateCache.jsm");
   1.131 +XPCOMUtils.defineLazyModuleGetter(this, "Utils",
   1.132 +  "resource:///modules/sessionstore/Utils.jsm");
   1.133 +
   1.134 +/**
   1.135 + * |true| if we are in debug mode, |false| otherwise.
   1.136 + * Debug mode is controlled by preference browser.sessionstore.debug
   1.137 + */
   1.138 +let gDebuggingEnabled = false;
   1.139 +function debug(aMsg) {
   1.140 +  if (gDebuggingEnabled) {
   1.141 +    aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n");
   1.142 +    Services.console.logStringMessage(aMsg);
   1.143 +  }
   1.144 +}
   1.145 +
   1.146 +this.SessionStore = {
   1.147 +  get promiseInitialized() {
   1.148 +    return SessionStoreInternal.promiseInitialized;
   1.149 +  },
   1.150 +
   1.151 +  get canRestoreLastSession() {
   1.152 +    return SessionStoreInternal.canRestoreLastSession;
   1.153 +  },
   1.154 +
   1.155 +  set canRestoreLastSession(val) {
   1.156 +    SessionStoreInternal.canRestoreLastSession = val;
   1.157 +  },
   1.158 +
   1.159 +  init: function ss_init() {
   1.160 +    SessionStoreInternal.init();
   1.161 +  },
   1.162 +
   1.163 +  getBrowserState: function ss_getBrowserState() {
   1.164 +    return SessionStoreInternal.getBrowserState();
   1.165 +  },
   1.166 +
   1.167 +  setBrowserState: function ss_setBrowserState(aState) {
   1.168 +    SessionStoreInternal.setBrowserState(aState);
   1.169 +  },
   1.170 +
   1.171 +  getWindowState: function ss_getWindowState(aWindow) {
   1.172 +    return SessionStoreInternal.getWindowState(aWindow);
   1.173 +  },
   1.174 +
   1.175 +  setWindowState: function ss_setWindowState(aWindow, aState, aOverwrite) {
   1.176 +    SessionStoreInternal.setWindowState(aWindow, aState, aOverwrite);
   1.177 +  },
   1.178 +
   1.179 +  getTabState: function ss_getTabState(aTab) {
   1.180 +    return SessionStoreInternal.getTabState(aTab);
   1.181 +  },
   1.182 +
   1.183 +  setTabState: function ss_setTabState(aTab, aState) {
   1.184 +    SessionStoreInternal.setTabState(aTab, aState);
   1.185 +  },
   1.186 +
   1.187 +  duplicateTab: function ss_duplicateTab(aWindow, aTab, aDelta = 0) {
   1.188 +    return SessionStoreInternal.duplicateTab(aWindow, aTab, aDelta);
   1.189 +  },
   1.190 +
   1.191 +  getClosedTabCount: function ss_getClosedTabCount(aWindow) {
   1.192 +    return SessionStoreInternal.getClosedTabCount(aWindow);
   1.193 +  },
   1.194 +
   1.195 +  getClosedTabData: function ss_getClosedTabDataAt(aWindow) {
   1.196 +    return SessionStoreInternal.getClosedTabData(aWindow);
   1.197 +  },
   1.198 +
   1.199 +  undoCloseTab: function ss_undoCloseTab(aWindow, aIndex) {
   1.200 +    return SessionStoreInternal.undoCloseTab(aWindow, aIndex);
   1.201 +  },
   1.202 +
   1.203 +  forgetClosedTab: function ss_forgetClosedTab(aWindow, aIndex) {
   1.204 +    return SessionStoreInternal.forgetClosedTab(aWindow, aIndex);
   1.205 +  },
   1.206 +
   1.207 +  getClosedWindowCount: function ss_getClosedWindowCount() {
   1.208 +    return SessionStoreInternal.getClosedWindowCount();
   1.209 +  },
   1.210 +
   1.211 +  getClosedWindowData: function ss_getClosedWindowData() {
   1.212 +    return SessionStoreInternal.getClosedWindowData();
   1.213 +  },
   1.214 +
   1.215 +  undoCloseWindow: function ss_undoCloseWindow(aIndex) {
   1.216 +    return SessionStoreInternal.undoCloseWindow(aIndex);
   1.217 +  },
   1.218 +
   1.219 +  forgetClosedWindow: function ss_forgetClosedWindow(aIndex) {
   1.220 +    return SessionStoreInternal.forgetClosedWindow(aIndex);
   1.221 +  },
   1.222 +
   1.223 +  getWindowValue: function ss_getWindowValue(aWindow, aKey) {
   1.224 +    return SessionStoreInternal.getWindowValue(aWindow, aKey);
   1.225 +  },
   1.226 +
   1.227 +  setWindowValue: function ss_setWindowValue(aWindow, aKey, aStringValue) {
   1.228 +    SessionStoreInternal.setWindowValue(aWindow, aKey, aStringValue);
   1.229 +  },
   1.230 +
   1.231 +  deleteWindowValue: function ss_deleteWindowValue(aWindow, aKey) {
   1.232 +    SessionStoreInternal.deleteWindowValue(aWindow, aKey);
   1.233 +  },
   1.234 +
   1.235 +  getTabValue: function ss_getTabValue(aTab, aKey) {
   1.236 +    return SessionStoreInternal.getTabValue(aTab, aKey);
   1.237 +  },
   1.238 +
   1.239 +  setTabValue: function ss_setTabValue(aTab, aKey, aStringValue) {
   1.240 +    SessionStoreInternal.setTabValue(aTab, aKey, aStringValue);
   1.241 +  },
   1.242 +
   1.243 +  deleteTabValue: function ss_deleteTabValue(aTab, aKey) {
   1.244 +    SessionStoreInternal.deleteTabValue(aTab, aKey);
   1.245 +  },
   1.246 +
   1.247 +  getGlobalValue: function ss_getGlobalValue(aKey) {
   1.248 +    return SessionStoreInternal.getGlobalValue(aKey);
   1.249 +  },
   1.250 +
   1.251 +  setGlobalValue: function ss_setGlobalValue(aKey, aStringValue) {
   1.252 +    SessionStoreInternal.setGlobalValue(aKey, aStringValue);
   1.253 +  },
   1.254 +
   1.255 +  deleteGlobalValue: function ss_deleteGlobalValue(aKey) {
   1.256 +    SessionStoreInternal.deleteGlobalValue(aKey);
   1.257 +  },
   1.258 +
   1.259 +  persistTabAttribute: function ss_persistTabAttribute(aName) {
   1.260 +    SessionStoreInternal.persistTabAttribute(aName);
   1.261 +  },
   1.262 +
   1.263 +  restoreLastSession: function ss_restoreLastSession() {
   1.264 +    SessionStoreInternal.restoreLastSession();
   1.265 +  },
   1.266 +
   1.267 +  getCurrentState: function (aUpdateAll) {
   1.268 +    return SessionStoreInternal.getCurrentState(aUpdateAll);
   1.269 +  },
   1.270 +
   1.271 +  /**
   1.272 +   * Backstage pass to implementation details, used for testing purpose.
   1.273 +   * Controlled by preference "browser.sessionstore.testmode".
   1.274 +   */
   1.275 +  get _internal() {
   1.276 +    if (Services.prefs.getBoolPref("browser.sessionstore.debug")) {
   1.277 +      return SessionStoreInternal;
   1.278 +    }
   1.279 +    return undefined;
   1.280 +  },
   1.281 +};
   1.282 +
   1.283 +// Freeze the SessionStore object. We don't want anyone to modify it.
   1.284 +Object.freeze(SessionStore);
   1.285 +
   1.286 +let SessionStoreInternal = {
   1.287 +  QueryInterface: XPCOMUtils.generateQI([
   1.288 +    Ci.nsIDOMEventListener,
   1.289 +    Ci.nsIObserver,
   1.290 +    Ci.nsISupportsWeakReference
   1.291 +  ]),
   1.292 +
   1.293 +  // set default load state
   1.294 +  _loadState: STATE_STOPPED,
   1.295 +
   1.296 +  _globalState: new GlobalState(),
   1.297 +
   1.298 +  // During the initial restore and setBrowserState calls tracks the number of
   1.299 +  // windows yet to be restored
   1.300 +  _restoreCount: -1,
   1.301 +
   1.302 +  // This number gets incremented each time we start to restore a tab.
   1.303 +  _nextRestoreEpoch: 1,
   1.304 +
   1.305 +  // For each <browser> element being restored, records the current epoch.
   1.306 +  _browserEpochs: new WeakMap(),
   1.307 +
   1.308 +  // whether a setBrowserState call is in progress
   1.309 +  _browserSetState: false,
   1.310 +
   1.311 +  // time in milliseconds when the session was started (saved across sessions),
   1.312 +  // defaults to now if no session was restored or timestamp doesn't exist
   1.313 +  _sessionStartTime: Date.now(),
   1.314 +
   1.315 +  // states for all currently opened windows
   1.316 +  _windows: {},
   1.317 +
   1.318 +  // counter for creating unique window IDs
   1.319 +  _nextWindowID: 0,
   1.320 +
   1.321 +  // states for all recently closed windows
   1.322 +  _closedWindows: [],
   1.323 +
   1.324 +  // collection of session states yet to be restored
   1.325 +  _statesToRestore: {},
   1.326 +
   1.327 +  // counts the number of crashes since the last clean start
   1.328 +  _recentCrashes: 0,
   1.329 +
   1.330 +  // whether the last window was closed and should be restored
   1.331 +  _restoreLastWindow: false,
   1.332 +
   1.333 +  // number of tabs currently restoring
   1.334 +  _tabsRestoringCount: 0,
   1.335 +
   1.336 +  // When starting Firefox with a single private window, this is the place
   1.337 +  // where we keep the session we actually wanted to restore in case the user
   1.338 +  // decides to later open a non-private window as well.
   1.339 +  _deferredInitialState: null,
   1.340 +
   1.341 +  // A promise resolved once initialization is complete
   1.342 +  _deferredInitialized: Promise.defer(),
   1.343 +
   1.344 +  // Whether session has been initialized
   1.345 +  _sessionInitialized: false,
   1.346 +
   1.347 +  // Promise that is resolved when we're ready to initialize
   1.348 +  // and restore the session.
   1.349 +  _promiseReadyForInitialization: null,
   1.350 +
   1.351 +  /**
   1.352 +   * A promise fulfilled once initialization is complete.
   1.353 +   */
   1.354 +  get promiseInitialized() {
   1.355 +    return this._deferredInitialized.promise;
   1.356 +  },
   1.357 +
   1.358 +  get canRestoreLastSession() {
   1.359 +    return LastSession.canRestore;
   1.360 +  },
   1.361 +
   1.362 +  set canRestoreLastSession(val) {
   1.363 +    // Cheat a bit; only allow false.
   1.364 +    if (!val) {
   1.365 +      LastSession.clear();
   1.366 +    }
   1.367 +  },
   1.368 +
   1.369 +  /**
   1.370 +   * Initialize the sessionstore service.
   1.371 +   */
   1.372 +  init: function () {
   1.373 +    if (this._initialized) {
   1.374 +      throw new Error("SessionStore.init() must only be called once!");
   1.375 +    }
   1.376 +
   1.377 +    TelemetryTimestamps.add("sessionRestoreInitialized");
   1.378 +    OBSERVING.forEach(function(aTopic) {
   1.379 +      Services.obs.addObserver(this, aTopic, true);
   1.380 +    }, this);
   1.381 +
   1.382 +    this._initPrefs();
   1.383 +    this._initialized = true;
   1.384 +  },
   1.385 +
   1.386 +  /**
   1.387 +   * Initialize the session using the state provided by SessionStartup
   1.388 +   */
   1.389 +  initSession: function () {
   1.390 +    let state;
   1.391 +    let ss = gSessionStartup;
   1.392 +
   1.393 +    try {
   1.394 +      if (ss.doRestore() ||
   1.395 +          ss.sessionType == Ci.nsISessionStartup.DEFER_SESSION)
   1.396 +        state = ss.state;
   1.397 +    }
   1.398 +    catch(ex) { dump(ex + "\n"); } // no state to restore, which is ok
   1.399 +
   1.400 +    if (state) {
   1.401 +      try {
   1.402 +        // If we're doing a DEFERRED session, then we want to pull pinned tabs
   1.403 +        // out so they can be restored.
   1.404 +        if (ss.sessionType == Ci.nsISessionStartup.DEFER_SESSION) {
   1.405 +          let [iniState, remainingState] = this._prepDataForDeferredRestore(state);
   1.406 +          // If we have a iniState with windows, that means that we have windows
   1.407 +          // with app tabs to restore.
   1.408 +          if (iniState.windows.length)
   1.409 +            state = iniState;
   1.410 +          else
   1.411 +            state = null;
   1.412 +
   1.413 +          if (remainingState.windows.length) {
   1.414 +            LastSession.setState(remainingState);
   1.415 +          }
   1.416 +        }
   1.417 +        else {
   1.418 +          // Get the last deferred session in case the user still wants to
   1.419 +          // restore it
   1.420 +          LastSession.setState(state.lastSessionState);
   1.421 +
   1.422 +          if (ss.previousSessionCrashed) {
   1.423 +            this._recentCrashes = (state.session &&
   1.424 +                                   state.session.recentCrashes || 0) + 1;
   1.425 +
   1.426 +            if (this._needsRestorePage(state, this._recentCrashes)) {
   1.427 +              // replace the crashed session with a restore-page-only session
   1.428 +              let pageData = {
   1.429 +                url: "about:sessionrestore",
   1.430 +                formdata: {
   1.431 +                  id: { "sessionData": state },
   1.432 +                  xpath: {}
   1.433 +                }
   1.434 +              };
   1.435 +              state = { windows: [{ tabs: [{ entries: [pageData] }] }] };
   1.436 +            } else if (this._hasSingleTabWithURL(state.windows,
   1.437 +                                                 "about:welcomeback")) {
   1.438 +              // On a single about:welcomeback URL that crashed, replace about:welcomeback
   1.439 +              // with about:sessionrestore, to make clear to the user that we crashed.
   1.440 +              state.windows[0].tabs[0].entries[0].url = "about:sessionrestore";
   1.441 +            }
   1.442 +          }
   1.443 +
   1.444 +          // Update the session start time using the restored session state.
   1.445 +          this._updateSessionStartTime(state);
   1.446 +
   1.447 +          // make sure that at least the first window doesn't have anything hidden
   1.448 +          delete state.windows[0].hidden;
   1.449 +          // Since nothing is hidden in the first window, it cannot be a popup
   1.450 +          delete state.windows[0].isPopup;
   1.451 +          // We don't want to minimize and then open a window at startup.
   1.452 +          if (state.windows[0].sizemode == "minimized")
   1.453 +            state.windows[0].sizemode = "normal";
   1.454 +          // clear any lastSessionWindowID attributes since those don't matter
   1.455 +          // during normal restore
   1.456 +          state.windows.forEach(function(aWindow) {
   1.457 +            delete aWindow.__lastSessionWindowID;
   1.458 +          });
   1.459 +        }
   1.460 +      }
   1.461 +      catch (ex) { debug("The session file is invalid: " + ex); }
   1.462 +    }
   1.463 +
   1.464 +    // at this point, we've as good as resumed the session, so we can
   1.465 +    // clear the resume_session_once flag, if it's set
   1.466 +    if (this._loadState != STATE_QUITTING &&
   1.467 +        this._prefBranch.getBoolPref("sessionstore.resume_session_once"))
   1.468 +      this._prefBranch.setBoolPref("sessionstore.resume_session_once", false);
   1.469 +
   1.470 +    this._performUpgradeBackup();
   1.471 +
   1.472 +    return state;
   1.473 +  },
   1.474 +
   1.475 +  /**
   1.476 +   * If this is the first time we launc this build of Firefox,
   1.477 +   * backup sessionstore.js.
   1.478 +   */
   1.479 +  _performUpgradeBackup: function ssi_performUpgradeBackup() {
   1.480 +    // Perform upgrade backup, if necessary
   1.481 +    const PREF_UPGRADE = "sessionstore.upgradeBackup.latestBuildID";
   1.482 +
   1.483 +    let buildID = Services.appinfo.platformBuildID;
   1.484 +    let latestBackup = this._prefBranch.getCharPref(PREF_UPGRADE);
   1.485 +    if (latestBackup == buildID) {
   1.486 +      return Promise.resolve();
   1.487 +    }
   1.488 +    return Task.spawn(function task() {
   1.489 +      try {
   1.490 +        // Perform background backup
   1.491 +        yield SessionFile.createBackupCopy("-" + buildID);
   1.492 +
   1.493 +        this._prefBranch.setCharPref(PREF_UPGRADE, buildID);
   1.494 +
   1.495 +        // In case of success, remove previous backup.
   1.496 +        yield SessionFile.removeBackupCopy("-" + latestBackup);
   1.497 +      } catch (ex) {
   1.498 +        debug("Could not perform upgrade backup " + ex);
   1.499 +        debug(ex.stack);
   1.500 +      }
   1.501 +    }.bind(this));
   1.502 +  },
   1.503 +
   1.504 +  _initPrefs : function() {
   1.505 +    this._prefBranch = Services.prefs.getBranch("browser.");
   1.506 +
   1.507 +    gDebuggingEnabled = this._prefBranch.getBoolPref("sessionstore.debug");
   1.508 +
   1.509 +    Services.prefs.addObserver("browser.sessionstore.debug", () => {
   1.510 +      gDebuggingEnabled = this._prefBranch.getBoolPref("sessionstore.debug");
   1.511 +    }, false);
   1.512 +
   1.513 +    this._max_tabs_undo = this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
   1.514 +    this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
   1.515 +
   1.516 +    this._max_windows_undo = this._prefBranch.getIntPref("sessionstore.max_windows_undo");
   1.517 +    this._prefBranch.addObserver("sessionstore.max_windows_undo", this, true);
   1.518 +  },
   1.519 +
   1.520 +  /**
   1.521 +   * Called on application shutdown, after notifications:
   1.522 +   * quit-application-granted, quit-application
   1.523 +   */
   1.524 +  _uninit: function ssi_uninit() {
   1.525 +    if (!this._initialized) {
   1.526 +      throw new Error("SessionStore is not initialized.");
   1.527 +    }
   1.528 +
   1.529 +    // save all data for session resuming
   1.530 +    if (this._sessionInitialized) {
   1.531 +      SessionSaver.run();
   1.532 +    }
   1.533 +
   1.534 +    // clear out priority queue in case it's still holding refs
   1.535 +    TabRestoreQueue.reset();
   1.536 +
   1.537 +    // Make sure to cancel pending saves.
   1.538 +    SessionSaver.cancel();
   1.539 +  },
   1.540 +
   1.541 +  /**
   1.542 +   * Handle notifications
   1.543 +   */
   1.544 +  observe: function ssi_observe(aSubject, aTopic, aData) {
   1.545 +    switch (aTopic) {
   1.546 +      case "domwindowopened": // catch new windows
   1.547 +        this.onOpen(aSubject);
   1.548 +        break;
   1.549 +      case "domwindowclosed": // catch closed windows
   1.550 +        this.onClose(aSubject);
   1.551 +        break;
   1.552 +      case "quit-application-requested":
   1.553 +        this.onQuitApplicationRequested();
   1.554 +        break;
   1.555 +      case "quit-application-granted":
   1.556 +        this.onQuitApplicationGranted();
   1.557 +        break;
   1.558 +      case "browser-lastwindow-close-granted":
   1.559 +        this.onLastWindowCloseGranted();
   1.560 +        break;
   1.561 +      case "quit-application":
   1.562 +        this.onQuitApplication(aData);
   1.563 +        break;
   1.564 +      case "browser:purge-session-history": // catch sanitization
   1.565 +        this.onPurgeSessionHistory();
   1.566 +        break;
   1.567 +      case "browser:purge-domain-data":
   1.568 +        this.onPurgeDomainData(aData);
   1.569 +        break;
   1.570 +      case "nsPref:changed": // catch pref changes
   1.571 +        this.onPrefChange(aData);
   1.572 +        break;
   1.573 +      case "gather-telemetry":
   1.574 +        this.onGatherTelemetry();
   1.575 +        break;
   1.576 +    }
   1.577 +  },
   1.578 +
   1.579 +  /**
   1.580 +   * This method handles incoming messages sent by the session store content
   1.581 +   * script and thus enables communication with OOP tabs.
   1.582 +   */
   1.583 +  receiveMessage: function ssi_receiveMessage(aMessage) {
   1.584 +    var browser = aMessage.target;
   1.585 +    var win = browser.ownerDocument.defaultView;
   1.586 +    let tab = this._getTabForBrowser(browser);
   1.587 +    if (!tab) {
   1.588 +      // Ignore messages from <browser> elements that are not tabs.
   1.589 +      return;
   1.590 +    }
   1.591 +
   1.592 +    switch (aMessage.name) {
   1.593 +      case "SessionStore:setupSyncHandler":
   1.594 +        TabState.setSyncHandler(browser, aMessage.objects.handler);
   1.595 +        break;
   1.596 +      case "SessionStore:update":
   1.597 +        this.recordTelemetry(aMessage.data.telemetry);
   1.598 +        TabState.update(browser, aMessage.data);
   1.599 +        this.saveStateDelayed(win);
   1.600 +        break;
   1.601 +      case "SessionStore:restoreHistoryComplete":
   1.602 +        if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
   1.603 +          // Notify the tabbrowser that the tab chrome has been restored.
   1.604 +          let tabData = browser.__SS_data;
   1.605 +
   1.606 +          // wall-paper fix for bug 439675: make sure that the URL to be loaded
   1.607 +          // is always visible in the address bar
   1.608 +          let activePageData = tabData.entries[tabData.index - 1] || null;
   1.609 +          let uri = activePageData ? activePageData.url || null : null;
   1.610 +          browser.userTypedValue = uri;
   1.611 +
   1.612 +          // If the page has a title, set it.
   1.613 +          if (activePageData) {
   1.614 +            if (activePageData.title) {
   1.615 +              tab.label = activePageData.title;
   1.616 +              tab.crop = "end";
   1.617 +            } else if (activePageData.url != "about:blank") {
   1.618 +              tab.label = activePageData.url;
   1.619 +              tab.crop = "center";
   1.620 +            }
   1.621 +          }
   1.622 +
   1.623 +          // Restore the tab icon.
   1.624 +          if ("image" in tabData) {
   1.625 +            win.gBrowser.setIcon(tab, tabData.image);
   1.626 +          }
   1.627 +
   1.628 +          let event = win.document.createEvent("Events");
   1.629 +          event.initEvent("SSTabRestoring", true, false);
   1.630 +          tab.dispatchEvent(event);
   1.631 +        }
   1.632 +        break;
   1.633 +      case "SessionStore:restoreTabContentStarted":
   1.634 +        if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
   1.635 +          // If the user was typing into the URL bar when we crashed, but hadn't hit
   1.636 +          // enter yet, then we just need to write that value to the URL bar without
   1.637 +          // loading anything. This must happen after the load, since it will clear
   1.638 +          // userTypedValue.
   1.639 +          let tabData = browser.__SS_data;
   1.640 +          if (tabData.userTypedValue && !tabData.userTypedClear) {
   1.641 +            browser.userTypedValue = tabData.userTypedValue;
   1.642 +            win.URLBarSetURI();
   1.643 +          }
   1.644 +        }
   1.645 +        break;
   1.646 +      case "SessionStore:restoreTabContentComplete":
   1.647 +        if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
   1.648 +          // This callback is used exclusively by tests that want to
   1.649 +          // monitor the progress of network loads.
   1.650 +          if (gDebuggingEnabled) {
   1.651 +            Services.obs.notifyObservers(browser, NOTIFY_TAB_RESTORED, null);
   1.652 +          }
   1.653 +
   1.654 +          if (tab) {
   1.655 +            SessionStoreInternal._resetLocalTabRestoringState(tab);
   1.656 +            SessionStoreInternal.restoreNextTab();
   1.657 +          }
   1.658 +        }
   1.659 +        break;
   1.660 +      case "SessionStore:restoreDocumentComplete":
   1.661 +        if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
   1.662 +          // Document has been restored. Delete all the state associated
   1.663 +          // with it and trigger SSTabRestored.
   1.664 +          let tab = browser.__SS_restore_tab;
   1.665 +
   1.666 +          delete browser.__SS_restore_data;
   1.667 +          delete browser.__SS_restore_tab;
   1.668 +          delete browser.__SS_data;
   1.669 +
   1.670 +          this._sendTabRestoredNotification(tab);
   1.671 +        }
   1.672 +        break;
   1.673 +      case "SessionStore:reloadPendingTab":
   1.674 +        if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
   1.675 +          if (tab && browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
   1.676 +            this.restoreTabContent(tab);
   1.677 +          }
   1.678 +        }
   1.679 +        break;
   1.680 +      default:
   1.681 +        debug("received unknown message '" + aMessage.name + "'");
   1.682 +        break;
   1.683 +    }
   1.684 +  },
   1.685 +
   1.686 +  /**
   1.687 +   * Record telemetry measurements stored in an object.
   1.688 +   * @param telemetry
   1.689 +   *        {histogramID: value, ...} An object mapping histogramIDs to the
   1.690 +   *        value to be recorded for that ID,
   1.691 +   */
   1.692 +  recordTelemetry: function (telemetry) {
   1.693 +    for (let histogramId in telemetry){
   1.694 +      Telemetry.getHistogramById(histogramId).add(telemetry[histogramId]);
   1.695 +    }
   1.696 +  },
   1.697 +
   1.698 +  /* ........ Window Event Handlers .............. */
   1.699 +
   1.700 +  /**
   1.701 +   * Implement nsIDOMEventListener for handling various window and tab events
   1.702 +   */
   1.703 +  handleEvent: function ssi_handleEvent(aEvent) {
   1.704 +    var win = aEvent.currentTarget.ownerDocument.defaultView;
   1.705 +    let browser;
   1.706 +    switch (aEvent.type) {
   1.707 +      case "TabOpen":
   1.708 +        this.onTabAdd(win, aEvent.originalTarget);
   1.709 +        break;
   1.710 +      case "TabClose":
   1.711 +        // aEvent.detail determines if the tab was closed by moving to a different window
   1.712 +        if (!aEvent.detail)
   1.713 +          this.onTabClose(win, aEvent.originalTarget);
   1.714 +        this.onTabRemove(win, aEvent.originalTarget);
   1.715 +        break;
   1.716 +      case "TabSelect":
   1.717 +        this.onTabSelect(win);
   1.718 +        break;
   1.719 +      case "TabShow":
   1.720 +        this.onTabShow(win, aEvent.originalTarget);
   1.721 +        break;
   1.722 +      case "TabHide":
   1.723 +        this.onTabHide(win, aEvent.originalTarget);
   1.724 +        break;
   1.725 +      case "TabPinned":
   1.726 +      case "TabUnpinned":
   1.727 +        this.saveStateDelayed(win);
   1.728 +        break;
   1.729 +    }
   1.730 +    this._clearRestoringWindows();
   1.731 +  },
   1.732 +
   1.733 +  /**
   1.734 +   * Generate a unique window identifier
   1.735 +   * @return string
   1.736 +   *         A unique string to identify a window
   1.737 +   */
   1.738 +  _generateWindowID: function ssi_generateWindowID() {
   1.739 +    return "window" + (this._nextWindowID++);
   1.740 +  },
   1.741 +
   1.742 +  /**
   1.743 +   * If it's the first window load since app start...
   1.744 +   * - determine if we're reloading after a crash or a forced-restart
   1.745 +   * - restore window state
   1.746 +   * - restart downloads
   1.747 +   * Set up event listeners for this window's tabs
   1.748 +   * @param aWindow
   1.749 +   *        Window reference
   1.750 +   * @param aInitialState
   1.751 +   *        The initial state to be loaded after startup (optional)
   1.752 +   */
   1.753 +  onLoad: function ssi_onLoad(aWindow, aInitialState = null) {
   1.754 +    // return if window has already been initialized
   1.755 +    if (aWindow && aWindow.__SSi && this._windows[aWindow.__SSi])
   1.756 +      return;
   1.757 +
   1.758 +    // ignore windows opened while shutting down
   1.759 +    if (this._loadState == STATE_QUITTING)
   1.760 +      return;
   1.761 +
   1.762 +    // Assign the window a unique identifier we can use to reference
   1.763 +    // internal data about the window.
   1.764 +    aWindow.__SSi = this._generateWindowID();
   1.765 +
   1.766 +    let mm = aWindow.messageManager;
   1.767 +    MESSAGES.forEach(msg => mm.addMessageListener(msg, this));
   1.768 +
   1.769 +    // Load the frame script after registering listeners.
   1.770 +    mm.loadFrameScript("chrome://browser/content/content-sessionStore.js", true);
   1.771 +
   1.772 +    // and create its data object
   1.773 +    this._windows[aWindow.__SSi] = { tabs: [], selected: 0, _closedTabs: [], busy: false };
   1.774 +
   1.775 +    let isPrivateWindow = false;
   1.776 +    if (PrivateBrowsingUtils.isWindowPrivate(aWindow))
   1.777 +      this._windows[aWindow.__SSi].isPrivate = isPrivateWindow = true;
   1.778 +    if (!this._isWindowLoaded(aWindow))
   1.779 +      this._windows[aWindow.__SSi]._restoring = true;
   1.780 +    if (!aWindow.toolbar.visible)
   1.781 +      this._windows[aWindow.__SSi].isPopup = true;
   1.782 +
   1.783 +    // perform additional initialization when the first window is loading
   1.784 +    if (this._loadState == STATE_STOPPED) {
   1.785 +      this._loadState = STATE_RUNNING;
   1.786 +      SessionSaver.updateLastSaveTime();
   1.787 +
   1.788 +      // restore a crashed session resp. resume the last session if requested
   1.789 +      if (aInitialState) {
   1.790 +        if (isPrivateWindow) {
   1.791 +          // We're starting with a single private window. Save the state we
   1.792 +          // actually wanted to restore so that we can do it later in case
   1.793 +          // the user opens another, non-private window.
   1.794 +          this._deferredInitialState = gSessionStartup.state;
   1.795 +
   1.796 +          // Nothing to restore now, notify observers things are complete.
   1.797 +          Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
   1.798 +        } else {
   1.799 +          TelemetryTimestamps.add("sessionRestoreRestoring");
   1.800 +          this._restoreCount = aInitialState.windows ? aInitialState.windows.length : 0;
   1.801 +
   1.802 +          // global data must be restored before restoreWindow is called so that
   1.803 +          // it happens before observers are notified
   1.804 +          this._globalState.setFromState(aInitialState);
   1.805 +
   1.806 +          let overwrite = this._isCmdLineEmpty(aWindow, aInitialState);
   1.807 +          let options = {firstWindow: true, overwriteTabs: overwrite};
   1.808 +          this.restoreWindow(aWindow, aInitialState, options);
   1.809 +        }
   1.810 +      }
   1.811 +      else {
   1.812 +        // Nothing to restore, notify observers things are complete.
   1.813 +        Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
   1.814 +
   1.815 +        // The next delayed save request should execute immediately.
   1.816 +        SessionSaver.clearLastSaveTime();
   1.817 +      }
   1.818 +    }
   1.819 +    // this window was opened by _openWindowWithState
   1.820 +    else if (!this._isWindowLoaded(aWindow)) {
   1.821 +      let state = this._statesToRestore[aWindow.__SS_restoreID];
   1.822 +      let options = {overwriteTabs: true, isFollowUp: state.windows.length == 1};
   1.823 +      this.restoreWindow(aWindow, state, options);
   1.824 +    }
   1.825 +    // The user opened another, non-private window after starting up with
   1.826 +    // a single private one. Let's restore the session we actually wanted to
   1.827 +    // restore at startup.
   1.828 +    else if (this._deferredInitialState && !isPrivateWindow &&
   1.829 +             aWindow.toolbar.visible) {
   1.830 +
   1.831 +      // global data must be restored before restoreWindow is called so that
   1.832 +      // it happens before observers are notified
   1.833 +      this._globalState.setFromState(this._deferredInitialState);
   1.834 +
   1.835 +      this._restoreCount = this._deferredInitialState.windows ?
   1.836 +        this._deferredInitialState.windows.length : 0;
   1.837 +      this.restoreWindow(aWindow, this._deferredInitialState, {firstWindow: true});
   1.838 +      this._deferredInitialState = null;
   1.839 +    }
   1.840 +    else if (this._restoreLastWindow && aWindow.toolbar.visible &&
   1.841 +             this._closedWindows.length && !isPrivateWindow) {
   1.842 +
   1.843 +      // default to the most-recently closed window
   1.844 +      // don't use popup windows
   1.845 +      let closedWindowState = null;
   1.846 +      let closedWindowIndex;
   1.847 +      for (let i = 0; i < this._closedWindows.length; i++) {
   1.848 +        // Take the first non-popup, point our object at it, and break out.
   1.849 +        if (!this._closedWindows[i].isPopup) {
   1.850 +          closedWindowState = this._closedWindows[i];
   1.851 +          closedWindowIndex = i;
   1.852 +          break;
   1.853 +        }
   1.854 +      }
   1.855 +
   1.856 +      if (closedWindowState) {
   1.857 +        let newWindowState;
   1.858 +#ifndef XP_MACOSX
   1.859 +        if (!this._doResumeSession()) {
   1.860 +#endif
   1.861 +          // We want to split the window up into pinned tabs and unpinned tabs.
   1.862 +          // Pinned tabs should be restored. If there are any remaining tabs,
   1.863 +          // they should be added back to _closedWindows.
   1.864 +          // We'll cheat a little bit and reuse _prepDataForDeferredRestore
   1.865 +          // even though it wasn't built exactly for this.
   1.866 +          let [appTabsState, normalTabsState] =
   1.867 +            this._prepDataForDeferredRestore({ windows: [closedWindowState] });
   1.868 +
   1.869 +          // These are our pinned tabs, which we should restore
   1.870 +          if (appTabsState.windows.length) {
   1.871 +            newWindowState = appTabsState.windows[0];
   1.872 +            delete newWindowState.__lastSessionWindowID;
   1.873 +          }
   1.874 +
   1.875 +          // In case there were no unpinned tabs, remove the window from _closedWindows
   1.876 +          if (!normalTabsState.windows.length) {
   1.877 +            this._closedWindows.splice(closedWindowIndex, 1);
   1.878 +          }
   1.879 +          // Or update _closedWindows with the modified state
   1.880 +          else {
   1.881 +            delete normalTabsState.windows[0].__lastSessionWindowID;
   1.882 +            this._closedWindows[closedWindowIndex] = normalTabsState.windows[0];
   1.883 +          }
   1.884 +#ifndef XP_MACOSX
   1.885 +        }
   1.886 +        else {
   1.887 +          // If we're just restoring the window, make sure it gets removed from
   1.888 +          // _closedWindows.
   1.889 +          this._closedWindows.splice(closedWindowIndex, 1);
   1.890 +          newWindowState = closedWindowState;
   1.891 +          delete newWindowState.hidden;
   1.892 +        }
   1.893 +#endif
   1.894 +        if (newWindowState) {
   1.895 +          // Ensure that the window state isn't hidden
   1.896 +          this._restoreCount = 1;
   1.897 +          let state = { windows: [newWindowState] };
   1.898 +          let options = {overwriteTabs: this._isCmdLineEmpty(aWindow, state)};
   1.899 +          this.restoreWindow(aWindow, state, options);
   1.900 +        }
   1.901 +      }
   1.902 +      // we actually restored the session just now.
   1.903 +      this._prefBranch.setBoolPref("sessionstore.resume_session_once", false);
   1.904 +    }
   1.905 +    if (this._restoreLastWindow && aWindow.toolbar.visible) {
   1.906 +      // always reset (if not a popup window)
   1.907 +      // we don't want to restore a window directly after, for example,
   1.908 +      // undoCloseWindow was executed.
   1.909 +      this._restoreLastWindow = false;
   1.910 +    }
   1.911 +
   1.912 +    var tabbrowser = aWindow.gBrowser;
   1.913 +
   1.914 +    // add tab change listeners to all already existing tabs
   1.915 +    for (let i = 0; i < tabbrowser.tabs.length; i++) {
   1.916 +      this.onTabAdd(aWindow, tabbrowser.tabs[i], true);
   1.917 +    }
   1.918 +    // notification of tab add/remove/selection/show/hide
   1.919 +    TAB_EVENTS.forEach(function(aEvent) {
   1.920 +      tabbrowser.tabContainer.addEventListener(aEvent, this, true);
   1.921 +    }, this);
   1.922 +  },
   1.923 +
   1.924 +  /**
   1.925 +   * On window open
   1.926 +   * @param aWindow
   1.927 +   *        Window reference
   1.928 +   */
   1.929 +  onOpen: function ssi_onOpen(aWindow) {
   1.930 +    let onload = () => {
   1.931 +      aWindow.removeEventListener("load", onload);
   1.932 +
   1.933 +      let windowType = aWindow.document.documentElement.getAttribute("windowtype");
   1.934 +
   1.935 +      // Ignore non-browser windows.
   1.936 +      if (windowType != "navigator:browser") {
   1.937 +        return;
   1.938 +      }
   1.939 +
   1.940 +      if (this._sessionInitialized) {
   1.941 +        this.onLoad(aWindow);
   1.942 +        return;
   1.943 +      }
   1.944 +
   1.945 +      // The very first window that is opened creates a promise that is then
   1.946 +      // re-used by all subsequent windows. The promise will be used to tell
   1.947 +      // when we're ready for initialization.
   1.948 +      if (!this._promiseReadyForInitialization) {
   1.949 +        let deferred = Promise.defer();
   1.950 +
   1.951 +        // Wait for the given window's delayed startup to be finished.
   1.952 +        Services.obs.addObserver(function obs(subject, topic) {
   1.953 +          if (aWindow == subject) {
   1.954 +            Services.obs.removeObserver(obs, topic);
   1.955 +            deferred.resolve();
   1.956 +          }
   1.957 +        }, "browser-delayed-startup-finished", false);
   1.958 +
   1.959 +        // We are ready for initialization as soon as the session file has been
   1.960 +        // read from disk and the initial window's delayed startup has finished.
   1.961 +        this._promiseReadyForInitialization =
   1.962 +          Promise.all([deferred.promise, gSessionStartup.onceInitialized]);
   1.963 +      }
   1.964 +
   1.965 +      // We can't call this.onLoad since initialization
   1.966 +      // hasn't completed, so we'll wait until it is done.
   1.967 +      // Even if additional windows are opened and wait
   1.968 +      // for initialization as well, the first opened
   1.969 +      // window should execute first, and this.onLoad
   1.970 +      // will be called with the initialState.
   1.971 +      this._promiseReadyForInitialization.then(() => {
   1.972 +        if (aWindow.closed) {
   1.973 +          return;
   1.974 +        }
   1.975 +
   1.976 +        if (this._sessionInitialized) {
   1.977 +          this.onLoad(aWindow);
   1.978 +        } else {
   1.979 +          let initialState = this.initSession();
   1.980 +          this._sessionInitialized = true;
   1.981 +          this.onLoad(aWindow, initialState);
   1.982 +
   1.983 +          // Let everyone know we're done.
   1.984 +          this._deferredInitialized.resolve();
   1.985 +        }
   1.986 +      }, console.error);
   1.987 +    };
   1.988 +
   1.989 +    aWindow.addEventListener("load", onload);
   1.990 +  },
   1.991 +
   1.992 +  /**
   1.993 +   * On window close...
   1.994 +   * - remove event listeners from tabs
   1.995 +   * - save all window data
   1.996 +   * @param aWindow
   1.997 +   *        Window reference
   1.998 +   */
   1.999 +  onClose: function ssi_onClose(aWindow) {
  1.1000 +    // this window was about to be restored - conserve its original data, if any
  1.1001 +    let isFullyLoaded = this._isWindowLoaded(aWindow);
  1.1002 +    if (!isFullyLoaded) {
  1.1003 +      if (!aWindow.__SSi) {
  1.1004 +        aWindow.__SSi = this._generateWindowID();
  1.1005 +      }
  1.1006 +
  1.1007 +      this._windows[aWindow.__SSi] = this._statesToRestore[aWindow.__SS_restoreID];
  1.1008 +      delete this._statesToRestore[aWindow.__SS_restoreID];
  1.1009 +      delete aWindow.__SS_restoreID;
  1.1010 +    }
  1.1011 +
  1.1012 +    // ignore windows not tracked by SessionStore
  1.1013 +    if (!aWindow.__SSi || !this._windows[aWindow.__SSi]) {
  1.1014 +      return;
  1.1015 +    }
  1.1016 +
  1.1017 +    // notify that the session store will stop tracking this window so that
  1.1018 +    // extensions can store any data about this window in session store before
  1.1019 +    // that's not possible anymore
  1.1020 +    let event = aWindow.document.createEvent("Events");
  1.1021 +    event.initEvent("SSWindowClosing", true, false);
  1.1022 +    aWindow.dispatchEvent(event);
  1.1023 +
  1.1024 +    if (this.windowToFocus && this.windowToFocus == aWindow) {
  1.1025 +      delete this.windowToFocus;
  1.1026 +    }
  1.1027 +
  1.1028 +    var tabbrowser = aWindow.gBrowser;
  1.1029 +
  1.1030 +    TAB_EVENTS.forEach(function(aEvent) {
  1.1031 +      tabbrowser.tabContainer.removeEventListener(aEvent, this, true);
  1.1032 +    }, this);
  1.1033 +
  1.1034 +    let winData = this._windows[aWindow.__SSi];
  1.1035 +
  1.1036 +    // Collect window data only when *not* closed during shutdown.
  1.1037 +    if (this._loadState == STATE_RUNNING) {
  1.1038 +      // Flush all data queued in the content script before the window is gone.
  1.1039 +      TabState.flushWindow(aWindow);
  1.1040 +
  1.1041 +      // update all window data for a last time
  1.1042 +      this._collectWindowData(aWindow);
  1.1043 +
  1.1044 +      if (isFullyLoaded) {
  1.1045 +        winData.title = aWindow.content.document.title || tabbrowser.selectedTab.label;
  1.1046 +        winData.title = this._replaceLoadingTitle(winData.title, tabbrowser,
  1.1047 +                                                  tabbrowser.selectedTab);
  1.1048 +        SessionCookies.update([winData]);
  1.1049 +      }
  1.1050 +
  1.1051 +#ifndef XP_MACOSX
  1.1052 +      // Until we decide otherwise elsewhere, this window is part of a series
  1.1053 +      // of closing windows to quit.
  1.1054 +      winData._shouldRestore = true;
  1.1055 +#endif
  1.1056 +
  1.1057 +      // Store the window's close date to figure out when each individual tab
  1.1058 +      // was closed. This timestamp should allow re-arranging data based on how
  1.1059 +      // recently something was closed.
  1.1060 +      winData.closedAt = Date.now();
  1.1061 +
  1.1062 +      // Save non-private windows if they have at
  1.1063 +      // least one saveable tab or are the last window.
  1.1064 +      if (!winData.isPrivate) {
  1.1065 +        // Remove any open private tabs the window may contain.
  1.1066 +        PrivacyFilter.filterPrivateTabs(winData);
  1.1067 +
  1.1068 +        // Determine whether the window has any tabs worth saving.
  1.1069 +        let hasSaveableTabs = winData.tabs.some(this._shouldSaveTabState);
  1.1070 +
  1.1071 +        // When closing windows one after the other until Firefox quits, we
  1.1072 +        // will move those closed in series back to the "open windows" bucket
  1.1073 +        // before writing to disk. If however there is only a single window
  1.1074 +        // with tabs we deem not worth saving then we might end up with a
  1.1075 +        // random closed or even a pop-up window re-opened. To prevent that
  1.1076 +        // we explicitly allow saving an "empty" window state.
  1.1077 +        let isLastWindow =
  1.1078 +          Object.keys(this._windows).length == 1 &&
  1.1079 +          !this._closedWindows.some(win => win._shouldRestore || false);
  1.1080 +
  1.1081 +        if (hasSaveableTabs || isLastWindow) {
  1.1082 +          // we don't want to save the busy state
  1.1083 +          delete winData.busy;
  1.1084 +
  1.1085 +          this._closedWindows.unshift(winData);
  1.1086 +          this._capClosedWindows();
  1.1087 +        }
  1.1088 +      }
  1.1089 +
  1.1090 +      // clear this window from the list
  1.1091 +      delete this._windows[aWindow.__SSi];
  1.1092 +
  1.1093 +      // save the state without this window to disk
  1.1094 +      this.saveStateDelayed();
  1.1095 +    }
  1.1096 +
  1.1097 +    for (let i = 0; i < tabbrowser.tabs.length; i++) {
  1.1098 +      this.onTabRemove(aWindow, tabbrowser.tabs[i], true);
  1.1099 +    }
  1.1100 +
  1.1101 +    // Cache the window state until it is completely gone.
  1.1102 +    DyingWindowCache.set(aWindow, winData);
  1.1103 +
  1.1104 +    let mm = aWindow.messageManager;
  1.1105 +    MESSAGES.forEach(msg => mm.removeMessageListener(msg, this));
  1.1106 +
  1.1107 +    delete aWindow.__SSi;
  1.1108 +  },
  1.1109 +
  1.1110 +  /**
  1.1111 +   * On quit application requested
  1.1112 +   */
  1.1113 +  onQuitApplicationRequested: function ssi_onQuitApplicationRequested() {
  1.1114 +    // get a current snapshot of all windows
  1.1115 +    this._forEachBrowserWindow(function(aWindow) {
  1.1116 +      // Flush all data queued in the content script to not lose it when
  1.1117 +      // shutting down.
  1.1118 +      TabState.flushWindow(aWindow);
  1.1119 +      this._collectWindowData(aWindow);
  1.1120 +    });
  1.1121 +    // we must cache this because _getMostRecentBrowserWindow will always
  1.1122 +    // return null by the time quit-application occurs
  1.1123 +    var activeWindow = this._getMostRecentBrowserWindow();
  1.1124 +    if (activeWindow)
  1.1125 +      this.activeWindowSSiCache = activeWindow.__SSi || "";
  1.1126 +    DirtyWindows.clear();
  1.1127 +  },
  1.1128 +
  1.1129 +  /**
  1.1130 +   * On quit application granted
  1.1131 +   */
  1.1132 +  onQuitApplicationGranted: function ssi_onQuitApplicationGranted() {
  1.1133 +    // freeze the data at what we've got (ignoring closing windows)
  1.1134 +    this._loadState = STATE_QUITTING;
  1.1135 +  },
  1.1136 +
  1.1137 +  /**
  1.1138 +   * On last browser window close
  1.1139 +   */
  1.1140 +  onLastWindowCloseGranted: function ssi_onLastWindowCloseGranted() {
  1.1141 +    // last browser window is quitting.
  1.1142 +    // remember to restore the last window when another browser window is opened
  1.1143 +    // do not account for pref(resume_session_once) at this point, as it might be
  1.1144 +    // set by another observer getting this notice after us
  1.1145 +    this._restoreLastWindow = true;
  1.1146 +  },
  1.1147 +
  1.1148 +  /**
  1.1149 +   * On quitting application
  1.1150 +   * @param aData
  1.1151 +   *        String type of quitting
  1.1152 +   */
  1.1153 +  onQuitApplication: function ssi_onQuitApplication(aData) {
  1.1154 +    if (aData == "restart") {
  1.1155 +      this._prefBranch.setBoolPref("sessionstore.resume_session_once", true);
  1.1156 +      // The browser:purge-session-history notification fires after the
  1.1157 +      // quit-application notification so unregister the
  1.1158 +      // browser:purge-session-history notification to prevent clearing
  1.1159 +      // session data on disk on a restart.  It is also unnecessary to
  1.1160 +      // perform any other sanitization processing on a restart as the
  1.1161 +      // browser is about to exit anyway.
  1.1162 +      Services.obs.removeObserver(this, "browser:purge-session-history");
  1.1163 +    }
  1.1164 +
  1.1165 +    if (aData != "restart") {
  1.1166 +      // Throw away the previous session on shutdown
  1.1167 +      LastSession.clear();
  1.1168 +    }
  1.1169 +
  1.1170 +    this._loadState = STATE_QUITTING; // just to be sure
  1.1171 +    this._uninit();
  1.1172 +  },
  1.1173 +
  1.1174 +  /**
  1.1175 +   * On purge of session history
  1.1176 +   */
  1.1177 +  onPurgeSessionHistory: function ssi_onPurgeSessionHistory() {
  1.1178 +    SessionFile.wipe();
  1.1179 +    // If the browser is shutting down, simply return after clearing the
  1.1180 +    // session data on disk as this notification fires after the
  1.1181 +    // quit-application notification so the browser is about to exit.
  1.1182 +    if (this._loadState == STATE_QUITTING)
  1.1183 +      return;
  1.1184 +    LastSession.clear();
  1.1185 +    let openWindows = {};
  1.1186 +    this._forEachBrowserWindow(function(aWindow) {
  1.1187 +      Array.forEach(aWindow.gBrowser.tabs, function(aTab) {
  1.1188 +        delete aTab.linkedBrowser.__SS_data;
  1.1189 +        if (aTab.linkedBrowser.__SS_restoreState)
  1.1190 +          this._resetTabRestoringState(aTab);
  1.1191 +      }, this);
  1.1192 +      openWindows[aWindow.__SSi] = true;
  1.1193 +    });
  1.1194 +    // also clear all data about closed tabs and windows
  1.1195 +    for (let ix in this._windows) {
  1.1196 +      if (ix in openWindows) {
  1.1197 +        this._windows[ix]._closedTabs = [];
  1.1198 +      } else {
  1.1199 +        delete this._windows[ix];
  1.1200 +      }
  1.1201 +    }
  1.1202 +    // also clear all data about closed windows
  1.1203 +    this._closedWindows = [];
  1.1204 +    // give the tabbrowsers a chance to clear their histories first
  1.1205 +    var win = this._getMostRecentBrowserWindow();
  1.1206 +    if (win) {
  1.1207 +      win.setTimeout(() => SessionSaver.run(), 0);
  1.1208 +    } else if (this._loadState == STATE_RUNNING) {
  1.1209 +      SessionSaver.run();
  1.1210 +    }
  1.1211 +
  1.1212 +    this._clearRestoringWindows();
  1.1213 +  },
  1.1214 +
  1.1215 +  /**
  1.1216 +   * On purge of domain data
  1.1217 +   * @param aData
  1.1218 +   *        String domain data
  1.1219 +   */
  1.1220 +  onPurgeDomainData: function ssi_onPurgeDomainData(aData) {
  1.1221 +    // does a session history entry contain a url for the given domain?
  1.1222 +    function containsDomain(aEntry) {
  1.1223 +      if (Utils.hasRootDomain(aEntry.url, aData)) {
  1.1224 +        return true;
  1.1225 +      }
  1.1226 +      return aEntry.children && aEntry.children.some(containsDomain, this);
  1.1227 +    }
  1.1228 +    // remove all closed tabs containing a reference to the given domain
  1.1229 +    for (let ix in this._windows) {
  1.1230 +      let closedTabs = this._windows[ix]._closedTabs;
  1.1231 +      for (let i = closedTabs.length - 1; i >= 0; i--) {
  1.1232 +        if (closedTabs[i].state.entries.some(containsDomain, this))
  1.1233 +          closedTabs.splice(i, 1);
  1.1234 +      }
  1.1235 +    }
  1.1236 +    // remove all open & closed tabs containing a reference to the given
  1.1237 +    // domain in closed windows
  1.1238 +    for (let ix = this._closedWindows.length - 1; ix >= 0; ix--) {
  1.1239 +      let closedTabs = this._closedWindows[ix]._closedTabs;
  1.1240 +      let openTabs = this._closedWindows[ix].tabs;
  1.1241 +      let openTabCount = openTabs.length;
  1.1242 +      for (let i = closedTabs.length - 1; i >= 0; i--)
  1.1243 +        if (closedTabs[i].state.entries.some(containsDomain, this))
  1.1244 +          closedTabs.splice(i, 1);
  1.1245 +      for (let j = openTabs.length - 1; j >= 0; j--) {
  1.1246 +        if (openTabs[j].entries.some(containsDomain, this)) {
  1.1247 +          openTabs.splice(j, 1);
  1.1248 +          if (this._closedWindows[ix].selected > j)
  1.1249 +            this._closedWindows[ix].selected--;
  1.1250 +        }
  1.1251 +      }
  1.1252 +      if (openTabs.length == 0) {
  1.1253 +        this._closedWindows.splice(ix, 1);
  1.1254 +      }
  1.1255 +      else if (openTabs.length != openTabCount) {
  1.1256 +        // Adjust the window's title if we removed an open tab
  1.1257 +        let selectedTab = openTabs[this._closedWindows[ix].selected - 1];
  1.1258 +        // some duplication from restoreHistory - make sure we get the correct title
  1.1259 +        let activeIndex = (selectedTab.index || selectedTab.entries.length) - 1;
  1.1260 +        if (activeIndex >= selectedTab.entries.length)
  1.1261 +          activeIndex = selectedTab.entries.length - 1;
  1.1262 +        this._closedWindows[ix].title = selectedTab.entries[activeIndex].title;
  1.1263 +      }
  1.1264 +    }
  1.1265 +
  1.1266 +    if (this._loadState == STATE_RUNNING) {
  1.1267 +      SessionSaver.run();
  1.1268 +    }
  1.1269 +
  1.1270 +    this._clearRestoringWindows();
  1.1271 +  },
  1.1272 +
  1.1273 +  /**
  1.1274 +   * On preference change
  1.1275 +   * @param aData
  1.1276 +   *        String preference changed
  1.1277 +   */
  1.1278 +  onPrefChange: function ssi_onPrefChange(aData) {
  1.1279 +    switch (aData) {
  1.1280 +      // if the user decreases the max number of closed tabs they want
  1.1281 +      // preserved update our internal states to match that max
  1.1282 +      case "sessionstore.max_tabs_undo":
  1.1283 +        this._max_tabs_undo = this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
  1.1284 +        for (let ix in this._windows) {
  1.1285 +          this._windows[ix]._closedTabs.splice(this._max_tabs_undo, this._windows[ix]._closedTabs.length);
  1.1286 +        }
  1.1287 +        break;
  1.1288 +      case "sessionstore.max_windows_undo":
  1.1289 +        this._max_windows_undo = this._prefBranch.getIntPref("sessionstore.max_windows_undo");
  1.1290 +        this._capClosedWindows();
  1.1291 +        break;
  1.1292 +    }
  1.1293 +  },
  1.1294 +
  1.1295 +  /**
  1.1296 +   * set up listeners for a new tab
  1.1297 +   * @param aWindow
  1.1298 +   *        Window reference
  1.1299 +   * @param aTab
  1.1300 +   *        Tab reference
  1.1301 +   * @param aNoNotification
  1.1302 +   *        bool Do not save state if we're updating an existing tab
  1.1303 +   */
  1.1304 +  onTabAdd: function ssi_onTabAdd(aWindow, aTab, aNoNotification) {
  1.1305 +    if (!aNoNotification) {
  1.1306 +      this.saveStateDelayed(aWindow);
  1.1307 +    }
  1.1308 +  },
  1.1309 +
  1.1310 +  /**
  1.1311 +   * remove listeners for a tab
  1.1312 +   * @param aWindow
  1.1313 +   *        Window reference
  1.1314 +   * @param aTab
  1.1315 +   *        Tab reference
  1.1316 +   * @param aNoNotification
  1.1317 +   *        bool Do not save state if we're updating an existing tab
  1.1318 +   */
  1.1319 +  onTabRemove: function ssi_onTabRemove(aWindow, aTab, aNoNotification) {
  1.1320 +    let browser = aTab.linkedBrowser;
  1.1321 +    delete browser.__SS_data;
  1.1322 +
  1.1323 +    // If this tab was in the middle of restoring or still needs to be restored,
  1.1324 +    // we need to reset that state. If the tab was restoring, we will attempt to
  1.1325 +    // restore the next tab.
  1.1326 +    let previousState = browser.__SS_restoreState;
  1.1327 +    if (previousState) {
  1.1328 +      this._resetTabRestoringState(aTab);
  1.1329 +      if (previousState == TAB_STATE_RESTORING)
  1.1330 +        this.restoreNextTab();
  1.1331 +    }
  1.1332 +
  1.1333 +    if (!aNoNotification) {
  1.1334 +      this.saveStateDelayed(aWindow);
  1.1335 +    }
  1.1336 +  },
  1.1337 +
  1.1338 +  /**
  1.1339 +   * When a tab closes, collect its properties
  1.1340 +   * @param aWindow
  1.1341 +   *        Window reference
  1.1342 +   * @param aTab
  1.1343 +   *        Tab reference
  1.1344 +   */
  1.1345 +  onTabClose: function ssi_onTabClose(aWindow, aTab) {
  1.1346 +    // notify the tabbrowser that the tab state will be retrieved for the last time
  1.1347 +    // (so that extension authors can easily set data on soon-to-be-closed tabs)
  1.1348 +    var event = aWindow.document.createEvent("Events");
  1.1349 +    event.initEvent("SSTabClosing", true, false);
  1.1350 +    aTab.dispatchEvent(event);
  1.1351 +
  1.1352 +    // don't update our internal state if we don't have to
  1.1353 +    if (this._max_tabs_undo == 0) {
  1.1354 +      return;
  1.1355 +    }
  1.1356 +
  1.1357 +    // Flush all data queued in the content script before the tab is gone.
  1.1358 +    TabState.flush(aTab.linkedBrowser);
  1.1359 +
  1.1360 +    // Get the latest data for this tab (generally, from the cache)
  1.1361 +    let tabState = TabState.collect(aTab);
  1.1362 +
  1.1363 +    // Don't save private tabs
  1.1364 +    let isPrivateWindow = PrivateBrowsingUtils.isWindowPrivate(aWindow);
  1.1365 +    if (!isPrivateWindow && tabState.isPrivate) {
  1.1366 +      return;
  1.1367 +    }
  1.1368 +
  1.1369 +    // store closed-tab data for undo
  1.1370 +    if (this._shouldSaveTabState(tabState)) {
  1.1371 +      let tabTitle = aTab.label;
  1.1372 +      let tabbrowser = aWindow.gBrowser;
  1.1373 +      tabTitle = this._replaceLoadingTitle(tabTitle, tabbrowser, aTab);
  1.1374 +
  1.1375 +      this._windows[aWindow.__SSi]._closedTabs.unshift({
  1.1376 +        state: tabState,
  1.1377 +        title: tabTitle,
  1.1378 +        image: tabbrowser.getIcon(aTab),
  1.1379 +        pos: aTab._tPos,
  1.1380 +        closedAt: Date.now()
  1.1381 +      });
  1.1382 +      var length = this._windows[aWindow.__SSi]._closedTabs.length;
  1.1383 +      if (length > this._max_tabs_undo)
  1.1384 +        this._windows[aWindow.__SSi]._closedTabs.splice(this._max_tabs_undo, length - this._max_tabs_undo);
  1.1385 +    }
  1.1386 +  },
  1.1387 +
  1.1388 +  /**
  1.1389 +   * When a tab is selected, save session data
  1.1390 +   * @param aWindow
  1.1391 +   *        Window reference
  1.1392 +   */
  1.1393 +  onTabSelect: function ssi_onTabSelect(aWindow) {
  1.1394 +    if (this._loadState == STATE_RUNNING) {
  1.1395 +      this._windows[aWindow.__SSi].selected = aWindow.gBrowser.tabContainer.selectedIndex;
  1.1396 +
  1.1397 +      let tab = aWindow.gBrowser.selectedTab;
  1.1398 +      // If __SS_restoreState is still on the browser and it is
  1.1399 +      // TAB_STATE_NEEDS_RESTORE, then then we haven't restored
  1.1400 +      // this tab yet. Explicitly call restoreTabContent to kick off the restore.
  1.1401 +      if (tab.linkedBrowser.__SS_restoreState &&
  1.1402 +          tab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
  1.1403 +        this.restoreTabContent(tab);
  1.1404 +    }
  1.1405 +  },
  1.1406 +
  1.1407 +  onTabShow: function ssi_onTabShow(aWindow, aTab) {
  1.1408 +    // If the tab hasn't been restored yet, move it into the right bucket
  1.1409 +    if (aTab.linkedBrowser.__SS_restoreState &&
  1.1410 +        aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
  1.1411 +      TabRestoreQueue.hiddenToVisible(aTab);
  1.1412 +
  1.1413 +      // let's kick off tab restoration again to ensure this tab gets restored
  1.1414 +      // with "restore_hidden_tabs" == false (now that it has become visible)
  1.1415 +      this.restoreNextTab();
  1.1416 +    }
  1.1417 +
  1.1418 +    // Default delay of 2 seconds gives enough time to catch multiple TabShow
  1.1419 +    // events due to changing groups in Panorama.
  1.1420 +    this.saveStateDelayed(aWindow);
  1.1421 +  },
  1.1422 +
  1.1423 +  onTabHide: function ssi_onTabHide(aWindow, aTab) {
  1.1424 +    // If the tab hasn't been restored yet, move it into the right bucket
  1.1425 +    if (aTab.linkedBrowser.__SS_restoreState &&
  1.1426 +        aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
  1.1427 +      TabRestoreQueue.visibleToHidden(aTab);
  1.1428 +    }
  1.1429 +
  1.1430 +    // Default delay of 2 seconds gives enough time to catch multiple TabHide
  1.1431 +    // events due to changing groups in Panorama.
  1.1432 +    this.saveStateDelayed(aWindow);
  1.1433 +  },
  1.1434 +
  1.1435 +  onGatherTelemetry: function() {
  1.1436 +    // On the first gather-telemetry notification of the session,
  1.1437 +    // gather telemetry data.
  1.1438 +    Services.obs.removeObserver(this, "gather-telemetry");
  1.1439 +    let stateString = SessionStore.getBrowserState();
  1.1440 +    return SessionFile.gatherTelemetry(stateString);
  1.1441 +  },
  1.1442 +
  1.1443 +  /* ........ nsISessionStore API .............. */
  1.1444 +
  1.1445 +  getBrowserState: function ssi_getBrowserState() {
  1.1446 +    let state = this.getCurrentState();
  1.1447 +
  1.1448 +    // Don't include the last session state in getBrowserState().
  1.1449 +    delete state.lastSessionState;
  1.1450 +
  1.1451 +    // Don't include any deferred initial state.
  1.1452 +    delete state.deferredInitialState;
  1.1453 +
  1.1454 +    return this._toJSONString(state);
  1.1455 +  },
  1.1456 +
  1.1457 +  setBrowserState: function ssi_setBrowserState(aState) {
  1.1458 +    this._handleClosedWindows();
  1.1459 +
  1.1460 +    try {
  1.1461 +      var state = JSON.parse(aState);
  1.1462 +    }
  1.1463 +    catch (ex) { /* invalid state object - don't restore anything */ }
  1.1464 +    if (!state) {
  1.1465 +      throw Components.Exception("Invalid state string: not JSON", Cr.NS_ERROR_INVALID_ARG);
  1.1466 +    }
  1.1467 +    if (!state.windows) {
  1.1468 +      throw Components.Exception("No windows", Cr.NS_ERROR_INVALID_ARG);
  1.1469 +    }
  1.1470 +
  1.1471 +    this._browserSetState = true;
  1.1472 +
  1.1473 +    // Make sure the priority queue is emptied out
  1.1474 +    this._resetRestoringState();
  1.1475 +
  1.1476 +    var window = this._getMostRecentBrowserWindow();
  1.1477 +    if (!window) {
  1.1478 +      this._restoreCount = 1;
  1.1479 +      this._openWindowWithState(state);
  1.1480 +      return;
  1.1481 +    }
  1.1482 +
  1.1483 +    // close all other browser windows
  1.1484 +    this._forEachBrowserWindow(function(aWindow) {
  1.1485 +      if (aWindow != window) {
  1.1486 +        aWindow.close();
  1.1487 +        this.onClose(aWindow);
  1.1488 +      }
  1.1489 +    });
  1.1490 +
  1.1491 +    // make sure closed window data isn't kept
  1.1492 +    this._closedWindows = [];
  1.1493 +
  1.1494 +    // determine how many windows are meant to be restored
  1.1495 +    this._restoreCount = state.windows ? state.windows.length : 0;
  1.1496 +
  1.1497 +    // global data must be restored before restoreWindow is called so that
  1.1498 +    // it happens before observers are notified
  1.1499 +    this._globalState.setFromState(state);
  1.1500 +
  1.1501 +    // restore to the given state
  1.1502 +    this.restoreWindow(window, state, {overwriteTabs: true});
  1.1503 +  },
  1.1504 +
  1.1505 +  getWindowState: function ssi_getWindowState(aWindow) {
  1.1506 +    if ("__SSi" in aWindow) {
  1.1507 +      return this._toJSONString(this._getWindowState(aWindow));
  1.1508 +    }
  1.1509 +
  1.1510 +    if (DyingWindowCache.has(aWindow)) {
  1.1511 +      let data = DyingWindowCache.get(aWindow);
  1.1512 +      return this._toJSONString({ windows: [data] });
  1.1513 +    }
  1.1514 +
  1.1515 +    throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
  1.1516 +  },
  1.1517 +
  1.1518 +  setWindowState: function ssi_setWindowState(aWindow, aState, aOverwrite) {
  1.1519 +    if (!aWindow.__SSi) {
  1.1520 +      throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
  1.1521 +    }
  1.1522 +
  1.1523 +    this.restoreWindow(aWindow, aState, {overwriteTabs: aOverwrite});
  1.1524 +  },
  1.1525 +
  1.1526 +  getTabState: function ssi_getTabState(aTab) {
  1.1527 +    if (!aTab.ownerDocument) {
  1.1528 +      throw Components.Exception("Invalid tab object: no ownerDocument", Cr.NS_ERROR_INVALID_ARG);
  1.1529 +    }
  1.1530 +    if (!aTab.ownerDocument.defaultView.__SSi) {
  1.1531 +      throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
  1.1532 +    }
  1.1533 +
  1.1534 +    let tabState = TabState.collect(aTab);
  1.1535 +
  1.1536 +    return this._toJSONString(tabState);
  1.1537 +  },
  1.1538 +
  1.1539 +  setTabState: function ssi_setTabState(aTab, aState) {
  1.1540 +    // Remove the tab state from the cache.
  1.1541 +    // Note that we cannot simply replace the contents of the cache
  1.1542 +    // as |aState| can be an incomplete state that will be completed
  1.1543 +    // by |restoreTabs|.
  1.1544 +    let tabState = JSON.parse(aState);
  1.1545 +    if (!tabState) {
  1.1546 +      throw Components.Exception("Invalid state string: not JSON", Cr.NS_ERROR_INVALID_ARG);
  1.1547 +    }
  1.1548 +    if (typeof tabState != "object") {
  1.1549 +      throw Components.Exception("Not an object", Cr.NS_ERROR_INVALID_ARG);
  1.1550 +    }
  1.1551 +    if (!("entries" in tabState)) {
  1.1552 +      throw Components.Exception("Invalid state object: no entries", Cr.NS_ERROR_INVALID_ARG);
  1.1553 +    }
  1.1554 +    if (!aTab.ownerDocument) {
  1.1555 +      throw Components.Exception("Invalid tab object: no ownerDocument", Cr.NS_ERROR_INVALID_ARG);
  1.1556 +    }
  1.1557 +
  1.1558 +    let window = aTab.ownerDocument.defaultView;
  1.1559 +    if (!("__SSi" in window)) {
  1.1560 +      throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
  1.1561 +    }
  1.1562 +
  1.1563 +    if (aTab.linkedBrowser.__SS_restoreState) {
  1.1564 +      this._resetTabRestoringState(aTab);
  1.1565 +    }
  1.1566 +
  1.1567 +    this._setWindowStateBusy(window);
  1.1568 +    this.restoreTabs(window, [aTab], [tabState], 0);
  1.1569 +  },
  1.1570 +
  1.1571 +  duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta = 0) {
  1.1572 +    if (!aTab.ownerDocument) {
  1.1573 +      throw Components.Exception("Invalid tab object: no ownerDocument", Cr.NS_ERROR_INVALID_ARG);
  1.1574 +    }
  1.1575 +    if (!aTab.ownerDocument.defaultView.__SSi) {
  1.1576 +      throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
  1.1577 +    }
  1.1578 +    if (!aWindow.getBrowser) {
  1.1579 +      throw Components.Exception("Invalid window object: no getBrowser", Cr.NS_ERROR_INVALID_ARG);
  1.1580 +    }
  1.1581 +
  1.1582 +    // Flush all data queued in the content script because we will need that
  1.1583 +    // state to properly duplicate the given tab.
  1.1584 +    TabState.flush(aTab.linkedBrowser);
  1.1585 +
  1.1586 +    // Duplicate the tab state
  1.1587 +    let tabState = TabState.clone(aTab);
  1.1588 +
  1.1589 +    tabState.index += aDelta;
  1.1590 +    tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
  1.1591 +    tabState.pinned = false;
  1.1592 +
  1.1593 +    this._setWindowStateBusy(aWindow);
  1.1594 +    let newTab = aTab == aWindow.gBrowser.selectedTab ?
  1.1595 +      aWindow.gBrowser.addTab(null, {relatedToCurrent: true, ownerTab: aTab}) :
  1.1596 +      aWindow.gBrowser.addTab();
  1.1597 +
  1.1598 +    this.restoreTabs(aWindow, [newTab], [tabState], 0,
  1.1599 +                     true /* Load this tab right away. */);
  1.1600 +
  1.1601 +    return newTab;
  1.1602 +  },
  1.1603 +
  1.1604 +  getClosedTabCount: function ssi_getClosedTabCount(aWindow) {
  1.1605 +    if ("__SSi" in aWindow) {
  1.1606 +      return this._windows[aWindow.__SSi]._closedTabs.length;
  1.1607 +    }
  1.1608 +
  1.1609 +    if (!DyingWindowCache.has(aWindow)) {
  1.1610 +      throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
  1.1611 +    }
  1.1612 +
  1.1613 +    return DyingWindowCache.get(aWindow)._closedTabs.length;
  1.1614 +  },
  1.1615 +
  1.1616 +  getClosedTabData: function ssi_getClosedTabDataAt(aWindow) {
  1.1617 +    if ("__SSi" in aWindow) {
  1.1618 +      return this._toJSONString(this._windows[aWindow.__SSi]._closedTabs);
  1.1619 +    }
  1.1620 +
  1.1621 +    if (!DyingWindowCache.has(aWindow)) {
  1.1622 +      throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
  1.1623 +    }
  1.1624 +
  1.1625 +    let data = DyingWindowCache.get(aWindow);
  1.1626 +    return this._toJSONString(data._closedTabs);
  1.1627 +  },
  1.1628 +
  1.1629 +  undoCloseTab: function ssi_undoCloseTab(aWindow, aIndex) {
  1.1630 +    if (!aWindow.__SSi) {
  1.1631 +      throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
  1.1632 +    }
  1.1633 +
  1.1634 +    var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
  1.1635 +
  1.1636 +    // default to the most-recently closed tab
  1.1637 +    aIndex = aIndex || 0;
  1.1638 +    if (!(aIndex in closedTabs)) {
  1.1639 +      throw Components.Exception("Invalid index: not in the closed tabs", Cr.NS_ERROR_INVALID_ARG);
  1.1640 +    }
  1.1641 +
  1.1642 +    // fetch the data of closed tab, while removing it from the array
  1.1643 +    let closedTab = closedTabs.splice(aIndex, 1).shift();
  1.1644 +    let closedTabState = closedTab.state;
  1.1645 +
  1.1646 +    this._setWindowStateBusy(aWindow);
  1.1647 +    // create a new tab
  1.1648 +    let tabbrowser = aWindow.gBrowser;
  1.1649 +    let tab = tabbrowser.addTab();
  1.1650 +
  1.1651 +    // restore tab content
  1.1652 +    this.restoreTabs(aWindow, [tab], [closedTabState], 1);
  1.1653 +
  1.1654 +    // restore the tab's position
  1.1655 +    tabbrowser.moveTabTo(tab, closedTab.pos);
  1.1656 +
  1.1657 +    // focus the tab's content area (bug 342432)
  1.1658 +    tab.linkedBrowser.focus();
  1.1659 +
  1.1660 +    return tab;
  1.1661 +  },
  1.1662 +
  1.1663 +  forgetClosedTab: function ssi_forgetClosedTab(aWindow, aIndex) {
  1.1664 +    if (!aWindow.__SSi) {
  1.1665 +      throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
  1.1666 +    }
  1.1667 +
  1.1668 +    var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
  1.1669 +
  1.1670 +    // default to the most-recently closed tab
  1.1671 +    aIndex = aIndex || 0;
  1.1672 +    if (!(aIndex in closedTabs)) {
  1.1673 +      throw Components.Exception("Invalid index: not in the closed tabs", Cr.NS_ERROR_INVALID_ARG);
  1.1674 +    }
  1.1675 +
  1.1676 +    // remove closed tab from the array
  1.1677 +    closedTabs.splice(aIndex, 1);
  1.1678 +  },
  1.1679 +
  1.1680 +  getClosedWindowCount: function ssi_getClosedWindowCount() {
  1.1681 +    return this._closedWindows.length;
  1.1682 +  },
  1.1683 +
  1.1684 +  getClosedWindowData: function ssi_getClosedWindowData() {
  1.1685 +    return this._toJSONString(this._closedWindows);
  1.1686 +  },
  1.1687 +
  1.1688 +  undoCloseWindow: function ssi_undoCloseWindow(aIndex) {
  1.1689 +    if (!(aIndex in this._closedWindows)) {
  1.1690 +      throw Components.Exception("Invalid index: not in the closed windows", Cr.NS_ERROR_INVALID_ARG);
  1.1691 +    }
  1.1692 +
  1.1693 +    // reopen the window
  1.1694 +    let state = { windows: this._closedWindows.splice(aIndex, 1) };
  1.1695 +    let window = this._openWindowWithState(state);
  1.1696 +    this.windowToFocus = window;
  1.1697 +    return window;
  1.1698 +  },
  1.1699 +
  1.1700 +  forgetClosedWindow: function ssi_forgetClosedWindow(aIndex) {
  1.1701 +    // default to the most-recently closed window
  1.1702 +    aIndex = aIndex || 0;
  1.1703 +    if (!(aIndex in this._closedWindows)) {
  1.1704 +      throw Components.Exception("Invalid index: not in the closed windows", Cr.NS_ERROR_INVALID_ARG);
  1.1705 +    }
  1.1706 +
  1.1707 +    // remove closed window from the array
  1.1708 +    this._closedWindows.splice(aIndex, 1);
  1.1709 +  },
  1.1710 +
  1.1711 +  getWindowValue: function ssi_getWindowValue(aWindow, aKey) {
  1.1712 +    if ("__SSi" in aWindow) {
  1.1713 +      var data = this._windows[aWindow.__SSi].extData || {};
  1.1714 +      return data[aKey] || "";
  1.1715 +    }
  1.1716 +
  1.1717 +    if (DyingWindowCache.has(aWindow)) {
  1.1718 +      let data = DyingWindowCache.get(aWindow).extData || {};
  1.1719 +      return data[aKey] || "";
  1.1720 +    }
  1.1721 +
  1.1722 +    throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
  1.1723 +  },
  1.1724 +
  1.1725 +  setWindowValue: function ssi_setWindowValue(aWindow, aKey, aStringValue) {
  1.1726 +    if (typeof aStringValue != "string") {
  1.1727 +      throw new TypeError("setWindowValue only accepts string values");
  1.1728 +    }
  1.1729 +
  1.1730 +    if (!("__SSi" in aWindow)) {
  1.1731 +      throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
  1.1732 +    }
  1.1733 +    if (!this._windows[aWindow.__SSi].extData) {
  1.1734 +      this._windows[aWindow.__SSi].extData = {};
  1.1735 +    }
  1.1736 +    this._windows[aWindow.__SSi].extData[aKey] = aStringValue;
  1.1737 +    this.saveStateDelayed(aWindow);
  1.1738 +  },
  1.1739 +
  1.1740 +  deleteWindowValue: function ssi_deleteWindowValue(aWindow, aKey) {
  1.1741 +    if (aWindow.__SSi && this._windows[aWindow.__SSi].extData &&
  1.1742 +        this._windows[aWindow.__SSi].extData[aKey])
  1.1743 +      delete this._windows[aWindow.__SSi].extData[aKey];
  1.1744 +    this.saveStateDelayed(aWindow);
  1.1745 +  },
  1.1746 +
  1.1747 +  getTabValue: function ssi_getTabValue(aTab, aKey) {
  1.1748 +    let data = {};
  1.1749 +    if (aTab.__SS_extdata) {
  1.1750 +      data = aTab.__SS_extdata;
  1.1751 +    }
  1.1752 +    else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) {
  1.1753 +      // If the tab hasn't been fully restored, get the data from the to-be-restored data
  1.1754 +      data = aTab.linkedBrowser.__SS_data.extData;
  1.1755 +    }
  1.1756 +    return data[aKey] || "";
  1.1757 +  },
  1.1758 +
  1.1759 +  setTabValue: function ssi_setTabValue(aTab, aKey, aStringValue) {
  1.1760 +    if (typeof aStringValue != "string") {
  1.1761 +      throw new TypeError("setTabValue only accepts string values");
  1.1762 +    }
  1.1763 +
  1.1764 +    // If the tab hasn't been restored, then set the data there, otherwise we
  1.1765 +    // could lose newly added data.
  1.1766 +    let saveTo;
  1.1767 +    if (aTab.__SS_extdata) {
  1.1768 +      saveTo = aTab.__SS_extdata;
  1.1769 +    }
  1.1770 +    else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) {
  1.1771 +      saveTo = aTab.linkedBrowser.__SS_data.extData;
  1.1772 +    }
  1.1773 +    else {
  1.1774 +      aTab.__SS_extdata = {};
  1.1775 +      saveTo = aTab.__SS_extdata;
  1.1776 +    }
  1.1777 +
  1.1778 +    saveTo[aKey] = aStringValue;
  1.1779 +    this.saveStateDelayed(aTab.ownerDocument.defaultView);
  1.1780 +  },
  1.1781 +
  1.1782 +  deleteTabValue: function ssi_deleteTabValue(aTab, aKey) {
  1.1783 +    // We want to make sure that if data is accessed early, we attempt to delete
  1.1784 +    // that data from __SS_data as well. Otherwise we'll throw in cases where
  1.1785 +    // data can be set or read.
  1.1786 +    let deleteFrom;
  1.1787 +    if (aTab.__SS_extdata) {
  1.1788 +      deleteFrom = aTab.__SS_extdata;
  1.1789 +    }
  1.1790 +    else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) {
  1.1791 +      deleteFrom = aTab.linkedBrowser.__SS_data.extData;
  1.1792 +    }
  1.1793 +
  1.1794 +    if (deleteFrom && aKey in deleteFrom) {
  1.1795 +      delete deleteFrom[aKey];
  1.1796 +      this.saveStateDelayed(aTab.ownerDocument.defaultView);
  1.1797 +    }
  1.1798 +  },
  1.1799 +
  1.1800 +  getGlobalValue: function ssi_getGlobalValue(aKey) {
  1.1801 +    return this._globalState.get(aKey);
  1.1802 +  },
  1.1803 +
  1.1804 +  setGlobalValue: function ssi_setGlobalValue(aKey, aStringValue) {
  1.1805 +    if (typeof aStringValue != "string") {
  1.1806 +      throw new TypeError("setGlobalValue only accepts string values");
  1.1807 +    }
  1.1808 +
  1.1809 +    this._globalState.set(aKey, aStringValue);
  1.1810 +    this.saveStateDelayed();
  1.1811 +  },
  1.1812 +
  1.1813 +  deleteGlobalValue: function ssi_deleteGlobalValue(aKey) {
  1.1814 +    this._globalState.delete(aKey);
  1.1815 +    this.saveStateDelayed();
  1.1816 +  },
  1.1817 +
  1.1818 +  persistTabAttribute: function ssi_persistTabAttribute(aName) {
  1.1819 +    if (TabAttributes.persist(aName)) {
  1.1820 +      this.saveStateDelayed();
  1.1821 +    }
  1.1822 +  },
  1.1823 +
  1.1824 +  /**
  1.1825 +   * Restores the session state stored in LastSession. This will attempt
  1.1826 +   * to merge data into the current session. If a window was opened at startup
  1.1827 +   * with pinned tab(s), then the remaining data from the previous session for
  1.1828 +   * that window will be opened into that winddow. Otherwise new windows will
  1.1829 +   * be opened.
  1.1830 +   */
  1.1831 +  restoreLastSession: function ssi_restoreLastSession() {
  1.1832 +    // Use the public getter since it also checks PB mode
  1.1833 +    if (!this.canRestoreLastSession) {
  1.1834 +      throw Components.Exception("Last session can not be restored");
  1.1835 +    }
  1.1836 +
  1.1837 +    // First collect each window with its id...
  1.1838 +    let windows = {};
  1.1839 +    this._forEachBrowserWindow(function(aWindow) {
  1.1840 +      if (aWindow.__SS_lastSessionWindowID)
  1.1841 +        windows[aWindow.__SS_lastSessionWindowID] = aWindow;
  1.1842 +    });
  1.1843 +
  1.1844 +    let lastSessionState = LastSession.getState();
  1.1845 +
  1.1846 +    // This shouldn't ever be the case...
  1.1847 +    if (!lastSessionState.windows.length) {
  1.1848 +      throw Components.Exception("lastSessionState has no windows", Cr.NS_ERROR_UNEXPECTED);
  1.1849 +    }
  1.1850 +
  1.1851 +    // We're technically doing a restore, so set things up so we send the
  1.1852 +    // notification when we're done. We want to send "sessionstore-browser-state-restored".
  1.1853 +    this._restoreCount = lastSessionState.windows.length;
  1.1854 +    this._browserSetState = true;
  1.1855 +
  1.1856 +    // We want to re-use the last opened window instead of opening a new one in
  1.1857 +    // the case where it's "empty" and not associated with a window in the session.
  1.1858 +    // We will do more processing via _prepWindowToRestoreInto if we need to use
  1.1859 +    // the lastWindow.
  1.1860 +    let lastWindow = this._getMostRecentBrowserWindow();
  1.1861 +    let canUseLastWindow = lastWindow &&
  1.1862 +                           !lastWindow.__SS_lastSessionWindowID;
  1.1863 +
  1.1864 +    // global data must be restored before restoreWindow is called so that
  1.1865 +    // it happens before observers are notified
  1.1866 +    this._globalState.setFromState(lastSessionState);
  1.1867 +
  1.1868 +    // Restore into windows or open new ones as needed.
  1.1869 +    for (let i = 0; i < lastSessionState.windows.length; i++) {
  1.1870 +      let winState = lastSessionState.windows[i];
  1.1871 +      let lastSessionWindowID = winState.__lastSessionWindowID;
  1.1872 +      // delete lastSessionWindowID so we don't add that to the window again
  1.1873 +      delete winState.__lastSessionWindowID;
  1.1874 +
  1.1875 +      // See if we can use an open window. First try one that is associated with
  1.1876 +      // the state we're trying to restore and then fallback to the last selected
  1.1877 +      // window.
  1.1878 +      let windowToUse = windows[lastSessionWindowID];
  1.1879 +      if (!windowToUse && canUseLastWindow) {
  1.1880 +        windowToUse = lastWindow;
  1.1881 +        canUseLastWindow = false;
  1.1882 +      }
  1.1883 +
  1.1884 +      let [canUseWindow, canOverwriteTabs] = this._prepWindowToRestoreInto(windowToUse);
  1.1885 +
  1.1886 +      // If there's a window already open that we can restore into, use that
  1.1887 +      if (canUseWindow) {
  1.1888 +        // Since we're not overwriting existing tabs, we want to merge _closedTabs,
  1.1889 +        // putting existing ones first. Then make sure we're respecting the max pref.
  1.1890 +        if (winState._closedTabs && winState._closedTabs.length) {
  1.1891 +          let curWinState = this._windows[windowToUse.__SSi];
  1.1892 +          curWinState._closedTabs = curWinState._closedTabs.concat(winState._closedTabs);
  1.1893 +          curWinState._closedTabs.splice(this._max_tabs_undo, curWinState._closedTabs.length);
  1.1894 +        }
  1.1895 +
  1.1896 +        // Restore into that window - pretend it's a followup since we'll already
  1.1897 +        // have a focused window.
  1.1898 +        //XXXzpao This is going to merge extData together (taking what was in
  1.1899 +        //        winState over what is in the window already. The hack we have
  1.1900 +        //        in _preWindowToRestoreInto will prevent most (all?) Panorama
  1.1901 +        //        weirdness but we will still merge other extData.
  1.1902 +        //        Bug 588217 should make this go away by merging the group data.
  1.1903 +        let options = {overwriteTabs: canOverwriteTabs, isFollowUp: true};
  1.1904 +        this.restoreWindow(windowToUse, { windows: [winState] }, options);
  1.1905 +      }
  1.1906 +      else {
  1.1907 +        this._openWindowWithState({ windows: [winState] });
  1.1908 +      }
  1.1909 +    }
  1.1910 +
  1.1911 +    // Merge closed windows from this session with ones from last session
  1.1912 +    if (lastSessionState._closedWindows) {
  1.1913 +      this._closedWindows = this._closedWindows.concat(lastSessionState._closedWindows);
  1.1914 +      this._capClosedWindows();
  1.1915 +    }
  1.1916 +
  1.1917 +    if (lastSessionState.scratchpads) {
  1.1918 +      ScratchpadManager.restoreSession(lastSessionState.scratchpads);
  1.1919 +    }
  1.1920 +
  1.1921 +    // Set data that persists between sessions
  1.1922 +    this._recentCrashes = lastSessionState.session &&
  1.1923 +                          lastSessionState.session.recentCrashes || 0;
  1.1924 +
  1.1925 +    // Update the session start time using the restored session state.
  1.1926 +    this._updateSessionStartTime(lastSessionState);
  1.1927 +
  1.1928 +    LastSession.clear();
  1.1929 +  },
  1.1930 +
  1.1931 +  /**
  1.1932 +   * See if aWindow is usable for use when restoring a previous session via
  1.1933 +   * restoreLastSession. If usable, prepare it for use.
  1.1934 +   *
  1.1935 +   * @param aWindow
  1.1936 +   *        the window to inspect & prepare
  1.1937 +   * @returns [canUseWindow, canOverwriteTabs]
  1.1938 +   *          canUseWindow: can the window be used to restore into
  1.1939 +   *          canOverwriteTabs: all of the current tabs are home pages and we
  1.1940 +   *                            can overwrite them
  1.1941 +   */
  1.1942 +  _prepWindowToRestoreInto: function ssi_prepWindowToRestoreInto(aWindow) {
  1.1943 +    if (!aWindow)
  1.1944 +      return [false, false];
  1.1945 +
  1.1946 +    // We might be able to overwrite the existing tabs instead of just adding
  1.1947 +    // the previous session's tabs to the end. This will be set if possible.
  1.1948 +    let canOverwriteTabs = false;
  1.1949 +
  1.1950 +    // Step 1 of processing:
  1.1951 +    // Inspect extData for Panorama identifiers. If found, then we want to
  1.1952 +    // inspect further. If there is a single group, then we can use this
  1.1953 +    // window. If there are multiple groups then we won't use this window.
  1.1954 +    let groupsData = this.getWindowValue(aWindow, "tabview-groups");
  1.1955 +    if (groupsData) {
  1.1956 +      groupsData = JSON.parse(groupsData);
  1.1957 +
  1.1958 +      // If there are multiple groups, we don't want to use this window.
  1.1959 +      if (groupsData.totalNumber > 1)
  1.1960 +        return [false, false];
  1.1961 +    }
  1.1962 +
  1.1963 +    // Step 2 of processing:
  1.1964 +    // If we're still here, then the window is usable. Look at the open tabs in
  1.1965 +    // comparison to home pages. If all the tabs are home pages then we'll end
  1.1966 +    // up overwriting all of them. Otherwise we'll just close the tabs that
  1.1967 +    // match home pages. Tabs with the about:blank URI will always be
  1.1968 +    // overwritten.
  1.1969 +    let homePages = ["about:blank"];
  1.1970 +    let removableTabs = [];
  1.1971 +    let tabbrowser = aWindow.gBrowser;
  1.1972 +    let normalTabsLen = tabbrowser.tabs.length - tabbrowser._numPinnedTabs;
  1.1973 +    let startupPref = this._prefBranch.getIntPref("startup.page");
  1.1974 +    if (startupPref == 1)
  1.1975 +      homePages = homePages.concat(aWindow.gHomeButton.getHomePage().split("|"));
  1.1976 +
  1.1977 +    for (let i = tabbrowser._numPinnedTabs; i < tabbrowser.tabs.length; i++) {
  1.1978 +      let tab = tabbrowser.tabs[i];
  1.1979 +      if (homePages.indexOf(tab.linkedBrowser.currentURI.spec) != -1) {
  1.1980 +        removableTabs.push(tab);
  1.1981 +      }
  1.1982 +    }
  1.1983 +
  1.1984 +    if (tabbrowser.tabs.length == removableTabs.length) {
  1.1985 +      canOverwriteTabs = true;
  1.1986 +    }
  1.1987 +    else {
  1.1988 +      // If we're not overwriting all of the tabs, then close the home tabs.
  1.1989 +      for (let i = removableTabs.length - 1; i >= 0; i--) {
  1.1990 +        tabbrowser.removeTab(removableTabs.pop(), { animate: false });
  1.1991 +      }
  1.1992 +    }
  1.1993 +
  1.1994 +    return [true, canOverwriteTabs];
  1.1995 +  },
  1.1996 +
  1.1997 +  /* ........ Saving Functionality .............. */
  1.1998 +
  1.1999 +  /**
  1.2000 +   * Store window dimensions, visibility, sidebar
  1.2001 +   * @param aWindow
  1.2002 +   *        Window reference
  1.2003 +   */
  1.2004 +  _updateWindowFeatures: function ssi_updateWindowFeatures(aWindow) {
  1.2005 +    var winData = this._windows[aWindow.__SSi];
  1.2006 +
  1.2007 +    WINDOW_ATTRIBUTES.forEach(function(aAttr) {
  1.2008 +      winData[aAttr] = this._getWindowDimension(aWindow, aAttr);
  1.2009 +    }, this);
  1.2010 +
  1.2011 +    var hidden = WINDOW_HIDEABLE_FEATURES.filter(function(aItem) {
  1.2012 +      return aWindow[aItem] && !aWindow[aItem].visible;
  1.2013 +    });
  1.2014 +    if (hidden.length != 0)
  1.2015 +      winData.hidden = hidden.join(",");
  1.2016 +    else if (winData.hidden)
  1.2017 +      delete winData.hidden;
  1.2018 +
  1.2019 +    var sidebar = aWindow.document.getElementById("sidebar-box").getAttribute("sidebarcommand");
  1.2020 +    if (sidebar)
  1.2021 +      winData.sidebar = sidebar;
  1.2022 +    else if (winData.sidebar)
  1.2023 +      delete winData.sidebar;
  1.2024 +  },
  1.2025 +
  1.2026 +  /**
  1.2027 +   * gather session data as object
  1.2028 +   * @param aUpdateAll
  1.2029 +   *        Bool update all windows
  1.2030 +   * @returns object
  1.2031 +   */
  1.2032 +  getCurrentState: function (aUpdateAll) {
  1.2033 +    this._handleClosedWindows();
  1.2034 +
  1.2035 +    var activeWindow = this._getMostRecentBrowserWindow();
  1.2036 +
  1.2037 +    TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_ALL_WINDOWS_DATA_MS");
  1.2038 +    if (this._loadState == STATE_RUNNING) {
  1.2039 +      // update the data for all windows with activities since the last save operation
  1.2040 +      this._forEachBrowserWindow(function(aWindow) {
  1.2041 +        if (!this._isWindowLoaded(aWindow)) // window data is still in _statesToRestore
  1.2042 +          return;
  1.2043 +        if (aUpdateAll || DirtyWindows.has(aWindow) || aWindow == activeWindow) {
  1.2044 +          this._collectWindowData(aWindow);
  1.2045 +        }
  1.2046 +        else { // always update the window features (whose change alone never triggers a save operation)
  1.2047 +          this._updateWindowFeatures(aWindow);
  1.2048 +        }
  1.2049 +      });
  1.2050 +      DirtyWindows.clear();
  1.2051 +    }
  1.2052 +    TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_ALL_WINDOWS_DATA_MS");
  1.2053 +
  1.2054 +    // An array that at the end will hold all current window data.
  1.2055 +    var total = [];
  1.2056 +    // The ids of all windows contained in 'total' in the same order.
  1.2057 +    var ids = [];
  1.2058 +    // The number of window that are _not_ popups.
  1.2059 +    var nonPopupCount = 0;
  1.2060 +    var ix;
  1.2061 +
  1.2062 +    // collect the data for all windows
  1.2063 +    for (ix in this._windows) {
  1.2064 +      if (this._windows[ix]._restoring) // window data is still in _statesToRestore
  1.2065 +        continue;
  1.2066 +      total.push(this._windows[ix]);
  1.2067 +      ids.push(ix);
  1.2068 +      if (!this._windows[ix].isPopup)
  1.2069 +        nonPopupCount++;
  1.2070 +    }
  1.2071 +
  1.2072 +    TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_COOKIES_MS");
  1.2073 +    SessionCookies.update(total);
  1.2074 +    TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_COOKIES_MS");
  1.2075 +
  1.2076 +    // collect the data for all windows yet to be restored
  1.2077 +    for (ix in this._statesToRestore) {
  1.2078 +      for each (let winData in this._statesToRestore[ix].windows) {
  1.2079 +        total.push(winData);
  1.2080 +        if (!winData.isPopup)
  1.2081 +          nonPopupCount++;
  1.2082 +      }
  1.2083 +    }
  1.2084 +
  1.2085 +    // shallow copy this._closedWindows to preserve current state
  1.2086 +    let lastClosedWindowsCopy = this._closedWindows.slice();
  1.2087 +
  1.2088 +#ifndef XP_MACOSX
  1.2089 +    // If no non-popup browser window remains open, return the state of the last
  1.2090 +    // closed window(s). We only want to do this when we're actually "ending"
  1.2091 +    // the session.
  1.2092 +    //XXXzpao We should do this for _restoreLastWindow == true, but that has
  1.2093 +    //        its own check for popups. c.f. bug 597619
  1.2094 +    if (nonPopupCount == 0 && lastClosedWindowsCopy.length > 0 &&
  1.2095 +        this._loadState == STATE_QUITTING) {
  1.2096 +      // prepend the last non-popup browser window, so that if the user loads more tabs
  1.2097 +      // at startup we don't accidentally add them to a popup window
  1.2098 +      do {
  1.2099 +        total.unshift(lastClosedWindowsCopy.shift())
  1.2100 +      } while (total[0].isPopup && lastClosedWindowsCopy.length > 0)
  1.2101 +    }
  1.2102 +#endif
  1.2103 +
  1.2104 +    if (activeWindow) {
  1.2105 +      this.activeWindowSSiCache = activeWindow.__SSi || "";
  1.2106 +    }
  1.2107 +    ix = ids.indexOf(this.activeWindowSSiCache);
  1.2108 +    // We don't want to restore focus to a minimized window or a window which had all its
  1.2109 +    // tabs stripped out (doesn't exist).
  1.2110 +    if (ix != -1 && total[ix] && total[ix].sizemode == "minimized")
  1.2111 +      ix = -1;
  1.2112 +
  1.2113 +    let session = {
  1.2114 +      lastUpdate: Date.now(),
  1.2115 +      startTime: this._sessionStartTime,
  1.2116 +      recentCrashes: this._recentCrashes
  1.2117 +    };
  1.2118 +
  1.2119 +    // get open Scratchpad window states too
  1.2120 +    let scratchpads = ScratchpadManager.getSessionState();
  1.2121 +
  1.2122 +    let state = {
  1.2123 +      windows: total,
  1.2124 +      selectedWindow: ix + 1,
  1.2125 +      _closedWindows: lastClosedWindowsCopy,
  1.2126 +      session: session,
  1.2127 +      scratchpads: scratchpads,
  1.2128 +      global: this._globalState.getState()
  1.2129 +    };
  1.2130 +
  1.2131 +    // Persist the last session if we deferred restoring it
  1.2132 +    if (LastSession.canRestore) {
  1.2133 +      state.lastSessionState = LastSession.getState();
  1.2134 +    }
  1.2135 +
  1.2136 +    // If we were called by the SessionSaver and started with only a private
  1.2137 +    // window we want to pass the deferred initial state to not lose the
  1.2138 +    // previous session.
  1.2139 +    if (this._deferredInitialState) {
  1.2140 +      state.deferredInitialState = this._deferredInitialState;
  1.2141 +    }
  1.2142 +
  1.2143 +    return state;
  1.2144 +  },
  1.2145 +
  1.2146 +  /**
  1.2147 +   * serialize session data for a window
  1.2148 +   * @param aWindow
  1.2149 +   *        Window reference
  1.2150 +   * @returns string
  1.2151 +   */
  1.2152 +  _getWindowState: function ssi_getWindowState(aWindow) {
  1.2153 +    if (!this._isWindowLoaded(aWindow))
  1.2154 +      return this._statesToRestore[aWindow.__SS_restoreID];
  1.2155 +
  1.2156 +    if (this._loadState == STATE_RUNNING) {
  1.2157 +      this._collectWindowData(aWindow);
  1.2158 +    }
  1.2159 +
  1.2160 +    let windows = [this._windows[aWindow.__SSi]];
  1.2161 +    SessionCookies.update(windows);
  1.2162 +
  1.2163 +    return { windows: windows };
  1.2164 +  },
  1.2165 +
  1.2166 +  _collectWindowData: function ssi_collectWindowData(aWindow) {
  1.2167 +    if (!this._isWindowLoaded(aWindow))
  1.2168 +      return;
  1.2169 +    TelemetryStopwatch.start("FX_SESSION_RESTORE_COLLECT_SINGLE_WINDOW_DATA_MS");
  1.2170 +
  1.2171 +    let tabbrowser = aWindow.gBrowser;
  1.2172 +    let tabs = tabbrowser.tabs;
  1.2173 +    let winData = this._windows[aWindow.__SSi];
  1.2174 +    let tabsData = winData.tabs = [];
  1.2175 +
  1.2176 +    // update the internal state data for this window
  1.2177 +    for (let tab of tabs) {
  1.2178 +      tabsData.push(TabState.collect(tab));
  1.2179 +    }
  1.2180 +    winData.selected = tabbrowser.mTabBox.selectedIndex + 1;
  1.2181 +
  1.2182 +    this._updateWindowFeatures(aWindow);
  1.2183 +
  1.2184 +    // Make sure we keep __SS_lastSessionWindowID around for cases like entering
  1.2185 +    // or leaving PB mode.
  1.2186 +    if (aWindow.__SS_lastSessionWindowID)
  1.2187 +      this._windows[aWindow.__SSi].__lastSessionWindowID =
  1.2188 +        aWindow.__SS_lastSessionWindowID;
  1.2189 +
  1.2190 +    DirtyWindows.remove(aWindow);
  1.2191 +    TelemetryStopwatch.finish("FX_SESSION_RESTORE_COLLECT_SINGLE_WINDOW_DATA_MS");
  1.2192 +  },
  1.2193 +
  1.2194 +  /* ........ Restoring Functionality .............. */
  1.2195 +
  1.2196 +  /**
  1.2197 +   * restore features to a single window
  1.2198 +   * @param aWindow
  1.2199 +   *        Window reference
  1.2200 +   * @param aState
  1.2201 +   *        JS object or its eval'able source
  1.2202 +   * @param aOptions
  1.2203 +   *        {overwriteTabs: true} to overwrite existing tabs w/ new ones
  1.2204 +   *        {isFollowUp: true} if this is not the restoration of the 1st window
  1.2205 +   *        {firstWindow: true} if this is the first non-private window we're
  1.2206 +   *                            restoring in this session, that might open an
  1.2207 +   *                            external link as well
  1.2208 +   */
  1.2209 +  restoreWindow: function ssi_restoreWindow(aWindow, aState, aOptions = {}) {
  1.2210 +    let overwriteTabs = aOptions && aOptions.overwriteTabs;
  1.2211 +    let isFollowUp = aOptions && aOptions.isFollowUp;
  1.2212 +    let firstWindow = aOptions && aOptions.firstWindow;
  1.2213 +
  1.2214 +    if (isFollowUp) {
  1.2215 +      this.windowToFocus = aWindow;
  1.2216 +    }
  1.2217 +    // initialize window if necessary
  1.2218 +    if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
  1.2219 +      this.onLoad(aWindow);
  1.2220 +
  1.2221 +    try {
  1.2222 +      var root = typeof aState == "string" ? JSON.parse(aState) : aState;
  1.2223 +      if (!root.windows[0]) {
  1.2224 +        this._sendRestoreCompletedNotifications();
  1.2225 +        return; // nothing to restore
  1.2226 +      }
  1.2227 +    }
  1.2228 +    catch (ex) { // invalid state object - don't restore anything
  1.2229 +      debug(ex);
  1.2230 +      this._sendRestoreCompletedNotifications();
  1.2231 +      return;
  1.2232 +    }
  1.2233 +
  1.2234 +    TelemetryStopwatch.start("FX_SESSION_RESTORE_RESTORE_WINDOW_MS");
  1.2235 +
  1.2236 +    // We're not returning from this before we end up calling restoreTabs
  1.2237 +    // for this window, so make sure we send the SSWindowStateBusy event.
  1.2238 +    this._setWindowStateBusy(aWindow);
  1.2239 +
  1.2240 +    if (root._closedWindows)
  1.2241 +      this._closedWindows = root._closedWindows;
  1.2242 +
  1.2243 +    var winData;
  1.2244 +    if (!root.selectedWindow || root.selectedWindow > root.windows.length) {
  1.2245 +      root.selectedWindow = 0;
  1.2246 +    }
  1.2247 +
  1.2248 +    // open new windows for all further window entries of a multi-window session
  1.2249 +    // (unless they don't contain any tab data)
  1.2250 +    for (var w = 1; w < root.windows.length; w++) {
  1.2251 +      winData = root.windows[w];
  1.2252 +      if (winData && winData.tabs && winData.tabs[0]) {
  1.2253 +        var window = this._openWindowWithState({ windows: [winData] });
  1.2254 +        if (w == root.selectedWindow - 1) {
  1.2255 +          this.windowToFocus = window;
  1.2256 +        }
  1.2257 +      }
  1.2258 +    }
  1.2259 +    winData = root.windows[0];
  1.2260 +    if (!winData.tabs) {
  1.2261 +      winData.tabs = [];
  1.2262 +    }
  1.2263 +    // don't restore a single blank tab when we've had an external
  1.2264 +    // URL passed in for loading at startup (cf. bug 357419)
  1.2265 +    else if (firstWindow && !overwriteTabs && winData.tabs.length == 1 &&
  1.2266 +             (!winData.tabs[0].entries || winData.tabs[0].entries.length == 0)) {
  1.2267 +      winData.tabs = [];
  1.2268 +    }
  1.2269 +
  1.2270 +    var tabbrowser = aWindow.gBrowser;
  1.2271 +    var openTabCount = overwriteTabs ? tabbrowser.browsers.length : -1;
  1.2272 +    var newTabCount = winData.tabs.length;
  1.2273 +    var tabs = [];
  1.2274 +
  1.2275 +    // disable smooth scrolling while adding, moving, removing and selecting tabs
  1.2276 +    var tabstrip = tabbrowser.tabContainer.mTabstrip;
  1.2277 +    var smoothScroll = tabstrip.smoothScroll;
  1.2278 +    tabstrip.smoothScroll = false;
  1.2279 +
  1.2280 +    // unpin all tabs to ensure they are not reordered in the next loop
  1.2281 +    if (overwriteTabs) {
  1.2282 +      for (let t = tabbrowser._numPinnedTabs - 1; t > -1; t--)
  1.2283 +        tabbrowser.unpinTab(tabbrowser.tabs[t]);
  1.2284 +    }
  1.2285 +
  1.2286 +    // We need to keep track of the initially open tabs so that they
  1.2287 +    // can be moved to the end of the restored tabs.
  1.2288 +    let initialTabs = [];
  1.2289 +    if (!overwriteTabs && firstWindow) {
  1.2290 +      initialTabs = Array.slice(tabbrowser.tabs);
  1.2291 +    }
  1.2292 +
  1.2293 +    // make sure that the selected tab won't be closed in order to
  1.2294 +    // prevent unnecessary flickering
  1.2295 +    if (overwriteTabs && tabbrowser.selectedTab._tPos >= newTabCount)
  1.2296 +      tabbrowser.moveTabTo(tabbrowser.selectedTab, newTabCount - 1);
  1.2297 +
  1.2298 +    let numVisibleTabs = 0;
  1.2299 +
  1.2300 +    for (var t = 0; t < newTabCount; t++) {
  1.2301 +      tabs.push(t < openTabCount ?
  1.2302 +                tabbrowser.tabs[t] :
  1.2303 +                tabbrowser.addTab("about:blank", {skipAnimation: true}));
  1.2304 +
  1.2305 +      if (winData.tabs[t].pinned)
  1.2306 +        tabbrowser.pinTab(tabs[t]);
  1.2307 +
  1.2308 +      if (winData.tabs[t].hidden) {
  1.2309 +        tabbrowser.hideTab(tabs[t]);
  1.2310 +      }
  1.2311 +      else {
  1.2312 +        tabbrowser.showTab(tabs[t]);
  1.2313 +        numVisibleTabs++;
  1.2314 +      }
  1.2315 +    }
  1.2316 +
  1.2317 +    if (!overwriteTabs && firstWindow) {
  1.2318 +      // Move the originally open tabs to the end
  1.2319 +      let endPosition = tabbrowser.tabs.length - 1;
  1.2320 +      for (let i = 0; i < initialTabs.length; i++) {
  1.2321 +        tabbrowser.moveTabTo(initialTabs[i], endPosition);
  1.2322 +      }
  1.2323 +    }
  1.2324 +
  1.2325 +    // if all tabs to be restored are hidden, make the first one visible
  1.2326 +    if (!numVisibleTabs && winData.tabs.length) {
  1.2327 +      winData.tabs[0].hidden = false;
  1.2328 +      tabbrowser.showTab(tabs[0]);
  1.2329 +    }
  1.2330 +
  1.2331 +    // If overwriting tabs, we want to reset each tab's "restoring" state. Since
  1.2332 +    // we're overwriting those tabs, they should no longer be restoring. The
  1.2333 +    // tabs will be rebuilt and marked if they need to be restored after loading
  1.2334 +    // state (in restoreTabs).
  1.2335 +    if (overwriteTabs) {
  1.2336 +      for (let i = 0; i < tabbrowser.tabs.length; i++) {
  1.2337 +        let tab = tabbrowser.tabs[i];
  1.2338 +        if (tabbrowser.browsers[i].__SS_restoreState)
  1.2339 +          this._resetTabRestoringState(tab);
  1.2340 +      }
  1.2341 +    }
  1.2342 +
  1.2343 +    // We want to correlate the window with data from the last session, so
  1.2344 +    // assign another id if we have one. Otherwise clear so we don't do
  1.2345 +    // anything with it.
  1.2346 +    delete aWindow.__SS_lastSessionWindowID;
  1.2347 +    if (winData.__lastSessionWindowID)
  1.2348 +      aWindow.__SS_lastSessionWindowID = winData.__lastSessionWindowID;
  1.2349 +
  1.2350 +    // when overwriting tabs, remove all superflous ones
  1.2351 +    if (overwriteTabs && newTabCount < openTabCount) {
  1.2352 +      Array.slice(tabbrowser.tabs, newTabCount, openTabCount)
  1.2353 +           .forEach(tabbrowser.removeTab, tabbrowser);
  1.2354 +    }
  1.2355 +
  1.2356 +    if (overwriteTabs) {
  1.2357 +      this.restoreWindowFeatures(aWindow, winData);
  1.2358 +      delete this._windows[aWindow.__SSi].extData;
  1.2359 +    }
  1.2360 +    if (winData.cookies) {
  1.2361 +      this.restoreCookies(winData.cookies);
  1.2362 +    }
  1.2363 +    if (winData.extData) {
  1.2364 +      if (!this._windows[aWindow.__SSi].extData) {
  1.2365 +        this._windows[aWindow.__SSi].extData = {};
  1.2366 +      }
  1.2367 +      for (var key in winData.extData) {
  1.2368 +        this._windows[aWindow.__SSi].extData[key] = winData.extData[key];
  1.2369 +      }
  1.2370 +    }
  1.2371 +
  1.2372 +    let newClosedTabsData = winData._closedTabs || [];
  1.2373 +
  1.2374 +    if (overwriteTabs || firstWindow) {
  1.2375 +      // Overwrite existing closed tabs data when overwriteTabs=true
  1.2376 +      // or we're the first window to be restored.
  1.2377 +      this._windows[aWindow.__SSi]._closedTabs = newClosedTabsData;
  1.2378 +    } else if (this._max_tabs_undo > 0) {
  1.2379 +      // If we merge tabs, we also want to merge closed tabs data. We'll assume
  1.2380 +      // the restored tabs were closed more recently and append the current list
  1.2381 +      // of closed tabs to the new one...
  1.2382 +      newClosedTabsData =
  1.2383 +        newClosedTabsData.concat(this._windows[aWindow.__SSi]._closedTabs);
  1.2384 +
  1.2385 +      // ... and make sure that we don't exceed the max number of closed tabs
  1.2386 +      // we can restore.
  1.2387 +      this._windows[aWindow.__SSi]._closedTabs =
  1.2388 +        newClosedTabsData.slice(0, this._max_tabs_undo);
  1.2389 +    }
  1.2390 +
  1.2391 +    this.restoreTabs(aWindow, tabs, winData.tabs,
  1.2392 +      (overwriteTabs ? (parseInt(winData.selected || "1")) : 0));
  1.2393 +
  1.2394 +    if (aState.scratchpads) {
  1.2395 +      ScratchpadManager.restoreSession(aState.scratchpads);
  1.2396 +    }
  1.2397 +
  1.2398 +    // set smoothScroll back to the original value
  1.2399 +    tabstrip.smoothScroll = smoothScroll;
  1.2400 +
  1.2401 +    TelemetryStopwatch.finish("FX_SESSION_RESTORE_RESTORE_WINDOW_MS");
  1.2402 +
  1.2403 +    this._sendRestoreCompletedNotifications();
  1.2404 +  },
  1.2405 +
  1.2406 +  /**
  1.2407 +   * Sets the tabs restoring order with the following priority:
  1.2408 +   * Selected tab, pinned tabs, optimized visible tabs, other visible tabs and
  1.2409 +   * hidden tabs.
  1.2410 +   * @param aTabBrowser
  1.2411 +   *        Tab browser object
  1.2412 +   * @param aTabs
  1.2413 +   *        Array of tab references
  1.2414 +   * @param aTabData
  1.2415 +   *        Array of tab data
  1.2416 +   * @param aSelectedTab
  1.2417 +   *        Index of selected tab (1 is first tab, 0 no selected tab)
  1.2418 +   */
  1.2419 +  _setTabsRestoringOrder : function ssi__setTabsRestoringOrder(
  1.2420 +    aTabBrowser, aTabs, aTabData, aSelectedTab) {
  1.2421 +
  1.2422 +    // Store the selected tab. Need to substract one to get the index in aTabs.
  1.2423 +    let selectedTab;
  1.2424 +    if (aSelectedTab > 0 && aTabs[aSelectedTab - 1]) {
  1.2425 +      selectedTab = aTabs[aSelectedTab - 1];
  1.2426 +    }
  1.2427 +
  1.2428 +    // Store the pinned tabs and hidden tabs.
  1.2429 +    let pinnedTabs = [];
  1.2430 +    let pinnedTabsData = [];
  1.2431 +    let hiddenTabs = [];
  1.2432 +    let hiddenTabsData = [];
  1.2433 +    if (aTabs.length > 1) {
  1.2434 +      for (let t = aTabs.length - 1; t >= 0; t--) {
  1.2435 +        if (aTabData[t].pinned) {
  1.2436 +          pinnedTabs.unshift(aTabs.splice(t, 1)[0]);
  1.2437 +          pinnedTabsData.unshift(aTabData.splice(t, 1)[0]);
  1.2438 +        } else if (aTabData[t].hidden) {
  1.2439 +          hiddenTabs.unshift(aTabs.splice(t, 1)[0]);
  1.2440 +          hiddenTabsData.unshift(aTabData.splice(t, 1)[0]);
  1.2441 +        }
  1.2442 +      }
  1.2443 +    }
  1.2444 +
  1.2445 +    // Optimize the visible tabs only if there is a selected tab.
  1.2446 +    if (selectedTab) {
  1.2447 +      let selectedTabIndex = aTabs.indexOf(selectedTab);
  1.2448 +      if (selectedTabIndex > 0) {
  1.2449 +        let scrollSize = aTabBrowser.tabContainer.mTabstrip.scrollClientSize;
  1.2450 +        let tabWidth = aTabs[0].getBoundingClientRect().width;
  1.2451 +        let maxVisibleTabs = Math.ceil(scrollSize / tabWidth);
  1.2452 +        if (maxVisibleTabs < aTabs.length) {
  1.2453 +          let firstVisibleTab = 0;
  1.2454 +          let nonVisibleTabsCount = aTabs.length - maxVisibleTabs;
  1.2455 +          if (nonVisibleTabsCount >= selectedTabIndex) {
  1.2456 +            // Selected tab is leftmost since we scroll to it when possible.
  1.2457 +            firstVisibleTab = selectedTabIndex;
  1.2458 +          } else {
  1.2459 +            // Selected tab is rightmost or no more room to scroll right.
  1.2460 +            firstVisibleTab = nonVisibleTabsCount;
  1.2461 +          }
  1.2462 +          aTabs = aTabs.splice(firstVisibleTab, maxVisibleTabs).concat(aTabs);
  1.2463 +          aTabData =
  1.2464 +            aTabData.splice(firstVisibleTab, maxVisibleTabs).concat(aTabData);
  1.2465 +        }
  1.2466 +      }
  1.2467 +    }
  1.2468 +
  1.2469 +    // Merge the stored tabs in order.
  1.2470 +    aTabs = pinnedTabs.concat(aTabs, hiddenTabs);
  1.2471 +    aTabData = pinnedTabsData.concat(aTabData, hiddenTabsData);
  1.2472 +
  1.2473 +    // Load the selected tab to the first position and select it.
  1.2474 +    if (selectedTab) {
  1.2475 +      let selectedTabIndex = aTabs.indexOf(selectedTab);
  1.2476 +      if (selectedTabIndex > 0) {
  1.2477 +        aTabs = aTabs.splice(selectedTabIndex, 1).concat(aTabs);
  1.2478 +        aTabData = aTabData.splice(selectedTabIndex, 1).concat(aTabData);
  1.2479 +      }
  1.2480 +      aTabBrowser.selectedTab = selectedTab;
  1.2481 +    }
  1.2482 +
  1.2483 +    return [aTabs, aTabData];
  1.2484 +  },
  1.2485 +
  1.2486 +  /**
  1.2487 +   * Manage history restoration for a window
  1.2488 +   * @param aWindow
  1.2489 +   *        Window to restore the tabs into
  1.2490 +   * @param aTabs
  1.2491 +   *        Array of tab references
  1.2492 +   * @param aTabData
  1.2493 +   *        Array of tab data
  1.2494 +   * @param aSelectTab
  1.2495 +   *        Index of selected tab
  1.2496 +   * @param aRestoreImmediately
  1.2497 +   *        Flag to indicate whether the given set of tabs aTabs should be
  1.2498 +   *        restored/loaded immediately even if restore_on_demand = true
  1.2499 +   */
  1.2500 +  restoreTabs: function (aWindow, aTabs, aTabData, aSelectTab,
  1.2501 +                         aRestoreImmediately = false)
  1.2502 +  {
  1.2503 +
  1.2504 +    var tabbrowser = aWindow.gBrowser;
  1.2505 +
  1.2506 +    if (!this._isWindowLoaded(aWindow)) {
  1.2507 +      // from now on, the data will come from the actual window
  1.2508 +      delete this._statesToRestore[aWindow.__SS_restoreID];
  1.2509 +      delete aWindow.__SS_restoreID;
  1.2510 +      delete this._windows[aWindow.__SSi]._restoring;
  1.2511 +    }
  1.2512 +
  1.2513 +    // It's important to set the window state to dirty so that
  1.2514 +    // we collect their data for the first time when saving state.
  1.2515 +    DirtyWindows.add(aWindow);
  1.2516 +
  1.2517 +    // Set the state to restore as the window's current state. Normally, this
  1.2518 +    // will just be overridden the next time we collect state but we need this
  1.2519 +    // as a fallback should Firefox be shutdown early without notifying us
  1.2520 +    // beforehand.
  1.2521 +    this._windows[aWindow.__SSi].tabs = aTabData.slice();
  1.2522 +    this._windows[aWindow.__SSi].selected = aSelectTab;
  1.2523 +
  1.2524 +    if (aTabs.length == 0) {
  1.2525 +      // This is normally done later, but as we're returning early
  1.2526 +      // here we need to take care of it.
  1.2527 +      this._setWindowStateReady(aWindow);
  1.2528 +      return;
  1.2529 +    }
  1.2530 +
  1.2531 +    // Sets the tabs restoring order.
  1.2532 +    [aTabs, aTabData] =
  1.2533 +      this._setTabsRestoringOrder(tabbrowser, aTabs, aTabData, aSelectTab);
  1.2534 +
  1.2535 +    // Prepare the tabs so that they can be properly restored. We'll pin/unpin
  1.2536 +    // and show/hide tabs as necessary. We'll also set the labels, user typed
  1.2537 +    // value, and attach a copy of the tab's data in case we close it before
  1.2538 +    // it's been restored.
  1.2539 +    for (let t = 0; t < aTabs.length; t++) {
  1.2540 +      let tab = aTabs[t];
  1.2541 +      let browser = tabbrowser.getBrowserForTab(tab);
  1.2542 +      let tabData = aTabData[t];
  1.2543 +
  1.2544 +      if (tabData.pinned)
  1.2545 +        tabbrowser.pinTab(tab);
  1.2546 +      else
  1.2547 +        tabbrowser.unpinTab(tab);
  1.2548 +
  1.2549 +      if (tabData.hidden)
  1.2550 +        tabbrowser.hideTab(tab);
  1.2551 +      else
  1.2552 +        tabbrowser.showTab(tab);
  1.2553 +
  1.2554 +      if (tabData.lastAccessed) {
  1.2555 +        tab.lastAccessed = tabData.lastAccessed;
  1.2556 +      }
  1.2557 +
  1.2558 +      if ("attributes" in tabData) {
  1.2559 +        // Ensure that we persist tab attributes restored from previous sessions.
  1.2560 +        Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a));
  1.2561 +      }
  1.2562 +
  1.2563 +      if (!tabData.entries) {
  1.2564 +        tabData.entries = [];
  1.2565 +      }
  1.2566 +      if (tabData.extData) {
  1.2567 +        tab.__SS_extdata = {};
  1.2568 +        for (let key in tabData.extData)
  1.2569 +         tab.__SS_extdata[key] = tabData.extData[key];
  1.2570 +      } else {
  1.2571 +        delete tab.__SS_extdata;
  1.2572 +      }
  1.2573 +
  1.2574 +      // Flush all data from the content script synchronously. This is done so
  1.2575 +      // that all async messages that are still on their way to chrome will
  1.2576 +      // be ignored and don't override any tab data set when restoring.
  1.2577 +      TabState.flush(tab.linkedBrowser);
  1.2578 +
  1.2579 +      // Ensure the index is in bounds.
  1.2580 +      let activeIndex = (tabData.index || tabData.entries.length) - 1;
  1.2581 +      activeIndex = Math.min(activeIndex, tabData.entries.length - 1);
  1.2582 +      activeIndex = Math.max(activeIndex, 0);
  1.2583 +
  1.2584 +      // Save the index in case we updated it above.
  1.2585 +      tabData.index = activeIndex + 1;
  1.2586 +
  1.2587 +      // In electrolysis, we may need to change the browser's remote
  1.2588 +      // attribute so that it runs in a content process.
  1.2589 +      let activePageData = tabData.entries[activeIndex] || null;
  1.2590 +      let uri = activePageData ? activePageData.url || null : null;
  1.2591 +      tabbrowser.updateBrowserRemoteness(browser, uri);
  1.2592 +
  1.2593 +      // Start a new epoch and include the epoch in the restoreHistory
  1.2594 +      // message. If a message is received that relates to a previous epoch, we
  1.2595 +      // discard it.
  1.2596 +      let epoch = this._nextRestoreEpoch++;
  1.2597 +      this._browserEpochs.set(browser.permanentKey, epoch);
  1.2598 +
  1.2599 +      // keep the data around to prevent dataloss in case
  1.2600 +      // a tab gets closed before it's been properly restored
  1.2601 +      browser.__SS_data = tabData;
  1.2602 +      browser.__SS_restoreState = TAB_STATE_NEEDS_RESTORE;
  1.2603 +      browser.setAttribute("pending", "true");
  1.2604 +      tab.setAttribute("pending", "true");
  1.2605 +
  1.2606 +      // Update the persistent tab state cache with |tabData| information.
  1.2607 +      TabStateCache.update(browser, {
  1.2608 +        history: {entries: tabData.entries, index: tabData.index},
  1.2609 +        scroll: tabData.scroll || null,
  1.2610 +        storage: tabData.storage || null,
  1.2611 +        formdata: tabData.formdata || null,
  1.2612 +        disallow: tabData.disallow || null,
  1.2613 +        pageStyle: tabData.pageStyle || null
  1.2614 +      });
  1.2615 +
  1.2616 +      browser.messageManager.sendAsyncMessage("SessionStore:restoreHistory",
  1.2617 +                                              {tabData: tabData, epoch: epoch});
  1.2618 +
  1.2619 +      // Restore tab attributes.
  1.2620 +      if ("attributes" in tabData) {
  1.2621 +        TabAttributes.set(tab, tabData.attributes);
  1.2622 +      }
  1.2623 +
  1.2624 +      // This could cause us to ignore MAX_CONCURRENT_TAB_RESTORES a bit, but
  1.2625 +      // it ensures each window will have its selected tab loaded.
  1.2626 +      if (aRestoreImmediately || tabbrowser.selectedBrowser == browser) {
  1.2627 +        this.restoreTabContent(tab);
  1.2628 +      } else {
  1.2629 +        TabRestoreQueue.add(tab);
  1.2630 +        this.restoreNextTab();
  1.2631 +      }
  1.2632 +    }
  1.2633 +
  1.2634 +    this._setWindowStateReady(aWindow);
  1.2635 +  },
  1.2636 +
  1.2637 +  /**
  1.2638 +   * Restores the specified tab. If the tab can't be restored (eg, no history or
  1.2639 +   * calling gotoIndex fails), then state changes will be rolled back.
  1.2640 +   * This method will check if gTabsProgressListener is attached to the tab's
  1.2641 +   * window, ensuring that we don't get caught without one.
  1.2642 +   * This method removes the session history listener right before starting to
  1.2643 +   * attempt a load. This will prevent cases of "stuck" listeners.
  1.2644 +   * If this method returns false, then it is up to the caller to decide what to
  1.2645 +   * do. In the common case (restoreNextTab), we will want to then attempt to
  1.2646 +   * restore the next tab. In the other case (selecting the tab, reloading the
  1.2647 +   * tab), the caller doesn't actually want to do anything if no page is loaded.
  1.2648 +   *
  1.2649 +   * @param aTab
  1.2650 +   *        the tab to restore
  1.2651 +   *
  1.2652 +   * @returns true/false indicating whether or not a load actually happened
  1.2653 +   */
  1.2654 +  restoreTabContent: function (aTab) {
  1.2655 +    let window = aTab.ownerDocument.defaultView;
  1.2656 +    let browser = aTab.linkedBrowser;
  1.2657 +    let tabData = browser.__SS_data;
  1.2658 +
  1.2659 +    // Make sure that this tab is removed from the priority queue.
  1.2660 +    TabRestoreQueue.remove(aTab);
  1.2661 +
  1.2662 +    // Increase our internal count.
  1.2663 +    this._tabsRestoringCount++;
  1.2664 +
  1.2665 +    // Set this tab's state to restoring
  1.2666 +    browser.__SS_restoreState = TAB_STATE_RESTORING;
  1.2667 +    browser.removeAttribute("pending");
  1.2668 +    aTab.removeAttribute("pending");
  1.2669 +
  1.2670 +    let activeIndex = tabData.index - 1;
  1.2671 +
  1.2672 +    // Attach data that will be restored on "load" event, after tab is restored.
  1.2673 +    if (tabData.entries.length) {
  1.2674 +      // restore those aspects of the currently active documents which are not
  1.2675 +      // preserved in the plain history entries (mainly scroll state and text data)
  1.2676 +      browser.__SS_restore_data = tabData.entries[activeIndex] || {};
  1.2677 +    } else {
  1.2678 +      browser.__SS_restore_data = {};
  1.2679 +    }
  1.2680 +
  1.2681 +    browser.__SS_restore_tab = aTab;
  1.2682 +
  1.2683 +    browser.messageManager.sendAsyncMessage("SessionStore:restoreTabContent");
  1.2684 +  },
  1.2685 +
  1.2686 +  /**
  1.2687 +   * This _attempts_ to restore the next available tab. If the restore fails,
  1.2688 +   * then we will attempt the next one.
  1.2689 +   * There are conditions where this won't do anything:
  1.2690 +   *   if we're in the process of quitting
  1.2691 +   *   if there are no tabs to restore
  1.2692 +   *   if we have already reached the limit for number of tabs to restore
  1.2693 +   */
  1.2694 +  restoreNextTab: function ssi_restoreNextTab() {
  1.2695 +    // If we call in here while quitting, we don't actually want to do anything
  1.2696 +    if (this._loadState == STATE_QUITTING)
  1.2697 +      return;
  1.2698 +
  1.2699 +    // Don't exceed the maximum number of concurrent tab restores.
  1.2700 +    if (this._tabsRestoringCount >= MAX_CONCURRENT_TAB_RESTORES)
  1.2701 +      return;
  1.2702 +
  1.2703 +    let tab = TabRestoreQueue.shift();
  1.2704 +    if (tab) {
  1.2705 +      this.restoreTabContent(tab);
  1.2706 +    }
  1.2707 +  },
  1.2708 +
  1.2709 +  /**
  1.2710 +   * Restore visibility and dimension features to a window
  1.2711 +   * @param aWindow
  1.2712 +   *        Window reference
  1.2713 +   * @param aWinData
  1.2714 +   *        Object containing session data for the window
  1.2715 +   */
  1.2716 +  restoreWindowFeatures: function ssi_restoreWindowFeatures(aWindow, aWinData) {
  1.2717 +    var hidden = (aWinData.hidden)?aWinData.hidden.split(","):[];
  1.2718 +    WINDOW_HIDEABLE_FEATURES.forEach(function(aItem) {
  1.2719 +      aWindow[aItem].visible = hidden.indexOf(aItem) == -1;
  1.2720 +    });
  1.2721 +
  1.2722 +    if (aWinData.isPopup) {
  1.2723 +      this._windows[aWindow.__SSi].isPopup = true;
  1.2724 +      if (aWindow.gURLBar) {
  1.2725 +        aWindow.gURLBar.readOnly = true;
  1.2726 +        aWindow.gURLBar.setAttribute("enablehistory", "false");
  1.2727 +      }
  1.2728 +    }
  1.2729 +    else {
  1.2730 +      delete this._windows[aWindow.__SSi].isPopup;
  1.2731 +      if (aWindow.gURLBar) {
  1.2732 +        aWindow.gURLBar.readOnly = false;
  1.2733 +        aWindow.gURLBar.setAttribute("enablehistory", "true");
  1.2734 +      }
  1.2735 +    }
  1.2736 +
  1.2737 +    var _this = this;
  1.2738 +    aWindow.setTimeout(function() {
  1.2739 +      _this.restoreDimensions.apply(_this, [aWindow,
  1.2740 +        +aWinData.width || 0,
  1.2741 +        +aWinData.height || 0,
  1.2742 +        "screenX" in aWinData ? +aWinData.screenX : NaN,
  1.2743 +        "screenY" in aWinData ? +aWinData.screenY : NaN,
  1.2744 +        aWinData.sizemode || "", aWinData.sidebar || ""]);
  1.2745 +    }, 0);
  1.2746 +  },
  1.2747 +
  1.2748 +  /**
  1.2749 +   * Restore a window's dimensions
  1.2750 +   * @param aWidth
  1.2751 +   *        Window width
  1.2752 +   * @param aHeight
  1.2753 +   *        Window height
  1.2754 +   * @param aLeft
  1.2755 +   *        Window left
  1.2756 +   * @param aTop
  1.2757 +   *        Window top
  1.2758 +   * @param aSizeMode
  1.2759 +   *        Window size mode (eg: maximized)
  1.2760 +   * @param aSidebar
  1.2761 +   *        Sidebar command
  1.2762 +   */
  1.2763 +  restoreDimensions: function ssi_restoreDimensions(aWindow, aWidth, aHeight, aLeft, aTop, aSizeMode, aSidebar) {
  1.2764 +    var win = aWindow;
  1.2765 +    var _this = this;
  1.2766 +    function win_(aName) { return _this._getWindowDimension(win, aName); }
  1.2767 +
  1.2768 +    // find available space on the screen where this window is being placed
  1.2769 +    let screen = gScreenManager.screenForRect(aLeft, aTop, aWidth, aHeight);
  1.2770 +    if (screen) {
  1.2771 +      let screenLeft = {}, screenTop = {}, screenWidth = {}, screenHeight = {};
  1.2772 +      screen.GetAvailRectDisplayPix(screenLeft, screenTop, screenWidth, screenHeight);
  1.2773 +      // constrain the dimensions to the actual space available
  1.2774 +      if (aWidth > screenWidth.value) {
  1.2775 +        aWidth = screenWidth.value;
  1.2776 +      }
  1.2777 +      if (aHeight > screenHeight.value) {
  1.2778 +        aHeight = screenHeight.value;
  1.2779 +      }
  1.2780 +      // and then pull the window within the screen's bounds
  1.2781 +      if (aLeft < screenLeft.value) {
  1.2782 +        aLeft = screenLeft.value;
  1.2783 +      } else if (aLeft + aWidth > screenLeft.value + screenWidth.value) {
  1.2784 +        aLeft = screenLeft.value + screenWidth.value - aWidth;
  1.2785 +      }
  1.2786 +      if (aTop < screenTop.value) {
  1.2787 +        aTop = screenTop.value;
  1.2788 +      } else if (aTop + aHeight > screenTop.value + screenHeight.value) {
  1.2789 +        aTop = screenTop.value + screenHeight.value - aHeight;
  1.2790 +      }
  1.2791 +    }
  1.2792 +
  1.2793 +    // only modify those aspects which aren't correct yet
  1.2794 +    if (aWidth && aHeight && (aWidth != win_("width") || aHeight != win_("height"))) {
  1.2795 +      // Don't resize the window if it's currently maximized and we would
  1.2796 +      // maximize it again shortly after.
  1.2797 +      if (aSizeMode != "maximized" || win_("sizemode") != "maximized") {
  1.2798 +        aWindow.resizeTo(aWidth, aHeight);
  1.2799 +      }
  1.2800 +    }
  1.2801 +    if (!isNaN(aLeft) && !isNaN(aTop) && (aLeft != win_("screenX") || aTop != win_("screenY"))) {
  1.2802 +      aWindow.moveTo(aLeft, aTop);
  1.2803 +    }
  1.2804 +    if (aSizeMode && win_("sizemode") != aSizeMode)
  1.2805 +    {
  1.2806 +      switch (aSizeMode)
  1.2807 +      {
  1.2808 +      case "maximized":
  1.2809 +        aWindow.maximize();
  1.2810 +        break;
  1.2811 +      case "minimized":
  1.2812 +        aWindow.minimize();
  1.2813 +        break;
  1.2814 +      case "normal":
  1.2815 +        aWindow.restore();
  1.2816 +        break;
  1.2817 +      }
  1.2818 +    }
  1.2819 +    var sidebar = aWindow.document.getElementById("sidebar-box");
  1.2820 +    if (sidebar.getAttribute("sidebarcommand") != aSidebar) {
  1.2821 +      aWindow.toggleSidebar(aSidebar);
  1.2822 +    }
  1.2823 +    // since resizing/moving a window brings it to the foreground,
  1.2824 +    // we might want to re-focus the last focused window
  1.2825 +    if (this.windowToFocus) {
  1.2826 +      this.windowToFocus.focus();
  1.2827 +    }
  1.2828 +  },
  1.2829 +
  1.2830 +  /**
  1.2831 +   * Restores cookies
  1.2832 +   * @param aCookies
  1.2833 +   *        Array of cookie objects
  1.2834 +   */
  1.2835 +  restoreCookies: function ssi_restoreCookies(aCookies) {
  1.2836 +    // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
  1.2837 +    var MAX_EXPIRY = Math.pow(2, 62);
  1.2838 +    for (let i = 0; i < aCookies.length; i++) {
  1.2839 +      var cookie = aCookies[i];
  1.2840 +      try {
  1.2841 +        Services.cookies.add(cookie.host, cookie.path || "", cookie.name || "",
  1.2842 +                             cookie.value, !!cookie.secure, !!cookie.httponly, true,
  1.2843 +                             "expiry" in cookie ? cookie.expiry : MAX_EXPIRY);
  1.2844 +      }
  1.2845 +      catch (ex) { console.error(ex); } // don't let a single cookie stop recovering
  1.2846 +    }
  1.2847 +  },
  1.2848 +
  1.2849 +  /* ........ Disk Access .............. */
  1.2850 +
  1.2851 +  /**
  1.2852 +   * Save the current session state to disk, after a delay.
  1.2853 +   *
  1.2854 +   * @param aWindow (optional)
  1.2855 +   *        Will mark the given window as dirty so that we will recollect its
  1.2856 +   *        data before we start writing.
  1.2857 +   */
  1.2858 +  saveStateDelayed: function (aWindow = null) {
  1.2859 +    if (aWindow) {
  1.2860 +      DirtyWindows.add(aWindow);
  1.2861 +    }
  1.2862 +
  1.2863 +    SessionSaver.runDelayed();
  1.2864 +  },
  1.2865 +
  1.2866 +  /* ........ Auxiliary Functions .............. */
  1.2867 +
  1.2868 +  /**
  1.2869 +   * Update the session start time and send a telemetry measurement
  1.2870 +   * for the number of days elapsed since the session was started.
  1.2871 +   *
  1.2872 +   * @param state
  1.2873 +   *        The session state.
  1.2874 +   */
  1.2875 +  _updateSessionStartTime: function ssi_updateSessionStartTime(state) {
  1.2876 +    // Attempt to load the session start time from the session state
  1.2877 +    if (state.session && state.session.startTime) {
  1.2878 +      this._sessionStartTime = state.session.startTime;
  1.2879 +
  1.2880 +      // ms to days
  1.2881 +      let sessionLength = (Date.now() - this._sessionStartTime) / MS_PER_DAY;
  1.2882 +
  1.2883 +      if (sessionLength > 0) {
  1.2884 +        // Submit the session length telemetry measurement
  1.2885 +        Services.telemetry.getHistogramById("FX_SESSION_RESTORE_SESSION_LENGTH").add(sessionLength);
  1.2886 +      }
  1.2887 +    }
  1.2888 +  },
  1.2889 +
  1.2890 +  /**
  1.2891 +   * call a callback for all currently opened browser windows
  1.2892 +   * (might miss the most recent one)
  1.2893 +   * @param aFunc
  1.2894 +   *        Callback each window is passed to
  1.2895 +   */
  1.2896 +  _forEachBrowserWindow: function ssi_forEachBrowserWindow(aFunc) {
  1.2897 +    var windowsEnum = Services.wm.getEnumerator("navigator:browser");
  1.2898 +
  1.2899 +    while (windowsEnum.hasMoreElements()) {
  1.2900 +      var window = windowsEnum.getNext();
  1.2901 +      if (window.__SSi && !window.closed) {
  1.2902 +        aFunc.call(this, window);
  1.2903 +      }
  1.2904 +    }
  1.2905 +  },
  1.2906 +
  1.2907 +  /**
  1.2908 +   * Returns most recent window
  1.2909 +   * @returns Window reference
  1.2910 +   */
  1.2911 +  _getMostRecentBrowserWindow: function ssi_getMostRecentBrowserWindow() {
  1.2912 +    return RecentWindow.getMostRecentBrowserWindow({ allowPopups: true });
  1.2913 +  },
  1.2914 +
  1.2915 +  /**
  1.2916 +   * Calls onClose for windows that are determined to be closed but aren't
  1.2917 +   * destroyed yet, which would otherwise cause getBrowserState and
  1.2918 +   * setBrowserState to treat them as open windows.
  1.2919 +   */
  1.2920 +  _handleClosedWindows: function ssi_handleClosedWindows() {
  1.2921 +    var windowsEnum = Services.wm.getEnumerator("navigator:browser");
  1.2922 +
  1.2923 +    while (windowsEnum.hasMoreElements()) {
  1.2924 +      var window = windowsEnum.getNext();
  1.2925 +      if (window.closed) {
  1.2926 +        this.onClose(window);
  1.2927 +      }
  1.2928 +    }
  1.2929 +  },
  1.2930 +
  1.2931 +  /**
  1.2932 +   * open a new browser window for a given session state
  1.2933 +   * called when restoring a multi-window session
  1.2934 +   * @param aState
  1.2935 +   *        Object containing session data
  1.2936 +   */
  1.2937 +  _openWindowWithState: function ssi_openWindowWithState(aState) {
  1.2938 +    var argString = Cc["@mozilla.org/supports-string;1"].
  1.2939 +                    createInstance(Ci.nsISupportsString);
  1.2940 +    argString.data = "";
  1.2941 +
  1.2942 +    // Build feature string
  1.2943 +    let features = "chrome,dialog=no,macsuppressanimation,all";
  1.2944 +    let winState = aState.windows[0];
  1.2945 +    WINDOW_ATTRIBUTES.forEach(function(aFeature) {
  1.2946 +      // Use !isNaN as an easy way to ignore sizemode and check for numbers
  1.2947 +      if (aFeature in winState && !isNaN(winState[aFeature]))
  1.2948 +        features += "," + aFeature + "=" + winState[aFeature];
  1.2949 +    });
  1.2950 +
  1.2951 +    if (winState.isPrivate) {
  1.2952 +      features += ",private";
  1.2953 +    }
  1.2954 +
  1.2955 +    var window =
  1.2956 +      Services.ww.openWindow(null, this._prefBranch.getCharPref("chromeURL"),
  1.2957 +                             "_blank", features, argString);
  1.2958 +
  1.2959 +    do {
  1.2960 +      var ID = "window" + Math.random();
  1.2961 +    } while (ID in this._statesToRestore);
  1.2962 +    this._statesToRestore[(window.__SS_restoreID = ID)] = aState;
  1.2963 +
  1.2964 +    return window;
  1.2965 +  },
  1.2966 +
  1.2967 +  /**
  1.2968 +   * Gets the tab for the given browser. This should be marginally better
  1.2969 +   * than using tabbrowser's getTabForContentWindow. This assumes the browser
  1.2970 +   * is the linkedBrowser of a tab, not a dangling browser.
  1.2971 +   *
  1.2972 +   * @param aBrowser
  1.2973 +   *        The browser from which to get the tab.
  1.2974 +   */
  1.2975 +  _getTabForBrowser: function ssi_getTabForBrowser(aBrowser) {
  1.2976 +    let window = aBrowser.ownerDocument.defaultView;
  1.2977 +    for (let i = 0; i < window.gBrowser.tabs.length; i++) {
  1.2978 +      let tab = window.gBrowser.tabs[i];
  1.2979 +      if (tab.linkedBrowser == aBrowser)
  1.2980 +        return tab;
  1.2981 +    }
  1.2982 +    return undefined;
  1.2983 +  },
  1.2984 +
  1.2985 +  /**
  1.2986 +   * Whether or not to resume session, if not recovering from a crash.
  1.2987 +   * @returns bool
  1.2988 +   */
  1.2989 +  _doResumeSession: function ssi_doResumeSession() {
  1.2990 +    return this._prefBranch.getIntPref("startup.page") == 3 ||
  1.2991 +           this._prefBranch.getBoolPref("sessionstore.resume_session_once");
  1.2992 +  },
  1.2993 +
  1.2994 +  /**
  1.2995 +   * whether the user wants to load any other page at startup
  1.2996 +   * (except the homepage) - needed for determining whether to overwrite the current tabs
  1.2997 +   * C.f.: nsBrowserContentHandler's defaultArgs implementation.
  1.2998 +   * @returns bool
  1.2999 +   */
  1.3000 +  _isCmdLineEmpty: function ssi_isCmdLineEmpty(aWindow, aState) {
  1.3001 +    var pinnedOnly = aState.windows &&
  1.3002 +                     aState.windows.every(function (win)
  1.3003 +                       win.tabs.every(function (tab) tab.pinned));
  1.3004 +
  1.3005 +    let hasFirstArgument = aWindow.arguments && aWindow.arguments[0];
  1.3006 +    if (!pinnedOnly) {
  1.3007 +      let defaultArgs = Cc["@mozilla.org/browser/clh;1"].
  1.3008 +                        getService(Ci.nsIBrowserHandler).defaultArgs;
  1.3009 +      if (aWindow.arguments &&
  1.3010 +          aWindow.arguments[0] &&
  1.3011 +          aWindow.arguments[0] == defaultArgs)
  1.3012 +        hasFirstArgument = false;
  1.3013 +    }
  1.3014 +
  1.3015 +    return !hasFirstArgument;
  1.3016 +  },
  1.3017 +
  1.3018 +  /**
  1.3019 +   * on popup windows, the XULWindow's attributes seem not to be set correctly
  1.3020 +   * we use thus JSDOMWindow attributes for sizemode and normal window attributes
  1.3021 +   * (and hope for reasonable values when maximized/minimized - since then
  1.3022 +   * outerWidth/outerHeight aren't the dimensions of the restored window)
  1.3023 +   * @param aWindow
  1.3024 +   *        Window reference
  1.3025 +   * @param aAttribute
  1.3026 +   *        String sizemode | width | height | other window attribute
  1.3027 +   * @returns string
  1.3028 +   */
  1.3029 +  _getWindowDimension: function ssi_getWindowDimension(aWindow, aAttribute) {
  1.3030 +    if (aAttribute == "sizemode") {
  1.3031 +      switch (aWindow.windowState) {
  1.3032 +      case aWindow.STATE_FULLSCREEN:
  1.3033 +      case aWindow.STATE_MAXIMIZED:
  1.3034 +        return "maximized";
  1.3035 +      case aWindow.STATE_MINIMIZED:
  1.3036 +        return "minimized";
  1.3037 +      default:
  1.3038 +        return "normal";
  1.3039 +      }
  1.3040 +    }
  1.3041 +
  1.3042 +    var dimension;
  1.3043 +    switch (aAttribute) {
  1.3044 +    case "width":
  1.3045 +      dimension = aWindow.outerWidth;
  1.3046 +      break;
  1.3047 +    case "height":
  1.3048 +      dimension = aWindow.outerHeight;
  1.3049 +      break;
  1.3050 +    default:
  1.3051 +      dimension = aAttribute in aWindow ? aWindow[aAttribute] : "";
  1.3052 +      break;
  1.3053 +    }
  1.3054 +
  1.3055 +    if (aWindow.windowState == aWindow.STATE_NORMAL) {
  1.3056 +      return dimension;
  1.3057 +    }
  1.3058 +    return aWindow.document.documentElement.getAttribute(aAttribute) || dimension;
  1.3059 +  },
  1.3060 +
  1.3061 +  /**
  1.3062 +   * Get nsIURI from string
  1.3063 +   * @param string
  1.3064 +   * @returns nsIURI
  1.3065 +   */
  1.3066 +  _getURIFromString: function ssi_getURIFromString(aString) {
  1.3067 +    return Services.io.newURI(aString, null, null);
  1.3068 +  },
  1.3069 +
  1.3070 +  /**
  1.3071 +   * @param aState is a session state
  1.3072 +   * @param aRecentCrashes is the number of consecutive crashes
  1.3073 +   * @returns whether a restore page will be needed for the session state
  1.3074 +   */
  1.3075 +  _needsRestorePage: function ssi_needsRestorePage(aState, aRecentCrashes) {
  1.3076 +    const SIX_HOURS_IN_MS = 6 * 60 * 60 * 1000;
  1.3077 +
  1.3078 +    // don't display the page when there's nothing to restore
  1.3079 +    let winData = aState.windows || null;
  1.3080 +    if (!winData || winData.length == 0)
  1.3081 +      return false;
  1.3082 +
  1.3083 +    // don't wrap a single about:sessionrestore page
  1.3084 +    if (this._hasSingleTabWithURL(winData, "about:sessionrestore") ||
  1.3085 +        this._hasSingleTabWithURL(winData, "about:welcomeback")) {
  1.3086 +      return false;
  1.3087 +    }
  1.3088 +
  1.3089 +    // don't automatically restore in Safe Mode
  1.3090 +    if (Services.appinfo.inSafeMode)
  1.3091 +      return true;
  1.3092 +
  1.3093 +    let max_resumed_crashes =
  1.3094 +      this._prefBranch.getIntPref("sessionstore.max_resumed_crashes");
  1.3095 +    let sessionAge = aState.session && aState.session.lastUpdate &&
  1.3096 +                     (Date.now() - aState.session.lastUpdate);
  1.3097 +
  1.3098 +    return max_resumed_crashes != -1 &&
  1.3099 +           (aRecentCrashes > max_resumed_crashes ||
  1.3100 +            sessionAge && sessionAge >= SIX_HOURS_IN_MS);
  1.3101 +  },
  1.3102 +
  1.3103 +  /**
  1.3104 +   * @param aWinData is the set of windows in session state
  1.3105 +   * @param aURL is the single URL we're looking for
  1.3106 +   * @returns whether the window data contains only the single URL passed
  1.3107 +   */
  1.3108 +  _hasSingleTabWithURL: function(aWinData, aURL) {
  1.3109 +    if (aWinData &&
  1.3110 +        aWinData.length == 1 &&
  1.3111 +        aWinData[0].tabs &&
  1.3112 +        aWinData[0].tabs.length == 1 &&
  1.3113 +        aWinData[0].tabs[0].entries &&
  1.3114 +        aWinData[0].tabs[0].entries.length == 1) {
  1.3115 +      return aURL == aWinData[0].tabs[0].entries[0].url;
  1.3116 +    }
  1.3117 +    return false;
  1.3118 +  },
  1.3119 +
  1.3120 +  /**
  1.3121 +   * Determine if the tab state we're passed is something we should save. This
  1.3122 +   * is used when closing a tab or closing a window with a single tab
  1.3123 +   *
  1.3124 +   * @param aTabState
  1.3125 +   *        The current tab state
  1.3126 +   * @returns boolean
  1.3127 +   */
  1.3128 +  _shouldSaveTabState: function ssi_shouldSaveTabState(aTabState) {
  1.3129 +    // If the tab has only a transient about: history entry, no other
  1.3130 +    // session history, and no userTypedValue, then we don't actually want to
  1.3131 +    // store this tab's data.
  1.3132 +    return aTabState.entries.length &&
  1.3133 +           !(aTabState.entries.length == 1 &&
  1.3134 +                (aTabState.entries[0].url == "about:blank" ||
  1.3135 +                 aTabState.entries[0].url == "about:newtab") &&
  1.3136 +                 !aTabState.userTypedValue);
  1.3137 +  },
  1.3138 +
  1.3139 +  /**
  1.3140 +   * This is going to take a state as provided at startup (via
  1.3141 +   * nsISessionStartup.state) and split it into 2 parts. The first part
  1.3142 +   * (defaultState) will be a state that should still be restored at startup,
  1.3143 +   * while the second part (state) is a state that should be saved for later.
  1.3144 +   * defaultState will be comprised of windows with only pinned tabs, extracted
  1.3145 +   * from state. It will contain the cookies that go along with the history
  1.3146 +   * entries in those tabs. It will also contain window position information.
  1.3147 +   *
  1.3148 +   * defaultState will be restored at startup. state will be passed into
  1.3149 +   * LastSession and will be kept in case the user explicitly wants
  1.3150 +   * to restore the previous session (publicly exposed as restoreLastSession).
  1.3151 +   *
  1.3152 +   * @param state
  1.3153 +   *        The state, presumably from nsISessionStartup.state
  1.3154 +   * @returns [defaultState, state]
  1.3155 +   */
  1.3156 +  _prepDataForDeferredRestore: function ssi_prepDataForDeferredRestore(state) {
  1.3157 +    // Make sure that we don't modify the global state as provided by
  1.3158 +    // nsSessionStartup.state.
  1.3159 +    state = Cu.cloneInto(state, {});
  1.3160 +
  1.3161 +    let defaultState = { windows: [], selectedWindow: 1 };
  1.3162 +
  1.3163 +    state.selectedWindow = state.selectedWindow || 1;
  1.3164 +
  1.3165 +    // Look at each window, remove pinned tabs, adjust selectedindex,
  1.3166 +    // remove window if necessary.
  1.3167 +    for (let wIndex = 0; wIndex < state.windows.length;) {
  1.3168 +      let window = state.windows[wIndex];
  1.3169 +      window.selected = window.selected || 1;
  1.3170 +      // We're going to put the state of the window into this object
  1.3171 +      let pinnedWindowState = { tabs: [], cookies: []};
  1.3172 +      for (let tIndex = 0; tIndex < window.tabs.length;) {
  1.3173 +        if (window.tabs[tIndex].pinned) {
  1.3174 +          // Adjust window.selected
  1.3175 +          if (tIndex + 1 < window.selected)
  1.3176 +            window.selected -= 1;
  1.3177 +          else if (tIndex + 1 == window.selected)
  1.3178 +            pinnedWindowState.selected = pinnedWindowState.tabs.length + 2;
  1.3179 +            // + 2 because the tab isn't actually in the array yet
  1.3180 +
  1.3181 +          // Now add the pinned tab to our window
  1.3182 +          pinnedWindowState.tabs =
  1.3183 +            pinnedWindowState.tabs.concat(window.tabs.splice(tIndex, 1));
  1.3184 +          // We don't want to increment tIndex here.
  1.3185 +          continue;
  1.3186 +        }
  1.3187 +        tIndex++;
  1.3188 +      }
  1.3189 +
  1.3190 +      // At this point the window in the state object has been modified (or not)
  1.3191 +      // We want to build the rest of this new window object if we have pinnedTabs.
  1.3192 +      if (pinnedWindowState.tabs.length) {
  1.3193 +        // First get the other attributes off the window
  1.3194 +        WINDOW_ATTRIBUTES.forEach(function(attr) {
  1.3195 +          if (attr in window) {
  1.3196 +            pinnedWindowState[attr] = window[attr];
  1.3197 +            delete window[attr];
  1.3198 +          }
  1.3199 +        });
  1.3200 +        // We're just copying position data into the pinned window.
  1.3201 +        // Not copying over:
  1.3202 +        // - _closedTabs
  1.3203 +        // - extData
  1.3204 +        // - isPopup
  1.3205 +        // - hidden
  1.3206 +
  1.3207 +        // Assign a unique ID to correlate the window to be opened with the
  1.3208 +        // remaining data
  1.3209 +        window.__lastSessionWindowID = pinnedWindowState.__lastSessionWindowID
  1.3210 +                                     = "" + Date.now() + Math.random();
  1.3211 +
  1.3212 +        // Extract the cookies that belong with each pinned tab
  1.3213 +        this._splitCookiesFromWindow(window, pinnedWindowState);
  1.3214 +
  1.3215 +        // Actually add this window to our defaultState
  1.3216 +        defaultState.windows.push(pinnedWindowState);
  1.3217 +        // Remove the window from the state if it doesn't have any tabs
  1.3218 +        if (!window.tabs.length) {
  1.3219 +          if (wIndex + 1 <= state.selectedWindow)
  1.3220 +            state.selectedWindow -= 1;
  1.3221 +          else if (wIndex + 1 == state.selectedWindow)
  1.3222 +            defaultState.selectedIndex = defaultState.windows.length + 1;
  1.3223 +
  1.3224 +          state.windows.splice(wIndex, 1);
  1.3225 +          // We don't want to increment wIndex here.
  1.3226 +          continue;
  1.3227 +        }
  1.3228 +
  1.3229 +
  1.3230 +      }
  1.3231 +      wIndex++;
  1.3232 +    }
  1.3233 +
  1.3234 +    return [defaultState, state];
  1.3235 +  },
  1.3236 +
  1.3237 +  /**
  1.3238 +   * Splits out the cookies from aWinState into aTargetWinState based on the
  1.3239 +   * tabs that are in aTargetWinState.
  1.3240 +   * This alters the state of aWinState and aTargetWinState.
  1.3241 +   */
  1.3242 +  _splitCookiesFromWindow:
  1.3243 +    function ssi_splitCookiesFromWindow(aWinState, aTargetWinState) {
  1.3244 +    if (!aWinState.cookies || !aWinState.cookies.length)
  1.3245 +      return;
  1.3246 +
  1.3247 +    // Get the hosts for history entries in aTargetWinState
  1.3248 +    let cookieHosts = SessionCookies.getHostsForWindow(aTargetWinState);
  1.3249 +
  1.3250 +    // By creating a regex we reduce overhead and there is only one loop pass
  1.3251 +    // through either array (cookieHosts and aWinState.cookies).
  1.3252 +    let hosts = Object.keys(cookieHosts).join("|").replace("\\.", "\\.", "g");
  1.3253 +    // If we don't actually have any hosts, then we don't want to do anything.
  1.3254 +    if (!hosts.length)
  1.3255 +      return;
  1.3256 +    let cookieRegex = new RegExp(".*(" + hosts + ")");
  1.3257 +    for (let cIndex = 0; cIndex < aWinState.cookies.length;) {
  1.3258 +      if (cookieRegex.test(aWinState.cookies[cIndex].host)) {
  1.3259 +        aTargetWinState.cookies =
  1.3260 +          aTargetWinState.cookies.concat(aWinState.cookies.splice(cIndex, 1));
  1.3261 +        continue;
  1.3262 +      }
  1.3263 +      cIndex++;
  1.3264 +    }
  1.3265 +  },
  1.3266 +
  1.3267 +  /**
  1.3268 +   * Converts a JavaScript object into a JSON string
  1.3269 +   * (see http://www.json.org/ for more information).
  1.3270 +   *
  1.3271 +   * The inverse operation consists of JSON.parse(JSON_string).
  1.3272 +   *
  1.3273 +   * @param aJSObject is the object to be converted
  1.3274 +   * @returns the object's JSON representation
  1.3275 +   */
  1.3276 +  _toJSONString: function ssi_toJSONString(aJSObject) {
  1.3277 +    return JSON.stringify(aJSObject);
  1.3278 +  },
  1.3279 +
  1.3280 +  _sendRestoreCompletedNotifications: function ssi_sendRestoreCompletedNotifications() {
  1.3281 +    // not all windows restored, yet
  1.3282 +    if (this._restoreCount > 1) {
  1.3283 +      this._restoreCount--;
  1.3284 +      return;
  1.3285 +    }
  1.3286 +
  1.3287 +    // observers were already notified
  1.3288 +    if (this._restoreCount == -1)
  1.3289 +      return;
  1.3290 +
  1.3291 +    // This was the last window restored at startup, notify observers.
  1.3292 +    Services.obs.notifyObservers(null,
  1.3293 +      this._browserSetState ? NOTIFY_BROWSER_STATE_RESTORED : NOTIFY_WINDOWS_RESTORED,
  1.3294 +      "");
  1.3295 +
  1.3296 +    this._browserSetState = false;
  1.3297 +    this._restoreCount = -1;
  1.3298 +  },
  1.3299 +
  1.3300 +   /**
  1.3301 +   * Set the given window's busy state
  1.3302 +   * @param aWindow the window
  1.3303 +   * @param aValue the window's busy state
  1.3304 +   */
  1.3305 +  _setWindowStateBusyValue:
  1.3306 +    function ssi_changeWindowStateBusyValue(aWindow, aValue) {
  1.3307 +
  1.3308 +    this._windows[aWindow.__SSi].busy = aValue;
  1.3309 +
  1.3310 +    // Keep the to-be-restored state in sync because that is returned by
  1.3311 +    // getWindowState() as long as the window isn't loaded, yet.
  1.3312 +    if (!this._isWindowLoaded(aWindow)) {
  1.3313 +      let stateToRestore = this._statesToRestore[aWindow.__SS_restoreID].windows[0];
  1.3314 +      stateToRestore.busy = aValue;
  1.3315 +    }
  1.3316 +  },
  1.3317 +
  1.3318 +  /**
  1.3319 +   * Set the given window's state to 'not busy'.
  1.3320 +   * @param aWindow the window
  1.3321 +   */
  1.3322 +  _setWindowStateReady: function ssi_setWindowStateReady(aWindow) {
  1.3323 +    this._setWindowStateBusyValue(aWindow, false);
  1.3324 +    this._sendWindowStateEvent(aWindow, "Ready");
  1.3325 +  },
  1.3326 +
  1.3327 +  /**
  1.3328 +   * Set the given window's state to 'busy'.
  1.3329 +   * @param aWindow the window
  1.3330 +   */
  1.3331 +  _setWindowStateBusy: function ssi_setWindowStateBusy(aWindow) {
  1.3332 +    this._setWindowStateBusyValue(aWindow, true);
  1.3333 +    this._sendWindowStateEvent(aWindow, "Busy");
  1.3334 +  },
  1.3335 +
  1.3336 +  /**
  1.3337 +   * Dispatch an SSWindowState_____ event for the given window.
  1.3338 +   * @param aWindow the window
  1.3339 +   * @param aType the type of event, SSWindowState will be prepended to this string
  1.3340 +   */
  1.3341 +  _sendWindowStateEvent: function ssi_sendWindowStateEvent(aWindow, aType) {
  1.3342 +    let event = aWindow.document.createEvent("Events");
  1.3343 +    event.initEvent("SSWindowState" + aType, true, false);
  1.3344 +    aWindow.dispatchEvent(event);
  1.3345 +  },
  1.3346 +
  1.3347 +  /**
  1.3348 +   * Dispatch the SSTabRestored event for the given tab.
  1.3349 +   * @param aTab the which has been restored
  1.3350 +   */
  1.3351 +  _sendTabRestoredNotification: function ssi_sendTabRestoredNotification(aTab) {
  1.3352 +    let event = aTab.ownerDocument.createEvent("Events");
  1.3353 +    event.initEvent("SSTabRestored", true, false);
  1.3354 +    aTab.dispatchEvent(event);
  1.3355 +  },
  1.3356 +
  1.3357 +  /**
  1.3358 +   * @param aWindow
  1.3359 +   *        Window reference
  1.3360 +   * @returns whether this window's data is still cached in _statesToRestore
  1.3361 +   *          because it's not fully loaded yet
  1.3362 +   */
  1.3363 +  _isWindowLoaded: function ssi_isWindowLoaded(aWindow) {
  1.3364 +    return !aWindow.__SS_restoreID;
  1.3365 +  },
  1.3366 +
  1.3367 +  /**
  1.3368 +   * Replace "Loading..." with the tab label (with minimal side-effects)
  1.3369 +   * @param aString is the string the title is stored in
  1.3370 +   * @param aTabbrowser is a tabbrowser object, containing aTab
  1.3371 +   * @param aTab is the tab whose title we're updating & using
  1.3372 +   *
  1.3373 +   * @returns aString that has been updated with the new title
  1.3374 +   */
  1.3375 +  _replaceLoadingTitle : function ssi_replaceLoadingTitle(aString, aTabbrowser, aTab) {
  1.3376 +    if (aString == aTabbrowser.mStringBundle.getString("tabs.connecting")) {
  1.3377 +      aTabbrowser.setTabTitle(aTab);
  1.3378 +      [aString, aTab.label] = [aTab.label, aString];
  1.3379 +    }
  1.3380 +    return aString;
  1.3381 +  },
  1.3382 +
  1.3383 +  /**
  1.3384 +   * Resize this._closedWindows to the value of the pref, except in the case
  1.3385 +   * where we don't have any non-popup windows on Windows and Linux. Then we must
  1.3386 +   * resize such that we have at least one non-popup window.
  1.3387 +   */
  1.3388 +  _capClosedWindows : function ssi_capClosedWindows() {
  1.3389 +    if (this._closedWindows.length <= this._max_windows_undo)
  1.3390 +      return;
  1.3391 +    let spliceTo = this._max_windows_undo;
  1.3392 +#ifndef XP_MACOSX
  1.3393 +    let normalWindowIndex = 0;
  1.3394 +    // try to find a non-popup window in this._closedWindows
  1.3395 +    while (normalWindowIndex < this._closedWindows.length &&
  1.3396 +           !!this._closedWindows[normalWindowIndex].isPopup)
  1.3397 +      normalWindowIndex++;
  1.3398 +    if (normalWindowIndex >= this._max_windows_undo)
  1.3399 +      spliceTo = normalWindowIndex + 1;
  1.3400 +#endif
  1.3401 +    this._closedWindows.splice(spliceTo, this._closedWindows.length);
  1.3402 +  },
  1.3403 +
  1.3404 +  /**
  1.3405 +   * Clears the set of windows that are "resurrected" before writing to disk to
  1.3406 +   * make closing windows one after the other until shutdown work as expected.
  1.3407 +   *
  1.3408 +   * This function should only be called when we are sure that there has been
  1.3409 +   * a user action that indicates the browser is actively being used and all
  1.3410 +   * windows that have been closed before are not part of a series of closing
  1.3411 +   * windows.
  1.3412 +   */
  1.3413 +  _clearRestoringWindows: function ssi_clearRestoringWindows() {
  1.3414 +    for (let i = 0; i < this._closedWindows.length; i++) {
  1.3415 +      delete this._closedWindows[i]._shouldRestore;
  1.3416 +    }
  1.3417 +  },
  1.3418 +
  1.3419 +  /**
  1.3420 +   * Reset state to prepare for a new session state to be restored.
  1.3421 +   */
  1.3422 +  _resetRestoringState: function ssi_initRestoringState() {
  1.3423 +    TabRestoreQueue.reset();
  1.3424 +    this._tabsRestoringCount = 0;
  1.3425 +  },
  1.3426 +
  1.3427 +  /**
  1.3428 +   * Reset the restoring state for a particular tab. This will be called when
  1.3429 +   * removing a tab or when a tab needs to be reset (it's being overwritten).
  1.3430 +   *
  1.3431 +   * @param aTab
  1.3432 +   *        The tab that will be "reset"
  1.3433 +   */
  1.3434 +  _resetLocalTabRestoringState: function (aTab) {
  1.3435 +    let window = aTab.ownerDocument.defaultView;
  1.3436 +    let browser = aTab.linkedBrowser;
  1.3437 +
  1.3438 +    // Keep the tab's previous state for later in this method
  1.3439 +    let previousState = browser.__SS_restoreState;
  1.3440 +
  1.3441 +    // The browser is no longer in any sort of restoring state.
  1.3442 +    delete browser.__SS_restoreState;
  1.3443 +    this._browserEpochs.delete(browser.permanentKey);
  1.3444 +
  1.3445 +    aTab.removeAttribute("pending");
  1.3446 +    browser.removeAttribute("pending");
  1.3447 +
  1.3448 +    if (previousState == TAB_STATE_RESTORING) {
  1.3449 +      if (this._tabsRestoringCount)
  1.3450 +        this._tabsRestoringCount--;
  1.3451 +    } else if (previousState == TAB_STATE_NEEDS_RESTORE) {
  1.3452 +      // Make sure that the tab is removed from the list of tabs to restore.
  1.3453 +      // Again, this is normally done in restoreTabContent, but that isn't being called
  1.3454 +      // for this tab.
  1.3455 +      TabRestoreQueue.remove(aTab);
  1.3456 +    }
  1.3457 +  },
  1.3458 +
  1.3459 +  _resetTabRestoringState: function (tab) {
  1.3460 +    let browser = tab.linkedBrowser;
  1.3461 +    if (browser.__SS_restoreState) {
  1.3462 +      browser.messageManager.sendAsyncMessage("SessionStore:resetRestore", {});
  1.3463 +    }
  1.3464 +    this._resetLocalTabRestoringState(tab);
  1.3465 +  },
  1.3466 +
  1.3467 +  /**
  1.3468 +   * Each time a <browser> element is restored, we increment its "epoch". To
  1.3469 +   * check if a message from content-sessionStore.js is out of date, we can
  1.3470 +   * compare the epoch received with the message to the <browser> element's
  1.3471 +   * epoch. This function does that, and returns true if |epoch| is up-to-date
  1.3472 +   * with respect to |browser|.
  1.3473 +   */
  1.3474 +  isCurrentEpoch: function (browser, epoch) {
  1.3475 +    return this._browserEpochs.get(browser.permanentKey, 0) == epoch;
  1.3476 +  },
  1.3477 +
  1.3478 +};
  1.3479 +
  1.3480 +/**
  1.3481 + * Priority queue that keeps track of a list of tabs to restore and returns
  1.3482 + * the tab we should restore next, based on priority rules. We decide between
  1.3483 + * pinned, visible and hidden tabs in that and FIFO order. Hidden tabs are only
  1.3484 + * restored with restore_hidden_tabs=true.
  1.3485 + */
  1.3486 +let TabRestoreQueue = {
  1.3487 +  // The separate buckets used to store tabs.
  1.3488 +  tabs: {priority: [], visible: [], hidden: []},
  1.3489 +
  1.3490 +  // Preferences used by the TabRestoreQueue to determine which tabs
  1.3491 +  // are restored automatically and which tabs will be on-demand.
  1.3492 +  prefs: {
  1.3493 +    // Lazy getter that returns whether tabs are restored on demand.
  1.3494 +    get restoreOnDemand() {
  1.3495 +      let updateValue = () => {
  1.3496 +        let value = Services.prefs.getBoolPref(PREF);
  1.3497 +        let definition = {value: value, configurable: true};
  1.3498 +        Object.defineProperty(this, "restoreOnDemand", definition);
  1.3499 +        return value;
  1.3500 +      }
  1.3501 +
  1.3502 +      const PREF = "browser.sessionstore.restore_on_demand";
  1.3503 +      Services.prefs.addObserver(PREF, updateValue, false);
  1.3504 +      return updateValue();
  1.3505 +    },
  1.3506 +
  1.3507 +    // Lazy getter that returns whether pinned tabs are restored on demand.
  1.3508 +    get restorePinnedTabsOnDemand() {
  1.3509 +      let updateValue = () => {
  1.3510 +        let value = Services.prefs.getBoolPref(PREF);
  1.3511 +        let definition = {value: value, configurable: true};
  1.3512 +        Object.defineProperty(this, "restorePinnedTabsOnDemand", definition);
  1.3513 +        return value;
  1.3514 +      }
  1.3515 +
  1.3516 +      const PREF = "browser.sessionstore.restore_pinned_tabs_on_demand";
  1.3517 +      Services.prefs.addObserver(PREF, updateValue, false);
  1.3518 +      return updateValue();
  1.3519 +    },
  1.3520 +
  1.3521 +    // Lazy getter that returns whether we should restore hidden tabs.
  1.3522 +    get restoreHiddenTabs() {
  1.3523 +      let updateValue = () => {
  1.3524 +        let value = Services.prefs.getBoolPref(PREF);
  1.3525 +        let definition = {value: value, configurable: true};
  1.3526 +        Object.defineProperty(this, "restoreHiddenTabs", definition);
  1.3527 +        return value;
  1.3528 +      }
  1.3529 +
  1.3530 +      const PREF = "browser.sessionstore.restore_hidden_tabs";
  1.3531 +      Services.prefs.addObserver(PREF, updateValue, false);
  1.3532 +      return updateValue();
  1.3533 +    }
  1.3534 +  },
  1.3535 +
  1.3536 +  // Resets the queue and removes all tabs.
  1.3537 +  reset: function () {
  1.3538 +    this.tabs = {priority: [], visible: [], hidden: []};
  1.3539 +  },
  1.3540 +
  1.3541 +  // Adds a tab to the queue and determines its priority bucket.
  1.3542 +  add: function (tab) {
  1.3543 +    let {priority, hidden, visible} = this.tabs;
  1.3544 +
  1.3545 +    if (tab.pinned) {
  1.3546 +      priority.push(tab);
  1.3547 +    } else if (tab.hidden) {
  1.3548 +      hidden.push(tab);
  1.3549 +    } else {
  1.3550 +      visible.push(tab);
  1.3551 +    }
  1.3552 +  },
  1.3553 +
  1.3554 +  // Removes a given tab from the queue, if it's in there.
  1.3555 +  remove: function (tab) {
  1.3556 +    let {priority, hidden, visible} = this.tabs;
  1.3557 +
  1.3558 +    // We'll always check priority first since we don't
  1.3559 +    // have an indicator if a tab will be there or not.
  1.3560 +    let set = priority;
  1.3561 +    let index = set.indexOf(tab);
  1.3562 +
  1.3563 +    if (index == -1) {
  1.3564 +      set = tab.hidden ? hidden : visible;
  1.3565 +      index = set.indexOf(tab);
  1.3566 +    }
  1.3567 +
  1.3568 +    if (index > -1) {
  1.3569 +      set.splice(index, 1);
  1.3570 +    }
  1.3571 +  },
  1.3572 +
  1.3573 +  // Returns and removes the tab with the highest priority.
  1.3574 +  shift: function () {
  1.3575 +    let set;
  1.3576 +    let {priority, hidden, visible} = this.tabs;
  1.3577 +
  1.3578 +    let {restoreOnDemand, restorePinnedTabsOnDemand} = this.prefs;
  1.3579 +    let restorePinned = !(restoreOnDemand && restorePinnedTabsOnDemand);
  1.3580 +    if (restorePinned && priority.length) {
  1.3581 +      set = priority;
  1.3582 +    } else if (!restoreOnDemand) {
  1.3583 +      if (visible.length) {
  1.3584 +        set = visible;
  1.3585 +      } else if (this.prefs.restoreHiddenTabs && hidden.length) {
  1.3586 +        set = hidden;
  1.3587 +      }
  1.3588 +    }
  1.3589 +
  1.3590 +    return set && set.shift();
  1.3591 +  },
  1.3592 +
  1.3593 +  // Moves a given tab from the 'hidden' to the 'visible' bucket.
  1.3594 +  hiddenToVisible: function (tab) {
  1.3595 +    let {hidden, visible} = this.tabs;
  1.3596 +    let index = hidden.indexOf(tab);
  1.3597 +
  1.3598 +    if (index > -1) {
  1.3599 +      hidden.splice(index, 1);
  1.3600 +      visible.push(tab);
  1.3601 +    } else {
  1.3602 +      throw new Error("restore queue: hidden tab not found");
  1.3603 +    }
  1.3604 +  },
  1.3605 +
  1.3606 +  // Moves a given tab from the 'visible' to the 'hidden' bucket.
  1.3607 +  visibleToHidden: function (tab) {
  1.3608 +    let {visible, hidden} = this.tabs;
  1.3609 +    let index = visible.indexOf(tab);
  1.3610 +
  1.3611 +    if (index > -1) {
  1.3612 +      visible.splice(index, 1);
  1.3613 +      hidden.push(tab);
  1.3614 +    } else {
  1.3615 +      throw new Error("restore queue: visible tab not found");
  1.3616 +    }
  1.3617 +  }
  1.3618 +};
  1.3619 +
  1.3620 +// A map storing a closed window's state data until it goes aways (is GC'ed).
  1.3621 +// This ensures that API clients can still read (but not write) states of
  1.3622 +// windows they still hold a reference to but we don't.
  1.3623 +let DyingWindowCache = {
  1.3624 +  _data: new WeakMap(),
  1.3625 +
  1.3626 +  has: function (window) {
  1.3627 +    return this._data.has(window);
  1.3628 +  },
  1.3629 +
  1.3630 +  get: function (window) {
  1.3631 +    return this._data.get(window);
  1.3632 +  },
  1.3633 +
  1.3634 +  set: function (window, data) {
  1.3635 +    this._data.set(window, data);
  1.3636 +  },
  1.3637 +
  1.3638 +  remove: function (window) {
  1.3639 +    this._data.delete(window);
  1.3640 +  }
  1.3641 +};
  1.3642 +
  1.3643 +// A weak set of dirty windows. We use it to determine which windows we need to
  1.3644 +// recollect data for when getCurrentState() is called.
  1.3645 +let DirtyWindows = {
  1.3646 +  _data: new WeakMap(),
  1.3647 +
  1.3648 +  has: function (window) {
  1.3649 +    return this._data.has(window);
  1.3650 +  },
  1.3651 +
  1.3652 +  add: function (window) {
  1.3653 +    return this._data.set(window, true);
  1.3654 +  },
  1.3655 +
  1.3656 +  remove: function (window) {
  1.3657 +    this._data.delete(window);
  1.3658 +  },
  1.3659 +
  1.3660 +  clear: function (window) {
  1.3661 +    this._data.clear();
  1.3662 +  }
  1.3663 +};
  1.3664 +
  1.3665 +// The state from the previous session (after restoring pinned tabs). This
  1.3666 +// state is persisted and passed through to the next session during an app
  1.3667 +// restart to make the third party add-on warning not trash the deferred
  1.3668 +// session
  1.3669 +let LastSession = {
  1.3670 +  _state: null,
  1.3671 +
  1.3672 +  get canRestore() {
  1.3673 +    return !!this._state;
  1.3674 +  },
  1.3675 +
  1.3676 +  getState: function () {
  1.3677 +    return this._state;
  1.3678 +  },
  1.3679 +
  1.3680 +  setState: function (state) {
  1.3681 +    this._state = state;
  1.3682 +  },
  1.3683 +
  1.3684 +  clear: function () {
  1.3685 +    if (this._state) {
  1.3686 +      this._state = null;
  1.3687 +      Services.obs.notifyObservers(null, NOTIFY_LAST_SESSION_CLEARED, null);
  1.3688 +    }
  1.3689 +  }
  1.3690 +};

mercurial