gecko/toolkit/mozapps/update/test/shared.js
Ehsan Akhgari ab9253304f Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy
When Firefox downloads an update, it previously kept the update around to apply
it on the next restart.  This patch changes this so that the updater program
is launched in the background as soon as the update has finished downloading
in order to stage the updated version of the application by copying the
existing installation directory to a temporary location and applying the update
on top of it, and replace the existing installation directory with the staged
directory on the next restart.

Because the replacing step is typically very fast, this patch eliminates the
wait for the update to be applied on restart, making it unnecessary to show a
progress dialog when restarting.

--HG--
rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul
rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js
rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js
rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js
rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js
rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js
rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js
rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js
rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js
rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js
rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 10:50:04 -04:00

523 lines
16 KiB
JavaScript

/* 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/. */
/* Shared code for xpcshell and mochitests-chrome */
// const Cc, Ci, and Cr are defined in netwerk/test/httpserver/httpd.js so we
// need to define unique ones.
const AUS_Cc = Components.classes;
const AUS_Ci = Components.interfaces;
const AUS_Cr = Components.results;
const AUS_Cu = Components.utils;
const PREF_APP_UPDATE_AUTO = "app.update.auto";
const PREF_APP_UPDATE_BACKGROUND = "app.update.stage.enabled";
const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors";
const PREF_APP_UPDATE_BACKGROUNDMAXERRORS = "app.update.backgroundMaxErrors";
const PREF_APP_UPDATE_CERTS_BRANCH = "app.update.certs.";
const PREF_APP_UPDATE_CERT_CHECKATTRS = "app.update.cert.checkAttributes";
const PREF_APP_UPDATE_CERT_ERRORS = "app.update.cert.errors";
const PREF_APP_UPDATE_CERT_MAXERRORS = "app.update.cert.maxErrors";
const PREF_APP_UPDATE_CERT_REQUIREBUILTIN = "app.update.cert.requireBuiltIn";
const PREF_APP_UPDATE_CHANNEL = "app.update.channel";
const PREF_APP_UPDATE_ENABLED = "app.update.enabled";
const PREF_APP_UPDATE_IDLETIME = "app.update.idletime";
const PREF_APP_UPDATE_LOG = "app.update.log";
const PREF_APP_UPDATE_NEVER_BRANCH = "app.update.never.";
const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime";
const PREF_APP_UPDATE_SHOW_INSTALLED_UI = "app.update.showInstalledUI";
const PREF_APP_UPDATE_SILENT = "app.update.silent";
const PREF_APP_UPDATE_URL = "app.update.url";
const PREF_APP_UPDATE_URL_DETAILS = "app.update.url.details";
const PREF_APP_UPDATE_URL_OVERRIDE = "app.update.url.override";
const PREF_APP_UPDATE_CERT_INVALID_ATTR_NAME = PREF_APP_UPDATE_CERTS_BRANCH +
"1.invalidName";
const PREF_APP_PARTNER_BRANCH = "app.partner.";
const PREF_DISTRIBUTION_ID = "distribution.id";
const PREF_DISTRIBUTION_VERSION = "distribution.version";
const PREF_EXTENSIONS_UPDATE_URL = "extensions.update.url";
const PREF_EXTENSIONS_STRICT_COMPAT = "extensions.strictCompatibility";
const NS_APP_PROFILE_DIR_STARTUP = "ProfDS";
const NS_APP_USER_PROFILE_50_DIR = "ProfD";
const NS_GRE_DIR = "GreD";
const NS_XPCOM_CURRENT_PROCESS_DIR = "XCurProcD";
const XRE_UPDATE_ROOT_DIR = "UpdRootD";
const CRC_ERROR = 4;
const WRITE_ERROR = 7;
const FILE_BACKUP_LOG = "backup-update.log";
const FILE_LAST_LOG = "last-update.log";
const FILE_UPDATER_INI = "updater.ini";
const FILE_UPDATES_DB = "updates.xml";
const FILE_UPDATE_ACTIVE = "active-update.xml";
const FILE_UPDATE_ARCHIVE = "update.mar";
const FILE_UPDATE_LOG = "update.log";
const FILE_UPDATE_STATUS = "update.status";
const FILE_UPDATE_VERSION = "update.version";
const MODE_RDONLY = 0x01;
const MODE_WRONLY = 0x02;
const MODE_CREATE = 0x08;
const MODE_APPEND = 0x10;
const MODE_TRUNCATE = 0x20;
const PR_RDWR = 0x04;
const PR_CREATE_FILE = 0x08;
const PR_APPEND = 0x10;
const PR_TRUNCATE = 0x20;
const PR_SYNC = 0x40;
const PR_EXCL = 0x80;
const PERMS_FILE = 0644;
const PERMS_DIRECTORY = 0755;
const DEFAULT_UPDATE_VERSION = "999999.0";
#include sharedUpdateXML.js
AUS_Cu.import("resource://gre/modules/Services.jsm");
AUS_Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties";
const gUpdateBundle = Services.strings.createBundle(URI_UPDATES_PROPERTIES);
XPCOMUtils.defineLazyGetter(this, "gAUS", function test_gAUS() {
return AUS_Cc["@mozilla.org/updates/update-service;1"].
getService(AUS_Ci.nsIApplicationUpdateService).
QueryInterface(AUS_Ci.nsITimerCallback).
QueryInterface(AUS_Ci.nsIObserver);
});
XPCOMUtils.defineLazyServiceGetter(this, "gUpdateManager",
"@mozilla.org/updates/update-manager;1",
"nsIUpdateManager");
XPCOMUtils.defineLazyGetter(this, "gUpdateChecker", function test_gUC() {
return AUS_Cc["@mozilla.org/updates/update-checker;1"].
createInstance(AUS_Ci.nsIUpdateChecker);
});
XPCOMUtils.defineLazyGetter(this, "gUP", function test_gUP() {
return AUS_Cc["@mozilla.org/updates/update-prompt;1"].
createInstance(AUS_Ci.nsIUpdatePrompt);
});
XPCOMUtils.defineLazyGetter(this, "gDefaultPrefBranch", function test_gDPB() {
return Services.prefs.getDefaultBranch(null);
});
XPCOMUtils.defineLazyGetter(this, "gZipW", function test_gZipW() {
return AUS_Cc["@mozilla.org/zipwriter;1"].
createInstance(AUS_Ci.nsIZipWriter);
});
/* Initializes the update service stub */
function initUpdateServiceStub() {
AUS_Cc["@mozilla.org/updates/update-service-stub;1"].
createInstance(AUS_Ci.nsISupports);
}
/* Reloads the update metadata from disk */
function reloadUpdateManagerData() {
gUpdateManager.QueryInterface(AUS_Ci.nsIObserver).
observe(null, "um-reload-update-data", "");
}
/**
* Sets the app.update.channel preference.
*
* @param aChannel
* The update channel. If not specified 'test_channel' will be used.
*/
function setUpdateChannel(aChannel) {
let channel = aChannel ? aChannel : "test_channel";
debugDump("setting default pref " + PREF_APP_UPDATE_CHANNEL + " to " + channel);
gDefaultPrefBranch.setCharPref(PREF_APP_UPDATE_CHANNEL, channel);
}
/**
* Sets the app.update.url.override preference.
*
* @param aURL
* The update url. If not specified 'URL_HOST + "update.xml"' will be
* used.
*/
function setUpdateURLOverride(aURL) {
let url = aURL ? aURL : URL_HOST + "update.xml";
debugDump("setting " + PREF_APP_UPDATE_URL_OVERRIDE + " to " + url);
Services.prefs.setCharPref(PREF_APP_UPDATE_URL_OVERRIDE, url);
}
/**
* Returns either the active or regular update database XML file.
*
* @param isActiveUpdate
* If true this will return the active-update.xml otherwise it will
* return the updates.xml file.
*/
function getUpdatesXMLFile(aIsActiveUpdate) {
var file = getUpdatesRootDir();
file.append(aIsActiveUpdate ? FILE_UPDATE_ACTIVE : FILE_UPDATES_DB);
return file;
}
/**
* Writes the updates specified to either the active-update.xml or the
* updates.xml.
*
* @param aContent
* The updates represented as a string to write to the XML file.
* @param isActiveUpdate
* If true this will write to the active-update.xml otherwise it will
* write to the updates.xml file.
*/
function writeUpdatesToXMLFile(aContent, aIsActiveUpdate) {
writeFile(getUpdatesXMLFile(aIsActiveUpdate), aContent);
}
/**
* Writes the current update operation/state to a file in the patch
* directory, indicating to the patching system that operations need
* to be performed.
*
* @param aStatus
* The status value to write.
*/
function writeStatusFile(aStatus) {
var file = getUpdatesDir();
file.append("0");
file.append(FILE_UPDATE_STATUS);
aStatus += "\n";
writeFile(file, aStatus);
}
/**
* Writes the current update version to a file in the patch directory,
* indicating to the patching system the version of the update.
*
* @param aVersion
* The version value to write.
*/
function writeVersionFile(aVersion) {
var file = getUpdatesDir();
file.append("0");
file.append(FILE_UPDATE_VERSION);
aVersion += "\n";
writeFile(file, aVersion);
}
/**
* Gets the updates root directory.
*
* @return nsIFile for the updates root directory.
*/
function getUpdatesRootDir() {
try {
return Services.dirsvc.get(XRE_UPDATE_ROOT_DIR, AUS_Ci.nsIFile);
} catch (e) {
// Fall back on the current process directory
return getCurrentProcessDir();
}
}
/**
* Gets the updates directory.
*
* @return nsIFile for the updates directory.
*/
function getUpdatesDir() {
var dir = getUpdatesRootDir();
dir.append("updates");
return dir;
}
/**
* Writes text to a file. This will replace existing text if the file exists
* and create the file if it doesn't exist.
*
* @param aFile
* The file to write to. Will be created if it doesn't exist.
* @param aText
* The text to write to the file. If there is existing text it will be
* replaced.
*/
function writeFile(aFile, aText) {
var fos = AUS_Cc["@mozilla.org/network/file-output-stream;1"].
createInstance(AUS_Ci.nsIFileOutputStream);
if (!aFile.exists())
aFile.create(AUS_Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
fos.init(aFile, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, PERMS_FILE, 0);
fos.write(aText, aText.length);
fos.close();
}
/**
* Reads the current update operation/state in a file in the patch
* directory.
*
* @param aFile (optional)
* nsIFile to read the update status from. If not provided the
* application's update status file will be used.
* @return The status value.
*/
function readStatusFile(aFile) {
var file;
if (aFile) {
file = aFile.clone();
file.append(FILE_UPDATE_STATUS);
}
else {
file = getUpdatesDir();
file.append("0");
file.append(FILE_UPDATE_STATUS);
}
return readFile(file).split("\n")[0];
}
/**
* Reads text from a file and returns the string.
*
* @param aFile
* The file to read from.
* @return The string of text read from the file.
*/
function readFile(aFile) {
var fis = AUS_Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(AUS_Ci.nsIFileInputStream);
if (!aFile.exists())
return null;
fis.init(aFile, MODE_RDONLY, PERMS_FILE, 0);
var sis = AUS_Cc["@mozilla.org/scriptableinputstream;1"].
createInstance(AUS_Ci.nsIScriptableInputStream);
sis.init(fis);
var text = sis.read(sis.available());
sis.close();
return text;
}
/**
* Reads the binary contents of a file and returns it as a string.
*
* @param aFile
* The file to read from.
* @return The contents of the file as a string.
*/
function readFileBytes(aFile) {
var fis = AUS_Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(AUS_Ci.nsIFileInputStream);
fis.init(aFile, -1, -1, false);
var bis = AUS_Cc["@mozilla.org/binaryinputstream;1"].
createInstance(AUS_Ci.nsIBinaryInputStream);
bis.setInputStream(fis);
var data = [];
var count = fis.available();
while (count > 0) {
var bytes = bis.readByteArray(Math.min(65535, count));
data.push(String.fromCharCode.apply(null, bytes));
count -= bytes.length;
if (bytes.length == 0)
throw "Nothing read from input stream!";
}
data.join('');
fis.close();
return data.toString();
}
/* Returns human readable status text from the updates.properties bundle */
function getStatusText(aErrCode) {
return getString("check_error-" + aErrCode);
}
/* Returns a string from the updates.properties bundle */
function getString(aName) {
try {
return gUpdateBundle.GetStringFromName(aName);
}
catch (e) {
}
return null;
}
/**
* Gets the file extension for an nsIFile.
*
* @param aFile
* The file to get the file extension for.
* @return The file extension.
*/
function getFileExtension(aFile) {
return Services.io.newFileURI(aFile).QueryInterface(AUS_Ci.nsIURL).
fileExtension;
}
/**
* Removes the updates.xml file, active-update.xml file, and all files and
* sub-directories in the updates directory except for the "0" sub-directory.
* This prevents some tests from failing due to files being left behind when the
* tests are interrupted.
*/
function removeUpdateDirsAndFiles() {
var file = getUpdatesXMLFile(true);
try {
if (file.exists())
file.remove(false);
}
catch (e) {
dump("Unable to remove file\npath: " + file.path +
"\nException: " + e + "\n");
}
file = getUpdatesXMLFile(false);
try {
if (file.exists())
file.remove(false);
}
catch (e) {
dump("Unable to remove file\npath: " + file.path +
"\nException: " + e + "\n");
}
// This fails sporadically on Mac OS X so wrap it in a try catch
var updatesDir = getUpdatesDir();
try {
cleanUpdatesDir(updatesDir);
}
catch (e) {
dump("Unable to remove files / directories from directory\npath: " +
updatesDir.path + "\nException: " + e + "\n");
}
}
/**
* Removes all files and sub-directories in the updates directory except for
* the "0" sub-directory.
*
* @param aDir
* nsIFile for the directory to be deleted.
*/
function cleanUpdatesDir(aDir) {
if (!aDir.exists())
return;
var dirEntries = aDir.directoryEntries;
while (dirEntries.hasMoreElements()) {
var entry = dirEntries.getNext().QueryInterface(AUS_Ci.nsIFile);
if (entry.isDirectory()) {
if (entry.leafName == "0" && entry.parent.leafName == "updates") {
cleanUpdatesDir(entry);
entry.permissions = PERMS_DIRECTORY;
}
else {
try {
entry.remove(true);
return;
}
catch (e) {
}
cleanUpdatesDir(entry);
entry.permissions = PERMS_DIRECTORY;
entry.remove(true);
}
}
else {
entry.permissions = PERMS_FILE;
entry.remove(false);
}
}
}
/**
* Deletes a directory and its children. First it tries nsIFile::Remove(true).
* If that fails it will fall back to recursing, setting the appropriate
* permissions, and deleting the current entry.
*
* @param aDir
* nsIFile for the directory to be deleted.
*/
function removeDirRecursive(aDir) {
if (!aDir.exists())
return;
try {
aDir.remove(true);
return;
}
catch (e) {
}
var dirEntries = aDir.directoryEntries;
while (dirEntries.hasMoreElements()) {
var entry = dirEntries.getNext().QueryInterface(AUS_Ci.nsIFile);
if (entry.isDirectory()) {
removeDirRecursive(entry);
}
else {
entry.permissions = PERMS_FILE;
entry.remove(false);
}
}
aDir.permissions = PERMS_DIRECTORY;
aDir.remove(true);
}
/**
* Returns the directory for the currently running process. This is used to
* clean up after the tests and to locate the active-update.xml and updates.xml
* files.
*
* @return nsIFile for the current process directory.
*/
function getCurrentProcessDir() {
return Services.dirsvc.get(NS_XPCOM_CURRENT_PROCESS_DIR, AUS_Ci.nsIFile);
}
/**
* Returns the Gecko Runtime Engine directory. This is used to locate the the
* updater binary (Windows and Linux) or updater package (Mac OS X). For
* XULRunner applications this is different than the currently running process
* directory.
*
* @return nsIFile for the Gecko Runtime Engine directory.
*/
function getGREDir() {
return Services.dirsvc.get(NS_GRE_DIR, AUS_Ci.nsIFile);
}
/**
* Logs TEST-INFO messages.
*
* @param aText
* The text to log.
* @param aCaller (optional)
* An optional Components.stack.caller. If not specified
* Components.stack.caller will be used.
*/
function logTestInfo(aText, aCaller) {
let caller = (aCaller ? aCaller : Components.stack.caller);
dump("TEST-INFO | " + caller.filename + " | [" + caller.name + " : " +
caller.lineNumber + "] " + aText + "\n");
}
/**
* Logs TEST-INFO messages when DEBUG_AUS_TEST evaluates to true.
*
* @param aText
* The text to log.
* @param aCaller (optional)
* An optional Components.stack.caller. If not specified
* Components.stack.caller will be used.
*/
function debugDump(aText, aCaller) {
if (DEBUG_AUS_TEST) {
let caller = aCaller ? aCaller : Components.stack.caller;
logTestInfo(aText, caller);
}
}