Backed out 5 changesets (bug 894595) for causing new mochitest-bc intermittent failures.

Backed out changeset 778adbf10481 (bug 894595)
Backed out changeset 5b621d8eb5e9 (bug 894595)
Backed out changeset 91303ff403ac (bug 894595)
Backed out changeset 3caa785122bf (bug 894595)
Backed out changeset 108e2b8e177c (bug 894595)
This commit is contained in:
Ryan VanderMeulen 2013-09-17 13:35:58 -04:00
parent ebb683b1ca
commit 59d81139f5
11 changed files with 493 additions and 1007 deletions

View File

@ -860,8 +860,6 @@ pref("browser.sessionstore.restore_pinned_tabs_on_demand", false);
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);

View File

@ -2,19 +2,12 @@
* 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";
function debug(msg) {
Services.console.logStringMessage("SessionStoreContent: " + msg);
}
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
"resource:///modules/sessionstore/SessionHistory.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
"resource:///modules/sessionstore/SessionStorage.jsm");
/**
* Listens for and handles content events that we need for the
* session store service to be notified of state changes in content.
@ -62,37 +55,7 @@ let EventListener = {
}
}
};
/**
* Listens for and handles messages sent by the session store service.
*/
let MessageListener = {
MESSAGES: [
"SessionStore:collectSessionHistory",
"SessionStore:collectSessionStorage"
],
init: function () {
this.MESSAGES.forEach(m => addMessageListener(m, this));
},
receiveMessage: function ({name, data: {id}}) {
switch (name) {
case "SessionStore:collectSessionHistory":
let history = SessionHistory.read(docShell);
sendAsyncMessage(name, {id: id, data: history});
break;
case "SessionStore:collectSessionStorage":
let storage = SessionStorage.serialize(docShell);
sendAsyncMessage(name, {id: id, data: storage});
break;
default:
debug("received unknown message '" + name + "'");
break;
}
}
};
EventListener.init();
let ProgressListener = {
init: function() {
@ -111,7 +74,4 @@ let ProgressListener = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference])
};
EventListener.init();
MessageListener.init();
ProgressListener.init();

View File

@ -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;
}
};

View File

@ -1,82 +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 = ["PrivacyLevel"];
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
const PREF_NORMAL = "browser.sessionstore.privacy_level";
const PREF_DEFERRED = "browser.sessionstore.privacy_level_deferred";
// The following constants represent the different possible privacy levels that
// can be set by the user and that we need to consider when collecting text
// data, cookies, and POSTDATA.
//
// Collect data from all sites (http and https).
const PRIVACY_NONE = 0;
// Collect data from unencrypted sites (http), only.
const PRIVACY_ENCRYPTED = 1;
// Collect no data.
const PRIVACY_FULL = 2;
/**
* Returns whether we will resume the session automatically on next startup.
*/
function willResumeAutomatically() {
return Services.prefs.getIntPref("browser.startup.page") == 3 ||
Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
}
/**
* Determines the current privacy level as set by the user.
*
* @param isPinned
* Whether to return the privacy level for pinned tabs.
* @return {int} The privacy level as read from the user's preferences.
*/
function getCurrentLevel(isPinned) {
let pref = PREF_NORMAL;
// If we're in the process of quitting and we're not autoresuming the session
// then we will use the deferred privacy level for non-pinned tabs.
if (!isPinned && Services.startup.shuttingDown && !willResumeAutomatically()) {
pref = PREF_DEFERRED;
}
return Services.prefs.getIntPref(pref);
}
/**
* The external API as exposed by this module.
*/
let PrivacyLevel = Object.freeze({
/**
* Checks whether we're allowed to save data for a specific site.
*
* @param {isHttps: boolean, isPinned: boolean}
* An object that must have two properties: 'isHttps' and 'isPinned'.
* 'isHttps' tells whether the site us secure communication (HTTPS).
* 'isPinned' tells whether the site is loaded in a pinned tab.
* @return {bool} Whether we can save data for the specified site.
*/
canSave: function ({isHttps, isPinned}) {
let level = getCurrentLevel(isPinned);
// Never save any data when full privacy is requested.
if (level == PRIVACY_FULL) {
return false;
}
// Don't save data for encrypted sites when requested.
if (isHttps && level == PRIVACY_ENCRYPTED) {
return false;
}
return true;
}
});

View File

@ -12,8 +12,8 @@ const Ci = Components.interfaces;
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
"resource:///modules/sessionstore/PrivacyLevel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
"resource:///modules/sessionstore/SessionStore.jsm");
// MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision.
const MAX_EXPIRY = Math.pow(2, 62);
@ -67,8 +67,8 @@ let SessionCookiesInternal = {
for (let cookie of CookieStore.getCookiesForHost(host)) {
// _getCookiesForHost() will only return hosts with the right privacy
// rules, so there is no need to do anything special with this call
// to PrivacyLevel.canSave().
if (PrivacyLevel.canSave({isHttps: cookie.secure, isPinned: isPinned})) {
// to checkPrivacyLevel().
if (SessionStore.checkPrivacyLevel(cookie.secure, isPinned)) {
cookies.push(cookie);
}
}
@ -209,7 +209,7 @@ let SessionCookiesInternal = {
// case testing scheme will be sufficient.
if (/https?/.test(scheme) && !hosts[host] &&
(!checkPrivacy ||
PrivacyLevel.canSave({isHttps: scheme == "https", isPinned: isPinned}))) {
SessionStore.checkPrivacyLevel(scheme == "https", isPinned))) {
// By setting this to true or false, we can determine when looking at
// the host in update() if we should check for privacy.
hosts[host] = isPinned;

View File

@ -1,272 +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 = ["SessionHistory"];
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
"resource:///modules/sessionstore/PrivacyLevel.jsm");
function debug(msg) {
Services.console.logStringMessage("SessionHistory: " + msg);
}
// The preference value that determines how much post data to save.
XPCOMUtils.defineLazyGetter(this, "gPostData", function () {
const PREF = "browser.sessionstore.postdata";
// Observer that updates the cached value when the preference changes.
Services.prefs.addObserver(PREF, () => {
this.gPostData = Services.prefs.getIntPref(PREF);
}, false);
return Services.prefs.getIntPref(PREF);
});
/**
* The external API exported by this module.
*/
this.SessionHistory = Object.freeze({
read: function (docShell, includePrivateData) {
return SessionHistoryInternal.read(docShell, includePrivateData);
}
});
/**
* The internal API for the SessionHistory module.
*/
let SessionHistoryInternal = {
/**
* Collects session history data for a given docShell.
*
* @param docShell
* The docShell that owns the session history.
* @param includePrivateData (optional)
* True to always include private data and skip any privacy checks.
*/
read: function (docShell, includePrivateData = false) {
let data = {entries: []};
let isPinned = docShell.isAppTab;
let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
let history = webNavigation.sessionHistory;
if (history && history.count > 0) {
try {
for (let i = 0; i < history.count; i++) {
let shEntry = history.getEntryAtIndex(i, false);
let entry = this._serializeEntry(shEntry, includePrivateData, isPinned);
data.entries.push(entry);
}
} catch (ex) {
// In some cases, getEntryAtIndex will throw. This seems to be due to
// history.count being higher than it should be. By doing this in a
// try-catch, we'll update history to where it breaks, print an error
// message, and still save sessionstore.js.
debug("SessionStore failed gathering complete history " +
"for the focused window/tab. See bug 669196.");
}
data.index = history.index + 1;
} else {
let uri = webNavigation.currentURI.spec;
// We landed here because the history is inaccessible or there are no
// history entries. In that case we should at least record the docShell's
// current URL as a single history entry. If the URL is not about:blank
// or it's a blank tab that was modified (like a custom newtab page),
// record it. For about:blank we explicitly want an empty array without
// an 'index' property to denote that there are no history entries.
if (uri != "about:blank" || webNavigation.document.body.hasChildNodes()) {
data.entries.push({ url: uri });
data.index = 1;
}
}
return data;
},
/**
* Get an object that is a serialized representation of a History entry.
*
* @param shEntry
* nsISHEntry instance
* @param includePrivateData
* Always return privacy sensitive data (use with care).
* @param isPinned
* The tab is pinned and should be treated differently for privacy.
* @return object
*/
_serializeEntry: function (shEntry, includePrivateData, isPinned) {
let entry = { url: shEntry.URI.spec };
// Save some bytes and don't include the title property
// if that's identical to the current entry's URL.
if (shEntry.title && shEntry.title != entry.url) {
entry.title = shEntry.title;
}
if (shEntry.isSubFrame) {
entry.subframe = true;
}
let cacheKey = shEntry.cacheKey;
if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 &&
cacheKey.data != 0) {
// XXXbz would be better to have cache keys implement
// nsISerializable or something.
entry.cacheKey = cacheKey.data;
}
entry.ID = shEntry.ID;
entry.docshellID = shEntry.docshellID;
// We will include the property only if it's truthy to save a couple of
// bytes when the resulting object is stringified and saved to disk.
if (shEntry.referrerURI)
entry.referrer = shEntry.referrerURI.spec;
if (shEntry.srcdocData)
entry.srcdocData = shEntry.srcdocData;
if (shEntry.isSrcdocEntry)
entry.isSrcdocEntry = shEntry.isSrcdocEntry;
if (shEntry.contentType)
entry.contentType = shEntry.contentType;
let x = {}, y = {};
shEntry.getScrollPosition(x, y);
if (x.value != 0 || y.value != 0)
entry.scroll = x.value + "," + y.value;
// Collect post data for the current history entry.
try {
let postdata = this._serializePostData(shEntry, isPinned);
if (postdata) {
entry.postdata_b64 = postdata;
}
} catch (ex) {
// POSTDATA is tricky - especially since some extensions don't get it right
debug("Failed serializing post data: " + ex);
}
// Collect owner data for the current history entry.
try {
let owner = this._serializeOwner(shEntry);
if (owner) {
entry.owner_b64 = owner;
}
} catch (ex) {
// Not catching anything specific here, just possible errors
// from writeCompoundObject() and the like.
debug("Failed serializing owner data: " + ex);
}
entry.docIdentifier = shEntry.BFCacheEntry.ID;
if (shEntry.stateData != null) {
entry.structuredCloneState = shEntry.stateData.getDataAsBase64();
entry.structuredCloneVersion = shEntry.stateData.formatVersion;
}
if (!(shEntry instanceof Ci.nsISHContainer)) {
return entry;
}
if (shEntry.childCount > 0) {
let children = [];
for (let i = 0; i < shEntry.childCount; i++) {
let child = shEntry.GetChildAt(i);
if (child) {
// Don't try to restore framesets containing wyciwyg URLs.
// (cf. bug 424689 and bug 450595)
if (child.URI.schemeIs("wyciwyg")) {
children.length = 0;
break;
}
children.push(this._serializeEntry(child, includePrivateData, isPinned));
}
}
if (children.length) {
entry.children = children;
}
}
return entry;
},
/**
* Serialize post data contained in the given session history entry.
*
* @param shEntry
* The session history entry.
* @param isPinned
* Whether the docShell is owned by a pinned tab.
* @return The base64 encoded post data.
*/
_serializePostData: function (shEntry, isPinned) {
let isHttps = shEntry.URI.schemeIs("https");
if (!shEntry.postData || !gPostData ||
!PrivacyLevel.canSave({isHttps: isHttps, isPinned: isPinned})) {
return null;
}
shEntry.postData.QueryInterface(Ci.nsISeekableStream)
.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
let stream = Cc["@mozilla.org/binaryinputstream;1"]
.createInstance(Ci.nsIBinaryInputStream);
stream.setInputStream(shEntry.postData);
let postBytes = stream.readByteArray(stream.available());
let postdata = String.fromCharCode.apply(null, postBytes);
if (gPostData != -1 &&
postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length > gPostData) {
return null;
}
// We can stop doing base64 encoding once our serialization into JSON
// is guaranteed to handle all chars in strings, including embedded
// nulls.
return btoa(postdata);
},
/**
* Serialize owner data contained in the given session history entry.
*
* @param shEntry
* The session history entry.
* @return The base64 encoded owner data.
*/
_serializeOwner: function (shEntry) {
if (!shEntry.owner) {
return null;
}
let binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
createInstance(Ci.nsIObjectOutputStream);
let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
pipe.init(false, false, 0, 0xffffffff, null);
binaryStream.setOutputStream(pipe.outputStream);
binaryStream.writeCompoundObject(shEntry.owner, Ci.nsISupports, true);
binaryStream.close();
// Now we want to read the data from the pipe's input end and encode it.
let scriptableStream = Cc["@mozilla.org/binaryinputstream;1"].
createInstance(Ci.nsIBinaryInputStream);
scriptableStream.setInputStream(pipe.inputStream);
let ownerBytes =
scriptableStream.readByteArray(scriptableStream.available());
// We can stop doing base64 encoding once our serialization into JSON
// is guaranteed to handle all chars in strings, including embedded
// nulls.
return btoa(String.fromCharCode.apply(null, ownerBytes));
}
};

View File

@ -151,7 +151,7 @@ let SessionSaverInternal = {
delay = Math.max(this._lastSaveTime + gInterval - Date.now(), delay, 0);
// Schedule a state save.
this._timeoutID = setTimeout(() => this._saveStateAsync(), delay);
this._timeoutID = setTimeout(() => this._saveState(), delay);
},
/**
@ -186,7 +186,8 @@ let SessionSaverInternal = {
* update the corresponding caches.
*/
_saveState: function (forceUpdateAllWindows = false) {
// Cancel any pending timeouts.
// Cancel any pending timeouts or just clear
// the timeout if this is why we've been called.
this.cancel();
stopWatchStart("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
@ -245,33 +246,6 @@ let SessionSaverInternal = {
this._writeState(state);
},
/**
* 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 the given state object to disk.
*/

View File

@ -7,13 +7,12 @@
this.EXPORTED_SYMBOLS = ["SessionStorage"];
const Cu = Components.utils;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
"resource:///modules/sessionstore/PrivacyLevel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
"resource:///modules/sessionstore/SessionStore.jsm");
this.SessionStorage = {
/**
@ -52,17 +51,16 @@ let DomStorage = {
read: function DomStorage_read(aDocShell, aFullData) {
let data = {};
let isPinned = aDocShell.isAppTab;
let webNavigation = aDocShell.QueryInterface(Ci.nsIWebNavigation);
let shistory = webNavigation.sessionHistory;
let shistory = aDocShell.sessionHistory;
for (let i = 0; shistory && i < shistory.count; i++) {
for (let i = 0; i < shistory.count; i++) {
let principal = History.getPrincipalForEntry(shistory, i, aDocShell);
if (!principal)
continue;
// Check if we're allowed to store sessionStorage data.
let isHttps = principal.URI && principal.URI.schemeIs("https");
if (aFullData || PrivacyLevel.canSave({isHttps: isHttps, isPinned: isPinned})) {
let isHTTPS = principal.URI && principal.URI.schemeIs("https");
if (aFullData || SessionStore.checkPrivacyLevel(isHTTPS, isPinned)) {
let origin = principal.jarPrefix + principal.origin;
// Don't read a host twice.
@ -92,8 +90,8 @@ let DomStorage = {
let storageManager = aDocShell.QueryInterface(Components.interfaces.nsIDOMStorageManager);
// There is no need to pass documentURI, it's only used to fill documentURI property of
// domstorage event, which in this case has no consumer. Prevention of events in case
// of missing documentURI will be solved in a followup bug to bug 600307.
// domstorage event, which in this case has no consumer. Prevention of events in case
// of missing documentURI will be solved in a followup bug to bug 600307.
let storage = storageManager.createStorage(principal, "", aDocShell.usePrivateBrowsing);
for (let [key, value] in Iterator(data)) {

File diff suppressed because it is too large Load Diff

View File

@ -14,10 +14,7 @@ JS_MODULES_PATH = 'modules/sessionstore'
EXTRA_JS_MODULES = [
'DocumentUtils.jsm',
'Messenger.jsm',
'PrivacyLevel.jsm',
'SessionCookies.jsm',
'SessionHistory.jsm',
'SessionMigration.jsm',
'SessionStorage.jsm',
'SessionWorker.js',

View File

@ -58,6 +58,7 @@ function test() {
ss.getBrowserState();
is(gBrowser.tabs[1], tab, "newly created tab should exist by now");
ok(tab.linkedBrowser.__SS_data, "newly created tab should be in save state");
// Start a load and interrupt it by closing the tab
tab.linkedBrowser.loadURI(URI_TO_LOAD);