зеркало из https://github.com/mozilla/gecko-dev.git
Backed out changesets 24c800ff4936 and 5e4e054f3c00 (bug 867143) for mochitest orange.
This commit is contained in:
Родитель
c5d1e4d1ad
Коммит
cb467ff94e
|
@ -13,7 +13,7 @@ function debug(msg) {
|
||||||
let EventListener = {
|
let EventListener = {
|
||||||
|
|
||||||
DOM_EVENTS: [
|
DOM_EVENTS: [
|
||||||
"pageshow", "change", "input", "MozStorageChanged"
|
"pageshow", "change", "input"
|
||||||
],
|
],
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
|
@ -30,24 +30,6 @@ let EventListener = {
|
||||||
case "change":
|
case "change":
|
||||||
sendAsyncMessage("SessionStore:input");
|
sendAsyncMessage("SessionStore:input");
|
||||||
break;
|
break;
|
||||||
case "MozStorageChanged":
|
|
||||||
{
|
|
||||||
let isSessionStorage = true;
|
|
||||||
// We are only interested in sessionStorage events
|
|
||||||
try {
|
|
||||||
if (event.storageArea != content.sessionStorage) {
|
|
||||||
isSessionStorage = false;
|
|
||||||
}
|
|
||||||
} catch (ex) {
|
|
||||||
// This page does not even have sessionStorage
|
|
||||||
// (this is typically the case of about: pages)
|
|
||||||
isSessionStorage = false;
|
|
||||||
}
|
|
||||||
if (isSessionStorage) {
|
|
||||||
sendAsyncMessage("SessionStore:MozStorageChanged");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
debug("received unknown event '" + event.type + "'");
|
debug("received unknown event '" + event.type + "'");
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -58,11 +58,7 @@ const MESSAGES = [
|
||||||
// The content script has received a pageshow event. This happens when a
|
// The content script has received a pageshow event. This happens when a
|
||||||
// page is loaded from bfcache without any network activity, i.e. when
|
// page is loaded from bfcache without any network activity, i.e. when
|
||||||
// clicking the back or forward button.
|
// clicking the back or forward button.
|
||||||
"SessionStore:pageshow",
|
"SessionStore:pageshow"
|
||||||
|
|
||||||
// The content script has received a MozStorageChanged event dealing
|
|
||||||
// with a change in the contents of the sessionStorage.
|
|
||||||
"SessionStore:MozStorageChanged"
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// These are tab events that we listen to.
|
// These are tab events that we listen to.
|
||||||
|
@ -124,16 +120,9 @@ XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
|
||||||
"@mozilla.org/xre/app-info;1", "nsICrashReporter");
|
"@mozilla.org/xre/app-info;1", "nsICrashReporter");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
|
||||||
* |true| if we are in debug mode, |false| otherwise.
|
|
||||||
* Debug mode is controlled by preference browser.sessionstore.debug
|
|
||||||
*/
|
|
||||||
let gDebuggingEnabled = false;
|
|
||||||
function debug(aMsg) {
|
function debug(aMsg) {
|
||||||
if (gDebuggingEnabled) {
|
aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n");
|
||||||
aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n");
|
Services.console.logStringMessage(aMsg);
|
||||||
Services.console.logStringMessage(aMsg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.SessionStore = {
|
this.SessionStore = {
|
||||||
|
@ -548,13 +537,9 @@ let SessionStoreInternal = {
|
||||||
},
|
},
|
||||||
|
|
||||||
_initPrefs : function() {
|
_initPrefs : function() {
|
||||||
this._prefBranch = Services.prefs.getBranch("browser.");
|
XPCOMUtils.defineLazyGetter(this, "_prefBranch", function () {
|
||||||
|
return Services.prefs.getBranch("browser.");
|
||||||
gDebuggingEnabled = this._prefBranch.getBoolPref("sessionstore.debug");
|
});
|
||||||
|
|
||||||
Services.prefs.addObserver("browser.sessionstore.debug", () => {
|
|
||||||
gDebuggingEnabled = this._prefBranch.getBoolPref("sessionstore.debug");
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
// minimal interval between two save operations (in milliseconds)
|
// minimal interval between two save operations (in milliseconds)
|
||||||
XPCOMUtils.defineLazyGetter(this, "_interval", function () {
|
XPCOMUtils.defineLazyGetter(this, "_interval", function () {
|
||||||
|
@ -636,43 +621,37 @@ let SessionStoreInternal = {
|
||||||
if (this._disabledForMultiProcess)
|
if (this._disabledForMultiProcess)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
try {
|
switch (aTopic) {
|
||||||
switch (aTopic) {
|
case "domwindowopened": // catch new windows
|
||||||
case "domwindowopened": // catch new windows
|
this.onOpen(aSubject);
|
||||||
this.onOpen(aSubject);
|
break;
|
||||||
break;
|
case "domwindowclosed": // catch closed windows
|
||||||
case "domwindowclosed": // catch closed windows
|
this.onClose(aSubject);
|
||||||
this.onClose(aSubject);
|
break;
|
||||||
break;
|
case "quit-application-requested":
|
||||||
case "quit-application-requested":
|
this.onQuitApplicationRequested();
|
||||||
this.onQuitApplicationRequested();
|
break;
|
||||||
break;
|
case "quit-application-granted":
|
||||||
case "quit-application-granted":
|
this.onQuitApplicationGranted();
|
||||||
this.onQuitApplicationGranted();
|
break;
|
||||||
break;
|
case "browser-lastwindow-close-granted":
|
||||||
case "browser-lastwindow-close-granted":
|
this.onLastWindowCloseGranted();
|
||||||
this.onLastWindowCloseGranted();
|
break;
|
||||||
break;
|
case "quit-application":
|
||||||
case "quit-application":
|
this.onQuitApplication(aData);
|
||||||
this.onQuitApplication(aData);
|
break;
|
||||||
break;
|
case "browser:purge-session-history": // catch sanitization
|
||||||
case "browser:purge-session-history": // catch sanitization
|
this.onPurgeSessionHistory();
|
||||||
this.onPurgeSessionHistory();
|
break;
|
||||||
break;
|
case "browser:purge-domain-data":
|
||||||
case "browser:purge-domain-data":
|
this.onPurgeDomainData(aData);
|
||||||
this.onPurgeDomainData(aData);
|
break;
|
||||||
break;
|
case "nsPref:changed": // catch pref changes
|
||||||
case "nsPref:changed": // catch pref changes
|
this.onPrefChange(aData);
|
||||||
this.onPrefChange(aData);
|
break;
|
||||||
break;
|
case "timer-callback": // timer call back for delayed saving
|
||||||
case "timer-callback": // timer call back for delayed saving
|
this.onTimerCallback();
|
||||||
this.onTimerCallback();
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (ex) {
|
|
||||||
debug("Uncaught error during observe");
|
|
||||||
debug(ex);
|
|
||||||
debug(ex.stack);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -691,10 +670,6 @@ let SessionStoreInternal = {
|
||||||
case "SessionStore:input":
|
case "SessionStore:input":
|
||||||
this.onTabInput(win, browser);
|
this.onTabInput(win, browser);
|
||||||
break;
|
break;
|
||||||
case "SessionStore:MozStorageChanged":
|
|
||||||
TabStateCache.delete(browser);
|
|
||||||
this.saveStateDelayed(win);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
debug("received unknown message '" + aMessage.name + "'");
|
debug("received unknown message '" + aMessage.name + "'");
|
||||||
break;
|
break;
|
||||||
|
@ -1116,7 +1091,6 @@ let SessionStoreInternal = {
|
||||||
let openWindows = {};
|
let openWindows = {};
|
||||||
this._forEachBrowserWindow(function(aWindow) {
|
this._forEachBrowserWindow(function(aWindow) {
|
||||||
Array.forEach(aWindow.gBrowser.tabs, function(aTab) {
|
Array.forEach(aWindow.gBrowser.tabs, function(aTab) {
|
||||||
TabStateCache.delete(aTab);
|
|
||||||
delete aTab.linkedBrowser.__SS_data;
|
delete aTab.linkedBrowser.__SS_data;
|
||||||
delete aTab.linkedBrowser.__SS_tabStillLoading;
|
delete aTab.linkedBrowser.__SS_tabStillLoading;
|
||||||
delete aTab.linkedBrowser.__SS_formDataSaved;
|
delete aTab.linkedBrowser.__SS_formDataSaved;
|
||||||
|
@ -1340,8 +1314,9 @@ let SessionStoreInternal = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the latest data for this tab (generally, from the cache)
|
// make sure that the tab related data is up-to-date
|
||||||
let tabState = this._collectTabData(aTab);
|
var tabState = this._collectTabData(aTab);
|
||||||
|
this._updateTextAndScrollDataForTab(aWindow, aTab.linkedBrowser, tabState);
|
||||||
|
|
||||||
// store closed-tab data for undo
|
// store closed-tab data for undo
|
||||||
if (this._shouldSaveTabState(tabState)) {
|
if (this._shouldSaveTabState(tabState)) {
|
||||||
|
@ -1362,8 +1337,7 @@ let SessionStoreInternal = {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When a tab loads, invalidate its cached state, trigger async save.
|
* When a tab loads, save state.
|
||||||
*
|
|
||||||
* @param aWindow
|
* @param aWindow
|
||||||
* Window reference
|
* Window reference
|
||||||
* @param aBrowser
|
* @param aBrowser
|
||||||
|
@ -1379,8 +1353,6 @@ let SessionStoreInternal = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TabStateCache.delete(aBrowser);
|
|
||||||
|
|
||||||
delete aBrowser.__SS_data;
|
delete aBrowser.__SS_data;
|
||||||
delete aBrowser.__SS_tabStillLoading;
|
delete aBrowser.__SS_tabStillLoading;
|
||||||
delete aBrowser.__SS_formDataSaved;
|
delete aBrowser.__SS_formDataSaved;
|
||||||
|
@ -1401,8 +1373,6 @@ let SessionStoreInternal = {
|
||||||
// deleting __SS_formDataSaved will cause us to recollect form data
|
// deleting __SS_formDataSaved will cause us to recollect form data
|
||||||
delete aBrowser.__SS_formDataSaved;
|
delete aBrowser.__SS_formDataSaved;
|
||||||
|
|
||||||
TabStateCache.delete(aBrowser);
|
|
||||||
|
|
||||||
this.saveStateDelayed(aWindow, 3000);
|
this.saveStateDelayed(aWindow, 3000);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1526,41 +1496,20 @@ let SessionStoreInternal = {
|
||||||
if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi)
|
if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi)
|
||||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||||
|
|
||||||
let tabState = this._collectTabData(aTab);
|
var tabState = this._collectTabData(aTab);
|
||||||
|
|
||||||
|
var window = aTab.ownerDocument.defaultView;
|
||||||
|
this._updateTextAndScrollDataForTab(window, aTab.linkedBrowser, tabState);
|
||||||
|
|
||||||
return this._toJSONString(tabState);
|
return this._toJSONString(tabState);
|
||||||
},
|
},
|
||||||
|
|
||||||
setTabState: function ssi_setTabState(aTab, aState) {
|
setTabState: function ssi_setTabState(aTab, aState) {
|
||||||
// Remove the tab state from the cache.
|
var tabState = JSON.parse(aState);
|
||||||
// Note that we cannot simply replace the contents of the cache
|
if (!tabState.entries || !aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi)
|
||||||
// as |aState| can be an incomplete state that will be completed
|
|
||||||
// by |restoreHistoryPrecursor|.
|
|
||||||
let tabState = JSON.parse(aState);
|
|
||||||
if (!tabState) {
|
|
||||||
debug("Empty state argument");
|
|
||||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||||
}
|
|
||||||
if (typeof tabState != "object") {
|
|
||||||
debug("State argument does not represent an object");
|
|
||||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
|
||||||
}
|
|
||||||
if (!("entries" in tabState)) {
|
|
||||||
debug("State argument must contain field 'entries'");
|
|
||||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
|
||||||
}
|
|
||||||
if (!aTab.ownerDocument) {
|
|
||||||
debug("Tab argument must have an owner document");
|
|
||||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
|
||||||
}
|
|
||||||
|
|
||||||
let window = aTab.ownerDocument.defaultView;
|
var window = aTab.ownerDocument.defaultView;
|
||||||
if (!("__SSi" in window)) {
|
|
||||||
debug("Default view of ownerDocument must have a unique identifier");
|
|
||||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
|
||||||
}
|
|
||||||
|
|
||||||
TabStateCache.delete(aTab);
|
|
||||||
this._setWindowStateBusy(window);
|
this._setWindowStateBusy(window);
|
||||||
this.restoreHistoryPrecursor(window, [aTab], [tabState], 0, 0, 0);
|
this.restoreHistoryPrecursor(window, [aTab], [tabState], 0, 0, 0);
|
||||||
},
|
},
|
||||||
|
@ -1570,9 +1519,9 @@ let SessionStoreInternal = {
|
||||||
!aWindow.getBrowser)
|
!aWindow.getBrowser)
|
||||||
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
|
||||||
|
|
||||||
// Duplicate the tab state
|
var tabState = this._collectTabData(aTab, true);
|
||||||
let tabState = this._cloneFullTabData(aTab);
|
var sourceWindow = aTab.ownerDocument.defaultView;
|
||||||
|
this._updateTextAndScrollDataForTab(sourceWindow, aTab.linkedBrowser, tabState, true);
|
||||||
tabState.index += aDelta;
|
tabState.index += aDelta;
|
||||||
tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
|
tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
|
||||||
tabState.pinned = false;
|
tabState.pinned = false;
|
||||||
|
@ -1742,7 +1691,6 @@ let SessionStoreInternal = {
|
||||||
if (aWindow.__SSi && this._windows[aWindow.__SSi].extData &&
|
if (aWindow.__SSi && this._windows[aWindow.__SSi].extData &&
|
||||||
this._windows[aWindow.__SSi].extData[aKey])
|
this._windows[aWindow.__SSi].extData[aKey])
|
||||||
delete this._windows[aWindow.__SSi].extData[aKey];
|
delete this._windows[aWindow.__SSi].extData[aKey];
|
||||||
this.saveStateDelayed(aWindow);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getTabValue: function ssi_getTabValue(aTab, aKey) {
|
getTabValue: function ssi_getTabValue(aTab, aKey) {
|
||||||
|
@ -1758,7 +1706,6 @@ let SessionStoreInternal = {
|
||||||
},
|
},
|
||||||
|
|
||||||
setTabValue: function ssi_setTabValue(aTab, aKey, aStringValue) {
|
setTabValue: function ssi_setTabValue(aTab, aKey, aStringValue) {
|
||||||
TabStateCache.delete(aTab);
|
|
||||||
// If the tab hasn't been restored, then set the data there, otherwise we
|
// If the tab hasn't been restored, then set the data there, otherwise we
|
||||||
// could lose newly added data.
|
// could lose newly added data.
|
||||||
let saveTo;
|
let saveTo;
|
||||||
|
@ -1777,7 +1724,6 @@ let SessionStoreInternal = {
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteTabValue: function ssi_deleteTabValue(aTab, aKey) {
|
deleteTabValue: function ssi_deleteTabValue(aTab, aKey) {
|
||||||
TabStateCache.delete(aTab);
|
|
||||||
// We want to make sure that if data is accessed early, we attempt to delete
|
// 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
|
// that data from __SS_data as well. Otherwise we'll throw in cases where
|
||||||
// data can be set or read.
|
// data can be set or read.
|
||||||
|
@ -1791,7 +1737,6 @@ let SessionStoreInternal = {
|
||||||
|
|
||||||
if (deleteFrom && deleteFrom[aKey])
|
if (deleteFrom && deleteFrom[aKey])
|
||||||
delete deleteFrom[aKey];
|
delete deleteFrom[aKey];
|
||||||
this.saveStateDelayed(aTab.ownerDocument.defaultView);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
persistTabAttribute: function ssi_persistTabAttribute(aName) {
|
persistTabAttribute: function ssi_persistTabAttribute(aName) {
|
||||||
|
@ -1969,51 +1914,32 @@ let SessionStoreInternal = {
|
||||||
/* ........ Saving Functionality .............. */
|
/* ........ Saving Functionality .............. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect data related to a single tab
|
* Store all session data for a window
|
||||||
*
|
* @param aWindow
|
||||||
* @param aTab
|
* Window reference
|
||||||
* tabbrowser tab
|
|
||||||
*
|
|
||||||
* @returns {TabData} An object with the data for this tab. If the
|
|
||||||
* tab has not been invalidated since the last call to
|
|
||||||
* _collectTabData(aTab), the same object is returned.
|
|
||||||
*/
|
*/
|
||||||
_collectTabData: function ssi_collectTabData(aTab) {
|
_saveWindowHistory: function ssi_saveWindowHistory(aWindow) {
|
||||||
if (!aTab) {
|
var tabbrowser = aWindow.gBrowser;
|
||||||
throw new TypeError("Expecting a tab");
|
var tabs = tabbrowser.tabs;
|
||||||
}
|
var tabsData = this._windows[aWindow.__SSi].tabs = [];
|
||||||
let tabData;
|
|
||||||
if ((tabData = TabStateCache.get(aTab))) {
|
|
||||||
return tabData;
|
|
||||||
}
|
|
||||||
tabData = new TabData(this._collectBaseTabData(aTab));
|
|
||||||
TabStateCache.set(aTab, tabData);
|
|
||||||
|
|
||||||
this._updateTextAndScrollDataForTab(aTab, tabData);
|
for (var i = 0; i < tabs.length; i++)
|
||||||
return tabData;
|
tabsData.push(this._collectTabData(tabs[i]));
|
||||||
|
|
||||||
|
this._windows[aWindow.__SSi].selected = tabbrowser.mTabBox.selectedIndex + 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect data related to a single tab, including private data.
|
* Collect data related to a single tab
|
||||||
* Use with caution.
|
|
||||||
*
|
|
||||||
* @param aTab
|
* @param aTab
|
||||||
* tabbrowser tab
|
* tabbrowser tab
|
||||||
*
|
* @param aFullData
|
||||||
* @returns {object} An object with the data for this tab. This object
|
* always return privacy sensitive data (use with care)
|
||||||
* is recomputed at every call.
|
* @returns object
|
||||||
*/
|
*/
|
||||||
_cloneFullTabData: function ssi_cloneFullTabData(aTab) {
|
_collectTabData: function ssi_collectTabData(aTab, aFullData) {
|
||||||
let options = { includePrivateData: true };
|
var tabData = { entries: [], lastAccessed: aTab.lastAccessed };
|
||||||
let tabData = this._collectBaseTabData(aTab, options);
|
var browser = aTab.linkedBrowser;
|
||||||
this._updateTextAndScrollDataForTab(aTab, tabData, options);
|
|
||||||
return tabData;
|
|
||||||
},
|
|
||||||
|
|
||||||
_collectBaseTabData: function ssi_collectBaseTabData(aTab, aOptions = null) {
|
|
||||||
let includePrivateData = aOptions && aOptions.includePrivateData;
|
|
||||||
let tabData = {entries: [], lastAccessed: aTab.lastAccessed };
|
|
||||||
let browser = aTab.linkedBrowser;
|
|
||||||
|
|
||||||
if (!browser || !browser.currentURI)
|
if (!browser || !browser.currentURI)
|
||||||
// can happen when calling this function right after .addTab()
|
// can happen when calling this function right after .addTab()
|
||||||
|
@ -2048,7 +1974,7 @@ let SessionStoreInternal = {
|
||||||
if (history && browser.__SS_data &&
|
if (history && browser.__SS_data &&
|
||||||
browser.__SS_data.entries[history.index] &&
|
browser.__SS_data.entries[history.index] &&
|
||||||
browser.__SS_data.entries[history.index].url == browser.currentURI.spec &&
|
browser.__SS_data.entries[history.index].url == browser.currentURI.spec &&
|
||||||
history.index < this._sessionhistory_max_entries - 1 && !includePrivateData) {
|
history.index < this._sessionhistory_max_entries - 1 && !aFullData) {
|
||||||
tabData = browser.__SS_data;
|
tabData = browser.__SS_data;
|
||||||
tabData.index = history.index + 1;
|
tabData.index = history.index + 1;
|
||||||
}
|
}
|
||||||
|
@ -2057,7 +1983,7 @@ let SessionStoreInternal = {
|
||||||
try {
|
try {
|
||||||
for (var j = 0; j < history.count; j++) {
|
for (var j = 0; j < history.count; j++) {
|
||||||
let entry = this._serializeHistoryEntry(history.getEntryAtIndex(j, false),
|
let entry = this._serializeHistoryEntry(history.getEntryAtIndex(j, false),
|
||||||
includePrivateData, aTab.pinned, browser.__SS_hostSchemeData);
|
aFullData, aTab.pinned, browser.__SS_hostSchemeData);
|
||||||
tabData.entries.push(entry);
|
tabData.entries.push(entry);
|
||||||
}
|
}
|
||||||
// If we make it through the for loop, then we're ok and we should clear
|
// If we make it through the for loop, then we're ok and we should clear
|
||||||
|
@ -2083,7 +2009,7 @@ let SessionStoreInternal = {
|
||||||
tabData.index = history.index + 1;
|
tabData.index = history.index + 1;
|
||||||
|
|
||||||
// make sure not to cache privacy sensitive data which shouldn't get out
|
// make sure not to cache privacy sensitive data which shouldn't get out
|
||||||
if (!includePrivateData)
|
if (!aFullData)
|
||||||
browser.__SS_data = tabData;
|
browser.__SS_data = tabData;
|
||||||
}
|
}
|
||||||
else if (browser.currentURI.spec != "about:blank" ||
|
else if (browser.currentURI.spec != "about:blank" ||
|
||||||
|
@ -2132,7 +2058,7 @@ let SessionStoreInternal = {
|
||||||
delete tabData.extData;
|
delete tabData.extData;
|
||||||
|
|
||||||
if (history && browser.docShell instanceof Ci.nsIDocShell) {
|
if (history && browser.docShell instanceof Ci.nsIDocShell) {
|
||||||
let storageData = SessionStorage.serialize(browser.docShell, includePrivateData)
|
let storageData = SessionStorage.serialize(browser.docShell, aFullData)
|
||||||
if (Object.keys(storageData).length)
|
if (Object.keys(storageData).length)
|
||||||
tabData.storage = storageData;
|
tabData.storage = storageData;
|
||||||
}
|
}
|
||||||
|
@ -2145,7 +2071,7 @@ let SessionStoreInternal = {
|
||||||
* Used for data storage
|
* Used for data storage
|
||||||
* @param aEntry
|
* @param aEntry
|
||||||
* nsISHEntry instance
|
* nsISHEntry instance
|
||||||
* @param aIncludePrivateData
|
* @param aFullData
|
||||||
* always return privacy sensitive data (use with care)
|
* always return privacy sensitive data (use with care)
|
||||||
* @param aIsPinned
|
* @param aIsPinned
|
||||||
* the tab is pinned and should be treated differently for privacy
|
* the tab is pinned and should be treated differently for privacy
|
||||||
|
@ -2154,7 +2080,7 @@ let SessionStoreInternal = {
|
||||||
* @returns object
|
* @returns object
|
||||||
*/
|
*/
|
||||||
_serializeHistoryEntry:
|
_serializeHistoryEntry:
|
||||||
function ssi_serializeHistoryEntry(aEntry, aIncludePrivateData, aIsPinned, aHostSchemeData) {
|
function ssi_serializeHistoryEntry(aEntry, aFullData, aIsPinned, aHostSchemeData) {
|
||||||
var entry = { url: aEntry.URI.spec };
|
var entry = { url: aEntry.URI.spec };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -2205,7 +2131,7 @@ let SessionStoreInternal = {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var prefPostdata = this._prefBranch.getIntPref("sessionstore.postdata");
|
var prefPostdata = this._prefBranch.getIntPref("sessionstore.postdata");
|
||||||
if (aEntry.postData && (aIncludePrivateData || prefPostdata &&
|
if (aEntry.postData && (aFullData || prefPostdata &&
|
||||||
this.checkPrivacyLevel(aEntry.URI.schemeIs("https"), aIsPinned))) {
|
this.checkPrivacyLevel(aEntry.URI.schemeIs("https"), aIsPinned))) {
|
||||||
aEntry.postData.QueryInterface(Ci.nsISeekableStream).
|
aEntry.postData.QueryInterface(Ci.nsISeekableStream).
|
||||||
seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
|
seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
|
||||||
|
@ -2214,7 +2140,7 @@ let SessionStoreInternal = {
|
||||||
stream.setInputStream(aEntry.postData);
|
stream.setInputStream(aEntry.postData);
|
||||||
var postBytes = stream.readByteArray(stream.available());
|
var postBytes = stream.readByteArray(stream.available());
|
||||||
var postdata = String.fromCharCode.apply(null, postBytes);
|
var postdata = String.fromCharCode.apply(null, postBytes);
|
||||||
if (aIncludePrivateData || prefPostdata == -1 ||
|
if (aFullData || prefPostdata == -1 ||
|
||||||
postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length <=
|
postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length <=
|
||||||
prefPostdata) {
|
prefPostdata) {
|
||||||
// We can stop doing base64 encoding once our serialization into JSON
|
// We can stop doing base64 encoding once our serialization into JSON
|
||||||
|
@ -2275,7 +2201,7 @@ let SessionStoreInternal = {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
children.push(this._serializeHistoryEntry(child, aIncludePrivateData,
|
children.push(this._serializeHistoryEntry(child, aFullData,
|
||||||
aIsPinned, aHostSchemeData));
|
aIsPinned, aHostSchemeData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2288,25 +2214,37 @@ let SessionStoreInternal = {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Go through all frames and store the current scroll positions
|
* go through all tabs and store the current scroll positions
|
||||||
* and innerHTML content of WYSIWYG editors
|
* and innerHTML content of WYSIWYG editors
|
||||||
*
|
* @param aWindow
|
||||||
* @param aTab
|
* Window reference
|
||||||
* tabbrowser tab
|
*/
|
||||||
|
_updateTextAndScrollData: function ssi_updateTextAndScrollData(aWindow) {
|
||||||
|
var browsers = aWindow.gBrowser.browsers;
|
||||||
|
this._windows[aWindow.__SSi].tabs.forEach(function (tabData, i) {
|
||||||
|
try {
|
||||||
|
this._updateTextAndScrollDataForTab(aWindow, browsers[i], tabData);
|
||||||
|
}
|
||||||
|
catch (ex) { debug(ex); } // get as much data as possible, ignore failures (might succeed the next time)
|
||||||
|
}, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* go through all frames and store the current scroll positions
|
||||||
|
* and innerHTML content of WYSIWYG editors
|
||||||
|
* @param aWindow
|
||||||
|
* Window reference
|
||||||
|
* @param aBrowser
|
||||||
|
* single browser reference
|
||||||
* @param aTabData
|
* @param aTabData
|
||||||
* tabData object to add the information to
|
* tabData object to add the information to
|
||||||
* @param options
|
* @param aFullData
|
||||||
* An optional object that may contain the following field:
|
* always return privacy sensitive data (use with care)
|
||||||
* - includePrivateData: always return privacy sensitive data
|
|
||||||
* (use with care)
|
|
||||||
*/
|
*/
|
||||||
_updateTextAndScrollDataForTab:
|
_updateTextAndScrollDataForTab:
|
||||||
function ssi_updateTextAndScrollDataForTab(aTab, aTabData, aOptions = null) {
|
function ssi_updateTextAndScrollDataForTab(aWindow, aBrowser, aTabData, aFullData) {
|
||||||
let includePrivateData = aOptions && aOptions.includePrivateData;
|
|
||||||
let window = aTab.ownerDocument.defaultView;
|
|
||||||
let browser = aTab.linkedBrowser;
|
|
||||||
// we shouldn't update data for incompletely initialized tabs
|
// we shouldn't update data for incompletely initialized tabs
|
||||||
if (browser.__SS_data && browser.__SS_tabStillLoading)
|
if (aBrowser.__SS_data && aBrowser.__SS_tabStillLoading)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var tabIndex = (aTabData.index || aTabData.entries.length) - 1;
|
var tabIndex = (aTabData.index || aTabData.entries.length) - 1;
|
||||||
|
@ -2314,22 +2252,22 @@ let SessionStoreInternal = {
|
||||||
if (!aTabData.entries[tabIndex])
|
if (!aTabData.entries[tabIndex])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let selectedPageStyle = browser.markupDocumentViewer.authorStyleDisabled ? "_nostyle" :
|
let selectedPageStyle = aBrowser.markupDocumentViewer.authorStyleDisabled ? "_nostyle" :
|
||||||
this._getSelectedPageStyle(browser.contentWindow);
|
this._getSelectedPageStyle(aBrowser.contentWindow);
|
||||||
if (selectedPageStyle)
|
if (selectedPageStyle)
|
||||||
aTabData.pageStyle = selectedPageStyle;
|
aTabData.pageStyle = selectedPageStyle;
|
||||||
else if (aTabData.pageStyle)
|
else if (aTabData.pageStyle)
|
||||||
delete aTabData.pageStyle;
|
delete aTabData.pageStyle;
|
||||||
|
|
||||||
this._updateTextAndScrollDataForFrame(window, browser.contentWindow,
|
this._updateTextAndScrollDataForFrame(aWindow, aBrowser.contentWindow,
|
||||||
aTabData.entries[tabIndex],
|
aTabData.entries[tabIndex],
|
||||||
!browser.__SS_formDataSaved, includePrivateData,
|
!aBrowser.__SS_formDataSaved, aFullData,
|
||||||
!!aTabData.pinned);
|
!!aTabData.pinned);
|
||||||
browser.__SS_formDataSaved = true;
|
aBrowser.__SS_formDataSaved = true;
|
||||||
if (browser.currentURI.spec == "about:config")
|
if (aBrowser.currentURI.spec == "about:config")
|
||||||
aTabData.entries[tabIndex].formdata = {
|
aTabData.entries[tabIndex].formdata = {
|
||||||
id: {
|
id: {
|
||||||
"textbox": browser.contentDocument.getElementById("textbox").value
|
"textbox": aBrowser.contentDocument.getElementById("textbox").value
|
||||||
},
|
},
|
||||||
xpath: {}
|
xpath: {}
|
||||||
};
|
};
|
||||||
|
@ -2346,26 +2284,26 @@ let SessionStoreInternal = {
|
||||||
* part of a tabData object to add the information to
|
* part of a tabData object to add the information to
|
||||||
* @param aUpdateFormData
|
* @param aUpdateFormData
|
||||||
* update all form data for this tab
|
* update all form data for this tab
|
||||||
* @param aIncludePrivateData
|
* @param aFullData
|
||||||
* always return privacy sensitive data (use with care)
|
* always return privacy sensitive data (use with care)
|
||||||
* @param aIsPinned
|
* @param aIsPinned
|
||||||
* the tab is pinned and should be treated differently for privacy
|
* the tab is pinned and should be treated differently for privacy
|
||||||
*/
|
*/
|
||||||
_updateTextAndScrollDataForFrame:
|
_updateTextAndScrollDataForFrame:
|
||||||
function ssi_updateTextAndScrollDataForFrame(aWindow, aContent, aData,
|
function ssi_updateTextAndScrollDataForFrame(aWindow, aContent, aData,
|
||||||
aUpdateFormData, aIncludePrivateData, aIsPinned) {
|
aUpdateFormData, aFullData, aIsPinned) {
|
||||||
for (var i = 0; i < aContent.frames.length; i++) {
|
for (var i = 0; i < aContent.frames.length; i++) {
|
||||||
if (aData.children && aData.children[i])
|
if (aData.children && aData.children[i])
|
||||||
this._updateTextAndScrollDataForFrame(aWindow, aContent.frames[i],
|
this._updateTextAndScrollDataForFrame(aWindow, aContent.frames[i],
|
||||||
aData.children[i], aUpdateFormData,
|
aData.children[i], aUpdateFormData,
|
||||||
aIncludePrivateData, aIsPinned);
|
aFullData, aIsPinned);
|
||||||
}
|
}
|
||||||
var isHTTPS = this._getURIFromString((aContent.parent || aContent).
|
var isHTTPS = this._getURIFromString((aContent.parent || aContent).
|
||||||
document.location.href).schemeIs("https");
|
document.location.href).schemeIs("https");
|
||||||
let topURL = aContent.top.document.location.href;
|
let topURL = aContent.top.document.location.href;
|
||||||
let isAboutSR = topURL == "about:sessionrestore" || topURL == "about:welcomeback";
|
let isAboutSR = topURL == "about:sessionrestore" || topURL == "about:welcomeback";
|
||||||
if (aIncludePrivateData || this.checkPrivacyLevel(isHTTPS, aIsPinned) || isAboutSR) {
|
if (aFullData || this.checkPrivacyLevel(isHTTPS, aIsPinned) || isAboutSR) {
|
||||||
if (aIncludePrivateData || aUpdateFormData) {
|
if (aFullData || aUpdateFormData) {
|
||||||
let formData = DocumentUtils.getFormData(aContent.document);
|
let formData = DocumentUtils.getFormData(aContent.document);
|
||||||
|
|
||||||
// We want to avoid saving data for about:sessionrestore as a string.
|
// We want to avoid saving data for about:sessionrestore as a string.
|
||||||
|
@ -2488,6 +2426,28 @@ let SessionStoreInternal = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* store all hosts for a URL
|
||||||
|
* @param aWindow
|
||||||
|
* Window reference
|
||||||
|
*/
|
||||||
|
_updateCookieHosts: function ssi_updateCookieHosts(aWindow) {
|
||||||
|
var hosts = this._internalWindows[aWindow.__SSi].hosts = {};
|
||||||
|
|
||||||
|
// Since _updateCookiesHosts is only ever called for open windows during a
|
||||||
|
// session, we can call into _extractHostsForCookiesFromHostScheme directly
|
||||||
|
// using data that is attached to each browser.
|
||||||
|
for (let i = 0; i < aWindow.gBrowser.tabs.length; i++) {
|
||||||
|
let tab = aWindow.gBrowser.tabs[i];
|
||||||
|
let hostSchemeData = tab.linkedBrowser.__SS_hostSchemeData || [];
|
||||||
|
for (let j = 0; j < hostSchemeData.length; j++) {
|
||||||
|
this._extractHostsForCookiesFromHostScheme(hostSchemeData[j].host,
|
||||||
|
hostSchemeData[j].scheme,
|
||||||
|
hosts, true, tab.pinned);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize cookie data
|
* Serialize cookie data
|
||||||
* @param aWindows
|
* @param aWindows
|
||||||
|
@ -2725,29 +2685,10 @@ let SessionStoreInternal = {
|
||||||
if (!this._isWindowLoaded(aWindow))
|
if (!this._isWindowLoaded(aWindow))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let tabbrowser = aWindow.gBrowser;
|
|
||||||
let tabs = tabbrowser.tabs;
|
|
||||||
let winData = this._windows[aWindow.__SSi];
|
|
||||||
let tabsData = winData.tabs = [];
|
|
||||||
let hosts = this._internalWindows[aWindow.__SSi].hosts = {};
|
|
||||||
|
|
||||||
// update the internal state data for this window
|
// update the internal state data for this window
|
||||||
for (let tab of tabs) {
|
this._saveWindowHistory(aWindow);
|
||||||
tabsData.push(this._collectTabData(tab));
|
this._updateTextAndScrollData(aWindow);
|
||||||
|
this._updateCookieHosts(aWindow);
|
||||||
// Since we are only ever called for open
|
|
||||||
// windows during a session, we can call into
|
|
||||||
// _extractHostsForCookiesFromHostScheme directly using data
|
|
||||||
// that is attached to each browser.
|
|
||||||
let hostSchemeData = tab.linkedBrowser.__SS_hostSchemeData || [];
|
|
||||||
for (let j = 0; j < hostSchemeData.length; j++) {
|
|
||||||
this._extractHostsForCookiesFromHostScheme(hostSchemeData[j].host,
|
|
||||||
hostSchemeData[j].scheme,
|
|
||||||
hosts, true, tab.pinned);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
winData.selected = tabbrowser.mTabBox.selectedIndex + 1;
|
|
||||||
|
|
||||||
this._updateWindowFeatures(aWindow);
|
this._updateWindowFeatures(aWindow);
|
||||||
|
|
||||||
// Make sure we keep __SS_lastSessionWindowID around for cases like entering
|
// Make sure we keep __SS_lastSessionWindowID around for cases like entering
|
||||||
|
@ -3046,7 +2987,6 @@ let SessionStoreInternal = {
|
||||||
restoreHistoryPrecursor:
|
restoreHistoryPrecursor:
|
||||||
function ssi_restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab,
|
function ssi_restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab,
|
||||||
aIx, aCount, aRestoreImmediately = false) {
|
aIx, aCount, aRestoreImmediately = false) {
|
||||||
|
|
||||||
var tabbrowser = aWindow.gBrowser;
|
var tabbrowser = aWindow.gBrowser;
|
||||||
|
|
||||||
// make sure that all browsers and their histories are available
|
// make sure that all browsers and their histories are available
|
||||||
|
@ -3062,7 +3002,7 @@ let SessionStoreInternal = {
|
||||||
var restoreHistoryFunc = function(self) {
|
var restoreHistoryFunc = function(self) {
|
||||||
self.restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab,
|
self.restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab,
|
||||||
aIx, aCount + 1, aRestoreImmediately);
|
aIx, aCount + 1, aRestoreImmediately);
|
||||||
};
|
}
|
||||||
aWindow.setTimeout(restoreHistoryFunc, 100, this);
|
aWindow.setTimeout(restoreHistoryFunc, 100, this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -3198,6 +3138,7 @@ let SessionStoreInternal = {
|
||||||
|
|
||||||
var tab = aTabs.shift();
|
var tab = aTabs.shift();
|
||||||
var tabData = aTabData.shift();
|
var tabData = aTabData.shift();
|
||||||
|
|
||||||
var browser = aWindow.gBrowser.getBrowserForTab(tab);
|
var browser = aWindow.gBrowser.getBrowserForTab(tab);
|
||||||
var history = browser.webNavigation.sessionHistory;
|
var history = browser.webNavigation.sessionHistory;
|
||||||
|
|
||||||
|
@ -3762,7 +3703,7 @@ let SessionStoreInternal = {
|
||||||
* @param aDelay
|
* @param aDelay
|
||||||
* Milliseconds to delay
|
* Milliseconds to delay
|
||||||
*/
|
*/
|
||||||
saveStateDelayed: function ssi_saveStateDelayed(aWindow = null, aDelay = 2000) {
|
saveStateDelayed: function ssi_saveStateDelayed(aWindow, aDelay) {
|
||||||
if (aWindow) {
|
if (aWindow) {
|
||||||
this._dirtyWindows[aWindow.__SSi] = true;
|
this._dirtyWindows[aWindow.__SSi] = true;
|
||||||
}
|
}
|
||||||
|
@ -3772,7 +3713,7 @@ let SessionStoreInternal = {
|
||||||
var minimalDelay = this._lastSaveTime + this._interval - Date.now();
|
var minimalDelay = this._lastSaveTime + this._interval - Date.now();
|
||||||
|
|
||||||
// if we have to wait, set a timer, otherwise saveState directly
|
// if we have to wait, set a timer, otherwise saveState directly
|
||||||
aDelay = Math.max(minimalDelay, aDelay);
|
aDelay = Math.max(minimalDelay, aDelay || 2000);
|
||||||
if (aDelay > 0) {
|
if (aDelay > 0) {
|
||||||
this._saveTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
this._saveTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||||
this._saveTimer.init(this, aDelay, Ci.nsITimer.TYPE_ONE_SHOT);
|
this._saveTimer.init(this, aDelay, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||||
|
@ -4044,7 +3985,6 @@ let SessionStoreInternal = {
|
||||||
if (tab.linkedBrowser == aBrowser)
|
if (tab.linkedBrowser == aBrowser)
|
||||||
return tab;
|
return tab;
|
||||||
}
|
}
|
||||||
return undefined;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4867,25 +4807,11 @@ SessionStoreSHistoryListener.prototype = {
|
||||||
Ci.nsISupportsWeakReference
|
Ci.nsISupportsWeakReference
|
||||||
]),
|
]),
|
||||||
browser: null,
|
browser: null,
|
||||||
// The following events (with the exception of OnHistoryPurge)
|
OnHistoryNewEntry: function(aNewURI) { },
|
||||||
// accompany either a "load" or a "pageshow" which will in turn cause
|
OnHistoryGoBack: function(aBackURI) { return true; },
|
||||||
// invalidations.
|
OnHistoryGoForward: function(aForwardURI) { return true; },
|
||||||
OnHistoryNewEntry: function(aNewURI) {
|
OnHistoryGotoIndex: function(aIndex, aGotoURI) { return true; },
|
||||||
|
OnHistoryPurge: function(aNumEntries) { return true; },
|
||||||
},
|
|
||||||
OnHistoryGoBack: function(aBackURI) {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
OnHistoryGoForward: function(aForwardURI) {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
OnHistoryGotoIndex: function(aIndex, aGotoURI) {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
OnHistoryPurge: function(aNumEntries) {
|
|
||||||
TabStateCache.delete(this.tab);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
OnHistoryReload: function(aReloadURI, aReloadFlags) {
|
OnHistoryReload: function(aReloadURI, aReloadFlags) {
|
||||||
// On reload, we want to make sure that session history loads the right
|
// On reload, we want to make sure that session history loads the right
|
||||||
// URI. In order to do that, we will juet call restoreTab. That will remove
|
// URI. In order to do that, we will juet call restoreTab. That will remove
|
||||||
|
@ -4908,83 +4834,4 @@ String.prototype.hasRootDomain = function hasRootDomain(aDomain) {
|
||||||
let prevChar = this[index - 1];
|
let prevChar = this[index - 1];
|
||||||
return (index == (this.length - aDomain.length)) &&
|
return (index == (this.length - aDomain.length)) &&
|
||||||
(prevChar == "." || prevChar == "/");
|
(prevChar == "." || prevChar == "/");
|
||||||
};
|
|
||||||
|
|
||||||
function TabData(obj = null) {
|
|
||||||
if (obj) {
|
|
||||||
if (obj instanceof TabData) {
|
|
||||||
// FIXME: Can we get rid of this?
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
for (let [key, value] in Iterator(obj)) {
|
|
||||||
this[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A cache for tabs data.
|
|
||||||
*
|
|
||||||
* This cache implements a weak map from tabs (as XUL elements)
|
|
||||||
* to tab data (as instances of TabData).
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
let TabStateCache = {
|
|
||||||
_data: new WeakMap(),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 {TabData} aValue The data associated to |aTab|.
|
|
||||||
*/
|
|
||||||
set: function(aTab, aValue) {
|
|
||||||
let key = this._normalizeToBrowser(aTab);
|
|
||||||
if (!(aValue instanceof TabData)) {
|
|
||||||
throw new TypeError("Attempting to cache a non TabData");
|
|
||||||
}
|
|
||||||
this._data.set(key, aValue);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the tab data associated with a tab.
|
|
||||||
*
|
|
||||||
* @param {XULElement} aKey The tab or the associated browser.
|
|
||||||
*
|
|
||||||
* @return {TabData|undefined} The data if available, |undefined|
|
|
||||||
* otherwise.
|
|
||||||
*/
|
|
||||||
get: function(aKey) {
|
|
||||||
let key = this._normalizeToBrowser(aKey);
|
|
||||||
return this._data.get(key);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
},
|
|
||||||
|
|
||||||
_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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ MOCHITEST_BROWSER_FILES = \
|
||||||
browser_input.js \
|
browser_input.js \
|
||||||
browser_input_sample.html \
|
browser_input_sample.html \
|
||||||
browser_pageshow.js \
|
browser_pageshow.js \
|
||||||
browser_sessionStorage.js \
|
|
||||||
browser_upgrade_backup.js \
|
browser_upgrade_backup.js \
|
||||||
browser_windowRestore_perwindowpb.js \
|
browser_windowRestore_perwindowpb.js \
|
||||||
browser_248970_b_perwindowpb.js \
|
browser_248970_b_perwindowpb.js \
|
||||||
|
|
|
@ -1,89 +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/. */
|
|
||||||
|
|
||||||
let Scope = {};
|
|
||||||
Cu.import("resource://gre/modules/Task.jsm", Scope);
|
|
||||||
Cu.import("resource://gre/modules/Promise.jsm", Scope);
|
|
||||||
let {Task, Promise} = Scope;
|
|
||||||
|
|
||||||
function promiseBrowserLoaded(aBrowser) {
|
|
||||||
let deferred = Promise.defer();
|
|
||||||
whenBrowserLoaded(aBrowser, () => deferred.resolve());
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
function forceWriteState() {
|
|
||||||
let deferred = Promise.defer();
|
|
||||||
const PREF = "browser.sessionstore.interval";
|
|
||||||
const TOPIC = "sessionstore-state-write";
|
|
||||||
|
|
||||||
Services.obs.addObserver(function observe() {
|
|
||||||
Services.obs.removeObserver(observe, TOPIC);
|
|
||||||
Services.prefs.clearUserPref(PREF);
|
|
||||||
deferred.resolve();
|
|
||||||
}, TOPIC, false);
|
|
||||||
|
|
||||||
Services.prefs.setIntPref(PREF, 0);
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
function waitForStorageChange(aTab) {
|
|
||||||
let deferred = Promise.defer();
|
|
||||||
waitForContentMessage(aTab.linkedBrowser,
|
|
||||||
"sessionstore:MozStorageChanged",
|
|
||||||
200,
|
|
||||||
((x) => deferred.resolve(x)));
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
function test() {
|
|
||||||
|
|
||||||
waitForExplicitFinish();
|
|
||||||
|
|
||||||
let tab;
|
|
||||||
Task.spawn(function() {
|
|
||||||
try {
|
|
||||||
tab = gBrowser.addTab("http://example.com");
|
|
||||||
// about:home supports sessionStorage and localStorage
|
|
||||||
|
|
||||||
let win = tab.linkedBrowser.contentWindow;
|
|
||||||
|
|
||||||
// Flush loading and next save, call getBrowserState()
|
|
||||||
// a few times to ensure that everything is cached.
|
|
||||||
yield promiseBrowserLoaded(tab.linkedBrowser);
|
|
||||||
yield forceWriteState();
|
|
||||||
info("Calling getBrowserState() to populate cache");
|
|
||||||
ss.getBrowserState();
|
|
||||||
|
|
||||||
info("Change sessionStorage, ensure that state is saved");
|
|
||||||
win.sessionStorage["SESSION_STORAGE_KEY"] = "SESSION_STORAGE_VALUE";
|
|
||||||
yield waitForStorageChange(tab);
|
|
||||||
yield forceWriteState();
|
|
||||||
|
|
||||||
let state = ss.getBrowserState();
|
|
||||||
ok(state.indexOf("SESSION_STORAGE_KEY") != -1, "Key appears in state");
|
|
||||||
ok(state.indexOf("SESSION_STORAGE_VALUE") != -1, "Value appears in state");
|
|
||||||
|
|
||||||
|
|
||||||
info("Change localStorage, ensure that state is not saved");
|
|
||||||
win.localStorage["LOCAL_STORAGE_KEY"] = "LOCAL_STORAGE_VALUE";
|
|
||||||
yield waitForStorageChange(tab);
|
|
||||||
yield forceWriteState();
|
|
||||||
|
|
||||||
state = ss.getBrowserState();
|
|
||||||
ok(state.indexOf("LOCAL_STORAGE_KEY") == -1, "Key does not appear in state");
|
|
||||||
ok(state.indexOf("LOCAL_STORAGE_VALUE") == -1, "Value does not appear in state");
|
|
||||||
} catch (ex) {
|
|
||||||
ok(false, ex);
|
|
||||||
info(ex.stack);
|
|
||||||
} finally {
|
|
||||||
// clean up
|
|
||||||
if (tab) {
|
|
||||||
gBrowser.removeTab(tab);
|
|
||||||
}
|
|
||||||
|
|
||||||
executeSoon(finish);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -156,90 +156,43 @@ function waitForTabState(aTab, aState, aCallback) {
|
||||||
ss.setTabState(aTab, JSON.stringify(aState));
|
ss.setTabState(aTab, JSON.stringify(aState));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// waitForSaveState waits for a state write but not necessarily for the state to
|
||||||
* Wait for a content -> chrome message.
|
// turn dirty.
|
||||||
*/
|
function waitForSaveState(aSaveStateCallback) {
|
||||||
function waitForContentMessage(aBrowser, aTopic, aTimeout, aCallback) {
|
|
||||||
let mm = aBrowser.messageManager;
|
|
||||||
let observing = false;
|
let observing = false;
|
||||||
function removeObserver() {
|
let topic = "sessionstore-state-write";
|
||||||
if (!observing)
|
|
||||||
return;
|
|
||||||
mm.removeMessageListener(aTopic, observer);
|
|
||||||
observing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeout = setTimeout(function () {
|
let sessionSaveTimeout = 1000 +
|
||||||
removeObserver();
|
|
||||||
aCallback(false);
|
|
||||||
}, aTimeout);
|
|
||||||
|
|
||||||
function observer(aSubject, aTopic, aData) {
|
|
||||||
removeObserver();
|
|
||||||
timeout = clearTimeout(timeout);
|
|
||||||
executeSoon(() => aCallback(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
registerCleanupFunction(function() {
|
|
||||||
removeObserver();
|
|
||||||
if (timeout) {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
observing = true;
|
|
||||||
mm.addMessageListener(aTopic, observer);
|
|
||||||
}
|
|
||||||
|
|
||||||
function waitForTopic(aTopic, aTimeout, aCallback) {
|
|
||||||
let observing = false;
|
|
||||||
function removeObserver() {
|
|
||||||
if (!observing)
|
|
||||||
return;
|
|
||||||
Services.obs.removeObserver(observer, aTopic);
|
|
||||||
observing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeout = setTimeout(function () {
|
|
||||||
removeObserver();
|
|
||||||
aCallback(false);
|
|
||||||
}, aTimeout);
|
|
||||||
|
|
||||||
function observer(aSubject, aTopic, aData) {
|
|
||||||
removeObserver();
|
|
||||||
timeout = clearTimeout(timeout);
|
|
||||||
executeSoon(() => aCallback(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
registerCleanupFunction(function() {
|
|
||||||
removeObserver();
|
|
||||||
if (timeout) {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
observing = true;
|
|
||||||
Services.obs.addObserver(observer, aTopic, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wait until session restore has finished collecting its data and is
|
|
||||||
* getting ready to write that data ("sessionstore-state-write").
|
|
||||||
*
|
|
||||||
* This function is meant to be called immediately after the code
|
|
||||||
* that will trigger the saving.
|
|
||||||
*
|
|
||||||
* Note that this does not wait for the disk write to be complete.
|
|
||||||
*
|
|
||||||
* @param {function} aCallback If sessionstore-state-write is sent
|
|
||||||
* within buffering interval + 100 ms, the callback is passed |true|,
|
|
||||||
* otherwise, it is passed |false|.
|
|
||||||
*/
|
|
||||||
function waitForSaveState(aCallback) {
|
|
||||||
let timeout = 100 +
|
|
||||||
Services.prefs.getIntPref("browser.sessionstore.interval");
|
Services.prefs.getIntPref("browser.sessionstore.interval");
|
||||||
return waitForTopic("sessionstore-state-write", timeout, aCallback);
|
|
||||||
}
|
function removeObserver() {
|
||||||
|
if (!observing)
|
||||||
|
return;
|
||||||
|
Services.obs.removeObserver(observer, topic);
|
||||||
|
observing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeout = setTimeout(function () {
|
||||||
|
removeObserver();
|
||||||
|
aSaveStateCallback();
|
||||||
|
}, sessionSaveTimeout);
|
||||||
|
|
||||||
|
function observer(aSubject, aTopic, aData) {
|
||||||
|
removeObserver();
|
||||||
|
timeout = clearTimeout(timeout);
|
||||||
|
executeSoon(aSaveStateCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerCleanupFunction(function() {
|
||||||
|
removeObserver();
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
observing = true;
|
||||||
|
Services.obs.addObserver(observer, topic, false);
|
||||||
|
};
|
||||||
|
|
||||||
function whenBrowserLoaded(aBrowser, aCallback = next) {
|
function whenBrowserLoaded(aBrowser, aCallback = next) {
|
||||||
aBrowser.addEventListener("load", function onLoad() {
|
aBrowser.addEventListener("load", function onLoad() {
|
||||||
|
|
Загрузка…
Ссылка в новой задаче