Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy
authorEhsan Akhgari <ehsan@mozilla.com>
Tue, 22 May 2012 10:50:04 -0400
changeset 96826 c20d415ef1b50fc6cd9751d5dbe79434307f50b4
parent 96825 b6d88d5947835a10f9819154824ad2a45137f6e1
child 96827 b038090f07c2dae64f83c6d557321d34c42e7060
child 96860 c54039aa8dccbf2934c156ed0fb4f9e6885fedbe
push id1439
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 20:19:22 +0000
treeherdermozilla-aurora@ea74834dccd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrstrong, bbondy
bugs307181
milestone15.0a1
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.
browser/app/profile/firefox.js
browser/base/content/aboutDialog.js
browser/base/content/aboutDialog.xul
browser/components/test/browser_bug538331.js
browser/locales/en-US/chrome/browser/aboutDialog.dtd
browser/locales/en-US/chrome/browser/browser.properties
build/automation.py.in
js/xpconnect/shell/Makefile.in
js/xpconnect/shell/xpcshell.cpp
toolkit/components/build/Makefile.in
toolkit/components/build/nsToolkitCompsCID.h
toolkit/components/build/nsToolkitCompsModule.cpp
toolkit/components/maintenanceservice/workmonitor.cpp
toolkit/components/maintenanceservice/workmonitor.h
toolkit/locales/en-US/chrome/mozapps/update/updates.properties
toolkit/mozapps/update/Makefile.in
toolkit/mozapps/update/common/errors.h
toolkit/mozapps/update/common/updatedefines.h
toolkit/mozapps/update/common/updatelogging.cpp
toolkit/mozapps/update/common/updatelogging.h
toolkit/mozapps/update/common/win_dirent.h
toolkit/mozapps/update/content/updates.js
toolkit/mozapps/update/nsIUpdateService.idl
toolkit/mozapps/update/nsUpdateService.js
toolkit/mozapps/update/test/Makefile.in
toolkit/mozapps/update/test/TestAUSHelper.cpp
toolkit/mozapps/update/test/TestAUSReadStrings.cpp
toolkit/mozapps/update/test/chrome/Makefile.in
toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul
toolkit/mozapps/update/test/chrome/test_0094_stagedServiceBackground.xul
toolkit/mozapps/update/test/chrome/test_9999_cleanup.xul
toolkit/mozapps/update/test/shared.js
toolkit/mozapps/update/test/sharedUpdateXML.js
toolkit/mozapps/update/test/unit/data/complete_cc_log_switch_success
toolkit/mozapps/update/test/unit/data/complete_log_switch_success
toolkit/mozapps/update/test/unit/data/partial_log_switch_success
toolkit/mozapps/update/test/unit/head_update.js.in
toolkit/mozapps/update/test/unit/test_0030_general.js
toolkit/mozapps/update/test/unit/test_0063_manager.js
toolkit/mozapps/update/test/unit/test_0112_general.js
toolkit/mozapps/update/test/unit/test_0113_general.js
toolkit/mozapps/update/test/unit/test_0113_versionDowngradeCheck.js
toolkit/mozapps/update/test/unit/test_0114_general.js
toolkit/mozapps/update/test/unit/test_0114_productChannelCheck.js
toolkit/mozapps/update/test/unit/test_0115_general.js
toolkit/mozapps/update/test/unit/test_0152_appBinReplaced_xp_win_complete.js
toolkit/mozapps/update/test/unit/test_0153_appBinPatched_xp_win_partial.js
toolkit/mozapps/update/test/unit/test_0161_appInUse_xp_unix_complete.js
toolkit/mozapps/update/test/unit/test_0161_appInUse_xp_win_complete.js
toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js
toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js
toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js
toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js
toolkit/mozapps/update/test/unit/test_0184_fileInUse_xp_win_complete.js
toolkit/mozapps/update/test/unit/test_0185_fileInUse_xp_win_partial.js
toolkit/mozapps/update/test/unit/test_0186_rmrfdirFileInUse_xp_win_complete.js
toolkit/mozapps/update/test/unit/test_0187_rmrfdirFileInUse_xp_win_partial.js
toolkit/mozapps/update/test/unit/test_0200_app_launch_apply_update.js
toolkit/mozapps/update/test/unit/test_0201_app_launch_apply_update.js
toolkit/mozapps/update/test/unit/test_0202_app_launch_apply_update_dirlocked.js
toolkit/mozapps/update/test/unit/xpcshell_updater.ini
toolkit/mozapps/update/test/unit/xpcshell_updater_windows.ini
toolkit/mozapps/update/test/unit/xpcshell_updater_xp_unix.ini
toolkit/mozapps/update/test_svc/Makefile.in
toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js
toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js
toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js
toolkit/mozapps/update/test_svc/unit/test_0152_appBinReplaced_xp_win_complete_svc.js
toolkit/mozapps/update/test_svc/unit/test_0153_appBinPatched_xp_win_partial_svc.js
toolkit/mozapps/update/test_svc/unit/test_0161_appInUse_xp_win_complete_svc.js
toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js
toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
toolkit/mozapps/update/test_svc/unit/test_0184_fileInUse_xp_win_complete_svc.js
toolkit/mozapps/update/test_svc/unit/test_0185_fileInUse_xp_win_partial_svc.js
toolkit/mozapps/update/test_svc/unit/test_0186_rmrfdirFileInUse_xp_win_complete_svc.js
toolkit/mozapps/update/test_svc/unit/test_0187_rmrfdirFileInUse_xp_win_partial_svc.js
toolkit/mozapps/update/test_svc/unit/test_0200_app_launch_apply_update_svc.js
toolkit/mozapps/update/test_svc/unit/test_0201_app_launch_apply_update_svc.js
toolkit/mozapps/update/test_svc/unit/test_0202_app_launch_apply_update_dirlocked_svc.js
toolkit/mozapps/update/test_svc/unit/xpcshell.ini
toolkit/mozapps/update/updater/Makefile.in
toolkit/mozapps/update/updater/updater.cpp
toolkit/mozapps/update/updater/win_dirent.cpp
toolkit/xre/MacLaunchHelper.h
toolkit/xre/MacLaunchHelper.mm
toolkit/xre/Makefile.in
toolkit/xre/nsAppRunner.cpp
toolkit/xre/nsAppRunner.h
toolkit/xre/nsUpdateDriver.cpp
toolkit/xre/nsUpdateDriver.h
toolkit/xre/nsWindowsRestart.cpp
toolkit/xre/nsXREDirProvider.cpp
toolkit/xre/nsXREDirProvider.h
toolkit/xre/test/win/Makefile.in
xpcom/build/nsXULAppAPI.h
xpcom/io/nsILocalFileWin.idl
xpcom/io/nsLocalFileWin.cpp
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -140,16 +140,20 @@ pref("app.update.auto", true);
 //
 // See chart in nsUpdateService.js source for more details
 //
 pref("app.update.mode", 1);
 
 // If set to true, the Update Service will present no UI for any event.
 pref("app.update.silent", false);
 
+// If set to true, the Update Service will apply updates in the background
+// when it finishes downloading them.
+pref("app.update.stage.enabled", true);
+
 // Update service URL:
 pref("app.update.url", "https://aus3.mozilla.org/update/3/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
 // app.update.url.manual is in branding section
 // app.update.url.details is in branding section
 
 // User-settable override to app.update.url for testing purposes.
 //pref("app.update.url.override", "");
 
--- a/browser/base/content/aboutDialog.js
+++ b/browser/base/content/aboutDialog.js
@@ -117,16 +117,22 @@ function appUpdater()
   }
 
   if (this.isPending) {
     this.setupUpdateButton("update.restart." +
                            (this.isMajor ? "upgradeButton" : "updateButton"));
     return;
   }
 
+  if (this.isApplied) {
+    this.setupUpdateButton("update.restart." +
+                           (this.isMajor ? "upgradeButton" : "restartButton"));
+    return;
+  }
+
   if (this.isDownloading) {
     this.startDownload();
     return;
   }
 
   if (this.updateEnabled && this.updateAuto) {
     this.selectPanel("checkingForUpdates");
     this.isChecking = true;
@@ -146,16 +152,26 @@ appUpdater.prototype =
       return this.update.state == "pending" || 
              this.update.state == "pending-service";
     }
     return this.um.activeUpdate &&
            (this.um.activeUpdate.state == "pending" ||
             this.um.activeUpdate.state == "pending-service");
   },
 
+  // true when there is an update already installed in the background.
+  get isApplied() {
+    if (this.update)
+      return this.update.state == "applied" ||
+             this.update.state == "applied-service";
+    return this.um.activeUpdate &&
+           (this.um.activeUpdate.state == "applied" ||
+            this.um.activeUpdate.state == "applied-service");
+  },
+
   // true when there is an update download in progress.
   get isDownloading() {
     if (this.update)
       return this.update.state == "downloading";
     return this.um.activeUpdate &&
            this.um.activeUpdate.state == "downloading";
   },
 
@@ -176,16 +192,22 @@ appUpdater.prototype =
   get updateEnabled() {
     try {
       return Services.prefs.getBoolPref("app.update.enabled");
     }
     catch (e) { }
     return true; // Firefox default is true
   },
 
+  // true when updating in background is enabled.
+  get backgroundUpdateEnabled() {
+    return this.updateEnabled &&
+           Services.prefs.getBoolPref("app.update.stage.enabled");
+  },
+
   // true when updating is automatic.
   get updateAuto() {
     try {
       return Services.prefs.getBoolPref("app.update.auto");
     }
     catch (e) { }
     return true; // Firefox default is true
   },
@@ -215,17 +237,17 @@ appUpdater.prototype =
         document.commandDispatcher.focusedElement == this.updateBtn)
       this.updateBtn.focus();
   },
 
   /**
    * Handles oncommand for the update button.
    */
   buttonOnCommand: function() {
-    if (this.isPending) {
+    if (this.isPending || this.isApplied) {
       // Notify all windows that an application quit has been requested.
       let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"].
                        createInstance(Components.interfaces.nsISupportsPRBool);
       Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
 
       // Something aborted the quit process.
       if (cancelQuit.data)
         return;
@@ -500,19 +522,44 @@ appUpdater.prototype =
       // Verification failed for a partial patch, complete patch is now
       // downloading so return early and do NOT remove the download listener!
       break;
     case Components.results.NS_BINDING_ABORTED:
       // Do not remove UI listener since the user may resume downloading again.
       break;
     case Components.results.NS_OK:
       this.removeDownloadListener();
-      this.selectPanel("updateButtonBox");
-      this.setupUpdateButton("update.restart." +
-                             (this.isMajor ? "upgradeButton" : "updateButton"));
+      if (this.backgroundUpdateEnabled) {
+        this.selectPanel("applying");
+        let update = this.um.activeUpdate;
+        let self = this;
+        let timer = Components.classes["@mozilla.org/timer;1"]
+                              .createInstance(Components.interfaces.nsITimer);
+        timer.initWithCallback(function () {
+          // Update the UI when the background updater is finished
+          let status = update.state;
+          if (status == "applied" || status == "applied-service") {
+            self.selectPanel("updateButtonBox");
+            self.setupUpdateButton("update.restart." +
+                                   (self.isMajor ? "upgradeButton" : "restartButton"));
+            timer.cancel();
+            timer = null;
+          } else if (status == "failed") {
+            // Background update has failed, let's show the UI responsible for
+            // prompting the user to update manually.
+            self.selectPanel("downloadFailed");
+            timer.cancel();
+            timer = null;
+          }
+        }, 500, timer.TYPE_REPEATING_SLACK);
+      } else {
+        this.selectPanel("updateButtonBox");
+        this.setupUpdateButton("update.restart." +
+                               (this.isMajor ? "upgradeButton" : "updateButton"));
+      }
       break;
     default:
       this.removeDownloadListener();
       this.selectPanel("downloadFailed");
       break;
     }
 
   },
--- a/browser/base/content/aboutDialog.xul
+++ b/browser/base/content/aboutDialog.xul
@@ -59,16 +59,19 @@
                 <image class="update-throbber"/><label>&update.checkingForUpdates;</label>
               </hbox>
               <hbox id="checkingAddonCompat" align="center">
                 <image class="update-throbber"/><label>&update.checkingAddonCompat;</label>
               </hbox>
               <hbox id="downloading" align="center">
                 <image class="update-throbber"/><label>&update.downloading.start;</label><label id="downloadStatus"/><label>&update.downloading.end;</label>
               </hbox>
+              <hbox id="applying" align="center">
+                <image class="update-throbber"/><label>&update.applying;</label>
+              </hbox>
               <hbox id="downloadFailed" align="center">
                 <label>&update.failed.start;</label><label id="failedLink" class="text-link">&update.failed.linkText;</label><label>&update.failed.end;</label>
               </hbox>
               <hbox id="adminDisabled" align="center">
                 <label>&update.adminDisabled;</label>
               </hbox>
               <hbox id="noUpdatesFound" align="center">
                 <label>&update.noUpdatesFound;</label>
--- a/browser/components/test/browser_bug538331.js
+++ b/browser/components/test/browser_bug538331.js
@@ -407,19 +407,20 @@ function reloadUpdateManagerData()
 function writeUpdatesToXMLFile(aText)
 {
   const PERMS_FILE = 0644;
 
   const MODE_WRONLY   = 0x02;
   const MODE_CREATE   = 0x08;
   const MODE_TRUNCATE = 0x20;
 
+  const kIsWin = (navigator.platform.indexOf("Win") == 0);
   let file = Cc["@mozilla.org/file/directory_service;1"].
              getService(Ci.nsIProperties).
-             get("XCurProcD", Ci.nsIFile);
+             get(kIsWin ? "UpdRootD" : "XCurProcD", Ci.nsIFile);
   file.append("updates.xml");
   let fos = Cc["@mozilla.org/network/file-output-stream;1"].
             createInstance(Ci.nsIFileOutputStream);
   if (!file.exists()) {
     file.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
   }
   fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, PERMS_FILE, 0);
   fos.write(aText, aText.length);
--- a/browser/locales/en-US/chrome/browser/aboutDialog.dtd
+++ b/browser/locales/en-US/chrome/browser/aboutDialog.dtd
@@ -64,13 +64,15 @@
 <!-- LOCALIZATION NOTE (update.downloading.start,update.downloading.end): update.downloading.start and 
      update.downloading.end all go into one line, with the amount downloaded inserted in between. As this
      is all in one line, try to make the localized text short (see bug 596813 for screenshots). The — is
      the "em dash" (long dash).
      example: Downloading update — 111 KB of 13 MB -->
 <!ENTITY update.downloading.start   "Downloading update — ">
 <!ENTITY update.downloading.end     "">
 
+<!ENTITY update.applying            "Applying update…">
+
 <!-- LOCALIZATION NOTE (channel.description.start,channel.description.end): channel.description.start and
      channel.description.end create one sentence, with the current channel label inserted in between.
      example: You are currently on the _Stable_ update channel. -->
 <!ENTITY channel.description.start  "You are currently on the ">
 <!ENTITY channel.description.end    " update channel. ">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -151,16 +151,18 @@ sanitizeSelectedWarning=All selected ite
 update.checkInsideButton.label=Check for Updates
 update.checkInsideButton.accesskey=C
 update.resumeButton.label=Resume Downloading %S…
 update.resumeButton.accesskey=D
 update.openUpdateUI.applyButton.label=Apply Update…
 update.openUpdateUI.applyButton.accesskey=A
 update.restart.updateButton.label=Restart to Update
 update.restart.updateButton.accesskey=R
+update.restart.restartButton.label=Update & Restart
+update.restart.restartButton.accesskey=R
 update.openUpdateUI.upgradeButton.label=Upgrade Now…
 update.openUpdateUI.upgradeButton.accesskey=U
 update.restart.upgradeButton.label=Upgrade Now
 update.restart.upgradeButton.accesskey=U
 
 # RSS Pretty Print
 feedShowFeedNew=Subscribe to '%S'…
 
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -326,16 +326,17 @@ user_pref("javascript.options.jit_harden
 user_pref("gfx.color_management.force_srgb", true);
 user_pref("network.manage-offline-status", false);
 user_pref("test.mousescroll", true);
 user_pref("security.default_personal_cert", "Select Automatically"); // Need to client auth test be w/o any dialogs
 user_pref("network.http.prompt-temp-redirect", false);
 user_pref("media.cache_size", 100);
 user_pref("security.warn_viewing_mixed", false);
 user_pref("app.update.enabled", false);
+user_pref("app.update.stage.enabled", false);
 user_pref("browser.panorama.experienced_first_run", true); // Assume experienced
 user_pref("dom.w3c_touch_events.enabled", true);
 user_pref("toolkit.telemetry.prompted", 2);
 // Existing tests assume there is no font size inflation.
 user_pref("font.size.inflation.emPerLine", 0);
 user_pref("font.size.inflation.minTwips", 0);
 
 // Only load extensions from the application and user profile
--- a/js/xpconnect/shell/Makefile.in
+++ b/js/xpconnect/shell/Makefile.in
@@ -15,16 +15,26 @@ PROGRAM		= xpcshell$(BIN_SUFFIX)
 SDK_BINARY	= $(PROGRAM)
 
 CPPSRCS		= xpcshell.cpp
 
 ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 CMMSRCS += xpcshellMacUtils.mm
 endif
 
+ifeq (windows,$(MOZ_WIDGET_TOOLKIT))
+ifdef MOZ_APP_PROFILE
+DEFINES += -DMOZ_APP_PROFILE='"$(MOZ_APP_PROFILE)"'
+else
+DEFINES += -DMOZ_APP_VENDOR='"$(MOZ_APP_VENDOR)"'
+DEFINES += -DMOZ_APP_BASENAME='"$(MOZ_APP_BASENAME)"'
+DEFINES += -DMOZ_APP_NAME='"$(MOZ_APP_NAME)"'
+endif
+endif
+
 LIBS		= \
 		$(DIST)/lib/$(LIB_PREFIX)xpcomglue_s.$(LIB_SUFFIX) \
 		$(LIBXUL_LIBS) \
 		$(XPCOM_LIBS) \
 		$(NULL)
 
 ifdef JS_SHARED_LIBRARY
 LIBS +=	$(MOZ_JS_LIBS)
--- a/js/xpconnect/shell/xpcshell.cpp
+++ b/js/xpconnect/shell/xpcshell.cpp
@@ -40,21 +40,23 @@
 #include "nsMemory.h"
 #include "nsISupportsImpl.h"
 #include "nsIJSRuntimeService.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsIXPCSecurityManager.h"
 #include "nsJSPrincipals.h"
 #include "xpcpublic.h"
+#include "nsXULAppAPI.h"
 #ifdef XP_MACOSX
 #include "xpcshellMacUtils.h"
 #endif
 #ifdef XP_WIN
 #include <windows.h>
+#include <shlobj.h>
 #endif
 
 #ifdef ANDROID
 #include <android/log.h>
 #endif
 
 #include "nsIScriptSecurityManager.h"
 #include "nsIPrincipal.h"
@@ -84,19 +86,22 @@ public:
     NS_DECL_NSIDIRECTORYSERVICEPROVIDER
     NS_DECL_NSIDIRECTORYSERVICEPROVIDER2
 
     XPCShellDirProvider() { }
     ~XPCShellDirProvider() { }
 
     bool SetGREDir(const char *dir);
     void ClearGREDir() { mGREDir = nsnull; }
+    void SetAppFile(nsILocalFile *appFile);
+    void ClearAppFile() { mAppFile = nsnull; }
 
 private:
     nsCOMPtr<nsILocalFile> mGREDir;
+    nsCOMPtr<nsILocalFile> mAppFile;
 };
 
 /***************************************************************************/
 
 #ifdef JS_THREADSAFE
 #define DoBeginRequest(cx) JS_BeginRequest((cx))
 #define DoEndRequest(cx)   JS_EndRequest((cx))
 #else
@@ -1709,16 +1714,18 @@ main(int argc, char **argv, char **envp)
     rv = appFile->GetParent(getter_AddRefs(appDir));
     if (NS_FAILED(rv)) {
         printf("Couldn't get application directory.\n");
         return 1;
     }
 
     XPCShellDirProvider dirprovider;
 
+    dirprovider.SetAppFile(appFile);
+
     if (argc > 1 && !strcmp(argv[1], "-g")) {
         if (argc < 3)
             return usage();
 
         if (!dirprovider.SetGREDir(argv[2])) {
             printf("SetGREDir failed.\n");
             return 1;
         }
@@ -1967,16 +1974,17 @@ main(int argc, char **argv, char **envp)
     JSContext* bogusCX;
     bogus->Peek(&bogusCX);
     bogus = nsnull;
 #endif
 
     appDir = nsnull;
     appFile = nsnull;
     dirprovider.ClearGREDir();
+    dirprovider.ClearAppFile();
 
 #ifdef MOZ_CRASHREPORTER
     // Shut down the crashreporter service to prevent leaking some strings it holds.
     if (crashReporter) {
         crashReporter->SetEnabled(false);
         crashReporter = nsnull;
     }
 #endif
@@ -1992,16 +2000,22 @@ main(int argc, char **argv, char **envp)
 
 bool
 XPCShellDirProvider::SetGREDir(const char *dir)
 {
     nsresult rv = XRE_GetFileFromPath(dir, getter_AddRefs(mGREDir));
     return NS_SUCCEEDED(rv);
 }
 
+void
+XPCShellDirProvider::SetAppFile(nsILocalFile* appFile)
+{
+    mAppFile = appFile;
+}
+
 NS_IMETHODIMP_(nsrefcnt)
 XPCShellDirProvider::AddRef()
 {
     return 2;
 }
 
 NS_IMETHODIMP_(nsrefcnt)
 XPCShellDirProvider::Release()
@@ -2015,25 +2029,59 @@ NS_IMPL_QUERY_INTERFACE2(XPCShellDirProv
 
 NS_IMETHODIMP
 XPCShellDirProvider::GetFile(const char *prop, bool *persistent,
                              nsIFile* *result)
 {
     if (mGREDir && !strcmp(prop, NS_GRE_DIR)) {
         *persistent = true;
         return mGREDir->Clone(result);
+    } else if (mAppFile && !strcmp(prop, XRE_EXECUTABLE_FILE)) {
+        *persistent = true;
+        return mAppFile->Clone(result);
     } else if (mGREDir && !strcmp(prop, NS_APP_PREF_DEFAULTS_50_DIR)) {
         nsCOMPtr<nsIFile> file;
         *persistent = true;
         if (NS_FAILED(mGREDir->Clone(getter_AddRefs(file))) ||
             NS_FAILED(file->AppendNative(NS_LITERAL_CSTRING("defaults"))) ||
             NS_FAILED(file->AppendNative(NS_LITERAL_CSTRING("pref"))))
             return NS_ERROR_FAILURE;
         NS_ADDREF(*result = file);
         return NS_OK;
+    } else if (mAppFile && !strcmp(prop, XRE_UPDATE_ROOT_DIR)) {
+        // For xpcshell, we pretend that the update root directory is always
+        // the same as the GRE directory, except for Windows, where we immitate
+        // the algorithm defined in nsXREDirProvider::GetUpdateRootDir.
+        *persistent = true;
+#ifdef XP_WIN
+        char appData[MAX_PATH] = {'\0'};
+        char path[MAX_PATH] = {'\0'};
+        LPITEMIDLIST pItemIDList;
+        if (FAILED(SHGetSpecialFolderLocation(NULL, CSIDL_LOCAL_APPDATA, &pItemIDList)) ||
+            FAILED(SHGetPathFromIDListA(pItemIDList, appData))) {
+            return NS_ERROR_FAILURE;
+        }
+#ifdef MOZ_APP_PROFILE
+        sprintf(path, "%s\\%s", appData, MOZ_APP_PROFILE);
+#else
+        sprintf(path, "%s\\%s\\%s\\%s", appData, MOZ_APP_VENDOR, MOZ_APP_BASENAME, MOZ_APP_NAME);
+#endif
+        nsAutoString pathName;
+        pathName.AssignASCII(path);
+        nsCOMPtr<nsILocalFile> localFile;
+        nsresult rv = NS_NewLocalFile(pathName, true, getter_AddRefs(localFile));
+        if (NS_FAILED(rv)) {
+            return rv;
+        }
+        return localFile->Clone(result);
+#else
+        // Fail on non-Windows platforms, the caller is supposed to fal back on
+        // the app dir.
+        return NS_ERROR_FAILURE;
+#endif
     }
 
     return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 XPCShellDirProvider::GetFiles(const char *prop, nsISimpleEnumerator* *result)
 {
--- a/toolkit/components/build/Makefile.in
+++ b/toolkit/components/build/Makefile.in
@@ -33,16 +33,17 @@ LOCAL_INCLUDES = \
   -I$(srcdir)/../downloads \
   -I$(srcdir)/../feeds \
   -I$(srcdir)/../find \
   -I$(srcdir)/../intl \
   -I$(srcdir)/../startup \
   -I$(srcdir)/../statusfilter \
   -I$(srcdir)/../typeaheadfind \
   -I$(srcdir)/../url-classifier \
+  -I$(srcdir)/../../xre \
   $(NULL)
 
 ifdef ALERTS_SERVICE
 LOCAL_INCLUDES += \
   -I$(srcdir)/../alerts \
   $(NULL)
 endif
 
--- a/toolkit/components/build/nsToolkitCompsCID.h
+++ b/toolkit/components/build/nsToolkitCompsCID.h
@@ -82,16 +82,21 @@
   "@mozilla.org/browser/favicon-service;1"
 
 #define NS_PLACESIMPORTEXPORTSERVICE_CONTRACTID \
   "@mozilla.org/browser/places/import-export-service;1"
 
 #define NS_APPSTARTUP_CONTRACTID \
   "@mozilla.org/toolkit/app-startup;1"
 
+#if defined(MOZ_UPDATER) && !defined(ANDROID)
+#define NS_UPDATEPROCESSOR_CONTRACTID \
+  "@mozilla.org/updates/update-processor;1"
+#endif
+
 /////////////////////////////////////////////////////////////////////////////
 
 // {A0CCAAF8-09DA-44D8-B250-9AC3E93C8117}
 #define NS_ALERTSSERVICE_CID \
 { 0xa0ccaaf8, 0x9da, 0x44d8, { 0xb2, 0x50, 0x9a, 0xc3, 0xe9, 0x3c, 0x81, 0x17 } }
 
 // {84E11F80-CA55-11DD-AD8B-0800200C9A66}
 #define NS_SYSTEMALERTSSERVICE_CID \
@@ -157,8 +162,13 @@
 { 0x9de95a0c, 0x39a4, 0x4d64, {0x9a, 0x53, 0x17, 0x94, 0x0d, 0xd7, 0xca, 0xbb}}
 
 #define NS_FAVICONSERVICE_CID \
 { 0x984e3259, 0x9266, 0x49cf, { 0xb6, 0x05, 0x60, 0xb0, 0x22, 0xa0, 0x07, 0x56 } }
 
 // {6fb0c970-e1b1-11db-8314-0800200c9a66}
 #define NS_PLACESIMPORTEXPORTSERVICE_CID \
 { 0x6fb0c970, 0xe1b1, 0x11db, { 0x83, 0x14, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 } }
+
+#if defined(MOZ_UPDATER) && !defined(ANDROID)
+#define NS_UPDATEPROCESSOR_CID \
+{ 0xf3dcf644, 0x79e8, 0x4f59, { 0xa1, 0xbb, 0x87, 0x84, 0x54, 0x48, 0x8e, 0xf9 } }
+#endif
--- a/toolkit/components/build/nsToolkitCompsModule.cpp
+++ b/toolkit/components/build/nsToolkitCompsModule.cpp
@@ -2,16 +2,19 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/ModuleUtils.h"
 #include "nsAppStartup.h"
 #include "nsUserInfo.h"
 #include "nsToolkitCompsCID.h"
 #include "nsFindService.h"
+#if defined(MOZ_UPDATER) && !defined(ANDROID)
+#include "nsUpdateDriver.h"
+#endif
 
 #if defined(XP_WIN) && !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
 #include "nsParentalControlsServiceWin.h"
 #endif
 
 #ifdef ALERTS_SERVICE
 #include "nsAlertsService.h"
 #endif
@@ -73,16 +76,19 @@ nsUrlClassifierDBServiceConstructor(nsIS
     rv = inst->QueryInterface(aIID, aResult);
     NS_RELEASE(inst);
 
     return rv;
 }
 #endif
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsBrowserStatusFilter)
+#if defined(MOZ_UPDATER) && !defined(ANDROID)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsUpdateProcessor)
+#endif
 
 NS_DEFINE_NAMED_CID(NS_TOOLKIT_APPSTARTUP_CID);
 NS_DEFINE_NAMED_CID(NS_USERINFO_CID);
 #ifdef ALERTS_SERVICE
 NS_DEFINE_NAMED_CID(NS_ALERTSSERVICE_CID);
 #endif
 #if defined(XP_WIN) && !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
 NS_DEFINE_NAMED_CID(NS_PARENTALCONTROLSSERVICE_CID);
@@ -94,16 +100,19 @@ NS_DEFINE_NAMED_CID(NS_TYPEAHEADFIND_CID
 #ifdef MOZ_URL_CLASSIFIER
 NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERPREFIXSET_CID);
 NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERDBSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERSTREAMUPDATER_CID);
 NS_DEFINE_NAMED_CID(NS_URLCLASSIFIERUTILS_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_BROWSERSTATUSFILTER_CID);
 NS_DEFINE_NAMED_CID(NS_CHARSETMENU_CID);
+#if defined(MOZ_UPDATER) && !defined(ANDROID)
+NS_DEFINE_NAMED_CID(NS_UPDATEPROCESSOR_CID);
+#endif
 
 static const mozilla::Module::CIDEntry kToolkitCIDs[] = {
   { &kNS_TOOLKIT_APPSTARTUP_CID, false, NULL, nsAppStartupConstructor },
   { &kNS_USERINFO_CID, false, NULL, nsUserInfoConstructor },
 #ifdef ALERTS_SERVICE
   { &kNS_ALERTSSERVICE_CID, false, NULL, nsAlertsServiceConstructor },
 #endif
 #if defined(XP_WIN) && !defined(MOZ_DISABLE_PARENTAL_CONTROLS)
@@ -116,16 +125,19 @@ static const mozilla::Module::CIDEntry k
 #ifdef MOZ_URL_CLASSIFIER
   { &kNS_URLCLASSIFIERPREFIXSET_CID, false, NULL, nsUrlClassifierPrefixSetConstructor },
   { &kNS_URLCLASSIFIERDBSERVICE_CID, false, NULL, nsUrlClassifierDBServiceConstructor },
   { &kNS_URLCLASSIFIERSTREAMUPDATER_CID, false, NULL, nsUrlClassifierStreamUpdaterConstructor },
   { &kNS_URLCLASSIFIERUTILS_CID, false, NULL, nsUrlClassifierUtilsConstructor },
 #endif
   { &kNS_BROWSERSTATUSFILTER_CID, false, NULL, nsBrowserStatusFilterConstructor },
   { &kNS_CHARSETMENU_CID, false, NULL, NS_NewCharsetMenu },
+#if defined(MOZ_UPDATER) && !defined(ANDROID)
+  { &kNS_UPDATEPROCESSOR_CID, false, NULL, nsUpdateProcessorConstructor },
+#endif
   { NULL }
 };
 
 static const mozilla::Module::ContractIDEntry kToolkitContracts[] = {
   { NS_APPSTARTUP_CONTRACTID, &kNS_TOOLKIT_APPSTARTUP_CID },
   { NS_USERINFO_CONTRACTID, &kNS_USERINFO_CID },
 #ifdef ALERTS_SERVICE
   { NS_ALERTSERVICE_CONTRACTID, &kNS_ALERTSSERVICE_CID },
@@ -141,16 +153,19 @@ static const mozilla::Module::ContractID
   { NS_URLCLASSIFIERPREFIXSET_CONTRACTID, &kNS_URLCLASSIFIERPREFIXSET_CID },
   { NS_URLCLASSIFIERDBSERVICE_CONTRACTID, &kNS_URLCLASSIFIERDBSERVICE_CID },
   { NS_URICLASSIFIERSERVICE_CONTRACTID, &kNS_URLCLASSIFIERDBSERVICE_CID },
   { NS_URLCLASSIFIERSTREAMUPDATER_CONTRACTID, &kNS_URLCLASSIFIERSTREAMUPDATER_CID },
   { NS_URLCLASSIFIERUTILS_CONTRACTID, &kNS_URLCLASSIFIERUTILS_CID },
 #endif
   { NS_BROWSERSTATUSFILTER_CONTRACTID, &kNS_BROWSERSTATUSFILTER_CID },
   { NS_RDF_DATASOURCE_CONTRACTID_PREFIX NS_CHARSETMENU_PID, &kNS_CHARSETMENU_CID },
+#if defined(MOZ_UPDATER) && !defined(ANDROID)
+  { NS_UPDATEPROCESSOR_CONTRACTID, &kNS_UPDATEPROCESSOR_CID },
+#endif
   { NULL }
 };
 
 static const mozilla::Module kToolkitModule = {
   mozilla::Module::kVersion,
   kToolkitCIDs,
   kToolkitContracts
 };
--- a/toolkit/components/maintenanceservice/workmonitor.cpp
+++ b/toolkit/components/maintenanceservice/workmonitor.cpp
@@ -77,16 +77,44 @@ IsStatusApplying(LPCWSTR updateDirPath, 
   const char kApplying[] = "applying";
   isApplying = strncmp(buf, kApplying, 
                        sizeof(kApplying) - 1) == 0;
   return TRUE;
 }
 
 
 /**
+ * Gets the installation directory from the arguments passed to updater.exe.
+ *
+ * @param argcTmp    The argc value normally sent to updater.exe
+ * @param argvTmp    The argv value normally sent to updater.exe
+ * @param aResultDir Buffer to hold the installation directory.
+ */
+static BOOL
+GetInstallationDir(int argcTmp, LPWSTR *argvTmp, WCHAR aResultDir[MAX_PATH])
+{
+  if (argcTmp < 2) {
+    return FALSE;
+  }
+  wcscpy(aResultDir, argvTmp[2]);
+  WCHAR* backSlash = wcsrchr(aResultDir, L'\\');
+  // Make sure that the path does not include trailing backslashes
+  if (backSlash && (backSlash[1] == L'\0')) {
+    *backSlash = L'\0';
+  }
+  // PID will be set to -1 if we're supposed to perform a background update.
+  bool backgroundUpdate = (argcTmp == 4 && !wcscmp(argvTmp[3], L"-1"));
+  bool replaceRequest = (argcTmp >= 4 && wcsstr(argvTmp[3], L"/replace"));
+  if (backgroundUpdate || replaceRequest) {
+    return PathRemoveFileSpecW(aResultDir);
+  }
+  return TRUE;
+}
+
+/**
  * Runs an update process as the service using the SYSTEM account.
  *
  * @param  argc           The number of arguments in argv
  * @param  argv           The arguments normally passed to updater.exe
  *                        argv[0] must be the path to updater.exe
  * @param  processStarted Set to TRUE if the process was started.
  * @return TRUE if the update process was run had a return code of 0.
  */
@@ -251,16 +279,26 @@ ProcessSoftwareUpdateCommand(DWORD argc,
         !WriteStatusFailure(argv[1], 
                             SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS)) {
       LOG(("Could not write update.status service update failure."
            "Last error: %d\n", GetLastError()));
     }
     return FALSE;
   }
 
+  WCHAR installDir[MAX_PATH] = {L'\0'};
+  if (!GetInstallationDir(argc, argv, installDir)) {
+    LOG(("Could not get the installation directory"));
+    if (!WriteStatusFailure(argv[1],
+                            SERVICE_INSTALLDIR_ERROR)) {
+      LOG(("Could not write update.status for GetInstallationDir failure.\n"));
+    }
+    return FALSE;
+  }
+
   // Make sure the path to the updater to use for the update is local.
   // We do this check to make sure that file locking is available for
   // race condition security checks.
   BOOL isLocal = FALSE;
   if (!IsLocalFile(argv[0], isLocal) || !isLocal) {
     LOG(("Filesystem in path %ls is not supported"
          "Last error: %d\n", argv[0], GetLastError()));
     if (!WriteStatusFailure(argv[1], 
@@ -281,19 +319,19 @@ ProcessSoftwareUpdateCommand(DWORD argc,
       LOG(("Could not write update.status service update failure."
            "Last error: %d\n", GetLastError()));
     }
     return FALSE;
   }
 
   // Verify that the updater.exe that we are executing is the same
   // as the one in the installation directory which we are updating.
-  // The installation dir that we are installing to is argv[2].
-  WCHAR installDirUpdater[MAX_PATH + 1];
-  wcsncpy(installDirUpdater, argv[2], MAX_PATH);
+  // The installation dir that we are installing to is installDir.
+  WCHAR installDirUpdater[MAX_PATH + 1] = {L'\0'};
+  wcsncpy(installDirUpdater, installDir, MAX_PATH);
   if (!PathAppendSafe(installDirUpdater, L"updater.exe")) {
     LOG(("Install directory updater could not be determined.\n"));
     result = FALSE;
   }
 
   BOOL updaterIsCorrect;
   if (result && !VerifySameFiles(argv[0], installDirUpdater, 
                                  updaterIsCorrect)) {
@@ -351,17 +389,17 @@ ProcessSoftwareUpdateCommand(DWORD argc,
       LOG(("Could not write update.status no updater identity.\n"));
     }
     return TRUE;
   }
 
   // Check for updater.exe sign problems
   BOOL updaterSignProblem = FALSE;
 #ifndef DISABLE_UPDATER_AUTHENTICODE_CHECK
-  updaterSignProblem = !DoesBinaryMatchAllowedCertificates(argv[2],
+  updaterSignProblem = !DoesBinaryMatchAllowedCertificates(installDir,
                                                            argv[0]);
 #endif
 
   // Only proceed with the update if we have no signing problems
   if (!updaterSignProblem) {
     BOOL updateProcessWasStarted = FALSE;
     if (StartUpdateProcess(argc, argv,
                            updateProcessWasStarted)) {
--- a/toolkit/components/maintenanceservice/workmonitor.h
+++ b/toolkit/components/maintenanceservice/workmonitor.h
@@ -1,6 +1,5 @@
 /* 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/. */
 
 BOOL ExecuteServiceCommand(int argc, LPWSTR *argv);
-BOOL GetUpdateDirectoryPath(LPWSTR);
--- a/toolkit/locales/en-US/chrome/mozapps/update/updates.properties
+++ b/toolkit/locales/en-US/chrome/mozapps/update/updates.properties
@@ -64,16 +64,18 @@ restartNowButton.accesskey=R
 statusSucceededFormat=Installed on: %S
 
 statusFailed=Install Failed
 pauseButtonPause=Pause
 pauseButtonResume=Resume
 hideButton=Hide
 hideButton.accesskey=H
 
+applyingUpdate=Applying update…
+
 updatesfound_minor.title=Update Available
 updatesfound_major.title=New Version Available
 
 installSuccess=The Update was successfully installed
 installPending=Install Pending
 patchApplyFailure=The Update could not be installed (patch apply failed)
 
 # LOCALIZATION NOTE: When present %S is the update name provided by the remote
--- a/toolkit/mozapps/update/Makefile.in
+++ b/toolkit/mozapps/update/Makefile.in
@@ -8,16 +8,20 @@ srcdir    = @srcdir@
 VPATH     = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE = update
 
 XPIDLSRCS = nsIUpdateTimerManager.idl
 
+DEFINES += \
+	-DMOZ_MACBUNDLE_NAME='"$(MOZ_MACBUNDLE_NAME)"' \
+	$(NULL)
+
 EXTRA_PP_COMPONENTS = nsUpdateTimerManager.js nsUpdateTimerManager.manifest
 
 ifdef MOZ_UPDATER
 ifneq (android,$(MOZ_WIDGET_TOOLKIT))
 DIRS += common
 DIRS += updater
 endif
 
--- a/toolkit/mozapps/update/common/errors.h
+++ b/toolkit/mozapps/update/common/errors.h
@@ -34,27 +34,30 @@
 #define CERT_LOAD_ERROR 17
 #define CERT_HANDLING_ERROR 18
 #define CERT_VERIFY_ERROR 19
 #define ARCHIVE_NOT_OPEN 20
 #define COULD_NOT_READ_PRODUCT_INFO_BLOCK_ERROR 21
 #define MAR_CHANNEL_MISMATCH_ERROR 22
 #define VERSION_DOWNGRADE_ERROR 23
 
-// Error codes 24-32 are related to the maintenance service
+// Error codes 24-34 are related to the maintenance service
 // and so are Windows only
 #define SERVICE_UPDATER_COULD_NOT_BE_STARTED 24
 #define SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS 25
 #define SERVICE_UPDATER_SIGN_ERROR 26
 #define SERVICE_UPDATER_COMPARE_ERROR 27
 #define SERVICE_UPDATER_IDENTITY_ERROR 28
 #define SERVICE_STILL_APPLYING_ON_SUCCESS 29
 #define SERVICE_STILL_APPLYING_ON_FAILURE 30
 #define SERVICE_UPDATER_NOT_FIXED_DRIVE 31
 #define SERVICE_COULD_NOT_LOCK_UPDATER 32
+#define SERVICE_INSTALLDIR_ERROR 33
+
+#define NO_INSTALLDIR_ERROR 34
 
 // The following error codes are only used by updater.exe
 // when a fallback key exists and XPCShell tests are being run.
 #define FALLBACKKEY_UNKNOWN_ERROR 100
 #define FALLBACKKEY_REGPATH_ERROR 101
 #define FALLBACKKEY_NOKEY_ERROR 102
 #define FALLBACKKEY_SERVICE_NO_STOP_ERROR 103
 #define FALLBACKKEY_LAUNCH_ERROR 104
--- a/toolkit/mozapps/update/common/updatedefines.h
+++ b/toolkit/mozapps/update/common/updatedefines.h
@@ -19,98 +19,131 @@
 #  define MAXPATHLEN CCHMAXPATH
 # else
 #  define MAXPATHLEN 1024
 # endif
 #endif
 
 #if defined(XP_WIN)
 # include <windows.h>
+# include <shlwapi.h>
 # include <direct.h>
 # include <io.h>
+# include <stdio.h>
+# include <stdarg.h>
 
 # define F_OK 00
 # define W_OK 02
 # define R_OK 04
 # define S_ISDIR(s) (((s) & _S_IFMT) == _S_IFDIR)
 # define S_ISREG(s) (((s) & _S_IFMT) == _S_IFREG)
 
 # define access _access
 
 # define putenv _putenv
 # define stat _stat
 # define DELETE_DIR L"tobedeleted"
 # define CALLBACK_BACKUP_EXT L".moz-callback"
 
 # define LOG_S "%S"
 # define NS_T(str) L ## str
+# define NS_SLASH NS_T('\\')
 // On Windows, _snprintf and _snwprintf don't guarantee null termination. These
 // macros always leave room in the buffer for null termination and set the end
 // of the buffer to null in case the string is larger than the buffer. Having
 // multiple nulls in a string is fine and this approach is simpler (possibly
 // faster) than calculating the string length to place the null terminator and
 // truncates the string as _snprintf and _snwprintf do on other platforms.
-# define snprintf(dest, count, fmt, ...) \
-  PR_BEGIN_MACRO \
-  int _count = count - 1; \
-  _snprintf(dest, _count, fmt, ##__VA_ARGS__); \
-  dest[_count] = '\0'; \
-  PR_END_MACRO
-#define NS_tsnprintf(dest, count, fmt, ...) \
-  PR_BEGIN_MACRO \
-  int _count = count - 1; \
-  _snwprintf(dest, _count, fmt, ##__VA_ARGS__); \
-  dest[_count] = L'\0'; \
-  PR_END_MACRO
+static int mysnprintf(char* dest, size_t count, const char* fmt, ...)
+{
+  size_t _count = count - 1;
+  va_list varargs;
+  va_start(varargs, fmt);
+  int result = _vsnprintf(dest, count - 1, fmt, varargs);
+  va_end(varargs);
+  dest[_count] = '\0';
+  return result;
+}
+#define snprintf mysnprintf
+static int mywcsprintf(WCHAR* dest, size_t count, const WCHAR* fmt, ...)
+{
+  size_t _count = count - 1;
+  va_list varargs;
+  va_start(varargs, fmt);
+  int result = _vsnwprintf(dest, count - 1, fmt, varargs);
+  va_end(varargs);
+  dest[_count] = L'\0';
+  return result;
+}
+#define NS_tsnprintf mywcsprintf
 # define NS_taccess _waccess
 # define NS_tchdir _wchdir
 # define NS_tchmod _wchmod
 # define NS_tfopen _wfopen
 # define NS_tmkdir(path, perms) _wmkdir(path)
 # define NS_tremove _wremove
 // _wrename is used to avoid the link tracking service.
 # define NS_trename _wrename
 # define NS_trmdir _wrmdir
 # define NS_tstat _wstat
+# define NS_tlstat _wstat // No symlinks on Windows
 # define NS_tstrcat wcscat
 # define NS_tstrcmp wcscmp
+# define NS_tstricmp wcsicmp
 # define NS_tstrcpy wcscpy
+# define NS_tstrncpy wcsncpy
 # define NS_tstrlen wcslen
+# define NS_tstrchr wcschr
 # define NS_tstrrchr wcsrchr
 # define NS_tstrstr wcsstr
+# include "win_dirent.h"
+# define NS_tDIR DIR
+# define NS_tdirent dirent
+# define NS_topendir opendir
+# define NS_tclosedir closedir
+# define NS_treaddir readdir
 #else
 # include <sys/wait.h>
 # include <unistd.h>
 
 #ifdef SOLARIS
 # include <sys/stat.h>
-# include <dirent.h>
 #else
 # include <fts.h>
 #endif
+# include <dirent.h>
 
 #ifdef XP_MACOSX
 # include <sys/time.h>
 #endif
 
 # define LOG_S "%s"
 # define NS_T(str) str
+# define NS_SLASH NS_T('/')
 # define NS_tsnprintf snprintf
 # define NS_taccess access
 # define NS_tchdir chdir
 # define NS_tchmod chmod
 # define NS_tfopen fopen
 # define NS_tmkdir mkdir
 # define NS_tremove remove
 # define NS_trename rename
 # define NS_trmdir rmdir
 # define NS_tstat stat
+# define NS_tlstat lstat
 # define NS_tstrcat strcat
 # define NS_tstrcmp strcmp
+# define NS_tstricmp strcasecmp
 # define NS_tstrcpy strcpy
+# define NS_tstrncpy strncpy
 # define NS_tstrlen strlen
 # define NS_tstrrchr strrchr
 # define NS_tstrstr strstr
+# define NS_tDIR DIR
+# define NS_tdirent dirent
+# define NS_topendir opendir
+# define NS_tclosedir closedir
+# define NS_treaddir readdir
 #endif
 
 #define BACKUP_EXT NS_T(".moz-backup")
 
 #endif
--- a/toolkit/mozapps/update/common/updatelogging.cpp
+++ b/toolkit/mozapps/update/common/updatelogging.cpp
@@ -13,27 +13,35 @@
 #include <stdarg.h>
 
 #include "updatelogging.h"
 
 UpdateLog::UpdateLog() : logFP(NULL)
 {
 }
 
-void UpdateLog::Init(NS_tchar* sourcePath, const NS_tchar* fileName)
+void UpdateLog::Init(NS_tchar* sourcePath,
+                     const NS_tchar* fileName,
+                     const NS_tchar* alternateFileName,
+                     bool append)
 {
   if (logFP)
     return;
 
   this->sourcePath = sourcePath;
   NS_tchar logFile[MAXPATHLEN];
   NS_tsnprintf(logFile, sizeof(logFile)/sizeof(logFile[0]),
     NS_T("%s/%s"), sourcePath, fileName);
 
-  logFP = NS_tfopen(logFile, NS_T("w"));
+  if (alternateFileName && NS_taccess(logFile, F_OK)) {
+    NS_tsnprintf(logFile, sizeof(logFile)/sizeof(logFile[0]),
+      NS_T("%s/%s"), sourcePath, alternateFileName);
+  }
+
+  logFP = NS_tfopen(logFile, append ? NS_T("a") : NS_T("w"));
 }
 
 void UpdateLog::Finish()
 {
   if (!logFP)
     return;
 
   fclose(logFP);
--- a/toolkit/mozapps/update/common/updatelogging.h
+++ b/toolkit/mozapps/update/common/updatelogging.h
@@ -12,17 +12,18 @@ class UpdateLog
 {
 public:
   static UpdateLog & GetPrimaryLog() 
   {
     static UpdateLog primaryLog;
     return primaryLog;
   }
 
-  void Init(NS_tchar* sourcePath, const NS_tchar* fileName);
+  void Init(NS_tchar* sourcePath, const NS_tchar* fileName,
+            const NS_tchar* alternateFileName, bool append);
   void Finish();
   void Flush();
   void Printf(const char *fmt, ... );
 
   ~UpdateLog()
   {
     Finish();
   }
@@ -30,13 +31,15 @@ public:
 protected:
   UpdateLog();
   FILE *logFP;
   NS_tchar* sourcePath;
 };
 
 #define LOG(args) UpdateLog::GetPrimaryLog().Printf args
 #define LogInit(PATHNAME_, FILENAME_) \
-  UpdateLog::GetPrimaryLog().Init(PATHNAME_, FILENAME_)
+  UpdateLog::GetPrimaryLog().Init(PATHNAME_, FILENAME_, 0, false)
+#define LogInitAppend(PATHNAME_, FILENAME_, ALTERNATE_) \
+  UpdateLog::GetPrimaryLog().Init(PATHNAME_, FILENAME_, ALTERNATE_, true)
 #define LogFinish() UpdateLog::GetPrimaryLog().Finish()
 #define LogFlush() UpdateLog::GetPrimaryLog().Flush()
 
 #endif
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/common/win_dirent.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef WINDIRENT_H__
+#define WINDIRENT_H__
+
+#ifndef XP_WIN
+#error This library should only be used on Windows
+#endif
+
+#include <windows.h>
+
+struct DIR {
+  explicit DIR(const WCHAR* path);
+  ~DIR();
+  HANDLE findHandle;
+  WCHAR name[MAX_PATH];
+};
+
+struct dirent {
+  dirent();
+  WCHAR d_name[MAX_PATH];
+};
+
+DIR* opendir(const WCHAR* path);
+int closedir(DIR* dir);
+dirent* readdir(DIR* dir);
+
+#endif  // WINDIRENT_H__
--- a/toolkit/mozapps/update/content/updates.js
+++ b/toolkit/mozapps/update/content/updates.js
@@ -10,16 +10,17 @@ Components.utils.import("resource://gre/
 // Firefox's macBrowserOverlay.xul includes scripts that define Cc, Ci, and Cr
 // so we have to use different names.
 const CoC = Components.classes;
 const CoI = Components.interfaces;
 const CoR = Components.results;
 
 const XMLNS_XUL               = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
+const PREF_APP_UPDATE_BACKGROUND         = "app.update.stage.enabled";
 const PREF_APP_UPDATE_BACKGROUNDERRORS   = "app.update.backgroundErrors";
 const PREF_APP_UPDATE_BILLBOARD_TEST_URL = "app.update.billboard.test_url";
 const PREF_APP_UPDATE_CERT_ERRORS        = "app.update.cert.errors";
 const PREF_APP_UPDATE_ENABLED            = "app.update.enabled";
 const PREF_APP_UPDATE_LOG                = "app.update.log";
 const PREF_APP_UPDATE_MANUAL_URL         = "app.update.url.manual";
 const PREF_APP_UPDATE_NEVER_BRANCH       = "app.update.never.";
 const PREF_APP_UPDATE_TEST_LOOP          = "app.update.test.loop";
@@ -30,16 +31,18 @@ const PREF_EM_HOTFIX_ID                 
 const UPDATE_TEST_LOOP_INTERVAL     = 2000;
 
 const URI_UPDATES_PROPERTIES  = "chrome://mozapps/locale/update/updates.properties";
 
 const STATE_DOWNLOADING       = "downloading";
 const STATE_PENDING           = "pending";
 const STATE_PENDING_SVC       = "pending-service";
 const STATE_APPLYING          = "applying";
+const STATE_APPLIED           = "applied";
+const STATE_APPLIED_SVC       = "applied-service";
 const STATE_SUCCEEDED         = "succeeded";
 const STATE_DOWNLOAD_FAILED   = "download-failed";
 const STATE_FAILED            = "failed";
 
 const SRCEVT_FOREGROUND       = 1;
 const SRCEVT_BACKGROUND       = 2;
 
 const CERT_ATTR_CHECK_FAILED_NO_UPDATE  = 100;
@@ -405,16 +408,18 @@ var gUpdates = {
             }
           }
 
           // Now select the best page to start with, given the current state of
           // the Update.
           switch (state) {
           case STATE_PENDING:
           case STATE_PENDING_SVC:
+          case STATE_APPLIED:
+          case STATE_APPLIED_SVC:
             this.sourceEvent = SRCEVT_BACKGROUND;
             aCallback("finishedBackground");
             return;
           case STATE_DOWNLOADING:
             aCallback("downloading");
             return;
           case STATE_FAILED:
             window.getAttention();
@@ -1209,16 +1214,21 @@ var gDownloadingPage = {
    */
   _lastSec: Infinity,
   _startTime: null,
   _pausedStatus: "",
 
   _hiding: false,
 
   /**
+   * Have we registered an observer for a background update being staged
+   */
+  _updateApplyingObserver: false,
+
+  /**
    * Initialize
    */
   onPageShow: function() {
     this._downloadStatus = document.getElementById("downloadStatus");
     this._downloadProgress = document.getElementById("downloadProgress");
     this._pauseButton = document.getElementById("pauseButton");
     this._label_downloadStatus = this._downloadStatus.textContent;
 
@@ -1255,17 +1265,17 @@ var gDownloadingPage = {
       // download.
       aus.pauseDownload();
       var state = aus.downloadUpdate(gUpdates.update, false);
       if (state == "failed") {
         // We've tried as hard as we could to download a valid update -
         // we fell back from a partial patch to a complete patch and even
         // then we couldn't validate. Show a validation error with instructions
         // on how to manually update.
-        this.removeDownloadListener();
+        this.cleanUp();
         gUpdates.wiz.goTo("errors");
         return;
       }
       else {
         // Add this UI as a listener for active downloads
         aus.addDownloadListener(this);
       }
 
@@ -1343,22 +1353,40 @@ var gDownloadingPage = {
       this._pauseButton.setAttribute("paused", "false");
       this._pauseButton.setAttribute("tooltiptext",
                                      gUpdates.getAUSString("pauseButtonPause"));
       this._setStatus(this._label_downloadStatus);
     }
   },
 
   /**
-   * Removes the download listener.
+   * Wait for an update being staged in the background.
    */
-  removeDownloadListener: function() {
+  _setUpdateApplying: function() {
+    this._downloadProgress.mode = "undetermined";
+    this._pauseButton.hidden = true;
+    let applyingStatus = gUpdates.getAUSString("applyingUpdate");
+    this._setStatus(applyingStatus);
+
+    Services.obs.addObserver(this, "update-staged", false);
+    this._updateApplyingObserver = true;
+  },
+
+  /**
+   * Clean up the listener and observer registered for the wizard.
+   */
+  cleanUp: function() {
     var aus = CoC["@mozilla.org/updates/update-service;1"].
               getService(CoI.nsIApplicationUpdateService);
     aus.removeDownloadListener(this);
+
+    if (this._updateApplyingObserver) {
+      Services.obs.removeObserver(this, "update-staged");
+      this._updateApplyingObserver = false;
+    }
   },
 
   /**
    * When the user clicks the Pause/Resume button
    */
   onPause: function() {
     var aus = CoC["@mozilla.org/updates/update-service;1"].
               getService(CoI.nsIApplicationUpdateService);
@@ -1379,31 +1407,31 @@ var gDownloadingPage = {
   /**
    * When the user has closed the window using a Window Manager control (this
    * page doesn't have a cancel button) cancel the update in progress.
    */
   onWizardCancel: function() {
     if (this._hiding)
       return;
 
-    this.removeDownloadListener();
+    this.cleanUp();
   },
 
   /**
    * When the user closes the Wizard UI by clicking the Hide button
    */
   onHide: function() {
     // Set _hiding to true to prevent onWizardCancel from cancelling the update
     // that is in progress.
     this._hiding = true;
 
     // Remove ourself as a download listener so that we don't continue to be
     // fed progress and state notifications after the UI we're updating has
     // gone away.
-    this.removeDownloadListener();
+    this.cleanUp();
 
     var aus = CoC["@mozilla.org/updates/update-service;1"].
               getService(CoI.nsIApplicationUpdateService);
     var um = CoC["@mozilla.org/updates/update-manager;1"].
              getService(CoI.nsIUpdateManager);
     um.activeUpdate = gUpdates.update;
 
     // If the download was paused by the user, ask the user if they want to
@@ -1527,17 +1555,17 @@ var gDownloadingPage = {
 
     var u = gUpdates.update;
     switch (status) {
     case CoR.NS_ERROR_UNEXPECTED:
       if (u.selectedPatch.state == STATE_DOWNLOAD_FAILED &&
           (u.isCompleteUpdate || u.patchCount != 2)) {
         // Verification error of complete patch, informational text is held in
         // the update object.
-        this.removeDownloadListener();
+        this.cleanUp();
         gUpdates.wiz.goTo("errors");
         break;
       }
       // Verification failed for a partial patch, complete patch is now
       // downloading so return early and do NOT remove the download listener!
 
       // Reset the progress meter to "undertermined" mode so that we don't
       // show old progress for the new download of the "complete" patch.
@@ -1546,34 +1574,56 @@ var gDownloadingPage = {
       document.getElementById("verificationFailed").hidden = false;
       break;
     case CoR.NS_BINDING_ABORTED:
       LOG("gDownloadingPage", "onStopRequest - pausing download");
       // Do not remove UI listener since the user may resume downloading again.
       break;
     case CoR.NS_OK:
       LOG("gDownloadingPage", "onStopRequest - patch verification succeeded");
-      this.removeDownloadListener();
-      gUpdates.wiz.goTo("finished");
+      // If the background update pref is set, we should wait until the update
+      // is actually staged in the background.
+      if (getPref("getBoolPref", PREF_APP_UPDATE_BACKGROUND, false)) {
+        this._setUpdateApplying();
+      } else {
+        this.cleanUp();
+        gUpdates.wiz.goTo("finished");
+      }
       break;
     default:
       LOG("gDownloadingPage", "onStopRequest - transfer failed");
       // Some kind of transfer error, die.
-      this.removeDownloadListener();
+      this.cleanUp();
       gUpdates.wiz.goTo("errors");
       break;
     }
   },
 
   /**
+   * See nsIObserver.idl
+   */
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic == "update-staged") {
+      this.cleanUp();
+      if (aData == STATE_APPLIED ||
+          aData == STATE_APPLIED_SVC) {
+        gUpdates.wiz.goTo("finished");
+      } else {
+        gUpdates.wiz.goTo("errors");
+      }
+    }
+  },
+
+  /**
    * See nsISupports.idl
    */
   QueryInterface: function(iid) {
     if (!iid.equals(CoI.nsIRequestObserver) &&
         !iid.equals(CoI.nsIProgressEventSink) &&
+        !iid.equals(CoI.nsIObserver) &&
         !iid.equals(CoI.nsISupports))
       throw CoR.NS_ERROR_NO_INTERFACE;
     return this;
   }
 };
 
 /**
  * The "There was an error during the update" page.
--- a/toolkit/mozapps/update/nsIUpdateService.idl
+++ b/toolkit/mozapps/update/nsIUpdateService.idl
@@ -212,16 +212,18 @@ interface nsIUpdate : nsISupports
   readonly attribute nsIUpdatePatch selectedPatch;
 
   /**
    * The state of the selected patch:
    *   "downloading"        The update is being downloaded.
    *   "pending"            The update is ready to be applied.
    *   "pending-service"    The update is ready to be applied with the service.
    *   "applying"           The update is being applied.
+   *   "applied"            The update is ready to be switched to.
+   *   "applied-service"    The update is ready to be switched to with the service.
    *   "succeeded"          The update was successfully applied.
    *   "download-failed"    The update failed to be downloaded.
    *   "failed"             The update failed to be applied.
    */
   attribute AString state;
 
   /**
    * A numeric error code that conveys additional information about the state
@@ -345,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(b5811144-ed30-4343-aff9-c514034aa19a)]
+[scriptable, uuid(7bd62f69-f604-484b-b97c-e7229d7a3ee8)]
 interface nsIApplicationUpdateService : nsISupports
 {
   /**
    * The Update Checker used for background update checking.
    */
   readonly attribute nsIUpdateChecker backgroundChecker;
 
   /**
@@ -388,16 +390,21 @@ 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);
+
+  /**
    * Pauses the active update download process
    */
   void pauseDownload();
 
   /**
    * Whether or not there is an download happening at the moment.
    */
   readonly attribute boolean isDownloading;
@@ -413,20 +420,34 @@ interface nsIApplicationUpdateService : 
    * Whether or not the Update Service can download and install updates.
    * This is a function of whether or not the current user has access
    * privileges to the install directory.
    */
   readonly attribute boolean canApplyUpdates;
 };
 
 /**
+ * An interface describing a component which handles the job of processing
+ * an update after it's been downloaded.
+ */
+[scriptable, uuid(74439497-d796-4915-8cef-3dfe43027e4d)]
+interface nsIUpdateProcessor : nsISupports
+{
+  /**
+   * Processes the update which has been downloaded.
+   * This happens without restarting the application.
+   */
+  void processUpdate(in nsIUpdate update);
+};
+
+/**
  * An interface describing a global application service that maintains a list
  * of updates previously performed as well as the current active update.
  */
-[scriptable, uuid(fede66a9-9f96-4507-a22a-775ee885577e)]
+[scriptable, uuid(c5df56de-919d-406b-aaf9-106dfa9b685b)]
 interface nsIUpdateManager : nsISupports
 {
   /**
    * Gets the update at the specified index
    * @param   index
    *          The index within the updates array
    * @returns The nsIUpdate object at the specified index
    */
@@ -441,16 +462,21 @@ interface nsIUpdateManager : nsISupports
    * The active (current) update. The active update is not in the history list.
    */
   attribute nsIUpdate activeUpdate;
 
   /**
    * Saves all updates to disk.
    */
   void saveUpdates();
+
+  /**
+   * Refresh the update status based on the information in update.status.
+   */
+  void refreshUpdateStatus(in nsIUpdate update);
 };
 
 /**
  * An interface describing an object that can show various kinds of Update
  * notification UI to the user.
  */
 [scriptable, uuid(599fd3c6-ec68-4499-ada5-2997739c97a6)]
 interface nsIUpdatePrompt : nsISupports
--- a/toolkit/mozapps/update/nsUpdateService.js
+++ b/toolkit/mozapps/update/nsUpdateService.js
@@ -1,14 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /*
 # 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/.
 */
+
+#filter substitution
+
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/FileUtils.jsm");
 Components.utils.import("resource://gre/modules/AddonManager.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/ctypes.jsm")
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
@@ -34,16 +37,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_BACKGROUND          = "app.update.stage.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_PARTNER_BRANCH                 = "app.partner.";
@@ -69,16 +73,21 @@ const KEY_GRED            = "GreD";
 #define USE_UPDROOT
 #endif
 
 #ifdef USE_UPDROOT
 const KEY_UPDROOT         = "UpdRootD";
 #endif
 
 const DIR_UPDATES         = "updates";
+#ifdef XP_MACOSX
+const UPDATED_DIR         = "Updated.app";
+#else
+const UPDATED_DIR         = "updated";
+#endif
 const FILE_UPDATE_STATUS  = "update.status";
 const FILE_UPDATE_VERSION = "update.version";
 #ifdef MOZ_WIDGET_ANDROID
 const FILE_UPDATE_ARCHIVE = "update.apk";
 #else
 const FILE_UPDATE_ARCHIVE = "update.mar";
 #endif
 const FILE_UPDATE_LOG     = "update.log"
@@ -89,16 +98,18 @@ const FILE_LAST_LOG       = "last-update
 const FILE_BACKUP_LOG     = "backup-update.log";
 const FILE_UPDATE_LOCALE  = "update.locale";
 
 const STATE_NONE            = "null";
 const STATE_DOWNLOADING     = "downloading";
 const STATE_PENDING         = "pending";
 const STATE_PENDING_SVC     = "pending-service";
 const STATE_APPLYING        = "applying";
+const STATE_APPLIED         = "applied";
+const STATE_APPLIED_SVC     = "applied-service";
 const STATE_SUCCEEDED       = "succeeded";
 const STATE_DOWNLOAD_FAILED = "download-failed";
 const STATE_FAILED          = "failed";
 
 // From updater/errors.h:
 const WRITE_ERROR        = 7;
 const UNEXPECTED_ERROR   = 8;
 const ELEVATION_CANCELED = 9;
@@ -108,16 +119,17 @@ const SERVICE_UPDATER_COULD_NOT_BE_START
 const SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 25;
 const SERVICE_UPDATER_SIGN_ERROR           = 26;
 const SERVICE_UPDATER_COMPARE_ERROR        = 27;
 const SERVICE_UPDATER_IDENTITY_ERROR       = 28;
 const SERVICE_STILL_APPLYING_ON_SUCCESS    = 29;
 const SERVICE_STILL_APPLYING_ON_FAILURE    = 30;
 const SERVICE_UPDATER_NOT_FIXED_DRIVE      = 31;
 const SERVICE_COULD_NOT_LOCK_UPDATER       = 32;
+const SERVICE_INSTALLDIR_ERROR             = 33;
 
 const CERT_ATTR_CHECK_FAILED_NO_UPDATE  = 100;
 const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101;
 const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
 
 const DOWNLOAD_CHUNK_SIZE           = 300000; // bytes
 const DOWNLOAD_BACKGROUND_INTERVAL  = 600;    // seconds
 const DOWNLOAD_FOREGROUND_INTERVAL  = 0;
@@ -345,18 +357,17 @@ XPCOMUtils.defineLazyGetter(this, "gCanA
         // When the installation directory is not under Program Files,
         // fall through to checking if write access to the 
         // installation directory is available.
         LOG("gCanApplyUpdates - on Vista, appDir is not under Program Files");
       }
     }
 
     /**
-#      On Windows, we no longer store the update under the app dir if the app
-#      dir is under C:\Program Files.
+#      On Windows, we no longer store the update under the app dir.
 #
 #      If we are on Windows (including Vista, if we can't elevate) we need to
 #      to check that we can create and remove files from the actual app 
 #      directory (like C:\Program Files\Mozilla Firefox).  If we can't
 #      (because this user is not an adminstrator, for example) canUpdate()
 #      should return false.
 #
 #      For Vista, we perform this check to enable updating the  application
@@ -532,16 +543,41 @@ function getStatusTextFromCode(code, def
  * @return The active updates directory, as a nsIFile object
  */
 function getUpdatesDir() {
   // Right now, we only support downloading one patch at a time, so we always
   // use the same target directory.
   return getUpdateDirCreate([DIR_UPDATES, "0"]);
 }
 
+#ifndef USE_UPDROOT
+/**
+ * Get the Active Updates directory inside the directory where we apply the
+ * background updates.
+ * @return The active updates directory inside the updated directory, as a
+ *         nsIFile object.
+ */
+function getUpdatesDirInApplyToDir() {
+  var dir = FileUtils.getDir(KEY_APPDIR, []);
+#ifdef XP_MACOSX
+  dir = dir.parent.parent; // the bundle directory
+#endif
+  dir.append(UPDATED_DIR);
+#ifdef XP_MACOSX
+  dir.append("Contents");
+  dir.append("MacOS");
+#endif
+  dir.append(DIR_UPDATES);
+  if (!dir.exists()) {
+    dir.create(Ci.nsILocalFile.DIRECTORY_TYPE, 0755);
+  }
+  return dir;
+}
+#endif
+
 /**
  * Reads the update state from the update.status file in the specified
  * directory.
  * @param   dir
  *          The dir to look for an update.status file in
  * @return  The status value of the update.
  */
 function readStatusFile(dir) {
@@ -602,33 +638,58 @@ function shouldUseService() {
 function writeVersionFile(dir, version) {
   var versionFile = dir.clone();
   versionFile.append(FILE_UPDATE_VERSION);
   writeStringToFile(versionFile, version);
 }
 
 /**
  * Removes the contents of the Updates Directory
+ *
+ * @param aBackgroundUpdate Whether the update has been performed in the
+ *        background.  If this is true, we move the update log file to the
+ *        updated directory, so that it survives replacing the directories
+ *        later on.
  */
-function cleanUpUpdatesDir() {
+function cleanUpUpdatesDir(aBackgroundUpdate) {
   // Bail out if we don't have appropriate permissions
   try {
     var updateDir = getUpdatesDir();
   }
   catch (e) {
     return;
   }
 
   var e = updateDir.directoryEntries;
   while (e.hasMoreElements()) {
     var f = e.getNext().QueryInterface(Ci.nsIFile);
     // Preserve the last update log file for debugging purposes
     if (f.leafName == FILE_UPDATE_LOG) {
+      var dir;
       try {
-        var dir = f.parent.parent;
+#ifdef USE_UPDROOT
+        // If we're on a platform which uses the update root directory, the log
+        // files are written outside of the application directory, so they will
+        // not get overwritten when we replace the directories after a
+        // background update.  Therefore, we don't need any special logic for
+        // that case here.
+        // Note that this currently only applies to Windows.
+        dir = f.parent.parent;
+#else
+        // If we don't use the update root directory, the log files are written
+        // inside the application directory.  In that case, we want to write
+        // the log files to the updated directory in the case of background
+        // updates, so that they would be available when we replace that
+        // directory with the application directory later on.
+        if (aBackgroundUpdate) {
+          dir = getUpdatesDirInApplyToDir();
+        } else {
+          dir = f.parent.parent;
+        }
+#endif
         var logFile = dir.clone();
         logFile.append(FILE_LAST_LOG);
         if (logFile.exists()) {
           try {
             logFile.moveTo(dir, FILE_BACKUP_LOG);
           }
           catch (e) {
             LOG("cleanUpUpdatesDir - failed to rename file " + logFile.path +
@@ -637,16 +698,23 @@ function cleanUpUpdatesDir() {
         }
         f.moveTo(dir, FILE_LAST_LOG);
         continue;
       }
       catch (e) {
         LOG("cleanUpUpdatesDir - failed to move file " + f.path + " to " +
             dir.path + " and rename it to " + FILE_LAST_LOG);
       }
+    } else if (f.leafName == FILE_UPDATE_STATUS && aBackgroundUpdate) {
+      // Leave the update.status file alone when a background update
+      // has been performed.  We don't remove this file here because
+      // after the application directory gets replaced by the staged
+      // update, this will end up being the update.status file which
+      // represents the status of the update performed.
+      continue;
     }
     // Now, recursively remove this file.  The recursive removal is really
     // only needed on Mac OSX because this directory will contain a copy of
     // updater.app, which is itself a directory.
     try {
       f.remove(true);
     }
     catch (e) {
@@ -1324,16 +1392,44 @@ UpdateService.prototype = {
         // Resume download
         var status = this.downloadUpdate(update, true);
         if (status == STATE_NONE)
           cleanupActiveUpdate();
       }
       return;
     }
 
+    if (status == STATE_APPLYING) {
+      // This indicates that the background updater service is in either of the
+      // following two states:
+      // 1. It is in the process of applying an update in the background, and
+      //    we just happen to be racing against that.
+      // 2. It has failed to apply an update for some reason, and we hit this
+      //    case because the updater process has set the update status to
+      //    applying, but has never finished.
+      // In order to differentiate between these two states, we look at the
+      // state field of the update object.  If it's "pending", then we know
+      // that this is the first time we're hitting this case, so we switch
+      // that state to "applying" and we just wait and hope for the best.
+      // If it's "applying", we know that we've already been here once, so
+      // we really want to start from a clean state.
+      if (update &&
+          (update.state == STATE_PENDING || update.state == STATE_PENDING_SVC)) {
+        LOG("UpdateService:_postUpdateProcessing - patch found in applying " +
+            "state for the first time");
+        update.state = STATE_APPLYING;
+        um.saveUpdates();
+      } else { // We get here even if we don't have an update object
+        LOG("UpdateService:_postUpdateProcessing - patch found in applying " +
+            "state for the second time");
+        cleanupActiveUpdate();
+      }
+      return;
+    }
+
     if (!update)
       update = new Update(null);
 
     var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
                    createInstance(Ci.nsIUpdatePrompt);
 
     update.state = status;
     this._submitTelemetryPing(status);
@@ -1375,17 +1471,18 @@ UpdateService.prototype = {
         if (update.errorCode == SERVICE_UPDATER_COULD_NOT_BE_STARTED ||
             update.errorCode == SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS ||
             update.errorCode == SERVICE_UPDATER_SIGN_ERROR ||
             update.errorCode == SERVICE_UPDATER_COMPARE_ERROR ||
             update.errorCode == SERVICE_UPDATER_IDENTITY_ERROR ||
             update.errorCode == SERVICE_STILL_APPLYING_ON_SUCCESS ||
             update.errorCode == SERVICE_STILL_APPLYING_ON_FAILURE ||
             update.errorCode == SERVICE_UPDATER_NOT_FIXED_DRIVE ||
-            update.errorCode == SERVICE_COULD_NOT_LOCK_UPDATER) {
+            update.errorCode == SERVICE_COULD_NOT_LOCK_UPDATER ||
+            update.errorCode == SERVICE_INSTALLDIR_ERROR) {
 
           var failCount = getPref("getIntPref", 
                                   PREF_APP_UPDATE_SERVICE_ERRORS, 0);
           var maxFail = getPref("getIntPref", 
                                 PREF_APP_UPDATE_SERVICE_MAX_ERRORS, 
                                 DEFAULT_SERVICE_MAX_ERRORS);
 
           // As a safety, when the service reaches maximum failures, it will
@@ -1911,16 +2008,36 @@ UpdateService.prototype = {
       this._downloader.cancel();
     }
     // Set the previous application version prior to downloading the update.
     update.previousAppVersion = Services.appinfo.version;
     this._downloader = new Downloader(background);
     return this._downloader.downloadUpdate(update);
   },
 
+  applyUpdateInBackground: function AUS_applyUpdateInBackground(update) {
+    // If background updates are disabled, then just bail out!
+    if (!getPref("getBoolPref", PREF_APP_UPDATE_BACKGROUND, false)) {
+      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();
   },
 
@@ -2183,26 +2300,60 @@ UpdateManager.prototype = {
 
     this._ensureUpdates();
     // Don't write updates that have a temporary state to the updates.xml file.
     if (this._updates) {
       let updates = this._updates.slice();
       for (let i = updates.length - 1; i >= 0; --i) {
         let state = updates[i].state;
         if (state == STATE_NONE || state == STATE_DOWNLOADING ||
+            state == STATE_APPLIED || state == STATE_APPLIED_SVC ||
             state == STATE_PENDING || state == STATE_PENDING_SVC) {
           updates.splice(i, 1);
         }
       }
 
       this._writeUpdatesToXMLFile(updates.slice(0, 10),
                                   getUpdateFile([FILE_UPDATES_DB]));
     }
   },
 
+  refreshUpdateStatus: function UM_refreshUpdateStatus(update) {
+    var status = readStatusFile(getUpdatesDir());
+    var ary = status.split(":");
+    update.state = ary[0];
+    if (update.state == STATE_FAILED && ary[1]) {
+      update.errorCode = parseInt(ary[1]);
+    }
+    if (update.state == STATE_APPLIED && shouldUseService()) {
+      writeStatusFile(getUpdatesDir(), update.state = STATE_APPLIED_SVC);
+    }
+    var um = Cc["@mozilla.org/updates/update-manager;1"].
+             getService(Ci.nsIUpdateManager);
+    um.saveUpdates();
+
+    // Destroy the updates directory, since we're done with it.
+    cleanUpUpdatesDir(true);
+
+    // Send an observer notification which the update wizard uses in
+    // order to update its UI.
+    Services.obs.notifyObservers(null, "update-staged", update.state);
+
+    // Do this after *everything* else, since it will likely cause the app
+    // to shut down.
+    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);
+    }
+  },
+
   classID: Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateManager, Ci.nsIObserver])
 };
 
 /**
  * Checker
  * Checks for new Updates
  * @constructor
@@ -2512,18 +2663,22 @@ Downloader.prototype = {
     if (this._request && this._request instanceof Ci.nsIRequest)
       this._request.cancel(Cr.NS_BINDING_ABORTED);
   },
 
   /**
    * Whether or not a patch has been downloaded and staged for installation.
    */
   get patchIsStaged() {
-    var readState = readStatusFile(getUpdatesDir()); 
-    return readState == STATE_PENDING || readState == STATE_PENDING_SVC;
+    var readState = readStatusFile(getUpdatesDir());
+    // Note that if we decide to download and apply new updates after another
+    // update has been successfully applied in the background, we need to stop
+    // checking for the APPLIED state here.
+    return readState == STATE_PENDING || readState == STATE_PENDING_SVC ||
+           readState == STATE_APPLIED || readState == STATE_APPLIED_SVC;
   },
 
   /**
    * Verify the downloaded file.  We assume that the download is complete at
    * this point.
    */
   _verifyDownload: function Downloader__verifyDownload() {
     if (!this._request)
@@ -2826,28 +2981,30 @@ Downloader.prototype = {
    * @param   status
    *          Status code containing the reason for the cessation.
    */
   onStopRequest: function  Downloader_onStopRequest(request, context, status) {
     if (request instanceof Ci.nsIIncrementalDownload)
       LOG("Downloader:onStopRequest - original URI spec: " + request.URI.spec +
           ", final URI spec: " + request.finalURI.spec + ", status: " + status);
 
+    // XXX ehsan shouldShowPrompt should always be false here.
+    // But what happens when there is already a UI showing?
     var state = this._patch.state;
     var shouldShowPrompt = false;
     var deleteActiveUpdate = false;
     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 = true;
+          shouldShowPrompt = !getPref("getBoolPref", PREF_APP_UPDATE_BACKGROUND, false);
 
         // 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 {
@@ -2955,16 +3112,24 @@ Downloader.prototype = {
     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);
+    }
+
     // Prevent leaking the update object (bug 454964)
     this._update = null;
   },
 
   /**
    * See nsIInterfaceRequestor.idl
    */
   getInterface: function Downloader_getInterface(iid) {
--- a/toolkit/mozapps/update/test/Makefile.in
+++ b/toolkit/mozapps/update/test/Makefile.in
@@ -14,18 +14,20 @@ XPCSHELL_TESTS = \
   unit \
   $(NULL)
 
 TESTROOT = $(call core_abspath,$(DEPTH))/_tests/xpcshell/$(relativesrcdir)
 
 DEFINES += \
   -DAB_CD=$(AB_CD) \
   -DMOZ_APP_NAME=$(MOZ_APP_NAME) \
+  -DMOZ_APP_DISPLAYNAME=$(MOZ_APP_DISPLAYNAME) \
   -DBIN_SUFFIX=$(BIN_SUFFIX) \
   -DNS_NO_XPCOM \
+  -DMOZ_DEBUG=$(MOZ_DEBUG) \
   $(NULL)
 
 # Android doesn't build the updater binary, so it skips the things that test it.
 ifneq ($(OS_TARGET),Android)
 DIRS = \
   chrome \
   $(NULL)
 
--- a/toolkit/mozapps/update/test/TestAUSHelper.cpp
+++ b/toolkit/mozapps/update/test/TestAUSHelper.cpp
@@ -25,32 +25,34 @@
     dest[_count] = L'\0'; \
   }
 # define NS_taccess _waccess
 # define NS_tchdir _wchdir
 # define NS_tfopen _wfopen
 # define NS_tstrcmp wcscmp
 # define NS_ttoi _wtoi
 # define NS_tstat _wstat
+# define NS_tgetcwd _wgetcwd
 # define LOG_S "%S"
 
 #include "../common/updatehelper.h"
 
 #else
 # include <unistd.h>
 # define NS_main main
   typedef char NS_tchar;
 # define NS_T(str) str
 # define NS_tsnprintf snprintf
 # define NS_taccess access
 # define NS_tchdir chdir
 # define NS_tfopen fopen
 # define NS_tstrcmp strcmp
 # define NS_ttoi atoi
 # define NS_tstat stat
+# define NS_tgetcwd getcwd
 # define LOG_S "%s"
 #endif
 
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -240,22 +242,23 @@ int NS_main(int argc, NS_tchar **argv)
   int i = 0;
 
   if (NS_tchdir(argv[1]) != 0) {
     return 1;
   }
 
   // File in use test helper section
   if (!NS_tstrcmp(argv[4], NS_T("-s"))) {
+    NS_tchar *cwd = NS_tgetcwd(NULL, 0);
     NS_tchar inFilePath[MAXPATHLEN];
     NS_tsnprintf(inFilePath, sizeof(inFilePath)/sizeof(inFilePath[0]),
-                 NS_T("%s"), argv[2]);
+                 NS_T("%s/%s"), cwd, argv[2]);
     NS_tchar outFilePath[MAXPATHLEN];
     NS_tsnprintf(outFilePath, sizeof(outFilePath)/sizeof(outFilePath[0]),
-                 NS_T("%s"), argv[3]);
+                 NS_T("%s/%s"), cwd, argv[3]);
 
     int seconds = NS_ttoi(argv[5]);
 #ifdef XP_WIN
     HANDLE hFile = INVALID_HANDLE_VALUE;
     if (argc == 7) {
       hFile = CreateFileW(argv[6],
                           DELETE | GENERIC_WRITE, 0,
                           NULL, OPEN_EXISTING, 0, NULL);
--- a/toolkit/mozapps/update/test/TestAUSReadStrings.cpp
+++ b/toolkit/mozapps/update/test/TestAUSReadStrings.cpp
@@ -30,16 +30,17 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <string.h>
 
 #include "updater/resource.h"
 #include "updater/progressui.h"
 #include "common/readstrings.h"
 #include "common/errors.h"
+#include "mozilla/Util.h"
 
 #ifndef MAXPATHLEN
 # ifdef PATH_MAX
 #  define MAXPATHLEN PATH_MAX
 # elif defined(MAX_PATH)
 #  define MAXPATHLEN MAX_PATH
 # elif defined(_MAX_PATH)
 #  define MAXPATHLEN _MAX_PATH
@@ -47,16 +48,18 @@
 #  define MAXPATHLEN CCHMAXPATH
 # else
 #  define MAXPATHLEN 1024
 # endif
 #endif
 
 #define TEST_NAME "Updater ReadStrings"
 
+using namespace mozilla;
+
 static int gFailCount = 0;
 
 /**
  * Prints the given failure message and arguments using printf, prepending
  * "TEST-UNEXPECTED-FAIL " for the benefit of the test harness and
  * appending "\n" to eliminate having to type it at each call site.
  */
 void fail(const char* msg, ...)
@@ -96,17 +99,17 @@ int NS_main(int argc, NS_tchar **argv)
   if (!slash) {
     fail("%s | unable to find platform specific path separator (check 1)", TEST_NAME);
     return 20;
   }
 
   *(++slash) = '\0';
   // Test success when the ini file exists with both Title and Info in the
   // Strings section and the values for Title and Info.
-  NS_tsnprintf(inifile, sizeof(inifile), NS_T("%sTestAUSReadStrings1.ini"), argv[0]);
+  NS_tsnprintf(inifile, ArrayLength(inifile), NS_T("%sTestAUSReadStrings1.ini"), argv[0]);
   retval = ReadStrings(inifile, &testStrings);
   if (retval == OK) {
     if (strcmp(testStrings.title, "Title Test - \xD0\x98\xD1\x81\xD0\xBF\xD1\x8B" \
                                   "\xD1\x82\xD0\xB0\xD0\xBD\xD0\xB8\xD0\xB5 " \
                                   "\xCE\x94\xCE\xBF\xCE\xBA\xCE\xB9\xCE\xBC\xCE\xAE " \
                                   "\xE3\x83\x86\xE3\x82\xB9\xE3\x83\x88 " \
                                   "\xE6\xB8\xAC\xE8\xA9\xA6 " \
                                   "\xE6\xB5\x8B\xE8\xAF\x95") != 0) {
@@ -126,42 +129,42 @@ int NS_main(int argc, NS_tchar **argv)
   }
   else {
     fail("%s | ReadStrings returned %i (check 2)", TEST_NAME, retval);
     rv = 23;
   }
 
   // Test failure when the ini file exists without Title and with Info in the
   // Strings section.
-  NS_tsnprintf(inifile, sizeof(inifile), NS_T("%sTestAUSReadStrings2.ini"), argv[0]);
+  NS_tsnprintf(inifile, ArrayLength(inifile), NS_T("%sTestAUSReadStrings2.ini"), argv[0]);
   retval = ReadStrings(inifile, &testStrings);
   if (retval != PARSE_ERROR) {
     rv = 24;
     fail("%s | ReadStrings returned %i (check 5)", TEST_NAME, retval);
   }
 
   // Test failure when the ini file exists with Title and without Info in the
   // Strings section.
-  NS_tsnprintf(inifile, sizeof(inifile), NS_T("%sTestAUSReadStrings3.ini"), argv[0]);
+  NS_tsnprintf(inifile, ArrayLength(inifile), NS_T("%sTestAUSReadStrings3.ini"), argv[0]);
   retval = ReadStrings(inifile, &testStrings);
   if (retval != PARSE_ERROR) {
     rv = 25;
     fail("%s | ReadStrings returned %i (check 6)", TEST_NAME, retval);
   }
 
   // Test failure when the ini file doesn't exist
-  NS_tsnprintf(inifile, sizeof(inifile), NS_T("%sTestAUSReadStringsBogus.ini"), argv[0]);
+  NS_tsnprintf(inifile, ArrayLength(inifile), NS_T("%sTestAUSReadStringsBogus.ini"), argv[0]);
   retval = ReadStrings(inifile, &testStrings);
   if (retval != READ_ERROR) {
     rv = 26;
     fail("%s | ini file doesn't exist (check 7)", TEST_NAME);
   }
 
   // Test reading a non-default section name
-  NS_tsnprintf(inifile, sizeof(inifile), NS_T("%sTestAUSReadStrings3.ini"), argv[0]);
+  NS_tsnprintf(inifile, ArrayLength(inifile), NS_T("%sTestAUSReadStrings3.ini"), argv[0]);
   retval = ReadStrings(inifile, "Title\0", 1, &testStrings.title, "BogusSection2");
   if (retval == OK) {
     if (strcmp(testStrings.title, "Bogus Title") != 0) {
       rv = 27;
       fail("%s | Title ini value incorrect (check 9)", TEST_NAME);
     }
   }
   else {
--- a/toolkit/mozapps/update/test/chrome/Makefile.in
+++ b/toolkit/mozapps/update/test/chrome/Makefile.in
@@ -52,16 +52,18 @@ include $(DEPTH)/config/autoconf.mk
   test_0073_notify_verifyFailPartialComplete.xul \
   test_0074_notify_verifyFailPartial_successComplete.xul \
   test_0081_error_patchApplyFailure_partial_only.xul \
   test_0082_error_patchApplyFailure_complete_only.xul \
   test_0083_error_patchApplyFailure_partial_complete.xul \
   test_0084_error_patchApplyFailure_verify_failed.xul \
   test_0091_installed.xul \
   test_0092_finishedBackground.xul \
+  test_0093_stagedBackground.xul \
+  test_0094_stagedServiceBackground.xul \
   test_0111_neverButton_basic.xul \
   test_0112_neverButton_billboard.xul \
   test_0113_showNeverForVersionRemovedWithPref.xul \
   test_0121_check_requireBuiltinCert.xul \
   test_0122_check_allowNonBuiltinCert_validCertAttrs.xul \
   test_0123_check_allowNonBuiltinCert_noCertAttrsCheck.xul \
   test_0131_check_invalidCertAttrs_noUpdate.xul \
   test_0132_check_invalidCertAttrs_hasUpdate.xul \
copy from toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul
copy to toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul
--- a/toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul
+++ b/toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul
@@ -3,17 +3,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 -->
 
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
 <?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
 
-<window title="Update Wizard pages: finished background"
+<window title="Update Wizard pages: staged background"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         onload="runTestDefault();">
 <script type="application/javascript"
         src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
 <script type="application/javascript"
         src="utils.js"/>
 
 <script type="application/javascript">
@@ -23,30 +23,30 @@ const TESTS = [ {
   pageid: PAGEID_FINISHED_BKGRD,
   buttonClick: "extra1"
 } ];
 
 function runTest() {
   debugDump("entering");
 
   let patches = getLocalPatchString("complete", null, null, null, null, null,
-                                    STATE_PENDING);
+                                    STATE_APPLIED);
   let updates = getLocalUpdateString(patches, null, null, null,
                                      Services.appinfo.version,
                                      Services.appinfo.platformVersion);
   writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
   writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
-  writeStatusFile(STATE_SUCCEEDED);
+  writeStatusFile(STATE_APPLIED);
 
   reloadUpdateManagerData();
 
-  is(gUpdateManager.activeUpdate.state, "pending",
-     "The active update should have a state of pending");
+  is(gUpdateManager.activeUpdate.state, "applied",
+     "The active update should have a state of applied");
 
-  gUP.showUpdateDownloaded(gUpdateManager.activeUpdate);
+  gUP.showUpdateDownloaded(gUpdateManager.activeUpdate, true);
 }
 
 ]]>
 </script>
 
 <body xmlns="http://www.w3.org/1999/xhtml">
   <p id="display"></p>
   <div id="content" style="display: none"></div>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test/chrome/test_0094_stagedServiceBackground.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Update Wizard pages: staged background"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="runTestDefault();">
+<script type="application/javascript"
+        src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<script type="application/javascript"
+        src="utils.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const TESTS = [ {
+  pageid: PAGEID_FINISHED_BKGRD,
+  buttonClick: "extra1"
+} ];
+
+function runTest() {
+  debugDump("entering");
+
+  let patches = getLocalPatchString("complete", null, null, null, null, null,
+                                    STATE_APPLIED_SVC);
+  let updates = getLocalUpdateString(patches, null, null, null,
+                                     Services.appinfo.version,
+                                     Services.appinfo.platformVersion);
+  writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+  writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
+  writeStatusFile(STATE_APPLIED_SVC);
+
+  reloadUpdateManagerData();
+
+  is(gUpdateManager.activeUpdate.state, "applied-service",
+     "The active update should have a state of applied-service");
+
+  gUP.showUpdateDownloaded(gUpdateManager.activeUpdate, true);
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test"></pre>
+</body>
+</window>
--- a/toolkit/mozapps/update/test/chrome/test_9999_cleanup.xul
+++ b/toolkit/mozapps/update/test/chrome/test_9999_cleanup.xul
@@ -37,27 +37,23 @@ function runTest() {
   SimpleTest.waitForExplicitFinish();
 
   closeUpdateWindow();
 
   resetPrefs();
   removeUpdateDirsAndFiles();
   reloadUpdateManagerData();
 
-  let dir = getCurrentProcessDir();
-  let file = dir.clone();
-  file.append(FILE_UPDATE_ACTIVE);
+  let file = getUpdatesXMLFile(true);
   ok(!file.exists(), file.path + " should not exist");
 
-  file = dir.clone();
-  file.append(FILE_UPDATES_DB);
+  file = getUpdatesXMLFile(false);
   ok(!file.exists(), file.path + " should not exist");
 
-  dir.append("updates");
-  dir.append("0");
+  let dir = getUpdatesDir();
 
   file = dir.clone();
   file.append(FILE_UPDATE_STATUS);
   ok(!file.exists(), file.path + " should not exist");
 
   file = dir.clone();
   file.append(FILE_UPDATE_ARCHIVE);
   ok(!file.exists(), file.path + " should not exist");
--- a/toolkit/mozapps/update/test/shared.js
+++ b/toolkit/mozapps/update/test/shared.js
@@ -7,16 +7,17 @@
 // 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";
@@ -149,29 +150,40 @@ function setUpdateChannel(aChannel) {
  */
 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) {
-  var file = getCurrentProcessDir();
-  file.append(aIsActiveUpdate ? FILE_UPDATE_ACTIVE : FILE_UPDATES_DB);
-  writeFile(file, aContent);
+  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
@@ -196,22 +208,36 @@ 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 = getCurrentProcessDir();
+  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.
  *
@@ -332,42 +358,38 @@ function getFileExtension(aFile) {
 
 /**
  * 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 appDir = getCurrentProcessDir();
-  var file = appDir.clone();
-  file.append(FILE_UPDATE_ACTIVE);
+  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 = appDir.clone();
-  file.append(FILE_UPDATES_DB);
+  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 = appDir.clone();
-  updatesDir.append("updates");
+  var updatesDir = getUpdatesDir();
   try {
     cleanUpdatesDir(updatesDir);
   }
   catch (e) {
     dump("Unable to remove files / directories from directory\npath: " +
          updatesDir.path + "\nException: " + e + "\n");
   }
 }
--- a/toolkit/mozapps/update/test/sharedUpdateXML.js
+++ b/toolkit/mozapps/update/test/sharedUpdateXML.js
@@ -26,16 +26,18 @@ const SHA512_HASH_SIMPLE_MAR = "55d3e2a8
                                "8f8ee9c9260ba01e6aef86fa4a6c46a3016b675ef94e7" +
                                "7e63fbe912f64d155bed9b1c341dd56e575a26";
 
 const STATE_NONE            = "null";
 const STATE_DOWNLOADING     = "downloading";
 const STATE_PENDING         = "pending";
 const STATE_PENDING_SVC     = "pending-service";
 const STATE_APPLYING        = "applying";
+const STATE_APPLIED         = "applied";
+const STATE_APPLIED_SVC     = "applied-service";
 const STATE_SUCCEEDED       = "succeeded";
 const STATE_DOWNLOAD_FAILED = "download-failed";
 const STATE_FAILED          = "failed";
 
 /**
  * Constructs a string representing a remote update xml file.
  *
  * @param  aUpdates
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test/unit/data/complete_cc_log_switch_success
@@ -0,0 +1,5 @@
+rename_file: proceeding to rename the directory
+rename_file: proceeding to rename the directory
+Now, remove the tmpDir
+succeeded
+calling QuitProgressUI
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test/unit/data/complete_log_switch_success
@@ -0,0 +1,345 @@
+UPDATE TYPE complete
+PREPARE REMOVEFILE precomplete
+PREPARE REMOVEFILE a/b/searchplugins/searchpluginstext0
+PREPARE REMOVEFILE a/b/searchplugins/searchpluginspng0.png
+PREPARE REMOVEFILE a/b/removed-files
+PREPARE REMOVEFILE a/b/extensions/extensions1/extensions1png1.png
+PREPARE REMOVEFILE a/b/extensions/extensions0/extensions0text0
+PREPARE REMOVEFILE a/b/exe0.exe
+PREPARE REMOVEFILE a/b/2/20/20text0
+PREPARE REMOVEFILE a/b/2/20/20png0.png
+PREPARE REMOVEFILE a/b/0/0exe0.exe
+PREPARE REMOVEFILE a/b/0/00/00text0
+PREPARE REMOVEDIR a/b/searchplugins/
+PREPARE REMOVEDIR a/b/extensions/extensions1/
+PREPARE REMOVEDIR a/b/extensions/extensions0/
+PREPARE REMOVEDIR a/b/extensions/
+PREPARE REMOVEDIR a/b/defaults/pref/
+PREPARE REMOVEDIR a/b/defaults/
+PREPARE REMOVEDIR a/b/2/20/
+PREPARE REMOVEDIR a/b/2/
+PREPARE REMOVEDIR a/b/0/00/
+PREPARE REMOVEDIR a/b/0/
+PREPARE REMOVEDIR a/b/
+PREPARE REMOVEDIR a/
+PREPARE ADD precomplete
+PREPARE ADD a/b/searchplugins/searchpluginstext0
+PREPARE ADD a/b/searchplugins/searchpluginspng1.png
+PREPARE ADD a/b/searchplugins/searchpluginspng0.png
+PREPARE ADD a/b/removed-files
+PREPARE ADD a/b/extensions/extensions1/extensions1text0
+PREPARE ADD a/b/extensions/extensions1/extensions1png1.png
+PREPARE ADD a/b/extensions/extensions1/extensions1png0.png
+PREPARE ADD a/b/extensions/extensions0/extensions0text0
+PREPARE ADD a/b/extensions/extensions0/extensions0png1.png
+PREPARE ADD a/b/extensions/extensions0/extensions0png0.png
+PREPARE ADD a/b/exe0.exe
+PREPARE ADD a/b/1/10/10text0
+PREPARE ADD a/b/0/0exe0.exe
+PREPARE ADD a/b/0/00/00text1
+PREPARE ADD a/b/0/00/00text0
+PREPARE ADD a/b/0/00/00png0.png
+PREPARE REMOVEDIR a/b/9/99/
+PREPARE REMOVEDIR a/b/9/99/
+PREPARE REMOVEDIR a/b/9/98/
+PREPARE REMOVEFILE a/b/9/97/970/97xtext0
+PREPARE REMOVEFILE a/b/9/97/970/97xtext1
+PREPARE REMOVEDIR a/b/9/97/970/
+PREPARE REMOVEFILE a/b/9/97/971/97xtext0
+PREPARE REMOVEFILE a/b/9/97/971/97xtext1
+PREPARE REMOVEDIR a/b/9/97/971/
+PREPARE REMOVEDIR a/b/9/97/
+PREPARE REMOVEFILE a/b/9/96/96text0
+PREPARE REMOVEFILE a/b/9/96/96text1
+PREPARE REMOVEDIR a/b/9/96/
+PREPARE REMOVEDIR a/b/9/95/
+PREPARE REMOVEDIR a/b/9/95/
+PREPARE REMOVEDIR a/b/9/94/
+PREPARE REMOVEDIR a/b/9/94/
+PREPARE REMOVEDIR a/b/9/93/
+PREPARE REMOVEDIR a/b/9/92/
+PREPARE REMOVEDIR a/b/9/91/
+PREPARE REMOVEDIR a/b/9/90/
+PREPARE REMOVEDIR a/b/9/90/
+PREPARE REMOVEDIR a/b/8/89/
+PREPARE REMOVEDIR a/b/8/89/
+PREPARE REMOVEDIR a/b/8/88/
+PREPARE REMOVEFILE a/b/8/87/870/87xtext0
+PREPARE REMOVEFILE a/b/8/87/870/87xtext1
+PREPARE REMOVEDIR a/b/8/87/870/
+PREPARE REMOVEFILE a/b/8/87/871/87xtext0
+PREPARE REMOVEFILE a/b/8/87/871/87xtext1
+PREPARE REMOVEDIR a/b/8/87/871/
+PREPARE REMOVEDIR a/b/8/87/
+PREPARE REMOVEFILE a/b/8/86/86text0
+PREPARE REMOVEFILE a/b/8/86/86text1
+PREPARE REMOVEDIR a/b/8/86/
+PREPARE REMOVEDIR a/b/8/85/
+PREPARE REMOVEDIR a/b/8/85/
+PREPARE REMOVEDIR a/b/8/84/
+PREPARE REMOVEDIR a/b/8/84/
+PREPARE REMOVEDIR a/b/8/83/
+PREPARE REMOVEDIR a/b/8/82/
+PREPARE REMOVEDIR a/b/8/81/
+PREPARE REMOVEDIR a/b/8/80/
+PREPARE REMOVEDIR a/b/8/80/
+PREPARE REMOVEFILE a/b/7/70/7xtest.exe
+PREPARE REMOVEFILE a/b/7/70/7xtext0
+PREPARE REMOVEFILE a/b/7/70/7xtext1
+PREPARE REMOVEDIR a/b/7/70/
+PREPARE REMOVEFILE a/b/7/71/7xtest.exe
+PREPARE REMOVEFILE a/b/7/71/7xtext0
+PREPARE REMOVEFILE a/b/7/71/7xtext1
+PREPARE REMOVEDIR a/b/7/71/
+PREPARE REMOVEDIR a/b/7/
+PREPARE REMOVEDIR a/b/6/
+PREPARE REMOVEFILE a/b/5/5text1
+PREPARE REMOVEFILE a/b/5/5text0
+PREPARE REMOVEFILE a/b/5/5test.exe
+PREPARE REMOVEFILE a/b/5/5text0
+PREPARE REMOVEFILE a/b/5/5text1
+PREPARE REMOVEDIR a/b/5/
+PREPARE REMOVEFILE a/b/4/4text1
+PREPARE REMOVEFILE a/b/4/4text0
+PREPARE REMOVEDIR a/b/4/
+PREPARE REMOVEFILE a/b/3/3text1
+PREPARE REMOVEFILE a/b/3/3text0
+EXECUTE REMOVEFILE precomplete
+EXECUTE REMOVEFILE a/b/searchplugins/searchpluginstext0
+EXECUTE REMOVEFILE a/b/searchplugins/searchpluginspng0.png
+EXECUTE REMOVEFILE a/b/removed-files
+EXECUTE REMOVEFILE a/b/extensions/extensions1/extensions1png1.png
+EXECUTE REMOVEFILE a/b/extensions/extensions0/extensions0text0
+EXECUTE REMOVEFILE a/b/exe0.exe
+EXECUTE REMOVEFILE a/b/2/20/20text0
+EXECUTE REMOVEFILE a/b/2/20/20png0.png
+EXECUTE REMOVEFILE a/b/0/0exe0.exe
+EXECUTE REMOVEFILE a/b/0/00/00text0
+EXECUTE REMOVEDIR a/b/searchplugins/
+EXECUTE REMOVEDIR a/b/extensions/extensions1/
+EXECUTE REMOVEDIR a/b/extensions/extensions0/
+EXECUTE REMOVEDIR a/b/extensions/
+EXECUTE REMOVEDIR a/b/defaults/pref/
+EXECUTE REMOVEDIR a/b/defaults/
+EXECUTE REMOVEDIR a/b/2/20/
+EXECUTE REMOVEDIR a/b/2/
+EXECUTE REMOVEDIR a/b/0/00/
+EXECUTE REMOVEDIR a/b/0/
+EXECUTE REMOVEDIR a/b/
+EXECUTE REMOVEDIR a/
+EXECUTE ADD precomplete
+EXECUTE ADD a/b/searchplugins/searchpluginstext0
+EXECUTE ADD a/b/searchplugins/searchpluginspng1.png
+EXECUTE ADD a/b/searchplugins/searchpluginspng0.png
+EXECUTE ADD a/b/removed-files
+EXECUTE ADD a/b/extensions/extensions1/extensions1text0
+EXECUTE ADD a/b/extensions/extensions1/extensions1png1.png
+EXECUTE ADD a/b/extensions/extensions1/extensions1png0.png
+EXECUTE ADD a/b/extensions/extensions0/extensions0text0
+EXECUTE ADD a/b/extensions/extensions0/extensions0png1.png
+EXECUTE ADD a/b/extensions/extensions0/extensions0png0.png
+EXECUTE ADD a/b/exe0.exe
+EXECUTE ADD a/b/1/10/10text0
+EXECUTE ADD a/b/0/0exe0.exe
+EXECUTE ADD a/b/0/00/00text1
+EXECUTE ADD a/b/0/00/00text0
+EXECUTE ADD a/b/0/00/00png0.png
+EXECUTE REMOVEDIR a/b/9/99/
+EXECUTE REMOVEDIR a/b/9/99/
+EXECUTE REMOVEDIR a/b/9/98/
+EXECUTE REMOVEFILE a/b/9/97/970/97xtext0
+EXECUTE REMOVEFILE a/b/9/97/970/97xtext1
+EXECUTE REMOVEDIR a/b/9/97/970/
+EXECUTE REMOVEFILE a/b/9/97/971/97xtext0
+EXECUTE REMOVEFILE a/b/9/97/971/97xtext1
+EXECUTE REMOVEDIR a/b/9/97/971/
+EXECUTE REMOVEDIR a/b/9/97/
+EXECUTE REMOVEFILE a/b/9/96/96text0
+EXECUTE REMOVEFILE a/b/9/96/96text1
+EXECUTE REMOVEDIR a/b/9/96/
+EXECUTE REMOVEDIR a/b/9/95/
+EXECUTE REMOVEDIR a/b/9/95/
+EXECUTE REMOVEDIR a/b/9/94/
+EXECUTE REMOVEDIR a/b/9/94/
+EXECUTE REMOVEDIR a/b/9/93/
+EXECUTE REMOVEDIR a/b/9/92/
+EXECUTE REMOVEDIR a/b/9/91/
+EXECUTE REMOVEDIR a/b/9/90/
+EXECUTE REMOVEDIR a/b/9/90/
+EXECUTE REMOVEDIR a/b/8/89/
+EXECUTE REMOVEDIR a/b/8/89/
+EXECUTE REMOVEDIR a/b/8/88/
+EXECUTE REMOVEFILE a/b/8/87/870/87xtext0
+EXECUTE REMOVEFILE a/b/8/87/870/87xtext1
+EXECUTE REMOVEDIR a/b/8/87/870/
+EXECUTE REMOVEFILE a/b/8/87/871/87xtext0
+EXECUTE REMOVEFILE a/b/8/87/871/87xtext1
+EXECUTE REMOVEDIR a/b/8/87/871/
+EXECUTE REMOVEDIR a/b/8/87/
+EXECUTE REMOVEFILE a/b/8/86/86text0
+EXECUTE REMOVEFILE a/b/8/86/86text1
+EXECUTE REMOVEDIR a/b/8/86/
+EXECUTE REMOVEDIR a/b/8/85/
+EXECUTE REMOVEDIR a/b/8/85/
+EXECUTE REMOVEDIR a/b/8/84/
+EXECUTE REMOVEDIR a/b/8/84/
+EXECUTE REMOVEDIR a/b/8/83/
+EXECUTE REMOVEDIR a/b/8/82/
+EXECUTE REMOVEDIR a/b/8/81/
+EXECUTE REMOVEDIR a/b/8/80/
+EXECUTE REMOVEDIR a/b/8/80/
+EXECUTE REMOVEFILE a/b/7/70/7xtest.exe
+EXECUTE REMOVEFILE a/b/7/70/7xtext0
+EXECUTE REMOVEFILE a/b/7/70/7xtext1
+EXECUTE REMOVEDIR a/b/7/70/
+EXECUTE REMOVEFILE a/b/7/71/7xtest.exe
+EXECUTE REMOVEFILE a/b/7/71/7xtext0
+EXECUTE REMOVEFILE a/b/7/71/7xtext1
+EXECUTE REMOVEDIR a/b/7/71/
+EXECUTE REMOVEDIR a/b/7/
+EXECUTE REMOVEDIR a/b/6/
+EXECUTE REMOVEFILE a/b/5/5text1
+EXECUTE REMOVEFILE a/b/5/5text0
+EXECUTE REMOVEFILE a/b/5/5test.exe
+EXECUTE REMOVEFILE a/b/5/5text0
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEFILE a/b/5/5text1
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEDIR a/b/5/
+EXECUTE REMOVEFILE a/b/4/4text1
+EXECUTE REMOVEFILE a/b/4/4text0
+EXECUTE REMOVEDIR a/b/4/
+EXECUTE REMOVEFILE a/b/3/3text1
+EXECUTE REMOVEFILE a/b/3/3text0
+FINISH REMOVEFILE precomplete
+FINISH REMOVEFILE a/b/searchplugins/searchpluginstext0
+FINISH REMOVEFILE a/b/searchplugins/searchpluginspng0.png
+FINISH REMOVEFILE a/b/removed-files
+FINISH REMOVEFILE a/b/extensions/extensions1/extensions1png1.png
+FINISH REMOVEFILE a/b/extensions/extensions0/extensions0text0
+FINISH REMOVEFILE a/b/exe0.exe
+FINISH REMOVEFILE a/b/2/20/20text0
+FINISH REMOVEFILE a/b/2/20/20png0.png
+FINISH REMOVEFILE a/b/0/0exe0.exe
+FINISH REMOVEFILE a/b/0/00/00text0
+FINISH REMOVEDIR a/b/searchplugins/
+removing directory: a/b/searchplugins/, rv: 0
+FINISH REMOVEDIR a/b/extensions/extensions1/
+removing directory: a/b/extensions/extensions1/, rv: 0
+FINISH REMOVEDIR a/b/extensions/extensions0/
+removing directory: a/b/extensions/extensions0/, rv: 0
+FINISH REMOVEDIR a/b/extensions/
+removing directory: a/b/extensions/, rv: 0
+FINISH REMOVEDIR a/b/defaults/pref/
+removing directory: a/b/defaults/pref/, rv: 0
+FINISH REMOVEDIR a/b/defaults/
+removing directory: a/b/defaults/, rv: 0
+FINISH REMOVEDIR a/b/2/20/
+FINISH REMOVEDIR a/b/2/
+FINISH REMOVEDIR a/b/0/00/
+removing directory: a/b/0/00/, rv: 0
+FINISH REMOVEDIR a/b/0/
+removing directory: a/b/0/, rv: 0
+FINISH REMOVEDIR a/b/
+removing directory: a/b/, rv: 0
+FINISH REMOVEDIR a/
+removing directory: a/, rv: 0
+FINISH ADD precomplete
+FINISH ADD a/b/searchplugins/searchpluginstext0
+FINISH ADD a/b/searchplugins/searchpluginspng1.png
+FINISH ADD a/b/searchplugins/searchpluginspng0.png
+FINISH ADD a/b/removed-files
+FINISH ADD a/b/extensions/extensions1/extensions1text0
+FINISH ADD a/b/extensions/extensions1/extensions1png1.png
+FINISH ADD a/b/extensions/extensions1/extensions1png0.png
+FINISH ADD a/b/extensions/extensions0/extensions0text0
+FINISH ADD a/b/extensions/extensions0/extensions0png1.png
+FINISH ADD a/b/extensions/extensions0/extensions0png0.png
+FINISH ADD a/b/exe0.exe
+FINISH ADD a/b/1/10/10text0
+FINISH ADD a/b/0/0exe0.exe
+FINISH ADD a/b/0/00/00text1
+FINISH ADD a/b/0/00/00text0
+FINISH ADD a/b/0/00/00png0.png
+FINISH REMOVEDIR a/b/9/99/
+FINISH REMOVEDIR a/b/9/99/
+directory no longer exists; skipping
+FINISH REMOVEDIR a/b/9/98/
+FINISH REMOVEFILE a/b/9/97/970/97xtext0
+FINISH REMOVEFILE a/b/9/97/970/97xtext1
+FINISH REMOVEDIR a/b/9/97/970/
+FINISH REMOVEFILE a/b/9/97/971/97xtext0
+FINISH REMOVEFILE a/b/9/97/971/97xtext1
+FINISH REMOVEDIR a/b/9/97/971/
+FINISH REMOVEDIR a/b/9/97/
+FINISH REMOVEFILE a/b/9/96/96text0
+FINISH REMOVEFILE a/b/9/96/96text1
+FINISH REMOVEDIR a/b/9/96/
+FINISH REMOVEDIR a/b/9/95/
+FINISH REMOVEDIR a/b/9/95/
+directory no longer exists; skipping
+FINISH REMOVEDIR a/b/9/94/
+FINISH REMOVEDIR a/b/9/94/
+directory no longer exists; skipping
+FINISH REMOVEDIR a/b/9/93/
+FINISH REMOVEDIR a/b/9/92/
+removing directory: a/b/9/92/, rv: 0
+FINISH REMOVEDIR a/b/9/91/
+removing directory: a/b/9/91/, rv: 0
+FINISH REMOVEDIR a/b/9/90/
+FINISH REMOVEDIR a/b/9/90/
+directory no longer exists; skipping
+FINISH REMOVEDIR a/b/8/89/
+FINISH REMOVEDIR a/b/8/89/
+directory no longer exists; skipping
+FINISH REMOVEDIR a/b/8/88/
+FINISH REMOVEFILE a/b/8/87/870/87xtext0
+FINISH REMOVEFILE a/b/8/87/870/87xtext1
+FINISH REMOVEDIR a/b/8/87/870/
+FINISH REMOVEFILE a/b/8/87/871/87xtext0
+FINISH REMOVEFILE a/b/8/87/871/87xtext1
+FINISH REMOVEDIR a/b/8/87/871/
+FINISH REMOVEDIR a/b/8/87/
+FINISH REMOVEFILE a/b/8/86/86text0
+FINISH REMOVEFILE a/b/8/86/86text1
+FINISH REMOVEDIR a/b/8/86/
+FINISH REMOVEDIR a/b/8/85/
+FINISH REMOVEDIR a/b/8/85/
+directory no longer exists; skipping
+FINISH REMOVEDIR a/b/8/84/
+FINISH REMOVEDIR a/b/8/84/
+directory no longer exists; skipping
+FINISH REMOVEDIR a/b/8/83/
+FINISH REMOVEDIR a/b/8/82/
+removing directory: a/b/8/82/, rv: 0
+FINISH REMOVEDIR a/b/8/81/
+removing directory: a/b/8/81/, rv: 0
+FINISH REMOVEDIR a/b/8/80/
+FINISH REMOVEDIR a/b/8/80/
+directory no longer exists; skipping
+FINISH REMOVEFILE a/b/7/70/7xtest.exe
+FINISH REMOVEFILE a/b/7/70/7xtext0
+FINISH REMOVEFILE a/b/7/70/7xtext1
+FINISH REMOVEDIR a/b/7/70/
+FINISH REMOVEFILE a/b/7/71/7xtest.exe
+FINISH REMOVEFILE a/b/7/71/7xtext0
+FINISH REMOVEFILE a/b/7/71/7xtext1
+FINISH REMOVEDIR a/b/7/71/
+FINISH REMOVEDIR a/b/7/
+FINISH REMOVEDIR a/b/6/
+FINISH REMOVEFILE a/b/5/5text1
+FINISH REMOVEFILE a/b/5/5text0
+FINISH REMOVEFILE a/b/5/5test.exe
+FINISH REMOVEDIR a/b/5/
+FINISH REMOVEFILE a/b/4/4text1
+FINISH REMOVEFILE a/b/4/4text0
+FINISH REMOVEDIR a/b/4/
+FINISH REMOVEFILE a/b/3/3text1
+FINISH REMOVEFILE a/b/3/3text0
+succeeded
+calling QuitProgressUI
+rename_file: proceeding to rename the directory
+rename_file: proceeding to rename the directory
+Now, remove the tmpDir
+succeeded
+calling QuitProgressUI
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test/unit/data/partial_log_switch_success
@@ -0,0 +1,278 @@
+UPDATE TYPE partial
+PREPARE ADD precomplete
+PREPARE ADD a/b/searchplugins/searchpluginstext0
+PREPARE PATCH a/b/searchplugins/searchpluginspng1.png
+PREPARE PATCH a/b/searchplugins/searchpluginspng0.png
+PREPARE ADD a/b/extensions/extensions1/extensions1text0
+PREPARE PATCH a/b/extensions/extensions1/extensions1png1.png
+PREPARE PATCH a/b/extensions/extensions1/extensions1png0.png
+PREPARE ADD a/b/extensions/extensions0/extensions0text0
+PREPARE PATCH a/b/extensions/extensions0/extensions0png1.png
+PREPARE PATCH a/b/extensions/extensions0/extensions0png0.png
+PREPARE PATCH a/b/exe0.exe
+PREPARE PATCH a/b/0/0exe0.exe
+PREPARE ADD a/b/0/00/00text0
+PREPARE PATCH a/b/0/00/00png0.png
+PREPARE ADD a/b/2/20/20text0
+PREPARE ADD a/b/2/20/20png0.png
+PREPARE ADD a/b/0/00/00text2
+PREPARE REMOVEFILE a/b/1/10/10text0
+PREPARE REMOVEFILE a/b/0/00/00text1
+PREPARE REMOVEDIR a/b/9/99/
+PREPARE REMOVEDIR a/b/9/99/
+PREPARE REMOVEDIR a/b/9/98/
+PREPARE REMOVEFILE a/b/9/97/970/97xtext0
+PREPARE REMOVEFILE a/b/9/97/970/97xtext1
+PREPARE REMOVEDIR a/b/9/97/970/
+PREPARE REMOVEFILE a/b/9/97/971/97xtext0
+PREPARE REMOVEFILE a/b/9/97/971/97xtext1
+PREPARE REMOVEDIR a/b/9/97/971/
+PREPARE REMOVEDIR a/b/9/97/
+PREPARE REMOVEFILE a/b/9/96/96text0
+PREPARE REMOVEFILE a/b/9/96/96text1
+PREPARE REMOVEDIR a/b/9/96/
+PREPARE REMOVEDIR a/b/9/95/
+PREPARE REMOVEDIR a/b/9/95/
+PREPARE REMOVEDIR a/b/9/94/
+PREPARE REMOVEDIR a/b/9/94/
+PREPARE REMOVEDIR a/b/9/93/
+PREPARE REMOVEDIR a/b/9/92/
+PREPARE REMOVEDIR a/b/9/91/
+PREPARE REMOVEDIR a/b/9/90/
+PREPARE REMOVEDIR a/b/9/90/
+PREPARE REMOVEDIR a/b/8/89/
+PREPARE REMOVEDIR a/b/8/89/
+PREPARE REMOVEDIR a/b/8/88/
+PREPARE REMOVEFILE a/b/8/87/870/87xtext0
+PREPARE REMOVEFILE a/b/8/87/870/87xtext1
+PREPARE REMOVEDIR a/b/8/87/870/
+PREPARE REMOVEFILE a/b/8/87/871/87xtext0
+PREPARE REMOVEFILE a/b/8/87/871/87xtext1
+PREPARE REMOVEDIR a/b/8/87/871/
+PREPARE REMOVEDIR a/b/8/87/
+PREPARE REMOVEFILE a/b/8/86/86text0
+PREPARE REMOVEFILE a/b/8/86/86text1
+PREPARE REMOVEDIR a/b/8/86/
+PREPARE REMOVEDIR a/b/8/85/
+PREPARE REMOVEDIR a/b/8/85/
+PREPARE REMOVEDIR a/b/8/84/
+PREPARE REMOVEDIR a/b/8/84/
+PREPARE REMOVEDIR a/b/8/83/
+PREPARE REMOVEDIR a/b/8/82/
+PREPARE REMOVEDIR a/b/8/81/
+PREPARE REMOVEDIR a/b/8/80/
+PREPARE REMOVEDIR a/b/8/80/
+PREPARE REMOVEFILE a/b/7/70/7xtest.exe
+PREPARE REMOVEFILE a/b/7/70/7xtext0
+PREPARE REMOVEFILE a/b/7/70/7xtext1
+PREPARE REMOVEDIR a/b/7/70/
+PREPARE REMOVEFILE a/b/7/71/7xtest.exe
+PREPARE REMOVEFILE a/b/7/71/7xtext0
+PREPARE REMOVEFILE a/b/7/71/7xtext1
+PREPARE REMOVEDIR a/b/7/71/
+PREPARE REMOVEDIR a/b/7/
+PREPARE REMOVEDIR a/b/6/
+PREPARE REMOVEFILE a/b/5/5text1
+PREPARE REMOVEFILE a/b/5/5text0
+PREPARE REMOVEFILE a/b/5/5test.exe
+PREPARE REMOVEFILE a/b/5/5text0
+PREPARE REMOVEFILE a/b/5/5text1
+PREPARE REMOVEDIR a/b/5/
+PREPARE REMOVEFILE a/b/4/4text1
+PREPARE REMOVEFILE a/b/4/4text0
+PREPARE REMOVEDIR a/b/4/
+PREPARE REMOVEFILE a/b/3/3text1
+PREPARE REMOVEFILE a/b/3/3text0
+PREPARE REMOVEDIR a/b/1/10/
+PREPARE REMOVEDIR a/b/1/
+EXECUTE ADD precomplete
+EXECUTE ADD a/b/searchplugins/searchpluginstext0
+EXECUTE PATCH a/b/searchplugins/searchpluginspng1.png
+EXECUTE PATCH a/b/searchplugins/searchpluginspng0.png
+EXECUTE ADD a/b/extensions/extensions1/extensions1text0
+EXECUTE PATCH a/b/extensions/extensions1/extensions1png1.png
+EXECUTE PATCH a/b/extensions/extensions1/extensions1png0.png
+EXECUTE ADD a/b/extensions/extensions0/extensions0text0
+EXECUTE PATCH a/b/extensions/extensions0/extensions0png1.png
+EXECUTE PATCH a/b/extensions/extensions0/extensions0png0.png
+EXECUTE PATCH a/b/exe0.exe
+EXECUTE PATCH a/b/0/0exe0.exe
+EXECUTE ADD a/b/0/00/00text0
+EXECUTE PATCH a/b/0/00/00png0.png
+EXECUTE ADD a/b/2/20/20text0
+EXECUTE ADD a/b/2/20/20png0.png
+EXECUTE ADD a/b/0/00/00text2
+EXECUTE REMOVEFILE a/b/1/10/10text0
+EXECUTE REMOVEFILE a/b/0/00/00text1
+EXECUTE REMOVEDIR a/b/9/99/
+EXECUTE REMOVEDIR a/b/9/99/
+EXECUTE REMOVEDIR a/b/9/98/
+EXECUTE REMOVEFILE a/b/9/97/970/97xtext0
+EXECUTE REMOVEFILE a/b/9/97/970/97xtext1
+EXECUTE REMOVEDIR a/b/9/97/970/
+EXECUTE REMOVEFILE a/b/9/97/971/97xtext0
+EXECUTE REMOVEFILE a/b/9/97/971/97xtext1
+EXECUTE REMOVEDIR a/b/9/97/971/
+EXECUTE REMOVEDIR a/b/9/97/
+EXECUTE REMOVEFILE a/b/9/96/96text0
+EXECUTE REMOVEFILE a/b/9/96/96text1
+EXECUTE REMOVEDIR a/b/9/96/
+EXECUTE REMOVEDIR a/b/9/95/
+EXECUTE REMOVEDIR a/b/9/95/
+EXECUTE REMOVEDIR a/b/9/94/
+EXECUTE REMOVEDIR a/b/9/94/
+EXECUTE REMOVEDIR a/b/9/93/
+EXECUTE REMOVEDIR a/b/9/92/
+EXECUTE REMOVEDIR a/b/9/91/
+EXECUTE REMOVEDIR a/b/9/90/
+EXECUTE REMOVEDIR a/b/9/90/
+EXECUTE REMOVEDIR a/b/8/89/
+EXECUTE REMOVEDIR a/b/8/89/
+EXECUTE REMOVEDIR a/b/8/88/
+EXECUTE REMOVEFILE a/b/8/87/870/87xtext0
+EXECUTE REMOVEFILE a/b/8/87/870/87xtext1
+EXECUTE REMOVEDIR a/b/8/87/870/
+EXECUTE REMOVEFILE a/b/8/87/871/87xtext0
+EXECUTE REMOVEFILE a/b/8/87/871/87xtext1
+EXECUTE REMOVEDIR a/b/8/87/871/
+EXECUTE REMOVEDIR a/b/8/87/
+EXECUTE REMOVEFILE a/b/8/86/86text0
+EXECUTE REMOVEFILE a/b/8/86/86text1
+EXECUTE REMOVEDIR a/b/8/86/
+EXECUTE REMOVEDIR a/b/8/85/
+EXECUTE REMOVEDIR a/b/8/85/
+EXECUTE REMOVEDIR a/b/8/84/
+EXECUTE REMOVEDIR a/b/8/84/
+EXECUTE REMOVEDIR a/b/8/83/
+EXECUTE REMOVEDIR a/b/8/82/
+EXECUTE REMOVEDIR a/b/8/81/
+EXECUTE REMOVEDIR a/b/8/80/
+EXECUTE REMOVEDIR a/b/8/80/
+EXECUTE REMOVEFILE a/b/7/70/7xtest.exe
+EXECUTE REMOVEFILE a/b/7/70/7xtext0
+EXECUTE REMOVEFILE a/b/7/70/7xtext1
+EXECUTE REMOVEDIR a/b/7/70/
+EXECUTE REMOVEFILE a/b/7/71/7xtest.exe
+EXECUTE REMOVEFILE a/b/7/71/7xtext0
+EXECUTE REMOVEFILE a/b/7/71/7xtext1
+EXECUTE REMOVEDIR a/b/7/71/
+EXECUTE REMOVEDIR a/b/7/
+EXECUTE REMOVEDIR a/b/6/
+EXECUTE REMOVEFILE a/b/5/5text1
+EXECUTE REMOVEFILE a/b/5/5text0
+EXECUTE REMOVEFILE a/b/5/5test.exe
+EXECUTE REMOVEFILE a/b/5/5text0
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEFILE a/b/5/5text1
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEDIR a/b/5/
+EXECUTE REMOVEFILE a/b/4/4text1
+EXECUTE REMOVEFILE a/b/4/4text0
+EXECUTE REMOVEDIR a/b/4/
+EXECUTE REMOVEFILE a/b/3/3text1
+EXECUTE REMOVEFILE a/b/3/3text0
+EXECUTE REMOVEDIR a/b/1/10/
+EXECUTE REMOVEDIR a/b/1/
+FINISH ADD precomplete
+FINISH ADD a/b/searchplugins/searchpluginstext0
+FINISH PATCH a/b/searchplugins/searchpluginspng1.png
+FINISH PATCH a/b/searchplugins/searchpluginspng0.png
+FINISH ADD a/b/extensions/extensions1/extensions1text0
+FINISH PATCH a/b/extensions/extensions1/extensions1png1.png
+FINISH PATCH a/b/extensions/extensions1/extensions1png0.png
+FINISH ADD a/b/extensions/extensions0/extensions0text0
+FINISH PATCH a/b/extensions/extensions0/extensions0png1.png
+FINISH PATCH a/b/extensions/extensions0/extensions0png0.png
+FINISH PATCH a/b/exe0.exe
+FINISH PATCH a/b/0/0exe0.exe
+FINISH ADD a/b/0/00/00text0
+FINISH PATCH a/b/0/00/00png0.png
+FINISH ADD a/b/2/20/20text0
+FINISH ADD a/b/2/20/20png0.png
+FINISH ADD a/b/0/00/00text2
+FINISH REMOVEFILE a/b/1/10/10text0
+FINISH REMOVEFILE a/b/0/00/00text1
+FINISH REMOVEDIR a/b/9/99/
+FINISH REMOVEDIR a/b/9/99/
+directory no longer exists; skipping
+FINISH REMOVEDIR a/b/9/98/
+FINISH REMOVEFILE a/b/9/97/970/97xtext0
+FINISH REMOVEFILE a/b/9/97/970/97xtext1
+FINISH REMOVEDIR a/b/9/97/970/
+FINISH REMOVEFILE a/b/9/97/971/97xtext0
+FINISH REMOVEFILE a/b/9/97/971/97xtext1
+FINISH REMOVEDIR a/b/9/97/971/
+FINISH REMOVEDIR a/b/9/97/
+FINISH REMOVEFILE a/b/9/96/96text0
+FINISH REMOVEFILE a/b/9/96/96text1
+FINISH REMOVEDIR a/b/9/96/
+FINISH REMOVEDIR a/b/9/95/
+FINISH REMOVEDIR a/b/9/95/
+directory no longer exists; skipping
+FINISH REMOVEDIR a/b/9/94/
+FINISH REMOVEDIR a/b/9/94/
+directory no longer exists; skipping
+FINISH REMOVEDIR a/b/9/93/
+FINISH REMOVEDIR a/b/9/92/
+removing directory: a/b/9/92/, rv: 0
+FINISH REMOVEDIR a/b/9/91/
+removing directory: a/b/9/91/, rv: 0
+FINISH REMOVEDIR a/b/9/90/
+FINISH REMOVEDIR a/b/9/90/
+directory no longer exists; skipping
+FINISH REMOVEDIR a/b/8/89/
+FINISH REMOVEDIR a/b/8/89/
+directory no longer exists; skipping
+FINISH REMOVEDIR a/b/8/88/
+FINISH REMOVEFILE a/b/8/87/870/87xtext0
+FINISH REMOVEFILE a/b/8/87/870/87xtext1
+FINISH REMOVEDIR a/b/8/87/870/
+FINISH REMOVEFILE a/b/8/87/871/87xtext0
+FINISH REMOVEFILE a/b/8/87/871/87xtext1
+FINISH REMOVEDIR a/b/8/87/871/
+FINISH REMOVEDIR a/b/8/87/
+FINISH REMOVEFILE a/b/8/86/86text0
+FINISH REMOVEFILE a/b/8/86/86text1
+FINISH REMOVEDIR a/b/8/86/
+FINISH REMOVEDIR a/b/8/85/
+FINISH REMOVEDIR a/b/8/85/
+directory no longer exists; skipping
+FINISH REMOVEDIR a/b/8/84/
+FINISH REMOVEDIR a/b/8/84/
+directory no longer exists; skipping
+FINISH REMOVEDIR a/b/8/83/
+FINISH REMOVEDIR a/b/8/82/
+removing directory: a/b/8/82/, rv: 0
+FINISH REMOVEDIR a/b/8/81/
+removing directory: a/b/8/81/, rv: 0
+FINISH REMOVEDIR a/b/8/80/
+FINISH REMOVEDIR a/b/8/80/
+directory no longer exists; skipping
+FINISH REMOVEFILE a/b/7/70/7xtest.exe
+FINISH REMOVEFILE a/b/7/70/7xtext0
+FINISH REMOVEFILE a/b/7/70/7xtext1
+FINISH REMOVEDIR a/b/7/70/
+FINISH REMOVEFILE a/b/7/71/7xtest.exe
+FINISH REMOVEFILE a/b/7/71/7xtext0
+FINISH REMOVEFILE a/b/7/71/7xtext1
+FINISH REMOVEDIR a/b/7/71/
+FINISH REMOVEDIR a/b/7/
+FINISH REMOVEDIR a/b/6/
+FINISH REMOVEFILE a/b/5/5text1
+FINISH REMOVEFILE a/b/5/5text0
+FINISH REMOVEFILE a/b/5/5test.exe
+FINISH REMOVEDIR a/b/5/
+FINISH REMOVEFILE a/b/4/4text1
+FINISH REMOVEFILE a/b/4/4text0
+FINISH REMOVEDIR a/b/4/
+FINISH REMOVEFILE a/b/3/3text1
+FINISH REMOVEFILE a/b/3/3text0
+FINISH REMOVEDIR a/b/1/10/
+FINISH REMOVEDIR a/b/1/
+succeeded
+calling QuitProgressUI
+rename_file: proceeding to rename the directory
+rename_file: proceeding to rename the directory
+Now, remove the tmpDir
+succeeded
+calling QuitProgressUI
\ No newline at end of file
--- a/toolkit/mozapps/update/test/unit/head_update.js.in
+++ b/toolkit/mozapps/update/test/unit/head_update.js.in
@@ -26,74 +26,90 @@ const IS_OS2 = false;
 
 #ifdef XP_MACOSX
 const IS_MACOSX = true;
 #ifdef MOZ_SHARK
 const IS_SHARK = true;
 #else
 const IS_SHARK = false;
 #endif
+#ifdef MOZ_DEBUG
+const BUNDLE_NAME = "@MOZ_APP_DISPLAYNAME@Debug.app";
+#else
+const BUNDLE_NAME = "@MOZ_APP_DISPLAYNAME@.app";
+#endif
 #else
 const IS_MACOSX = false;
 #endif
 
 #ifdef XP_UNIX
 const IS_UNIX = true;
 #else
 const IS_UNIX = false;
 #endif
 
 #ifdef ANDROID
 const IS_ANDROID = true;
 #else
 const IS_ANDROID = false;
 #endif
 
+const USE_EXECV = IS_UNIX && !IS_MACOSX;
 
 #ifdef MOZ_VERIFY_MAR_SIGNATURE
 const IS_MAR_CHECKS_ENABLED = true;
 #else
 const IS_MAR_CHECKS_ENABLED = false;
 #endif
 
 const URL_HOST = "http://localhost:4444/";
 const URL_PATH = "data";
 
 const APPLY_TO_DIR_SUFFIX = "_applyToDir/";
+#ifdef XP_MACOSX
+const UPDATED_DIR_SUFFIX = "Updated.app/";
+#else
+const UPDATED_DIR_SUFFIX = "updated/";
+#endif
 const HELPER_BIN_FILE = "TestAUSHelper" + BIN_SUFFIX;
 const MAR_COMPLETE_FILE = "data/complete.mar";
 const MAR_PARTIAL_FILE = "data/partial.mar";
 const MAR_OLD_VERSION_FILE = "data/old_version_mar.mar";
 const MAR_WRONG_CHANNEL_FILE = "data/wrong_product_channel_mar.mar";
 const UPDATER_BIN_FILE = "updater" + BIN_SUFFIX;
 const MAINTENANCE_SERVICE_BIN_FILE = "maintenanceservice.exe";
 const MAINTENANCE_SERVICE_INSTALLER_BIN_FILE = "maintenanceservice_installer.exe";
 const UPDATE_SETTINGS_INI_FILE = "update-settings.ini";
 const UPDATE_SETTINGS_CONTENTS = "[Settings]\n" +
                                  "ACCEPTED_MAR_CHANNEL_IDS=xpcshell-test\n"
 const UPDATES_DIR_SUFFIX = "_mar";
 
 const LOG_COMPLETE_SUCCESS = "data/complete_log_success";
+const LOG_COMPLETE_SWITCH_SUCCESS = "data/complete_log_switch_success"
 const LOG_COMPLETE_CC_SUCCESS = "data/complete_cc_log_success";
+const LOG_COMPLETE_CC_SWITCH_SUCCESS = "data/complete_cc_log_switch_success";
 
 const LOG_PARTIAL_SUCCESS = "data/partial_log_success";
+const LOG_PARTIAL_SWITCH_SUCCESS = "data/partial_log_switch_success";
 const LOG_PARTIAL_FAILURE = "data/partial_log_failure";
 
 const ERR_CALLBACK_FILE_IN_USE = "NS_main: file in use - failed to " +
                                  "exclusively open executable file:"
 
 const ERR_RENAME_FILE = "rename_file: failed to rename file";
 const ERR_UNABLE_OPEN_DEST = "unable to open destination file";
 const ERR_BACKUP_DISCARD = "backup_discard: unable to remove";
 
-const LOG_SVC_SUCCESSFUL_LAUNCH = "updater.exe was launched and run successfully!";
+const LOG_SVC_SUCCESSFUL_LAUNCH = "Process was started... waiting on result.";
 
 // variables are used instead of contants so tests can override these values
 var gCallbackBinFile = "callback_app" + BIN_SUFFIX;
 var gCallbackArgs = ["./", "callback.log", "Test Arg 2", "Test Arg 3"];
+var gBackgroundUpdate = false;
+var gSwitchApp = false;
 
 // Time to wait for the test helper process before continuing the test
 const TEST_HELPER_TIMEOUT = 100;
 
 // Use a copy of the main application executable for the test to avoid main
 // executable in use errors.
 const FILE_WIN_TEST_EXE = "_aus_test_app.exe";
 
@@ -271,16 +287,18 @@ var ADDITIONAL_TEST_DIRS = [];
 
 // Set to true to log additional information for debugging. To log additional
 // information for an individual test set DEBUG_AUS_TEST to true in the test's
 // run_test function.
 var DEBUG_AUS_TEST = true;
 
 #include ../shared.js
 
+const STATE_APPLIED_PLATFORM = IS_WIN ? STATE_APPLIED_SVC : STATE_APPLIED;
+
 /**
  * Nulls out the most commonly used global vars used by tests as appropriate.
  */
 function cleanUp() {
   removeUpdateDirsAndFiles();
 
   // Force the update manager to reload the update data to prevent it from
   // writing the old data to the files that have just been removed.
@@ -288,18 +306,16 @@ function cleanUp() {
 
   // Call app update's observe method passing xpcom-shutdown to test that the
   // shutdown of app update runs without throwing or leaking. The observer
   // method is used directly instead of calling notifyObservers so components
   // outside of the scope of this test don't assert and thereby cause app update
   // tests to fail.
   gAUS.observe(null, "xpcom-shutdown", "");
 
-  Services.dirsvc.unregisterProvider(gDirProvider);
-
   if (gXHR) {
     gXHRCallback     = null;
 
     gXHR.responseXML = null;
     // null out the event handlers to prevent a mFreeCount leak of 1
     gXHR.onerror     = null;
     gXHR.onload      = null;
     gXHR.onprogress  = null;
@@ -367,16 +383,73 @@ function getApplyDirPath() {
  * @return  The nsIFile for the directory where the update will be applied.
  */
 function getApplyDirFile(aRelPath, allowNonexistent) {
   let relpath = getApplyDirPath() + (aRelPath ? aRelPath : "");
   return do_get_file(relpath, allowNonexistent);
 }
 
 /**
+ * Helper function for getting the updated directory.
+ */
+function getUpdatedDirPath() {
+  let suffix = "";
+  if (gBackgroundUpdate) {
+    suffix = UPDATED_DIR_SUFFIX;
+  }
+  return getApplyDirPath() + suffix;
+}
+
+/**
+ * Helper function for getting the nsIFile for the directory where the update
+ * has been applied.
+ *
+ * This will be the same as getApplyDirFile for foreground updates, but will
+ * point to a different file for the case of background updates.
+ *
+ * Functions which attempt to access the files in the updated directory should
+ * be using this instead of getApplyDirFile.
+ *
+ * @return  The nsIFile for the directory where the update has been applied.
+ */
+function getTargetDirFile(aRelPath, allowNonexistent) {
+  let relpath = getUpdatedDirPath() + (aRelPath ? aRelPath : "");
+  return do_get_file(relpath, allowNonexistent);
+}
+
+if (IS_WIN) {
+  const kLockFileName = "updated.update_in_progress.lock";
+  /**
+   * Helper function for locking a directory on Windows.
+   */
+  function lockDirectory(aDir) {
+    var file = aDir.clone();
+    file.append(kLockFileName);
+    file.create(file.NORMAL_FILE_TYPE, 0444);
+    file.QueryInterface(AUS_Ci.nsILocalFileWin);
+    file.fileAttributesWin |= file.WFA_READONLY;
+    file.fileAttributesWin &= ~file.WFA_READWRITE;
+    do_check_true(file.exists());
+    do_check_false(file.isWritable());
+  }
+  /**
+   * Helper function for unlocking a directory on Windows.
+   */
+  function unlockDirectory(aDir) {
+    var file = aDir.clone();
+    file.append(kLockFileName);
+    file.QueryInterface(AUS_Ci.nsILocalFileWin);
+    file.fileAttributesWin |= file.WFA_READWRITE;
+    file.fileAttributesWin &= ~file.WFA_READONLY;
+    file.remove(false);
+    do_check_false(file.exists());
+  }
+}
+
+/**
  * Helper function for updater tests for launching the updater binary to apply
  * a mar file.
  *
  * @return  The exit value returned from the updater binary.
  */
 function runUpdate() {
   // Copy the updater binary to the updates directory.
   let binDir = getGREDir();
@@ -403,16 +476,23 @@ function runUpdate() {
   }
 
   let updatesDirPath = updatesDir.path;
   if (/ /.test(updatesDirPath))
     updatesDirPath = '"' + updatesDirPath + '"';
 
   let applyToDir = getApplyDirFile();
   let applyToDirPath = applyToDir.path;
+  if (gBackgroundUpdate || gSwitchApp) {
+    applyToDirPath += "/" + UPDATED_DIR_SUFFIX;
+  }
+  if (IS_WIN) {
+    // Convert to native path
+    applyToDirPath = applyToDirPath.replace(/\//g, "\\");
+  }
   if (/ /.test(applyToDirPath))
     applyToDirPath = '"' + applyToDirPath + '"';
 
   let callbackApp = getApplyDirFile("a/b/" + gCallbackBinFile);
   callbackApp.permissions = PERMS_DIRECTORY;
 
   let cwdPath = callbackApp.parent.path;
   if (/ /.test(cwdPath))
@@ -421,18 +501,28 @@ function runUpdate() {
   let callbackAppPath = callbackApp.path;
   if (/ /.test(callbackAppPath))
     callbackAppPath = '"' + callbackAppPath + '"';
 
   let updateSettingsIni = getApplyDirFile(null, true);
   updateSettingsIni.append(UPDATE_SETTINGS_INI_FILE);
   writeFile(updateSettingsIni, UPDATE_SETTINGS_CONTENTS);
 
-  let args = [updatesDirPath, applyToDirPath, 0, cwdPath, callbackAppPath].
-             concat(gCallbackArgs);
+  let args = [updatesDirPath, applyToDirPath, 0];
+  if (gBackgroundUpdate) {
+    args[2] = -1;
+  } else {
+    if (gSwitchApp) {
+      args[2] = "0/replace";
+    }
+    args = args.concat([cwdPath, callbackAppPath]);
+    args = args.concat(gCallbackArgs);
+  }
+  logTestInfo("Running the updater: " + updateBin.path + " " + args.join(" "));
+
   let process = AUS_Cc["@mozilla.org/process/util;1"].
                 createInstance(AUS_Ci.nsIProcess);
   process.init(updateBin);
   process.run(true, args, args.length);
   return process.exitValue;
 }
 
 let gServiceLaunchedCallbackLog = null;
@@ -495,28 +585,33 @@ function shouldRunServiceTest(aFirstTest
   // If this is the first test in the series, then there is no reason the
   // service should be anything but stopped, so be strict here and throw
   // an error.
   if (aFirstTest && process.exitValue != 0) {
     do_throw("First test, check for service stopped state returned error " + 
              process.exitValue);
   }
 
+#ifdef DISABLE_UPDATER_AUTHENTICODE_CHECK
+  // We won't be performing signature checks.
+  return true;
+#else
   // Make sure the binaries are signed
   args = ["check-signature", updaterBinPath];
   process = AUS_Cc["@mozilla.org/process/util;1"].
             createInstance(AUS_Ci.nsIProcess);
   process.init(helperBin);
   process.run(true, args, args.length);
   if (process.exitValue == 0) {
     return true;
   }
   logTestInfo("this test can only run on builds with signed binaries. " +
               HELPER_BIN_FILE + " returned " + process.exitValue)
   return false;
+#endif
 }
 
 /**
  * Copies the specified filename from the dist/bin
  * directory into the apply-to directory.
  *
  * @param filename The name of the file to copy
 */
@@ -713,16 +808,21 @@ function runUpdateUsingService(aInitialS
   let process = AUS_Cc["@mozilla.org/process/util;1"].
                    createInstance(AUS_Ci.nsIProcess);
   process.init(launchBin);
 
   // Override the update root directory
   gEnvUpdateRootOverride = updatesDir.path;
   gEnvAppDirOverride = getApplyDirFile(null).path;
 
+  if (gSwitchApp) {
+    // We want to set the env vars again
+    gShouldResetEnv = undefined;
+  }
+
   setEnvironment();
 
   // There is a security check done by the service to make sure the updater 
   // we are executing is the same as the one in the apply-to dir. 
   // To make sure they match from tests we copy updater.exe to the apply-to dir.
   copyBinToApplyToDir(UPDATER_BIN_FILE);
 
   // The service will execute maintenanceservice_installer.exe and
@@ -923,23 +1023,25 @@ function setupUpdaterTest(aMarFile) {
       }
     }
   });
 
   let helperBin = do_get_file(HELPER_BIN_FILE);
   let afterApplyBinDir = getApplyDirFile("a/b/", true);
   helperBin.copyTo(afterApplyBinDir, gCallbackBinFile);
 
-  let updaterIniContents = "[Strings]\n" +
-                           "Title=Update XPCShell Test\n" +
-                           "Info=Application Update Test - " + TEST_ID + "\n";
-  let updaterIni = updatesDir.clone();
-  updaterIni.append(FILE_UPDATER_INI);
-  writeFile(updaterIni, updaterIniContents);
-  updaterIni.copyTo(afterApplyBinDir, FILE_UPDATER_INI);
+  if (!gBackgroundUpdate && !gSwitchApp) {
+    let updaterIniContents = "[Strings]\n" +
+                             "Title=Update XPCShell Test\n" +
+                             "Info=Application Update Test - " + TEST_ID + "\n";
+    let updaterIni = updatesDir.clone();
+    updaterIni.append(FILE_UPDATER_INI);
+    writeFile(updaterIni, updaterIniContents);
+    updaterIni.copyTo(afterApplyBinDir, FILE_UPDATER_INI);
+  }
 
   // Copy the mar that will be applied
   let mar = do_get_file(aMarFile);
   mar.copyTo(updatesDir, FILE_UPDATE_ARCHIVE);
 
   // Add the test directory that will be updated for a successful update or left in
   // the initial state for a failed update.
   var testDirs = TEST_DIRS.concat(ADDITIONAL_TEST_DIRS);
@@ -1009,29 +1111,56 @@ function cleanupUpdaterTest() {
 
 /**
  * Helper function for updater binary tests for verifying the contents of the
  * update log after a successful update.
  */
 function checkUpdateLogContents(aCompareLogFile) {
   let updateLog = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX, true);
   updateLog.append(FILE_UPDATE_LOG);
-  // Skip the first two lines since they contain absolute paths.
-  let updateLogContents = readFileBytes(updateLog).split("\n").slice(2).join("\n");
+  let updateLogContents = readFileBytes(updateLog);
+  if (gBackgroundUpdate) {
+    // Skip the background update messages
+    updateLogContents = updateLogContents.replace(/Performing a background update/, "");
+  } else if (gSwitchApp) {
+    // Skip the switch app request messages
+    updateLogContents = updateLogContents.replace(/Performing a background update/, "");
+    updateLogContents = updateLogContents.replace(/Performing a replace request/, "");
+  }
+  // Skip the source/destination lines since they contain absolute paths.
+  updateLogContents = updateLogContents.replace(/SOURCE DIRECTORY.*/g, "");
+  updateLogContents = updateLogContents.replace(/DESTINATION DIRECTORY.*/g, "");
+  if (gSwitchApp) {
+    // Remove the lines which contain absolute paths
+    updateLogContents = updateLogContents.replace(/^Begin moving.*$/mg, "");
+#ifdef XP_MACOSX
+    // Remove the entire section about moving the precomplete file as it contains
+    // absolute paths.
+    updateLogContents = updateLogContents.replace(/\n/g, "%%%EOL%%%");
+    updateLogContents = updateLogContents.replace(/Moving the precomplete file.*Finished moving the precomplete file/, "");
+    updateLogContents = updateLogContents.replace(/%%%EOL%%%/g, "\n");
+#endif
+  }
   updateLogContents = updateLogContents.replace(/\r/g, "");
   // Replace error codes since they are different on each platform.
   updateLogContents = updateLogContents.replace(/, err:.*\n/g, "\n");
   // Replace to make the log parsing happy.
   updateLogContents = updateLogContents.replace(/non-fatal error /g, "");
   // The FindFile results when enumerating the filesystem on Windows is not
   // determistic so the results matching the following need to be ignored.
   updateLogContents = updateLogContents.replace(/.* a\/b\/7\/7text.*\n/g, "");
+  // Remove consecutive newlines
+  updateLogContents = updateLogContents.replace(/\n+/g, "\n");
+  // Remove leading and trailing newlines
+  updateLogContents = updateLogContents.replace(/^\n|\n$/g, "");
 
   let compareLog = do_get_file(aCompareLogFile);
   let compareLogContents = readFileBytes(compareLog);
+  // Remove leading and trailing newlines
+  compareLogContents = compareLogContents.replace(/^\n|\n$/g, "");
 
   do_check_eq(compareLogContents, updateLogContents);
 }
 
 function checkUpdateLogContains(aCheckString) {
   let updateLog = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX, true);
   updateLog.append(FILE_UPDATE_LOG);
   let updateLogContents = readFileBytes(updateLog);
@@ -1041,17 +1170,17 @@ function checkUpdateLogContains(aCheckSt
 
 /**
  * Helper function for updater binary tests for verifying the state of files and
  * directories after a successful update.
  */
 function checkFilesAfterUpdateSuccess() {
   logTestInfo("testing contents of files after a successful update");
   TEST_FILES.forEach(function CFAUS_TF_FE(aTestFile) {
-    let testFile = getApplyDirFile(aTestFile.relPathDir + aTestFile.fileName, true);
+    let testFile = getTargetDirFile(aTestFile.relPathDir + aTestFile.fileName, true);
     logTestInfo("testing file: " + testFile.path);
     if (aTestFile.compareFile || aTestFile.compareContents) {
       do_check_true(testFile.exists());
 
       // Skip these tests on Windows and OS/2 since their
       // implementaions of chmod doesn't really set permissions.
       if (!IS_WIN && !IS_OS2 && aTestFile.comparePerms) {
         // Check if the permssions as set in the complete mar file are correct.
@@ -1077,65 +1206,69 @@ function checkFilesAfterUpdateSuccess() 
       do_check_false(testFile.exists());
     }
   });
 
   logTestInfo("testing operations specified in removed-files were performed " +
               "after a successful update");
   var testDirs = TEST_DIRS.concat(ADDITIONAL_TEST_DIRS);
   testDirs.forEach(function CFAUS_TD_FE(aTestDir) {
-    let testDir = getApplyDirFile(aTestDir.relPathDir, true);
+    let testDir = getTargetDirFile(aTestDir.relPathDir, true);
     logTestInfo("testing directory: " + testDir.path);
     if (aTestDir.dirRemoved) {
       do_check_false(testDir.exists());
     }
     else {
       do_check_true(testDir.exists());
 
       if (aTestDir.files) {
         aTestDir.files.forEach(function CFAUS_TD_F_FE(aTestFile) {
-          let testFile = getApplyDirFile(aTestDir.relPathDir + aTestFile, true);
+          let testFile = getTargetDirFile(aTestDir.relPathDir + aTestFile, true);
           logTestInfo("testing directory file: " + testFile.path);
           if (aTestDir.filesRemoved) {
             do_check_false(testFile.exists());
           }
           else {
             do_check_true(testFile.exists());
           }
         });
       }
 
       if (aTestDir.subDirs) {
         aTestDir.subDirs.forEach(function CFAUS_TD_SD_FE(aSubDir) {
-          let testSubDir = getApplyDirFile(aTestDir.relPathDir + aSubDir, true);
+          let testSubDir = getTargetDirFile(aTestDir.relPathDir + aSubDir, true);
           logTestInfo("testing sub-directory: " + testSubDir.path);
           do_check_true(testSubDir.exists());
           if (aTestDir.subDirFiles) {
             aTestDir.subDirFiles.forEach(function CFAUS_TD_SDF_FE(aTestFile) {
-              let testFile = getApplyDirFile(aTestDir.relPathDir + aSubDir + aTestFile, true);
+              let testFile = getTargetDirFile(aTestDir.relPathDir + aSubDir + aTestFile, true);
               logTestInfo("testing sub-directory file: " + testFile.path);
               do_check_true(testFile.exists());
             });
           }
         });
       }
     }
   });
 
   checkFilesAfterUpdateCommon();
 }
 
 /**
  * Helper function for updater binary tests for verifying the state of files and
  * directories after a failed update.
+ *
+ * @param aGetDirectory: the function used to get the files in the target directory.
+ * Pass getApplyDirFile if you want to test the case of a failed switch request.
  */
-function checkFilesAfterUpdateFailure() {
+function checkFilesAfterUpdateFailure(aGetDirectory) {
+  let getdir = aGetDirectory || getTargetDirFile;
   logTestInfo("testing contents of files after a failed update");
   TEST_FILES.forEach(function CFAUF_TF_FE(aTestFile) {
-    let testFile = getApplyDirFile(aTestFile.relPathDir + aTestFile.fileName, true);
+    let testFile = getdir(aTestFile.relPathDir + aTestFile.fileName, true);
     logTestInfo("testing file: " + testFile.path);
     if (aTestFile.compareFile || aTestFile.compareContents) {
       do_check_true(testFile.exists());
 
       // Skip these tests on Windows and OS/2 since their
       // implementaions of chmod doesn't really set permissions.
       if (!IS_WIN && !IS_OS2 && aTestFile.comparePerms) {
         // Check the original permssions are retained on the file.
@@ -1160,36 +1293,36 @@ function checkFilesAfterUpdateFailure() 
     else {
       do_check_false(testFile.exists());
     }
   });
 
   logTestInfo("testing operations specified in removed-files were not " +
               "performed after a failed update");
   TEST_DIRS.forEach(function CFAUF_TD_FE(aTestDir) {
-    let testDir = getApplyDirFile(aTestDir.relPathDir, true);
+    let testDir = getdir(aTestDir.relPathDir, true);
     logTestInfo("testing directory file: " + testDir.path);
     do_check_true(testDir.exists());
 
     if (aTestDir.files) {
       aTestDir.files.forEach(function CFAUS_TD_F_FE(aTestFile) {
-        let testFile = getApplyDirFile(aTestDir.relPathDir + aTestFile, true);
+        let testFile = getdir(aTestDir.relPathDir + aTestFile, true);
         logTestInfo("testing directory file: " + testFile.path);
         do_check_true(testFile.exists());
       });
     }
 
     if (aTestDir.subDirs) {
       aTestDir.subDirs.forEach(function CFAUS_TD_SD_FE(aSubDir) {
-        let testSubDir = getApplyDirFile(aTestDir.relPathDir + aSubDir, true);
+        let testSubDir = getdir(aTestDir.relPathDir + aSubDir, true);
         logTestInfo("testing sub-directory: " + testSubDir.path);
         do_check_true(testSubDir.exists());
         if (aTestDir.subDirFiles) {
           aTestDir.subDirFiles.forEach(function CFAUS_TD_SDF_FE(aTestFile) {
-            let testFile = getApplyDirFile(aTestDir.relPathDir + aSubDir + aTestFile, true);
+            let testFile = getdir(aTestDir.relPathDir + aSubDir + aTestFile, true);
             logTestInfo("testing sub-directory file: " + testFile.path);
             do_check_true(testFile.exists());
           });
         }
       });
     }
   });
 
@@ -1205,17 +1338,17 @@ function checkFilesAfterUpdateCommon() {
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX, true);
   let entries = updatesDir.QueryInterface(AUS_Ci.nsIFile).directoryEntries;
   while (entries.hasMoreElements()) {
     let entry = entries.getNext().QueryInterface(AUS_Ci.nsIFile);
     do_check_neq(getFileExtension(entry), "patch");
   }
 
   logTestInfo("testing backup files should not be left behind");
-  let applyToDir = getApplyDirFile(null, true);
+  let applyToDir = getTargetDirFile(null, true);
   checkFilesInDirRecursive(applyToDir, checkForBackupFiles);
 }
 
 /**
  * Helper function for updater binary tests for verifying the contents of the
  * updater callback application log which should contain the arguments passed to
  * the callback application.
  */
@@ -1267,40 +1400,38 @@ function checkCallbackServiceLog() {
     do_timeout(TEST_HELPER_TIMEOUT, checkCallbackServiceLog);
     return;
   }
 
   logTestInfo("testing that the callback application successfully launched " +
               "and the expected command line arguments passed to it");
   do_check_eq(logContents, expectedLogContents);
 
+  do_timeout(TEST_HELPER_TIMEOUT, do_test_finished);
+}
 
-  (function removeCallbackCopy() {
-    // Remove the copy of the application executable used for the test on
-    // Windows if it exists.
-    let appBinCopy = getCurrentProcessDir();
-    appBinCopy.append(TEST_ID + FILE_WIN_TEST_EXE);
-    if (appBinCopy.exists()) {
-      try {
-        appBinCopy.remove(false);
-        // Use a timeout to give any files that were in use additional 
-        // time to close.  Same as updater.exe without service tests.
-        do_timeout(TEST_HELPER_TIMEOUT, do_test_finished);
-      }
-      catch (e) {
-        logTestInfo("unable to remove file during cleanup. Exception: " + e);
-        do_timeout(TEST_HELPER_TIMEOUT, removeCallbackCopy);
-      }
-    } else {
+function removeCallbackCopy() {
+  // Remove the copy of the application executable used for the test on
+  // Windows if it exists.
+  let appBinCopy = getAppDir();
+  appBinCopy.append(TEST_ID + FILE_WIN_TEST_EXE);
+  if (appBinCopy.exists()) {
+    try {
+      appBinCopy.remove(false);
+      // Use a timeout to give any files that were in use additional 
+      // time to close.  Same as updater.exe without service tests.
       do_timeout(TEST_HELPER_TIMEOUT, do_test_finished);
     }
-  })();
-
-
-
+    catch (e) {
+      logTestInfo("unable to remove file during cleanup. Exception: " + e);
+      do_timeout(TEST_HELPER_TIMEOUT, removeCallbackCopy);
+    }
+  } else {
+    do_timeout(TEST_HELPER_TIMEOUT, do_test_finished);
+  }
 }
 
 /**
  * Helper function for updater binary tests for verifying there are no update
  * backup files left behind after an update.
  *
  * @param   aFile
  *          An nsIFile to check if it has moz-backup for its extension.
@@ -1515,16 +1646,19 @@ function createAppInfo(id, name, version
     inSafeMode: false,
     logConsoleErrors: true,
     OS: "XPCShell",
     XPCOMABI: "noarch-spidermonkey",
 
     QueryInterface: function QueryInterface(iid) {
       if (iid.equals(AUS_Ci.nsIXULAppInfo) ||
           iid.equals(AUS_Ci.nsIXULRuntime) ||
+#ifdef XP_WIN
+          iid.equals(AUS_Ci.nsIWinAppHelper) ||
+#endif
           iid.equals(AUS_Ci.nsISupports))
         return this;
       throw AUS_Cr.NS_ERROR_NO_INTERFACE;
     }
   };
   
   var XULAppInfoFactory = {
     createInstance: function (outer, iid) {
@@ -1534,35 +1668,16 @@ function createAppInfo(id, name, version
     }
   };
 
   var registrar = Components.manager.QueryInterface(AUS_Ci.nsIComponentRegistrar);
   registrar.registerFactory(XULAPPINFO_CID, "XULAppInfo",
                             XULAPPINFO_CONTRACTID, XULAppInfoFactory);
 }
 
-// On Vista XRE_UPDATE_ROOT_DIR can be a directory other than the one in the
-// application directory. This will reroute it back to the one in the
-// application directory.
-var gDirProvider = {
-  getFile: function DP_getFile(prop, persistent) {
-    persistent.value = true;
-    if (prop == XRE_UPDATE_ROOT_DIR)
-      return getCurrentProcessDir();
-    return null;
-  },
-  QueryInterface: function(iid) {
-    if (iid.equals(AUS_Ci.nsIDirectoryServiceProvider) ||
-        iid.equals(AUS_Ci.nsISupports))
-      return this;
-    throw AUS_Cr.NS_ERROR_NO_INTERFACE;
-  }
-};
-Services.dirsvc.QueryInterface(AUS_Ci.nsIDirectoryService).registerProvider(gDirProvider);
-
 /**
  * Returns the platform specific arguments used by nsIProcess when launching
  * the application.
  *
  * @param aExtraArgs optional array of extra arguments
  * @return  an array of arguments to be passed to nsIProcess.
  *
  * Notes:
@@ -1888,16 +2003,21 @@ function setEnvironment() {
   }
 
   if (gEnvAppDirOverride) {
     logTestInfo("setting the MOZ_UPDATE_APPDIR_OVERRIDE environment variable to " +
                 gEnvAppDirOverride + "\n");
     env.set("MOZ_UPDATE_APPDIR_OVERRIDE", gEnvAppDirOverride);
   }
 
+  if (gBackgroundUpdate) {
+    logTestInfo("setting the MOZ_UPDATE_BACKGROUND environment variable to 1\n");
+    env.set("MOZ_UPDATE_BACKGROUND", "1");
+  }
+
   logTestInfo("setting MOZ_NO_SERVICE_FALLBACK environment variable to 1");
   env.set("MOZ_NO_SERVICE_FALLBACK", "1");
 }
 
 /**
  * Sets the environment back to the original values after launching the
  * application.
  */
@@ -1965,11 +2085,16 @@ function resetEnvironment() {
   }
 
   if (gEnvAppDirOverride) {
     logTestInfo("removing the MOZ_UPDATE_APPDIR_OVERRIDE environment variable\n");
     env.set("MOZ_UPDATE_APPDIR_OVERRIDE", "");
     gEnvAppDirOverride = null;
   }
 
+  if (gBackgroundUpdate) {
+    logTestInfo("removing the MOZ_UPDATE_BACKGROUND environment variable\n");
+    env.set("MOZ_UPDATE_BACKGROUND", "");
+  }
+
   logTestInfo("removing MOZ_NO_SERVICE_FALLBACK environment variable");
   env.set("MOZ_NO_SERVICE_FALLBACK", "");
 }
--- a/toolkit/mozapps/update/test/unit/test_0030_general.js
+++ b/toolkit/mozapps/update/test/unit/test_0030_general.js
@@ -7,16 +7,17 @@
 
 var gNextRunFunc;
 var gStatusResult;
 var gExpectedStatusResult;
 
 function run_test() {
   do_test_pending();
   do_register_cleanup(end_test);
+  Services.prefs.setBoolPref(PREF_APP_UPDATE_BACKGROUND, false);
   removeUpdateDirsAndFiles();
   setUpdateURLOverride();
   // The mock XMLHttpRequest is MUCH faster
   overrideXHR(callHandleEvent);
   standardInit();
   // The HTTP server is only used for the mar file downloads which is slow
   start_httpserver(URL_PATH);
   do_execute_soon(run_test_pt1);
--- a/toolkit/mozapps/update/test/unit/test_0063_manager.js
+++ b/toolkit/mozapps/update/test/unit/test_0063_manager.js
@@ -33,18 +33,17 @@ function run_test() {
 
   do_check_eq(gUpdateManager.updateCount, 1);
   update = gUpdateManager.getUpdateAt(0);
   do_check_eq(update.name, "Existing");
 
   do_check_eq(gUpdateManager.activeUpdate, null);
   // Verify that the active-update.xml file has had the update from the old
   // channel removed.
-  file = getCurrentProcessDir();
-  file.append(FILE_UPDATE_ACTIVE);
+  file = getUpdatesXMLFile(true);
   logTestInfo("verifying contents of " + FILE_UPDATE_ACTIVE);
   do_check_eq(readFile(file), getLocalUpdatesXMLString(""));
 
   do_test_finished();
 }
 
 function end_test() {
   cleanUp();
--- a/toolkit/mozapps/update/test/unit/test_0112_general.js
+++ b/toolkit/mozapps/update/test/unit/test_0112_general.js
@@ -244,19 +244,20 @@ function run_test() {
   if (IS_MACOSX) {
     let now = Date.now();
     let yesterday = now - (1000 * 60 * 60 * 24);
     applyToDir.lastModifiedTime = yesterday;
   }
 
   // apply the partial mar
   let exitValue = runUpdate();
-  logTestInfo("testing updater binary process exitValue for success when " +
+  logTestInfo("testing updater binary process exitValue for failure when " +
               "applying a partial mar");
-  do_check_eq(exitValue, 0);
+  // Note that on platforms where we use execv, we cannot trust the return code.
+  do_check_eq(exitValue, USE_EXECV ? 0 : 1);
 
   logTestInfo("testing update.status should be " + STATE_FAILED);
   // The update status format for a failure is failed: # where # is the error
   // code for the failure.
   do_check_eq(readStatusFile(updatesDir).split(": ")[0], STATE_FAILED);
 
   // For Mac OS X check that the last modified time for a directory has been
   // updated after a successful update (bug 600098).
copy from toolkit/mozapps/update/test/unit/test_0110_general.js
copy to toolkit/mozapps/update/test/unit/test_0113_general.js
--- a/toolkit/mozapps/update/test/unit/test_0110_general.js
+++ b/toolkit/mozapps/update/test/unit/test_0113_general.js
@@ -1,16 +1,16 @@
 /* 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/.
  */
 
-/* General Complete MAR File Patch Apply Test */
+/* General Complete MAR File Background Patch Apply Test */
 
-const TEST_ID = "0110";
+const TEST_ID = "0113";
 // All we care about is that the last modified time has changed so that Mac OS
 // X Launch Services invalidates its cache so the test allows up to one minute
 // difference in the last modified time.
 const MAX_TIME_DIFFERENCE = 60000;
 
 // The files are listed in the same order as they are applied from the mar's
 // update.manifest. Complete updates have remove file and rmdir directory
 // operations located in the precomplete file performed first.
@@ -233,16 +233,17 @@ ADDITIONAL_TEST_DIRS = [
   relPathDir   : "a/b/2/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
   do_register_cleanup(cleanupUpdaterTest);
 
+  gBackgroundUpdate = true;
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
   let applyToDir = getApplyDirFile();
 
   // For Mac OS X set the last modified time for the root directory to a date in
   // the past to test that the last modified time is updated on a successful
   // update (bug 600098).
@@ -253,19 +254,19 @@ function run_test() {
   }
 
   // apply the complete mar
   let exitValue = runUpdate();
   logTestInfo("testing updater binary process exitValue for success when " +
               "applying a complete mar");
   do_check_eq(exitValue, 0);
 
-  logTestInfo("testing update.status should be " + STATE_SUCCEEDED);
+  logTestInfo("testing update.status should be " + STATE_APPLIED);
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
-  do_check_eq(readStatusFile(updatesDir), STATE_SUCCEEDED);
+  do_check_eq(readStatusFile(updatesDir), STATE_APPLIED);
 
   // For Mac OS X check that the last modified time for a directory has been
   // updated after a successful update (bug 600098).
   if (IS_MACOSX) {
     logTestInfo("testing last modified time on the apply to directory has " +
                 "changed after a successful update (bug 600098)");
     let now = Date.now();
     let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
@@ -273,14 +274,54 @@ function run_test() {
   }
 
   checkFilesAfterUpdateSuccess();
   // Sorting on Linux is different so skip this check for now.
   if (!IS_UNIX) {
     checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
   }
 
+  // This shouldn't exist anyways in background updates, but let's make sure
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
+  toBeDeletedDir = getTargetDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for success when " +
+              "switching to the updated application");
+  do_check_eq(exitValue, 0);
+
+  logTestInfo("testing update.status should be " + STATE_SUCCEEDED);
+  do_check_eq(readStatusFile(updatesDir), STATE_SUCCEEDED);
+
+  // For Mac OS X check that the last modified time for a directory has been
+  // updated after a successful update (bug 600098).
+  if (IS_MACOSX) {
+    logTestInfo("testing last modified time on the apply to directory has " +
+                "changed after a successful update (bug 600098)");
+    let now = Date.now();
+    let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
+    do_check_true(timeDiff < MAX_TIME_DIFFERENCE);
+  }
+
+  checkFilesAfterUpdateSuccess();
+  // Sorting on Linux is different so skip this check for now.
+  if (!IS_UNIX) {
+    checkUpdateLogContents(LOG_COMPLETE_SWITCH_SUCCESS);
+  }
+
+  // This shouldn't exist anyways in background updates, but let's make sure
+  logTestInfo("testing tobedeleted directory doesn't exist");
+  toBeDeletedDir = getApplyDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
+
+  // Make sure that the intermediate directory has been removed
+  let updatedDir = applyToDir.clone();
+  updatedDir.append(UPDATED_DIR_SUFFIX.replace("/", ""));
+  do_check_false(updatedDir.exists());
 
   checkCallbackAppLog();
 }
--- a/toolkit/mozapps/update/test/unit/test_0113_versionDowngradeCheck.js
+++ b/toolkit/mozapps/update/test/unit/test_0113_versionDowngradeCheck.js
@@ -21,16 +21,19 @@ function run_test() {
   // Setup an old version MAR file
   do_register_cleanup(cleanupUpdaterTest);
   setupUpdaterTest(MAR_OLD_VERSION_FILE);
 
   // Apply the MAR
   let exitValue = runUpdate();
   logTestInfo("testing updater binary process exitValue for failure when " +
               "applying a version downgrade MAR");
-  // Make sure the updater executed successfully
-  do_check_eq(exitValue, 0);
+  // Make sure the updater execution failed.
+  // Note that if execv is used, the updater process will turn into the
+  // callback process, so its return code will be that of the callback
+  // app.
+  do_check_eq(exitValue, USE_EXECV ? 0 : 1);
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
 
   //Make sure we get a version downgrade error
   let updateStatus = readStatusFile(updatesDir);
   do_check_eq(updateStatus.split(": ")[1], VERSION_DOWNGRADE_ERROR);
 }
copy from toolkit/mozapps/update/test/unit/test_0111_general.js
copy to toolkit/mozapps/update/test/unit/test_0114_general.js
--- a/toolkit/mozapps/update/test/unit/test_0111_general.js
+++ b/toolkit/mozapps/update/test/unit/test_0114_general.js
@@ -1,16 +1,16 @@
 /* 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/.
  */
 
-/* General Partial MAR File Patch Apply Test */
+/* General Partial MAR File Background Patch Apply Test */
 
-const TEST_ID = "0111";
+const TEST_ID = "0114";
 // All we care about is that the last modified time has changed so that Mac OS
 // X Launch Services invalidates its cache so the test allows up to one minute
 // difference in the last modified time.
 const MAX_TIME_DIFFERENCE = 60000;
 
 // The files are listed in the same order as they are applied from the mar's
 // update.manifest. Complete updates have remove file and rmdir directory
 // operations located in the precomplete file performed first.
@@ -235,16 +235,17 @@ ADDITIONAL_TEST_DIRS = [
   relPathDir   : "a/b/1/",
   dirRemoved   : true
 }];
 
 function run_test() {
   do_test_pending();
   do_register_cleanup(cleanupUpdaterTest);
 
+  gBackgroundUpdate = true;
   setupUpdaterTest(MAR_PARTIAL_FILE);
 
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
   let applyToDir = getApplyDirFile();
 
   // For Mac OS X set the last modified time for the root directory to a date in
   // the past to test that the last modified time is updated on all updates since
   // the precomplete file in the root of the bundle is renamed, etc. (bug 600098).
@@ -255,18 +256,18 @@ function run_test() {
   }
 
   // apply the partial mar
   let exitValue = runUpdate();
   logTestInfo("testing updater binary process exitValue for success when " +
               "applying a partial mar");
   do_check_eq(exitValue, 0);
 
-  logTestInfo("testing update.status should be " + STATE_SUCCEEDED);
-  do_check_eq(readStatusFile(updatesDir), STATE_SUCCEEDED);
+  logTestInfo("testing update.status should be " + STATE_APPLIED);
+  do_check_eq(readStatusFile(updatesDir), STATE_APPLIED);
 
   // For Mac OS X check that the last modified time for a directory has been
   // updated after a successful update (bug 600098).
   if (IS_MACOSX) {
     logTestInfo("testing last modified time on the apply to directory has " +
                 "changed after a successful update (bug 600098)");
     let now = Date.now();
     let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
@@ -274,14 +275,54 @@ function run_test() {
   }
 
   checkFilesAfterUpdateSuccess();
   // Sorting on Linux is different so skip this check for now.
   if (!IS_UNIX) {
     checkUpdateLogContents(LOG_PARTIAL_SUCCESS);
   }
 
+  // This shouldn't exist anyways in background updates, but let's make sure
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
+  toBeDeletedDir = getTargetDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for success when " +
+              "switching to the updated application");
+  do_check_eq(exitValue, 0);
+
+  logTestInfo("testing update.status should be " + STATE_SUCCEEDED);
+  do_check_eq(readStatusFile(updatesDir), STATE_SUCCEEDED);
+
+  // For Mac OS X check that the last modified time for a directory has been
+  // updated after a successful update (bug 600098).
+  if (IS_MACOSX) {
+    logTestInfo("testing last modified time on the apply to directory has " +
+                "changed after a successful update (bug 600098)");
+    let now = Date.now();
+    let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
+    do_check_true(timeDiff < MAX_TIME_DIFFERENCE);
+  }
+
+  checkFilesAfterUpdateSuccess();
+  // Sorting on Linux is different so skip this check for now.
+  if (!IS_UNIX) {
+    checkUpdateLogContents(LOG_PARTIAL_SWITCH_SUCCESS);
+  }
+
+  // This shouldn't exist anyways in background updates, but let's make sure
+  logTestInfo("testing tobedeleted directory doesn't exist");
+  toBeDeletedDir = getApplyDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
+
+  // Make sure that the intermediate directory has been removed
+  let updatedDir = applyToDir.clone();
+  updatedDir.append(UPDATED_DIR_SUFFIX.replace("/", ""));
+  do_check_false(updatedDir.exists());
 
   checkCallbackAppLog();
 }
--- a/toolkit/mozapps/update/test/unit/test_0114_productChannelCheck.js
+++ b/toolkit/mozapps/update/test/unit/test_0114_productChannelCheck.js
@@ -21,16 +21,19 @@ function run_test() {
   // Setup a wrong channel MAR file
   do_register_cleanup(cleanupUpdaterTest);
   setupUpdaterTest(MAR_WRONG_CHANNEL_FILE);
 
   // Apply the MAR
   let exitValue = runUpdate();
   logTestInfo("testing updater binary process exitValue for failure when " +
               "applying a wrong product and channel MAR file");
-  // Make sure the updater executed successfully
-  do_check_eq(exitValue, 0);
+  // Make sure the updater execution failed.
+  // Note that if execv is used, the updater process will turn into the
+  // callback process, so its return code will be that of the callback
+  // app.
+  do_check_eq(exitValue, USE_EXECV ? 0 : 1);
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
 
   //Make sure we get a version downgrade error
   let updateStatus = readStatusFile(updatesDir);
   do_check_eq(updateStatus.split(": ")[1], MAR_CHANNEL_MISMATCH_ERROR);
 }
copy from toolkit/mozapps/update/test/unit/test_0112_general.js
copy to toolkit/mozapps/update/test/unit/test_0115_general.js
--- a/toolkit/mozapps/update/test/unit/test_0112_general.js
+++ b/toolkit/mozapps/update/test/unit/test_0115_general.js
@@ -1,16 +1,16 @@
 /* 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/.
  */
 
-/* General Partial MAR File Patch Apply Failure Test */
+/* General Partial MAR File Background Patch Apply Failure Test */
 
-const TEST_ID = "0112";
+const TEST_ID = "0115";
 // All we care about is that the last modified time has changed so that Mac OS
 // X Launch Services invalidates its cache so the test allows up to one minute
 // difference in the last modified time.
 const MAX_TIME_DIFFERENCE = 60000;
 
 // The files are listed in the same order as they are applied from the mar's
 // update.manifest. Complete updates have remove file and rmdir directory
 // operations located in the precomplete file performed first.
@@ -225,38 +225,38 @@ ADDITIONAL_TEST_DIRS = [
   dirRemoved   : false
 }, {
   description  : "Not removed for failed update (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : false
 }];
 
 function run_test() {
-  do_test_pending();
   do_register_cleanup(cleanupUpdaterTest);
 
+  gBackgroundUpdate = true;
   setupUpdaterTest(MAR_PARTIAL_FILE);
 
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
   let applyToDir = getApplyDirFile();
 
   // For Mac OS X set the last modified time for the root directory to a date in
   // the past to test that the last modified time is updated on all updates since
   // the precomplete file in the root of the bundle is renamed, etc. (bug 600098).
   if (IS_MACOSX) {
     let now = Date.now();
     let yesterday = now - (1000 * 60 * 60 * 24);
     applyToDir.lastModifiedTime = yesterday;
   }
 
   // apply the partial mar
   let exitValue = runUpdate();
-  logTestInfo("testing updater binary process exitValue for success when " +
+  logTestInfo("testing updater binary process exitValue for failure when " +
               "applying a partial mar");
-  do_check_eq(exitValue, 0);
+  do_check_eq(exitValue, 1);
 
   logTestInfo("testing update.status should be " + STATE_FAILED);
   // The update status format for a failure is failed: # where # is the error
   // code for the failure.
   do_check_eq(readStatusFile(updatesDir).split(": ")[0], STATE_FAILED);
 
   // For Mac OS X check that the last modified time for a directory has been
   // updated after a successful update (bug 600098).
@@ -269,14 +269,15 @@ function run_test() {
   }
 
   checkFilesAfterUpdateFailure();
   // Sorting on Linux is different so skip this check for now.
   if (!IS_UNIX) {
     checkUpdateLogContents(LOG_PARTIAL_FAILURE);
   }
 
+  // This shouldn't exist anyways in background updates, but let's make sure
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
-
-  checkCallbackAppLog();
+  toBeDeletedDir = getTargetDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test/unit/test_0152_appBinReplaced_xp_win_complete.js
@@ -0,0 +1,230 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Replace app binary complete MAR file background patch apply success test */
+
+const TEST_ID = "0152";
+const MAR_COMPLETE_WIN_FILE = "data/complete_win.mar";
+
+// The files are listed in the same order as they are applied from the mar's
+// update.manifest. Complete updates have remove file and rmdir directory
+// operations located in the precomplete file performed first.
+const TEST_FILES = [
+{
+  description      : "Should never change",
+  fileName         : "channel-prefs.js",
+  relPathDir       : "a/b/defaults/pref/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "precomplete",
+  relPathDir       : "",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_precomplete",
+  compareFile      : "data/complete_precomplete"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginstext0",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "FromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginspng1.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginspng0.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "removed-files",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_removed-files",
+  compareFile      : "data/complete_removed-files"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1text0",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : "FromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1png1.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1png0.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0text0",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "FromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0png1.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0png0.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "exe0.exe",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_in_use_win_after.exe",
+  compareFile      : "data/partial_in_use_win_before.exe"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "10text0",
+  relPathDir       : "a/b/1/10/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "FromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add) file in use",
+  fileName         : "0exe0.exe",
+  relPathDir       : "a/b/0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_in_use_win_after.exe",
+  compareFile      : "data/partial_in_use_win_before.exe"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text1",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "FromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text0",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "FromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00png0.png",
+  relPathDir       : "a/b/0/00/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Removed by precomplete (remove)",
+  fileName         : "20text0",
+  relPathDir       : "a/b/2/20/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Removed by precomplete (remove)",
+  fileName         : "20png0.png",
+  relPathDir       : "a/b/2/20/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}];
+
+ADDITIONAL_TEST_DIRS = [
+{
+  description  : "Removed by precomplete (rmdir)",
+  relPathDir   : "a/b/2/20/",
+  dirRemoved   : true
+}, {
+  description  : "Removed by precomplete (rmdir)",
+  relPathDir   : "a/b/2/",
+  dirRemoved   : true
+}];
+
+function run_test() {
+  do_test_pending();
+  do_register_cleanup(cleanupUpdaterTest);
+
+  gBackgroundUpdate = true;
+  setupUpdaterTest(MAR_COMPLETE_WIN_FILE);
+
+  gCallbackBinFile = "exe0.exe";
+
+  // apply the complete mar
+  let exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for success when " +
+              "applying a complete mar");
+  do_check_eq(exitValue, 0);
+
+  logTestInfo("testing update.status should be " + STATE_APPLIED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir), STATE_APPLIED);
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for success when " +
+              "switching to the updated application");
+  do_check_eq(exitValue, 0);
+
+  logTestInfo("testing update.status should be " + STATE_SUCCEEDED);
+  do_check_eq(readStatusFile(updatesDir), STATE_SUCCEEDED);
+
+  checkFilesAfterUpdateSuccess();
+
+  logTestInfo("testing tobedeleted directory doesn't exist");
+  let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
+
+  checkCallbackAppLog();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test/unit/test_0153_appBinPatched_xp_win_partial.js
@@ -0,0 +1,232 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Patch app binary partial MAR file background patch apply success test */
+
+const TEST_ID = "0153";
+const MAR_IN_USE_WIN_FILE = "data/partial_win.mar";
+
+// The files are listed in the same order as they are applied from the mar's
+// update.manifest. Complete updates have remove file and rmdir directory
+// operations located in the precomplete file performed first.
+const TEST_FILES = [
+{
+  description      : "Should never change",
+  fileName         : "channel-prefs.js",
+  relPathDir       : "a/b/defaults/pref/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "precomplete",
+  relPathDir       : "",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete_precomplete",
+  compareFile      : "data/partial_precomplete"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginstext0",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : "ToBeReplacedWithFromPartial\n",
+  compareContents  : "FromPartial\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Patched by update.manifest if the file exists " +
+                     "(patch-if)",
+  fileName         : "searchpluginspng1.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Patched by update.manifest if the file exists " +
+                     "(patch-if)",
+  fileName         : "searchpluginspng0.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1text0",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : "FromPartial\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions1png1.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions1png0.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0text0",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : "ToBeReplacedWithFromPartial\n",
+  compareContents  : "FromPartial\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions0png1.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions0png0.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Patched by update.manifest (patch)",
+  fileName         : "exe0.exe",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_in_use_win_before.exe",
+  compareFile      : "data/partial_in_use_win_after.exe"
+}, {
+  description      : "Patched by update.manifest (patch) file in use",
+  fileName         : "0exe0.exe",
+  relPathDir       : "a/b/0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_in_use_win_before.exe",
+  compareFile      : "data/partial_in_use_win_after.exe"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text0",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeReplacedWithFromPartial\n",
+  compareContents  : "FromPartial\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Patched by update.manifest (patch)",
+  fileName         : "00png0.png",
+  relPathDir       : "a/b/0/00/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "20text0",
+  relPathDir       : "a/b/2/20/",
+  originalContents : null,
+  compareContents  : "FromPartial\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "20png0.png",
+  relPathDir       : "a/b/2/20/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text2",
+  relPathDir       : "a/b/0/00/",
+  originalContents : null,
+  compareContents  : "FromPartial\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Removed by update.manifest (remove)",
+  fileName         : "10text0",
+  relPathDir       : "a/b/1/10/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Removed by update.manifest (remove)",
+  fileName         : "00text1",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}];
+
+ADDITIONAL_TEST_DIRS = [
+{
+  description  : "Removed by update.manifest (rmdir)",
+  relPathDir   : "a/b/1/10/",
+  dirRemoved   : true
+}, {
+  description  : "Removed by update.manifest (rmdir)",
+  relPathDir   : "a/b/1/",
+  dirRemoved   : true
+}];
+
+function run_test() {
+  do_test_pending();
+  do_register_cleanup(cleanupUpdaterTest);
+
+  gBackgroundUpdate = true;
+  setupUpdaterTest(MAR_IN_USE_WIN_FILE);
+
+  gCallbackBinFile = "exe0.exe";
+
+  // apply the complete mar
+  let exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for success when " +
+              "applying a partial mar");
+  do_check_eq(exitValue, 0);
+
+  logTestInfo("testing update.status should be " + STATE_APPLIED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir), STATE_APPLIED);
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for success when " +
+              "switching to the updated application");
+  do_check_eq(exitValue, 0);
+
+  logTestInfo("testing update.status should be " + STATE_SUCCEEDED);
+  do_check_eq(readStatusFile(updatesDir), STATE_SUCCEEDED);
+
+  checkFilesAfterUpdateSuccess();
+
+  logTestInfo("testing tobedeleted directory doesn't exist");
+  let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
+
+  checkCallbackAppLog();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test/unit/test_0161_appInUse_xp_unix_complete.js
@@ -0,0 +1,328 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Application in use complete MAR file background patch apply success test */
+
+const TEST_ID = "0161";
+// All we care about is that the last modified time has changed so that Mac OS
+// X Launch Services invalidates its cache so the test allows up to one minute
+// difference in the last modified time.
+const MAX_TIME_DIFFERENCE = 60000;
+
+// The files are listed in the same order as they are applied from the mar's
+// update.manifest. Complete updates have remove file and rmdir directory
+// operations located in the precomplete file performed first.
+const TEST_FILES = [
+{
+  description      : "Should never change",
+  fileName         : "channel-prefs.js",
+  relPathDir       : "a/b/defaults/pref/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : 0767,
+  comparePerms     : 0767
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "precomplete",
+  relPathDir       : "",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_precomplete",
+  compareFile      : "data/complete_precomplete",
+  originalPerms    : 0666,
+  comparePerms     : 0644
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginstext0",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "FromComplete\n",
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : 0775,
+  comparePerms     : 0644
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginspng1.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : "data/complete.png",
+  originalPerms    : null,
+  comparePerms     : 0644
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginspng0.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/complete.png",
+  originalPerms    : 0666,
+  comparePerms     : 0644
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "removed-files",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_removed-files",
+  compareFile      : "data/complete_removed-files",
+  originalPerms    : 0666,
+  comparePerms     : 0644
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1text0",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : "FromComplete\n",
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : null,
+  comparePerms     : 0644
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1png1.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/complete.png",
+  originalPerms    : 0666,
+  comparePerms     : 0644
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1png0.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : "data/complete.png",
+  originalPerms    : null,
+  comparePerms     : 0644
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0text0",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "FromComplete\n",
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : null,
+  comparePerms     : 0644
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0png1.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : "data/complete.png",
+  originalPerms    : null,
+  comparePerms     : 0644
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0png0.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : "data/complete.png",
+  originalPerms    : null,
+  comparePerms     : 0644
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "exe0.exe",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/complete.png",
+  originalPerms    : 0777,
+  comparePerms     : 0755
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "10text0",
+  relPathDir       : "a/b/1/10/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "FromComplete\n",
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : 0767,
+  comparePerms     : 0644
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "0exe0.exe",
+  relPathDir       : "a/b/0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/complete.png",
+  originalPerms    : 0777,
+  comparePerms     : 0755
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text1",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "FromComplete\n",
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : 0677,
+  comparePerms     : 0644
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text0",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "FromComplete\n",
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : 0775,
+  comparePerms     : 0644
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00png0.png",
+  relPathDir       : "a/b/0/00/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : "data/complete.png",
+  originalPerms    : 0776,
+  comparePerms     : 0644
+}, {
+  description      : "Removed by precomplete (remove)",
+  fileName         : "20text0",
+  relPathDir       : "a/b/2/20/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : null,
+  comparePerms     : null
+}, {
+  description      : "Removed by precomplete (remove)",
+  fileName         : "20png0.png",
+  relPathDir       : "a/b/2/20/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : null,
+  comparePerms     : null
+}];
+
+ADDITIONAL_TEST_DIRS = [
+{
+  description  : "Removed by precomplete (rmdir)",
+  relPathDir   : "a/b/2/20/",
+  dirRemoved   : true
+}, {
+  description  : "Removed by precomplete (rmdir)",
+  relPathDir   : "a/b/2/",
+  dirRemoved   : true
+}];
+
+function run_test() {
+  do_test_pending();
+  do_register_cleanup(cleanupUpdaterTest);
+
+  gBackgroundUpdate = true;
+  setupUpdaterTest(MAR_COMPLETE_FILE);
+
+  let applyToDir = getApplyDirFile();
+
+  // For Mac OS X set the last modified time for the root directory to a date in
+  // the past to test that the last modified time is updated on a successful
+  // update (bug 600098).
+  if (IS_MACOSX) {
+    let now = Date.now();
+    let yesterday = now - (1000 * 60 * 60 * 24);
+    applyToDir.lastModifiedTime = yesterday;
+  }
+
+  // Launch the callback helper application so it is in use during the update
+  let callbackApp = getApplyDirFile("a/b/" + gCallbackBinFile);
+  callbackApp.permissions = PERMS_DIRECTORY;
+  let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "40"];
+  let callbackAppProcess = AUS_Cc["@mozilla.org/process/util;1"].
+                           createInstance(AUS_Ci.nsIProcess);
+  callbackAppProcess.init(callbackApp);
+  callbackAppProcess.run(false, args, args.length);
+
+  do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+  // apply the complete mar
+  let exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for success when " +
+              "applying a complete mar");
+  do_check_eq(exitValue, 0);
+
+  logTestInfo("testing update.status should be " + STATE_APPLIED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir), STATE_APPLIED);
+
+  let applyToDir = getApplyDirFile();
+
+  // For Mac OS X check that the last modified time for a directory has been
+  // updated after a successful update (bug 600098).
+  if (IS_MACOSX) {
+    logTestInfo("testing last modified time on the apply to directory has " +
+                "changed after a successful update (bug 600098)");
+    let now = Date.now();
+    let applyToDir = getApplyDirFile();
+    let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
+    do_check_true(timeDiff < MAX_TIME_DIFFERENCE);
+  }
+
+  checkFilesAfterUpdateSuccess();
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for success when " +
+              "switching to the updated application");
+  do_check_eq(exitValue, 0);
+
+  logTestInfo("testing update.status should be " + STATE_SUCCEEDED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir), STATE_SUCCEEDED);
+
+  // For Mac OS X check that the last modified time for a directory has been
+  // updated after a successful update (bug 600098).
+  if (IS_MACOSX) {
+    logTestInfo("testing last modified time on the apply to directory has " +
+                "changed after a successful update (bug 600098)");
+    let now = Date.now();
+    let applyToDir = getApplyDirFile();
+    let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
+    do_check_true(timeDiff < MAX_TIME_DIFFERENCE);
+  }
+
+  checkFilesAfterUpdateSuccess();
+
+  // Make sure that the intermediate directory has been removed
+  let updatedDir = applyToDir.clone();
+  updatedDir.append(UPDATED_DIR_SUFFIX.replace("/", ""));
+  do_check_false(updatedDir.exists());
+
+  setupHelperFinish();
+}
+
+
+function checkUpdate() {
+  checkCallbackAppLog();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test/unit/test_0161_appInUse_xp_win_complete.js
@@ -0,0 +1,241 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Application in use complete MAR file background patch apply failure test */
+
+const TEST_ID = "0161";
+
+// The files are listed in the same order as they are applied from the mar's
+// update.manifest. Complete updates have remove file and rmdir directory
+// operations located in the precomplete file performed first.
+const TEST_FILES = [
+{
+  description      : "Should never change",
+  fileName         : "channel-prefs.js",
+  relPathDir       : "a/b/defaults/pref/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "precomplete",
+  relPathDir       : "",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_precomplete",
+  compareFile      : "data/partial_precomplete"
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "searchpluginstext0",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "searchpluginspng1.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "searchpluginspng0.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "removed-files",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_removed-files",
+  compareFile      : "data/partial_removed-files"
+}, {
+  description      : "Not added for failed update (add-if)",
+  fileName         : "extensions1text0",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add-if)",
+  fileName         : "extensions1png1.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add-if)",
+  fileName         : "extensions1png0.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add-if)",
+  fileName         : "extensions0text0",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add-if)",
+  fileName         : "extensions0png1.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Not added for failed update (add-if)",
+  fileName         : "extensions0png0.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "exe0.exe",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "10text0",
+  relPathDir       : "a/b/1/10/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "0exe0.exe",
+  relPathDir       : "a/b/0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "00text1",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "00text0",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "00png0.png",
+  relPathDir       : "a/b/0/00/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Not removed for failed update (remove)",
+  fileName         : "20text0",
+  relPathDir       : "a/b/2/20/",
+  originalContents : "ShouldNotBeDeleted\n",
+  compareContents  : "ShouldNotBeDeleted\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not removed for failed update (remove)",
+  fileName         : "20png0.png",
+  relPathDir       : "a/b/2/20/",
+  originalContents : "ShouldNotBeDeleted\n",
+  compareContents  : "ShouldNotBeDeleted\n",
+  originalFile     : null,
+  compareFile      : null
+}];
+
+ADDITIONAL_TEST_DIRS = [
+{
+  description  : "Not removed for failed update (rmdir)",
+  relPathDir   : "a/b/2/20/",
+  dirRemoved   : false
+}, {
+  description  : "Not removed for failed update (rmdir)",
+  relPathDir   : "a/b/2/",
+  dirRemoved   : false
+}];
+
+function run_test() {
+  do_test_pending();
+  do_register_cleanup(cleanupUpdaterTest);
+
+  gBackgroundUpdate = true;
+  setupUpdaterTest(MAR_COMPLETE_FILE);
+
+  // Launch the callback helper application so it is in use during the update
+  let callbackApp = getApplyDirFile("a/b/" + gCallbackBinFile);
+  let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "40"];
+  let callbackAppProcess = AUS_Cc["@mozilla.org/process/util;1"].
+                           createInstance(AUS_Ci.nsIProcess);
+  callbackAppProcess.init(callbackApp);
+  callbackAppProcess.run(false, args, args.length);
+
+  do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+  // apply the complete mar
+  let exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for success when " +
+              "applying a complete mar");
+  do_check_eq(exitValue, 0);
+
+  logTestInfo("testing update.status should be " + STATE_APPLIED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir), STATE_APPLIED);
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for failure when " +
+              "switching to the updated application");
+  do_check_eq(exitValue, 1);
+
+  setupHelperFinish();
+}
+
+function checkUpdate() {
+  logTestInfo("testing update.status should be " + STATE_FAILED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  // The update status format for a failure is failed: # where # is the error
+  // code for the failure.
+  do_check_eq(readStatusFile(updatesDir).split(": ")[0], STATE_FAILED);
+
+  checkFilesAfterUpdateFailure(getApplyDirFile);
+  checkUpdateLogContains(ERR_RENAME_FILE);
+
+  logTestInfo("testing tobedeleted directory doesn't exist");
+  let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
+
+  checkCallbackAppLog();
+}
--- a/toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js
+++ b/toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js
@@ -206,19 +206,19 @@ function run_test() {
   lockFileProcess.run(false, args, args.length);
 
   do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
 }
 
 function doUpdate() {
   // apply the complete mar
   let exitValue = runUpdate();
-  logTestInfo("testing updater binary process exitValue for success when " +
+  logTestInfo("testing updater binary process exitValue for failure when " +
               "applying a complete mar");
-  do_check_eq(exitValue, 0);
+  do_check_eq(exitValue, 1);
 
   setupHelperFinish();
 }
 
 function checkUpdate() {
   logTestInfo("testing update.status should be " + STATE_FAILED);
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
   // The update status format for a failure is failed: # where # is the error
--- a/toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js
+++ b/toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js
@@ -207,19 +207,19 @@ function run_test() {
   lockFileProcess.run(false, args, args.length);
 
   do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
 }
 
 function doUpdate() {
   // apply the complete mar
   let exitValue = runUpdate();
-  logTestInfo("testing updater binary process exitValue for success when " +
+  logTestInfo("testing updater binary process exitValue for failure when " +
               "applying a partial mar");
-  do_check_eq(exitValue, 0);
+  do_check_eq(exitValue, 1);
 
   setupHelperFinish();
 }
 
 function checkUpdate() {
   logTestInfo("testing update.status should be " + STATE_FAILED);
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
   // The update status format for a failure is failed: # where # is the error
copy from toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js
copy to toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js
--- a/toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js
+++ b/toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-/* File locked complete MAR file patch apply failure test */
+/* File locked complete MAR file background patch apply failure test */
 
-const TEST_ID = "0170";
+const TEST_ID = "0172";
 
 // The files are listed in the same order as they are applied from the mar's
 // update.manifest. Complete updates have remove file and rmdir directory
 // operations located in the precomplete file performed first.
 const TEST_FILES = [
 {
   description      : "Should never change",
   fileName         : "channel-prefs.js",
@@ -182,55 +182,68 @@ ADDITIONAL_TEST_DIRS = [
   relPathDir   : "a/b/2/",
   dirRemoved   : false
 }];
 
 function run_test() {
   do_test_pending();
   do_register_cleanup(cleanupUpdaterTest);
 
+  gBackgroundUpdate = true;
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
   // Exclusively lock an existing file so it is in use during the update
   let helperBin = do_get_file(HELPER_BIN_FILE);
   let helperDestDir = getApplyDirFile("a/b/");
   helperBin.copyTo(helperDestDir, HELPER_BIN_FILE);
   helperBin = getApplyDirFile("a/b/" + HELPER_BIN_FILE);
   // Strip off the first two directories so the path has to be from the helper's
   // working directory.
   let lockFileRelPath = TEST_FILES[3].relPathDir.split("/");
   lockFileRelPath = lockFileRelPath.slice(2);
   lockFileRelPath = lockFileRelPath.join("/") + "/" + TEST_FILES[3].fileName;
-  let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "20", lockFileRelPath];
+  let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "40", lockFileRelPath];
   let lockFileProcess = AUS_Cc["@mozilla.org/process/util;1"].
                      createInstance(AUS_Ci.nsIProcess);
   lockFileProcess.init(helperBin);
   lockFileProcess.run(false, args, args.length);
 
   do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
 }
 
 function doUpdate() {
   // apply the complete mar
   let exitValue = runUpdate();
-  logTestInfo("testing updater binary process exitValue for success when " +
+  logTestInfo("testing updater binary process exitValue for failure when " +
               "applying a complete mar");
-  do_check_eq(exitValue, 0);
+  do_check_eq(exitValue, 1);
+
+  logTestInfo("testing update.status should be " + STATE_FAILED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir).split(": ")[0], STATE_FAILED);
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for failure when " +
+              "switching to the updated application");
+  do_check_eq(exitValue, 1);
 
   setupHelperFinish();
 }
 
 function checkUpdate() {
   logTestInfo("testing update.status should be " + STATE_FAILED);
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
   // The update status format for a failure is failed: # where # is the error
   // code for the failure.
   do_check_eq(readStatusFile(updatesDir).split(": ")[0], STATE_FAILED);
 
-  checkFilesAfterUpdateFailure();
+  checkFilesAfterUpdateFailure(getApplyDirFile);
   checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
copy from toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js
copy to toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js
--- a/toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js
+++ b/toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-/* File locked partial MAR file patch apply failure test */
+/* File locked partial MAR file background patch apply failure test */
 
-const TEST_ID = "0171";
+const TEST_ID = "0173";
 
 // The files are listed in the same order as they are applied from the mar's
 // update.manifest. Complete updates have remove file and rmdir directory
 // operations located in the precomplete file performed first.
 const TEST_FILES = [
 {
   description      : "Should never change",
   fileName         : "channel-prefs.js",
@@ -183,55 +183,68 @@ ADDITIONAL_TEST_DIRS = [
   relPathDir   : "a/b/1/",
   dirRemoved   : false
 }];
 
 function run_test() {
   do_test_pending();
   do_register_cleanup(cleanupUpdaterTest);
 
+  gBackgroundUpdate = true;
   setupUpdaterTest(MAR_PARTIAL_FILE);
 
   // Exclusively lock an existing file so it is in use during the update
   let helperBin = do_get_file(HELPER_BIN_FILE);
   let helperDestDir = getApplyDirFile("a/b/");
   helperBin.copyTo(helperDestDir, HELPER_BIN_FILE);
   helperBin = getApplyDirFile("a/b/" + HELPER_BIN_FILE);
   // Strip off the first two directories so the path has to be from the helper's
   // working directory.
   let lockFileRelPath = TEST_FILES[3].relPathDir.split("/");
   lockFileRelPath = lockFileRelPath.slice(2);
   lockFileRelPath = lockFileRelPath.join("/") + "/" + TEST_FILES[3].fileName;
-  let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "20", lockFileRelPath];
+  let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "40", lockFileRelPath];
   let lockFileProcess = AUS_Cc["@mozilla.org/process/util;1"].
                      createInstance(AUS_Ci.nsIProcess);
   lockFileProcess.init(helperBin);
   lockFileProcess.run(false, args, args.length);
 
   do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
 }
 
 function doUpdate() {
   // apply the complete mar
   let exitValue = runUpdate();
-  logTestInfo("testing updater binary process exitValue for success when " +
-              "applying a partial mar");
-  do_check_eq(exitValue, 0);
+  logTestInfo("testing updater binary process exitValue for failure when " +
+              "applying a complete mar");
+  do_check_eq(exitValue, 1);
+
+  logTestInfo("testing update.status should be " + STATE_FAILED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir).split(": ")[0], STATE_FAILED);
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for failure when " +
+              "switching to the updated application");
+  do_check_eq(exitValue, 1);
 
   setupHelperFinish();
 }
 
 function checkUpdate() {
   logTestInfo("testing update.status should be " + STATE_FAILED);
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
   // The update status format for a failure is failed: # where # is the error
   // code for the failure.
   do_check_eq(readStatusFile(updatesDir).split(": ")[0], STATE_FAILED);
 
-  checkFilesAfterUpdateFailure();
-  checkUpdateLogContains(ERR_UNABLE_OPEN_DEST);
+  checkFilesAfterUpdateFailure(getApplyDirFile);
+  checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test/unit/test_0184_fileInUse_xp_win_complete.js
@@ -0,0 +1,246 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use complete MAR file background patch apply success test */
+
+const TEST_ID = "0184";
+
+// The files are listed in the same order as they are applied from the mar's
+// update.manifest. Complete updates have remove file and rmdir directory
+// operations located in the precomplete file performed first.
+const TEST_FILES = [
+{
+  description      : "Should never change",
+  fileName         : "channel-prefs.js",
+  relPathDir       : "a/b/defaults/pref/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "precomplete",
+  relPathDir       : "",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_precomplete",
+  compareFile      : "data/partial_precomplete"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginstext0",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "ToBeReplacedWithFromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginspng1.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginspng0.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "removed-files",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_removed-files",
+  compareFile      : "data/partial_removed-files"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1text0",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1png1.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1png0.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0text0",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "ToBeReplacedWithFromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0png1.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0png0.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "exe0.exe",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : HELPER_BIN_FILE,
+  compareFile      : HELPER_BIN_FILE
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "10text0",
+  relPathDir       : "a/b/1/10/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "ToBeReplacedWithFromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add) file in use",
+  fileName         : "0exe0.exe",
+  relPathDir       : "a/b/0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : HELPER_BIN_FILE,
+  compareFile      : HELPER_BIN_FILE
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text1",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "ToBeReplacedWithFromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text0",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "ToBeReplacedWithFromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00png0.png",
+  relPathDir       : "a/b/0/00/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Removed by precomplete (remove)",
+  fileName         : "20text0",
+  relPathDir       : "a/b/2/20/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : "ToBeDeleted\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Removed by precomplete (remove)",
+  fileName         : "20png0.png",
+  relPathDir       : "a/b/2/20/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : "ToBeDeleted\n",
+  originalFile     : null,
+  compareFile      : null
+}];
+
+ADDITIONAL_TEST_DIRS = [
+{
+  description  : "Removed by precomplete (rmdir)",
+  relPathDir   : "a/b/2/20/",
+  dirRemoved   : true
+}, {
+  description  : "Removed by precomplete (rmdir)",
+  relPathDir   : "a/b/2/",
+  dirRemoved   : true
+}];
+
+function run_test() {
+  do_test_pending();
+  do_register_cleanup(cleanupUpdaterTest);
+
+  gBackgroundUpdate = true;
+  setupUpdaterTest(MAR_COMPLETE_FILE);
+
+  // Launch an existing file so it is in use during the update
+  let fileInUseBin = getApplyDirFile(TEST_FILES[14].relPathDir +
+                                     TEST_FILES[14].fileName);
+  let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "40"];
+  let fileInUseProcess = AUS_Cc["@mozilla.org/process/util;1"].
+                         createInstance(AUS_Ci.nsIProcess);
+  fileInUseProcess.init(fileInUseBin);
+  fileInUseProcess.run(false, args, args.length);
+
+  do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+  // apply the complete mar
+  let exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for success when " +
+              "applying a complete mar");
+  do_check_eq(exitValue, 0);
+
+  logTestInfo("testing update.status should be " + STATE_APPLIED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir), STATE_APPLIED);
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for failure when " +
+              "switching to the updated application");
+  do_check_eq(exitValue, 1);
+
+  setupHelperFinish();
+}
+
+function checkUpdate() {
+  logTestInfo("testing update.status should be " + STATE_FAILED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir).split(": ")[0], STATE_FAILED);
+
+  checkFilesAfterUpdateFailure(getApplyDirFile);
+  checkUpdateLogContains(ERR_RENAME_FILE);
+
+  logTestInfo("testing tobedeleted directory does not exist");
+  let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
+
+  checkCallbackAppLog();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test/unit/test_0185_fileInUse_xp_win_partial.js
@@ -0,0 +1,249 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use partial MAR file background patch apply success test */
+
+const TEST_ID = "0185";
+const MAR_IN_USE_WIN_FILE = "data/partial_win.mar";
+
+// The files are listed in the same order as they are applied from the mar's
+// update.manifest. Complete updates have remove file and rmdir directory
+// operations located in the precomplete file performed first.
+const TEST_FILES = [
+{
+  description      : "Should never change",
+  fileName         : "channel-prefs.js",
+  relPathDir       : "a/b/defaults/pref/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "precomplete",
+  relPathDir       : "",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete_precomplete",
+  compareFile      : "data/complete_precomplete"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginstext0",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : "ToBeReplacedWithFromPartial\n",
+  compareContents  : "ToBeReplacedWithFromPartial\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Patched by update.manifest if the file exists " +
+                     "(patch-if)",
+  fileName         : "searchpluginspng1.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Patched by update.manifest if the file exists " +
+                     "(patch-if)",
+  fileName         : "searchpluginspng0.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1text0",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions1png1.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions1png0.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0text0",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : "ToBeReplacedWithFromPartial\n",
+  compareContents  : "ToBeReplacedWithFromPartial\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions0png1.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions0png0.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Patched by update.manifest (patch)",
+  fileName         : "exe0.exe",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_in_use_win_before.exe",
+  compareFile      : "data/partial_in_use_win_before.exe"
+}, {
+  description      : "Patched by update.manifest (patch) file in use",
+  fileName         : "0exe0.exe",
+  relPathDir       : "a/b/0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_in_use_win_before.exe",
+  compareFile      : "data/partial_in_use_win_before.exe"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text0",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeReplacedWithFromPartial\n",
+  compareContents  : "ToBeReplacedWithFromPartial\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Patched by update.manifest (patch)",
+  fileName         : "00png0.png",
+  relPathDir       : "a/b/0/00/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "20text0",
+  relPathDir       : "a/b/2/20/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "20png0.png",
+  relPathDir       : "a/b/2/20/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text2",
+  relPathDir       : "a/b/0/00/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Removed by update.manifest (remove)",
+  fileName         : "10text0",
+  relPathDir       : "a/b/1/10/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : "ToBeDeleted\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Removed by update.manifest (remove)",
+  fileName         : "00text1",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : "ToBeDeleted\n",
+  originalFile     : null,
+  compareFile      : null
+}];
+
+ADDITIONAL_TEST_DIRS = [
+{
+  description  : "Removed by update.manifest (rmdir)",
+  relPathDir   : "a/b/1/10/",
+  dirRemoved   : true
+}, {
+  description  : "Removed by update.manifest (rmdir)",
+  relPathDir   : "a/b/1/",
+  dirRemoved   : true
+}];
+
+function run_test() {
+  do_test_pending();
+  do_register_cleanup(cleanupUpdaterTest);
+
+  gBackgroundUpdate = true;
+  setupUpdaterTest(MAR_IN_USE_WIN_FILE);
+
+  // Launch an existing file so it is in use during the update
+  let fileInUseBin = getApplyDirFile(TEST_FILES[12].relPathDir +
+                                     TEST_FILES[12].fileName);
+  let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "40"];
+  let fileInUseProcess = AUS_Cc["@mozilla.org/process/util;1"].
+                         createInstance(AUS_Ci.nsIProcess);
+  fileInUseProcess.init(fileInUseBin);
+  fileInUseProcess.run(false, args, args.length);
+
+  do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+  // apply the complete mar
+  let exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for success when " +
+              "applying a complete mar");
+  do_check_eq(exitValue, 0);
+
+  logTestInfo("testing update.status should be " + STATE_APPLIED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir), STATE_APPLIED);
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for failure when " +
+              "switching to the updated application");
+  do_check_eq(exitValue, 1);
+
+  setupHelperFinish();
+}
+
+function checkUpdate() {
+  logTestInfo("testing update.status should be " + STATE_FAILED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir).split(": ")[0], STATE_FAILED);
+
+  checkFilesAfterUpdateFailure(getApplyDirFile);
+  checkUpdateLogContains(ERR_RENAME_FILE);
+
+  logTestInfo("testing tobedeleted directory does not exist");
+  let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
+
+  checkCallbackAppLog();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test/unit/test_0186_rmrfdirFileInUse_xp_win_complete.js
@@ -0,0 +1,255 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir complete MAR file background patch apply success test */
+
+const TEST_ID = "0186";
+
+// The files are listed in the same order as they are applied from the mar's
+// update.manifest. Complete updates have remove file and rmdir directory
+// operations located in the precomplete file performed first.
+const TEST_FILES = [
+{
+  description      : "Should never change",
+  fileName         : "channel-prefs.js",
+  relPathDir       : "a/b/defaults/pref/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "precomplete",
+  relPathDir       : "",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_precomplete",
+  compareFile      : "data/partial_precomplete"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginstext0",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "ToBeReplacedWithFromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginspng1.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginspng0.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "removed-files",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_removed-files",
+  compareFile      : "data/partial_removed-files"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1text0",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1png1.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1png0.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0text0",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "ToBeReplacedWithFromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0png1.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0png0.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "exe0.exe",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : HELPER_BIN_FILE,
+  compareFile      : HELPER_BIN_FILE
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "10text0",
+  relPathDir       : "a/b/1/10/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "ToBeReplacedWithFromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add) file in use",
+  fileName         : "0exe0.exe",
+  relPathDir       : "a/b/0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : HELPER_BIN_FILE,
+  compareFile      : HELPER_BIN_FILE
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text1",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "ToBeReplacedWithFromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text0",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "ToBeReplacedWithFromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00png0.png",
+  relPathDir       : "a/b/0/00/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Removed by precomplete (remove)",
+  fileName         : "20text0",
+  relPathDir       : "a/b/2/20/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : "ToBeDeleted\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Removed by precomplete (remove)",
+  fileName         : "20png0.png",
+  relPathDir       : "a/b/2/20/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : "ToBeDeleted\n",
+  originalFile     : null,
+  compareFile      : null
+}];
+
+ADDITIONAL_TEST_DIRS = [
+{
+  description  : "Removed by precomplete (rmdir)",
+  relPathDir   : "a/b/2/20/",
+  dirRemoved   : true
+}, {
+  description  : "Removed by precomplete (rmdir)",
+  relPathDir   : "a/b/2/",
+  dirRemoved   : true
+}];
+
+function run_test() {
+  do_test_pending();
+  do_register_cleanup(cleanupUpdaterTest);
+
+  gBackgroundUpdate = true;
+  setupUpdaterTest(MAR_COMPLETE_FILE);
+
+  let fileInUseBin = getApplyDirFile(TEST_DIRS[4].relPathDir +
+                                     TEST_DIRS[4].subDirs[0] +
+                                     TEST_DIRS[4].subDirFiles[0]);
+  // Remove the empty file created for the test so the helper application can
+  // replace it.
+  fileInUseBin.remove(false);
+
+  let helperBin = do_get_file(HELPER_BIN_FILE);
+  let fileInUseDir = getApplyDirFile(TEST_DIRS[4].relPathDir +
+                                    TEST_DIRS[4].subDirs[0]);
+  helperBin.copyTo(fileInUseDir, TEST_DIRS[4].subDirFiles[0]);
+
+  // Launch an existing file so it is in use during the update
+  let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "40"];
+  let fileInUseProcess = AUS_Cc["@mozilla.org/process/util;1"].
+                         createInstance(AUS_Ci.nsIProcess);
+  fileInUseProcess.init(fileInUseBin);
+  fileInUseProcess.run(false, args, args.length);
+
+  do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+  let exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for success when " +
+              "applying a complete mar");
+  do_check_eq(exitValue, 0);
+
+  logTestInfo("testing update.status should be " + STATE_APPLIED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir), STATE_APPLIED);
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for failure when " +
+              "switching to the updated application");
+  do_check_eq(exitValue, 1);
+
+  setupHelperFinish();
+}
+
+function checkUpdate() {
+  logTestInfo("testing update.status should be " + STATE_FAILED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir).split(": ")[0], STATE_FAILED);
+
+  checkFilesAfterUpdateFailure(getApplyDirFile);
+  checkUpdateLogContains(ERR_RENAME_FILE);
+
+  logTestInfo("testing tobedeleted directory does not exist");
+  let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
+
+  checkCallbackAppLog();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test/unit/test_0187_rmrfdirFileInUse_xp_win_partial.js
@@ -0,0 +1,296 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir partial MAR file background patch apply success test */
+
+const TEST_ID = "0187";
+const MAR_IN_USE_WIN_FILE = "data/partial.mar";
+
+// The files are listed in the same order as they are applied from the mar's
+// update.manifest. Complete updates have remove file and rmdir directory
+// operations located in the precomplete file performed first.
+const TEST_FILES = [
+{
+  description      : "Should never change",
+  fileName         : "channel-prefs.js",
+  relPathDir       : "a/b/defaults/pref/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : 0644,
+  comparePerms     : 0644
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "precomplete",
+  relPathDir       : "",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete_precomplete",
+  compareFile      : "data/complete_precomplete",
+  originalPerms    : 0666,
+  comparePerms     : 0666
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginstext0",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : "ToBeReplacedWithFromPartial\n",
+  compareContents  : "ToBeReplacedWithFromPartial\n",
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : 0775,
+  comparePerms     : 0775
+}, {
+  description      : "Patched by update.manifest if the file exists " +
+                     "(patch-if)",
+  fileName         : "searchpluginspng1.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png",
+  originalPerms    : 0666,
+  comparePerms     : 0666
+}, {
+  description      : "Patched by update.manifest if the file exists " +
+                     "(patch-if)",
+  fileName         : "searchpluginspng0.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png",
+  originalPerms    : 0666,
+  comparePerms     : 0666
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1text0",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : null,
+  comparePerms     : null
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions1png1.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png",
+  originalPerms    : 0666,
+  comparePerms     : 0666
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions1png0.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png",
+  originalPerms    : 0666,
+  comparePerms     : 0666
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0text0",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : "ToBeReplacedWithFromPartial\n",
+  compareContents  : "ToBeReplacedWithFromPartial\n",
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : null,
+  comparePerms     : null
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions0png1.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png",
+  originalPerms    : null,
+  comparePerms     : null
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions0png0.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png",
+  originalPerms    : null,
+  comparePerms     : null
+}, {
+  description      : "Patched by update.manifest (patch)",
+  fileName         : "exe0.exe",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png",
+  originalPerms    : 0755,
+  comparePerms     : 0755
+}, {
+  description      : "Patched by update.manifest (patch)",
+  fileName         : "0exe0.exe",
+  relPathDir       : "a/b/0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png",
+  originalPerms    : 0755,
+  comparePerms     : 0755
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text0",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeReplacedWithFromPartial\n",
+  compareContents  : "ToBeReplacedWithFromPartial\n",
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : 0644,
+  comparePerms     : 0644
+}, {
+  description      : "Patched by update.manifest (patch)",
+  fileName         : "00png0.png",
+  relPathDir       : "a/b/0/00/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png",
+  originalPerms    : 0666,
+  comparePerms     : 0666
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "20text0",
+  relPathDir       : "a/b/2/20/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : null,
+  comparePerms     : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "20png0.png",
+  relPathDir       : "a/b/2/20/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : null,
+  comparePerms     : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text2",
+  relPathDir       : "a/b/0/00/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : null,
+  comparePerms     : null
+}, {
+  description      : "Removed by update.manifest (remove)",
+  fileName         : "10text0",
+  relPathDir       : "a/b/1/10/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : "ToBeDeleted\n",
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : null,
+  comparePerms     : null
+}, {
+  description      : "Removed by update.manifest (remove)",
+  fileName         : "00text1",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : "ToBeDeleted\n",
+  originalFile     : null,
+  compareFile      : null,
+  originalPerms    : null,
+  comparePerms     : null
+}];
+
+ADDITIONAL_TEST_DIRS = [
+{
+  description  : "Removed by update.manifest (rmdir)",
+  relPathDir   : "a/b/1/10/",
+  dirRemoved   : true
+}, {
+  description  : "Removed by update.manifest (rmdir)",
+  relPathDir   : "a/b/1/",
+  dirRemoved   : true
+}];
+
+function run_test() {
+  do_test_pending();
+  do_register_cleanup(cleanupUpdaterTest);
+
+  gBackgroundUpdate = true;
+  setupUpdaterTest(MAR_IN_USE_WIN_FILE);
+
+  let fileInUseBin = getApplyDirFile(TEST_DIRS[2].relPathDir +
+                                     TEST_DIRS[2].files[0]);
+  // Remove the empty file created for the test so the helper application can
+  // replace it.
+  fileInUseBin.remove(false);
+
+  let helperBin = do_get_file(HELPER_BIN_FILE);
+  let fileInUseDir = getApplyDirFile(TEST_DIRS[2].relPathDir);
+  helperBin.copyTo(fileInUseDir, TEST_DIRS[2].files[0]);
+
+  // Launch an existing file so it is in use during the update
+  let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "40"];
+  let fileInUseProcess = AUS_Cc["@mozilla.org/process/util;1"].
+                         createInstance(AUS_Ci.nsIProcess);
+  fileInUseProcess.init(fileInUseBin);
+  fileInUseProcess.run(false, args, args.length);
+
+  do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+  let exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for success when " +
+              "applying a complete mar");
+  do_check_eq(exitValue, 0);
+
+  logTestInfo("testing update.status should be " + STATE_APPLIED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir), STATE_APPLIED);
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for failure when " +
+              "switching to the updated application");
+  do_check_eq(exitValue, 1);
+
+  setupHelperFinish();
+}
+
+function checkUpdate() {
+  logTestInfo("testing update.status should be " + STATE_FAILED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir).split(": ")[0], STATE_FAILED);
+
+  checkFilesAfterUpdateFailure(getApplyDirFile);
+  checkUpdateLogContains(ERR_RENAME_FILE);
+
+  logTestInfo("testing tobedeleted directory does not exist");
+  let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
+
+  checkCallbackAppLog();
+}
--- a/toolkit/mozapps/update/test/unit/test_0200_app_launch_apply_update.js
+++ b/toolkit/mozapps/update/test/unit/test_0200_app_launch_apply_update.js
@@ -282,17 +282,17 @@ function checkUpdateFinished() {
   do_check_eq(readFileBytes(file), "UpdateTestAddFile\n");
 
   file = updateTestDir.clone();
   file.append("removed-files");
   do_check_true(file.exists());
   do_check_eq(readFileBytes(file), "update_test/UpdateTestRemoveFile\n");
 
   let updatesDir = getUpdatesDir();
-  let log = updatesDir.clone();
+  log = updatesDir.clone();
   log.append("0");
   log.append(FILE_UPDATE_LOG);
   logTestInfo("testing " + log.path + " shouldn't exist");
   do_check_false(log.exists());
 
   log = updatesDir.clone();
   log.append(FILE_LAST_LOG);
   logTestInfo("testing " + log.path + " should exist");
@@ -302,10 +302,10 @@ function checkUpdateFinished() {
   log.append(FILE_BACKUP_LOG);
   logTestInfo("testing " + log.path + " shouldn't exist");
   do_check_false(log.exists());
 
   updatesDir.append("0");
   logTestInfo("testing " + updatesDir.path + " should exist");
   do_check_true(updatesDir.exists());
 
-  do_timeout(CHECK_TIMEOUT_MILLI, do_test_finished);
+  removeCallbackCopy();
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test/unit/test_0201_app_launch_apply_update.js
@@ -0,0 +1,555 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by applying an update in the background and
+ * launching an application
+ */
+
+/**
+ * The MAR file used for this test should not contain a version 2 update
+ * manifest file (e.g. updatev2.manifest).
+ */
+
+const TEST_ID = "0201";
+
+// Backup the updater.ini and use a custom one to prevent the updater from
+// launching a post update executable.
+const FILE_UPDATER_INI_BAK = "updater.ini.bak";
+
+// Number of milliseconds for each do_timeout call.
+const CHECK_TIMEOUT_MILLI = 1000;
+
+// Maximum number of milliseconds the process that is launched can run before
+// the test will try to kill it.
+const APP_TIMER_TIMEOUT = 15000;
+
+let gAppTimer;
+let gProcess;
+let gActiveUpdate;
+
+// Override getUpdatesRootDir on Mac because we need to apply the update
+// inside the bundle directory.
+function symlinkUpdateFilesIntoBundleDirectory() {
+  if (!shouldAdjustPathsOnMac()) {
+    return;
+  }
+  // Symlink active-update.xml and updates/ inside the dist/bin directory
+  // to point to the bundle directory.
+  // This is necessary because in order to test the code which actually ships
+  // with Firefox, we need to perform the update inside the bundle directory,
+  // whereas xpcshell runs from dist/bin/, and the updater service code looks
+  // at the current process directory to find things like these two files.
+
+  Components.utils.import("resource://gre/modules/ctypes.jsm");
+  let libc = ctypes.open("/usr/lib/libc.dylib");
+  // We need these two low level APIs because their functionality is not
+  // provided in nsIFile APIs.
+  let symlink = libc.declare("symlink", ctypes.default_abi, ctypes.int,
+                             ctypes.char.ptr, ctypes.char.ptr);
+  let unlink = libc.declare("unlink", ctypes.default_abi, ctypes.int,
+                            ctypes.char.ptr);
+
+  // Symlink active-update.xml
+  let dest = getAppDir();
+  dest.append("active-update.xml");
+  if (!dest.exists()) {
+    dest.create(dest.NORMAL_FILE_TYPE, 0644);
+  }
+  do_check_true(dest.exists());
+  let source = getUpdatesRootDir();
+  source.append("active-update.xml");
+  unlink(source.path);
+  let ret = symlink(dest.path, source.path);
+  do_check_eq(ret, 0);
+  do_check_true(source.exists());
+
+  // Symlink updates/
+  let dest2 = getAppDir();
+  dest2.append("updates");
+  if (dest2.exists()) {
+    dest2.remove(true);
+  }
+  dest2.create(dest.DIRECTORY_TYPE, 0755);
+  do_check_true(dest2.exists());
+  let source2 = getUpdatesRootDir();
+  source2.append("updates");
+  if (source2.exists()) {
+    source2.remove(true);
+  }
+  ret = symlink(dest2.path, source2.path);
+  do_check_eq(ret, 0);
+  do_check_true(source2.exists());
+
+  // Cleanup the symlinks when the test is finished.
+  do_register_cleanup(function() {
+    let ret = unlink(source.path);
+    do_check_false(source.exists());
+    let ret = unlink(source2.path);
+    do_check_false(source2.exists());
+  });
+
+  // Now, make sure that getUpdatesRootDir returns the application bundle
+  // directory, to make the various stuff in the test framework to work
+  // correctly.
+  getUpdatesRootDir = getAppDir;
+}
+
+function run_test() {
+  do_test_pending();
+  do_register_cleanup(end_test);
+
+  removeUpdateDirsAndFiles();
+
+  symlinkUpdateFilesIntoBundleDirectory();
+  if (IS_WIN) {
+    adjustPathsOnWindows();
+  }
+
+  if (!gAppBinPath) {
+    do_throw("Main application binary not found... expected: " +
+             APP_BIN_NAME + APP_BIN_SUFFIX);
+    return;
+  }
+
+  // Don't attempt to show a prompt when the update is finished.
+  Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, true);
+
+  let channel = Services.prefs.getCharPref(PREF_APP_UPDATE_CHANNEL);
+  let patches = getLocalPatchString(null, null, null, null, null, "true",
+                                    STATE_PENDING);
+  let updates = getLocalUpdateString(patches, null, null, null, null, null,
+                                     null, null, null, null, null, null,
+                                     null, "true", channel);
+  writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+
+  // Read the application.ini and use its application version
+  let processDir = getAppDir();
+  let file = processDir.clone();
+  file.append("application.ini");
+  let ini = AUS_Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
+            getService(AUS_Ci.nsIINIParserFactory).
+            createINIParser(file);
+  let version = ini.getString("App", "Version");
+  writeVersionFile(version);
+  writeStatusFile(STATE_PENDING);
+
+  // This is the directory where the update files will be located
+  let updateTestDir = getUpdateTestDir();
+  try {
+    removeDirRecursive(updateTestDir);
+  }
+  catch (e) {
+    logTestInfo("unable to remove directory - path: " + updateTestDir.path +
+                ", exception: " + e);
+  }
+
+  let updatesPatchDir = getUpdatesDir();
+  updatesPatchDir.append("0");
+  let mar = do_get_file("data/simple.mar");
+  mar.copyTo(updatesPatchDir, FILE_UPDATE_ARCHIVE);
+
+  reloadUpdateManagerData();
+  gActiveUpdate = gUpdateManager.activeUpdate;
+  do_check_true(!!gActiveUpdate);
+
+  let updateSettingsIni = processDir.clone();
+  updateSettingsIni.append(UPDATE_SETTINGS_INI_FILE);
+  writeFile(updateSettingsIni, UPDATE_SETTINGS_CONTENTS);
+
+  // Initiate a background update.
+  AUS_Cc["@mozilla.org/updates/update-processor;1"].
+    createInstance(AUS_Ci.nsIUpdateProcessor).
+    processUpdate(gActiveUpdate);
+
+  checkUpdateApplied();
+}
+
+function switchApp() {
+  let launchBin = getLaunchBin();
+  let args = getProcessArgs();
+  logTestInfo("launching " + launchBin.path + " " + args.join(" "));
+
+  gProcess = AUS_Cc["@mozilla.org/process/util;1"].
+                createInstance(AUS_Ci.nsIProcess);
+  gProcess.init(launchBin);
+
+  gAppTimer = AUS_Cc["@mozilla.org/timer;1"].createInstance(AUS_Ci.nsITimer);
+  gAppTimer.initWithCallback(gTimerCallback, APP_TIMER_TIMEOUT,
+                             AUS_Ci.nsITimer.TYPE_ONE_SHOT);
+
+  setEnvironment();
+
+  gProcess.runAsync(args, args.length, gProcessObserver);
+
+  resetEnvironment();
+}
+
+function end_test() {
+  if (gProcess.isRunning) {
+    logTestInfo("attempt to kill process");
+    gProcess.kill();
+  }
+
+  if (gAppTimer) {
+    logTestInfo("cancelling timer");
+    gAppTimer.cancel();
+    gAppTimer = null;
+  }
+
+  resetEnvironment();
+
+  // Remove the files added by the update.
+  let updateTestDir = getUpdateTestDir();
+  try {
+    logTestInfo("removing update test directory " + updateTestDir.path);
+    removeDirRecursive(updateTestDir);
+  }
+  catch (e) {
+    logTestInfo("unable to remove directory - path: " + updateTestDir.path +
+                ", exception: " + e);
+  }
+
+  if (IS_UNIX) {
+    // This will delete the launch script if it exists.
+    getLaunchScript();
+    if (IS_MACOSX) {
+      // This will delete the version script and version file if they exist.
+      getVersionScriptAndFile();
+    }
+  }
+
+  cleanUp();
+}
+
+/**
+ * The observer for the call to nsIProcess:runAsync.
+ */
+let gProcessObserver = {
+  observe: function PO_observe(subject, topic, data) {
+    logTestInfo("topic " + topic + ", process exitValue " + gProcess.exitValue);
+    if (gAppTimer) {
+      gAppTimer.cancel();
+      gAppTimer = null;
+    }
+    if (topic != "process-finished" || gProcess.exitValue != 0) {
+      do_throw("Failed to launch application");
+    }
+    do_timeout(CHECK_TIMEOUT_MILLI, checkUpdateFinished);
+  },
+  QueryInterface: XPCOMUtils.generateQI([AUS_Ci.nsIObserver])
+};
+
+/**
+ * The timer callback to kill the process if it takes too long.
+ */
+let gTimerCallback = {
+  notify: function TC_notify(aTimer) {
+    gAppTimer = null;
+    if (gProcess.isRunning) {
+      gProcess.kill();
+    }
+    do_throw("launch application timer expired");
+  },
+  QueryInterface: XPCOMUtils.generateQI([AUS_Ci.nsITimerCallback])
+};
+
+function shouldAdjustPathsOnMac() {
+  // When running xpcshell tests locally, xpcshell and firefox-bin do not live
+  // in the same directory.
+  let dir = getCurrentProcessDir();
+  return (IS_MACOSX && dir.leafName != "MacOS");
+}
+
+/**
+ * This function copies the entire process directory over to a new one which we
+ * can write to, so that we can test under Windows which holds locks on opened
+ * files.
+ */
+function adjustPathsOnWindows() {
+  // We copy the entire GRE directory into another location so that xpcshell
+  // running doesn't prevent the updater from moving stuff around.
+  let tmpDir = do_get_profile();
+  tmpDir.append("ExecutableDir.tmp");
+  tmpDir.createUnique(tmpDir.DIRECTORY_TYPE, 0755);
+  let procDir = getCurrentProcessDir();
+  procDir.copyTo(tmpDir, "bin");
+  let newDir = tmpDir.clone();
+  newDir.append("bin");
+  gWindowsBinDir = newDir;
+  logTestInfo("Using this new bin directory: " + gWindowsBinDir.path);
+  // Note that this directory will be deleted as part of the xpcshell teardown,
+  // so we don't need to remove it explicitly.
+
+  // We need to make NS_GRE_DIR point to the new bindir, since
+  // nsUpdateProcessor::ProcessUpdate uses NS_GRE_DIR to construct the
+  // destination path name which would be passed to updater.exe.
+  let dirProvider = {
+    getFile: function DP_getFile(prop, persistent) {
+      persistent.value = true;
+      if (prop == NS_GRE_DIR)
+        return getAppDir();
+      return null;
+    },
+    QueryInterface: function(iid) {
+      if (iid.equals(AUS_Ci.nsIDirectoryServiceProvider) ||
+          iid.equals(AUS_Ci.nsISupports))
+        return this;
+      throw AUS_Cr.NS_ERROR_NO_INTERFACE;
+    }
+  };
+  let ds = Services.dirsvc.QueryInterface(AUS_Ci.nsIDirectoryService);
+  ds.QueryInterface(AUS_Ci.nsIProperties).undefine(NS_GRE_DIR);
+  ds.registerProvider(dirProvider);
+  do_register_cleanup(function() {
+    ds.unregisterProvider(dirProvider);
+  });
+}
+
+/**
+ * Gets the directory where the update adds / removes the files contained in the
+ * update.
+ *
+ * @return  nsIFile for the directory where the update adds / removes the files
+ *          contained in the update mar.
+ */
+function getUpdateTestDir() {
+  let updateTestDir = getAppDir();
+  if (IS_MACOSX) {
+    updateTestDir = updateTestDir.parent.parent;
+  }
+  updateTestDir.append("update_test");
+  return updateTestDir;
+}
+
+/**
+ * Checks if the update has finished being applied in the background.
+ */
+function checkUpdateApplied() {
+  // Don't proceed until the update has been applied.
+  if (gUpdateManager.activeUpdate.state != STATE_APPLIED_PLATFORM) {
+    do_timeout(CHECK_TIMEOUT_MILLI, checkUpdateApplied);
+    return;
+  }
+
+  let updatedDir = getAppDir();
+  if (IS_MACOSX) {
+    updatedDir = updatedDir.parent.parent;
+  }
+  updatedDir.append(UPDATED_DIR_SUFFIX.replace("/", ""));
+  logTestInfo("testing " + updatedDir.path + " should exist");
+  do_check_true(updatedDir.exists());
+
+  let log;
+  if (IS_WIN) {
+    log = getUpdatesDir();
+  } else {
+    log = updatedDir.clone();
+    if (IS_MACOSX) {
+      log.append("Contents");
+      log.append("MacOS");
+    }
+    log.append("updates");
+  }
+  log.append(FILE_LAST_LOG);
+  if (!log.exists()) {
+    do_timeout(CHECK_TIMEOUT_MILLI, checkUpdateApplied);
+    return;
+  }
+
+  // Don't proceed until the update status is no longer pending or applying.
+  let status = readStatusFile();
+  do_check_eq(status, STATE_APPLIED_PLATFORM);
+
+  // On Windows, make sure not to use the maintenance service for switching
+  // the app.
+  if (IS_WIN) {
+    writeStatusFile(STATE_APPLIED);
+    status = readStatusFile();
+    do_check_eq(status, STATE_APPLIED);
+  }
+
+  // Log the contents of the update.log so it is simpler to diagnose a test
+  // failure.
+  let contents = readFile(log);
+  logTestInfo("contents of " + log.path + ":\n" +
+              contents.replace(/\r\n/g, "\n"));
+
+  let updateTestDir = getUpdateTestDir();
+  logTestInfo("testing " + updateTestDir.path + " shouldn't exist");
+  do_check_false(updateTestDir.exists());
+
+  updateTestDir = updatedDir.clone();
+  updateTestDir.append("update_test");
+  let file = updateTestDir.clone();
+  file.append("UpdateTestRemoveFile");
+  logTestInfo("testing " + file.path + " shouldn't exist");
+  do_check_false(file.exists());
+
+  file = updateTestDir.clone();
+  file.append("UpdateTestAddFile");
+  logTestInfo("testing " + file.path + " should exist");
+  do_check_true(file.exists());
+  do_check_eq(readFileBytes(file), "UpdateTestAddFile\n");
+
+  file = updateTestDir.clone();
+  file.append("removed-files");
+  logTestInfo("testing " + file.path + " should exist");
+  do_check_true(file.exists());
+  do_check_eq(readFileBytes(file), "update_test/UpdateTestRemoveFile\n");
+
+  let updatesDir = getUpdatesDir();
+  log = updatesDir.clone();
+  log.append("0");
+  log.append(FILE_UPDATE_LOG);
+  logTestInfo("testing " + log.path + " shouldn't exist");
+  do_check_false(log.exists());
+
+  log = updatesDir.clone();
+  log.append(FILE_LAST_LOG);
+  if (IS_WIN) {
+    // On Windows this file lives outside of the app directory, so it should exist.
+    logTestInfo("testing " + log.path + " should exist");
+    do_check_true(log.exists());
+  } else {
+    logTestInfo("testing " + log.path + " shouldn't exist");
+    do_check_false(log.exists());
+  }
+
+  log = updatesDir.clone();
+  log.append(FILE_BACKUP_LOG);
+  logTestInfo("testing " + log.path + " shouldn't exist");
+  do_check_false(log.exists());
+
+  updatesDir = updatedDir.clone();
+  if (IS_MACOSX) {
+    updatesDir.append("Contents");
+    updatesDir.append("MacOS");
+  }
+  updatesDir.append("updates");
+  log = updatesDir.clone();
+  log.append("0");
+  log.append(FILE_UPDATE_LOG);
+  logTestInfo("testing " + log.path + " shouldn't exist");
+  do_check_false(log.exists());
+
+  if (!IS_WIN) {
+    log = updatesDir.clone();
+    log.append(FILE_LAST_LOG);
+    logTestInfo("testing " + log.path + " should exist");
+    do_check_true(log.exists());
+  }
+
+  log = updatesDir.clone();
+  log.append(FILE_BACKUP_LOG);
+  logTestInfo("testing " + log.path + " shouldn't exist");
+  do_check_false(log.exists());
+
+  updatesDir.append("0");
+  logTestInfo("testing " + updatesDir.path + " shouldn't exist");
+  do_check_false(updatesDir.exists());
+
+  // Now, switch the updated version of the app
+  do_timeout(CHECK_TIMEOUT_MILLI, switchApp);
+}
+
+/**
+ * Checks if the update has finished and if it has finished performs checks for
+ * the test.
+ */
+function checkUpdateFinished() {
+  // Don't proceed until the update status is no longer applied.
+  try {
+    let status = readStatusFile();
+    if (status != STATE_SUCCEEDED) {
+      do_timeout(CHECK_TIMEOUT_MILLI, checkUpdateFinished);
+      return;
+    }
+  } catch (e) {
+    // Ignore exceptions if the status file is not found
+  }
+
+  try {
+    // This will delete the app console log file if it exists.
+    getAppConsoleLogPath();
+  } catch (e) {
+    if (e.result == Components.results.NS_ERROR_FILE_IS_LOCKED) {
+      // This might happen on Windows in case the callback application has not
+      // finished its job yet.  So, we'll wait some more.
+      do_timeout(CHECK_TIMEOUT_MILLI, checkUpdateFinished);
+      return;
+    } else {
+      do_throw("getAppConsoleLogPath threw: " + e);
+    }
+  }
+
+  // At this point we need to see if the application was switched successfully.
+
+  let updatedDir = getAppDir();
+  if (IS_MACOSX) {
+    updatedDir = updatedDir.parent.parent;
+  }
+  updatedDir.append(UPDATED_DIR_SUFFIX.replace("/", ""));
+  logTestInfo("testing " + updatedDir.path + " shouldn't exist");
+  if (updatedDir.exists()) {
+    do_timeout(CHECK_TIMEOUT_MILLI, checkUpdateFinished);
+    return;
+  }
+
+  let updateTestDir = getUpdateTestDir();
+
+  let file = updateTestDir.clone();
+  file.append("UpdateTestRemoveFile");
+  logTestInfo("testing " + file.path + " shouldn't exist");
+  do_check_false(file.exists());
+
+  file = updateTestDir.clone();
+  file.append("UpdateTestAddFile");
+  logTestInfo("testing " + file.path + " should exist");
+  do_check_true(file.exists());
+  do_check_eq(readFileBytes(file), "UpdateTestAddFile\n");
+
+  file = updateTestDir.clone();
+  file.append("removed-files");
+  logTestInfo("testing " + file.path + " should exist");
+  do_check_true(file.exists());
+  do_check_eq(readFileBytes(file), "update_test/UpdateTestRemoveFile\n");
+
+  let updatesDir = getUpdatesDir();
+  log = updatesDir.clone();
+  log.append("0");
+  log.append(FILE_UPDATE_LOG);
+  if (IS_WIN) {
+    // On Windows, this log file is written to the AppData directory, and will
+    // therefore exist.
+    logTestInfo("testing " + log.path + " should exist");
+    do_check_true(log.exists());
+  } else {
+    logTestInfo("testing " + log.path + " shouldn't exist");
+    do_check_false(log.exists());
+  }
+
+  log = updatesDir.clone();
+  log.append(FILE_LAST_LOG);
+  logTestInfo("testing " + log.path + " should exist");
+  do_check_true(log.exists());
+
+  log = updatesDir.clone();
+  log.append(FILE_BACKUP_LOG);
+  logTestInfo("testing " + log.path + " shouldn't exist");
+  do_check_false(log.exists());
+
+  updatesDir.append("0");
+  if (IS_WIN) {
+    // On Windows, this log file is written to the AppData directory, and will
+    // therefore exist.
+    logTestInfo("testing " + updatesDir.path + " should exist");
+    do_check_true(updatesDir.exists());
+  } else {
+    logTestInfo("testing " + updatesDir.path + " shouldn't exist");
+    do_check_false(updatesDir.exists());
+  }
+
+  removeCallbackCopy();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test/unit/test_0202_app_launch_apply_update_dirlocked.js
@@ -0,0 +1,292 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by applying an update in the background and
+ * launching an application
+ */
+
+/**
+ * This test is identical to test_0201_app_launch_apply_update.js, except
+ * that it locks the application directory when the test is launched to
+ * check if the alternate updated directory logic works correctly.
+ */
+
+/**
+ * The MAR file used for this test should not contain a version 2 update
+ * manifest file (e.g. updatev2.manifest).
+ */
+
+const TEST_ID = "0202";
+
+// Backup the updater.ini and use a custom one to prevent the updater from
+// launching a post update executable.
+const FILE_UPDATER_INI_BAK = "updater.ini.bak";
+
+// Number of milliseconds for each do_timeout call.
+const CHECK_TIMEOUT_MILLI = 1000;
+
+// Maximum number of milliseconds the process that is launched can run before
+// the test will try to kill it.
+const APP_TIMER_TIMEOUT = 15000;
+
+let gAppTimer;
+let gProcess;
+let gActiveUpdate;
+
+// Override getUpdatesRootDir on Mac because we need to apply the update
+// inside the bundle directory.
+function symlinkUpdateFilesIntoBundleDirectory() {
+  if (!shouldAdjustPathsOnMac()) {
+    return;
+  }
+  // Symlink active-update.xml and updates/ inside the dist/bin directory
+  // to point to the bundle directory.
+  // This is necessary because in order to test the code which actually ships
+  // with Firefox, we need to perform the update inside the bundle directory,
+  // whereas xpcshell runs from dist/bin/, and the updater service code looks
+  // at the current process directory to find things like these two files.
+
+  Components.utils.import("resource://gre/modules/ctypes.jsm");
+  let libc = ctypes.open("/usr/lib/libc.dylib");
+  // We need these two low level APIs because their functionality is not
+  // provided in nsIFile APIs.
+  let symlink = libc.declare("symlink", ctypes.default_abi, ctypes.int,
+                             ctypes.char.ptr, ctypes.char.ptr);
+  let unlink = libc.declare("unlink", ctypes.default_abi, ctypes.int,
+                            ctypes.char.ptr);
+
+  // Symlink active-update.xml
+  let dest = getAppDir();
+  dest.append("active-update.xml");
+  if (!dest.exists()) {
+    dest.create(dest.NORMAL_FILE_TYPE, 0644);
+  }
+  do_check_true(dest.exists());
+  let source = getUpdatesRootDir();
+  source.append("active-update.xml");
+  unlink(source.path);
+  let ret = symlink(dest.path, source.path);
+  do_check_eq(ret, 0);
+  do_check_true(source.exists());
+
+  // Symlink updates/
+  let dest2 = getAppDir();
+  dest2.append("updates");
+  if (dest2.exists()) {
+    dest2.remove(true);
+  }
+  dest2.create(dest.DIRECTORY_TYPE, 0755);
+  do_check_true(dest2.exists());
+  let source2 = getUpdatesRootDir();
+  source2.append("updates");
+  if (source2.exists()) {
+    source2.remove(true);
+  }
+  ret = symlink(dest2.path, source2.path);
+  do_check_eq(ret, 0);
+  do_check_true(source2.exists());
+
+  // Cleanup the symlinks when the test is finished.
+  do_register_cleanup(function() {
+    let ret = unlink(source.path);
+    do_check_false(source.exists());
+    let ret = unlink(source2.path);
+    do_check_false(source2.exists());
+  });
+
+  // Now, make sure that getUpdatesRootDir returns the application bundle
+  // directory, to make the various stuff in the test framework to work
+  // correctly.
+  getUpdatesRootDir = getAppDir;
+}
+
+function run_test() {
+  do_test_pending();
+  do_register_cleanup(end_test);
+
+  removeUpdateDirsAndFiles();
+
+  symlinkUpdateFilesIntoBundleDirectory();
+  if (IS_WIN) {
+    adjustPathsOnWindows();
+  }
+
+  if (!gAppBinPath) {
+    do_throw("Main application binary not found... expected: " +
+             APP_BIN_NAME + APP_BIN_SUFFIX);
+    return;
+  }
+
+  // Don't attempt to show a prompt when the update is finished.
+  Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, true);
+
+  let channel = Services.prefs.getCharPref(PREF_APP_UPDATE_CHANNEL);
+  let patches = getLocalPatchString(null, null, null, null, null, "true",
+                                    STATE_PENDING);
+  let updates = getLocalUpdateString(patches, null, null, null, null, null,
+                                     null, null, null, null, null, null,
+                                     null, "true", channel);
+  writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+
+  // Read the application.ini and use its application version
+  let processDir = getAppDir();
+  lockDirectory(processDir);
+  let file = processDir.clone();
+  file.append("application.ini");
+  let ini = AUS_Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
+            getService(AUS_Ci.nsIINIParserFactory).
+            createINIParser(file);
+  let version = ini.getString("App", "Version");
+  writeVersionFile(version);
+  writeStatusFile(STATE_PENDING);
+
+  // Remove the old updated directory which might be left over from previous tests.
+  let oldUpdatedDir = processDir.clone();
+  oldUpdatedDir.append(UPDATED_DIR_SUFFIX.replace("/", ""));
+  if (oldUpdatedDir.exists()) {
+    oldUpdatedDir.remove(true);
+  }
+
+  // This is the directory where the update files will be located
+  let updateTestDir = getUpdateTestDir();
+  try {
+    removeDirRecursive(updateTestDir);
+  }
+  catch (e) {
+    logTestInfo("unable to remove directory - path: " + updateTestDir.path +
+                ", exception: " + e);
+  }
+
+  let updatesPatchDir = getUpdatesDir();
+  updatesPatchDir.append("0");
+  let mar = do_get_file("data/simple.mar");
+  mar.copyTo(updatesPatchDir, FILE_UPDATE_ARCHIVE);
+
+  reloadUpdateManagerData();
+  gActiveUpdate = gUpdateManager.activeUpdate;
+  do_check_true(!!gActiveUpdate);
+
+  let updateSettingsIni = processDir.clone();
+  updateSettingsIni.append(UPDATE_SETTINGS_INI_FILE);
+  writeFile(updateSettingsIni, UPDATE_SETTINGS_CONTENTS);
+
+  // Initiate a background update.
+  AUS_Cc["@mozilla.org/updates/update-processor;1"].
+    createInstance(AUS_Ci.nsIUpdateProcessor).
+    processUpdate(gActiveUpdate);
+
+  checkUpdateApplied();
+}
+
+function end_test() {
+  // Remove the files added by the update.
+  let updateTestDir = getUpdateTestDir();
+  try {
+    logTestInfo("removing update test directory " + updateTestDir.path);
+    removeDirRecursive(updateTestDir);
+  }
+  catch (e) {
+    logTestInfo("unable to remove directory - path: " + updateTestDir.path +
+                ", exception: " + e);
+  }
+
+  if (IS_UNIX) {
+    // This will delete the launch script if it exists.
+    getLaunchScript();
+    if (IS_MACOSX) {
+      // This will delete the version script and version file if they exist.
+      getVersionScriptAndFile();
+    }
+  }
+
+  cleanUp();
+}
+
+function shouldAdjustPathsOnMac() {
+  // When running xpcshell tests locally, xpcshell and firefox-bin do not live
+  // in the same directory.
+  let dir = getCurrentProcessDir();
+  return (IS_MACOSX && dir.leafName != "MacOS");
+}
+
+/**
+ * This function copies the entire process directory over to a new one which we
+ * can write to, so that we can test under Windows which holds locks on opened
+ * files.
+ */
+function adjustPathsOnWindows() {
+  // We copy the entire GRE directory into another location so that xpcshell
+  // running doesn't prevent the updater from moving stuff around.
+  let tmpDir = do_get_profile();
+  tmpDir.append("ExecutableDir.tmp");
+  tmpDir.createUnique(tmpDir.DIRECTORY_TYPE, 0755);
+  let procDir = getCurrentProcessDir();
+  procDir.copyTo(tmpDir, "bin");
+  let newDir = tmpDir.clone();
+  newDir.append("bin");
+  gWindowsBinDir = newDir;
+  logTestInfo("Using this new bin directory: " + gWindowsBinDir.path);
+  // Note that this directory will be deleted as part of the xpcshell teardown,
+  // so we don't need to remove it explicitly.
+
+  // We need to make NS_GRE_DIR point to the new bindir, since
+  // nsUpdateProcessor::ProcessUpdate uses NS_GRE_DIR to construct the
+  // destination path name which would be passed to updater.exe.
+  let dirProvider = {
+    getFile: function DP_getFile(prop, persistent) {
+      persistent.value = true;
+      if (prop == NS_GRE_DIR)
+        return getAppDir();
+      return null;
+    },
+    QueryInterface: function(iid) {
+      if (iid.equals(AUS_Ci.nsIDirectoryServiceProvider) ||
+          iid.equals(AUS_Ci.nsISupports))
+        return this;
+      throw AUS_Cr.NS_ERROR_NO_INTERFACE;
+    }
+  };
+  let ds = Services.dirsvc.QueryInterface(AUS_Ci.nsIDirectoryService);
+  ds.QueryInterface(AUS_Ci.nsIProperties).undefine(NS_GRE_DIR);
+  ds.registerProvider(dirProvider);
+  do_register_cleanup(function() {
+    ds.unregisterProvider(dirProvider);
+  });
+}
+
+/**
+ * Gets the directory where the update adds / removes the files contained in the
+ * update.
+ *
+ * @return  nsIFile for the directory where the update adds / removes the files
+ *          contained in the update mar.
+ */
+function getUpdateTestDir() {
+  let updateTestDir = getAppDir();
+  if (IS_MACOSX) {
+    updateTestDir = updateTestDir.parent.parent;
+  }
+  updateTestDir.append("update_test");
+  return updateTestDir;
+}
+
+/**
+ * Checks if the update has failed being applied in the background.
+ */
+function checkUpdateApplied() {
+  // Don't proceed until the update has failed, and reset to pending.
+  if (gUpdateManager.activeUpdate.state != STATE_PENDING) {
+    do_timeout(CHECK_TIMEOUT_MILLI, checkUpdateApplied);
+    return;
+  }
+
+  // Don't proceed until the update status is pending.
+  let status = readStatusFile();
+  do_check_eq(status, STATE_PENDING);
+
+  unlockDirectory(getAppDir());
+
+  removeCallbackCopy();
+}
--- a/toolkit/mozapps/update/test/unit/xpcshell_updater.ini
+++ b/toolkit/mozapps/update/test/unit/xpcshell_updater.ini
@@ -1,4 +1,10 @@
 [test_0110_general.js]
 [test_0111_general.js]
 [test_0112_general.js]
+[test_0113_general.js]
+skip-if = os == 'mac'
+[test_0114_general.js]
+skip-if = os == 'mac'
+[test_0115_general.js]
 [test_0200_app_launch_apply_update.js]
+[test_0201_app_launch_apply_update.js]
--- a/toolkit/mozapps/update/test/unit/xpcshell_updater_windows.ini
+++ b/toolkit/mozapps/update/test/unit/xpcshell_updater_windows.ini
@@ -1,11 +1,21 @@
 [test_0113_versionDowngradeCheck.js]
 [test_0114_productChannelCheck.js]
 [test_0150_appBinReplaced_xp_win_complete.js]
 [test_0151_appBinPatched_xp_win_partial.js]
+[test_0152_appBinReplaced_xp_win_complete.js]
+[test_0153_appBinPatched_xp_win_partial.js]
 [test_0160_appInUse_xp_win_complete.js]
+[test_0161_appInUse_xp_win_complete.js]
 [test_0170_fileLocked_xp_win_complete.js]
 [test_0171_fileLocked_xp_win_partial.js]
+[test_0172_fileLocked_xp_win_complete.js]
+[test_0173_fileLocked_xp_win_partial.js]
 [test_0180_fileInUse_xp_win_complete.js]
 [test_0181_fileInUse_xp_win_partial.js]
 [test_0182_rmrfdirFileInUse_xp_win_complete.js]
 [test_0183_rmrfdirFileInUse_xp_win_partial.js]
+[test_0184_fileInUse_xp_win_complete.js]
+[test_0185_fileInUse_xp_win_partial.js]
+[test_0186_rmrfdirFileInUse_xp_win_complete.js]
+[test_0187_rmrfdirFileInUse_xp_win_partial.js]
+[test_0202_app_launch_apply_update_dirlocked.js]
--- a/toolkit/mozapps/update/test/unit/xpcshell_updater_xp_unix.ini
+++ b/toolkit/mozapps/update/test/unit/xpcshell_updater_xp_unix.ini
@@ -1,1 +1,3 @@
 [test_0160_appInUse_xp_unix_complete.js]
+[test_0161_appInUse_xp_unix_complete.js]
+skip-if = os == 'mac'
--- a/toolkit/mozapps/update/test_svc/Makefile.in
+++ b/toolkit/mozapps/update/test_svc/Makefile.in
@@ -14,20 +14,25 @@ XPCSHELL_TESTS = \
   unit \
   $(NULL)
 
 TESTROOT = $(call core_abspath,$(DEPTH))/_tests/xpcshell/$(relativesrcdir)
 
 DEFINES += \
   -DAB_CD=$(AB_CD) \
   -DMOZ_APP_NAME=$(MOZ_APP_NAME) \
+  -DMOZ_APP_DISPLAYNAME=$(MOZ_APP_DISPLAYNAME) \
   -DBIN_SUFFIX=$(BIN_SUFFIX) \
   -DNS_NO_XPCOM \
+  -DMOZ_DEBUG=$(MOZ_DEBUG) \
   $(NULL)
 
+# For debugging purposes only
+#DEFINES += -DDISABLE_UPDATER_AUTHENTICODE_CHECK
+
 include $(topsrcdir)/config/rules.mk
 
 libs:: ../test/unit/head_update.js.in
 	$(PYTHON) $(MOZILLA_DIR)/config/Preprocessor.py -Fsubstitution $(DEFINES) $(ACDEFINES) $^ > $(TESTROOT)/unit/head_update.js
 
 ifneq ($(OS_TARGET),Android)
 ifndef MOZ_PROFILE_GENERATE
 libs::
copy from toolkit/mozapps/update/test/unit/test_0110_general.js
copy to toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js
--- a/toolkit/mozapps/update/test/unit/test_0110_general.js
+++ b/toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js
@@ -1,16 +1,16 @@
 /* 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/.
  */
 
-/* General Complete MAR File Patch Apply Test */
+/* General Complete MAR File Background Patch Apply Test */
 
-const TEST_ID = "0110";
+const TEST_ID = "0113_svc";
 // All we care about is that the last modified time has changed so that Mac OS
 // X Launch Services invalidates its cache so the test allows up to one minute
 // difference in the last modified time.
 const MAX_TIME_DIFFERENCE = 60000;
 
 // The files are listed in the same order as they are applied from the mar's
 // update.manifest. Complete updates have remove file and rmdir directory
 // operations located in the precomplete file performed first.
@@ -230,57 +230,64 @@ ADDITIONAL_TEST_DIRS = [
   dirRemoved   : true
 }, {
   description  : "Removed by precomplete (rmdir)",
   relPathDir   : "a/b/2/",
   dirRemoved   : true
 }];
 
 function run_test() {
+  if (!shouldRunServiceTest()) {
+    return;
+  }
+
   do_test_pending();
   do_register_cleanup(cleanupUpdaterTest);
 
+  gBackgroundUpdate = true;
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
+  // apply the complete mar
+  runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED, checkUpdateApplied);
+}
+
+function checkUpdateApplied() {
+  logTestInfo("testing update.status should be " + STATE_APPLIED);
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
-  let applyToDir = getApplyDirFile();
+  do_check_eq(readStatusFile(updatesDir), STATE_APPLIED);
+
+  checkFilesAfterUpdateSuccess();
+  checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
 
-  // For Mac OS X set the last modified time for the root directory to a date in
-  // the past to test that the last modified time is updated on a successful
-  // update (bug 600098).
-  if (IS_MACOSX) {
-    let now = Date.now();
-    let yesterday = now - (1000 * 60 * 60 * 24);
-    applyToDir.lastModifiedTime = yesterday;
-  }
+  // This shouldn't exist anyways in background updates, but let's make sure
+  logTestInfo("testing tobedeleted directory doesn't exist");
+  let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
+  toBeDeletedDir = getTargetDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
 
-  // apply the complete mar
-  let exitValue = runUpdate();
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
   logTestInfo("testing updater binary process exitValue for success when " +
-              "applying a complete mar");
+              "switching to the updated application");
   do_check_eq(exitValue, 0);
 
   logTestInfo("testing update.status should be " + STATE_SUCCEEDED);
-  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
   do_check_eq(readStatusFile(updatesDir), STATE_SUCCEEDED);
 
-  // For Mac OS X check that the last modified time for a directory has been
-  // updated after a successful update (bug 600098).
-  if (IS_MACOSX) {
-    logTestInfo("testing last modified time on the apply to directory has " +
-                "changed after a successful update (bug 600098)");
-    let now = Date.now();
-    let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
-    do_check_true(timeDiff < MAX_TIME_DIFFERENCE);
-  }
+  checkFilesAfterUpdateSuccess();
+  checkUpdateLogContents(LOG_COMPLETE_SWITCH_SUCCESS);
 
-  checkFilesAfterUpdateSuccess();
-  // Sorting on Linux is different so skip this check for now.
-  if (!IS_UNIX) {
-    checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
-  }
-
+  // This shouldn't exist anyways in background updates, but let's make sure
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
+  // Make sure that the intermediate directory has been removed
+  let applyToDir = getApplyDirFile();
+  let updatedDir = applyToDir.clone();
+  updatedDir.append(UPDATED_DIR_SUFFIX.replace("/", ""));
+  do_check_false(updatedDir.exists());
+
   checkCallbackAppLog();
 }
copy from toolkit/mozapps/update/test/unit/test_0111_general.js
copy to toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js
--- a/toolkit/mozapps/update/test/unit/test_0111_general.js
+++ b/toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js
@@ -1,16 +1,16 @@
 /* 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/.
  */
 
-/* General Partial MAR File Patch Apply Test */
+/* General Partial MAR File Background Patch Apply Test */
 
-const TEST_ID = "0111";
+const TEST_ID = "0114";
 // All we care about is that the last modified time has changed so that Mac OS
 // X Launch Services invalidates its cache so the test allows up to one minute
 // difference in the last modified time.
 const MAX_TIME_DIFFERENCE = 60000;
 
 // The files are listed in the same order as they are applied from the mar's
 // update.manifest. Complete updates have remove file and rmdir directory
 // operations located in the precomplete file performed first.
@@ -232,56 +232,66 @@ ADDITIONAL_TEST_DIRS = [
   dirRemoved   : true
 }, {
   description  : "Removed by update.manifest (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : true
 }];
 
 function run_test() {
+  if (!shouldRunServiceTest()) {
+    return;
+  }
+
   do_test_pending();
   do_register_cleanup(cleanupUpdaterTest);
 
+  gBackgroundUpdate = true;
   setupUpdaterTest(MAR_PARTIAL_FILE);
 
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
-  let applyToDir = getApplyDirFile();
-
-  // For Mac OS X set the last modified time for the root directory to a date in
-  // the past to test that the last modified time is updated on all updates since
-  // the precomplete file in the root of the bundle is renamed, etc. (bug 600098).
-  if (IS_MACOSX) {
-    let now = Date.now();
-    let yesterday = now - (1000 * 60 * 60 * 24);
-    applyToDir.lastModifiedTime = yesterday;
-  }
 
   // apply the partial mar
-  let exitValue = runUpdate();
+  runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED, checkUpdateApplied);
+}
+
+function checkUpdateApplied() {
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  logTestInfo("testing update.status should be " + STATE_APPLIED);
+  do_check_eq(readStatusFile(updatesDir), STATE_APPLIED);
+
+  checkFilesAfterUpdateSuccess();
+  checkUpdateLogContents(LOG_PARTIAL_SUCCESS);
+
+  // This shouldn't exist anyways in background updates, but let's make sure
+  logTestInfo("testing tobedeleted directory doesn't exist");
+  let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
+  toBeDeletedDir = getTargetDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
   logTestInfo("testing updater binary process exitValue for success when " +
-              "applying a partial mar");
+              "switching to the updated application");
   do_check_eq(exitValue, 0);
 
   logTestInfo("testing update.status should be " + STATE_SUCCEEDED);
   do_check_eq(readStatusFile(updatesDir), STATE_SUCCEEDED);
 
-  // For Mac OS X check that the last modified time for a directory has been
-  // updated after a successful update (bug 600098).
-  if (IS_MACOSX) {
-    logTestInfo("testing last modified time on the apply to directory has " +
-                "changed after a successful update (bug 600098)");
-    let now = Date.now();
-    let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
-    do_check_true(timeDiff < MAX_TIME_DIFFERENCE);
-  }
+  checkFilesAfterUpdateSuccess();
+  checkUpdateLogContents(LOG_PARTIAL_SWITCH_SUCCESS);
 
-  checkFilesAfterUpdateSuccess();
-  // Sorting on Linux is different so skip this check for now.
-  if (!IS_UNIX) {
-    checkUpdateLogContents(LOG_PARTIAL_SUCCESS);
-  }
+  // This shouldn't exist anyways in background updates, but let's make sure
+  logTestInfo("testing tobedeleted directory doesn't exist");
+  toBeDeletedDir = getApplyDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
 
-  logTestInfo("testing tobedeleted directory doesn't exist");
-  let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
-  do_check_false(toBeDeletedDir.exists());
+  // Make sure that the intermediate directory has been removed
+  let applyToDir = getApplyDirFile();
+  let updatedDir = applyToDir.clone();
+  updatedDir.append(UPDATED_DIR_SUFFIX.replace("/", ""));
+  do_check_false(updatedDir.exists());
 
   checkCallbackAppLog();
 }
copy from toolkit/mozapps/update/test/unit/test_0112_general.js
copy to toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js
--- a/toolkit/mozapps/update/test/unit/test_0112_general.js
+++ b/toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js
@@ -1,16 +1,16 @@
 /* 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/.
  */
 
-/* General Partial MAR File Patch Apply Failure Test */
+/* General Partial MAR File Background Patch Apply Failure Test */
 
-const TEST_ID = "0112";
+const TEST_ID = "0115_svc";
 // All we care about is that the last modified time has changed so that Mac OS
 // X Launch Services invalidates its cache so the test allows up to one minute
 // difference in the last modified time.
 const MAX_TIME_DIFFERENCE = 60000;
 
 // The files are listed in the same order as they are applied from the mar's
 // update.manifest. Complete updates have remove file and rmdir directory
 // operations located in the precomplete file performed first.
@@ -225,58 +225,43 @@ ADDITIONAL_TEST_DIRS = [
   dirRemoved   : false
 }, {
   description  : "Not removed for failed update (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : false
 }];
 
 function run_test() {
+  if (!shouldRunServiceTest()) {
+    return;
+  }
+
   do_test_pending();
   do_register_cleanup(cleanupUpdaterTest);
 
+  gBackgroundUpdate = true;
   setupUpdaterTest(MAR_PARTIAL_FILE);
 
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
-  let applyToDir = getApplyDirFile();
-
-  // For Mac OS X set the last modified time for the root directory to a date in
-  // the past to test that the last modified time is updated on all updates since
-  // the precomplete file in the root of the bundle is renamed, etc. (bug 600098).
-  if (IS_MACOSX) {
-    let now = Date.now();
-    let yesterday = now - (1000 * 60 * 60 * 24);
-    applyToDir.lastModifiedTime = yesterday;
-  }
 
   // apply the partial mar
-  let exitValue = runUpdate();
-  logTestInfo("testing updater binary process exitValue for success when " +
-              "applying a partial mar");
-  do_check_eq(exitValue, 0);
+  runUpdateUsingService(STATE_PENDING_SVC, STATE_FAILED, checkUpdateApplied);
+}
 
+function checkUpdateApplied() {
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
   logTestInfo("testing update.status should be " + STATE_FAILED);
   // The update status format for a failure is failed: # where # is the error
   // code for the failure.
   do_check_eq(readStatusFile(updatesDir).split(": ")[0], STATE_FAILED);
 
-  // For Mac OS X check that the last modified time for a directory has been
-  // updated after a successful update (bug 600098).
-  if (IS_MACOSX) {
-    logTestInfo("testing last modified time on the apply to directory has " +
-                "changed after a successful update (bug 600098)");
-    let now = Date.now();
-    let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
-    do_check_true(timeDiff < MAX_TIME_DIFFERENCE);
-  }
+  checkFilesAfterUpdateFailure();
+  checkUpdateLogContents(LOG_PARTIAL_FAILURE);
 
-  checkFilesAfterUpdateFailure();
-  // Sorting on Linux is different so skip this check for now.
-  if (!IS_UNIX) {
-    checkUpdateLogContents(LOG_PARTIAL_FAILURE);
-  }
-
+  // This shouldn't exist anyways in background updates, but let's make sure
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
+  toBeDeletedDir = getTargetDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
 
-  checkCallbackAppLog();
+  do_timeout(TEST_HELPER_TIMEOUT, do_test_finished);
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test_svc/unit/test_0152_appBinReplaced_xp_win_complete_svc.js
@@ -0,0 +1,233 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Replace app binary complete MAR file background patch apply success test */
+
+const TEST_ID = "0152_svc";
+const MAR_COMPLETE_WIN_FILE = "data/complete_win.mar";
+
+// The files are listed in the same order as they are applied from the mar's
+// update.manifest. Complete updates have remove file and rmdir directory
+// operations located in the precomplete file performed first.
+const TEST_FILES = [
+{
+  description      : "Should never change",
+  fileName         : "channel-prefs.js",
+  relPathDir       : "a/b/defaults/pref/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "precomplete",
+  relPathDir       : "",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_precomplete",
+  compareFile      : "data/complete_precomplete"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginstext0",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "FromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginspng1.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginspng0.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "removed-files",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_removed-files",
+  compareFile      : "data/complete_removed-files"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1text0",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : "FromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1png1.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1png0.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0text0",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "FromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0png1.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0png0.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "exe0.exe",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_in_use_win_after.exe",
+  compareFile      : "data/partial_in_use_win_before.exe"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "10text0",
+  relPathDir       : "a/b/1/10/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "FromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add) file in use",
+  fileName         : "0exe0.exe",
+  relPathDir       : "a/b/0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_in_use_win_after.exe",
+  compareFile      : "data/partial_in_use_win_before.exe"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text1",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "FromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text0",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "FromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00png0.png",
+  relPathDir       : "a/b/0/00/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Removed by precomplete (remove)",
+  fileName         : "20text0",
+  relPathDir       : "a/b/2/20/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Removed by precomplete (remove)",
+  fileName         : "20png0.png",
+  relPathDir       : "a/b/2/20/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}];
+
+ADDITIONAL_TEST_DIRS = [
+{
+  description  : "Removed by precomplete (rmdir)",
+  relPathDir   : "a/b/2/20/",
+  dirRemoved   : true
+}, {
+  description  : "Removed by precomplete (rmdir)",
+  relPathDir   : "a/b/2/",
+  dirRemoved   : true
+}];
+
+function run_test() {
+  if (!shouldRunServiceTest()) {
+    return;
+  }
+
+  do_test_pending();
+  do_register_cleanup(cleanupUpdaterTest);
+
+  gBackgroundUpdate = true;
+  setupUpdaterTest(MAR_COMPLETE_WIN_FILE);
+
+  gCallbackBinFile = "exe0.exe";
+
+  // apply the complete mar
+  runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED, checkUpdateApplied);
+}
+
+function checkUpdateApplied() {
+  logTestInfo("testing update.status should be " + STATE_APPLIED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir), STATE_APPLIED);
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for success when " +
+              "switching to the updated application");
+  do_check_eq(exitValue, 0);
+
+  logTestInfo("testing update.status should be " + STATE_SUCCEEDED);
+  do_check_eq(readStatusFile(updatesDir), STATE_SUCCEEDED);
+
+  checkFilesAfterUpdateSuccess();
+
+  logTestInfo("testing tobedeleted directory doesn't exist");
+  let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
+
+  checkCallbackAppLog();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test_svc/unit/test_0153_appBinPatched_xp_win_partial_svc.js
@@ -0,0 +1,235 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Patch app binary partial MAR file background patch apply success test */
+
+const TEST_ID = "0153_svc";
+const MAR_IN_USE_WIN_FILE = "data/partial_win.mar";
+
+// The files are listed in the same order as they are applied from the mar's
+// update.manifest. Complete updates have remove file and rmdir directory
+// operations located in the precomplete file performed first.
+const TEST_FILES = [
+{
+  description      : "Should never change",
+  fileName         : "channel-prefs.js",
+  relPathDir       : "a/b/defaults/pref/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "precomplete",
+  relPathDir       : "",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete_precomplete",
+  compareFile      : "data/partial_precomplete"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginstext0",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : "ToBeReplacedWithFromPartial\n",
+  compareContents  : "FromPartial\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Patched by update.manifest if the file exists " +
+                     "(patch-if)",
+  fileName         : "searchpluginspng1.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Patched by update.manifest if the file exists " +
+                     "(patch-if)",
+  fileName         : "searchpluginspng0.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1text0",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : "FromPartial\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions1png1.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions1png0.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0text0",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : "ToBeReplacedWithFromPartial\n",
+  compareContents  : "FromPartial\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions0png1.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions0png0.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Patched by update.manifest (patch)",
+  fileName         : "exe0.exe",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_in_use_win_before.exe",
+  compareFile      : "data/partial_in_use_win_after.exe"
+}, {
+  description      : "Patched by update.manifest (patch) file in use",
+  fileName         : "0exe0.exe",
+  relPathDir       : "a/b/0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_in_use_win_before.exe",
+  compareFile      : "data/partial_in_use_win_after.exe"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text0",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeReplacedWithFromPartial\n",
+  compareContents  : "FromPartial\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Patched by update.manifest (patch)",
+  fileName         : "00png0.png",
+  relPathDir       : "a/b/0/00/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "20text0",
+  relPathDir       : "a/b/2/20/",
+  originalContents : null,
+  compareContents  : "FromPartial\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "20png0.png",
+  relPathDir       : "a/b/2/20/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text2",
+  relPathDir       : "a/b/0/00/",
+  originalContents : null,
+  compareContents  : "FromPartial\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Removed by update.manifest (remove)",
+  fileName         : "10text0",
+  relPathDir       : "a/b/1/10/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Removed by update.manifest (remove)",
+  fileName         : "00text1",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}];
+
+ADDITIONAL_TEST_DIRS = [
+{
+  description  : "Removed by update.manifest (rmdir)",
+  relPathDir   : "a/b/1/10/",
+  dirRemoved   : true
+}, {
+  description  : "Removed by update.manifest (rmdir)",
+  relPathDir   : "a/b/1/",
+  dirRemoved   : true
+}];
+
+function run_test() {
+  if (!shouldRunServiceTest()) {
+    return;
+  }
+
+  do_test_pending();
+  do_register_cleanup(cleanupUpdaterTest);
+
+  gBackgroundUpdate = true;
+  setupUpdaterTest(MAR_IN_USE_WIN_FILE);
+
+  gCallbackBinFile = "exe0.exe";
+
+  // apply the complete mar
+  runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED, checkUpdateApplied);
+}
+
+function checkUpdateApplied() {
+  logTestInfo("testing update.status should be " + STATE_APPLIED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir), STATE_APPLIED);
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for success when " +
+              "switching to the updated application");
+  do_check_eq(exitValue, 0);
+
+  logTestInfo("testing update.status should be " + STATE_SUCCEEDED);
+  do_check_eq(readStatusFile(updatesDir), STATE_SUCCEEDED);
+
+  checkFilesAfterUpdateSuccess();
+
+  logTestInfo("testing tobedeleted directory doesn't exist");
+  let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
+
+  checkCallbackAppLog();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test_svc/unit/test_0161_appInUse_xp_win_complete_svc.js
@@ -0,0 +1,244 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Application in use complete MAR file background patch apply failure test */
+
+const TEST_ID = "0161_svc";
+
+// The files are listed in the same order as they are applied from the mar's
+// update.manifest. Complete updates have remove file and rmdir directory
+// operations located in the precomplete file performed first.
+const TEST_FILES = [
+{
+  description      : "Should never change",
+  fileName         : "channel-prefs.js",
+  relPathDir       : "a/b/defaults/pref/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "precomplete",
+  relPathDir       : "",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_precomplete",
+  compareFile      : "data/partial_precomplete"
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "searchpluginstext0",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "searchpluginspng1.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "searchpluginspng0.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "removed-files",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_removed-files",
+  compareFile      : "data/partial_removed-files"
+}, {
+  description      : "Not added for failed update (add-if)",
+  fileName         : "extensions1text0",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add-if)",
+  fileName         : "extensions1png1.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add-if)",
+  fileName         : "extensions1png0.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add-if)",
+  fileName         : "extensions0text0",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add-if)",
+  fileName         : "extensions0png1.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Not added for failed update (add-if)",
+  fileName         : "extensions0png0.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "exe0.exe",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "10text0",
+  relPathDir       : "a/b/1/10/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "0exe0.exe",
+  relPathDir       : "a/b/0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "00text1",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "00text0",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not added for failed update (add)",
+  fileName         : "00png0.png",
+  relPathDir       : "a/b/0/00/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Not removed for failed update (remove)",
+  fileName         : "20text0",
+  relPathDir       : "a/b/2/20/",
+  originalContents : "ShouldNotBeDeleted\n",
+  compareContents  : "ShouldNotBeDeleted\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Not removed for failed update (remove)",
+  fileName         : "20png0.png",
+  relPathDir       : "a/b/2/20/",
+  originalContents : "ShouldNotBeDeleted\n",
+  compareContents  : "ShouldNotBeDeleted\n",
+  originalFile     : null,
+  compareFile      : null
+}];
+
+ADDITIONAL_TEST_DIRS = [
+{
+  description  : "Not removed for failed update (rmdir)",
+  relPathDir   : "a/b/2/20/",
+  dirRemoved   : false
+}, {
+  description  : "Not removed for failed update (rmdir)",
+  relPathDir   : "a/b/2/",
+  dirRemoved   : false
+}];
+
+function run_test() {
+  if (!shouldRunServiceTest()) {
+    return;
+  }
+
+  do_test_pending();
+  do_register_cleanup(cleanupUpdaterTest);
+
+  gBackgroundUpdate = true;
+  setupUpdaterTest(MAR_COMPLETE_FILE);
+
+  // Launch the callback helper application so it is in use during the update
+  let callbackApp = getApplyDirFile("a/b/" + gCallbackBinFile);
+  let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "40"];
+  let callbackAppProcess = AUS_Cc["@mozilla.org/process/util;1"].
+                           createInstance(AUS_Ci.nsIProcess);
+  callbackAppProcess.init(callbackApp);
+  callbackAppProcess.run(false, args, args.length);
+
+  do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+  // apply the complete mar
+  runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED, checkUpdateApplied);
+}
+
+function checkUpdateApplied() {
+  logTestInfo("testing update.status should be " + STATE_APPLIED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir), STATE_APPLIED);
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for failure when " +
+              "switching to the updated application");
+  do_check_eq(exitValue, 1);
+
+  setupHelperFinish();
+}
+
+function checkUpdate() {
+  logTestInfo("testing update.status should be " + STATE_FAILED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  // The update status format for a failure is failed: # where # is the error
+  // code for the failure.
+  do_check_eq(readStatusFile(updatesDir).split(": ")[0], STATE_FAILED);
+
+  checkFilesAfterUpdateFailure(getApplyDirFile);
+  checkUpdateLogContains(ERR_RENAME_FILE);
+
+  logTestInfo("testing tobedeleted directory doesn't exist");
+  let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
+
+  checkCallbackAppLog();
+}
copy from toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js
copy to toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js
--- a/toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js
+++ b/toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-/* File locked complete MAR file patch apply failure test */
+/* File locked complete MAR file background patch apply failure test */
 
-const TEST_ID = "0170";
+const TEST_ID = "0172_svc";
 
 // The files are listed in the same order as they are applied from the mar's
 // update.manifest. Complete updates have remove file and rmdir directory
 // operations located in the precomplete file performed first.
 const TEST_FILES = [
 {
   description      : "Should never change",
   fileName         : "channel-prefs.js",
@@ -179,58 +179,74 @@ ADDITIONAL_TEST_DIRS = [
   dirRemoved   : false
 }, {
   description  : "Not removed for failed update (rmdir)",
   relPathDir   : "a/b/2/",
   dirRemoved   : false
 }];
 
 function run_test() {
+  if (!shouldRunServiceTest()) {
+    return;
+  }
+
   do_test_pending();
   do_register_cleanup(cleanupUpdaterTest);
 
+  gBackgroundUpdate = true;
   setupUpdaterTest(MAR_COMPLETE_FILE);
 
   // Exclusively lock an existing file so it is in use during the update
   let helperBin = do_get_file(HELPER_BIN_FILE);
   let helperDestDir = getApplyDirFile("a/b/");
   helperBin.copyTo(helperDestDir, HELPER_BIN_FILE);
   helperBin = getApplyDirFile("a/b/" + HELPER_BIN_FILE);
   // Strip off the first two directories so the path has to be from the helper's
   // working directory.
   let lockFileRelPath = TEST_FILES[3].relPathDir.split("/");
   lockFileRelPath = lockFileRelPath.slice(2);
   lockFileRelPath = lockFileRelPath.join("/") + "/" + TEST_FILES[3].fileName;
-  let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "20", lockFileRelPath];
+  let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "40", lockFileRelPath];
   let lockFileProcess = AUS_Cc["@mozilla.org/process/util;1"].
                      createInstance(AUS_Ci.nsIProcess);
   lockFileProcess.init(helperBin);
   lockFileProcess.run(false, args, args.length);
 
   do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
 }
 
 function doUpdate() {
   // apply the complete mar
-  let exitValue = runUpdate();
-  logTestInfo("testing updater binary process exitValue for success when " +
-              "applying a complete mar");
-  do_check_eq(exitValue, 0);
+  runUpdateUsingService(STATE_PENDING_SVC, STATE_FAILED, checkUpdateApplied);
+}
+
+function checkUpdateApplied() {
+  logTestInfo("testing update.status should be " + STATE_FAILED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir).split(": ")[0], STATE_FAILED);
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for failure when " +
+              "switching to the updated application");
+  do_check_eq(exitValue, 1);
 
   setupHelperFinish();
 }
 
 function checkUpdate() {
   logTestInfo("testing update.status should be " + STATE_FAILED);
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
   // The update status format for a failure is failed: # where # is the error
   // code for the failure.
   do_check_eq(readStatusFile(updatesDir).split(": ")[0], STATE_FAILED);
 
-  checkFilesAfterUpdateFailure();
+  checkFilesAfterUpdateFailure(getApplyDirFile);
   checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
copy from toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js
copy to toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
--- a/toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js
+++ b/toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-/* File locked partial MAR file patch apply failure test */
+/* File locked partial MAR file background patch apply failure test */
 
-const TEST_ID = "0171";
+const TEST_ID = "0173_svc";
 
 // The files are listed in the same order as they are applied from the mar's
 // update.manifest. Complete updates have remove file and rmdir directory
 // operations located in the precomplete file performed first.
 const TEST_FILES = [
 {
   description      : "Should never change",
   fileName         : "channel-prefs.js",
@@ -180,58 +180,74 @@ ADDITIONAL_TEST_DIRS = [
   dirRemoved   : false
 }, {
   description  : "Not removed for failed update (rmdir)",
   relPathDir   : "a/b/1/",
   dirRemoved   : false
 }];
 
 function run_test() {
+  if (!shouldRunServiceTest()) {
+    return;
+  }
+
   do_test_pending();
   do_register_cleanup(cleanupUpdaterTest);
 
+  gBackgroundUpdate = true;
   setupUpdaterTest(MAR_PARTIAL_FILE);
 
   // Exclusively lock an existing file so it is in use during the update
   let helperBin = do_get_file(HELPER_BIN_FILE);
   let helperDestDir = getApplyDirFile("a/b/");
   helperBin.copyTo(helperDestDir, HELPER_BIN_FILE);
   helperBin = getApplyDirFile("a/b/" + HELPER_BIN_FILE);
   // Strip off the first two directories so the path has to be from the helper's
   // working directory.
   let lockFileRelPath = TEST_FILES[3].relPathDir.split("/");
   lockFileRelPath = lockFileRelPath.slice(2);
   lockFileRelPath = lockFileRelPath.join("/") + "/" + TEST_FILES[3].fileName;
-  let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "20", lockFileRelPath];
+  let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "40", lockFileRelPath];
   let lockFileProcess = AUS_Cc["@mozilla.org/process/util;1"].
                      createInstance(AUS_Ci.nsIProcess);
   lockFileProcess.init(helperBin);
   lockFileProcess.run(false, args, args.length);
 
   do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
 }
 
 function doUpdate() {
   // apply the complete mar
-  let exitValue = runUpdate();
-  logTestInfo("testing updater binary process exitValue for success when " +
-              "applying a partial mar");
-  do_check_eq(exitValue, 0);
+  runUpdateUsingService(STATE_PENDING_SVC, STATE_FAILED, checkUpdateApplied);
+}
+
+function checkUpdateApplied() {
+  logTestInfo("testing update.status should be " + STATE_FAILED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir).split(": ")[0], STATE_FAILED);
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for failure when " +
+              "switching to the updated application");
+  do_check_eq(exitValue, 1);
 
   setupHelperFinish();
 }
 
 function checkUpdate() {
   logTestInfo("testing update.status should be " + STATE_FAILED);
   let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
   // The update status format for a failure is failed: # where # is the error
   // code for the failure.
   do_check_eq(readStatusFile(updatesDir).split(": ")[0], STATE_FAILED);
 
-  checkFilesAfterUpdateFailure();
-  checkUpdateLogContains(ERR_UNABLE_OPEN_DEST);
+  checkFilesAfterUpdateFailure(getApplyDirFile);
+  checkUpdateLogContains(ERR_RENAME_FILE);
 
   logTestInfo("testing tobedeleted directory doesn't exist");
   let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
   do_check_false(toBeDeletedDir.exists());
 
   checkCallbackAppLog();
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test_svc/unit/test_0184_fileInUse_xp_win_complete_svc.js
@@ -0,0 +1,249 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use complete MAR file background patch apply success test */
+
+const TEST_ID = "0184_svc";
+
+// The files are listed in the same order as they are applied from the mar's
+// update.manifest. Complete updates have remove file and rmdir directory
+// operations located in the precomplete file performed first.
+const TEST_FILES = [
+{
+  description      : "Should never change",
+  fileName         : "channel-prefs.js",
+  relPathDir       : "a/b/defaults/pref/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "precomplete",
+  relPathDir       : "",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_precomplete",
+  compareFile      : "data/partial_precomplete"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginstext0",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "ToBeReplacedWithFromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginspng1.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginspng0.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "removed-files",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_removed-files",
+  compareFile      : "data/partial_removed-files"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1text0",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1png1.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial.png",
+  compareFile      : "data/partial.png"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1png0.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0text0",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "ToBeReplacedWithFromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0png1.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0png0.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "exe0.exe",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : HELPER_BIN_FILE,
+  compareFile      : HELPER_BIN_FILE
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "10text0",
+  relPathDir       : "a/b/1/10/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "ToBeReplacedWithFromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add) file in use",
+  fileName         : "0exe0.exe",
+  relPathDir       : "a/b/0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : HELPER_BIN_FILE,
+  compareFile      : HELPER_BIN_FILE
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text1",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "ToBeReplacedWithFromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00text0",
+  relPathDir       : "a/b/0/00/",
+  originalContents : "ToBeReplacedWithFromComplete\n",
+  compareContents  : "ToBeReplacedWithFromComplete\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "00png0.png",
+  relPathDir       : "a/b/0/00/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Removed by precomplete (remove)",
+  fileName         : "20text0",
+  relPathDir       : "a/b/2/20/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : "ToBeDeleted\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Removed by precomplete (remove)",
+  fileName         : "20png0.png",
+  relPathDir       : "a/b/2/20/",
+  originalContents : "ToBeDeleted\n",
+  compareContents  : "ToBeDeleted\n",
+  originalFile     : null,
+  compareFile      : null
+}];
+
+ADDITIONAL_TEST_DIRS = [
+{
+  description  : "Removed by precomplete (rmdir)",
+  relPathDir   : "a/b/2/20/",
+  dirRemoved   : true
+}, {
+  description  : "Removed by precomplete (rmdir)",
+  relPathDir   : "a/b/2/",
+  dirRemoved   : true
+}];
+
+function run_test() {
+  if (!shouldRunServiceTest()) {
+    return;
+  }
+
+  do_test_pending();
+  do_register_cleanup(cleanupUpdaterTest);
+
+  gBackgroundUpdate = true;
+  setupUpdaterTest(MAR_COMPLETE_FILE);
+
+  // Launch an existing file so it is in use during the update
+  let fileInUseBin = getApplyDirFile(TEST_FILES[14].relPathDir +
+                                     TEST_FILES[14].fileName);
+  let args = [getApplyDirPath() + "a/b/", "input", "output", "-s", "40"];
+  let fileInUseProcess = AUS_Cc["@mozilla.org/process/util;1"].
+                         createInstance(AUS_Ci.nsIProcess);
+  fileInUseProcess.init(fileInUseBin);
+  fileInUseProcess.run(false, args, args.length);
+
+  do_timeout(TEST_HELPER_TIMEOUT, waitForHelperSleep);
+}
+
+function doUpdate() {
+  // apply the complete mar
+  runUpdateUsingService(STATE_PENDING_SVC, STATE_APPLIED, checkUpdateApplied);
+}
+
+function checkUpdateApplied() {
+  logTestInfo("testing update.status should be " + STATE_APPLIED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir), STATE_APPLIED);
+
+  // Now switch the application and its updated version
+  gBackgroundUpdate = false;
+  gSwitchApp = true;
+  exitValue = runUpdate();
+  logTestInfo("testing updater binary process exitValue for failure when " +
+              "switching to the updated application");
+  do_check_eq(exitValue, 1);
+
+  setupHelperFinish();
+}
+
+function checkUpdate() {
+  logTestInfo("testing update.status should be " + STATE_FAILED);
+  let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+  do_check_eq(readStatusFile(updatesDir).split(": ")[0], STATE_FAILED);
+
+  checkFilesAfterUpdateFailure(getApplyDirFile);
+  checkUpdateLogContains(ERR_RENAME_FILE);
+
+  logTestInfo("testing tobedeleted directory does not exist");
+  let toBeDeletedDir = getApplyDirFile("tobedeleted", true);
+  do_check_false(toBeDeletedDir.exists());
+
+  checkCallbackAppLog();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/test_svc/unit/test_0185_fileInUse_xp_win_partial_svc.js
@@ -0,0 +1,252 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use partial MAR file background patch apply success test */
+
+const TEST_ID = "0185_svc";
+const MAR_IN_USE_WIN_FILE = "data/partial_win.mar";
+
+// The files are listed in the same order as they are applied from the mar's
+// update.manifest. Complete updates have remove file and rmdir directory
+// operations located in the precomplete file performed first.
+const TEST_FILES = [
+{
+  description      : "Should never change",
+  fileName         : "channel-prefs.js",
+  relPathDir       : "a/b/defaults/pref/",
+  originalContents : "ShouldNotBeReplaced\n",
+  compareContents  : "ShouldNotBeReplaced\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "precomplete",
+  relPathDir       : "",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete_precomplete",
+  compareFile      : "data/complete_precomplete"
+}, {
+  description      : "Added by update.manifest (add)",
+  fileName         : "searchpluginstext0",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : "ToBeReplacedWithFromPartial\n",
+  compareContents  : "ToBeReplacedWithFromPartial\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Patched by update.manifest if the file exists " +
+                     "(patch-if)",
+  fileName         : "searchpluginspng1.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Patched by update.manifest if the file exists " +
+                     "(patch-if)",
+  fileName         : "searchpluginspng0.png",
+  relPathDir       : "a/b/searchplugins/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions1text0",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions1png1.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions1png0.png",
+  relPathDir       : "a/b/extensions/extensions1/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Added by update.manifest if the parent directory " +
+                     "exists (add-if)",
+  fileName         : "extensions0text0",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : "ToBeReplacedWithFromPartial\n",
+  compareContents  : "ToBeReplacedWithFromPartial\n",
+  originalFile     : null,
+  compareFile      : null
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions0png1.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Patched by update.manifest if the parent directory " +
+                     "exists (patch-if)",
+  fileName         : "extensions0png0.png",
+  relPathDir       : "a/b/extensions/extensions0/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/complete.png",
+  compareFile      : "data/complete.png"
+}, {
+  description      : "Patched by update.manifest (patch)",
+  fileName         : "exe0.exe",
+  relPathDir       : "a/b/",
+  originalContents : null,
+  compareContents  : null,
+  originalFile     : "data/partial_in_use_win_before.exe",
+  compareFile      : "data/partial_in_use_win_before.exe"
+}, {
+  description      : "Patched by update.manifest (pa