Bug 648683 - Expose tabs on-demand preference [r=dietrich, r=gavin, ui-r=boriss]
authorPaul O’Shannessy <paul@oshannessy.com>
Mon, 15 Aug 2011 23:24:25 -0700
changeset 75362 32d45b1d060d51d1d7e9d9f33d98db849402274f
parent 75361 67142b6a15ab393573e477981a089ad6ea59057a
child 75363 85b5d609a73cddbf7f09e4b708266975c24e7731
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
reviewersdietrich, gavin, boriss
bugs648683
milestone8.0a1
Bug 648683 - Expose tabs on-demand preference [r=dietrich, r=gavin, ui-r=boriss] This removes the browser.sessionstore.max_concurrent_tabs integer preference in favor of a boolean preference. This disables the hidden way of disabling cascaded restore. The new browser.sessionstore.restore_on_demand preference is exposed in the "General" pref pane.
browser/app/profile/firefox.js
browser/base/content/test/tabview/browser_tabview_bug595601.js
browser/components/preferences/main.js
browser/components/preferences/main.xul
browser/components/sessionstore/src/nsSessionStore.js
browser/components/sessionstore/test/browser/browser_586068-cascaded_restore.js
browser/components/sessionstore/test/browser/browser_595601-restore_hidden.js
browser/components/sessionstore/test/browser/browser_599909.js
browser/components/sessionstore/test/browser/browser_607016.js
browser/components/sessionstore/test/browser/browser_636279.js
browser/locales/en-US/chrome/browser/preferences/main.dtd
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -778,22 +778,22 @@ pref("browser.sessionstore.privacy_level
 // how many tabs can be reopened (per window)
 pref("browser.sessionstore.max_tabs_undo", 10);
 // how many windows can be reopened (per session) - on non-OS X platforms this
 // pref may be ignored when dealing with pop-up windows to ensure proper startup
 pref("browser.sessionstore.max_windows_undo", 3);
 // number of crashes that can occur before the about:sessionrestore page is displayed
 // (this pref has no effect if more than 6 hours have passed since the last crash)
 pref("browser.sessionstore.max_resumed_crashes", 1);
-// The number of tabs that can restore concurrently:
-// < 0 = All tabs can restore at the same time
-//   0 = Only the selected tab in each window will be restored
-//       Other tabs won't be restored until they are selected
-//   N = The number of tabs to restore at the same time
-pref("browser.sessionstore.max_concurrent_tabs", 3);
+// restore_on_demand overrides MAX_CONCURRENT_TAB_RESTORES (sessionstore constant)
+// and restore_hidden_tabs. When true, tabs will not be restored until they are
+// focused (also applies to tabs that aren't visible). When false, the values
+// for MAX_CONCURRENT_TAB_RESTORES and restore_hidden_tabs are respected.
+// Selected tabs are always restored regardless of this pref.
+pref("browser.sessionstore.restore_on_demand", false);
 // Whether to automatically restore hidden tabs (i.e., tabs in other tab groups) or not
 pref("browser.sessionstore.restore_hidden_tabs", false);
 
 // allow META refresh by default
 pref("accessibility.blockautorefresh", false);
 
 // Whether history is enabled or not.
 pref("places.history.enabled", true);
--- a/browser/base/content/test/tabview/browser_tabview_bug595601.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug595601.js
@@ -30,24 +30,21 @@ function test() {
 
   Services.prefs.setBoolPref("browser.sessionstore.restore_hidden_tabs", false);
 
   TabsProgressListener.init();
 
   registerCleanupFunction(function () {
     TabsProgressListener.uninit();
 
-    Services.prefs.clearUserPref("browser.sessionstore.max_concurrent_tabs");
     Services.prefs.clearUserPref("browser.sessionstore.restore_hidden_tabs");
 
     ss.setBrowserState(stateBackup);
   });
 
-  Services.prefs.setIntPref("browser.sessionstore.max_concurrent_tabs", 3);
-
   TabView._initFrame(function () {
     executeSoon(testRestoreWithHiddenTabs);
   });
 }
 
 function testRestoreWithHiddenTabs() {
   let checked = false;
   let ssReady = false;
--- a/browser/components/preferences/main.js
+++ b/browser/components/preferences/main.js
@@ -48,16 +48,17 @@ var gMainPane = {
   {
     this._pane = document.getElementById("paneMain");
 
     // set up the "use current page" label-changing listener
     this._updateUseCurrentButton();
     window.addEventListener("focus", this._updateUseCurrentButton, false);
 
     this.updateBrowserStartupLastSession();
+    this.startupPagePrefChanged();
 
     // Notify observers that the UI is now ready
     Components.classes["@mozilla.org/observer-service;1"]
               .getService(Components.interfaces.nsIObserverService)
               .notifyObservers(window, "main-pane-loaded", null);
   },
 
   // HOME PAGE
@@ -76,16 +77,26 @@ var gMainPane = {
    *     2: the last page the user visited (DEPRECATED)
    *     3: windows and tabs from the last session (a.k.a. session restore)
    *
    *   The deprecated option is not exposed in UI; however, if the user has it
    *   selected and doesn't change the UI for this preference, the deprecated
    *   option is preserved.
    */
 
+  /**
+   * Enables/Disables the restore on demand checkbox.
+   */
+  startupPagePrefChanged: function ()
+  {
+    let startupPref = document.getElementById("browser.startup.page");
+    let restoreOnDemandPref = document.getElementById("browser.sessionstore.restore_on_demand");
+    restoreOnDemandPref.disabled = startupPref.value != 3;
+  },
+
   syncFromHomePref: function ()
   {
     let homePref = document.getElementById("browser.startup.homepage");
 
     // If the pref is set to about:home, set the value to "" to show the
     // placeholder text (about:home title).
     if (homePref.value.toLowerCase() == "about:home")
       return "";
--- a/browser/components/preferences/main.xul
+++ b/browser/components/preferences/main.xul
@@ -59,17 +59,21 @@
     <script type="application/javascript" src="chrome://browser/content/preferences/main.js"/>
 
     <preferences id="mainPreferences">
       <!-- XXX Button preferences -->
 
       <!-- Startup -->
       <preference id="browser.startup.page"
                   name="browser.startup.page"
-                  type="int"/>
+                  type="int"
+                  onchange="gMainPane.startupPagePrefChanged();"/>
+      <preference id="browser.sessionstore.restore_on_demand"
+                  name="browser.sessionstore.restore_on_demand"
+                  type="bool"/>
       <preference id="browser.startup.homepage"
                   name="browser.startup.homepage"
                   type="wstring"/>
 
       <preference id="pref.browser.homepage.disable_button.current_page"
                   name="pref.browser.homepage.disable_button.current_page"
                   type="bool"/>
       <preference id="pref.browser.homepage.disable_button.bookmark_page"
@@ -115,16 +119,23 @@
         <menulist id="browserStartupPage" preference="browser.startup.page">
           <menupopup>
             <menuitem label="&startupHomePage.label;"     value="1" id="browserStartupHomePage"/>
             <menuitem label="&startupBlankPage.label;"    value="0" id="browserStartupBlank"/>
             <menuitem label="&startupLastSession.label;"  value="3" id="browserStartupLastSession"/>
           </menupopup>
         </menulist>
       </hbox>
+      <hbox align="center">
+        <checkbox id="restoreOnDemand"
+                  label="&restoreOnDemand.label;"
+                  accesskey="&restoreOnDemand.accesskey;"
+                  class="indent"
+                  preference="browser.sessionstore.restore_on_demand"/>
+      </hbox>
       <separator class="thin"/>
       <hbox align="center">
         <label value="&homepage.label;" accesskey="&homepage.accesskey;" control="browserHomePage"/>
         <textbox id="browserHomePage" class="padded uri-element" flex="1"
                  type="autocomplete" autocompletesearch="history"
                  onsyncfrompreference="return gMainPane.syncFromHomePref();"
                  onsynctopreference="return gMainPane.syncToHomePref(this.value);"
                  placeholder="&abouthome.pageTitle;"
--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -70,16 +70,20 @@ const TAB_STATE_RESTORING = 2;
 
 const PRIVACY_NONE = 0;
 const PRIVACY_ENCRYPTED = 1;
 const PRIVACY_FULL = 2;
 
 const NOTIFY_WINDOWS_RESTORED = "sessionstore-windows-restored";
 const NOTIFY_BROWSER_STATE_RESTORED = "sessionstore-browser-state-restored";
 
+// Maximum number of tabs to restore simultaneously. Previously controlled by
+// the browser.sessionstore.max_concurrent_tabs pref.
+const MAX_CONCURRENT_TAB_RESTORES = 3;
+
 // global notifications observed
 const OBSERVING = [
   "domwindowopened", "domwindowclosed",
   "quit-application-requested", "quit-application-granted",
   "browser-lastwindow-close-granted",
   "quit-application", "browser:purge-session-history",
   "private-browsing", "browser:purge-domain-data",
   "private-browsing-change-granted"
@@ -224,19 +228,19 @@ SessionStoreService.prototype = {
 
   // whether the last window was closed and should be restored
   _restoreLastWindow: false,
 
   // tabs to restore in order
   _tabsToRestore: { visible: [], hidden: [] },
   _tabsRestoringCount: 0,
 
-  // number of tabs to restore concurrently, pref controlled.
-  _maxConcurrentTabRestores: null,
-  
+  // overrides MAX_CONCURRENT_TAB_RESTORES and _restoreHiddenTabs when true
+  _restoreOnDemand: false,
+
   // whether to restore hidden tabs or not, pref controlled.
   _restoreHiddenTabs: null,
 
   // The state from the previous session (after restoring pinned tabs). This
   // state is persisted and passed through to the next session during an app
   // restart to make the third party add-on warning not trash the deferred
   // session
   _lastSessionState: null,
@@ -276,28 +280,31 @@ SessionStoreService.prototype = {
   initService: function() {
     OBSERVING.forEach(function(aTopic) {
       Services.obs.addObserver(this, aTopic, true);
     }, this);
 
     var pbs = Cc["@mozilla.org/privatebrowsing;1"].
               getService(Ci.nsIPrivateBrowsingService);
     this._inPrivateBrowsing = pbs.privateBrowsingEnabled;
-    
+
+    // Do pref migration before we store any values and start observing changes
+    this._migratePrefs();
+
     // observe prefs changes so we can modify stored data to match
     this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
     this._prefBranch.addObserver("sessionstore.max_windows_undo", this, true);
     
     // this pref is only read at startup, so no need to observe it
     this._sessionhistory_max_entries =
       this._prefBranch.getIntPref("sessionhistory.max_entries");
 
-    this._maxConcurrentTabRestores =
-      this._prefBranch.getIntPref("sessionstore.max_concurrent_tabs");
-    this._prefBranch.addObserver("sessionstore.max_concurrent_tabs", this, true);
+    this._restoreOnDemand =
+      this._prefBranch.getBoolPref("sessionstore.restore_on_demand");
+    this._prefBranch.addObserver("sessionstore.restore_on_demand", this, true);
 
     this._restoreHiddenTabs =
       this._prefBranch.getBoolPref("sessionstore.restore_hidden_tabs");
     this._prefBranch.addObserver("sessionstore.restore_hidden_tabs", this, true);
 
     // Make sure gRestoreTabsProgressListener has a reference to sessionstore
     // so that it can make calls back in
     gRestoreTabsProgressListener.ss = this;
@@ -438,16 +445,29 @@ SessionStoreService.prototype = {
 
     // Make sure to break our cycle with the save timer
     if (this._saveTimer) {
       this._saveTimer.cancel();
       this._saveTimer = null;
     }
   },
 
+  _migratePrefs: function sss__migratePrefs() {
+    // Added For Firefox 8
+    // max_concurrent_tabs is going away. We're going to hard code a max value
+    // (MAX_CONCURRENT_TAB_RESTORES) and start using a boolean pref restore_on_demand.
+    if (this._prefBranch.prefHasUserValue("sessionstore.max_concurrent_tabs") &&
+        !this._prefBranch.prefHasUserValue("sessionstore.restore_on_demand")) {
+      let maxConcurrentTabs =
+        this._prefBranch.getIntPref("sessionstore.max_concurrent_tabs");
+      this._prefBranch.setBoolPref("sessionstore.restore_on_demand", maxConcurrentTabs == 0);
+      this._prefBranch.clearUserPref("sessionstore.max_concurrent_tabs");
+    }
+  },
+
   /**
    * Handle notifications
    */
   observe: function sss_observe(aSubject, aTopic, aData) {
     // for event listeners
     var _this = this;
 
     switch (aTopic) {
@@ -627,19 +647,19 @@ SessionStoreService.prototype = {
           this._resume_session_once_on_shutdown = null;
         }
         // either create the file with crash recovery information or remove it
         // (when _loadState is not STATE_RUNNING, that file is used for session resuming instead)
         if (!this._resume_from_crash)
           this._clearDisk();
         this.saveState(true);
         break;
-      case "sessionstore.max_concurrent_tabs":
-        this._maxConcurrentTabRestores =
-          this._prefBranch.getIntPref("sessionstore.max_concurrent_tabs");
+      case "sessionstore.restore_on_demand":
+        this._restoreOnDemand =
+          this._prefBranch.getBoolPref("sessionstore.restore_on_demand");
         break;
       case "sessionstore.restore_hidden_tabs":
         this._restoreHiddenTabs =
           this._prefBranch.getBoolPref("sessionstore.restore_hidden_tabs");
         break;
       }
       break;
     case "timer-callback": // timer call back for delayed saving
@@ -2901,17 +2921,17 @@ SessionStoreService.prototype = {
     event.initEvent("SSTabRestoring", true, false);
     tab.dispatchEvent(event);
 
     // Restore the history in the next tab
     aWindow.setTimeout(function(){
       _this.restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap);
     }, 0);
 
-    // This could cause us to ignore the max_concurrent_tabs pref a bit, but
+    // This could cause us to ignore MAX_CONCURRENT_TAB_RESTORES a bit, but
     // it ensures each window will have its selected tab loaded.
     if (aWindow.gBrowser.selectedBrowser == browser) {
       this.restoreTab(tab);
     }
     else {
       // Put the tab into the right bucket
       if (tabData.hidden)
         this._tabsToRestore.hidden.push(tab);
@@ -3037,18 +3057,18 @@ SessionStoreService.prototype = {
    *   if we have already reached the limit for number of tabs to restore
    */
   restoreNextTab: function sss_restoreNextTab() {
     // If we call in here while quitting, we don't actually want to do anything
     if (this._loadState == STATE_QUITTING)
       return;
 
     // If it's not possible to restore anything, then just bail out.
-    if (this._maxConcurrentTabRestores >= 0 &&
-        this._tabsRestoringCount >= this._maxConcurrentTabRestores)
+    if (this._restoreOnDemand ||
+        this._tabsRestoringCount >= MAX_CONCURRENT_TAB_RESTORES)
       return;
 
     // Look in visible, then hidden
     let nextTabArray;
     if (this._tabsToRestore.visible.length) {
       nextTabArray = this._tabsToRestore.visible;
     }
     else if (this._restoreHiddenTabs && this._tabsToRestore.hidden.length) {
--- a/browser/components/sessionstore/test/browser/browser_586068-cascaded_restore.js
+++ b/browser/components/sessionstore/test/browser/browser_586068-cascaded_restore.js
@@ -55,17 +55,17 @@ function test() {
 let tests = [test_cascade, test_select, test_multiWindowState,
              test_setWindowStateNoOverwrite, test_setWindowStateOverwrite,
              test_setBrowserStateInterrupted, test_reload,
              /* test_reloadReload, */ test_reloadCascadeSetup,
              /* test_reloadCascade */];
 function runNextTest() {
   // Reset the pref
   try {
-    Services.prefs.clearUserPref("browser.sessionstore.max_concurrent_tabs");
+    Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
   } catch (e) {}
 
   // set an empty state & run the next test, or finish
   if (tests.length) {
     // Enumerate windows and close everything but our primary window. We can't
     // use waitForFocus() because apparently it's buggy. See bug 599253.
     var windowsEnum = Services.wm.getEnumerator("navigator:browser");
     while (windowsEnum.hasMoreElements()) {
@@ -83,19 +83,16 @@ function runNextTest() {
   else {
     ss.setBrowserState(stateBackup);
     executeSoon(finish);
   }
 }
 
 
 function test_cascade() {
-  // Set the pref to 1 so we know exactly how many tabs should be restoring at any given time
-  Services.prefs.setIntPref("browser.sessionstore.max_concurrent_tabs", 1);
-
   // We have our own progress listener for this test, which we'll attach before our state is set
   let progressListener = {
     onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
       dump("\n\nload: " + aBrowser.currentURI.spec + "\n" + JSON.stringify(countTabs()) + "\n\n");
       if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
@@ -113,21 +110,21 @@ function test_cascade() {
   ] }] };
 
   let loadCount = 0;
   // Since our progress listener is fired before the one in sessionstore, our
   // expected counts look a little weird. This is because we inspect the state
   // before sessionstore has marked the tab as finished restoring and before it
   // starts restoring the next tab
   let expectedCounts = [
-    [5, 1, 0],
-    [4, 1, 1],
-    [3, 1, 2],
-    [2, 1, 3],
-    [1, 1, 4],
+    [3, 3, 0],
+    [2, 3, 1],
+    [1, 3, 2],
+    [0, 3, 3],
+    [0, 2, 4],
     [0, 1, 5]
   ];
 
   function test_cascade_progressCallback() {
     loadCount++;
     let counts = countTabs();
     let expected = expectedCounts[loadCount - 1];
 
@@ -144,19 +141,19 @@ function test_cascade() {
 
   // This progress listener will get attached before the listener in session store.
   window.gBrowser.addTabsProgressListener(progressListener);
   ss.setBrowserState(JSON.stringify(state));
 }
 
 
 function test_select() {
-  // Set the pref to 0 so we know exactly how many tabs should be restoring at
+  // Set the pref to true so we know exactly how many tabs should be restoring at
   // any given time. This guarantees that a finishing load won't start another.
-  Services.prefs.setIntPref("browser.sessionstore.max_concurrent_tabs", 0);
+  Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
 
   // We have our own progress listener for this test, which we'll attach before our state is set
   let progressListener = {
     onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
       if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
@@ -293,19 +290,16 @@ function test_multiWindowState() {
   Services.ww.registerNotification(windowObserver);
 
   window.gBrowser.addTabsProgressListener(progressListener);
   ss.setBrowserState(JSON.stringify(state));
 }
 
 
 function test_setWindowStateNoOverwrite() {
-  // Set the pref to 1 so we know exactly how many tabs should be restoring at any given time
-  Services.prefs.setIntPref("browser.sessionstore.max_concurrent_tabs", 1);
-
   // We have our own progress listener for this test, which we'll attach before our state is set
   let progressListener = {
     onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
       // We only care about load events when the tab still has
       // __SS_restoreState == TAB_STATE_RESTORING on it.
       // Since our listener is attached before the sessionstore one, this works out.
       if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
@@ -365,19 +359,16 @@ function test_setWindowStateNoOverwrite(
   }
 
   window.gBrowser.addTabsProgressListener(progressListener);
   ss.setWindowState(window, JSON.stringify(state1), true);
 }
 
 
 function test_setWindowStateOverwrite() {
-  // Set the pref to 1 so we know exactly how many tabs should be restoring at any given time
-  Services.prefs.setIntPref("browser.sessionstore.max_concurrent_tabs", 1);
-
   // We have our own progress listener for this test, which we'll attach before our state is set
   let progressListener = {
     onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
       // We only care about load events when the tab still has
       // __SS_restoreState == TAB_STATE_RESTORING on it.
       // Since our listener is attached before the sessionstore one, this works out.
       if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
@@ -437,19 +428,16 @@ function test_setWindowStateOverwrite() 
   }
 
   window.gBrowser.addTabsProgressListener(progressListener);
   ss.setWindowState(window, JSON.stringify(state1), true);
 }
 
 
 function test_setBrowserStateInterrupted() {
-  // Set the pref to 1 so we know exactly how many tabs should be restoring at any given time
-  Services.prefs.setIntPref("browser.sessionstore.max_concurrent_tabs", 1);
-
   // We have our own progress listener for this test, which we'll attach before our state is set
   let progressListener = {
     onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
       // We only care about load events when the tab still has
       // __SS_restoreState == TAB_STATE_RESTORING on it.
       // Since our listener is attached before the sessionstore one, this works out.
       if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
@@ -556,19 +544,19 @@ function test_setBrowserStateInterrupted
   Services.ww.registerNotification(windowObserver);
 
   window.gBrowser.addTabsProgressListener(progressListener);
   ss.setBrowserState(JSON.stringify(state1));
 }
 
 
 function test_reload() {
-  // Set the pref to 0 so we know exactly how many tabs should be restoring at
+  // Set the pref to true so we know exactly how many tabs should be restoring at
   // any given time. This guarantees that a finishing load won't start another.
-  Services.prefs.setIntPref("browser.sessionstore.max_concurrent_tabs", 0);
+  Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
 
   // We have our own progress listener for this test, which we'll attach before our state is set
   let progressListener = {
     onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
       if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
--- a/browser/components/sessionstore/test/browser/browser_595601-restore_hidden.js
+++ b/browser/components/sessionstore/test/browser/browser_595601-restore_hidden.js
@@ -16,28 +16,25 @@ let state = {windows:[{tabs:[
   {entries:[{url:"http://example.com#7"}], hidden: true},
   {entries:[{url:"http://example.com#8"}], hidden: true}
 ]}]};
 
 function test() {
   waitForExplicitFinish();
 
   registerCleanupFunction(function () {
-    Services.prefs.clearUserPref("browser.sessionstore.max_concurrent_tabs");
     Services.prefs.clearUserPref("browser.sessionstore.restore_hidden_tabs");
 
     TabsProgressListener.uninit();
 
     ss.setBrowserState(stateBackup);
   });
 
   TabsProgressListener.init();
 
-  Services.prefs.setIntPref("browser.sessionstore.max_concurrent_tabs", 3);
-
   // First stage: restoreHiddenTabs = true
   // Second stage: restoreHiddenTabs = false
   test_loadTabs(true, function () {
     test_loadTabs(false, function () {
       waitForFocus(finish);
     });
   });
 }
--- a/browser/components/sessionstore/test/browser/browser_599909.js
+++ b/browser/components/sessionstore/test/browser/browser_599909.js
@@ -38,29 +38,29 @@
 const TAB_STATE_NEEDS_RESTORE = 1;
 const TAB_STATE_RESTORING = 2;
 
 let stateBackup = ss.getBrowserState();
 
 function cleanup() {
   // Reset the pref
   try {
-    Services.prefs.clearUserPref("browser.sessionstore.max_concurrent_tabs");
+    Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
   } catch (e) {}
   ss.setBrowserState(stateBackup);
   executeSoon(finish);
 }
 
 function test() {
   /** Bug 599909 - to-be-reloaded tabs don't show up in switch-to-tab **/
   waitForExplicitFinish();
 
-  // Set the pref to 0 so we know exactly how many tabs should be restoring at
+  // Set the pref to true so we know exactly how many tabs should be restoring at
   // any given time. This guarantees that a finishing load won't start another.
-  Services.prefs.setIntPref("browser.sessionstore.max_concurrent_tabs", 0);
+  Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
 
   let state = { windows: [{ tabs: [
     { entries: [{ url: "http://example.org/#1" }] },
     { entries: [{ url: "http://example.org/#2" }] },
     { entries: [{ url: "http://example.org/#3" }] },
     { entries: [{ url: "http://example.org/#4" }] }
   ], selected: 1 }] };
 
--- a/browser/components/sessionstore/test/browser/browser_607016.js
+++ b/browser/components/sessionstore/test/browser/browser_607016.js
@@ -38,29 +38,29 @@
 const TAB_STATE_NEEDS_RESTORE = 1;
 const TAB_STATE_RESTORING = 2;
 
 let stateBackup = ss.getBrowserState();
 
 function cleanup() {
   // Reset the pref
   try {
-    Services.prefs.clearUserPref("browser.sessionstore.max_concurrent_tabs");
+    Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
   } catch (e) {}
   ss.setBrowserState(stateBackup);
   executeSoon(finish);
 }
 
 function test() {
   /** Bug 607016 - If a tab is never restored, attributes (eg. hidden) aren't updated correctly **/
   waitForExplicitFinish();
 
-  // Set the pref to 0 so we know exactly how many tabs should be restoring at
+  // Set the pref to true so we know exactly how many tabs should be restoring at
   // any given time. This guarantees that a finishing load won't start another.
-  Services.prefs.setIntPref("browser.sessionstore.max_concurrent_tabs", 0);
+  Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
 
   // We have our own progress listener for this test, which we'll attach before our state is set
   let progressListener = {
     onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
       if (aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
           aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
--- a/browser/components/sessionstore/test/browser/browser_636279.js
+++ b/browser/components/sessionstore/test/browser/browser_636279.js
@@ -8,43 +8,42 @@ let stateBackup = ss.getBrowserState();
 
 let statePinned = {windows:[{tabs:[
   {entries:[{url:"http://example.com#1"}], pinned: true}
 ]}]};
 
 let state = {windows:[{tabs:[
   {entries:[{url:"http://example.com#1"}]},
   {entries:[{url:"http://example.com#2"}]},
-  {entries:[{url:"http://example.com#3"}]}
+  {entries:[{url:"http://example.com#3"}]},
+  {entries:[{url:"http://example.com#4"}]},
 ]}]};
 
 function test() {
   waitForExplicitFinish();
 
   registerCleanupFunction(function () {
-    Services.prefs.clearUserPref("browser.sessionstore.max_concurrent_tabs");
     TabsProgressListener.uninit();
     ss.setBrowserState(stateBackup);
   });
 
-  Services.prefs.setIntPref("browser.sessionstore.max_concurrent_tabs", 2);
 
   TabsProgressListener.init();
 
   window.addEventListener("SSWindowStateReady", function onReady() {
     window.removeEventListener("SSWindowStateReady", onReady, false);
 
     let firstProgress = true;
 
     TabsProgressListener.setCallback(function (needsRestore, isRestoring) {
       if (firstProgress) {
         firstProgress = false;
-        is(isRestoring, 2, "restoring 2 tabs concurrently");
+        is(isRestoring, 3, "restoring 3 tabs concurrently");
       } else {
-        ok(isRestoring < 3, "restoring max. 2 tabs concurrently");
+        ok(isRestoring <= 3, "restoring max. 2 tabs concurrently");
       }
 
       if (0 == needsRestore) {
         TabsProgressListener.unsetCallback();
         waitForFocus(finish);
       }
     });
 
--- a/browser/locales/en-US/chrome/browser/preferences/main.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/main.dtd
@@ -1,15 +1,18 @@
 <!ENTITY startup.label             "Startup">
 
 <!ENTITY startupPage.label         "When &brandShortName; starts:">
 <!ENTITY startupPage.accesskey     "s">
 <!ENTITY startupHomePage.label     "Show my home page">
 <!ENTITY startupBlankPage.label    "Show a blank page">
 <!ENTITY startupLastSession.label  "Show my windows and tabs from last time">
+<!ENTITY restoreOnDemand.label     "Don’t load tabs until selected">
+<!ENTITY restoreOnDemand.accesskey "d">
+
 <!ENTITY homepage.label            "Home Page:">
 <!ENTITY homepage.accesskey        "P">
 <!ENTITY useCurrentPage.label      "Use Current Page">
 <!ENTITY useCurrentPage.accesskey  "C">
 <!ENTITY useMultiple.label         "Use Current Pages">
 <!ENTITY useBookmark.label         "Use Bookmark">
 <!ENTITY useBookmark.accesskey     "B">
 <!ENTITY restoreDefault.label      "Restore to Default">