Main patch - Bug 882007 - Restart notification is not shown when staging is enabled. r=bbondy

This commit is contained in:
Robert Strong 2013-06-19 18:05:04 -07:00
parent 5c6db5dc34
commit 3e07312ff7
2 changed files with 145 additions and 110 deletions

View File

@ -352,7 +352,7 @@ interface nsIUpdateChecker : nsISupports
* background update checks and provides utilities for selecting and * background update checks and provides utilities for selecting and
* downloading update patches. * downloading update patches.
*/ */
[scriptable, uuid(900b4a18-3bef-4f3e-bcf5-84dce0021c6d)] [scriptable, uuid(814f185b-cac6-494b-8fd6-63cf7380d857)]
interface nsIApplicationUpdateService : nsISupports interface nsIApplicationUpdateService : nsISupports
{ {
/** /**
@ -394,14 +394,9 @@ interface nsIApplicationUpdateService : nsISupports
*/ */
AString downloadUpdate(in nsIUpdate update, in boolean background); AString downloadUpdate(in nsIUpdate update, in boolean background);
/**
* Apply an update in the background.
*/
void applyUpdateInBackground(in nsIUpdate update);
/** /**
* Get the Active Updates directory * Get the Active Updates directory
* @returns The active updates directory. * @returns An nsIFile for the active updates directory.
*/ */
nsIFile getUpdatesDirectory(); nsIFile getUpdatesDirectory();

View File

@ -43,7 +43,7 @@ const PREF_APP_UPDATE_POSTUPDATE = "app.update.postupdate";
const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime"; const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime";
const PREF_APP_UPDATE_SHOW_INSTALLED_UI = "app.update.showInstalledUI"; const PREF_APP_UPDATE_SHOW_INSTALLED_UI = "app.update.showInstalledUI";
const PREF_APP_UPDATE_SILENT = "app.update.silent"; const PREF_APP_UPDATE_SILENT = "app.update.silent";
const PREF_APP_UPDATE_STAGE_ENABLED = "app.update.staging.enabled"; const PREF_APP_UPDATE_STAGING_ENABLED = "app.update.staging.enabled";
const PREF_APP_UPDATE_URL = "app.update.url"; const PREF_APP_UPDATE_URL = "app.update.url";
const PREF_APP_UPDATE_URL_DETAILS = "app.update.url.details"; const PREF_APP_UPDATE_URL_DETAILS = "app.update.url.details";
const PREF_APP_UPDATE_URL_OVERRIDE = "app.update.url.override"; const PREF_APP_UPDATE_URL_OVERRIDE = "app.update.url.override";
@ -81,11 +81,11 @@ const KEY_UPDATE_ARCHIVE_DIR = "UpdArchD"
#endif #endif
#ifdef XP_WIN #ifdef XP_WIN
#define SKIP_STAGE_UPDATES_TEST #define CHECK_CAN_USE_SERVICE
#elifdef MOZ_WIDGET_GONK #elifdef MOZ_WIDGET_GONK
// In Gonk, the updater will remount the /system partition to move staged files // In Gonk, the updater will remount the /system partition to move staged files
// into place, so we skip the test here to keep things isolated. // into place, so we skip the test here to keep things isolated.
#define SKIP_STAGE_UPDATES_TEST #define CHECK_CAN_USE_SERVICE
#endif #endif
const DIR_UPDATES = "updates"; const DIR_UPDATES = "updates";
@ -455,7 +455,7 @@ function getPerInstallationMutexName(aGlobal) {
hasher.init(hasher.SHA1); hasher.init(hasher.SHA1);
Components.utils.import("resource://gre/modules/Services.jsm"); Components.utils.import("resource://gre/modules/Services.jsm");
var exeFile = Services.dirsvc.get("XREExeF", Components.interfaces.nsILocalFile); var exeFile = Services.dirsvc.get(KEY_EXECUTABLE, Components.interfaces.nsILocalFile);
let converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]. let converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Components.interfaces.nsIScriptableUnicodeConverter); createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
@ -469,10 +469,11 @@ function getPerInstallationMutexName(aGlobal) {
/** /**
* Whether or not the current instance has the update mutex. The update mutex * Whether or not the current instance has the update mutex. The update mutex
* gives protection against 2 browsers from the same installation updating: * gives protection against 2 applications from the same installation updating:
* 1) Running multiple profiles from the same installation path * 1) Running multiple profiles from the same installation path
* 2) Running a Metro and Desktop browser at the same time from the same path * 2) Running a Metro and Desktop application at the same time from the same
* 3) 2 browsers running in 2 different user sessions from the same path * path
* 3) 2 applications running in 2 different user sessions from the same path
* *
* @return true if this instance holds the update mutex * @return true if this instance holds the update mutex
*/ */
@ -490,13 +491,13 @@ function hasUpdateMutex() {
XPCOMUtils.defineLazyGetter(this, "gCanApplyUpdates", function aus_gCanApplyUpdates() { XPCOMUtils.defineLazyGetter(this, "gCanApplyUpdates", function aus_gCanApplyUpdates() {
function submitHasPermissionsTelemetryPing(val) { function submitHasPermissionsTelemetryPing(val) {
try { try {
let h = Services.telemetry.getHistogramById("UPDATER_HAS_PERMISSIONS"); let h = Services.telemetry.getHistogramById("UPDATER_HAS_PERMISSIONS");
h.add(+val); h.add(+val);
} catch(e) { } catch(e) {
// Don't allow any exception to be propagated. // Don't allow any exception to be propagated.
Components.utils.reportError(e); Components.utils.reportError(e);
} }
} }
try { try {
@ -584,8 +585,9 @@ XPCOMUtils.defineLazyGetter(this, "gCanApplyUpdates", function aus_gCanApplyUpda
} }
if (!hasUpdateMutex()) { if (!hasUpdateMutex()) {
LOG("gCanApplyUpdates - unable to apply updates because another " + LOG("gCanApplyUpdates - unable to apply updates because another instance" +
"browser is already handling updates for this installation."); "of the application is already handling updates for this " +
"installation.");
return false; return false;
} }
@ -594,54 +596,74 @@ XPCOMUtils.defineLazyGetter(this, "gCanApplyUpdates", function aus_gCanApplyUpda
return true; return true;
}); });
XPCOMUtils.defineLazyGetter(this, "gCanStageUpdates", function aus_gCanStageUpdates() { /**
* Whether or not the application can stage an update.
*
* @return true if updates can be staged.
*/
function getCanStageUpdates() {
// If background updates are disabled, then just bail out! // If background updates are disabled, then just bail out!
if (!getPref("getBoolPref", PREF_APP_UPDATE_STAGE_ENABLED, false)) { if (!getPref("getBoolPref", PREF_APP_UPDATE_STAGING_ENABLED, false)) {
LOG("gCanStageUpdates - staging updates is disabled by preference " + PREF_APP_UPDATE_STAGE_ENABLED); LOG("getCanStageUpdates - staging updates is disabled by preference " +
PREF_APP_UPDATE_STAGING_ENABLED);
return false; return false;
} }
#ifdef SKIP_STAGE_UPDATES_TEST #ifdef CHECK_CAN_USE_SERVICE
if (getPref("getBoolPref", PREF_APP_UPDATE_SERVICE_ENABLED, false)) { if (getPref("getBoolPref", PREF_APP_UPDATE_SERVICE_ENABLED, false)) {
// No need to perform directory write checks, the maintenance service will // No need to perform directory write checks, the maintenance service will
// be able to write to all directories. // be able to write to all directories.
LOG("gCanStageUpdates - able to stage updates because we'll use the service"); LOG("getCanStageUpdates - able to stage updates because we'll use the service");
return true; return true;
} }
#endif #endif
try {
var updateTestFile = getInstallDirRoot();
updateTestFile.append(FILE_PERMS_TEST);
LOG("gCanStageUpdates - testing write access " + updateTestFile.path);
testWriteAccess(updateTestFile, true);
#ifndef XP_MACOSX
// On all platforms except Mac, we need to test the parent directory as well,
// as we need to be able to move files in that directory during the replacing
// step.
updateTestFile = getInstallDirRoot().parent;
updateTestFile.append(FILE_PERMS_TEST);
LOG("gCanStageUpdates - testing write access " + updateTestFile.path);
updateTestFile.createUnique(Ci.nsILocalFile.DIRECTORY_TYPE,
FileUtils.PERMS_DIRECTORY);
updateTestFile.remove(false);
#endif
}
catch (e) {
LOG("gCanStageUpdates - unable to stage updates. Exception: " + e);
// No write privileges
return false;
}
if (!hasUpdateMutex()) { if (!hasUpdateMutex()) {
LOG("gCanStageUpdates - unable to stage updates because another " + LOG("getCanStageUpdates - unable to apply updates because another " +
"browser is already handling updates for this installation."); "instance of the application is already handling updates for this " +
"installation.");
return false; return false;
} }
LOG("gCanStageUpdates - able to stage updates"); /**
return true; * Whether or not the application can stage an update for the current session.
}); * These checks are only performed once per session due to using a lazy getter.
*
* @return true if updates can be staged for this session.
*/
XPCOMUtils.defineLazyGetter(this, "canStageUpdatesSession", function canStageUpdatesSession() {
try {
var updateTestFile = getInstallDirRoot();
updateTestFile.append(FILE_PERMS_TEST);
LOG("canStageUpdatesSession - testing write access " +
updateTestFile.path);
testWriteAccess(updateTestFile, true);
#ifndef XP_MACOSX
// On all platforms except Mac, we need to test the parent directory as
// well, as we need to be able to move files in that directory during the
// replacing step.
updateTestFile = getInstallDirRoot().parent;
updateTestFile.append(FILE_PERMS_TEST);
LOG("canStageUpdatesSession - testing write access " +
updateTestFile.path);
updateTestFile.createUnique(Ci.nsILocalFile.DIRECTORY_TYPE,
FileUtils.PERMS_DIRECTORY);
updateTestFile.remove(false);
#endif
}
catch (e) {
LOG("canStageUpdatesSession - unable to stage updates. Exception: " +
e);
// No write privileges
return false;
}
LOG("canStageUpdatesSession - able to stage updates");
return true;
});
return canStageUpdatesSession;
}
XPCOMUtils.defineLazyGetter(this, "gIsMetro", function aus_gIsMetro() { XPCOMUtils.defineLazyGetter(this, "gIsMetro", function aus_gIsMetro() {
#ifdef XP_WIN #ifdef XP_WIN
@ -663,8 +685,8 @@ XPCOMUtils.defineLazyGetter(this, "gMetroUpdatesEnabled", function aus_gMetroUpd
if (gIsMetro) { if (gIsMetro) {
let metroUpdate = getPref("getBoolPref", PREF_APP_UPDATE_METRO_ENABLED, true); let metroUpdate = getPref("getBoolPref", PREF_APP_UPDATE_METRO_ENABLED, true);
if (!metroUpdate) { if (!metroUpdate) {
LOG("gMetroUpdatesEnabled - unable to automatically check for metro" + LOG("gMetroUpdatesEnabled - unable to automatically check for metro " +
" updates, disabled by pref"); "updates, disabled by pref");
return false; return false;
} }
} }
@ -675,13 +697,13 @@ XPCOMUtils.defineLazyGetter(this, "gMetroUpdatesEnabled", function aus_gMetroUpd
}); });
XPCOMUtils.defineLazyGetter(this, "gCanCheckForUpdates", function aus_gCanCheckForUpdates() { XPCOMUtils.defineLazyGetter(this, "gCanCheckForUpdates", function aus_gCanCheckForUpdates() {
// If the administrator has locked the app update functionality // If the administrator has disabled app update and locked the preference so
// OFF - this is not just a user setting, so disable the manual // users can't check for updates. This preference check is ok in this lazy
// UI too. // getter since locked prefs don't change until the application is restarted.
var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true); var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
if (!enabled && Services.prefs.prefIsLocked(PREF_APP_UPDATE_ENABLED)) { if (!enabled && Services.prefs.prefIsLocked(PREF_APP_UPDATE_ENABLED)) {
LOG("gCanCheckForUpdates - unable to automatically check for updates, " + LOG("gCanCheckForUpdates - unable to automatically check for updates, " +
"disabled by pref"); "the preference is disabled and admistratively locked.");
return false; return false;
} }
@ -704,7 +726,8 @@ XPCOMUtils.defineLazyGetter(this, "gCanCheckForUpdates", function aus_gCanCheckF
if (!hasUpdateMutex()) { if (!hasUpdateMutex()) {
LOG("gCanCheckForUpdates - unable to apply updates because another " + LOG("gCanCheckForUpdates - unable to apply updates because another " +
"browser is already handling updates for this installation."); "instance of the application is already handling updates for this " +
"installation.");
return false; return false;
} }
@ -771,21 +794,22 @@ function getUpdateDirCreate(pathArray) {
return FileUtils.getDir(KEY_UPDROOT, pathArray, true); return FileUtils.getDir(KEY_UPDROOT, pathArray, true);
} }
/** /**
# Gets the specified directory at the specified hierarchy under the # Gets the specified directory at the specified hierarchy under the
# update root directory and without creating it if it doesn't exist. # update root directory and without creating it if it doesn't exist.
# @param pathArray # @param pathArray
# An array of path components to locate beneath the directory # An array of path components to locate beneath the directory
# specified by |key| # specified by |key|
# @return nsIFile object for the location specified. # @return nsIFile object for the location specified.
*/ */
function getUpdateDirNoCreate(pathArray) { function getUpdateDirNoCreate(pathArray) {
return FileUtils.getDir(KEY_UPDROOT, pathArray, false); return FileUtils.getDir(KEY_UPDROOT, pathArray, false);
} }
/** /**
# Gets the application base directory. * Gets the application base directory.
# @return nsIFile object for the application base directory. *
* @return nsIFile object for the application base directory.
*/ */
function getAppBaseDir() { function getAppBaseDir() {
return Services.dirsvc.get(KEY_EXECUTABLE, Ci.nsIFile).parent; return Services.dirsvc.get(KEY_EXECUTABLE, Ci.nsIFile).parent;
@ -1842,6 +1866,7 @@ const UpdateServiceFactory = {
function UpdateService() { function UpdateService() {
LOG("Creating UpdateService"); LOG("Creating UpdateService");
Services.obs.addObserver(this, "xpcom-shutdown", false); Services.obs.addObserver(this, "xpcom-shutdown", false);
Services.prefs.addObserver("app.update.log", this, false);
#ifdef MOZ_WIDGET_GONK #ifdef MOZ_WIDGET_GONK
// PowerManagerService::SyncProfile (which is called for Reboot, PowerOff // PowerManagerService::SyncProfile (which is called for Reboot, PowerOff
// and Restart) sends the profile-change-net-teardown event. We can then // and Restart) sends the profile-change-net-teardown event. We can then
@ -1895,11 +1920,17 @@ UpdateService.prototype = {
case "network:offline-status-changed": case "network:offline-status-changed":
this._offlineStatusChanged(data); this._offlineStatusChanged(data);
break; break;
case "nsPref:changed":
if (data == PREF_APP_UPDATE_LOG) {
gLogEnabled = getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
}
break;
#ifdef MOZ_WIDGET_GONK #ifdef MOZ_WIDGET_GONK
case "profile-change-net-teardown": // fall thru case "profile-change-net-teardown": // fall thru
#endif #endif
case "xpcom-shutdown": case "xpcom-shutdown":
Services.obs.removeObserver(this, topic); Services.obs.removeObserver(this, topic);
Services.prefs.removeObserver("app.update.log", this);
if (this._retryTimer) { if (this._retryTimer) {
this._retryTimer.cancel(); this._retryTimer.cancel();
@ -1923,7 +1954,7 @@ UpdateService.prototype = {
/** /**
* Perform post-processing on updates lingering in the updates directory * Perform post-processing on updates lingering in the updates directory
* from a previous browser session - either report install failures (and * from a previous application session - either report install failures (and
* optionally attempt to fetch a different version if appropriate) or * optionally attempt to fetch a different version if appropriate) or
* notify the user of install success. * notify the user of install success.
*/ */
@ -2065,7 +2096,7 @@ UpdateService.prototype = {
"UPDATER_UPDATES_METRO_ENABLED"); "UPDATER_UPDATES_METRO_ENABLED");
this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_AUTO, this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_AUTO,
"UPDATER_UPDATES_AUTOMATIC"); "UPDATER_UPDATES_AUTOMATIC");
this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_STAGE_ENABLED, this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_STAGING_ENABLED,
"UPDATER_STAGE_ENABLED"); "UPDATER_STAGE_ENABLED");
#ifdef XP_WIN #ifdef XP_WIN
@ -2688,7 +2719,7 @@ UpdateService.prototype = {
* See nsIUpdateService.idl * See nsIUpdateService.idl
*/ */
get canStageUpdates() { get canStageUpdates() {
return gCanStageUpdates; return getCanStageUpdates();
}, },
/** /**
@ -2777,26 +2808,6 @@ UpdateService.prototype = {
return this._downloader.downloadUpdate(update); return this._downloader.downloadUpdate(update);
}, },
applyUpdateInBackground: function AUS_applyUpdateInBackground(update) {
// If we can't stage an update, then just bail out!
if (!gCanStageUpdates) {
return;
}
LOG("UpdateService:applyUpdateInBackground called with the following update: " +
update.name);
// Initiate the update in the background
try {
Cc["@mozilla.org/updates/update-processor;1"].
createInstance(Ci.nsIUpdateProcessor).
processUpdate(update);
} catch (e) {
// Fail gracefully in case the application does not support the update
// processor service.
}
},
/** /**
* See nsIUpdateService.idl * See nsIUpdateService.idl
*/ */
@ -3127,6 +3138,7 @@ UpdateManager.prototype = {
// Do this after *everything* else, since it will likely cause the app // Do this after *everything* else, since it will likely cause the app
// to shut down. // to shut down.
#ifdef MOZ_WIDGET_GONK
if (update.state == STATE_APPLIED) { if (update.state == STATE_APPLIED) {
// Notify the user that an update has been staged and is ready for // Notify the user that an update has been staged and is ready for
// installation (i.e. that they should restart the application). We do // installation (i.e. that they should restart the application). We do
@ -3137,6 +3149,23 @@ UpdateManager.prototype = {
} else { } else {
releaseSDCardMountLock(); releaseSDCardMountLock();
} }
#else
// Only prompt when the UI isn't already open.
let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null);
if (Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME) ||
windowType && Services.wm.getMostRecentWindow(windowType)) {
return;
}
if (update.state == STATE_APPLIED || update.state == STATE_APPLIED_SVC ||
update.state == STATE_PENDING || update.state == STATE_PENDING_SVC) {
// Notify the user that an update has been staged and is ready for
// installation (i.e. that they should restart the application).
var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
createInstance(Ci.nsIUpdatePrompt);
prompter.showUpdateDownloaded(update, true);
}
#endif
}, },
classID: Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"), classID: Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"),
@ -3968,12 +3997,9 @@ Downloader.prototype = {
if (Components.isSuccessCode(status)) { if (Components.isSuccessCode(status)) {
if (this._verifyDownload()) { if (this._verifyDownload()) {
state = shouldUseService() ? STATE_PENDING_SVC : STATE_PENDING state = shouldUseService() ? STATE_PENDING_SVC : STATE_PENDING
if (this.background) {
// We only need to explicitly show the prompt if this is a background shouldShowPrompt = !getCanStageUpdates();
// download, since otherwise some kind of UI is already visible and }
// that UI will notify.
if (this.background)
shouldShowPrompt = !getPref("getBoolPref", PREF_APP_UPDATE_STAGE_ENABLED, false);
// Tell the updater.exe we're ready to apply. // Tell the updater.exe we're ready to apply.
writeStatusFile(getUpdatesDir(), state); writeStatusFile(getUpdatesDir(), state);
@ -3984,7 +4010,6 @@ Downloader.prototype = {
else { else {
LOG("Downloader:onStopRequest - download verification failed"); LOG("Downloader:onStopRequest - download verification failed");
state = STATE_DOWNLOAD_FAILED; state = STATE_DOWNLOAD_FAILED;
status = Cr.NS_ERROR_CORRUPTED_CONTENT; status = Cr.NS_ERROR_CORRUPTED_CONTENT;
// Yes, this code is a string. // Yes, this code is a string.
@ -4112,6 +4137,28 @@ Downloader.prototype = {
return; return;
} }
if (state == STATE_PENDING || state == STATE_PENDING_SVC) {
if (getCanStageUpdates()) {
LOG("Downloader:onStopRequest - attempting to stage update: " +
this._update.name);
// Initiate the update in the background
try {
Cc["@mozilla.org/updates/update-processor;1"].
createInstance(Ci.nsIUpdateProcessor).
processUpdate(this._update);
} catch (e) {
// Fail gracefully in case the application does not support the update
// processor service.
LOG("Downloader:onStopRequest - failed to stage update. Exception: " +
e);
if (this.background) {
shouldShowPrompt = true;
}
}
}
}
// Do this after *everything* else, since it will likely cause the app // Do this after *everything* else, since it will likely cause the app
// to shut down. // to shut down.
if (shouldShowPrompt) { if (shouldShowPrompt) {
@ -4123,13 +4170,6 @@ Downloader.prototype = {
prompter.showUpdateDownloaded(this._update, true); prompter.showUpdateDownloaded(this._update, true);
} }
if (state == STATE_PENDING || state == STATE_PENDING_SVC) {
// Initiate the background update job.
Cc["@mozilla.org/updates/update-service;1"].
getService(Ci.nsIApplicationUpdateService).
applyUpdateInBackground(this._update);
}
if (shouldRegisterOnlineObserver) { if (shouldRegisterOnlineObserver) {
LOG("Downloader:onStopRequest - Registering online observer"); LOG("Downloader:onStopRequest - Registering online observer");
this.updateService._registerOnlineObserver(); this.updateService._registerOnlineObserver();