Bug 1520321 - Use BITS in nsUpdateService r=rstrong
authorKirk Steuber <ksteuber@mozilla.com>
Mon, 15 Apr 2019 19:44:45 +0000
changeset 469555 84cc6e41ba17
parent 469554 a1cbd2f6e5bc
child 469556 fb9ff7baed43
push id35874
push userccoroiu@mozilla.com
push dateTue, 16 Apr 2019 04:04:58 +0000
treeherdermozilla-central@be3f40425b52 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrstrong
bugs1520321
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1520321 - Use BITS in nsUpdateService r=rstrong nsUpdateService should use BITS for download. If the BITS download fails, it will fallback to the existing download mechanism (nsIIncrementalDownload). Differential Revision: https://phabricator.services.mozilla.com/D25162
browser/app/profile/firefox.js
browser/base/content/aboutDialog-appUpdater.js
toolkit/mozapps/update/UpdateService.jsm
toolkit/mozapps/update/content/updates.js
toolkit/mozapps/update/nsIUpdateService.idl
toolkit/mozapps/update/tests/chrome/utils.js
toolkit/mozapps/update/tests/data/shared.js
toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js
toolkit/mozapps/update/tests/unit_aus_update/uiAutoPref.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -163,16 +163,21 @@ pref("app.update.url", "https://aus5.moz
 // Show the Update Checking/Ready UI when the user was idle for x seconds
 pref("app.update.idletime", 60);
 
 // Whether or not to attempt using the service for updates.
 #ifdef MOZ_MAINTENANCE_SERVICE
 pref("app.update.service.enabled", true);
 #endif
 
+#ifdef XP_WIN
+// This pref prevents BITS from being used by Firefox to download updates.
+pref("app.update.BITS.enabled", false);
+#endif
+
 // Symmetric (can be overridden by individual extensions) update preferences.
 // e.g.
 //  extensions.{GUID}.update.enabled
 //  extensions.{GUID}.update.url
 //  .. etc ..
 //
 pref("extensions.update.enabled", true);
 pref("extensions.update.url", "https://versioncheck.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%&currentAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%");
--- a/browser/base/content/aboutDialog-appUpdater.js
+++ b/browser/base/content/aboutDialog-appUpdater.js
@@ -290,17 +290,16 @@ appUpdater.prototype =
    * Starts the download of an update mar.
    */
   startDownload() {
     if (!this.update)
       this.update = this.um.activeUpdate;
     this.update.QueryInterface(Ci.nsIWritablePropertyBag);
     this.update.setProperty("foregroundDownload", "true");
 
-    this.aus.pauseDownload();
     let state = this.aus.downloadUpdate(this.update, false);
     if (state == "failed") {
       this.selectPanel("downloadFailed");
       return;
     }
 
     this.setupDownloadingUI();
   },
--- a/toolkit/mozapps/update/UpdateService.jsm
+++ b/toolkit/mozapps/update/UpdateService.jsm
@@ -3,16 +3,18 @@
 /* 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/. */
 
 "use strict";
 
 const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 const {AUSTLMY} = ChromeUtils.import("resource://gre/modules/UpdateTelemetry.jsm");
+const {Bits, BitsRequest} =
+  ChromeUtils.import("resource://gre/modules/Bits.jsm");
 const {FileUtils} = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
 const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGlobalGetters(this, ["DOMParser", "XMLHttpRequest"]);
 
 XPCOMUtils.defineLazyModuleGetters(this, {
@@ -25,16 +27,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   WindowsRegistry: "resource://gre/modules/WindowsRegistry.jsm",
 });
 
 const UPDATESERVICE_CID = Components.ID("{B3C290A6-3943-4B89-8BBE-C01EB7B3B311}");
 
 const PREF_APP_UPDATE_ALTWINDOWTYPE        = "app.update.altwindowtype";
 const PREF_APP_UPDATE_BACKGROUNDERRORS     = "app.update.backgroundErrors";
 const PREF_APP_UPDATE_BACKGROUNDMAXERRORS  = "app.update.backgroundMaxErrors";
+const PREF_APP_UPDATE_BITS_ENABLED         = "app.update.BITS.enabled";
 const PREF_APP_UPDATE_CANCELATIONS         = "app.update.cancelations";
 const PREF_APP_UPDATE_CANCELATIONS_OSX     = "app.update.cancelations.osx";
 const PREF_APP_UPDATE_CANCELATIONS_OSX_MAX = "app.update.cancelations.osx.max";
 const PREF_APP_UPDATE_DOORHANGER           = "app.update.doorhanger";
 const PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS    = "app.update.download.attempts";
 const PREF_APP_UPDATE_DOWNLOAD_MAXATTEMPTS = "app.update.download.maxAttempts";
 const PREF_APP_UPDATE_ELEVATE_NEVER        = "app.update.elevate.never";
 const PREF_APP_UPDATE_ELEVATE_VERSION      = "app.update.elevate.version";
@@ -51,16 +54,17 @@ const PREF_APP_UPDATE_SERVICE_ENABLED   
 const PREF_APP_UPDATE_SERVICE_ERRORS       = "app.update.service.errors";
 const PREF_APP_UPDATE_SERVICE_MAXERRORS    = "app.update.service.maxErrors";
 const PREF_APP_UPDATE_SILENT               = "app.update.silent";
 const PREF_APP_UPDATE_SOCKET_MAXERRORS     = "app.update.socket.maxErrors";
 const PREF_APP_UPDATE_SOCKET_RETRYTIMEOUT  = "app.update.socket.retryTimeout";
 const PREF_APP_UPDATE_STAGING_ENABLED      = "app.update.staging.enabled";
 const PREF_APP_UPDATE_URL                  = "app.update.url";
 const PREF_APP_UPDATE_URL_DETAILS          = "app.update.url.details";
+const PREF_NETWORK_PROXY_TYPE              = "network.proxy.type";
 
 const URI_BRAND_PROPERTIES      = "chrome://branding/locale/brand.properties";
 const URI_UPDATE_HISTORY_DIALOG = "chrome://mozapps/content/update/history.xul";
 const URI_UPDATE_NS             = "http://www.mozilla.org/2005/app-update";
 const URI_UPDATE_PROMPT_DIALOG  = "chrome://mozapps/content/update/updates.xul";
 const URI_UPDATES_PROPERTIES    = "chrome://mozapps/locale/update/updates.properties";
 
 const KEY_EXECUTABLE      = "XREExeF";
@@ -88,16 +92,23 @@ const STATE_PENDING_SERVICE = "pending-s
 const STATE_PENDING_ELEVATE = "pending-elevate";
 const STATE_APPLYING        = "applying";
 const STATE_APPLIED         = "applied";
 const STATE_APPLIED_SERVICE = "applied-service";
 const STATE_SUCCEEDED       = "succeeded";
 const STATE_DOWNLOAD_FAILED = "download-failed";
 const STATE_FAILED          = "failed";
 
+// These value control how frequently we get updates from the BITS client on
+// the progress made downloading. The difference between the two is that the
+// active interval is the one used when the user is watching. The idle interval
+// is the one used when no one is watching.
+const BITS_IDLE_POLL_RATE_MS = 1000;
+const BITS_ACTIVE_POLL_RATE_MS = 200;
+
 // The values below used by this code are from common/updatererrors.h
 const WRITE_ERROR                          = 7;
 const ELEVATION_CANCELED                   = 9;
 const SERVICE_UPDATER_COULD_NOT_BE_STARTED = 24;
 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;
@@ -164,16 +175,21 @@ const INVALID_UPDATER_STATUS_CODE       
 
 // Custom update error codes
 const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
 const NETWORK_ERROR_OFFLINE             = 111;
 
 // Error codes should be < 1000. Errors above 1000 represent http status codes
 const HTTP_ERROR_OFFSET                 = 1000;
 
+// The is an HRESULT error that may be returned from the BITS interface
+// indicating that access was denied. Often, this error code is returned when
+// attempting to access a job created by a different user.
+const HRESULT_E_ACCESSDENIED            = -2147024891;
+
 const DOWNLOAD_CHUNK_SIZE           = 300000; // bytes
 
 const UPDATE_WINDOW_NAME      = "Update:Wizard";
 
 // The number of consecutive failures when updating using the service before
 // setting the app.update.service.enabled preference to false.
 const DEFAULT_SERVICE_MAX_ERRORS = 10;
 
@@ -207,16 +223,21 @@ const XML_SAVER_INTERVAL_MS = 200;
 // for the phase.
 var gUpdateFileWriteInfo = {phase: null, failure: false};
 var gUpdateMutexHandle = null;
 // The permissions of the update directory should be fixed no more than once per
 // session
 var gUpdateDirPermissionFixAttempted = false;
 // This is used for serializing writes to the update log file
 var gLogfileWritePromise;
+// This value will be set to true if it appears that BITS is being used by
+// another user to download updates. We don't really want two users using BITS
+// at once. Computers with many users (ex: a school computer), should not end
+// up with dozens of BITS jobs.
+var gBITSInUseByAnotherUser = false;
 
 XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function aus_gLogEnabled() {
   return Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG, false) ||
          Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG_FILE, false);
 });
 
 XPCOMUtils.defineLazyGetter(this, "gLogfileEnabled",
                             function aus_gLogfileEnabled() {
@@ -529,16 +550,57 @@ function getCanStageUpdates() {
         "instance of the application is already handling updates for this " +
         "installation.");
     return false;
   }
 
   return gCanStageUpdatesSession;
 }
 
+/*
+ * Whether or not the application can use BITS to download updates.
+ *
+ * @return A string with one of these values:
+ *           CanUseBits
+ *           NoBits_NotWindows
+ *           NoBits_FeatureOff
+ *           NoBits_Pref
+ *           NoBits_Proxy
+ *           NoBits_OtherUser
+ */
+function getCanUseBits() {
+  if (AppConstants.platform != "win") {
+    LOG("getCanUseBits - Not using BITS because this is not Windows");
+    return "NoBits_NotWindows";
+  }
+  if (!AppConstants.MOZ_BITS_DOWNLOAD) {
+    LOG("getCanUseBits - Not using BITS because the feature is disabled");
+    return "NoBits_FeatureOff";
+  }
+  if (!Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED, true)) {
+    LOG("getCanUseBits - Not using BITS. Disabled by pref.");
+    return "NoBits_Pref";
+  }
+  if (gBITSInUseByAnotherUser) {
+    LOG("getCanUseBits - Not using BITS. Already in use by another user");
+    return "NoBits_OtherUser";
+  }
+  // Firefox support for passing proxies to BITS is still rudimentary.
+  // For now, disable BITS support on configurations that are not using the
+  // standard system proxy.
+  let defaultProxy = Ci.nsIProtocolProxyService.PROXYCONFIG_SYSTEM;
+  if (Services.prefs.getIntPref(PREF_NETWORK_PROXY_TYPE, defaultProxy) !=
+      defaultProxy) {
+    LOG("getCanUseBits - Not using BITS because of proxy usage");
+    return "NoBits_Proxy";
+  }
+  LOG("getCanUseBits - BITS can be used to download updates");
+  return "CanUseBits";
+}
+
 /**
  * Logs a string to the error console. If enabled, also logs to the update
  * messages file.
  * @param   string
  *          The string to write to the error console.
  */
 function LOG(string) {
   if (gLogEnabled) {
@@ -1856,17 +1918,25 @@ UpdateService.prototype = {
           // The OS would clean this up sometime after shutdown,
           // but that would have no guarantee on timing.
           closeHandle(gUpdateMutexHandle);
         }
         if (this._retryTimer) {
           this._retryTimer.cancel();
         }
 
-        this.pauseDownload();
+        // When downloading an update with nsIIncrementalDownload the download
+        // is stopped when the quit-application observer notification is
+        // received and networking hasn't started to shutdown. The download will
+        // be resumed the next time the application starts. Downloads using
+        // Windows BITS are not stopped since they don't require Firefox to be
+        // running to perform the download.
+        if (this._downloader && !this._downloader.usingBits) {
+          this.stopDownload();
+        }
         // Prevent leaking the downloader (bug 454964)
         this._downloader = null;
         // In case an update check is in progress.
         Cc["@mozilla.org/updates/update-checker;1"].
           createInstance(Ci.nsIUpdateChecker).stopCurrentCheck();
 
         if (gLogfileWritePromise) {
           // Intentionally passing a null function for the failure case, which
@@ -2700,40 +2770,42 @@ UpdateService.prototype = {
           "current build ID: " + Services.appinfo.appBuildID + "\n" +
           "update build ID : " + update.buildID);
       cleanupActiveUpdate();
       return STATE_NONE;
     }
 
     // If a download request is in progress vs. a download ready to resume
     if (this.isDownloading) {
-      if (update.isCompleteUpdate == this._downloader.isCompleteUpdate &&
-          background == this._downloader.background) {
+      if (update.isCompleteUpdate == this._downloader.isCompleteUpdate) {
         LOG("UpdateService:downloadUpdate - no support for downloading more " +
             "than one update at a time");
+        this._downloader.background = background;
         return readStatusFile(getUpdatesDir());
       }
       this._downloader.cancel();
     }
     this._downloader = new Downloader(background, this);
     return this._downloader.downloadUpdate(update);
   },
 
   /**
    * See nsIUpdateService.idl
    */
-  pauseDownload: function AUS_pauseDownload() {
+  stopDownload: function AUS_stopDownload() {
     if (this.isDownloading) {
       this._downloader.cancel();
     } else if (this._retryTimer) {
-      // Download status is still consider as 'downloading' during retry.
+      // Download status is still considered as 'downloading' during retry.
       // We need to cancel both retry and download at this stage.
       this._retryTimer.cancel();
       this._retryTimer = null;
-      this._downloader.cancel();
+      if (this._downloader) {
+        this._downloader.cancel();
+      }
     }
   },
 
   /**
    * See nsIUpdateService.idl
    */
   getUpdatesDirectory: getUpdatesDir,
 
@@ -2743,27 +2815,47 @@ UpdateService.prototype = {
   get isDownloading() {
     return this._downloader && this._downloader.isBusy;
   },
 
   _logStatus: function AUS__logStatus() {
     if (!gLogEnabled) {
       return;
     }
+    LOG("Logging current UpdateService status:");
     // These getters print their own logging
     this.canCheckForUpdates;
     this.canApplyUpdates;
     this.canStageUpdates;
     LOG("Elevation required: " + this.elevationRequired);
     LOG("Update being handled by other instance: " +
         this.isOtherInstanceHandlingUpdates);
     LOG("Downloading: " + !!this.isDownloading);
-    if (this._downloader) {
+    if (this._downloader && this._downloader.isBusy) {
       LOG("Downloading complete update: " + this._downloader.isCompleteUpdate);
+      LOG("Downloader using BITS: " + this._downloader.usingBits);
+      if (this._downloader._patch) {
+        // This will print its own logging
+        this._downloader._canUseBits(this._downloader._patch);
+
+        // Downloader calls QueryInterface(Ci.nsIWritablePropertyBag) on
+        // its _patch member as soon as it is assigned, so no need to do so
+        // again here.
+        let bitsResult = this._downloader._patch.getProperty("bitsResult");
+        if (bitsResult != null) {
+          LOG("Patch BITS result: " + bitsResult);
+        }
+        let internalResult =
+          this._downloader._patch.getProperty("internalResult");
+        if (internalResult != null) {
+          LOG("Patch nsIIncrementalDownload result: " + internalResult);
+        }
+      }
     }
+    LOG("End of UpdateService status");
   },
 
   classID: UPDATESERVICE_CID,
 
   _xpcom_factory: UpdateServiceFactory,
   QueryInterface: ChromeUtils.generateQI([Ci.nsIApplicationUpdateService,
                                           Ci.nsIUpdateCheckListener,
                                           Ci.nsITimerCallback,
@@ -3085,18 +3177,16 @@ UpdateManager.prototype = {
     // removed.
     cleanUpUpdatesDir(false);
 
     if (update.state == STATE_FAILED && parts[1]) {
       if (!handleUpdateFailure(update, parts[1])) {
         handleFallbackToCompleteUpdate(update, true);
       }
 
-      // This can be removed after the update ui under update/content is
-      // removed.
       update.QueryInterface(Ci.nsIWritablePropertyBag);
       update.setProperty("stagingFailed", "true");
     }
     if (update.state == STATE_APPLIED && shouldUseService()) {
       writeStatusFile(getUpdatesDir(), update.state = STATE_APPLIED_SERVICE);
     }
 
     // Now that the active update's properties have been updated write the
@@ -3512,36 +3602,71 @@ Downloader.prototype = {
   _patch: null,
 
   /**
    * The nsIUpdate that we are downloading
    */
   _update: null,
 
   /**
-   * The nsIIncrementalDownload object handling the download
+   * The nsIRequest object handling the download.
    */
   _request: null,
 
   /**
    * Whether or not the update being downloaded is a complete replacement of
    * the user's existing installation or a patch representing the difference
    * between the new version and the previous version.
    */
   isCompleteUpdate: null,
 
   /**
-   * Cancels the active download.
+   * We get the nsIRequest from nsIBITS asynchronously. When downloadUpdate has
+   * been called, but this._request is not yet valid, _pendingRequest will be
+   * a promise that will resolve when this._request has been set.
    */
-  cancel: function Downloader_cancel(cancelError) {
+  _pendingRequest: null,
+
+  /**
+   * BITS receives progress notifications slowly, unless a user is watching.
+   * This tracks what frequency notifications are happening at.
+   *
+   * This is needed because BITS downloads are started asynchronously.
+   * Specifically, this is needed to prevent a situation where the download is
+   * still starting (Downloader._pendingRequest has not resolved) when the first
+   * observer registers itself. Without this variable, there is no way of
+   * knowing whether the download was started as Active or Idle and, therefore,
+   * we don't know if we need to start Active mode when _pendingRequest
+   * resolves.
+   */
+  _bitsActiveNotifications: false,
+
+  /**
+   * Cancels the active download.
+   *
+   * For a BITS download, this will cancel and remove the download job. For
+   * an nsIIncrementalDownload, this will stop the download, but leaves the
+   * data around to allow the transfer to be resumed later.
+   */
+  cancel: async function Downloader_cancel(cancelError) {
     LOG("Downloader: cancel");
     if (cancelError === undefined) {
       cancelError = Cr.NS_BINDING_ABORTED;
     }
-    if (this._request && this._request instanceof Ci.nsIRequest) {
+    if (this.usingBits) {
+      if (this._pendingRequest) {
+        await this._pendingRequest;
+      }
+      if (this._patch.getProperty("bitsId") != null) {
+        // Make sure that we don't try to resume this download after it was
+        // cancelled.
+        this._patch.deleteProperty("bitsId");
+      }
+      await this._request.cancelAsync(cancelError);
+    } else if (this._request && this._request instanceof Ci.nsIRequest) {
       this._request.cancel(cancelError);
     }
   },
 
   /**
    * Whether or not a patch has been downloaded and staged for installation.
    */
   get patchIsStaged() {
@@ -3561,17 +3686,18 @@ Downloader.prototype = {
   _verifyDownload: function Downloader__verifyDownload() {
     LOG("Downloader:_verifyDownload called");
     if (!this._request) {
       AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
                                AUSTLMY.DWNLD_ERR_VERIFY_NO_REQUEST);
       return false;
     }
 
-    let destination = this._request.destination;
+    let destination = getUpdatesDir();
+    destination.append(FILE_UPDATE_MAR);
 
     // Ensure that the file size matches the expected file size.
     if (destination.fileSize != this._patch.size) {
       LOG("Downloader:_verifyDownload downloaded size != expected size.");
       AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
                                AUSTLMY.DWNLD_ERR_VERIFY_PATCH_SIZE_NOT_EQUAL);
       return false;
     }
@@ -3627,16 +3753,26 @@ Downloader.prototype = {
       }
       if (state == STATE_PENDING || state == STATE_PENDING_SERVICE ||
           state == STATE_PENDING_ELEVATE || state == STATE_APPLIED ||
           state == STATE_APPLIED_SERVICE) {
         LOG("Downloader:_selectPatch - already downloaded");
         return null;
       }
 
+      selectedPatch.QueryInterface(Ci.nsIWritablePropertyBag);
+      if (selectedPatch.getProperty("bitsResult") != null &&
+          selectedPatch.getProperty("internalResult") == null &&
+          selectedPatch.getProperty("stagingFailed") != null) {
+        LOG("Downloader:_selectPatch - Falling back to non-BITS download " +
+            "mechanism due to existing BITS result: " +
+            selectedPatch.getProperty("bitsResult"));
+        return selectedPatch;
+      }
+
       if (update && selectedPatch.type == "complete") {
         // This is a pretty fatal error.  Just bail.
         LOG("Downloader:_selectPatch - failed to apply complete patch!");
         writeStatusFile(updateDir, STATE_NONE);
         writeVersionFile(getUpdatesDir(), null);
         return null;
       }
 
@@ -3661,32 +3797,54 @@ Downloader.prototype = {
     updateDir = getUpdatesDir();
 
     // if update only contains a partial patch, selectedPatch == null here if
     // the partial patch has been attempted and fails and we're trying to get a
     // complete patch
     if (selectedPatch)
       selectedPatch.selected = true;
 
-    update.isCompleteUpdate = useComplete;
+    update.isCompleteUpdate = (selectedPatch.type == "complete");
 
     // Reset the Active Update object on the Update Manager and flush the
     // Active Update DB.
     var um = Cc["@mozilla.org/updates/update-manager;1"].
              getService(Ci.nsIUpdateManager);
     um.activeUpdate = update;
 
     return selectedPatch;
   },
 
   /**
    * Whether or not we are currently downloading something.
    */
   get isBusy() {
-    return this._request != null;
+    return this._request != null || this._pendingRequest != null;
+  },
+
+  get usingBits() {
+    return this._pendingRequest != null || this._request instanceof BitsRequest;
+  },
+
+  /**
+   * Returns true if the specified patch can be downloaded with BITS.
+   */
+  _canUseBits: function Downloader__canUseBits(patch) {
+    if (getCanUseBits() != "CanUseBits") {
+      // This will have printed its own logging. No need to print more.
+      return false;
+    }
+    // Regardless of success or failure, don't download the same patch with BITS
+    // twice.
+    if (patch.getProperty("bitsResult") != null) {
+      LOG("Downloader:_canUseBits - Not using BITS because it was already tried");
+      return false;
+    }
+    LOG("Downloader:_canUseBits - Patch is able to use BITS download");
+    return true;
   },
 
   /**
    * Download and stage the given update.
    * @param   update
    *          A nsIUpdate object to download a patch for. Cannot be null.
    */
   downloadUpdate: function Downloader_downloadUpdate(update) {
@@ -3704,32 +3862,134 @@ Downloader.prototype = {
     // This function may return null, which indicates that there are no patches
     // to download.
     this._patch = this._selectPatch(update, updateDir);
     if (!this._patch) {
       LOG("Downloader:downloadUpdate - no patch to download");
       AUSTLMY.pingDownloadCode(undefined, AUSTLMY.DWNLD_ERR_NO_UPDATE_PATCH);
       return readStatusFile(updateDir);
     }
+    // this._patch implements nsIWritablePropertyBag. Expose that interface
+    // immediately after a patch is assigned so that this._patch.getProperty
+    // and this._patch.setProperty can always safely be called.
+    this._patch.QueryInterface(Ci.nsIWritablePropertyBag);
     this.isCompleteUpdate = this._patch.type == "complete";
 
-    let patchFile = getUpdatesDir().clone();
-    patchFile.append(FILE_UPDATE_MAR);
-
-    // The interval is 0 since there is no need to throttle downloads.
-    let interval = 0;
-
-    LOG("Downloader:downloadUpdate - url: " + this._patch.URL + ", path: " +
-        patchFile.path + ", interval: " + interval);
-    var uri = Services.io.newURI(this._patch.URL);
-
-    this._request = Cc["@mozilla.org/network/incremental-download;1"].
-                    createInstance(Ci.nsIIncrementalDownload);
-    this._request.init(uri, patchFile, DOWNLOAD_CHUNK_SIZE, interval);
-    this._request.start(this, null);
+    if (!this._canUseBits(this._patch)) {
+      let patchFile = getUpdatesDir().clone();
+      patchFile.append(FILE_UPDATE_MAR);
+
+      // The interval is 0 since there is no need to throttle downloads.
+      let interval = 0;
+
+      LOG("Downloader:downloadUpdate - Starting nsIIncrementalDownload with " +
+          "url: " + this._patch.URL + ", path: " + patchFile.path +
+          ", interval: " + interval);
+      let uri = Services.io.newURI(this._patch.URL);
+
+      this._request = Cc["@mozilla.org/network/incremental-download;1"].
+                      createInstance(Ci.nsIIncrementalDownload);
+      this._request.init(uri, patchFile, DOWNLOAD_CHUNK_SIZE, interval);
+      this._request.start(this, null);
+    } else {
+      let monitorInterval = BITS_IDLE_POLL_RATE_MS;
+      this._bitsActiveNotifications = false;
+      // The monitor's timeout should be much greater than the longest monitor
+      // poll interval. If the timeout is too short, delay in the pipe to the
+      // update agent might cause BITS to falsely report an error, causing an
+      // unnecessary fallback to nsIIncrementalDownload.
+      let monitorTimeout = Math.max(10 * monitorInterval, 10 * 60 * 1000);
+      if (this.hasDownloadListeners) {
+        monitorInterval = BITS_ACTIVE_POLL_RATE_MS;
+        this._bitsActiveNotifications = true;
+      }
+
+      let updateRootDir = FileUtils.getDir("UpdRootD", [], true);
+      let jobName = "MozillaUpdate " + updateRootDir.leafName;
+      let updatePath = updateDir.path;
+      if (!Bits.initialized) {
+        Bits.init(jobName, updatePath, monitorTimeout);
+      }
+
+      let bitsId = this._patch.getProperty("bitsId");
+      if (bitsId) {
+        LOG("Downloader:downloadUpdate - Connecting to in-progress download. " +
+            "BITS ID: " + bitsId);
+
+        this._pendingRequest = Bits.monitorDownload(bitsId, monitorInterval,
+                                                    this, null);
+      } else {
+        LOG("Downloader:downloadUpdate - Starting BITS download with url: " +
+            this._patch.URL + ", updateDir: " + updatePath + ", filename: " +
+            FILE_UPDATE_MAR);
+
+        this._pendingRequest = Bits.startDownload(this._patch.URL,
+                                                  FILE_UPDATE_MAR,
+                                                  Ci.nsIBits.PROXY_PRECONFIG,
+                                                  monitorInterval, this, null);
+      }
+      this._pendingRequest = this._pendingRequest.then(request => {
+        this._request = request;
+        this._patch.setProperty("bitsId", request.bitsId);
+
+        LOG("Downloader:downloadUpdate - BITS download running. BITS ID: " +
+            request.bitsId);
+
+        if (this.hasDownloadListeners) {
+          this._maybeStartActiveNotifications();
+        } else {
+          this._maybeStopActiveNotifications();
+        }
+
+        Cc["@mozilla.org/updates/update-manager;1"].
+          getService(Ci.nsIUpdateManager).saveUpdates();
+        this._pendingRequest = null;
+      }, error => {
+        if (error.type == Ci.nsIBits.ERROR_TYPE_FAILED_TO_GET_BITS_JOB &&
+            error.action == Ci.nsIBits.ERROR_ACTION_MONITOR_DOWNLOAD &&
+            error.stage == Ci.nsIBits.ERROR_STAGE_BITS_CLIENT &&
+            error.codeType == Ci.nsIBits.ERROR_CODE_TYPE_HRESULT &&
+            error.code == HRESULT_E_ACCESSDENIED) {
+          LOG("Downloader:downloadUpdate - Failed to connect to existing " +
+              "BITS job. It is likely owned by another user.");
+          // This isn't really a failure code since the BITS job may be working
+          // just fine on another account, so convert this to a code that
+          // indicates that. This will make it easier to identify in telemetry.
+          error.type = Ci.nsIBits.ERROR_TYPE_ACCESS_DENIED_EXPECTED;
+          error.codeType = Ci.nsIBits.ERROR_CODE_TYPE_NONE;
+          error.code = null;
+          // When we detect this situation, disable BITS until Firefox shuts
+          // down. There are a couple of reasons for this. First, without any
+          // kind of flag, we enter an infinite loop here where we keep trying
+          // BITS over and over again (normally setting bitsResult prevents
+          // this, but we don't know the result of the BITS job, so we don't
+          // want to set that). Second, since we are trying to update, this
+          // process must have the update mutex. We don't ever give up the
+          // update mutex, so even if the other user starts Firefox, they will
+          // not complete the BITS job while this Firefox instance is around.
+          gBITSInUseByAnotherUser = true;
+        } else {
+          this._patch.setProperty("bitsResult", Cr.NS_ERROR_FAILURE);
+          Cc["@mozilla.org/updates/update-manager;1"].
+            getService(Ci.nsIUpdateManager).saveUpdates();
+
+          LOG("Downloader:downloadUpdate - Failed to start to BITS job. " +
+              "Error: " + error);
+        }
+
+        this._pendingRequest = null;
+        // Try download again with nsIIncrementalDownload
+        // The update status file has already had STATE_DOWNLOADING written to
+        // it. If the downloadUpdate call below returns early, that status
+        // should probably be rewritten. However, the only conditions that might
+        // cause it to return early would have prevented this code from running.
+        // So it should be fine.
+        this.downloadUpdate(this._update);
+      });
+    }
 
     writeStatusFile(updateDir, STATE_DOWNLOADING);
     if (this._patch.state != STATE_DOWNLOADING) {
       this._patch.state = STATE_DOWNLOADING;
       Cc["@mozilla.org/updates/update-manager;1"].
         getService(Ci.nsIUpdateManager).saveUpdates();
     }
     return STATE_DOWNLOADING;
@@ -3748,48 +4008,97 @@ Downloader.prototype = {
    *          nsIProgressEventSink
    */
   addDownloadListener: function Downloader_addDownloadListener(listener) {
     for (var i = 0; i < this._listeners.length; ++i) {
       if (this._listeners[i] == listener)
         return;
     }
     this._listeners.push(listener);
+
+    // Increase the status update frequency when someone starts listening
+    this._maybeStartActiveNotifications();
   },
 
   /**
    * Removes a download listener
    * @param   listener
    *          The listener to remove.
    */
   removeDownloadListener: function Downloader_removeDownloadListener(listener) {
     for (let i = 0; i < this._listeners.length; ++i) {
       if (this._listeners[i] == listener) {
         this._listeners.splice(i, 1);
         return;
       }
     }
+
+    // Decrease the status update frequency when no one is listening
+    if (this._listeners.length == 0) {
+      this._maybeStopActiveNotifications();
+    }
+  },
+
+  /**
+   * Returns a boolean indicating whether there are any download listeners
+   */
+  get hasDownloadListeners() {
+    return this._listeners.length > 0;
+  },
+
+  /**
+   * This speeds up BITS progress notifications in response to a user watching
+   * the notifications.
+   */
+  _maybeStartActiveNotifications: async function Downloader__maybeStartActiveNotifications() {
+    if (this.usingBits && !this._bitsActiveNotifications &&
+        this.hasDownloadListeners && this._request) {
+      LOG("Downloader:_maybeStartActiveNotifications - Starting active " +
+          "notifications");
+      await this._request.changeMonitorInterval(BITS_ACTIVE_POLL_RATE_MS).catch(error => {
+        LOG("Downloader:_maybeStartActiveNotifications - Failed to increase " +
+            "status update frequency. Error: " + error);
+      });
+    }
+  },
+
+  /**
+   * This slows down BITS progress notifications in response to a user no longer
+   * watching the notifications.
+   */
+  _maybeStopActiveNotifications: async function Downloader__maybeStopActiveNotifications() {
+    if (this.usingBits && this._bitsActiveNotifications &&
+        !this.hasDownloadListeners && this._request) {
+      LOG("Downloader:_maybeStopActiveNotifications - Stopping active " +
+          "notifications");
+      await this._request.changeMonitorInterval(BITS_IDLE_POLL_RATE_MS).catch(error => {
+        LOG("Downloader:_maybeStopActiveNotifications - Failed to decrease " +
+            "status update frequency: " + error);
+      });
+    }
   },
 
   /**
    * When the async request begins
    * @param   request
    *          The nsIRequest object for the transfer
    */
   onStartRequest: function Downloader_onStartRequest(request) {
-    if (request instanceof Ci.nsIIncrementalDownload)
+    if (!this.usingBits) {
       LOG("Downloader:onStartRequest - original URI spec: " + request.URI.spec +
           ", final URI spec: " + request.finalURI.spec);
-    // Set finalURL in onStartRequest if it is different.
-    if (this._patch.finalURL != request.finalURI.spec) {
-      this._patch.finalURL = request.finalURI.spec;
-      Cc["@mozilla.org/updates/update-manager;1"].
-        getService(Ci.nsIUpdateManager).saveUpdates();
+      // Set finalURL in onStartRequest if it is different.
+      if (this._patch.finalURL != request.finalURI.spec) {
+        this._patch.finalURL = request.finalURI.spec;
+        Cc["@mozilla.org/updates/update-manager;1"].
+          getService(Ci.nsIUpdateManager).saveUpdates();
+      }
     }
 
+    // Make shallow copy in case listeners remove themselves when called.
     let listeners = this._listeners.concat();
     let listenerCount = listeners.length;
     for (let i = 0; i < listenerCount; ++i) {
       listeners[i].onStartRequest(request);
     }
   },
 
   /**
@@ -3798,38 +4107,42 @@ Downloader.prototype = {
    *          The nsIRequest object for the transfer
    * @param   context
    *          Additional data
    * @param   progress
    *          The current number of bytes transferred
    * @param   maxProgress
    *          The total number of bytes that must be transferred
    */
-    onProgress: function Downloader_onProgress(request, context, progress,
+  onProgress: function Downloader_onProgress(request, context, progress,
                                              maxProgress) {
     LOG("Downloader:onProgress - progress: " + progress + "/" + maxProgress);
 
     if (progress > this._patch.size) {
       LOG("Downloader:onProgress - progress: " + progress +
           " is higher than patch size: " + this._patch.size);
       AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
                                AUSTLMY.DWNLD_ERR_PATCH_SIZE_LARGER);
       this.cancel(Cr.NS_ERROR_UNEXPECTED);
       return;
     }
 
-    if (maxProgress != this._patch.size) {
+    // Wait until the transfer has started (progress > 0) to verify maxProgress
+    // so that we don't check it before it is available (in which case, -1 would
+    // have been passed).
+    if (progress > 0 && maxProgress != this._patch.size) {
       LOG("Downloader:onProgress - maxProgress: " + maxProgress +
           " is not equal to expected patch size: " + this._patch.size);
       AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
                                AUSTLMY.DWNLD_ERR_PATCH_SIZE_NOT_EQUAL);
       this.cancel(Cr.NS_ERROR_UNEXPECTED);
       return;
     }
 
+    // Make shallow copy in case listeners remove themselves when called.
     var listeners = this._listeners.concat();
     var listenerCount = listeners.length;
     for (var i = 0; i < listenerCount; ++i) {
       var listener = listeners[i];
       if (listener instanceof Ci.nsIProgressEventSink)
         listener.onProgress(request, context, progress, maxProgress);
     }
     this.updateService._consecutiveSocketErrors = 0;
@@ -3846,16 +4159,17 @@ Downloader.prototype = {
    *          A status code
    * @param   statusText
    *          Human readable version of |status|
    */
   onStatus: function Downloader_onStatus(request, context, status, statusText) {
     LOG("Downloader:onStatus - status: " + status + ", statusText: " +
         statusText);
 
+    // Make shallow copy in case listeners remove themselves when called.
     var listeners = this._listeners.concat();
     var listenerCount = listeners.length;
     for (var i = 0; i < listenerCount; ++i) {
       var listener = listeners[i];
       if (listener instanceof Ci.nsIProgressEventSink)
         listener.onStatus(request, context, status, statusText);
     }
   },
@@ -3863,20 +4177,48 @@ Downloader.prototype = {
   /**
    * When data transfer ceases
    * @param   request
    *          The nsIRequest object for the transfer
    * @param   status
    *          Status code containing the reason for the cessation.
    */
    /* eslint-disable-next-line complexity */
-  onStopRequest: function Downloader_onStopRequest(request, status) {
-    if (request instanceof Ci.nsIIncrementalDownload)
-      LOG("Downloader:onStopRequest - original URI spec: " + request.URI.spec +
-          ", final URI spec: " + request.finalURI.spec + ", status: " + status);
+  onStopRequest: async function Downloader_onStopRequest(request, status) {
+    if (!this.usingBits) {
+      LOG("Downloader:onStopRequest - downloader: nsIIncrementalDownload, " +
+          "original URI spec: " + request.URI.spec + ", final URI spec: " +
+          request.finalURI.spec + ", status: " + status);
+    } else {
+      LOG("Downloader:onStopRequest - downloader: BITS, status: " + status);
+    }
+
+    if (this.usingBits) {
+      if (Components.isSuccessCode(status)) {
+        try {
+          await request.complete();
+        } catch (e) {
+          LOG("Downloader:onStopRequest - Unable to complete BITS download: " +
+              e);
+          status = Cr.NS_ERROR_FAILURE;
+        }
+      } else {
+        // BITS jobs that failed to complete should still have cancel called on
+        // them to remove the job.
+        try {
+          await request.cancelAsync();
+        } catch (e) {
+          // This will fail if the job stopped because it was cancelled.
+          // Even if this is a "real" error, there isn't really anything to do
+          // about it, and it's not really a big problem. It just means that the
+          // BITS job will stay around until it is removed automatically
+          // (default of 90 days).
+        }
+      }
+    }
 
     // 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 shouldRegisterOnlineObserver = false;
     var shouldRetrySoon = false;
     var deleteActiveUpdate = false;
@@ -3934,16 +4276,17 @@ Downloader.prototype = {
       // The online observer will continue the incremental download by
       // calling downloadUpdate on the active update which continues
       // downloading the file from where it was.
       LOG("Downloader:onStopRequest - offline, register online observer: true");
       AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
                                AUSTLMY.DWNLD_RETRY_OFFLINE);
       shouldRegisterOnlineObserver = true;
       deleteActiveUpdate = false;
+
     // Each of NS_ERROR_NET_TIMEOUT, ERROR_CONNECTION_REFUSED,
     // NS_ERROR_NET_RESET and NS_ERROR_DOCUMENT_NOT_CACHED can be returned
     // when disconnecting the internet while a download of a MAR is in
     // progress.  There may be others but I have not encountered them during
     // testing.
     } else if ((status == Cr.NS_ERROR_NET_TIMEOUT ||
                 status == Cr.NS_ERROR_CONNECTION_REFUSED ||
                 status == Cr.NS_ERROR_NET_RESET ||
@@ -3990,54 +4333,79 @@ Downloader.prototype = {
       this._update.statusText = getStatusTextFromCode(status,
                                                       Cr.NS_BINDING_FAILED);
 
       // Destroy the updates directory, since we're done with it.
       cleanUpUpdatesDir();
 
       deleteActiveUpdate = true;
     }
-    let saveUpdate = false;
+    if (!this.usingBits) {
+      this._patch.setProperty("internalResult", status);
+    } else {
+      this._patch.setProperty("bitsResult", status);
+
+      // If we failed when using BITS, we want to override the retry decision
+      // since we need to retry with nsIncrementalDownload before we give up.
+      // However, if the download was cancelled, don't retry. If the transfer
+      // was cancelled, we don't want it to restart on its own.
+      if (!Components.isSuccessCode(status) &&
+          status != Cr.NS_BINDING_ABORTED &&
+          status != Cr.NS_ERROR_ABORT) {
+        deleteActiveUpdate = false;
+        shouldRetrySoon = true;
+      }
+    }
+
     LOG("Downloader:onStopRequest - setting state to: " + state);
     if (this._patch.state != state) {
-      saveUpdate = true;
       this._patch.state = state;
     }
     var um = Cc["@mozilla.org/updates/update-manager;1"].
              getService(Ci.nsIUpdateManager);
     if (deleteActiveUpdate) {
-      saveUpdate = true;
       this._update.installDate = (new Date()).getTime();
       // Setting |activeUpdate| to null will move the active update to the
       // update history.
       um.activeUpdate = null;
     } else if (um.activeUpdate && um.activeUpdate.state != state) {
-      saveUpdate = true;
       um.activeUpdate.state = state;
     }
-    if (saveUpdate) {
-      um.saveUpdates();
-    }
+    um.saveUpdates();
 
     // Only notify listeners about the stopped state if we
     // aren't handling an internal retry.
     if (!shouldRetrySoon && !shouldRegisterOnlineObserver) {
+      // Make shallow copy in case listeners remove themselves when called.
       var listeners = this._listeners.concat();
       var listenerCount = listeners.length;
       for (var i = 0; i < listenerCount; ++i) {
         listeners[i].onStopRequest(request, status);
       }
     }
 
     this._request = null;
 
     if (state == STATE_DOWNLOAD_FAILED) {
       var allFailed = true;
+      // If we haven't already, attempt to download without BITS
+      if (request instanceof BitsRequest) {
+        LOG("Downloader:onStopRequest - BITS download failed. Falling back " +
+            "to nsIIncrementalDownload");
+        let updateStatus = this.downloadUpdate(this._update);
+        if (updateStatus == STATE_NONE) {
+          cleanupActiveUpdate();
+        } else {
+          allFailed = false;
+        }
+      }
+
       // Check if there is a complete update patch that can be downloaded.
-      if (!this._update.isCompleteUpdate && this._update.patchCount == 2) {
+      if (allFailed && !this._update.isCompleteUpdate &&
+          this._update.patchCount == 2) {
         LOG("Downloader:onStopRequest - verification of patch failed, " +
             "downloading complete update patch");
         this._update.isCompleteUpdate = true;
         let updateStatus = this.downloadUpdate(this._update);
 
         if (updateStatus == STATE_NONE) {
           cleanupActiveUpdate();
         } else {
--- a/toolkit/mozapps/update/content/updates.js
+++ b/toolkit/mozapps/update/content/updates.js
@@ -763,20 +763,17 @@ var gDownloadingPage = {
     this._startTime = Date.now();
 
     try {
       // Say that this was a foreground download, not a background download,
       // since the user cared enough to look in on this process.
       gUpdates.update.QueryInterface(Ci.nsIWritablePropertyBag);
       gUpdates.update.setProperty("foregroundDownload", "true");
 
-      // Pause any active background download and restart it as a foreground
-      // download.
-      gAUS.pauseDownload();
-      var state = gAUS.downloadUpdate(gUpdates.update, false);
+      let state = gAUS.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.cleanUp();
         gUpdates.wiz.goTo("errors");
         return;
@@ -889,17 +886,17 @@ var gDownloadingPage = {
    */
   onPause() {
     if (this._paused) {
       gAUS.downloadUpdate(gUpdates.update, false);
     } else {
       var patch = gUpdates.update.selectedPatch;
       patch.QueryInterface(Ci.nsIWritablePropertyBag);
       patch.setProperty("status", this._pausedStatus);
-      gAUS.pauseDownload();
+      gAUS.stopDownload();
     }
     this._paused = !this._paused;
 
     // Update the UI
     this._setUIState(this._paused);
   },
 
   /**
--- a/toolkit/mozapps/update/nsIUpdateService.idl
+++ b/toolkit/mozapps/update/nsIUpdateService.idl
@@ -341,19 +341,22 @@ interface nsIApplicationUpdateService : 
 
   /**
    * Get the Active Updates directory
    * @returns An nsIFile for the active updates directory.
    */
   nsIFile getUpdatesDirectory();
 
   /**
-   * Pauses the active update download process
+   * Stop the active update download process. This is the equivalent of
+   * calling nsIRequest::Cancel on the download's nsIRequest. When downloading
+   * with nsIIncrementalDownload, this will leave the partial download in place.
+   * When downloading with BITS, any partial download progress will be removed.
    */
-  void pauseDownload();
+  void stopDownload();
 
   /**
    * Whether or not there is an download happening at the moment.
    */
   readonly attribute boolean isDownloading;
 
   /**
    * Whether or not the Update Service can check for updates. This is a function
--- a/toolkit/mozapps/update/tests/chrome/utils.js
+++ b/toolkit/mozapps/update/tests/chrome/utils.js
@@ -838,16 +838,17 @@ function setupPrefs() {
     gAppUpdateStagingEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_STAGING_ENABLED);
   }
   Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
 
   Services.prefs.setIntPref(PREF_APP_UPDATE_IDLETIME, 0);
   Services.prefs.setIntPref(PREF_APP_UPDATE_PROMPTWAITTIME, 0);
   Services.prefs.setBoolPref(PREF_APP_UPDATE_SILENT, false);
   Services.prefs.setBoolPref(PREF_APP_UPDATE_DOORHANGER, false);
+  Services.prefs.setBoolPref(PREF_APP_UPDATE_BITS_ENABLED, false);
 }
 
 /**
  * Resets the most common preferences used by tests to their original values.
  */
 function resetPrefs() {
   if (gAppUpdateURLDefault) {
     gDefaultPrefBranch.setCharPref(PREF_APP_UPDATE_URL, gAppUpdateURLDefault);
@@ -903,16 +904,20 @@ function resetPrefs() {
 
   if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDMAXERRORS)) {
     Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDMAXERRORS);
   }
 
   if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_DOORHANGER)) {
     Services.prefs.clearUserPref(PREF_APP_UPDATE_DOORHANGER);
   }
+
+  if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BITS_ENABLED)) {
+    Services.prefs.clearUserPref(PREF_APP_UPDATE_BITS_ENABLED);
+  }
 }
 
 function setupTimer(aTestTimeout) {
   gTestTimeout = aTestTimeout;
   if (gTimeoutTimer) {
     gTimeoutTimer.cancel();
     gTimeoutTimer = null;
   }
--- a/toolkit/mozapps/update/tests/data/shared.js
+++ b/toolkit/mozapps/update/tests/data/shared.js
@@ -14,16 +14,17 @@ const {XPCOMUtils} = ChromeUtils.import(
 ChromeUtils.defineModuleGetter(this, "ctypes",
                                "resource://gre/modules/ctypes.jsm");
 ChromeUtils.defineModuleGetter(this, "UpdateUtils",
                                "resource://gre/modules/UpdateUtils.jsm");
 
 const PREF_APP_UPDATE_AUTO                       = "app.update.auto";
 const PREF_APP_UPDATE_BACKGROUNDERRORS           = "app.update.backgroundErrors";
 const PREF_APP_UPDATE_BACKGROUNDMAXERRORS        = "app.update.backgroundMaxErrors";
+const PREF_APP_UPDATE_BITS_ENABLED               = "app.update.BITS.enabled";
 const PREF_APP_UPDATE_CANCELATIONS               = "app.update.cancelations";
 const PREF_APP_UPDATE_CHANNEL                    = "app.update.channel";
 const PREF_APP_UPDATE_DOORHANGER                 = "app.update.doorhanger";
 const PREF_APP_UPDATE_DOWNLOADPROMPTATTEMPTS     = "app.update.download.attempts";
 const PREF_APP_UPDATE_DOWNLOADPROMPT_MAXATTEMPTS = "app.update.download.maxAttempts";
 const PREF_APP_UPDATE_DISABLEDFORTESTING         = "app.update.disabledForTesting";
 const PREF_APP_UPDATE_IDLETIME                   = "app.update.idletime";
 const PREF_APP_UPDATE_INTERVAL                   = "app.update.interval";
--- a/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
+++ b/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
@@ -728,18 +728,21 @@ var gTestDirsPartialSuccess = [
 // Concatenate the common files to the beginning of the array.
 gTestDirsPartialSuccess = gTestDirsCommon.concat(gTestDirsPartialSuccess);
 
 /**
  * Helper function for setting up the test environment.
  *
  * @param  aAppUpdateAutoEnabled
  *         See setAppUpdateAutoSync in shared.js for details.
+ * @param  aAllowBits
+ *         If true, allow update downloads via the Windows BITS service.
+ *         If false, this download mechanism will not be used.
  */
-function setupTestCommon(aAppUpdateAutoEnabled = false) {
+function setupTestCommon(aAppUpdateAutoEnabled = false, aAllowBits = false) {
   debugDump("start - general test setup");
 
   Assert.strictEqual(gTestID, undefined,
                      "gTestID should be 'undefined' (setupTestCommon should " +
                      "only be called once)");
 
   let caller = Components.stack.caller;
   gTestID = caller.filename.toString().split("/").pop().split(".")[0];
@@ -834,16 +837,17 @@ function setupTestCommon(aAppUpdateAutoE
       } catch (e) {
         logTestInfo("non-fatal error removing directory. Path: " +
                     updatesDir.path + ", Exception: " + e);
       }
     }
   }
 
   setAppUpdateAutoSync(aAppUpdateAutoEnabled);
+  Services.prefs.setBoolPref(PREF_APP_UPDATE_BITS_ENABLED, aAllowBits);
 
   debugDump("finish - general test setup");
   return true;
 }
 
 /**
  * Nulls out the most commonly used global vars used by tests to prevent leaks
  * as needed and attempts to restore the system to its original state.
@@ -937,16 +941,17 @@ function cleanupTestCommon() {
       removeDirRecursive(applyDir);
     } catch (e) {
       logTestInfo("non-fatal error removing directory. Path: " +
                   applyDir.path + ", Exception: " + e);
     }
   }
 
   resetEnvironment();
+  Services.prefs.clearUserPref(PREF_APP_UPDATE_BITS_ENABLED);
 
   debugDump("finish - general test cleanup");
 
   if (gRealDump) {
     dump = gRealDump;
     gRealDump = null;
   }
 
--- a/toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js
+++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js
@@ -19,13 +19,13 @@ function run_test() {
   standardInit();
 
   Assert.equal(gUpdateManager.updateCount, 0,
                "the update manager updateCount attribute" + MSG_SHOULD_EQUAL);
   Assert.equal(gUpdateManager.activeUpdate.state, STATE_DOWNLOADING,
                "the update manager activeUpdate state attribute" +
                MSG_SHOULD_EQUAL);
 
-  // Pause the download early to prevent it writing the update xml files during
+  // Cancel the download early to prevent it writing the update xml files during
   // shutdown.
-  gAUS.pauseDownload();
+  gAUS.stopDownload();
   executeSoon(doTestFinish);
 }
--- a/toolkit/mozapps/update/tests/unit_aus_update/uiAutoPref.js
+++ b/toolkit/mozapps/update/tests/unit_aus_update/uiAutoPref.js
@@ -50,20 +50,20 @@ function run_test() {
   gAUS.notify(null);
 }
 
 function check_status() {
   let status = readStatusFile();
   Assert.notEqual(status, STATE_DOWNLOADING,
                   "the update state" + MSG_SHOULD_EQUAL);
 
-  // Pause the download and reload the Update Manager with an empty update so
+  // Cancel the download and reload the Update Manager with an empty update so
   // the Application Update Service doesn't write the update xml files during
   // xpcom-shutdown which will leave behind the test directory.
-  gAUS.pauseDownload();
+  gAUS.stopDownload();
   writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), true);
   writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
   reloadUpdateManagerData();
 
   executeSoon(doTestFinish);
 }
 
 function check_showUpdateAvailable() {