mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-14 13:55:43 +00:00
Bug 586068 - Cascade page loads when restoring [r=dietrich, a=blocking]
This commit is contained in:
parent
6455f29ae1
commit
8ee118773b
@ -800,6 +800,8 @@ 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);
|
||||
// number of tabs to restore concurrently
|
||||
pref("browser.sessionstore.max_concurrent_tabs", 3);
|
||||
|
||||
// allow META refresh by default
|
||||
pref("accessibility.blockautorefresh", false);
|
||||
|
@ -114,6 +114,9 @@ const CAPABILITIES = [
|
||||
// that gets saved to disk nor part of the strings returned by the API.
|
||||
const INTERNAL_KEYS = ["_tabStillLoading", "_hosts", "_formDataSaved"];
|
||||
|
||||
// These are tab events that we listen to.
|
||||
const TAB_EVENTS = ["TabOpen", "TabClose", "TabSelect", "TabShow", "TabHide"];
|
||||
|
||||
#ifndef XP_WIN
|
||||
#define BROKEN_WM_Z_ORDER
|
||||
#endif
|
||||
@ -201,6 +204,13 @@ 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,
|
||||
|
||||
// The state from the previous session (after restoring pinned tabs)
|
||||
_lastSessionState: null,
|
||||
|
||||
@ -259,6 +269,14 @@ SessionStoreService.prototype = {
|
||||
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);
|
||||
|
||||
// Make sure gRestoreTabsProgressListener has a reference to sessionstore
|
||||
// so that it can make calls back in
|
||||
gRestoreTabsProgressListener.ss = this;
|
||||
|
||||
// get file references
|
||||
this._sessionFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
|
||||
this._sessionFileBackup = this._sessionFile.clone();
|
||||
@ -348,6 +366,13 @@ SessionStoreService.prototype = {
|
||||
// save all data for session resuming
|
||||
this.saveState(true);
|
||||
|
||||
// clear out _tabsToRestore in case it's still holding refs
|
||||
this._tabsToRestore.visible = null;
|
||||
this._tabsToRestore.hidden = null;
|
||||
|
||||
// remove the ref to us from the progress listener
|
||||
gRestoreTabsProgressListener.ss = null;
|
||||
|
||||
// Make sure to break our cycle with the save timer
|
||||
if (this._saveTimer) {
|
||||
this._saveTimer.cancel();
|
||||
@ -403,6 +428,7 @@ SessionStoreService.prototype = {
|
||||
this._forEachBrowserWindow(function(aWindow) {
|
||||
Array.forEach(aWindow.gBrowser.browsers, function(aBrowser) {
|
||||
delete aBrowser.__SS_data;
|
||||
delete aBrowser.__SS_needsRestore;
|
||||
});
|
||||
openWindows[aWindow.__SSi] = true;
|
||||
});
|
||||
@ -507,6 +533,10 @@ SessionStoreService.prototype = {
|
||||
this._clearDisk();
|
||||
this.saveState(true);
|
||||
break;
|
||||
case "sessionstore.max_concurrent_tabs":
|
||||
this._maxConcurrentTabRestores =
|
||||
this._prefBranch.getIntPref("sessionstore.max_concurrent_tabs");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "timer-callback": // timer call back for delayed saving
|
||||
@ -547,6 +577,9 @@ SessionStoreService.prototype = {
|
||||
// to objects that can change (specifically this._windows[x]).
|
||||
this._stateBackup = JSON.parse(this._toJSONString(this._getCurrentState(true)));
|
||||
}
|
||||
// Make sure _tabsToRestore is cleared. It will be repopulated when
|
||||
// entering/exiting private browsing (by calls to setBrowserState).
|
||||
this._resetRestoringState();
|
||||
break;
|
||||
}
|
||||
},
|
||||
@ -586,6 +619,12 @@ SessionStoreService.prototype = {
|
||||
case "TabSelect":
|
||||
this.onTabSelect(win);
|
||||
break;
|
||||
case "TabShow":
|
||||
this.onTabShow(aEvent.originalTarget);
|
||||
break;
|
||||
case "TabHide":
|
||||
this.onTabHide(aEvent.originalTarget);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@ -691,10 +730,10 @@ SessionStoreService.prototype = {
|
||||
for (let i = 0; i < tabbrowser.tabs.length; i++) {
|
||||
this.onTabAdd(aWindow, tabbrowser.tabs[i], true);
|
||||
}
|
||||
// notification of tab add/remove/selection
|
||||
tabbrowser.tabContainer.addEventListener("TabOpen", this, true);
|
||||
tabbrowser.tabContainer.addEventListener("TabClose", this, true);
|
||||
tabbrowser.tabContainer.addEventListener("TabSelect", this, true);
|
||||
// notification of tab add/remove/selection/show/hide
|
||||
TAB_EVENTS.forEach(function(aEvent) {
|
||||
tabbrowser.tabContainer.addEventListener(aEvent, this, true);
|
||||
}, this);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -726,10 +765,13 @@ SessionStoreService.prototype = {
|
||||
|
||||
var tabbrowser = aWindow.gBrowser;
|
||||
|
||||
tabbrowser.tabContainer.removeEventListener("TabOpen", this, true);
|
||||
tabbrowser.tabContainer.removeEventListener("TabClose", this, true);
|
||||
tabbrowser.tabContainer.removeEventListener("TabSelect", this, true);
|
||||
|
||||
TAB_EVENTS.forEach(function(aEvent) {
|
||||
tabbrowser.tabContainer.removeEventListener(aEvent, this, true);
|
||||
}, this);
|
||||
|
||||
// remove the progress listener for this window
|
||||
tabbrowser.removeTabsProgressListener(gRestoreTabsProgressListener);
|
||||
|
||||
let winData = this._windows[aWindow.__SSi];
|
||||
if (this._loadState == STATE_RUNNING) { // window not closed during a regular shut-down
|
||||
// update all window data for a last time
|
||||
@ -809,6 +851,18 @@ SessionStoreService.prototype = {
|
||||
|
||||
delete browser.__SS_data;
|
||||
|
||||
// If this tab was in the middle of restoring, we want to restore the next
|
||||
// tab. If the tab hasn't been restored, we want to remove it from the array.
|
||||
if (browser.__SS_restoring) {
|
||||
this.restoreNextTab(true);
|
||||
}
|
||||
else if (browser.__SS_needsRestore) {
|
||||
if (aTab.hidden)
|
||||
this._tabsToRestore.hidden.splice(this._tabsToRestore.hidden.indexOf(aTab));
|
||||
else
|
||||
this._tabsToRestore.visible.splice(this._tabsToRestore.visible.indexOf(aTab));
|
||||
}
|
||||
|
||||
if (!aNoNotification) {
|
||||
this.saveStateDelayed(aWindow);
|
||||
}
|
||||
@ -902,11 +956,37 @@ SessionStoreService.prototype = {
|
||||
if (this._loadState == STATE_RUNNING) {
|
||||
this._windows[aWindow.__SSi].selected = aWindow.gBrowser.tabContainer.selectedIndex;
|
||||
|
||||
let tab = aWindow.gBrowser.selectedTab;
|
||||
// If __SS_needsRestore is still on the browser, then we haven't restored
|
||||
// this tab yet. Explicitly call restoreTab to kick off the restore.
|
||||
if (tab.linkedBrowser.__SS_needsRestore) {
|
||||
this._tabsToRestore.visible.splice(this._tabsToRestore.visible.indexOf(tab));
|
||||
this.restoreTab(tab);
|
||||
}
|
||||
|
||||
// attempt to update the current URL we send in a crash report
|
||||
this._updateCrashReportURL(aWindow);
|
||||
}
|
||||
},
|
||||
|
||||
onTabShow: function sss_onTabShow(aTab) {
|
||||
// If the tab hasn't been restored yet, move it into the right _tabsToRestore bucket
|
||||
if (aTab.linkedBrowser.__SS_needsRestore) {
|
||||
this._tabsToRestore.hidden.splice(this._tabsToRestore.hidden.indexOf(aTab));
|
||||
// Just put it at the end of the list of visible tabs;
|
||||
this._tabsToRestore.visible.push(aTab);
|
||||
}
|
||||
},
|
||||
|
||||
onTabHide: function sss_onTabHide(aTab) {
|
||||
// If the tab hasn't been restored yet, move it into the right _tabsToRestore bucket
|
||||
if (aTab.linkedBrowser.__SS_needsRestore) {
|
||||
this._tabsToRestore.visible.splice(this._tabsToRestore.visible.indexOf(aTab));
|
||||
// Just put it at the end of the list of hidden tabs;
|
||||
this._tabsToRestore.hidden.push(aTab);
|
||||
}
|
||||
},
|
||||
|
||||
/* ........ nsISessionStore API .............. */
|
||||
|
||||
getBrowserState: function sss_getBrowserState() {
|
||||
@ -925,6 +1005,9 @@ SessionStoreService.prototype = {
|
||||
|
||||
this._browserSetState = true;
|
||||
|
||||
// Make sure _tabsToRestore is emptied out
|
||||
this._resetRestoringState();
|
||||
|
||||
var window = this._getMostRecentBrowserWindow();
|
||||
if (!window) {
|
||||
this._restoreCount = 1;
|
||||
@ -2173,7 +2256,14 @@ SessionStoreService.prototype = {
|
||||
restoreHistoryPrecursor:
|
||||
function sss_restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab, aIx, aCount) {
|
||||
var tabbrowser = aWindow.gBrowser;
|
||||
|
||||
|
||||
// In order for setTabState to add gTabsProgressListener, we want to add it
|
||||
// here. We'll only attempt to do it when aCount == 0 though so we don't add
|
||||
// it multiple times.
|
||||
if (aCount == 0 &&
|
||||
tabbrowser.mTabsProgressListeners.indexOf(gRestoreTabsProgressListener) == -1)
|
||||
tabbrowser.addTabsProgressListener(gRestoreTabsProgressListener);
|
||||
|
||||
// make sure that all browsers and their histories are available
|
||||
// - if one's not, resume this check in 100ms (repeat at most 10 times)
|
||||
for (var t = aIx; t < aTabs.length; t++) {
|
||||
@ -2206,6 +2296,12 @@ SessionStoreService.prototype = {
|
||||
tab.hidden = tabData.hidden;
|
||||
|
||||
tabData._tabStillLoading = true;
|
||||
|
||||
// keep the data around to prevent dataloss in case
|
||||
// a tab gets closed before it's been properly restored
|
||||
browser.__SS_data = tabData;
|
||||
browser.__SS_needsRestore = true;
|
||||
|
||||
if (!tabData.entries || tabData.entries.length == 0) {
|
||||
// make sure to blank out this tab's content
|
||||
// (just purging the tab's history won't be enough)
|
||||
@ -2217,17 +2313,16 @@ SessionStoreService.prototype = {
|
||||
|
||||
tab.setAttribute("busy", "true");
|
||||
tabbrowser.updateIcon(tab);
|
||||
tabbrowser.setTabTitleLoading(tab);
|
||||
|
||||
// wall-paper fix for bug 439675: make sure that the URL to be loaded
|
||||
// is always visible in the address bar
|
||||
let activeIndex = (tabData.index || tabData.entries.length) - 1;
|
||||
let activePageData = tabData.entries[activeIndex] || null;
|
||||
browser.userTypedValue = activePageData ? activePageData.url || null : null;
|
||||
|
||||
// keep the data around to prevent dataloss in case
|
||||
// a tab gets closed before it's been properly restored
|
||||
browser.__SS_data = tabData;
|
||||
|
||||
// If the page has a title, set it.
|
||||
if (activePageData && activePageData.title)
|
||||
tab.label = activePageData.title;
|
||||
}
|
||||
|
||||
if (aTabs.length > 0) {
|
||||
@ -2356,39 +2451,124 @@ SessionStoreService.prototype = {
|
||||
var event = aWindow.document.createEvent("Events");
|
||||
event.initEvent("SSTabRestoring", true, false);
|
||||
tab.dispatchEvent(event);
|
||||
|
||||
let activeIndex = (tabData.index || tabData.entries.length) - 1;
|
||||
if (activeIndex >= tabData.entries.length)
|
||||
activeIndex = tabData.entries.length - 1;
|
||||
try {
|
||||
if (activeIndex >= 0)
|
||||
browser.webNavigation.gotoIndex(activeIndex);
|
||||
}
|
||||
catch (ex) {
|
||||
// ignore page load errors
|
||||
tab.removeAttribute("busy");
|
||||
}
|
||||
|
||||
if (tabData.entries.length > 0) {
|
||||
// restore those aspects of the currently active documents
|
||||
// which are not preserved in the plain history entries
|
||||
// (mainly scroll state and text data)
|
||||
browser.__SS_restore_data = tabData.entries[activeIndex] || {};
|
||||
browser.__SS_restore_pageStyle = tabData.pageStyle || "";
|
||||
browser.__SS_restore_tab = tab;
|
||||
}
|
||||
|
||||
// Handle userTypedValue. Setting userTypedValue seems to update gURLbar
|
||||
// as needed. Calling loadURI will cancel form filling in restoreDocument_proxy
|
||||
if (tabData.userTypedValue) {
|
||||
browser.userTypedValue = tabData.userTypedValue;
|
||||
if (tabData.userTypedClear)
|
||||
browser.loadURI(tabData.userTypedValue, null, null, true);
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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);
|
||||
else
|
||||
this._tabsToRestore.visible.push(tab);
|
||||
this.restoreNextTab();
|
||||
}
|
||||
},
|
||||
|
||||
restoreTab: function(aTab) {
|
||||
let browser = aTab.linkedBrowser;
|
||||
let tabData = browser.__SS_data;
|
||||
|
||||
// There are cases within where we haven't actually started a load and so we
|
||||
// should call restoreNextTab. We don't want to do it immediately though
|
||||
// since we might not set userTypedValue in a timely fashion.
|
||||
let shouldRestoreNextTab = false;
|
||||
|
||||
// Increase our internal count.
|
||||
this._tabsRestoringCount++;
|
||||
|
||||
delete browser.__SS_needsRestore;
|
||||
|
||||
let activeIndex = (tabData.index || tabData.entries.length) - 1;
|
||||
if (activeIndex >= tabData.entries.length)
|
||||
activeIndex = tabData.entries.length - 1;
|
||||
|
||||
// Attach data that will be restored on "load" event, after tab is restored.
|
||||
if (activeIndex > -1) {
|
||||
// restore those aspects of the currently active documents which are not
|
||||
// preserved in the plain history entries (mainly scroll state and text data)
|
||||
browser.__SS_restore_data = tabData.entries[activeIndex] || {};
|
||||
browser.__SS_restore_pageStyle = tabData.pageStyle || "";
|
||||
browser.__SS_restore_tab = aTab;
|
||||
|
||||
try {
|
||||
// Get the tab title (set in restoreHistoryPrecursor) for later
|
||||
let label = aTab.label;
|
||||
browser.__SS_restoring = true;
|
||||
browser.webNavigation.gotoIndex(activeIndex);
|
||||
// gotoIndex will force the "loading" string, so set the title
|
||||
aTab.label = label;
|
||||
}
|
||||
catch (ex) {
|
||||
// ignore page load errors
|
||||
aTab.removeAttribute("busy");
|
||||
shouldRestoreNextTab = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If there is no active index, then we'll never set __SS_restore_data
|
||||
// and even if there is a load event, we won't kick of the next tab
|
||||
// (restoreDocument won't get called), so do it ourselves.
|
||||
shouldRestoreNextTab = true;
|
||||
}
|
||||
|
||||
// Handle userTypedValue. Setting userTypedValue seems to update gURLbar
|
||||
// as needed. Calling loadURI will cancel form filling in restoreDocument
|
||||
if (tabData.userTypedValue) {
|
||||
browser.userTypedValue = tabData.userTypedValue;
|
||||
if (tabData.userTypedClear) {
|
||||
browser.__SS_restoring = true;
|
||||
browser.loadURI(tabData.userTypedValue, null, null, true);
|
||||
}
|
||||
else {
|
||||
shouldRestoreNextTab = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldRestoreNextTab)
|
||||
this.restoreNextTab(true);
|
||||
},
|
||||
|
||||
restoreNextTab: function sss_restoreNextTab(aFromTabFinished) {
|
||||
// If we call in here while quitting, we don't actually want to do anything
|
||||
if (this._loadState == STATE_QUITTING)
|
||||
return;
|
||||
if (aFromTabFinished)
|
||||
this._tabsRestoringCount--;
|
||||
|
||||
// If it's not possible to restore anything, then just bail out.
|
||||
if (this._maxConcurrentTabRestores >= 0 &&
|
||||
this._tabsRestoringCount >= this._maxConcurrentTabRestores)
|
||||
return;
|
||||
|
||||
// Look in visible, then hidden
|
||||
let nextTabArray;
|
||||
if (this._tabsToRestore.visible.length) {
|
||||
nextTabArray = this._tabsToRestore.visible;
|
||||
}
|
||||
else if (this._tabsToRestore.hidden.length) {
|
||||
nextTabArray = this._tabsToRestore.hidden;
|
||||
}
|
||||
|
||||
if (nextTabArray) {
|
||||
let tab = nextTabArray.shift();
|
||||
this.restoreTab(tab);
|
||||
}
|
||||
else {
|
||||
// Remove the progress listener from windows. It will get re-added as needed.
|
||||
this._forEachBrowserWindow(function(aWindow) {
|
||||
// This won't fail since removeTabsProgressListener just filters. It
|
||||
// doesn't attempt to splice.
|
||||
aWindow.gBrowser.removeTabsProgressListener(gRestoreTabsProgressListener)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -3302,6 +3482,15 @@ SessionStoreService.prototype = {
|
||||
this._closedWindows.splice(spliceTo);
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset state to prepare for a new session state to be restored.
|
||||
*/
|
||||
_resetRestoringState: function sss__initRestoringState() {
|
||||
//
|
||||
this._tasToRestore = { visible: [], hidden: [] };
|
||||
this._tabsRestoringCount = 0;
|
||||
},
|
||||
|
||||
/* ........ Storage API .............. */
|
||||
|
||||
/**
|
||||
@ -3427,6 +3616,25 @@ let XPathHelper = {
|
||||
}
|
||||
};
|
||||
|
||||
// This is used to help meter the number of restoring tabs. This is the control
|
||||
// point for telling the next tab to restore. It gets attached to each gBrowser
|
||||
// via gBrowser.addTabsProgressListener
|
||||
let gRestoreTabsProgressListener = {
|
||||
ss: null,
|
||||
onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
|
||||
// Ignore state changes on browsers that we've already restored
|
||||
if (!aBrowser.__SS_restoring)
|
||||
return;
|
||||
|
||||
if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
|
||||
delete aBrowser.__SS_restoring;
|
||||
this.ss.restoreNextTab(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// see nsPrivateBrowsingService.js
|
||||
String.prototype.hasRootDomain = function hasRootDomain(aDomain)
|
||||
{
|
||||
|
@ -117,6 +117,7 @@ _BROWSER_TEST_FILES = \
|
||||
browser_579879.js \
|
||||
browser_580512.js \
|
||||
browser_586147.js \
|
||||
browser_586068-cascaded_restore.js \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_BROWSER_TEST_FILES)
|
||||
|
@ -0,0 +1,245 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is sessionstore test code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Paul O’Shannessy <paul@oshannessy.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
let ss = Cc["@mozilla.org/browser/sessionstore;1"].
|
||||
getService(Ci.nsISessionStore);
|
||||
|
||||
let stateBackup = ss.getBrowserState();
|
||||
|
||||
|
||||
function test() {
|
||||
/** Test for Bug 586068 - Cascade page loads when restoring **/
|
||||
waitForExplicitFinish();
|
||||
runNextTest();
|
||||
}
|
||||
|
||||
let tests = [test_cascade, test_select];
|
||||
function runNextTest() {
|
||||
if (tests.length) {
|
||||
ss.setWindowState(window,
|
||||
JSON.stringify({ windows: [{ tabs: [{ url: 'about:blank' }], }] }),
|
||||
true);
|
||||
executeSoon(tests.shift());
|
||||
}
|
||||
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) {
|
||||
if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
|
||||
test_cascade_progressCallback();
|
||||
}
|
||||
}
|
||||
|
||||
let state = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.com" }], extData: { "uniq": r() } }
|
||||
] }] };
|
||||
|
||||
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],
|
||||
[0, 1, 5]
|
||||
];
|
||||
|
||||
function test_cascade_progressCallback() {
|
||||
loadCount++;
|
||||
// We'll get the first <length of windows[0].tabs> load events here, even
|
||||
// though they are ignored by sessionstore. Those are due to explicit
|
||||
// "stop" events before any restoring action takes place. We can safely
|
||||
// ignore these events.
|
||||
if (loadCount <= state.windows[0].tabs.length)
|
||||
return;
|
||||
|
||||
let counts = countTabs();
|
||||
let expected = expectedCounts[loadCount - state.windows[0].tabs.length - 1];
|
||||
|
||||
is(counts[0], expected[0], "test_cascade: load " + loadCount + " - # tabs that need to be restored");
|
||||
is(counts[1], expected[1], "test_cascade: load " + loadCount + " - # tabs that are restoring");
|
||||
is(counts[2], expected[2], "test_cascade: load " + loadCount + " - # tabs that has been restored");
|
||||
|
||||
if (loadCount == state.windows[0].tabs.length * 2) {
|
||||
window.gBrowser.removeTabsProgressListener(progressListener);
|
||||
// Reset the pref
|
||||
try {
|
||||
Services.prefs.clearUserPref("browser.sessionstore.max_concurrent_tabs");
|
||||
} catch (e) {}
|
||||
runNextTest();
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// any given time. This guarantees that a finishing load won't start another.
|
||||
Services.prefs.setIntPref("browser.sessionstore.max_concurrent_tabs", 0);
|
||||
|
||||
// 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 (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
|
||||
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)
|
||||
test_select_progressCallback(aBrowser);
|
||||
}
|
||||
}
|
||||
|
||||
let state = { windows: [{ tabs: [
|
||||
{ entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org" }], extData: { "uniq": r() } },
|
||||
{ entries: [{ url: "http://example.org" }], extData: { "uniq": r() } }
|
||||
], selectedIndex: 1 }] };
|
||||
|
||||
let loadCount = 0;
|
||||
// expectedCounts looks a little wierd for the test case, but it works. See
|
||||
// comment in test_cascade for an explanation
|
||||
let expectedCounts = [
|
||||
[5, 1, 0],
|
||||
[4, 1, 1],
|
||||
[3, 1, 2],
|
||||
[2, 1, 3],
|
||||
[1, 1, 4],
|
||||
[0, 1, 5]
|
||||
];
|
||||
let tabOrder = [0, 5, 1, 4, 3, 2];
|
||||
|
||||
function test_select_progressCallback(aBrowser) {
|
||||
loadCount++;
|
||||
// We'll get the first <length of windows[0].tabs> load events here, even
|
||||
// though they are ignored by sessionstore. Those are due to explicit
|
||||
// "stop" events before any restoring action takes place. We can safely
|
||||
// ignore these events.
|
||||
if (loadCount <= state.windows[0].tabs.length)
|
||||
return;
|
||||
|
||||
let loadIndex = loadCount - state.windows[0].tabs.length - 1;
|
||||
let counts = countTabs();
|
||||
let expected = expectedCounts[loadIndex];
|
||||
|
||||
is(counts[0], expected[0], "test_select: load " + loadCount + " - # tabs that need to be restored");
|
||||
is(counts[1], expected[1], "test_select: load " + loadCount + " - # tabs that are restoring");
|
||||
is(counts[2], expected[2], "test_select: load " + loadCount + " - # tabs that has been restored");
|
||||
|
||||
if (loadCount == state.windows[0].tabs.length * 2) {
|
||||
window.gBrowser.removeTabsProgressListener(progressListener);
|
||||
// Reset the pref
|
||||
try {
|
||||
Services.prefs.clearUserPref("browser.sessionstore.max_concurrent_tabs");
|
||||
} catch (e) {}
|
||||
runNextTest();
|
||||
}
|
||||
else {
|
||||
// double check that this tab was the right one
|
||||
let expectedData = state.windows[0].tabs[tabOrder[loadIndex]].extData.uniq;
|
||||
let tab;
|
||||
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
|
||||
if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser)
|
||||
tab = window.gBrowser.tabs[i];
|
||||
}
|
||||
is(ss.getTabValue(tab, "uniq"), expectedData, "test_select: load " + loadCount + " - correct tab was restored");
|
||||
|
||||
// select the next tab
|
||||
window.gBrowser.selectTabAtIndex(tabOrder[loadIndex + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
window.gBrowser.addTabsProgressListener(progressListener);
|
||||
ss.setBrowserState(JSON.stringify(state));
|
||||
}
|
||||
|
||||
|
||||
function countTabs() {
|
||||
let needsRestore = 0,
|
||||
isRestoring = 0,
|
||||
wasRestored = 0;
|
||||
|
||||
let windowsEnum = Services.wm.getEnumerator("navigator:browser");
|
||||
|
||||
while (windowsEnum.hasMoreElements()) {
|
||||
let window = windowsEnum.getNext();
|
||||
if (window.closed)
|
||||
continue;
|
||||
|
||||
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
|
||||
let browser = window.gBrowser.tabs[i].linkedBrowser;
|
||||
if (browser.__SS_restoring)
|
||||
isRestoring++;
|
||||
else if (browser.__SS_needsRestore)
|
||||
needsRestore++;
|
||||
else
|
||||
wasRestored++;
|
||||
}
|
||||
}
|
||||
return [needsRestore, isRestoring, wasRestored];
|
||||
}
|
||||
|
||||
function r() {
|
||||
return "" + Date.now() + Math.random();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user