Merge mozilla-central and fx-team

This commit is contained in:
Ed Morley 2013-09-17 15:22:00 +01:00
commit 2ba324aea3
31 changed files with 1225 additions and 551 deletions

View File

@ -860,6 +860,8 @@ 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

@ -1435,7 +1435,6 @@ var gBrowserInit = {
}
// Final window teardown, do this last.
window.XULBrowserWindow.destroy();
window.XULBrowserWindow = null;
window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
@ -3690,14 +3689,6 @@ var XULBrowserWindow = {
this.onSecurityChange(null, null, securityUI.state);
},
destroy: function () {
// XXXjag to avoid leaks :-/, see bug 60729
delete this.throbberElement;
delete this.stopCommand;
delete this.reloadCommand;
delete this.statusText;
},
setJSStatus: function () {
// unsupported
},

View File

@ -2,12 +2,19 @@
* 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.
@ -55,7 +62,37 @@ let EventListener = {
}
}
};
EventListener.init();
/**
* 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;
}
}
};
let ProgressListener = {
init: function() {
@ -74,4 +111,7 @@ let ProgressListener = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference])
};
EventListener.init();
MessageListener.init();
ProgressListener.init();

View File

@ -0,0 +1,71 @@
/* 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

@ -0,0 +1,82 @@
/* 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, "SessionStore",
"resource:///modules/sessionstore/SessionStore.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
"resource:///modules/sessionstore/PrivacyLevel.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 checkPrivacyLevel().
if (SessionStore.checkPrivacyLevel(cookie.secure, isPinned)) {
// to PrivacyLevel.canSave().
if (PrivacyLevel.canSave({isHttps: cookie.secure, isPinned: isPinned})) {
cookies.push(cookie);
}
}
@ -209,7 +209,7 @@ let SessionCookiesInternal = {
// case testing scheme will be sufficient.
if (/https?/.test(scheme) && !hosts[host] &&
(!checkPrivacy ||
SessionStore.checkPrivacyLevel(scheme == "https", isPinned))) {
PrivacyLevel.canSave({isHttps: scheme == "https", isPinned: 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

@ -0,0 +1,272 @@
/* 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._saveState(), delay);
this._timeoutID = setTimeout(() => this._saveStateAsync(), delay);
},
/**
@ -186,8 +186,7 @@ let SessionSaverInternal = {
* update the corresponding caches.
*/
_saveState: function (forceUpdateAllWindows = false) {
// Cancel any pending timeouts or just clear
// the timeout if this is why we've been called.
// Cancel any pending timeouts.
this.cancel();
stopWatchStart("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
@ -246,6 +245,33 @@ 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,12 +7,13 @@
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, "SessionStore",
"resource:///modules/sessionstore/SessionStore.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
"resource:///modules/sessionstore/PrivacyLevel.jsm");
this.SessionStorage = {
/**
@ -51,16 +52,17 @@ let DomStorage = {
read: function DomStorage_read(aDocShell, aFullData) {
let data = {};
let isPinned = aDocShell.isAppTab;
let shistory = aDocShell.sessionHistory;
let webNavigation = aDocShell.QueryInterface(Ci.nsIWebNavigation);
let shistory = webNavigation.sessionHistory;
for (let i = 0; i < shistory.count; i++) {
for (let i = 0; shistory && 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 || SessionStore.checkPrivacyLevel(isHTTPS, isPinned)) {
let isHttps = principal.URI && principal.URI.schemeIs("https");
if (aFullData || PrivacyLevel.canSave({isHttps: isHttps, isPinned: isPinned})) {
let origin = principal.jarPrefix + principal.origin;
// Don't read a host twice.
@ -90,8 +92,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

@ -34,6 +34,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/osfile/_PromiseWorker.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
"resource://gre/modules/TelemetryStopwatch.jsm");
@ -148,6 +149,18 @@ let SessionFileInternal = {
*/
backupPath: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak"),
/**
* The promise returned by the latest call to |write|.
* We use it to ensure that AsyncShutdown.profileBeforeChange cannot
* interrupt a call to |write|.
*/
_latestWrite: null,
/**
* |true| once we have decided to stop receiving write instructiosn
*/
_isClosed: false,
/**
* Utility function to safely read a file synchronously.
* @param aPath
@ -210,8 +223,11 @@ let SessionFileInternal = {
},
write: function (aData) {
if (this._isClosed) {
return Promise.reject(new Error("_SessionFile is closed"));
}
let refObj = {};
return TaskUtils.spawn(function task() {
return this._latestWrite = TaskUtils.spawn(function task() {
TelemetryStopwatch.start("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
try {
@ -227,6 +243,15 @@ let SessionFileInternal = {
Cu.reportError("Could not write session state file " + this.path
+ ": " + ex);
}
// At this stage, we are done writing. If shutdown has started,
// we will want to stop receiving write instructions.
if (Services.startup.shuttingDown) {
this._isClosed = true;
}
// In rare cases, we may already have other writes pending,
// which we need to flush before shutdown proceeds. AsyncShutdown
// uses _latestWrite to determine what needs to be flushed during
// shutdown.
}.bind(this));
},
@ -277,3 +302,11 @@ let SessionWorker = (function () {
}
};
})();
// Ensure that we can write sessionstore.js cleanly before the profile
// becomes unaccessible.
AsyncShutdown.profileBeforeChange.addBlocker(
"SessionFile: Finish writing the latest sessionstore.js",
function() {
return _SessionFile._latestWrite;
});

View File

@ -14,7 +14,10 @@ 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

@ -20,7 +20,7 @@ MOCHITEST_BROWSER_FILES = \
browser_input_sample.html \
browser_pageshow.js \
browser_sessionStorage.js \
browser_upgrade_backup.js \
browser_upgrade_backup.js \
browser_windowRestore_perwindowpb.js \
browser_248970_b_perwindowpb.js \
browser_248970_b_sample.html \
@ -135,6 +135,8 @@ MOCHITEST_BROWSER_FILES = \
browser_739805.js \
browser_819510_perwindowpb.js \
browser_833286_atomic_backup.js \
browser_916390_form_data_loss.js \
browser_916390_sample.html \
$(filter disabled-for-intermittent-failures--bug-766044, browser_459906_empty.html) \
$(filter disabled-for-intermittent-failures--bug-766044, browser_459906_sample.html) \
$(filter disabled-for-intermittent-failures--bug-765389, browser_461743_sample.html) \

View File

@ -2,13 +2,15 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
waitForExplicitFinish();
TestRunner.run();
}
let assertNumberOfTabs = function (num, msg) {
function runTests() {
function assertNumberOfTabs(num, msg) {
is(gBrowser.tabs.length, num, msg);
}
let assertNumberOfPinnedTabs = function (num, msg) {
function assertNumberOfPinnedTabs(num, msg) {
is(gBrowser._numPinnedTabs, num, msg);
}
@ -27,13 +29,13 @@ function test() {
assertNumberOfPinnedTabs(2, "both tabs are now pinned");
// run the test
waitForBrowserState(
yield waitForBrowserState(
{ windows: [{ tabs: [{ url: "about:blank" }] }] },
function () {
assertNumberOfTabs(1, "one tab left after setBrowserState()");
assertNumberOfPinnedTabs(0, "there are no pinned tabs");
is(gBrowser.tabs[0].linkedBrowser, linkedBrowser, "first tab's browser got re-used");
finish();
next();
}
);
}

View File

@ -58,7 +58,6 @@ 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);

View File

@ -0,0 +1,65 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const URL = "http://mochi.test:8888/browser/" +
"browser/components/sessionstore/test/browser_916390_sample.html";
function test() {
TestRunner.run();
}
function runTests() {
// Create a tab with some form fields.
let tab = gBrowser.selectedTab = gBrowser.addTab(URL);
let browser = gBrowser.selectedBrowser;
yield waitForLoad(browser);
// Modify the text input field's state.
browser.contentDocument.getElementById("txt").focus();
EventUtils.synthesizeKey("m", {});
yield waitForInput();
// Check that we'll save the form data state correctly.
let state = JSON.parse(ss.getBrowserState());
let {formdata} = state.windows[0].tabs[1].entries[0];
is(formdata.id.txt, "m", "txt's value is correct");
// Change the number of session history entries and modify
// DOMSessionStorage data to invalidate the TabStateCache.
browser.loadURI(URL + "#");
browser.contentWindow.sessionStorage.foo = "bar";
yield waitForStorageChange();
// Check that we'll save the form data state correctly.
let state = JSON.parse(ss.getBrowserState());
let {formdata} = state.windows[0].tabs[1].entries[1];
is(formdata.id.txt, "m", "txt's value is correct");
// Clean up.
gBrowser.removeTab(tab);
}
function waitForLoad(aElement) {
aElement.addEventListener("load", function onLoad() {
aElement.removeEventListener("load", onLoad, true);
executeSoon(next);
}, true);
}
function waitForInput() {
let mm = gBrowser.selectedBrowser.messageManager;
mm.addMessageListener("SessionStore:input", function onInput() {
mm.removeMessageListener("SessionStore:input", onInput);
executeSoon(next);
});
}
function waitForStorageChange() {
let mm = gBrowser.selectedBrowser.messageManager;
mm.addMessageListener("SessionStore:MozStorageChanged", function onChanged() {
mm.removeMessageListener("SessionStore:MozStorageChanged", onChanged);
executeSoon(next);
});
}

View File

@ -0,0 +1,10 @@
<!DOCTYPE HTML>
<html dir="ltr" xml:lang="en-US" lang="en-US">
<head>
<meta charset="utf-8">
<title>bug 916390</title>
</head>
<body>
<input id="txt" />
</body>
</html>

View File

@ -68,7 +68,11 @@ STUB_HOOK = $(NSINSTALL) -D "$(_ABS_DIST)/$(PKG_INST_PATH)"; \
$(NULL)
endif
ifeq ($(MOZ_WIDGET_TOOLKIT) $(DIST_SUBDIR),windows metro)
SEARCHPLUGINS_NAMES = $(shell cat $(call MERGE_FILE,/searchplugins/metrolist.txt))
else
SEARCHPLUGINS_NAMES = $(shell cat $(call MERGE_FILE,/searchplugins/list.txt))
endif
SEARCHPLUGINS_PATH := $(FINAL_TARGET)/searchplugins
SEARCHPLUGINS := $(addsuffix .xml,$(SEARCHPLUGINS_NAMES))
PP_TARGETS += SEARCHPLUGINS

View File

@ -0,0 +1,18 @@
<!-- 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/. -->
<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
<ShortName>Google</ShortName>
<Description>Google Search</Description>
<InputEncoding>UTF-8</InputEncoding>
<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAB9AQAAJgAAACAgAAAAAAAA8gIAAKMBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAFESURBVDjLpZNJSwNBEIXnt4lE4kHxovgT9BDwJHqPy0HEEOJBiAuCRg+KUdC4QS4KrpC4gCBGE3NQ48JsnZ6eZ3UOM6gjaePhQU93v6+qq2q0pqgeJj2S8EdJT1hr0OxBtKCD5iEd8QxDYpvhvOBAuMDKURX9C9aPu4GA1GEVkzvMg10UBfYveWAWgYAP00V01fa+R9M2bA51wJvhIn3qR+ybt3D3JNQBE5sMjCIOLFpoHzOwdsLRO22qA6R6kiZiWwxUvy/PUQZIhYZ1vFM9cvcOOsYNdcBgysISdSJBnZjJMlR0Fw8vAp0xoz5gao/h+NZBy4i/10XGwrPA+hmvDyhVRG2Avu/LwcrkFADZa16L1h330w1RNgc3DiJzCpPYRm1bpveXX11clQR28xwblHpk1vq1iP/5mcoS0CoXDZiL0vsJ+dzfl+3T/VYAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAK5SURBVFjDxVfrSxRRFJ9/Jta/oyWjF5XQm6D6EkHRgygIIgjUTcueVgqVWSRRkppEUQYWWB8ye1iGWilWlo/Ude489s7M6Zw7D9dlt53dmd29cFiWvXvO77x+51xpaaUsoSxBaUWZQ4ECy5xji2xKZDyCMlMEw6lCNiOSgwZKJK1SkcKeSealfP64t0mBjl4Ow39MkDUL0p2RSROOtqhZdeUEYM1pBl39XCg/fEeFtWcY7G9W4csvUxjlBkCsQ4Nt9QyWVfvT6RsAKXw3aoDGATZeYIt+W1kjw7cJG0RctWDTRebbKd8A6h5pwsDb70ba3w/eUr3wt/cmwgfw6Yft4TNMQaY7o1P2ncm4FT4ANQH/jQBJ2xv7kqIXEADDql8eS3+n8bku7oxNm+EDIM/dU92upb3T/NJGeaNbDx/AsbsLRUY5Xn92caWXY5d8RV6gWllxSg4fAEnTC90DQW13BLlgXR2D3dcUeDVkwOthA1bXspxILWcm3HdThcfvufB26LcJpkOEAz9NKI/lzqpSEC7feol5EWnpSeSlIxCALUkApmULdjUqxQVAQnl3D/X/yQda4QBEq2TYc12By091MQ17Bg3R88nHKlQbVmHvj89awNBLYrwT9zXY2aBAxTkGFdiSxP/Jp6FLDw+AS7GfsdJTJ2EqSO5khD43nGfBARy/ZxOQgZHe7GPM1jzUvChUtmnBAXQPcKGMJp3fdFGq6NByEhiAO4b/YptFfQJwNyQ/bZkVQGcf90Ja25ndIyrKBOa/f8wIpwi3X1G8UcxNu7ozUS7tiH0jBswwS3RIaF1w6LYKU/ML2+8sGnjygQswtKrVIy/Qd9qQP6LnO64q4fPAKpxyZIymHo1jWk6p1ag2BsdNwQMHcC+M5kHFJX+YlPxpVlbCx2mZ5DzPI04k4kUwHHdskU3pH76iftG8yWlkAAAAAElFTkSuQmCC</Image>
<Url type="application/x-suggestions+json" method="GET" template="https://www.google.com/complete/search?client=metrofirefox&amp;q={searchTerms}"/>
<Url type="text/html" method="GET" template="https://www.google.com/search">
<Param name="q" value="{searchTerms}"/>
<Param name="ie" value="utf-8"/>
<Param name="oe" value="utf-8"/>
<Param name="client" value="metrofirefox"/>
</Url>
<SearchForm>https://www.google.com/</SearchForm>
</SearchPlugin>

View File

@ -0,0 +1,4 @@
bing
googlemetrofx
wikipedia
yahoo

View File

@ -184,6 +184,7 @@ var ContextUI = {
// Dismiss the navbar if visible.
dismissNavbar: function dismissNavbar() {
if (!BrowserUI.isStartTabVisible) {
Elements.autocomplete.closePopup();
Elements.navbar.dismiss();
ContentAreaObserver.updateContentArea();
}

View File

@ -176,7 +176,7 @@ let Util = {
*/
getDownloadSize: function dv__getDownloadSize (aSize) {
let [size, units] = DownloadUtils.convertByteUnits(aSize);
if (size > 0)
if (aSize > 0)
return size + units;
else
return Strings.browser.GetStringFromName("downloadsUnknownSize");

View File

@ -44,7 +44,8 @@ HelperAppLauncherDialog.prototype = {
_getDownloadSize: function dv__getDownloadSize (aSize) {
let displaySize = DownloadUtils.convertByteUnits(aSize);
if (!isNaN(displaySize[0]) && displaySize[0] > 0) // [0] is size, [1] is units
// displaySize[0] is formatted size, displaySize[1] is units
if (aSize > 0)
return displaySize.join("");
else {
let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");

View File

@ -713,7 +713,7 @@ IFACEMETHODIMP CExecuteCommandVerb::Execute()
}
CComPtr<IApplicationActivationManager> activateMgr;
if (!PrepareActivationManager(activateMgr)) {
if (FAILED(PrepareActivationManager(activateMgr))) {
LaunchDesktopBrowser();
return S_OK;
}

View File

@ -182,16 +182,7 @@ abstract public class BrowserApp extends GeckoApp
// fall through
case SELECTED:
if (Tabs.getInstance().isSelectedTab(tab)) {
if (isAboutHome(tab)) {
showHomePager(tab.getAboutHomePage());
if (isDynamicToolbarEnabled()) {
// Show the toolbar.
mLayerView.getLayerMarginsAnimator().showMargins(false);
}
} else {
hideHomePager();
}
updateHomePagerForTab(tab);
if (mSiteIdentityPopup != null)
mSiteIdentityPopup.dismiss();
@ -1508,6 +1499,27 @@ abstract public class BrowserApp extends GeckoApp
}
}
/**
* Shows or hides the home pager for the given tab.
*/
private void updateHomePagerForTab(Tab tab) {
// Don't change the visibility of the home pager if we're in editing mode.
if (mBrowserToolbar.isEditing()) {
return;
}
if (isAboutHome(tab)) {
showHomePager(tab.getAboutHomePage());
if (isDynamicToolbarEnabled()) {
// Show the toolbar.
mLayerView.getLayerMarginsAnimator().showMargins(false);
}
} else {
hideHomePager();
}
}
private void showHomePager(HomePager.Page page) {
showHomePagerWithAnimator(page, null);
}
@ -2125,7 +2137,18 @@ abstract public class BrowserApp extends GeckoApp
GeckoAppShell.sendEventToGecko(GeckoEvent.createURILoadEvent(uri));
}
if (!Intent.ACTION_MAIN.equals(action) || !mInitialized) {
if (!mInitialized) {
return;
}
// Dismiss editing mode if the user is loading a URL from an external app.
if (Intent.ACTION_VIEW.equals(action)) {
dismissEditingMode();
return;
}
// Only solicit feedback when the app has been launched from the icon shortcut.
if (!Intent.ACTION_MAIN.equals(action)) {
return;
}

View File

@ -49,8 +49,8 @@ abstract class BaseTest extends ActivityInstrumentationTestCase2<Activity> {
private static final int VERIFY_URL_TIMEOUT = 2000;
private static final int MAX_LIST_ATTEMPTS = 3;
private static final int MAX_WAIT_ENABLED_TEXT_MS = 10000;
private static final int MAX_WAIT_HOME_PAGER_HIDDEN_MS = 10000;
public static final int MAX_WAIT_MS = 3000;
private static final int MAX_WAIT_HOME_PAGER_HIDDEN_MS = 15000;
public static final int MAX_WAIT_MS = 4500;
// IDs for UI views
private static final String BROWSER_TOOLBAR_ID = "browser_toolbar";

View File

@ -4,11 +4,12 @@ package @ANDROID_PACKAGE_NAME@.tests;
import @ANDROID_PACKAGE_NAME@.*;
abstract class PixelTest extends BaseTest {
private static final long PAINT_CLEAR_DELAY = 3000; // milliseconds
private static final long PAINT_CLEAR_DELAY = 10000; // milliseconds
protected final PaintedSurface loadAndGetPainted(String url) {
Actions.RepeatedEventExpecter paintExpecter = mActions.expectPaint();
loadUrl(url);
verifyHomePagerHidden();
paintExpecter.blockUntilClear(PAINT_CLEAR_DELAY);
paintExpecter.unregisterListener();
PaintedSurface p = mDriver.getPaintedSurface();

View File

@ -12,7 +12,6 @@ public class testCheck2 extends PixelTest {
blockForGeckoReady();
loadAndPaint(url);
verifyHomePagerHidden();
mDriver.setupScrollHandling();

View File

@ -6,6 +6,7 @@ from __future__ import unicode_literals
from configobj import ConfigObj
import re
import os
BUGZILLA_FINGERPRINT = '45:77:35:fd:6f:2c:1c:c2:90:4b:f7:b4:4d:60:c6:97:c5:5c:47:27'
@ -15,9 +16,22 @@ HG_FINGERPRINT = '10:78:e8:57:2d:95:de:7c:de:90:bd:22:e1:38:17:67:c5:a7:9c:14'
class MercurialConfig(object):
"""Interface for manipulating a Mercurial config file."""
def __init__(self, infile=None):
def __init__(self, infiles=None):
"""Create a new instance, optionally from an existing hgrc file."""
if infiles:
# If multiple files were specified, figure out which file we're using:
if len(infiles) > 1:
picky_infiles = filter(os.path.isfile, infiles)
if picky_infiles:
picky_infiles = [(os.path.getsize(path), path) for path in picky_infiles]
infiles = [max(picky_infiles)[1]]
infile = infiles[0]
self.config_path = infile
else:
infile = None
# write_empty_values is necessary to prevent built-in extensions (which
# have no value) from being dropped on write.
# list_values aren't needed by Mercurial and disabling them prevents

View File

@ -90,7 +90,7 @@ class MercurialSetupWizard(object):
self.state_dir = state_dir
self.ext_dir = os.path.join(state_dir, 'mercurial', 'extensions')
def run(self, config_path):
def run(self, config_paths):
try:
os.makedirs(self.ext_dir)
except OSError as e:
@ -105,7 +105,7 @@ class MercurialSetupWizard(object):
'up to date.')
return 1
c = MercurialConfig(config_path)
c = MercurialConfig(config_paths)
print(INITIAL_MESSAGE)
raw_input()
@ -225,6 +225,7 @@ class MercurialSetupWizard(object):
new_lines = [line.rstrip() for line in b.getvalue().splitlines()]
old_lines = []
config_path = c.config_path
if os.path.exists(config_path):
with open(config_path, 'rt') as fh:
old_lines = [line.rstrip() for line in fh.readlines()]

View File

@ -26,7 +26,10 @@ class VersionControlCommands(object):
from hgsetup.wizard import MercurialSetupWizard
wizard = MercurialSetupWizard(self._context.state_dir)
result = wizard.run(os.path.expanduser('~/.hgrc'))
config_paths = ['~/.hgrc']
if sys.platform in ('win32', 'cygwin'):
config_paths.insert(0, '~/mercurial.ini')
result = wizard.run(map(os.path.expanduser, config_paths))
# Touch a file so we can periodically prompt to update extensions.
state_path = os.path.join(self._context.state_dir,