Merge fx-team to m-c.

This commit is contained in:
Ryan VanderMeulen 2013-07-12 15:50:50 -04:00
commit 951d8dee8b
22 changed files with 516 additions and 430 deletions

View File

@ -51,6 +51,7 @@ endif
# The following tests are disabled because they are unreliable:
# browser_bug423833.js is bug 428712
# browser_sanitize-download-history.js is bug 432425
# browser_aboutHome.js is bug 890409
#
# browser_sanitizeDialog_treeView.js is disabled until the tree view is added
# back to the clear recent history dialog (sanitize.xul), if it ever is (bug
@ -71,7 +72,6 @@ MOCHITEST_BROWSER_FILES = \
blockPluginVulnerableNoUpdate.xml \
blockPluginVulnerableUpdatable.xml \
browser_aboutHealthReport.js \
browser_aboutHome.js \
browser_aboutSyncProgress.js \
browser_addKeywordSearch.js \
browser_addon_bar_aomlistener.js \

View File

@ -89,27 +89,8 @@ let gTests = [
},
{
desc: "Check that performing a search fires a search event.",
setup: function () { },
run: function () {
let deferred = Promise.defer();
let doc = gBrowser.contentDocument;
doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) {
is(e.detail, doc.documentElement.getAttribute("searchEngineName"), "Detail is search engine name");
gBrowser.stop();
deferred.resolve();
}, true, true);
doc.getElementById("searchText").value = "it works";
doc.getElementById("searchSubmit").click();
return deferred.promise;
}
},
{
desc: "Check that performing a search records to Firefox Health Report.",
desc: "Check that performing a search fires a search event and records to " +
"Firefox Health Report.",
setup: function () { },
run: function () {
try {
@ -120,46 +101,33 @@ let gTests = [
return Promise.resolve();
}
let numSearchesBefore = 0;
let deferred = Promise.defer();
let doc = gBrowser.contentDocument;
// We rely on the listener in browser.js being installed and fired before
// this one. If this ever changes, we should add an executeSoon() or similar.
doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) {
executeSoon(gBrowser.stop.bind(gBrowser));
let reporter = Components.classes["@mozilla.org/datareporting/service;1"]
.getService()
.wrappedJSObject
.healthReporter;
ok(reporter, "Health Reporter instance available.");
let engineName = doc.documentElement.getAttribute("searchEngineName");
is(e.detail, engineName, "Detail is search engine name");
reporter.onInit().then(function onInit() {
let provider = reporter.getProvider("org.mozilla.searches");
ok(provider, "Searches provider is available.");
let engineName = doc.documentElement.getAttribute("searchEngineName");
let id = Services.search.getEngineByName(engineName).identifier;
let m = provider.getMeasurement("counts", 2);
m.getValues().then(function onValues(data) {
let now = new Date();
ok(data.days.hasDay(now), "Have data for today.");
let day = data.days.getDay(now);
let field = id + ".abouthome";
ok(day.has(field), "Have data for about home on this engine.");
// Note the search from the previous test.
is(day.get(field), 2, "Have searches recorded.");
deferred.resolve();
});
gBrowser.stop();
getNumberOfSearches().then(num => {
is(num, numSearchesBefore + 1, "One more search recorded.");
deferred.resolve();
});
}, true, true);
doc.getElementById("searchText").value = "a search";
doc.getElementById("searchSubmit").click();
// Get the current number of recorded searches.
getNumberOfSearches().then(num => {
numSearchesBefore = num;
info("Perform a search.");
doc.getElementById("searchText").value = "a search";
doc.getElementById("searchSubmit").click();
});
return deferred.promise;
}
},
@ -422,3 +390,54 @@ function promiseBrowserAttributes(aTab)
return deferred.promise;
}
/**
* Retrieves the number of about:home searches recorded for the current day.
*
* @return {Promise} Returns a promise resolving to the number of searches.
*/
function getNumberOfSearches() {
let reporter = Components.classes["@mozilla.org/datareporting/service;1"]
.getService()
.wrappedJSObject
.healthReporter;
ok(reporter, "Health Reporter instance available.");
return reporter.onInit().then(function onInit() {
let provider = reporter.getProvider("org.mozilla.searches");
ok(provider, "Searches provider is available.");
let m = provider.getMeasurement("counts", 2);
return m.getValues().then(data => {
let now = new Date();
let yday = new Date(now);
yday.setDate(yday.getDate() - 1);
// Add the number of searches recorded yesterday to the number of searches
// recorded today. This makes the test not fail intermittently when it is
// run at midnight and we accidentally compare the number of searches from
// different days. Tests are always run with an empty profile so there
// are no searches from yesterday, normally. Should the test happen to run
// past midnight we make sure to count them in as well.
return getNumberOfSearchesByDate(data, now) +
getNumberOfSearchesByDate(data, yday);
});
});
}
function getNumberOfSearchesByDate(aData, aDate) {
if (aData.days.hasDay(aDate)) {
let doc = gBrowser.contentDocument;
let engineName = doc.documentElement.getAttribute("searchEngineName");
let id = Services.search.getEngineByName(engineName).identifier;
let day = aData.days.getDay(aDate);
let field = id + ".abouthome";
if (day.has(field)) {
return day.get(field) || 0;
}
}
return 0; // No records found.
}

View File

@ -460,7 +460,11 @@ let SessionStoreInternal = {
// A Lazy getter for the sessionstore.js backup promise.
XPCOMUtils.defineLazyGetter(this, "_backupSessionFileOnce", function () {
return _SessionFile.createBackupCopy();
// We're creating a backup of sessionstore.js by moving it to .bak
// because that's a lot faster than creating a copy. sessionstore.js
// would be overwritten shortly afterwards anyway so we can save time
// and just move instead of copy.
return _SessionFile.moveToBackupPath();
});
// at this point, we've as good as resumed the session, so we can
@ -504,12 +508,12 @@ let SessionStoreInternal = {
return Task.spawn(function task() {
try {
// Perform background backup
yield _SessionFile.createUpgradeBackupCopy("-" + buildID);
yield _SessionFile.createBackupCopy("-" + buildID);
this._prefBranch.setCharPref(PREF_UPGRADE, buildID);
// In case of success, remove previous backup.
yield _SessionFile.removeUpgradeBackup("-" + latestBackup);
yield _SessionFile.removeBackupCopy("-" + latestBackup);
} catch (ex) {
debug("Could not perform upgrade backup " + ex);
debug(ex.stack);
@ -772,12 +776,12 @@ let SessionStoreInternal = {
this._restoreCount = this._initialState.windows ? this._initialState.windows.length : 0;
this.restoreWindow(aWindow, this._initialState,
this._isCmdLineEmpty(aWindow, this._initialState));
// _loadState changed from "stopped" to "running"
// force a save operation so that crashes happening during startup are correctly counted
this._initialState.session.state = STATE_RUNNING_STR;
this._saveStateObject(this._initialState);
this._initialState = null;
// _loadState changed from "stopped" to "running". Save the session's
// load state immediately so that crashes happening during startup
// are correctly counted.
_SessionFile.writeLoadStateOnceAfterStartup(STATE_RUNNING_STR);
}
}
else {

View File

@ -0,0 +1,221 @@
/* 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/. */
/**
* A worker dedicated to handle I/O for Session Store.
*/
"use strict";
importScripts("resource://gre/modules/osfile.jsm");
let File = OS.File;
let Encoder = new TextEncoder();
let Decoder = new TextDecoder();
/**
* Communications with the controller.
*
* Accepts messages:
* {fun:function_name, args:array_of_arguments_or_null, id: custom_id}
*
* Sends messages:
* {ok: result, id: custom_id} / {fail: serialized_form_of_OS.File.Error,
* id: custom_id}
*/
self.onmessage = function (msg) {
let data = msg.data;
if (!(data.fun in Agent)) {
throw new Error("Cannot find method " + data.fun);
}
let result;
let id = data.id;
try {
result = Agent[data.fun].apply(Agent, data.args);
} catch (ex if ex instanceof OS.File.Error) {
// Instances of OS.File.Error know how to serialize themselves
// (deserialization ensures that we end up with OS-specific
// instances of |OS.File.Error|)
self.postMessage({fail: OS.File.Error.toMsg(ex), id: id});
return;
}
// Other exceptions do not, and should be propagated through DOM's
// built-in mechanism for uncaught errors, although this mechanism
// may lose interesting information.
self.postMessage({ok: result, id: id});
};
let Agent = {
// The initial session string as read from disk.
initialState: null,
// Boolean that tells whether we already wrote
// the loadState to disk once after startup.
hasWrittenLoadStateOnce: false,
// The path to sessionstore.js
path: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"),
// The path to sessionstore.bak
backupPath: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak"),
/**
* This method is only intended to be called by _SessionFile.syncRead() and
* can be removed when we're not supporting synchronous SessionStore
* initialization anymore. When sessionstore.js is read from disk
* synchronously the state string must be supplied to the worker manually by
* calling this method.
*/
setInitialState: function (aState) {
// _SessionFile.syncRead() should not be called after startup has finished.
// Thus we also don't support any setInitialState() calls after we already
// wrote the loadState to disk.
if (this.hasWrittenLoadStateOnce) {
throw new Error("writeLoadStateOnceAfterStartup() must only be called once.");
}
// Initial state might have been filled by read() already but yet we might
// be called by _SessionFile.syncRead() before SessionStore.jsm had a chance
// to call writeLoadStateOnceAfterStartup(). It's safe to ignore
// setInitialState() calls if this happens.
if (!this.initialState) {
this.initialState = aState;
}
},
/**
* Read the session from disk.
* In case sessionstore.js does not exist, attempt to read sessionstore.bak.
*/
read: function () {
for (let path of [this.path, this.backupPath]) {
try {
return this.initialState = Decoder.decode(File.read(path));
} catch (ex if isNoSuchFileEx(ex)) {
// Ignore exceptions about non-existent files.
}
}
// No sessionstore data files found. Return an empty string.
return "";
},
/**
* Write the session to disk.
*/
write: function (stateString) {
let bytes = Encoder.encode(stateString);
return File.writeAtomic(this.path, bytes, {tmpPath: this.path + ".tmp"});
},
/**
* Writes the session state to disk again but changes session.state to
* 'running' before doing so. This is intended to be called only once, shortly
* after startup so that we detect crashes on startup correctly.
*/
writeLoadStateOnceAfterStartup: function (loadState) {
if (this.hasWrittenLoadStateOnce) {
throw new Error("writeLoadStateOnceAfterStartup() must only be called once.");
}
if (!this.initialState) {
throw new Error("writeLoadStateOnceAfterStartup() must not be called " +
"without a valid session state or before it has been " +
"read from disk.");
}
// Make sure we can't call this function twice.
this.hasWrittenLoadStateOnce = true;
let state;
try {
state = JSON.parse(this.initialState);
} finally {
this.initialState = null;
}
state.session = state.session || {};
state.session.state = loadState;
return this.write(JSON.stringify(state));
},
/**
* Moves sessionstore.js to sessionstore.bak.
*/
moveToBackupPath: function () {
try {
return File.move(this.path, this.backupPath);
} catch (ex if isNoSuchFileEx(ex)) {
// Ignore exceptions about non-existent files.
return true;
}
},
/**
* Creates a copy of sessionstore.js.
*/
createBackupCopy: function (ext) {
try {
return File.copy(this.path, this.backupPath + ext);
} catch (ex if isNoSuchFileEx(ex)) {
// Ignore exceptions about non-existent files.
return true;
}
},
/**
* Removes a backup copy.
*/
removeBackupCopy: function (ext) {
try {
return File.remove(this.backupPath + ext);
} catch (ex if isNoSuchFileEx(ex)) {
// Ignore exceptions about non-existent files.
return true;
}
},
/**
* Wipes all files holding session data from disk.
*/
wipe: function () {
let exn;
// Erase session state file
try {
File.remove(this.path);
} catch (ex if isNoSuchFileEx(ex)) {
// Ignore exceptions about non-existent files.
} catch (ex) {
// Don't stop immediately.
exn = ex;
}
// Erase any backup, any file named "sessionstore.bak[-buildID]".
let iter = new File.DirectoryIterator(OS.Constants.Path.profileDir);
for (let entry in iter) {
if (!entry.isDir && entry.path.startsWith(this.backupPath)) {
try {
File.remove(entry.path);
} catch (ex) {
// Don't stop immediately.
exn = exn || ex;
}
}
}
if (exn) {
throw exn;
}
return true;
}
};
function isNoSuchFileEx(aReason) {
return aReason instanceof OS.File.Error && aReason.becauseNoSuchFile;
}

View File

@ -32,6 +32,7 @@ const Ci = Components.interfaces;
Cu.import("resource://gre/modules/Services.jsm");
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/commonjs/sdk/core/promise.js");
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
@ -44,66 +45,62 @@ XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
"@mozilla.org/base/telemetry;1", "nsITelemetry");
// An encoder to UTF-8.
XPCOMUtils.defineLazyGetter(this, "gEncoder", function () {
return new TextEncoder();
});
// A decoder.
XPCOMUtils.defineLazyGetter(this, "gDecoder", function () {
return new TextDecoder();
});
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
"resource://gre/modules/Deprecated.jsm");
this._SessionFile = {
/**
* A promise fulfilled once initialization (either synchronous or
* asynchronous) is complete.
*/
promiseInitialized: function SessionFile_initialized() {
return SessionFileInternal.promiseInitialized;
},
/**
* Read the contents of the session file, asynchronously.
*/
read: function SessionFile_read() {
read: function () {
return SessionFileInternal.read();
},
/**
* Read the contents of the session file, synchronously.
*/
syncRead: function SessionFile_syncRead() {
syncRead: function () {
Deprecated.warning(
"syncRead is deprecated and will be removed in a future version",
"https://bugzilla.mozilla.org/show_bug.cgi?id=532150")
return SessionFileInternal.syncRead();
},
/**
* Write the contents of the session file, asynchronously.
*/
write: function SessionFile_write(aData) {
write: function (aData) {
return SessionFileInternal.write(aData);
},
/**
* Writes the initial state to disk again only to change the session's load
* state. This must only be called once, it will throw an error otherwise.
*/
writeLoadStateOnceAfterStartup: function (aLoadState) {
return SessionFileInternal.writeLoadStateOnceAfterStartup(aLoadState);
},
/**
* Create a backup copy, asynchronously.
*/
createBackupCopy: function SessionFile_createBackupCopy() {
return SessionFileInternal.createBackupCopy();
moveToBackupPath: function () {
return SessionFileInternal.moveToBackupPath();
},
/**
* Create a backup copy, asynchronously.
* This is designed to perform backup on upgrade.
*/
createUpgradeBackupCopy: function(ext) {
return SessionFileInternal.createUpgradeBackupCopy(ext);
createBackupCopy: function (ext) {
return SessionFileInternal.createBackupCopy(ext);
},
/**
* Remove a backup copy, asynchronously.
* This is designed to clean up a backup on upgrade.
*/
removeUpgradeBackup: function(ext) {
return SessionFileInternal.removeUpgradeBackup(ext);
removeBackupCopy: function (ext) {
return SessionFileInternal.removeBackupCopy(ext);
},
/**
* Wipe the contents of the session file, asynchronously.
*/
wipe: function SessionFile_wipe() {
wipe: function () {
return SessionFileInternal.wipe();
}
};
@ -147,11 +144,6 @@ const TaskUtils = {
};
let SessionFileInternal = {
/**
* A promise fulfilled once initialization is complete
*/
promiseInitialized: Promise.defer(),
/**
* The path to sessionstore.js
*/
@ -168,7 +160,7 @@ let SessionFileInternal = {
* A path to read the file from.
* @returns string if successful, undefined otherwise.
*/
readAuxSync: function ssfi_readAuxSync(aPath) {
readAuxSync: function (aPath) {
let text;
try {
let file = new FileUtils.File(aPath);
@ -197,7 +189,7 @@ let SessionFileInternal = {
* happened between backup and write), attempt to read the sessionstore.bak
* instead.
*/
syncRead: function ssfi_syncRead() {
syncRead: function () {
// Start measuring the duration of the synchronous read.
TelemetryStopwatch.start("FX_SESSION_RESTORE_SYNC_READ_FILE_MS");
// First read the sessionstore.js.
@ -208,83 +200,26 @@ let SessionFileInternal = {
}
// Finish the telemetry probe and return an empty string.
TelemetryStopwatch.finish("FX_SESSION_RESTORE_SYNC_READ_FILE_MS");
return text || "";
text = text || "";
// The worker needs to know the initial state read from
// disk so that writeLoadStateOnceAfterStartup() works.
SessionWorker.post("setInitialState", [text]);
return text;
},
/**
* Utility function to safely read a file asynchronously.
* @param aPath
* A path to read the file from.
* @param aReadOptions
* Read operation options.
* |outExecutionDuration| option will be reused and can be
* incrementally updated by the worker process.
* @returns string if successful, undefined otherwise.
*/
readAux: function ssfi_readAux(aPath, aReadOptions) {
let self = this;
return TaskUtils.spawn(function () {
let text;
try {
let bytes = yield OS.File.read(aPath, undefined, aReadOptions);
text = gDecoder.decode(bytes);
// If the file is read successfully, add a telemetry probe based on
// the updated duration value of the |outExecutionDuration| option.
let histogram = Telemetry.getHistogramById(
"FX_SESSION_RESTORE_READ_FILE_MS");
histogram.add(aReadOptions.outExecutionDuration);
} catch (ex if self._isNoSuchFile(ex)) {
// Ignore exceptions about non-existent files.
} catch (ex) {
Cu.reportError(ex);
}
throw new Task.Result(text);
});
read: function () {
return SessionWorker.post("read").then(msg => msg.ok);
},
/**
* Read the sessionstore file asynchronously.
*
* In case sessionstore.js file does not exist or is corrupted (something
* happened between backup and write), attempt to read the sessionstore.bak
* instead.
*/
read: function ssfi_read() {
let self = this;
return TaskUtils.spawn(function task() {
// Specify |outExecutionDuration| option to hold the combined duration of
// the asynchronous reads off the main thread (of both sessionstore.js and
// sessionstore.bak, if necessary). If sessionstore.js does not exist or
// is corrupted, |outExecutionDuration| will register the time it took to
// attempt to read the file. It will then be subsequently incremented by
// the read time of sessionsore.bak.
let readOptions = {
outExecutionDuration: null
};
// First read the sessionstore.js.
let text = yield self.readAux(self.path, readOptions);
if (typeof text === "undefined") {
// If sessionstore.js does not exist or is corrupted, read the
// sessionstore.bak.
text = yield self.readAux(self.backupPath, readOptions);
}
// Return either the content of the sessionstore.bak if it was read
// successfully or an empty string otherwise.
throw new Task.Result(text || "");
});
},
write: function ssfi_write(aData) {
write: function (aData) {
let refObj = {};
let self = this;
return TaskUtils.spawn(function task() {
TelemetryStopwatch.start("FX_SESSION_RESTORE_WRITE_FILE_MS", refObj);
TelemetryStopwatch.start("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
let bytes = gEncoder.encode(aData);
try {
let promise = OS.File.writeAtomic(self.path, bytes, {tmpPath: self.path + ".tmp"});
let promise = SessionWorker.post("write", [aData]);
// At this point, we measure how long we stop the main thread
TelemetryStopwatch.finish("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
@ -294,93 +229,51 @@ let SessionFileInternal = {
} catch (ex) {
TelemetryStopwatch.cancel("FX_SESSION_RESTORE_WRITE_FILE_LONGEST_OP_MS", refObj);
TelemetryStopwatch.cancel("FX_SESSION_RESTORE_WRITE_FILE_MS", refObj);
Cu.reportError("Could not write session state file " + self.path
+ ": " + aReason);
}
});
},
createBackupCopy: function ssfi_createBackupCopy() {
let backupCopyOptions = {
outExecutionDuration: null
};
let self = this;
return TaskUtils.spawn(function task() {
try {
yield OS.File.move(self.path, self.backupPath, backupCopyOptions);
Telemetry.getHistogramById("FX_SESSION_RESTORE_BACKUP_FILE_MS").add(
backupCopyOptions.outExecutionDuration);
} catch (ex if self._isNoSuchFile(ex)) {
// Ignore exceptions about non-existent files.
} catch (ex) {
Cu.reportError("Could not backup session state file: " + ex);
throw ex;
}
});
},
createUpgradeBackupCopy: function(ext) {
return TaskUtils.spawn(function task() {
try {
yield OS.File.copy(this.path, this.backupPath + ext);
} catch (ex if this._isNoSuchFile(ex)) {
// Ignore exceptions about non-existent files.
} catch (ex) {
Cu.reportError("Could not backup session state file to " +
dest + ": " + ex);
throw ex;
Cu.reportError("Could not write session state file " + this.path
+ ": " + ex);
}
}.bind(this));
},
removeUpgradeBackup: function(ext) {
return TaskUtils.spawn(function task() {
try {
yield OS.File.remove(this.backupPath + ext);
} catch (ex if this._isNoSuchFile(ex)) {
// Ignore exceptions about non-existent files.
}
}.bind(this));
writeLoadStateOnceAfterStartup: function (aLoadState) {
return SessionWorker.post("writeLoadStateOnceAfterStartup", [aLoadState]);
},
wipe: function ssfi_wipe() {
let self = this;
return TaskUtils.spawn(function task() {
let exn;
// Erase session state file
try {
yield OS.File.remove(self.path);
} catch (ex if self._isNoSuchFile(ex)) {
// Ignore exceptions about non-existent files.
} catch (ex) {
// Report error, don't stop immediately
Cu.reportError("Could not remove session state file: " + ex);
exn = ex;
}
// Erase any backup, any file named "sessionstore.bak[-buildID]".
let iterator = new OS.File.DirectoryIterator(OS.Constants.Path.profileDir);
for (let promise of iterator) {
let entry = yield promise;
if (!entry.isDir && entry.path.startsWith(self.backupPath)) {
try {
yield OS.File.remove(entry.path);
} catch (ex) {
// Report error, don't stop immediately
Cu.reportError("Could not remove backup file " + entry.path + " : " + ex);
exn = exn || ex;
}
}
}
if (exn) {
throw exn;
}
});
moveToBackupPath: function () {
return SessionWorker.post("moveToBackupPath");
},
_isNoSuchFile: function ssfi_isNoSuchFile(aReason) {
return aReason instanceof OS.File.Error && aReason.becauseNoSuchFile;
createBackupCopy: function (ext) {
return SessionWorker.post("createBackupCopy", [ext]);
},
removeBackupCopy: function (ext) {
return SessionWorker.post("removeBackupCopy", [ext]);
},
wipe: function () {
return SessionWorker.post("wipe");
}
};
// Interface to a dedicated thread handling I/O
let SessionWorker = (function () {
let worker = new PromiseWorker("resource:///modules/sessionstore/SessionWorker.js",
OS.Shared.LOG.bind("SessionWorker"));
return {
post: function post(...args) {
let promise = worker.post.apply(worker, args);
return promise.then(
null,
function onError(error) {
// Decode any serialized error
if (error instanceof PromiseWorker.WorkerError) {
throw OS.File.Error.fromMsg(error.data);
} else {
throw error;
}
}
);
}
};
})();

View File

@ -16,6 +16,7 @@ EXTRA_JS_MODULES = [
'DocumentUtils.jsm',
'SessionMigration.jsm',
'SessionStorage.jsm',
'SessionWorker.js',
'XPathGenerator.jsm',
'_SessionFile.jsm',
]

View File

@ -196,18 +196,13 @@ function test() {
options = {private: true};
}
let newWin = OpenBrowserWindow(options);
newWin.addEventListener("load", function(aEvent) {
newWin.removeEventListener("load", arguments.callee, false);
newWin.gBrowser.addEventListener("load", function(aEvent) {
newWin.gBrowser.removeEventListener("load", arguments.callee, true);
TEST_URLS.forEach(function (url) {
newWin.gBrowser.addTab(url);
});
whenNewWindowLoaded(options, function (newWin) {
TEST_URLS.forEach(function (url) {
newWin.gBrowser.addTab(url);
});
executeSoon(function() testFn(newWin));
}, true);
}, false);
executeSoon(() => testFn(newWin));
});
}
/**
@ -230,20 +225,16 @@ function test() {
// Open a new window
// The previously closed window should be restored
newWin = OpenBrowserWindow({});
newWin.addEventListener("load", function() {
this.removeEventListener("load", arguments.callee, true);
executeSoon(function() {
is(newWin.gBrowser.browsers.length, TEST_URLS.length + 1,
"Restored window in-session with otherpopup windows around");
whenNewWindowLoaded({}, function (newWin) {
is(newWin.gBrowser.browsers.length, TEST_URLS.length + 1,
"Restored window in-session with otherpopup windows around");
// Cleanup
newWin.close();
// Cleanup
newWin.close();
// Next please
executeSoon(nextFn);
});
}, true);
// Next please
executeSoon(nextFn);
});
});
}
@ -259,32 +250,24 @@ function test() {
// Enter private browsing mode
// Open a new window.
// The previously closed window should NOT be restored
newWin = OpenBrowserWindow({private: true});
newWin.addEventListener("load", function() {
this.removeEventListener("load", arguments.callee, true);
executeSoon(function() {
is(newWin.gBrowser.browsers.length, 1,
"Did not restore in private browing mode");
whenNewWindowLoaded({private: true}, function (newWin) {
is(newWin.gBrowser.browsers.length, 1,
"Did not restore in private browing mode");
// Cleanup
newWin.BrowserTryToCloseWindow();
// Cleanup
newWin.BrowserTryToCloseWindow();
// Exit private browsing mode again
newWin = OpenBrowserWindow({});
newWin.addEventListener("load", function() {
this.removeEventListener("load", arguments.callee, true);
executeSoon(function() {
is(newWin.gBrowser.browsers.length, TEST_URLS.length + 1,
"Restored after leaving private browsing again");
// Exit private browsing mode again
whenNewWindowLoaded({}, function (newWin) {
is(newWin.gBrowser.browsers.length, TEST_URLS.length + 1,
"Restored after leaving private browsing again");
newWin.close();
newWin.close();
// Next please
executeSoon(nextFn);
});
}, true);
// Next please
executeSoon(nextFn);
});
}, true);
});
});
}
@ -312,21 +295,17 @@ function test() {
popup2.close();
// open a new window the previously closed window should be restored to
newWin = OpenBrowserWindow({});
newWin.addEventListener("load", function() {
this.removeEventListener("load", arguments.callee, true);
executeSoon(function() {
is(newWin.gBrowser.browsers.length, TEST_URLS.length + 1,
"Restored window and associated tabs in session");
whenNewWindowLoaded({}, function (newWin) {
is(newWin.gBrowser.browsers.length, TEST_URLS.length + 1,
"Restored window and associated tabs in session");
// Cleanup
newWin.close();
popup.close();
// Cleanup
newWin.close();
popup.close();
// Next please
executeSoon(nextFn);
});
}, true);
// Next please
executeSoon(nextFn);
});
}, true);
}, false);
});
@ -366,22 +345,18 @@ function test() {
// but instead a new window is opened without restoring anything
popup.close();
let newWin = OpenBrowserWindow({});
newWin.addEventListener("load", function() {
newWin.removeEventListener("load", arguments.callee, true);
executeSoon(function() {
isnot(newWin.gBrowser.browsers.length, 2,
"Did not restore the popup window");
is(TEST_URLS.indexOf(newWin.gBrowser.browsers[0].currentURI.spec), -1,
"Did not restore the popup window (2)");
whenNewWindowLoaded({}, function (newWin) {
isnot(newWin.gBrowser.browsers.length, 2,
"Did not restore the popup window");
is(TEST_URLS.indexOf(newWin.gBrowser.browsers[0].currentURI.spec), -1,
"Did not restore the popup window (2)");
// Cleanup
newWin.close();
// Cleanup
newWin.close();
// Next please
executeSoon(nextFn);
});
}, true);
// Next please
executeSoon(nextFn);
});
}, true);
}, false);
}, true);
@ -402,27 +377,23 @@ function test() {
newWin = undoCloseWindow(0);
newWin2 = OpenBrowserWindow({});
newWin2.addEventListener("load", function() {
newWin2.removeEventListener("load", arguments.callee, true);
executeSoon(function() {
is(newWin2.gBrowser.browsers.length, 1,
"Did not restore, as undoCloseWindow() was last called");
is(TEST_URLS.indexOf(newWin2.gBrowser.browsers[0].currentURI.spec), -1,
"Did not restore, as undoCloseWindow() was last called (2)");
whenNewWindowLoaded({}, function (newWin2) {
is(newWin2.gBrowser.browsers.length, 1,
"Did not restore, as undoCloseWindow() was last called");
is(TEST_URLS.indexOf(newWin2.gBrowser.browsers[0].currentURI.spec), -1,
"Did not restore, as undoCloseWindow() was last called (2)");
browserWindowsCount([2, 3], "browser windows while running testOpenCloseRestoreFromPopup");
browserWindowsCount([2, 3], "browser windows while running testOpenCloseRestoreFromPopup");
// Cleanup
newWin.close();
newWin2.close();
// Cleanup
newWin.close();
newWin2.close();
browserWindowsCount([0, 1], "browser windows while running testOpenCloseRestoreFromPopup");
browserWindowsCount([0, 1], "browser windows while running testOpenCloseRestoreFromPopup");
// Next please
executeSoon(nextFn);
});
}, true);
// Next please
executeSoon(nextFn);
});
});
});
}

View File

@ -10,37 +10,34 @@ function test() {
waitForExplicitFinish();
let window_B = openDialog(location, "_blank", "chrome,all,dialog=no");
window_B.addEventListener("load", function(aEvent) {
window_B.removeEventListener("load", arguments.callee, false);
whenNewWindowLoaded({ private: false }, function (window_B) {
waitForFocus(function() {
// Add identifying information to window_B
ss.setWindowValue(window_B, uniqKey, uniqVal);
let state = JSON.parse(ss.getBrowserState());
let selectedWindow = state.windows[state.selectedWindow - 1];
is(selectedWindow.extData && selectedWindow.extData[uniqKey], uniqVal,
"selectedWindow is window_B");
// Now minimize window_B. The selected window shouldn't have the secret data
window_B.minimize();
waitForFocus(function() {
// Add identifying information to window_B
ss.setWindowValue(window_B, uniqKey, uniqVal);
let state = JSON.parse(ss.getBrowserState());
let selectedWindow = state.windows[state.selectedWindow - 1];
is(selectedWindow.extData && selectedWindow.extData[uniqKey], uniqVal,
"selectedWindow is window_B");
state = JSON.parse(ss.getBrowserState());
selectedWindow = state.windows[state.selectedWindow - 1];
ok(!selectedWindow.extData || !selectedWindow.extData[uniqKey],
"selectedWindow is not window_B after minimizing it");
// Now minimize window_B. The selected window shouldn't have the secret data
window_B.minimize();
waitForFocus(function() {
state = JSON.parse(ss.getBrowserState());
selectedWindow = state.windows[state.selectedWindow - 1];
ok(!selectedWindow.extData || !selectedWindow.extData[uniqKey],
"selectedWindow is not window_B after minimizing it");
// Now minimize the last open window (assumes no other tests left windows open)
window.minimize();
state = JSON.parse(ss.getBrowserState());
is(state.selectedWindow, 0,
"selectedWindow should be 0 when all windows are minimized");
// Now minimize the last open window (assumes no other tests left windows open)
window.minimize();
state = JSON.parse(ss.getBrowserState());
is(state.selectedWindow, 0,
"selectedWindow should be 0 when all windows are minimized");
// Cleanup
window.restore();
window_B.close();
finish();
});
}, window_B);
}, false);
// Cleanup
window.restore();
window_B.close();
finish();
});
}, window_B);
});
}

View File

@ -174,23 +174,7 @@ function forceWriteState(aCallback) {
}
function testOnWindow(aIsPrivate, aCallback) {
let win = OpenBrowserWindow({private: aIsPrivate});
let gotLoad = false;
let gotActivate = false;
win.addEventListener("activate", function onActivate() {
win.removeEventListener("activate", onActivate, false);
gotActivate = true;
if (gotLoad) {
executeSoon(function() { aCallback(win) });
}
}, false);
win.addEventListener("load", function onLoad() {
win.removeEventListener("load", onLoad, false);
gotLoad = true;
if (gotActivate) {
executeSoon(function() { aCallback(win) });
}
}, false);
whenNewWindowLoaded({private: aIsPrivate}, aCallback);
}
function waitForTabLoad(aWin, aURL, aCallback) {

View File

@ -78,9 +78,9 @@ function testWriteNoBackup() {
let array = yield OS.File.read(path);
gSSData = gDecoder.decode(array);
// Manually trigger _SessionFile.createBackupCopy since the backup once
// Manually trigger _SessionFile.moveToBackupPath since the backup once
// promise is already resolved and backup would not be triggered again.
yield _SessionFile.createBackupCopy();
yield _SessionFile.moveToBackupPath();
nextTest(testWriteBackup);
}
@ -140,4 +140,4 @@ function testNoWriteBackup() {
is(ssBakData, gSSBakData, "sessionstore.bak is unchanged.");
executeSoon(finish);
}
}

View File

@ -14,10 +14,11 @@ function test() {
function runTests() {
// Open a new window.
let win = OpenBrowserWindow();
yield whenWindowLoaded(win);
yield whenDelayedStartupFinished(win, next);
// Load some URL in the current tab.
win.gBrowser.selectedBrowser.loadURI("about:robots");
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;
win.gBrowser.selectedBrowser.loadURIWithFlags("about:robots", flags);
yield whenBrowserLoaded(win.gBrowser.selectedBrowser);
// Open a second tab and close the first one.

View File

@ -19,7 +19,7 @@ function runTests() {
// because we always collect data for tabs of active windows no matter if
// the window is dirty or not.
let win = OpenBrowserWindow();
yield waitForLoad(win);
yield whenDelayedStartupFinished(win, next);
// Create a tab with some form fields.
let tab = gBrowser.selectedTab = gBrowser.addTab(URL);

View File

@ -23,7 +23,7 @@ function runTests() {
// because we always collect data for tabs of active windows no matter if
// the window is dirty or not.
let win = OpenBrowserWindow();
yield waitForLoad(win);
yield whenDelayedStartupFinished(win, next);
// Create a tab with two history entries.
let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");

View File

@ -13,9 +13,7 @@ function test() {
// Load a private window, then close it
// and verify it doesn't get remembered for restoring
var win = OpenBrowserWindow({private: true});
whenWindowLoaded(win, function onload() {
whenNewWindowLoaded({private: true}, function (win) {
info("The private window got loaded");
win.addEventListener("SSWindowClosing", function onclosing() {
win.removeEventListener("SSWindowClosing", onclosing, false);

View File

@ -289,39 +289,34 @@ function closeAllButPrimaryWindow() {
}
}
/**
* When opening a new window it is not sufficient to wait for its load event.
* We need to use whenDelayedStartupFinshed() here as the browser window's
* delayedStartup() routine is executed one tick after the window's load event
* has been dispatched. browser-delayed-startup-finished might be deferred even
* further if parts of the window's initialization process take more time than
* expected (e.g. reading a big session state from disk).
*/
function whenNewWindowLoaded(aOptions, aCallback) {
let win = OpenBrowserWindow(aOptions);
let gotLoad = false;
let gotActivate = (Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager).activeWindow == win);
function maybeRunCallback() {
if (gotLoad && gotActivate) {
win.BrowserChromeTest.runWhenReady(function() {
executeSoon(function() { aCallback(win); });
});
}
}
if (!gotActivate) {
win.addEventListener("activate", function onActivate() {
info("Got activate.");
win.removeEventListener("activate", onActivate, false);
gotActivate = true;
maybeRunCallback();
}, false);
} else {
info("Was activated.");
}
win.addEventListener("load", function onLoad() {
info("Got load");
win.removeEventListener("load", onLoad, false);
gotLoad = true;
maybeRunCallback();
}, false);
whenDelayedStartupFinished(win, () => aCallback(win));
return win;
}
/**
* This waits for the browser-delayed-startup-finished notification of a given
* window. It indicates that the windows has loaded completely and is ready to
* be used for testing.
*/
function whenDelayedStartupFinished(aWindow, aCallback) {
Services.obs.addObserver(function observer(aSubject, aTopic) {
if (aWindow == aSubject) {
Services.obs.removeObserver(observer, aTopic);
executeSoon(aCallback);
}
}, "browser-delayed-startup-finished", false);
}
/**
* The test runner that controls the execution flow of our tests.
*/

View File

@ -19,7 +19,7 @@ function pathBackup(ext) {
// Ensure that things proceed smoothly if there is no file to back up
add_task(function test_nothing_to_backup() {
yield _SessionFile.createUpgradeBackupCopy("");
yield _SessionFile.createBackupCopy("");
});
// Create a file, back it up, remove it
@ -29,14 +29,14 @@ add_task(function test_do_backup() {
yield OS.File.writeAtomic(pathStore, content, {tmpPath: pathStore + ".tmp"});
do_print("Ensuring that the backup is created");
yield _SessionFile.createUpgradeBackupCopy(ext);
yield _SessionFile.createBackupCopy(ext);
do_check_true((yield OS.File.exists(pathBackup(ext))));
let data = yield OS.File.read(pathBackup(ext));
do_check_eq((new TextDecoder()).decode(data), content);
do_print("Ensuring that we can remove the backup");
yield _SessionFile.removeUpgradeBackup(ext);
yield _SessionFile.removeBackupCopy(ext);
do_check_false((yield OS.File.exists(pathBackup(ext))));
});

View File

@ -69,7 +69,7 @@ gcli.addCommand({
throw gcli.lookup("profilerAlreadyStarted2");
panel.toggleRecording();
return gcli.lookup("profilerStarted");
return gcli.lookup("profilerStarted2");
}
return gDevTools.showToolbox(context.environment.target, "jsprofiler")

View File

@ -48,7 +48,7 @@ function testProfilerStart() {
deferred.resolve();
});
cmd("profiler start", gcli.lookup("profilerStarted"));
cmd("profiler start", gcli.lookup("profilerStarted2"));
return deferred.promise;
}

View File

@ -1474,7 +1474,9 @@ let SyntaxTreeVisitor = {
aCallbacks.onArrayExpression(aNode);
}
for (let element of aNode.elements) {
if (element) {
// TODO: remove the typeof check when support for SpreadExpression is
// added (bug 890913).
if (element && typeof this[element.type] == "function") {
this[element.type](element, aNode, aCallbacks);
}
}

View File

@ -1251,9 +1251,9 @@ profilerNotFound=Profile not found
# start the profiler.
profilerNotStarted3=Profiler has not been started yet. Use 'profile start' to start profiling
# LOCALIZATION NOTE (profilerStarted) A very short string that indicates that
# LOCALIZATION NOTE (profilerStarted2) A very short string that indicates that
# we have started recording.
profilerStarted=Recording...
profilerStarted2=Recording…
# LOCALIZATION NOTE (profilerNotReady) A message that is displayed whenever
# an operation cannot be completed because the profiler has not been opened yet.

View File

@ -2849,7 +2849,7 @@ function fetch(aURL, aOptions={ loadFromCache: true }) {
try {
NetUtil.asyncFetch(url, function onFetch(aStream, aStatus) {
if (!Components.isSuccessCode(aStatus)) {
deferred.reject("Request failed: " + url);
deferred.reject(new Error("Request failed: " + url));
return;
}
@ -2858,7 +2858,7 @@ function fetch(aURL, aOptions={ loadFromCache: true }) {
aStream.close();
});
} catch (ex) {
deferred.reject("Request failed: " + url);
deferred.reject(new Error("Request failed: " + url));
}
break;
@ -2876,7 +2876,7 @@ function fetch(aURL, aOptions={ loadFromCache: true }) {
let streamListener = {
onStartRequest: function(aRequest, aContext, aStatusCode) {
if (!Components.isSuccessCode(aStatusCode)) {
deferred.reject("Request failed: " + url);
deferred.reject(new Error("Request failed: " + url));
}
},
onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
@ -2884,7 +2884,7 @@ function fetch(aURL, aOptions={ loadFromCache: true }) {
},
onStopRequest: function(aRequest, aContext, aStatusCode) {
if (!Components.isSuccessCode(aStatusCode)) {
deferred.reject("Request failed: " + url);
deferred.reject(new Error("Request failed: " + url));
return;
}
@ -2934,6 +2934,7 @@ function convertToUnicode(aString, aCharset=null) {
* An optional prefix for the reported error message.
*/
function reportError(aError, aPrefix="") {
dbg_assert(aError instanceof Error, "Must pass Error objects to reportError");
let msg = aPrefix + aError.message + ":\n" + aError.stack;
Cu.reportError(msg);
dumpn(msg);

View File

@ -24,8 +24,7 @@ let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
Cu.import("resource://gre/modules/jsdebugger.jsm");
addDebuggerToGlobal(this);
let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
const { defer, resolve, reject, all } = promise;
loadSubScript.call(this, "resource://gre/modules/commonjs/sdk/core/promise.js");
Cu.import("resource://gre/modules/devtools/SourceMap.jsm");
@ -198,11 +197,11 @@ var DebuggerServer = {
get initialized() this._initialized,
/**
* Performs cleanup tasks before shutting down the debugger server, if no
* connections are currently open. Such tasks include clearing any actor
* constructors added at runtime. This method should be called whenever a
* debugger server is no longer useful, to avoid memory leaks. After this
* method returns, the debugger server must be initialized again before use.
* Performs cleanup tasks before shutting down the debugger server. Such tasks
* include clearing any actor constructors added at runtime. This method
* should be called whenever a debugger server is no longer useful, to avoid
* memory leaks. After this method returns, the debugger server must be
* initialized again before use.
*/
destroy: function DS_destroy() {
if (!this._initialized) {