--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -866,18 +866,16 @@ pref("browser.sessionstore.restore_hidde
// If restore_on_demand is set, pinned tabs are restored on startup by default.
// When set to true, this pref overrides that behavior, and pinned tabs will only
// be restored when they are focused.
pref("browser.sessionstore.restore_pinned_tabs_on_demand", false);
// The version at which we performed the latest upgrade backup
pref("browser.sessionstore.upgradeBackup.latestBuildID", "");
// End-users should not run sessionstore in debug mode
pref("browser.sessionstore.debug", false);
-// Enable asynchronous data collection by default.
-pref("browser.sessionstore.async", true);
// allow META refresh by default
pref("accessibility.blockautorefresh", false);
// Whether history is enabled or not.
pref("places.history.enabled", true);
// the (maximum) number of the recent visits to sample
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -71,74 +71,57 @@ function isSessionStorageEvent(event) {
/**
* Listens for and handles content events that we need for the
* session store service to be notified of state changes in content.
*/
let EventListener = {
init: function () {
addEventListener("load", this, true);
- addEventListener("pageshow", this, true);
},
handleEvent: function (event) {
- switch (event.type) {
- case "load":
- // Ignore load events from subframes.
- if (event.target == content.document) {
- // If we're in the process of restoring, this load may signal
- // the end of the restoration.
- let epoch = gContentRestore.getRestoreEpoch();
- if (epoch) {
- // Restore the form data and scroll position.
- gContentRestore.restoreDocument();
+ // Ignore load events from subframes.
+ if (event.target != content.document) {
+ return;
+ }
- // Ask SessionStore.jsm to trigger SSTabRestored.
- sendAsyncMessage("SessionStore:restoreDocumentComplete", {epoch: epoch});
- }
+ // If we're in the process of restoring, this load may signal
+ // the end of the restoration.
+ let epoch = gContentRestore.getRestoreEpoch();
+ if (epoch) {
+ // Restore the form data and scroll position.
+ gContentRestore.restoreDocument();
- // Send a load message for all loads so we can invalidate the TabStateCache.
- sendAsyncMessage("SessionStore:load");
- }
- break;
- case "pageshow":
- if (event.persisted && event.target == content.document)
- sendAsyncMessage("SessionStore:pageshow");
- break;
- default:
- debug("received unknown event '" + event.type + "'");
- break;
+ // Ask SessionStore.jsm to trigger SSTabRestored.
+ sendAsyncMessage("SessionStore:restoreDocumentComplete", {epoch: epoch});
}
+
+ // Send a load message for all loads.
+ sendAsyncMessage("SessionStore:load");
}
};
/**
* Listens for and handles messages sent by the session store service.
*/
let MessageListener = {
MESSAGES: [
- "SessionStore:collectSessionHistory",
-
"SessionStore:restoreHistory",
"SessionStore:restoreTabContent",
"SessionStore:resetRestore",
],
init: function () {
this.MESSAGES.forEach(m => addMessageListener(m, this));
},
receiveMessage: function ({name, data}) {
- let id = data ? data.id : 0;
switch (name) {
- case "SessionStore:collectSessionHistory":
- let history = SessionHistory.collect(docShell);
- sendAsyncMessage(name, {id: id, data: history});
- break;
case "SessionStore:restoreHistory":
let reloadCallback = () => {
// Inform SessionStore.jsm about the reload. It will send
// restoreTabContent in response.
sendAsyncMessage("SessionStore:reloadPendingTab", {epoch: data.epoch});
};
gContentRestore.restoreHistory(data.epoch, data.tabData, reloadCallback);
@@ -175,36 +158,32 @@ let MessageListener = {
default:
debug("received unknown message '" + name + "'");
break;
}
}
};
/**
- * If session data must be collected synchronously, we do it via
- * method calls to this object (rather than via messages to
- * MessageListener). When using multiple processes, these methods run
- * in the content process, but the parent synchronously waits on them
- * using cross-process object wrappers. Without multiple processes, we
- * still use this code for synchronous collection.
+ * On initialization, this handler gets sent to the parent process as a CPOW.
+ * The parent will use it only to flush pending data from the frame script
+ * when needed, i.e. when closing a tab, closing a window, shutting down, etc.
+ *
+ * This will hopefully not be needed in the future once we have async APIs for
+ * closing windows and tabs.
*/
let SyncHandler = {
init: function () {
// Send this object as a CPOW to chrome. In single-process mode,
// the synchronous send ensures that the handler object is
// available in SessionStore.jsm immediately upon loading
// content-sessionStore.js.
sendSyncMessage("SessionStore:setupSyncHandler", {}, {handler: this});
},
- collectSessionHistory: function (includePrivateData) {
- return SessionHistory.collect(docShell);
- },
-
/**
* This function is used to make the tab process flush all data that
* hasn't been sent to the parent process, yet.
*
* @param id (int)
* A unique id that represents the last message received by the chrome
* process before flushing. We will use this to determine data that
* would be lost when data has been sent asynchronously shortly
@@ -220,31 +199,61 @@ let SyncHandler = {
* This function is used to simulate certain situations where race conditions
* can occur by sending data shortly before flushing synchronously.
*/
flushAsync: function () {
MessageQueue.flushAsync();
}
};
-let ProgressListener = {
- init: function() {
- let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebProgress);
- webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
+/**
+ * Listens for changes to the session history. Whenever the user navigates
+ * we will collect URLs and everything belonging to session history.
+ *
+ * Causes a SessionStore:update message to be sent that contains the current
+ * session history.
+ *
+ * Example:
+ * {entries: [{url: "about:mozilla", ...}, ...], index: 1}
+ */
+let SessionHistoryListener = {
+ init: function () {
+ gFrameTree.addObserver(this);
+ addEventListener("hashchange", this, true);
+ Services.obs.addObserver(this, "browser:purge-session-history", true);
},
- onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
- // We are changing page, so time to invalidate the state of the tab
- sendAsyncMessage("SessionStore:loadStart");
+
+ observe: function () {
+ // We need to use setTimeout() here because we listen for
+ // "browser:purge-session-history". When that is fired all observers are
+ // expected to purge their data. We can't expect to be called *after* the
+ // observer in browser.xml that clears session history so we need to wait
+ // a tick before actually collecting data.
+ setTimeout(() => this.collect(), 0);
+ },
+
+ handleEvent: function () {
+ this.collect();
},
- onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {},
- onProgressChange: function() {},
- onStatusChange: function() {},
- onSecurityChange: function() {},
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+
+ collect: function () {
+ if (docShell) {
+ MessageQueue.push("history", () => SessionHistory.collect(docShell));
+ }
+ },
+
+ onFrameTreeCollected: function () {
+ this.collect();
+ },
+
+ onFrameTreeReset: function () {
+ this.collect();
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference])
};
/**
* Listens for scroll position changes. Whenever the user scrolls the top-most
* frame we update the scroll position and will restore it when requested.
*
* Causes a SessionStore:update message to be sent that contains the current
@@ -365,19 +374,17 @@ let PageStyleListener = {
},
onFrameTreeCollected: function () {
MessageQueue.push("pageStyle", () => this.collect());
},
onFrameTreeReset: function () {
MessageQueue.push("pageStyle", () => null);
- },
-
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+ }
};
/**
* Listens for changes to docShell capabilities. Whenever a new load is started
* we need to re-check the list of capabilities and send message when it has
* changed.
*
* Causes a SessionStore:update message to be sent that contains the currently
@@ -451,19 +458,17 @@ let SessionStorageListener = {
},
onFrameTreeCollected: function () {
this.collect();
},
onFrameTreeReset: function () {
this.collect();
- },
-
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver])
+ }
};
/**
* Listen for changes to the privacy status of the tab.
* By definition, tabs start in non-private mode.
*
* Causes a SessionStore:update message to be sent for
* field "isPrivate". This message contains
@@ -652,18 +657,18 @@ let MessageQueue = {
this.send();
}
};
EventListener.init();
MessageListener.init();
FormDataListener.init();
SyncHandler.init();
-ProgressListener.init();
PageStyleListener.init();
+SessionHistoryListener.init();
SessionStorageListener.init();
ScrollPositionListener.init();
DocShellCapabilitiesListener.init();
PrivacyListener.init();
addEventListener("unload", () => {
// Remove all registered nsIObservers.
PageStyleListener.uninit();
deleted file mode 100644
--- a/browser/components/sessionstore/src/Messenger.jsm
+++ /dev/null
@@ -1,71 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
-* License, v. 2.0. If a copy of the MPL was not distributed with this file,
-* You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["Messenger"];
-
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/Promise.jsm", this);
-Cu.import("resource://gre/modules/Timer.jsm", this);
-
-/**
- * The external API exported by this module.
- */
-this.Messenger = Object.freeze({
- send: function (tab, type, options = {}) {
- return MessengerInternal.send(tab, type, options);
- }
-});
-
-/**
- * A module that handles communication between the main and content processes.
- */
-let MessengerInternal = {
- // The id of the last message we sent. This is used to assign a unique ID to
- // every message we send to handle multiple responses from the same browser.
- _latestMessageID: 0,
-
- /**
- * Sends a message to the given tab and waits for a response.
- *
- * @param tab
- * tabbrowser tab
- * @param type
- * {string} the type of the message
- * @param options (optional)
- * {timeout: int} to set the timeout in milliseconds
- * @return {Promise} A promise that will resolve to the response message or
- * be reject when timing out.
- */
- send: function (tab, type, options = {}) {
- let browser = tab.linkedBrowser;
- let mm = browser.messageManager;
- let deferred = Promise.defer();
- let id = ++this._latestMessageID;
- let timeout;
-
- function onMessage({data: {id: mid, data}}) {
- if (mid == id) {
- mm.removeMessageListener(type, onMessage);
- clearTimeout(timeout);
- deferred.resolve(data);
- }
- }
-
- mm.addMessageListener(type, onMessage);
- mm.sendAsyncMessage(type, {id: id});
-
- function onTimeout() {
- mm.removeMessageListener(type, onMessage);
- deferred.reject(new Error("Timed out while waiting for a " + type + " " +
- "response message."));
- }
-
- let delay = (options && options.timeout) || 5000;
- timeout = setTimeout(onTimeout, delay);
- return deferred.promise;
- }
-};
--- a/browser/components/sessionstore/src/SessionSaver.jsm
+++ b/browser/components/sessionstore/src/SessionSaver.jsm
@@ -229,32 +229,18 @@ let SessionSaverInternal = {
* Saves the current session state. Collects data asynchronously and calls
* _saveState() to collect data again (with a cache hit rate of hopefully
* 100%) and write to disk afterwards.
*/
_saveStateAsync: function () {
// Allow scheduling delayed saves again.
this._timeoutID = null;
- // Check whether asynchronous data collection is disabled.
- if (!Services.prefs.getBoolPref("browser.sessionstore.async")) {
- this._saveState();
- return;
- }
-
- // Update the last save time to make sure we wait at least another interval
- // length until we call _saveStateAsync() again.
- this.updateLastSaveTime();
-
- // Save state synchronously after all tab caches have been filled. The data
- // for the tab caches is collected asynchronously. We will reuse this
- // cached data if the tab hasn't been invalidated in the meantime. In that
- // case we will just fall back to synchronous data collection for single
- // tabs.
- SessionStore.fillTabCachesAsynchronously().then(() => this._saveState());
+ // Write to disk.
+ this._saveState();
},
/**
* Write the given state object to disk.
*/
_writeState: function (state) {
stopWatchStart("SERIALIZE_DATA_MS", "SERIALIZE_DATA_LONGEST_OP_MS");
let data = JSON.stringify(state);
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -44,34 +44,25 @@ const WINDOW_ATTRIBUTES = ["width", "hei
// Hideable window features to (re)store
// Restored in restoreWindowFeatures()
const WINDOW_HIDEABLE_FEATURES = [
"menubar", "toolbar", "locationbar", "personalbar", "statusbar", "scrollbars"
];
const MESSAGES = [
- // The content script has received a pageshow event. This happens when a
- // page is loaded from bfcache without any network activity, i.e. when
- // clicking the back or forward button.
- "SessionStore:pageshow",
-
- // The content script tells us that a new page just started loading in a
- // browser.
- "SessionStore:loadStart",
-
// The content script gives us a reference to an object that performs
// synchronous collection of session data.
"SessionStore:setupSyncHandler",
// The content script sends us data that has been invalidated and needs to
// be saved to disk.
"SessionStore:update",
- // A "load" event happened. Invalidate the TabStateCache.
+ // A "load" event happened.
"SessionStore:load",
// The restoreHistory code has run. This is a good time to run SSTabRestoring.
"SessionStore:restoreHistoryComplete",
// The load for the restoring tab has begun. We update the URL bar at this
// time; if we did it before, the load would overwrite it.
"SessionStore:restoreTabContentStarted",
@@ -90,21 +81,16 @@ const MESSAGES = [
];
// These are tab events that we listen to.
const TAB_EVENTS = [
"TabOpen", "TabClose", "TabSelect", "TabShow", "TabHide", "TabPinned",
"TabUnpinned"
];
-// Browser events observed.
-const BROWSER_EVENTS = [
- "SwapDocShells", "UserTypedValueChanged"
-];
-
// The number of milliseconds in a day
const MS_PER_DAY = 1000.0 * 60.0 * 60.0 * 24.0;
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", this);
Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
Cu.import("resource://gre/modules/osfile.jsm", this);
@@ -116,24 +102,23 @@ XPCOMUtils.defineLazyServiceGetter(this,
"@mozilla.org/browser/sessionstartup;1", "nsISessionStartup");
XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager",
"@mozilla.org/gfx/screenmanager;1", "nsIScreenManager");
XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
"@mozilla.org/base/telemetry;1", "nsITelemetry");
XPCOMUtils.defineLazyModuleGetter(this, "console",
"resource://gre/modules/devtools/Console.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+ "resource:///modules/RecentWindow.jsm");
+
XPCOMUtils.defineLazyModuleGetter(this, "GlobalState",
"resource:///modules/sessionstore/GlobalState.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
- "resource:///modules/sessionstore/Messenger.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
"resource:///modules/sessionstore/PrivacyFilter.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
- "resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
"resource:///modules/devtools/scratchpad-manager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionSaver",
"resource:///modules/sessionstore/SessionSaver.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionCookies",
"resource:///modules/sessionstore/SessionCookies.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
"resource:///modules/sessionstore/SessionFile.jsm");
@@ -283,20 +268,16 @@ this.SessionStore = {
restoreLastSession: function ss_restoreLastSession() {
SessionStoreInternal.restoreLastSession();
},
getCurrentState: function (aUpdateAll) {
return SessionStoreInternal.getCurrentState(aUpdateAll);
},
- fillTabCachesAsynchronously: function () {
- return SessionStoreInternal.fillTabCachesAsynchronously();
- },
-
/**
* Backstage pass to implementation details, used for testing purpose.
* Controlled by preference "browser.sessionstore.testmode".
*/
get _internal() {
if (Services.prefs.getBoolPref("browser.sessionstore.debug")) {
return SessionStoreInternal;
}
@@ -604,32 +585,25 @@ let SessionStoreInternal = {
* This method handles incoming messages sent by the session store content
* script and thus enables communication with OOP tabs.
*/
receiveMessage: function ssi_receiveMessage(aMessage) {
var browser = aMessage.target;
var win = browser.ownerDocument.defaultView;
switch (aMessage.name) {
- case "SessionStore:pageshow":
- this.onTabLoad(win, browser);
- break;
- case "SessionStore:loadStart":
- TabStateCache.delete(browser);
- break;
case "SessionStore:setupSyncHandler":
TabState.setSyncHandler(browser, aMessage.objects.handler);
break;
case "SessionStore:update":
this.recordTelemetry(aMessage.data.telemetry);
TabState.update(browser, aMessage.data);
this.saveStateDelayed(win);
break;
case "SessionStore:load":
- TabStateCache.delete(browser);
this.onTabLoad(win, browser);
break;
case "SessionStore:restoreHistoryComplete":
if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
// Notify the tabbrowser that the tab chrome has been restored.
let tab = this._getTabForBrowser(browser);
let tabData = browser.__SS_data;
@@ -739,26 +713,16 @@ let SessionStoreInternal = {
let browser;
switch (aEvent.type) {
case "SwapDocShells":
browser = aEvent.currentTarget;
let otherBrowser = aEvent.detail;
TabState.onBrowserContentsSwapped(browser, otherBrowser);
TabStateCache.onBrowserContentsSwapped(browser, otherBrowser);
break;
- case "UserTypedValueChanged":
- browser = aEvent.currentTarget;
- if (browser.userTypedValue) {
- TabStateCache.updateField(browser, "userTypedValue", browser.userTypedValue);
- TabStateCache.updateField(browser, "userTypedClear", browser.userTypedClear);
- } else {
- TabStateCache.removeField(browser, "userTypedValue");
- TabStateCache.removeField(browser, "userTypedClear");
- }
- break;
case "TabOpen":
this.onTabAdd(win, aEvent.originalTarget);
break;
case "TabClose":
// aEvent.detail determines if the tab was closed by moving to a different window
if (!aEvent.detail)
this.onTabClose(win, aEvent.originalTarget);
this.onTabRemove(win, aEvent.originalTarget);
@@ -768,23 +732,17 @@ let SessionStoreInternal = {
break;
case "TabShow":
this.onTabShow(win, aEvent.originalTarget);
break;
case "TabHide":
this.onTabHide(win, aEvent.originalTarget);
break;
case "TabPinned":
- // If possible, update cached data without having to invalidate it
- TabStateCache.updateField(aEvent.originalTarget, "pinned", true);
- this.saveStateDelayed(win);
- break;
case "TabUnpinned":
- // If possible, update cached data without having to invalidate it
- TabStateCache.updateField(aEvent.originalTarget, "pinned", false);
this.saveStateDelayed(win);
break;
}
this._clearRestoringWindows();
},
/**
* Generate a unique window identifier
@@ -1217,17 +1175,16 @@ let SessionStoreInternal = {
// session data on disk as this notification fires after the
// quit-application notification so the browser is about to exit.
if (this._loadState == STATE_QUITTING)
return;
LastSession.clear();
let openWindows = {};
this._forEachBrowserWindow(function(aWindow) {
Array.forEach(aWindow.gBrowser.tabs, function(aTab) {
- TabStateCache.delete(aTab);
delete aTab.linkedBrowser.__SS_data;
if (aTab.linkedBrowser.__SS_restoreState)
this._resetTabRestoringState(aTab);
}, this);
openWindows[aWindow.__SSi] = true;
});
// also clear all data about closed tabs and windows
for (let ix in this._windows) {
@@ -1336,17 +1293,17 @@ let SessionStoreInternal = {
* Window reference
* @param aTab
* Tab reference
* @param aNoNotification
* bool Do not save state if we're updating an existing tab
*/
onTabAdd: function ssi_onTabAdd(aWindow, aTab, aNoNotification) {
let browser = aTab.linkedBrowser;
- BROWSER_EVENTS.forEach(msg => browser.addEventListener(msg, this, true));
+ browser.addEventListener("SwapDocShells", this, true);
let mm = browser.messageManager;
MESSAGES.forEach(msg => mm.addMessageListener(msg, this));
// Load the frame script after registering listeners.
mm.loadFrameScript("chrome://browser/content/content-sessionStore.js", false);
if (!aNoNotification) {
@@ -1362,17 +1319,17 @@ let SessionStoreInternal = {
* Window reference
* @param aTab
* Tab reference
* @param aNoNotification
* bool Do not save state if we're updating an existing tab
*/
onTabRemove: function ssi_onTabRemove(aWindow, aTab, aNoNotification) {
let browser = aTab.linkedBrowser;
- BROWSER_EVENTS.forEach(msg => browser.removeEventListener(msg, this, true));
+ browser.removeEventListener("SwapDocShells", this, true);
let mm = browser.messageManager;
MESSAGES.forEach(msg => mm.removeMessageListener(msg, this));
delete browser.__SS_data;
// If this tab was in the middle of restoring or still needs to be restored,
// we need to reset that state. If the tab was restoring, we will attempt to
@@ -1407,17 +1364,17 @@ let SessionStoreInternal = {
if (this._max_tabs_undo == 0) {
return;
}
// Flush all data queued in the content script before the tab is gone.
TabState.flush(aTab.linkedBrowser);
// Get the latest data for this tab (generally, from the cache)
- let tabState = TabState.collectSync(aTab);
+ let tabState = TabState.collect(aTab);
// Don't save private tabs
let isPrivateWindow = PrivateBrowsingUtils.isWindowPrivate(aWindow);
if (!isPrivateWindow && tabState.isPrivate) {
return;
}
// store closed-tab data for undo
@@ -1443,27 +1400,23 @@ let SessionStoreInternal = {
* When a tab loads, invalidate its cached state, trigger async save.
*
* @param aWindow
* Window reference
* @param aBrowser
* Browser reference
*/
onTabLoad: function ssi_onTabLoad(aWindow, aBrowser) {
- // react on "load" and solitary "pageshow" events (the first "pageshow"
- // following "load" is too late for deleting the data caches)
// It's possible to get a load event after calling stop on a browser (when
// overwriting tabs). We want to return early if the tab hasn't been restored yet.
if (aBrowser.__SS_restoreState &&
aBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
return;
}
- TabStateCache.delete(aBrowser);
-
delete aBrowser.__SS_data;
this.saveStateDelayed(aWindow);
// attempt to update the current URL we send in a crash report
this._updateCrashReportURL(aWindow);
},
/**
@@ -1494,47 +1447,39 @@ let SessionStoreInternal = {
aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
TabRestoreQueue.hiddenToVisible(aTab);
// let's kick off tab restoration again to ensure this tab gets restored
// with "restore_hidden_tabs" == false (now that it has become visible)
this.restoreNextTab();
}
- // If possible, update cached data without having to invalidate it
- TabStateCache.updateField(aTab, "hidden", false);
-
// Default delay of 2 seconds gives enough time to catch multiple TabShow
// events due to changing groups in Panorama.
this.saveStateDelayed(aWindow);
},
onTabHide: function ssi_onTabHide(aWindow, aTab) {
// If the tab hasn't been restored yet, move it into the right bucket
if (aTab.linkedBrowser.__SS_restoreState &&
aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
TabRestoreQueue.visibleToHidden(aTab);
}
- // If possible, update cached data without having to invalidate it
- TabStateCache.updateField(aTab, "hidden", true);
-
// Default delay of 2 seconds gives enough time to catch multiple TabHide
// events due to changing groups in Panorama.
this.saveStateDelayed(aWindow);
},
onGatherTelemetry: function() {
// On the first gather-telemetry notification of the session,
// gather telemetry data.
Services.obs.removeObserver(this, "gather-telemetry");
- this.fillTabCachesAsynchronously().then(function() {
- let stateString = SessionStore.getBrowserState();
- return SessionFile.gatherTelemetry(stateString);
- });
+ let stateString = SessionStore.getBrowserState();
+ return SessionFile.gatherTelemetry(stateString);
},
/* ........ nsISessionStore API .............. */
getBrowserState: function ssi_getBrowserState() {
let state = this.getCurrentState();
// Don't include the last session state in getBrowserState().
@@ -1618,17 +1563,17 @@ let SessionStoreInternal = {
getTabState: function ssi_getTabState(aTab) {
if (!aTab.ownerDocument) {
throw Components.Exception("Invalid tab object: no ownerDocument", Cr.NS_ERROR_INVALID_ARG);
}
if (!aTab.ownerDocument.defaultView.__SSi) {
throw Components.Exception("Default view is not tracked", Cr.NS_ERROR_INVALID_ARG);
}
- let tabState = TabState.collectSync(aTab);
+ let tabState = TabState.collect(aTab);
return this._toJSONString(tabState);
},
setTabState: function ssi_setTabState(aTab, aState) {
// Remove the tab state from the cache.
// Note that we cannot simply replace the contents of the cache
// as |aState| can be an incomplete state that will be completed
@@ -1651,17 +1596,16 @@ let SessionStoreInternal = {
if (!("__SSi" in window)) {
throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
}
if (aTab.linkedBrowser.__SS_restoreState) {
this._resetTabRestoringState(aTab);
}
- TabStateCache.delete(aTab);
this._setWindowStateBusy(window);
this.restoreTabs(window, [aTab], [tabState], 0);
},
duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta = 0) {
if (!aTab.ownerDocument) {
throw Components.Exception("Invalid tab object: no ownerDocument", Cr.NS_ERROR_INVALID_ARG);
}
@@ -1856,17 +1800,16 @@ let SessionStoreInternal = {
saveTo = aTab.linkedBrowser.__SS_data.extData;
}
else {
aTab.__SS_extdata = {};
saveTo = aTab.__SS_extdata;
}
saveTo[aKey] = aStringValue;
- TabStateCache.updateField(aTab, "extData", saveTo);
this.saveStateDelayed(aTab.ownerDocument.defaultView);
},
deleteTabValue: function ssi_deleteTabValue(aTab, aKey) {
// We want to make sure that if data is accessed early, we attempt to delete
// that data from __SS_data as well. Otherwise we'll throw in cases where
// data can be set or read.
let deleteFrom;
@@ -1874,25 +1817,16 @@ let SessionStoreInternal = {
deleteFrom = aTab.__SS_extdata;
}
else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) {
deleteFrom = aTab.linkedBrowser.__SS_data.extData;
}
if (deleteFrom && aKey in deleteFrom) {
delete deleteFrom[aKey];
-
- // Keep the extData object only if it is not empty, to save
- // a little disk space when serializing the tab state later.
- if (Object.keys(deleteFrom).length) {
- TabStateCache.updateField(aTab, "extData", deleteFrom);
- } else {
- TabStateCache.removeField(aTab, "extData");
- }
-
this.saveStateDelayed(aTab.ownerDocument.defaultView);
}
},
getGlobalValue: function ssi_getGlobalValue(aKey) {
return this._globalState.get(aKey);
},
@@ -1903,17 +1837,16 @@ let SessionStoreInternal = {
deleteGlobalValue: function ssi_deleteGlobalValue(aKey) {
this._globalState.delete(aKey);
this.saveStateDelayed();
},
persistTabAttribute: function ssi_persistTabAttribute(aName) {
if (TabAttributes.persist(aName)) {
- TabStateCache.clear();
this.saveStateDelayed();
}
},
/**
* Restores the session state stored in LastSession. This will attempt
* to merge data into the current session. If a window was opened at startup
* with pinned tab(s), then the remaining data from the previous session for
@@ -2081,78 +2014,16 @@ let SessionStoreInternal = {
for (let i = removableTabs.length - 1; i >= 0; i--) {
tabbrowser.removeTab(removableTabs.pop(), { animate: false });
}
}
return [true, canOverwriteTabs];
},
- /* ........ Async Data Collection .............. */
-
- /**
- * Kicks off asynchronous data collection for all tabs that do not have any
- * cached data. The returned promise will only notify that the tab collection
- * has been finished without resolving to any data. The tab collection for a
- * a few or all tabs might have failed or timed out. By calling
- * fillTabCachesAsynchronously() and waiting for the promise to be resolved
- * before calling getCurrentState(), callers ensure that most of the data
- * should have been collected asynchronously, without blocking the main
- * thread.
- *
- * @return {Promise} the promise that is fulfilled when the tab data is ready
- */
- fillTabCachesAsynchronously: function () {
- let countdown = 0;
- let deferred = Promise.defer();
- let activeWindow = this._getMostRecentBrowserWindow();
-
- // The callback that will be called when a promise has been resolved
- // successfully, i.e. the tab data has been collected.
- function done() {
- if (--countdown === 0) {
- deferred.resolve();
- }
- }
-
- // The callback that will be called when a promise is rejected, i.e. we
- // we couldn't collect the tab data because of a script error or a timeout.
- function fail(reason) {
- debug("Failed collecting tab data asynchronously: " + reason);
- done();
- }
-
- this._forEachBrowserWindow(win => {
- if (!this._isWindowLoaded(win)) {
- // Bail out if the window hasn't even loaded, yet.
- return;
- }
-
- if (!DirtyWindows.has(win) && win != activeWindow) {
- // Bail out if the window is not dirty and inactive.
- return;
- }
-
- for (let tab of win.gBrowser.tabs) {
- if (!tab.closing && !TabStateCache.has(tab)) {
- countdown++;
- TabState.collect(tab).then(done, fail);
- }
- }
- });
-
- // If no dirty tabs were found, return a resolved
- // promise because there is nothing to do here.
- if (countdown == 0) {
- return Promise.resolve();
- }
-
- return deferred.promise;
- },
-
/* ........ Saving Functionality .............. */
/**
* Store window dimensions, visibility, sidebar
* @param aWindow
* Window reference
*/
_updateWindowFeatures: function ssi_updateWindowFeatures(aWindow) {
@@ -2318,17 +2189,17 @@ let SessionStoreInternal = {
let tabbrowser = aWindow.gBrowser;
let tabs = tabbrowser.tabs;
let winData = this._windows[aWindow.__SSi];
let tabsData = winData.tabs = [];
// update the internal state data for this window
for (let tab of tabs) {
- tabsData.push(TabState.collectSync(tab));
+ tabsData.push(TabState.collect(tab));
}
winData.selected = tabbrowser.mTabBox.selectedIndex + 1;
this._updateWindowFeatures(aWindow);
// Make sure we keep __SS_lastSessionWindowID around for cases like entering
// or leaving PB mode.
if (aWindow.__SS_lastSessionWindowID)
@@ -2698,20 +2569,16 @@ let SessionStoreInternal = {
else
tabbrowser.showTab(tab);
if ("attributes" in tabData) {
// Ensure that we persist tab attributes restored from previous sessions.
Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a));
}
- // Any data that's in the process of being collected for this tab will be
- // out of date now that we're restoring it.
- TabState.dropPendingCollections(browser);
-
if (!tabData.entries) {
tabData.entries = [];
}
if (tabData.extData) {
tab.__SS_extdata = {};
for (let key in tabData.extData)
tab.__SS_extdata[key] = tabData.extData[key];
} else {
@@ -2740,17 +2607,18 @@ let SessionStoreInternal = {
// 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_restoreState = TAB_STATE_NEEDS_RESTORE;
browser.setAttribute("pending", "true");
tab.setAttribute("pending", "true");
// Update the persistent tab state cache with |tabData| information.
- TabStateCache.updatePersistent(browser, {
+ TabStateCache.update(browser, {
+ history: {entries: tabData.entries, index: tabData.index},
scroll: tabData.scroll || null,
storage: tabData.storage || null,
formdata: tabData.formdata || null,
disallow: tabData.disallow || null,
pageStyle: tabData.pageStyle || null
});
// In electrolysis, we may need to change the browser's remote
--- a/browser/components/sessionstore/src/TabState.jsm
+++ b/browser/components/sessionstore/src/TabState.jsm
@@ -9,18 +9,16 @@ this.EXPORTED_SYMBOLS = ["TabState"];
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
XPCOMUtils.defineLazyModuleGetter(this, "console",
"resource://gre/modules/devtools/Console.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
- "resource:///modules/sessionstore/Messenger.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
"resource:///modules/sessionstore/PrivacyFilter.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
"resource:///modules/sessionstore/TabStateCache.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
"resource:///modules/sessionstore/TabAttributes.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Utils",
"resource:///modules/sessionstore/Utils.jsm");
@@ -48,34 +46,22 @@ this.TabState = Object.freeze({
flushWindow: function (window) {
TabStateInternal.flushWindow(window);
},
collect: function (tab) {
return TabStateInternal.collect(tab);
},
- collectSync: function (tab) {
- return TabStateInternal.collectSync(tab);
- },
-
clone: function (tab) {
return TabStateInternal.clone(tab);
- },
-
- dropPendingCollections: function (browser) {
- TabStateInternal.dropPendingCollections(browser);
}
});
let TabStateInternal = {
- // A map (xul:browser -> promise) that keeps track of tabs and
- // their promises when collecting tab data asynchronously.
- _pendingCollections: new WeakMap(),
-
// A map (xul:browser -> handler) that maps a tab to the
// synchronous collection handler object for that tab.
// See SyncHandler in content-sessionStore.js.
_syncHandlers: new WeakMap(),
// A map (xul:browser -> int) that maps a browser to the
// last "SessionStore:update" message ID we received for it.
_latestMessageID: new WeakMap(),
@@ -92,17 +78,17 @@ let TabStateInternal = {
* Processes a data update sent by the content script.
*/
update: function (browser, {id, data}) {
// Only ever process messages that have an ID higher than the last one we
// saw. This ensures we don't use stale data that has already been received
// synchronously.
if (id > this._latestMessageID.get(browser)) {
this._latestMessageID.set(browser, id);
- TabStateCache.updatePersistent(browser, data);
+ TabStateCache.update(browser, data);
}
},
/**
* Flushes all data currently queued in the given browser's content script.
*/
flush: function (browser) {
if (this._syncHandlers.has(browser)) {
@@ -122,338 +108,61 @@ let TabStateInternal = {
/**
* When a docshell swap happens, a xul:browser element will be
* associated with a different content-sessionStore.js script
* global. In this case, the sync handler for the element needs to
* be swapped just like the docshell.
*/
onBrowserContentsSwapped: function (browser, otherBrowser) {
- // Data collected while docShells have been swapped should not go into
- // the TabStateCache. Collections will most probably time out but we want
- // to make sure.
- this.dropPendingCollections(browser);
- this.dropPendingCollections(otherBrowser);
-
// Swap data stored per-browser.
[this._syncHandlers, this._latestMessageID]
.forEach(map => Utils.swapMapEntries(map, browser, otherBrowser));
},
/**
- * Collect data related to a single tab, asynchronously.
- *
- * @param tab
- * tabbrowser tab
- *
- * @returns {Promise} A promise that will resolve to a TabData instance.
- */
- collect: function (tab) {
- if (!tab) {
- throw new TypeError("Expecting a tab");
- }
-
- // Don't collect if we don't need to.
- if (TabStateCache.has(tab)) {
- return Promise.resolve(TabStateCache.get(tab));
- }
-
- // If the tab was recently added, or if it's being restored, we
- // just collect basic data about it and skip the cache.
- if (!this._tabNeedsExtraCollection(tab)) {
- let tabData = this._collectBaseTabData(tab);
- return Promise.resolve(tabData);
- }
-
- let browser = tab.linkedBrowser;
-
- let promise = Task.spawn(function task() {
- // Collect session history data asynchronously.
- let history = yield Messenger.send(tab, "SessionStore:collectSessionHistory");
-
- // The tab could have been closed while waiting for a response.
- if (!tab.linkedBrowser) {
- return;
- }
-
- // Collect basic tab data, without session history and storage.
- let tabData = this._collectBaseTabData(tab);
-
- // Apply collected data.
- tabData.entries = history.entries;
- if ("index" in history) {
- tabData.index = history.index;
- }
-
- // If we're still the latest async collection for the given tab and
- // the cache hasn't been filled by collect() in the meantime, let's
- // fill the cache with the data we received.
- if (this._pendingCollections.get(browser) == promise) {
- TabStateCache.set(tab, tabData);
- this._pendingCollections.delete(browser);
- }
-
- // Copy data from the persistent cache. We need to create an explicit
- // copy of the |tabData| object so that the properties injected by
- // |_copyFromPersistentCache| don't end up in the non-persistent cache.
- // The persistent cache does not store "null" values, so any values that
- // have been cleared by the frame script would not be overriden by
- // |_copyFromPersistentCache|. These two caches are only an interim
- // solution and the non-persistent one will go away soon.
- tabData = Utils.copy(tabData);
- this._copyFromPersistentCache(tab, tabData);
-
- throw new Task.Result(tabData);
- }.bind(this));
-
- // Save the current promise as the latest asynchronous collection that is
- // running. This will be used to check whether the collected data is still
- // valid and will be used to fill the tab state cache.
- this._pendingCollections.set(browser, promise);
-
- return promise;
- },
-
- /**
* Collect data related to a single tab, synchronously.
*
* @param tab
* tabbrowser tab
*
* @returns {TabData} An object with the data for this tab. If the
* tab has not been invalidated since the last call to
- * collectSync(aTab), the same object is returned.
+ * collect(aTab), the same object is returned.
*/
- collectSync: function (tab) {
- if (!tab) {
- throw new TypeError("Expecting a tab");
- }
- if (TabStateCache.has(tab)) {
- // Copy data from the persistent cache. We need to create an explicit
- // copy of the |tabData| object so that the properties injected by
- // |_copyFromPersistentCache| don't end up in the non-persistent cache.
- // The persistent cache does not store "null" values, so any values that
- // have been cleared by the frame script would not be overriden by
- // |_copyFromPersistentCache|. These two caches are only an interim
- // solution and the non-persistent one will go away soon.
- let tabData = Utils.copy(TabStateCache.get(tab));
- this._copyFromPersistentCache(tab, tabData);
- return tabData;
- }
-
- let tabData = this._collectSyncUncached(tab);
-
- if (this._tabCachingAllowed(tab)) {
- TabStateCache.set(tab, tabData);
- }
-
- // Copy data from the persistent cache. We need to create an explicit
- // copy of the |tabData| object so that the properties injected by
- // |_copyFromPersistentCache| don't end up in the non-persistent cache.
- // The persistent cache does not store "null" values, so any values that
- // have been cleared by the frame script would not be overriden by
- // |_copyFromPersistentCache|. These two caches are only an interim
- // solution and the non-persistent one will go away soon.
- tabData = Utils.copy(tabData);
- this._copyFromPersistentCache(tab, tabData);
-
- // Prevent all running asynchronous collections from filling the cache.
- // Every asynchronous data collection started before a collectSync() call
- // can't expect to retrieve different data than the sync call. That's why
- // we just fill the cache with the data collected from the sync call and
- // discard any data collected asynchronously.
- this.dropPendingCollections(tab.linkedBrowser);
-
- return tabData;
- },
-
- /**
- * Drop any pending calls to TabState.collect. These calls will
- * continue to run, but they won't store their results in the
- * TabStateCache.
- *
- * @param browser
- * xul:browser
- */
- dropPendingCollections: function (browser) {
- this._pendingCollections.delete(browser);
+ collect: function (tab) {
+ return this._collectBaseTabData(tab);
},
/**
* Collect data related to a single tab, including private data.
* Use with caution.
*
* @param tab
* tabbrowser tab
*
* @returns {object} An object with the data for this tab. This data is never
* cached, it will always be read from the tab and thus be
* up-to-date.
*/
clone: function (tab) {
- let options = {includePrivateData: true};
- let tabData = this._collectSyncUncached(tab, options);
-
- // Copy data from the persistent cache.
- this._copyFromPersistentCache(tab, tabData, options);
-
- return tabData;
- },
-
- /**
- * Synchronously collect all session data for a tab. The
- * TabStateCache is not consulted, and the resulting data is not put
- * in the cache.
- */
- _collectSyncUncached: function (tab, options = {}) {
- // Collect basic tab data, without session history and storage.
- let tabData = this._collectBaseTabData(tab);
-
- // If we don't need any other data, return what we have.
- if (!this._tabNeedsExtraCollection(tab)) {
- return tabData;
- }
-
- // In multiprocess Firefox, there is a small window of time after
- // tab creation when we haven't received a sync handler object. In
- // this case the tab shouldn't have any history or cookie data, so we
- // just return the base data already collected.
- if (!this._syncHandlers.has(tab.linkedBrowser)) {
- return tabData;
- }
-
- let syncHandler = this._syncHandlers.get(tab.linkedBrowser);
-
- let includePrivateData = options && options.includePrivateData;
-
- let history;
- try {
- history = syncHandler.collectSessionHistory(includePrivateData);
- } catch (e) {
- // This may happen if the tab has crashed.
- console.error(e);
- return tabData;
- }
-
- tabData.entries = history.entries;
- if ("index" in history) {
- tabData.index = history.index;
- }
-
- return tabData;
- },
-
- /**
- * Copy tab data for the given |tab| from the persistent cache to |tabData|.
- *
- * @param tab (xul:tab)
- * The tab belonging to the given |tabData| object.
- * @param tabData (object)
- * The tab data belonging to the given |tab|.
- * @param options (object)
- * {includePrivateData: true} to always include private data
- */
- _copyFromPersistentCache: function (tab, tabData, options = {}) {
- let data = TabStateCache.getPersistent(tab.linkedBrowser);
- if (!data) {
- return;
- }
-
- // The caller may explicitly request to omit privacy checks.
- let includePrivateData = options && options.includePrivateData;
-
- for (let key of Object.keys(data)) {
- let value = data[key];
-
- // Filter sensitive data according to the current privacy level.
- if (!includePrivateData) {
- if (key === "storage") {
- value = PrivacyFilter.filterSessionStorageData(value, tab.pinned);
- } else if (key === "formdata") {
- value = PrivacyFilter.filterFormData(value, tab.pinned);
- }
- }
-
- if (value) {
- tabData[key] = value;
- }
- }
- },
-
- /*
- * Returns true if the xul:tab element is newly added (i.e., if it's
- * showing about:blank with no history).
- */
- _tabIsNew: function (tab) {
- let browser = tab.linkedBrowser;
- return (!browser || !browser.currentURI);
- },
-
- /*
- * Returns true if the xul:tab element is in the process of being
- * restored.
- */
- _tabIsRestoring: function (tab) {
- return !!tab.linkedBrowser.__SS_data;
- },
-
- /**
- * This function returns true if we need to collect history, page
- * style, and text and scroll data from the tab. Normally we do. The
- * cases when we don't are:
- * 1. the tab is about:blank with no history, or
- * 2. the tab is waiting to be restored.
- *
- * @param tab A xul:tab element.
- * @returns True if the tab is in the process of being restored.
- */
- _tabNeedsExtraCollection: function (tab) {
- if (this._tabIsNew(tab)) {
- // Tab is about:blank with no history.
- return false;
- }
-
- if (this._tabIsRestoring(tab)) {
- // Tab is waiting to be restored.
- return false;
- }
-
- // Otherwise we need the extra data.
- return true;
- },
-
- /*
- * Returns true if we should cache the tabData for the given the
- * xul:tab element.
- */
- _tabCachingAllowed: function (tab) {
- if (this._tabIsNew(tab)) {
- // No point in caching data for newly created tabs.
- return false;
- }
-
- if (this._tabIsRestoring(tab)) {
- // If the tab is being restored, we just return the data being
- // restored. This data may be incomplete (if supplied by
- // setBrowserState, for example), so we don't want to cache it.
- return false;
- }
-
- return true;
+ return this._collectBaseTabData(tab, {includePrivateData: true});
},
/**
* Collects basic tab data for a given tab.
*
* @param tab
* tabbrowser tab
+ * @param options (object)
+ * {includePrivateData: true} to always include private data
*
* @returns {object} An object with the basic data for this tab.
*/
- _collectBaseTabData: function (tab) {
+ _collectBaseTabData: function (tab, options) {
let tabData = {entries: [], lastAccessed: tab.lastAccessed };
let browser = tab.linkedBrowser;
if (!browser || !browser.currentURI) {
// can happen when calling this function right after .addTab()
return tabData;
}
if (browser.__SS_data) {
@@ -502,11 +211,58 @@ let TabStateInternal = {
let tabbrowser = tab.ownerDocument.defaultView.gBrowser;
tabData.image = tabbrowser.getIcon(tab);
if (tab.__SS_extdata)
tabData.extData = tab.__SS_extdata;
else if (tabData.extData)
delete tabData.extData;
+ // Copy data from the tab state cache only if the tab has fully finished
+ // restoring. We don't want to overwrite data contained in __SS_data.
+ this._copyFromCache(tab, tabData, options);
+
return tabData;
+ },
+
+ /**
+ * Copy tab data for the given |tab| from the cache to |tabData|.
+ *
+ * @param tab (xul:tab)
+ * The tab belonging to the given |tabData| object.
+ * @param tabData (object)
+ * The tab data belonging to the given |tab|.
+ * @param options (object)
+ * {includePrivateData: true} to always include private data
+ */
+ _copyFromCache: function (tab, tabData, options = {}) {
+ let data = TabStateCache.get(tab.linkedBrowser);
+ if (!data) {
+ return;
+ }
+
+ // The caller may explicitly request to omit privacy checks.
+ let includePrivateData = options && options.includePrivateData;
+
+ for (let key of Object.keys(data)) {
+ let value = data[key];
+
+ // Filter sensitive data according to the current privacy level.
+ if (!includePrivateData) {
+ if (key === "storage") {
+ value = PrivacyFilter.filterSessionStorageData(value, tab.pinned);
+ } else if (key === "formdata") {
+ value = PrivacyFilter.filterFormData(value, tab.pinned);
+ }
+ }
+
+ if (key === "history") {
+ tabData.entries = value.entries;
+
+ if (value.hasOwnProperty("index")) {
+ tabData.index = value.index;
+ }
+ } else if (value) {
+ tabData[key] = value;
+ }
+ }
}
};
--- a/browser/components/sessionstore/src/TabStateCache.jsm
+++ b/browser/components/sessionstore/src/TabStateCache.jsm
@@ -20,364 +20,95 @@ XPCOMUtils.defineLazyModuleGetter(this,
* to tab data (as objects).
*
* Note that we should never cache private data, as:
* - that data is used very seldom by SessionStore;
* - caching private data in addition to public data is memory consuming.
*/
this.TabStateCache = Object.freeze({
/**
- * Tells whether an entry is in the cache.
- *
- * @param {XULElement} aKey The tab or the associated browser.
- * @return {bool} Whether there's a cached entry for the given tab.
- */
- has: function (aTab) {
- return TabStateCacheInternal.has(aTab);
- },
-
- /**
- * Add or replace an entry in the cache.
- *
- * @param {XULElement} aTab The key, which may be either a tab
- * or the corresponding browser. The binding will disappear
- * if the tab/browser is destroyed.
- * @param {*} aValue The data associated to |aTab|.
- */
- set: function(aTab, aValue) {
- return TabStateCacheInternal.set(aTab, aValue);
- },
-
- /**
- * Return the tab data associated with a tab.
- *
- * @param {XULElement} aKey The tab or the associated browser.
- *
- * @return {*|undefined} The data if available, |undefined|
- * otherwise.
- */
- get: function(aKey) {
- return TabStateCacheInternal.get(aKey);
- },
-
- /**
- * Delete the tab data associated with a tab.
- *
- * @param {XULElement} aKey The tab or the associated browser.
- *
- * Noop of there is no tab data associated with the tab.
- */
- delete: function(aKey) {
- return TabStateCacheInternal.delete(aKey);
- },
-
- /**
- * Delete all tab data.
- */
- clear: function() {
- return TabStateCacheInternal.clear();
- },
-
- /**
- * Update in place a piece of data.
- *
- * @param {XULElement} aKey The tab or the associated browser.
- * If the tab/browser is not present, do nothing.
- * @param {string} aField The field to update.
- * @param {*} aValue The new value to place in the field.
- */
- updateField: function(aKey, aField, aValue) {
- return TabStateCacheInternal.updateField(aKey, aField, aValue);
- },
-
- /**
- * Remove a given field from a cached tab state.
- *
- * @param {XULElement} aKey The tab or the associated browser.
- * If the tab/browser is not present, do nothing.
- * @param {string} aField The field to remove.
- */
- removeField: function(aKey, aField) {
- return TabStateCacheInternal.removeField(aKey, aField);
- },
-
- /**
* Swap cached data for two given browsers.
*
* @param {xul:browser} browser
* The first of the two browsers that swapped docShells.
* @param {xul:browser} otherBrowser
* The second of the two browsers that swapped docShells.
*/
onBrowserContentsSwapped: function(browser, otherBrowser) {
TabStateCacheInternal.onBrowserContentsSwapped(browser, otherBrowser);
},
/**
- * Retrieves persistently cached data for a given |browser|.
+ * Retrieves cached data for a given |browser|.
*
* @param browser (xul:browser)
* The browser to retrieve cached data for.
* @return (object)
- * The persistently cached data stored for the given |browser|.
+ * The cached data stored for the given |browser|.
*/
- getPersistent: function (browser) {
- return TabStateCacheInternal.getPersistent(browser);
+ get: function (browser) {
+ return TabStateCacheInternal.get(browser);
},
/**
- * Updates persistently cached data for a given |browser|. This data is
- * persistently in the sense that we never clear it, it will always be
- * overwritten.
+ * Updates cached data for a given |browser|.
*
* @param browser (xul:browser)
* The browser belonging to the given tab data.
* @param newData (object)
* The new data to be stored for the given |browser|.
*/
- updatePersistent: function (browser, newData) {
- TabStateCacheInternal.updatePersistent(browser, newData);
- },
-
- /**
- * Total number of cache hits during the session.
- */
- get hits() {
- return TabStateCacheTelemetry.hits;
- },
-
- /**
- * Total number of cache misses during the session.
- */
- get misses() {
- return TabStateCacheTelemetry.misses;
- },
-
- /**
- * Total number of cache clears during the session.
- */
- get clears() {
- return TabStateCacheTelemetry.clears;
- },
+ update: function (browser, newData) {
+ TabStateCacheInternal.update(browser, newData);
+ }
});
let TabStateCacheInternal = {
_data: new WeakMap(),
- _persistentData: new WeakMap(),
-
- /**
- * Tells whether an entry is in the cache.
- *
- * @param {XULElement} aKey The tab or the associated browser.
- * @return {bool} Whether there's a cached entry for the given tab.
- */
- has: function (aTab) {
- let key = this._normalizeToBrowser(aTab);
- return this._data.has(key);
- },
-
- /**
- * Add or replace an entry in the cache.
- *
- * @param {XULElement} aTab The key, which may be either a tab
- * or the corresponding browser. The binding will disappear
- * if the tab/browser is destroyed.
- * @param {*} aValue The data associated to |aTab|.
- */
- set: function(aTab, aValue) {
- let key = this._normalizeToBrowser(aTab);
- this._data.set(key, aValue);
- },
-
- /**
- * Return the tab data associated with a tab.
- *
- * @param {XULElement} aKey The tab or the associated browser.
- *
- * @return {*|undefined} The data if available, |undefined|
- * otherwise.
- */
- get: function(aKey) {
- let key = this._normalizeToBrowser(aKey);
- let result = this._data.get(key);
- TabStateCacheTelemetry.recordAccess(!!result);
- return result;
- },
-
- /**
- * Delete the tab data associated with a tab.
- *
- * @param {XULElement} aKey The tab or the associated browser.
- *
- * Noop of there is no tab data associated with the tab.
- */
- delete: function(aKey) {
- let key = this._normalizeToBrowser(aKey);
- this._data.delete(key);
- },
-
- /**
- * Delete all tab data.
- */
- clear: function() {
- TabStateCacheTelemetry.recordClear();
- this._data.clear();
- },
-
- /**
- * Update in place a piece of data.
- *
- * @param {XULElement} aKey The tab or the associated browser.
- * If the tab/browser is not present, do nothing.
- * @param {string} aField The field to update.
- * @param {*} aValue The new value to place in the field.
- */
- updateField: function(aKey, aField, aValue) {
- let key = this._normalizeToBrowser(aKey);
- let data = this._data.get(key);
- if (data) {
- data[aField] = aValue;
- }
- },
-
- /**
- * Remove a given field from a cached tab state.
- *
- * @param {XULElement} aKey The tab or the associated browser.
- * If the tab/browser is not present, do nothing.
- * @param {string} aField The field to remove.
- */
- removeField: function(aKey, aField) {
- let key = this._normalizeToBrowser(aKey);
- let data = this._data.get(key);
- if (data && aField in data) {
- delete data[aField];
- }
- },
/**
* Swap cached data for two given browsers.
*
* @param {xul:browser} browser
* The first of the two browsers that swapped docShells.
* @param {xul:browser} otherBrowser
* The second of the two browsers that swapped docShells.
*/
onBrowserContentsSwapped: function(browser, otherBrowser) {
// Swap data stored per-browser.
- [this._data, this._persistentData]
- .forEach(map => Utils.swapMapEntries(map, browser, otherBrowser));
+ Utils.swapMapEntries(this._data, browser, otherBrowser);
},
/**
- * Retrieves persistently cached data for a given |browser|.
+ * Retrieves cached data for a given |browser|.
*
* @param browser (xul:browser)
* The browser to retrieve cached data for.
* @return (object)
- * The persistently cached data stored for the given |browser|.
+ * The cached data stored for the given |browser|.
*/
- getPersistent: function (browser) {
- return this._persistentData.get(browser);
+ get: function (browser) {
+ return this._data.get(browser);
},
/**
- * Updates persistently cached data for a given |browser|. This data is
- * persistent in the sense that we never clear it, it will always be
- * overwritten.
+ * Updates cached data for a given |browser|.
*
* @param browser (xul:browser)
* The browser belonging to the given tab data.
* @param newData (object)
* The new data to be stored for the given |browser|.
*/
- updatePersistent: function (browser, newData) {
- let data = this._persistentData.get(browser) || {};
+ update: function (browser, newData) {
+ let data = this._data.get(browser) || {};
for (let key of Object.keys(newData)) {
let value = newData[key];
if (value === null) {
delete data[key];
} else {
data[key] = value;
}
}
- this._persistentData.set(browser, data);
- },
-
- _normalizeToBrowser: function(aKey) {
- let nodeName = aKey.localName;
- if (nodeName == "tab") {
- return aKey.linkedBrowser;
- }
- if (nodeName == "browser") {
- return aKey;
- }
- throw new TypeError("Key is neither a tab nor a browser: " + nodeName);
+ this._data.set(browser, data);
}
};
-
-let TabStateCacheTelemetry = {
- // Total number of hits during the session
- hits: 0,
- // Total number of misses during the session
- misses: 0,
- // Total number of clears during the session
- clears: 0,
- // |true| once we have been initialized
- _initialized: false,
-
- /**
- * Record a cache access.
- *
- * @param {boolean} isHit If |true|, the access was a hit, otherwise
- * a miss.
- */
- recordAccess: function(isHit) {
- this._init();
- if (isHit) {
- ++this.hits;
- } else {
- ++this.misses;
- }
- },
-
- /**
- * Record a cache clear
- */
- recordClear: function() {
- this._init();
- ++this.clears;
- },
-
- /**
- * Initialize the telemetry.
- */
- _init: function() {
- if (this._initialized) {
- // Avoid double initialization
- return;
- }
- this._initialized = true;
- Services.obs.addObserver(this, "profile-before-change", false);
- },
-
- observe: function() {
- Services.obs.removeObserver(this, "profile-before-change");
-
- // Record hit/miss rate
- let accesses = this.hits + this.misses;
- if (accesses == 0) {
- return;
- }
-
- this._fillHistogram("HIT_RATE", this.hits, accesses);
- this._fillHistogram("CLEAR_RATIO", this.clears, accesses);
- },
-
- _fillHistogram: function(suffix, positive, total) {
- let PREFIX = "FX_SESSION_RESTORE_TABSTATECACHE_";
- let histo = Services.telemetry.getHistogramById(PREFIX + suffix);
- let rate = Math.floor( ( positive * 100 ) / total );
- histo.add(rate);
- }
-};
--- a/browser/components/sessionstore/src/Utils.jsm
+++ b/browser/components/sessionstore/src/Utils.jsm
@@ -59,21 +59,10 @@ this.Utils = Object.freeze({
if (map.has(otherKey)) {
let otherValue = map.get(otherKey);
map.set(key, otherValue);
map.set(otherKey, value);
} else {
map.set(otherKey, value);
map.delete(key);
}
- },
-
- // Copies all properties of a given object to a new one and returns it.
- copy: function (from) {
- let to = {};
-
- for (let key of Object.keys(from)) {
- to[key] = from[key];
- }
-
- return to;
}
});
--- a/browser/components/sessionstore/src/moz.build
+++ b/browser/components/sessionstore/src/moz.build
@@ -13,17 +13,16 @@ EXTRA_COMPONENTS += [
JS_MODULES_PATH = 'modules/sessionstore'
EXTRA_JS_MODULES = [
'ContentRestore.jsm',
'DocShellCapabilities.jsm',
'FormData.jsm',
'FrameTree.jsm',
'GlobalState.jsm',
- 'Messenger.jsm',
'PageStyle.jsm',
'PrivacyFilter.jsm',
'PrivacyLevel.jsm',
'RecentlyClosedTabsAndWindowsMenuUtils.jsm',
'ScrollPosition.jsm',
'SessionCookies.jsm',
'SessionFile.jsm',
'SessionHistory.jsm',
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -3364,28 +3364,16 @@
},
"FX_SESSION_RESTORE_SESSION_LENGTH": {
"expires_in_version": "never",
"kind": "exponential",
"high": "365",
"n_buckets": 15,
"description": "Session restore: Days elapsed since the session was first started"
},
- "FX_SESSION_RESTORE_TABSTATECACHE_HIT_RATE": {
- "expires_in_version": "never",
- "kind": "enumerated",
- "n_values": 101,
- "description": "Session restore: Percentage of tab state cache hits in all tab state cache accesses"
- },
- "FX_SESSION_RESTORE_TABSTATECACHE_CLEAR_RATIO": {
- "expires_in_version": "never",
- "kind": "enumerated",
- "n_values": 101,
- "description": "Session restore: Number of times the tab state cache has been cleared during a session divided by number of total accesses during the session (percentage)"
- },
"FX_SESSION_RESTORE_EXTRACTING_STATISTICS_DURATION_MS": {
"expires_in_version": "never",
"kind": "exponential",
"high": "3000",
"n_buckets": 10,
"extended_statistics_ok": true,
"description": "Session restore: Duration of the off main thread statistics extraction mechanism (ms)"
},