Main patch - Bug 882007 - Restart notification is not shown when staging is enabled. r=bbondy
authorRobert Strong <robert.bugzilla@gmail.com>
Wed, 19 Jun 2013 18:05:04 -0700
changeset 135748 c7f6325c015ed7318f9eabe1ae8e752cd0a1dd09
parent 135747 6a4bbbbe19bbd77e7228a6aba31eb10d69d8f073
child 135749 5d23a7000b52f302fa156c19095ba86b61aedd39
push id24848
push useremorley@mozilla.com
push dateThu, 20 Jun 2013 07:59:05 +0000
treeherdermozilla-central@83aa31ec53d9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbondy
bugs882007
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Main patch - Bug 882007 - Restart notification is not shown when staging is enabled. r=bbondy
toolkit/mozapps/update/nsIUpdateService.idl
toolkit/mozapps/update/nsUpdateService.js
--- a/toolkit/mozapps/update/nsIUpdateService.idl
+++ b/toolkit/mozapps/update/nsIUpdateService.idl
@@ -347,17 +347,17 @@ interface nsIUpdateChecker : nsISupports
   void stopChecking(in unsigned short duration);
 };
 
 /**
  * An interface describing a global application service that handles performing
  * background update checks and provides utilities for selecting and
  * downloading update patches.
  */
-[scriptable, uuid(900b4a18-3bef-4f3e-bcf5-84dce0021c6d)]
+[scriptable, uuid(814f185b-cac6-494b-8fd6-63cf7380d857)]
 interface nsIApplicationUpdateService : nsISupports
 {
   /**
    * The Update Checker used for background update checking.
    */
   readonly attribute nsIUpdateChecker backgroundChecker;
 
   /**
@@ -390,23 +390,18 @@ interface nsIApplicationUpdateService : 
   void removeDownloadListener(in nsIRequestObserver listener);
 
   /**
    *
    */
   AString downloadUpdate(in nsIUpdate update, in boolean background);
 
   /**
-   * Apply an update in the background.
-   */
-  void applyUpdateInBackground(in nsIUpdate update);
-
-  /**
    * Get the Active Updates directory
-   * @returns The active updates directory.
+   * @returns An nsIFile for the active updates directory.
    */
   nsIFile getUpdatesDirectory();
 
   /**
    * Pauses the active update download process
    */
   void pauseDownload();
 
--- a/toolkit/mozapps/update/nsUpdateService.js
+++ b/toolkit/mozapps/update/nsUpdateService.js
@@ -38,17 +38,17 @@ const PREF_APP_UPDATE_INCOMPATIBLE_MODE 
 const PREF_APP_UPDATE_INTERVAL            = "app.update.interval";
 const PREF_APP_UPDATE_LOG                 = "app.update.log";
 const PREF_APP_UPDATE_MODE                = "app.update.mode";
 const PREF_APP_UPDATE_NEVER_BRANCH        = "app.update.never.";
 const PREF_APP_UPDATE_POSTUPDATE          = "app.update.postupdate";
 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_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_DETAILS         = "app.update.url.details";
 const PREF_APP_UPDATE_URL_OVERRIDE        = "app.update.url.override";
 const PREF_APP_UPDATE_SERVICE_ENABLED     = "app.update.service.enabled";
 const PREF_APP_UPDATE_SERVICE_ERRORS      = "app.update.service.errors";
 const PREF_APP_UPDATE_SERVICE_MAX_ERRORS  = "app.update.service.maxErrors";
 const PREF_APP_UPDATE_SOCKET_ERRORS       = "app.update.socket.maxErrors";
 const PREF_APP_UPDATE_RETRY_TIMEOUT       = "app.update.socket.retryTimeout";
@@ -76,21 +76,21 @@ const KEY_EXECUTABLE      = "XREExeF";
 #define USE_UPDATE_ARCHIVE_DIR
 #endif
 
 #ifdef USE_UPDATE_ARCHIVE_DIR
 const KEY_UPDATE_ARCHIVE_DIR = "UpdArchD"
 #endif
 
 #ifdef XP_WIN
-#define SKIP_STAGE_UPDATES_TEST
+#define CHECK_CAN_USE_SERVICE
 #elifdef MOZ_WIDGET_GONK
 // 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.
-#define SKIP_STAGE_UPDATES_TEST
+#define CHECK_CAN_USE_SERVICE
 #endif
 
 const DIR_UPDATES         = "updates";
 #ifdef XP_MACOSX
 const UPDATED_DIR         = "Updated.app";
 #else
 const UPDATED_DIR         = "updated";
 #endif
@@ -450,34 +450,35 @@ function getPerInstallationMutexName(aGl
   if (aGlobal === undefined) {
     aGobal = true;
   }
   let hasher = Components.classes["@mozilla.org/security/hash;1"].
               createInstance(Components.interfaces.nsICryptoHash);
     hasher.init(hasher.SHA1);
 
   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"].
                   createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
   converter.charset = "UTF-8";
   var data = converter.convertToByteArray(exeFile.path.toLowerCase());
 
   hasher.update(data, data.length);
   return (aGlobal ? "Global\\" : "") + "MozillaUpdateMutex-" + hasher.finish(true);
 }
 #endif // XP_WIN
 
 /**
  * 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
- * 2) Running a Metro and Desktop browser at the same time from the same path
- * 3) 2 browsers running in 2 different user sessions from the same path
+ * 2) Running a Metro and Desktop application at the same time from the same
+ *    path
+ * 3) 2 applications running in 2 different user sessions from the same path
  *
  * @return true if this instance holds the update mutex
  */
 function hasUpdateMutex() {
 #ifdef XP_WIN
   if (!this._updateMutexHandle) {
     this._updateMutexHandle = createMutex(getPerInstallationMutexName(true), false);
   }
@@ -485,23 +486,23 @@ function hasUpdateMutex() {
   return !!this._updateMutexHandle;
 #else
   return true;
 #endif // XP_WIN
 }
 
 XPCOMUtils.defineLazyGetter(this, "gCanApplyUpdates", function aus_gCanApplyUpdates() {
   function submitHasPermissionsTelemetryPing(val) {
-      try {
-        let h = Services.telemetry.getHistogramById("UPDATER_HAS_PERMISSIONS");
-        h.add(+val);
-      } catch(e) {
-        // Don't allow any exception to be propagated.
-        Components.utils.reportError(e);
-      }
+    try {
+      let h = Services.telemetry.getHistogramById("UPDATER_HAS_PERMISSIONS");
+      h.add(+val);
+    } catch(e) {
+      // Don't allow any exception to be propagated.
+      Components.utils.reportError(e);
+    }
   }
 
   try {
     var updateTestFile = getUpdateFile([FILE_PERMS_TEST]);
     LOG("gCanApplyUpdates - testing write access " + updateTestFile.path);
     testWriteAccess(updateTestFile, false);
 #ifdef XP_WIN
     var sysInfo = Cc["@mozilla.org/system-info;1"].
@@ -579,74 +580,95 @@ XPCOMUtils.defineLazyGetter(this, "gCanA
   catch (e) {
      LOG("gCanApplyUpdates - unable to apply updates. Exception: " + e);
     // No write privileges to install directory
     submitHasPermissionsTelemetryPing(false);
     return false;
   }
 
   if (!hasUpdateMutex()) {
-    LOG("gCanApplyUpdates - unable to apply updates because another " +
-        "browser is already handling updates for this installation.");
+    LOG("gCanApplyUpdates - unable to apply updates because another instance" +
+        "of the application is already handling updates for this " +
+        "installation.");
     return false;
   }
 
   LOG("gCanApplyUpdates - able to apply updates");
   submitHasPermissionsTelemetryPing(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 (!getPref("getBoolPref", PREF_APP_UPDATE_STAGE_ENABLED, false)) {
-    LOG("gCanStageUpdates - staging updates is disabled by preference " + PREF_APP_UPDATE_STAGE_ENABLED);
+  if (!getPref("getBoolPref", PREF_APP_UPDATE_STAGING_ENABLED, false)) {
+    LOG("getCanStageUpdates - staging updates is disabled by preference " +
+        PREF_APP_UPDATE_STAGING_ENABLED);
     return false;
   }
 
-#ifdef SKIP_STAGE_UPDATES_TEST
+#ifdef CHECK_CAN_USE_SERVICE
   if (getPref("getBoolPref", PREF_APP_UPDATE_SERVICE_ENABLED, false)) {
     // No need to perform directory write checks, the maintenance service will
     // 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;
   }
 #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
+  if (!hasUpdateMutex()) {
+    LOG("getCanStageUpdates - unable to apply updates because another " +
+        "instance of the application is already handling updates for this " +
+        "installation.");
     return false;
   }
 
-  if (!hasUpdateMutex()) {
-    LOG("gCanStageUpdates - unable to stage updates because another " +
-        "browser is already handling updates for this installation.");
-    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() {
 #ifdef XP_WIN
 #ifdef MOZ_METRO
   try {
     let metroUtils = Cc["@mozilla.org/windows-metroutils;1"].
                     createInstance(Ci.nsIWinMetroUtils);
     return metroUtils && metroUtils.immersive;
@@ -658,35 +680,35 @@ XPCOMUtils.defineLazyGetter(this, "gIsMe
 });
 
 XPCOMUtils.defineLazyGetter(this, "gMetroUpdatesEnabled", function aus_gMetroUpdatesEnabled() {
 #ifdef XP_WIN
 #ifdef MOZ_METRO
   if (gIsMetro) {
     let metroUpdate = getPref("getBoolPref", PREF_APP_UPDATE_METRO_ENABLED, true);
     if (!metroUpdate) {
-      LOG("gMetroUpdatesEnabled - unable to automatically check for metro" +
-          " updates, disabled by pref");
+      LOG("gMetroUpdatesEnabled - unable to automatically check for metro " +
+          "updates, disabled by pref");
       return false;
     }
   }
 #endif
 #endif
 
   return true;
 });
 
 XPCOMUtils.defineLazyGetter(this, "gCanCheckForUpdates", function aus_gCanCheckForUpdates() {
-  // If the administrator has locked the app update functionality
-  // OFF - this is not just a user setting, so disable the manual
-  // UI too.
+  // If the administrator has disabled app update and locked the preference so
+  // users can't check for updates. This preference check is ok in this lazy
+  // getter since locked prefs don't change until the application is restarted.
   var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
   if (!enabled && Services.prefs.prefIsLocked(PREF_APP_UPDATE_ENABLED)) {
     LOG("gCanCheckForUpdates - unable to automatically check for updates, " +
-        "disabled by pref");
+        "the preference is disabled and admistratively locked.");
     return false;
   }
 
   if (!gMetroUpdatesEnabled) {
     return false;
   }
 
   // If we don't know the binary platform we're updating, we can't update.
@@ -699,17 +721,18 @@ XPCOMUtils.defineLazyGetter(this, "gCanC
   if (!gOSVersion) {
     LOG("gCanCheckForUpdates - unable to check for updates, unknown OS " +
         "version");
     return false;
   }
 
   if (!hasUpdateMutex()) {
     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;
   }
 
   LOG("gCanCheckForUpdates - able to check for updates");
   return true;
 });
 
 /**
@@ -766,31 +789,32 @@ function binaryToHex(input) {
 #           An array of path components to locate beneath the directory
 #           specified by |key|
 #  @return  nsIFile object for the location specified.
  */
 function getUpdateDirCreate(pathArray) {
   return FileUtils.getDir(KEY_UPDROOT, pathArray, true);
 }
 
- /**
- #  Gets the specified directory at the specified hierarchy under the
- #  update root directory and without creating it if it doesn't exist.
- #  @param   pathArray
- #           An array of path components to locate beneath the directory
- #           specified by |key|
- #  @return  nsIFile object for the location specified.
-  */
+/**
+#  Gets the specified directory at the specified hierarchy under the
+#  update root directory and without creating it if it doesn't exist.
+#  @param   pathArray
+#           An array of path components to locate beneath the directory
+#           specified by |key|
+#  @return  nsIFile object for the location specified.
+ */
 function getUpdateDirNoCreate(pathArray) {
   return FileUtils.getDir(KEY_UPDROOT, pathArray, false);
 }
 
 /**
-#  Gets the application base directory.
-#  @return  nsIFile object for the application base directory.
+ * Gets the application base directory.
+ *
+ * @return  nsIFile object for the application base directory.
  */
 function getAppBaseDir() {
   return Services.dirsvc.get(KEY_EXECUTABLE, Ci.nsIFile).parent;
 }
 
 /**
  * Gets the root of the installation directory which is the application
  * bundle directory on Mac OS X and the location of the application binary
@@ -1837,16 +1861,17 @@ const UpdateServiceFactory = {
 /**
  * UpdateService
  * A Service for managing the discovery and installation of software updates.
  * @constructor
  */
 function UpdateService() {
   LOG("Creating UpdateService");
   Services.obs.addObserver(this, "xpcom-shutdown", false);
+  Services.prefs.addObserver("app.update.log", this, false);
 #ifdef MOZ_WIDGET_GONK
   // PowerManagerService::SyncProfile (which is called for Reboot, PowerOff
   // and Restart) sends the profile-change-net-teardown event. We can then
   // pause the download in a similar manner to xpcom-shutdown.
   Services.obs.addObserver(this, "profile-change-net-teardown", false);
 #endif
 }
 
@@ -1890,21 +1915,27 @@ UpdateService.prototype = {
     switch (topic) {
     case "post-update-processing":
       // Clean up any extant updates
       this._postUpdateProcessing();
       break;
     case "network:offline-status-changed":
       this._offlineStatusChanged(data);
       break;
+    case "nsPref:changed":
+      if (data == PREF_APP_UPDATE_LOG) {
+        gLogEnabled = getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
+      }
+      break;
 #ifdef MOZ_WIDGET_GONK
     case "profile-change-net-teardown": // fall thru
 #endif
     case "xpcom-shutdown":
       Services.obs.removeObserver(this, topic);
+      Services.prefs.removeObserver("app.update.log", this);
 
       if (this._retryTimer) {
         this._retryTimer.cancel();
       }
 
       this.pauseDownload();
       // Prevent leaking the downloader (bug 454964)
       this._downloader = null;
@@ -1918,17 +1949,17 @@ UpdateService.prototype = {
    * 1. post update processing
    * 2. resume of a download that was in progress during a previous session
    * 3. start of a complete update download after the failure to apply a partial
    *    update
    */
 
   /**
    * 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
    * notify the user of install success.
    */
   _postUpdateProcessing: function AUS__postUpdateProcessing() {
     if (!this.canCheckForUpdates) {
       LOG("UpdateService:_postUpdateProcessing - unable to check for " +
           "updates... returning early");
       return;
@@ -2060,17 +2091,17 @@ UpdateService.prototype = {
       // UAC for example, we don't want 2 reports from them on the same
       // version.
       this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_ENABLED,
                                       "UPDATER_UPDATES_ENABLED");
       this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_METRO_ENABLED,
                                       "UPDATER_UPDATES_METRO_ENABLED");
       this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_AUTO,
                                       "UPDATER_UPDATES_AUTOMATIC");
-      this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_STAGE_ENABLED,
+      this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_STAGING_ENABLED,
                                       "UPDATER_STAGE_ENABLED");
 
 #ifdef XP_WIN
       this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_SERVICE_ENABLED,
                                       "UPDATER_SERVICE_ENABLED");
       this._sendIntPrefTelemetryPing(PREF_APP_UPDATE_SERVICE_ERRORS,
                                      "UPDATER_SERVICE_ERRORS");
       this._sendServiceInstalledTelemetryPing();
@@ -2683,17 +2714,17 @@ UpdateService.prototype = {
   get canApplyUpdates() {
     return gCanApplyUpdates;
   },
 
   /**
    * See nsIUpdateService.idl
    */
   get canStageUpdates() {
-    return gCanStageUpdates;
+    return getCanStageUpdates();
   },
 
   /**
    * See nsIUpdateService.idl
    */
   get isOtherInstanceHandlingUpdates() {
     return !hasUpdateMutex();
   },
@@ -2772,36 +2803,16 @@ UpdateService.prototype = {
     }
 #endif
     // Set the previous application version prior to downloading the update.
     update.previousAppVersion = Services.appinfo.version;
     this._downloader = new Downloader(background, this);
     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
    */
   pauseDownload: function AUS_pauseDownload() {
     if (this.isDownloading)
       this._downloader.cancel();
   },
 
@@ -3122,26 +3133,44 @@ UpdateManager.prototype = {
     // Send an observer notification which the update wizard uses in
     // order to update its UI.
     LOG("UpdateManager:refreshUpdateStatus - Notifying observers that " +
         "the update was staged. state: " + update.state + ", status: " + status);
     Services.obs.notifyObservers(null, "update-staged", update.state);
 
     // Do this after *everything* else, since it will likely cause the app
     // to shut down.
+#ifdef MOZ_WIDGET_GONK
     if (update.state == STATE_APPLIED) {
       // Notify the user that an update has been staged and is ready for
       // installation (i.e. that they should restart the application). We do
       // not notify on failed update attempts.
       var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
                      createInstance(Ci.nsIUpdatePrompt);
       prompter.showUpdateDownloaded(update, true);
     } else {
       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}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateManager, Ci.nsIObserver])
 };
 
 /**
  * Checker
@@ -3963,33 +3992,29 @@ Downloader.prototype = {
     var maxFail = getPref("getIntPref", PREF_APP_UPDATE_SOCKET_ERRORS,
                           DEFAULT_SOCKET_MAX_ERRORS);
     LOG("Downloader:onStopRequest - status: " + status + ", " +
         "current fail: " + this.updateService._consecutiveSocketErrors + ", " +
         "max fail: " + maxFail + ", " + "retryTimeout: " + retryTimeout);
     if (Components.isSuccessCode(status)) {
       if (this._verifyDownload()) {
         state = shouldUseService() ? STATE_PENDING_SVC : STATE_PENDING
-
-        // We only need to explicitly show the prompt if this is a background
-        // 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);
+        if (this.background) {
+          shouldShowPrompt = !getCanStageUpdates();
+        }
 
         // Tell the updater.exe we're ready to apply.
         writeStatusFile(getUpdatesDir(), state);
         writeVersionFile(getUpdatesDir(), this._update.appVersion);
         this._update.installDate = (new Date()).getTime();
         this._update.statusText = gUpdateBundle.GetStringFromName("installPending");
       }
       else {
         LOG("Downloader:onStopRequest - download verification failed");
         state = STATE_DOWNLOAD_FAILED;
-
         status = Cr.NS_ERROR_CORRUPTED_CONTENT;
 
         // Yes, this code is a string.
         const vfCode = "verification_failed";
         var message = getStatusTextFromCode(vfCode, vfCode);
         this._update.statusText = message;
 
         if (this._update.isCompleteUpdate || this._update.patchCount != 2)
@@ -4107,34 +4132,49 @@ Downloader.prototype = {
 
         // Prevent leaking the update object (bug 454964).
         this._update = null;
       }
       // A complete download has been initiated or the failure was handled.
       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
     // to shut down.
     if (shouldShowPrompt) {
       // Notify the user that an update has been downloaded and is ready for
       // installation (i.e. that they should restart the application). We do
       // not notify on failed update attempts.
       var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
                      createInstance(Ci.nsIUpdatePrompt);
       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) {
       LOG("Downloader:onStopRequest - Registering online observer");
       this.updateService._registerOnlineObserver();
     } else if (shouldRetrySoon) {
       LOG("Downloader:onStopRequest - Retrying soon");
       this.updateService._consecutiveSocketErrors++;
       if (this.updateService._retryTimer) {
         this.updateService._retryTimer.cancel();