Bug 617851 - Restore session history on crash or OOM [r=mfinkle r=mbrubeck r=vingtetun]

This commit is contained in:
Doug Turner 2010-12-15 20:47:38 -05:00
Родитель ddf1b63876
Коммит 6f829b48b8
5 изменённых файлов: 166 добавлений и 29 удалений

Просмотреть файл

@ -112,6 +112,12 @@ pref("browser.display.history.maxresults", 100);
pref("browser.sessionhistory.max_total_viewers", 1);
pref("browser.sessionhistory.max_entries", 50);
/* session store */
pref("browser.sessionstore.resume_from_crash", true);
pref("browser.sessionstore.resume_from_crash_timeout", 60); // minutes
pref("browser.sessionstore.interval", 10000); // milliseconds
pref("browser.sessionstore.max_tabs_undo", 5);
/* these should help performance */
pref("mozilla.widget.force-24bpp", true);
pref("mozilla.widget.use-buffer-pixmap", true);

Просмотреть файл

@ -365,8 +365,13 @@ var Browser = {
if (window.arguments && window.arguments[0])
defaultURL = window.arguments[0];
this.addTab(defaultURL, true);
// Should we restore the previous session (crash or some other event)
let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
if (ss.shouldRestore())
ss.restoreLastSession();
else
this.addTab(defaultURL, true);
// JavaScript Error Console
if (Services.prefs.getBoolPref("browser.console.showInPanel")){
let button = document.getElementById("tool-console");
@ -2754,6 +2759,10 @@ Tab.prototype = {
this._chromeTab = document.getElementById("tabs").addTab();
let browser = this._createBrowser(aURI, null);
// Should we fully load the new browser, or wait until later
if ("delayLoad" in aParams && aParams.delayLoad)
return;
let flags = aParams.flags || Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
browser.loadURIWithFlags(aURI, flags, aParams.referrerURI, aParams.charset, aParams.postData);
},

Просмотреть файл

@ -65,10 +65,19 @@
let ratio = tabHeight / tabWidth;
height = width * ratio;
let thumbnail = this.thumbnail;
thumbnail.removeAttribute("empty");
// Recreate the canvas as it may be tainted and not useable for remote pages
if (thumbnail.hasAttribute("restored")) {
thumbnail.removeAttribute("restored");
thumbnail = this.thumbnail.cloneNode(false);
this.thumbnail.parentNode.replaceChild(thumbnail, this.thumbnail);
this.thumbnail = thumbnail;
}
let self = this;
let renderer = rendererFactory(browser, thumbnail)
renderer.drawContent(function(ctx, callback) {
ctx.save();
@ -76,6 +85,14 @@
ctx.scale(tabWidth / width, tabHeight / height);
callback(browser, 0, 0, width, height, "white");
ctx.restore();
// We don't have an event for the async drawContent anymore, so hack it
setTimeout(function() {
// Save the thumbnail to the session in case we need to use it in a restore
let data = thumbnail.toDataURL("image/png");
let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
ss.setTabValue(self, "thumbnail", data);
}, 800);
});
]]>
</body>

Просмотреть файл

@ -102,4 +102,14 @@ interface nsISessionStore : nsISupports
* @param aKey is the value's name.
*/
void deleteTabValue(in nsIDOMNode aTab, in AString aKey);
/**
* @returns A boolean indicating we should restore previous browser session
*/
boolean shouldRestore();
/**
* Restores the previous browser session using a fast, lightweight strategy
*/
void restoreLastSession();
};

Просмотреть файл

@ -18,7 +18,7 @@
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Mark Finkle <mfinkle@mozilla.com>
* Mark 'evil' Finkle <mfinkle@mozilla.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
@ -72,9 +72,11 @@ SessionStore.prototype = {
_windows: {},
_lastSaveTime: 0,
_interval: 15000,
_lastSessionTime: 0,
_interval: 10000,
_maxTabsUndo: 5,
_shouldRestore: false,
init: function ss_init() {
// Get file references
this._sessionFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
@ -85,18 +87,30 @@ SessionStore.prototype = {
this._loadState = STATE_STOPPED;
try {
if (this._sessionFileBackup.exists())
if (this._sessionFileBackup.exists()) {
this._shouldRestore = true;
this._sessionFileBackup.remove(false);
if (this._sessionFile.exists())
}
if (this._sessionFile.exists()) {
// Disable crash recovery if we have exceeded the timeout
this._lastSessionTime = this._sessionFile.lastModifiedTime;
let delta = Date.now() - this._lastSessionTime;
let timeout = Services.prefs.getIntPref("browser.sessionstore.resume_from_crash_timeout");
if (delta > (timeout * 60000))
this._shouldRestore = false;
this._sessionFile.copyTo(null, this._sessionFileBackup.leafName);
}
} catch (ex) {
Cu.reportError(ex); // file was write-locked?
Cu.reportError(ex); // file was write-locked?
}
try {
this._interval = Services.prefs.getIntPref("sessionstore.interval");
this._maxTabsUndo = Services.prefs.getIntPref("sessionstore.max_tabs_undo");
} catch (e) {}
this._interval = Services.prefs.getIntPref("browser.sessionstore.interval");
this._maxTabsUndo = Services.prefs.getIntPref("browser.sessionstore.max_tabs_undo");
// Disable crash recovery if it has been turned off
if (!Services.prefs.getBoolPref("browser.sessionstore.resume_from_crash"))
this._shouldRestore = false;
},
observe: function ss_observe(aSubject, aTopic, aData) {
@ -156,6 +170,9 @@ SessionStore.prototype = {
// Freeze the data at what we've got (ignoring closing windows)
this._loadState = STATE_QUITTING;
// No need for this back up, we are shutting down just fine
this._sessionFileBackup.remove(false);
observerService.removeObserver(this, "domwindowopened");
observerService.removeObserver(this, "domwindowclosed");
observerService.removeObserver(this, "browser-lastwindow-close-granted");
@ -267,7 +284,7 @@ SessionStore.prototype = {
onTabAdd: function ss_onTabAdd(aWindow, aBrowser, aNoNotification) {
aBrowser.messageManager.addMessageListener("pageshow", this);
if (!aNoNotification)
this.saveStateDelayed();
this._updateCrashReportURL(aWindow);
@ -275,7 +292,11 @@ SessionStore.prototype = {
onTabRemove: function ss_onTabRemove(aWindow, aBrowser, aNoNotification) {
aBrowser.messageManager.removeMessageListener("pageshow", this);
// If this browser is being restored, skip any session save activity
if (aBrowser.__SS_restore)
return;
delete aBrowser.__SS_data;
if (!aNoNotification)
@ -290,7 +311,7 @@ SessionStore.prototype = {
// Bundle this browser's data and extra data and save in the closedTabs
// window property
let data = aBrowser.__SS_data;
data.extraData = aBrowser.__SS_extdata;
data.extData = aBrowser.__SS_extdata;
this._windows[aWindow.__SSID].closedTabs.unshift(data);
let length = this._windows[aWindow.__SSID].closedTabs.length;
@ -300,6 +321,10 @@ SessionStore.prototype = {
},
onTabLoad: function ss_onTabLoad(aWindow, aBrowser, aMessage) {
// If this browser is being restored, skip any session save activity
if (aBrowser.__SS_restore)
return;
delete aBrowser.__SS_data;
this._collectTabData(aBrowser);
@ -311,17 +336,8 @@ SessionStore.prototype = {
if (this._loadState != STATE_RUNNING)
return;
let index = 0;
let browser = aWindow.Browser;
let tabs = browser.tabs;
for (let i = 0; i < tabs.length; i++) {
if (tabs[i].browser == aBrowser) {
index = i;
break;
}
}
this._windows[aWindow.__SSID].selected = index + 1; // 1-based
let index = aWindow.Elements.browsers.selectedIndex;
this._windows[aWindow.__SSID].selected = parseInt(index) + 1; // 1-based
// Restore the resurrected browser
// * currently we only load the last URL into the browser
@ -329,7 +345,7 @@ SessionStore.prototype = {
let data = aBrowser.__SS_data;
if (data.entries.length > 0)
aBrowser.loadURI(data.entries[0].url, null, null);
delete aBrowser.__SS_restore;
}
@ -374,6 +390,10 @@ SessionStore.prototype = {
},
_collectTabData: function ss__collectTabData(aBrowser) {
// If this browser is being restored, skip any session save activity
if (aBrowser.__SS_restore)
return;
let tabData = { entries: [{}] };
tabData.entries[0] = { url: aBrowser.currentURI.spec, title: aBrowser.contentTitle };
tabData.index = 1;
@ -390,6 +410,9 @@ SessionStore.prototype = {
let winData = this._windows[aWindow.__SSID];
winData.tabs = [];
let index = aWindow.Elements.browsers.selectedIndex;
winData.selected = parseInt(index) + 1; // 1-based
let tabs = aWindow.Browser.tabs;
for (let i = 0; i < tabs.length; i++) {
if (tabs[i].browser.__SS_data) {
@ -437,6 +460,27 @@ SessionStore.prototype = {
});
},
_readFile: function ss_readFile(aFile) {
try {
let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
stream.init(aFile, 0x01, 0, 0);
let cvstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream);
let fileSize = stream.available();
cvstream.init(stream, "UTF-8", fileSize, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
let data = {};
cvstream.readString(fileSize, data);
let content = data.value;
cvstream.close();
return content.replace(/\r\n?/g, "\n");
} catch (ex) {
Cu.reportError(ex);
}
return null;
},
_updateCrashReportURL: function ss_updateCrashReportURL(aWindow) {
#ifdef MOZ_CRASH_REPORTER
try {
@ -496,7 +540,7 @@ SessionStore.prototype = {
let tab = aWindow.Browser.addTab(closedTab.entries[0].url, true);
// Put back the extra data
tab.browser.__SS_extdata = closedTab.extraData;
tab.browser.__SS_extdata = closedTab.extData;
// TODO: save and restore more data (position, field values, etc)
@ -538,6 +582,57 @@ SessionStore.prototype = {
delete browser.__SS_extdata[aKey];
else
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
},
shouldRestore: function ss_shouldRestore() {
return this._shouldRestore;
},
restoreLastSession: function ss_restoreLastSession() {
// The previous session data has already been renamed to the backup file
let dirService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
let session = dirService.get("ProfD", Ci.nsILocalFile);
session.append("sessionstore.bak");
let data = JSON.parse(this._readFile(session));
if (!data || data.windows.length == 0)
return;
let window = Services.wm.getMostRecentWindow("navigator:browser");
let selected = data.windows[0].selected;
let tabs = data.windows[0].tabs;
for (let i=0; i<tabs.length; i++) {
let tabData = tabs[i];
// Add a tab, but don't load the URL until we need to
let params = { getAttention: false, delayLoad: true };
if (i + 1 == selected)
params.delayLoad = false;
// We must have selected tabs as soon as possible, so we let all tabs be selected
// until we get the real selected tab. Then we stop selecting tabs. The end result
// is that the right tab is selected, but we also don't get a bunch of errors
let bringToFront = (i + 1 <= selected);
let tab = window.Browser.addTab(tabData.entries[0].url, bringToFront, null, params);
// Recreate the thumbnail if we are delay loading the tab
if (tabData.extData && params.delayLoad) {
let canvas = tab.chromeTab.thumbnail;
canvas.setAttribute("restored", "true");
let image = new window.Image();
image.onload = function() {
if (canvas)
canvas.getContext("2d").drawImage(image, 0, 0);
};
image.src = tabData.extData.thumbnail;
}
tab.browser.__SS_data = tabData;
tab.browser.__SS_extdata = tabData.extData;
tab.browser.__SS_restore = params.delayLoad;
}
}
};