Bug 1423967 - Backed out 4 changesets (bug 1348087, bug 1416295) for application update issues - r=backout a=me DEVEDITION_58_0b10_RELEASE FIREFOX_58_0b10_BUILD1 FIREFOX_58_0b10_RELEASE
authorJulien Cristau <jcristau@mozilla.com>
Thu, 07 Dec 2017 17:47:37 +0100
changeset 445262 4fba9ec9d4ba8b3a224984f68b3358a85ed60ca6
parent 445261 23ea04b2a74479da19d1655b85ba88d6af25ce78
child 445263 acc119a1fbe3baca0a5570145fb5281a74a94349
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout, me
bugs1423967, 1348087, 1416295, 1423571, 1420210
milestone58.0
Bug 1423967 - Backed out 4 changesets (bug 1348087, bug 1416295) for application update issues - r=backout a=me Quoting Matt: Bug 1348087 is continually causing issues like bug 1423571 and bug 1420210. It needs more time. We should back it out from beta 58 to give it that time. Backed out changeset df5703f27971 (bug 1416295) Backed out changeset ae2fcdddead1 (bug 1348087) Backed out changeset fb54cd45fa10 (bug 1348087) Backed out changeset edfa340ec9fb (bug 1348087)
browser/branding/aurora/pref/firefox-branding.js
browser/branding/nightly/pref/firefox-branding.js
browser/branding/official/pref/firefox-branding.js
browser/branding/unofficial/pref/firefox-branding.js
js/xpconnect/src/xpc.msg
toolkit/mozapps/update/UpdateTelemetry.jsm
toolkit/mozapps/update/nsUpdateService.js
toolkit/mozapps/update/tests/chrome/utils.js
toolkit/mozapps/update/tests/data/shared.js
toolkit/mozapps/update/tests/data/sharedUpdateXML.js
toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedNoRecovery.js
toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedOffline.js
toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js
toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecoveryChannel.js
toolkit/mozapps/update/tests/unit_aus_update/downloadResumeAfterRestart.js
toolkit/mozapps/update/tests/unit_aus_update/downloadResumeErrorNoEntityID.js
toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js
toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js
toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js
toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini
--- a/browser/branding/aurora/pref/firefox-branding.js
+++ b/browser/branding/aurora/pref/firefox-branding.js
@@ -4,16 +4,20 @@
  * 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/. */
 
 pref("startup.homepage_override_url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%a2/whatsnew/");
 pref("startup.homepage_welcome_url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%a2/firstrun/");
 pref("startup.homepage_welcome_url.additional", "");
 // The time interval between checks for a new version (in seconds)
 pref("app.update.interval", 28800); // 8 hours
+// The time interval between the downloading of mar file chunks in the
+// background (in seconds)
+// 0 means "download everything at once"
+pref("app.update.download.backgroundInterval", 0);
 // Give the user x seconds to react before showing the big UI. default=192 hours
 pref("app.update.promptWaitTime", 691200);
 // URL user can browse to manually if for some reason all update installation
 // attempts fail.
 pref("app.update.url.manual", "https://www.mozilla.org/firefox/aurora/");
 // A default value for the "More information about this update" link
 // supplied in the "An update is available" page of the update wizard.
 pref("app.update.url.details", "https://www.mozilla.org/firefox/aurora/");
--- a/browser/branding/nightly/pref/firefox-branding.js
+++ b/browser/branding/nightly/pref/firefox-branding.js
@@ -2,16 +2,20 @@
  * 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/. */
 
 pref("startup.homepage_override_url", "https://www.mozilla.org/projects/firefox/%VERSION%/whatsnew/?oldversion=%OLD_VERSION%");
 pref("startup.homepage_welcome_url", "https://www.mozilla.org/projects/firefox/%VERSION%/firstrun/");
 pref("startup.homepage_welcome_url.additional", "");
 // The time interval between checks for a new version (in seconds)
 pref("app.update.interval", 7200); // 2 hours
+// The time interval between the downloading of mar file chunks in the
+// background (in seconds)
+// 0 means "download everything at once"
+pref("app.update.download.backgroundInterval", 0);
 // Give the user x seconds to react before showing the big UI. default=12 hours
 pref("app.update.promptWaitTime", 43200);
 // URL user can browse to manually if for some reason all update installation
 // attempts fail.
 pref("app.update.url.manual", "https://www.mozilla.org/%LOCALE%/firefox/nightly/");
 // A default value for the "More information about this update" link
 // supplied in the "An update is available" page of the update wizard.
 pref("app.update.url.details", "https://www.mozilla.org/%LOCALE%/firefox/nightly/notes/");
--- a/browser/branding/official/pref/firefox-branding.js
+++ b/browser/branding/official/pref/firefox-branding.js
@@ -2,16 +2,20 @@
  * 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/. */
 
 pref("startup.homepage_override_url", "");
 pref("startup.homepage_welcome_url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/firstrun/");
 pref("startup.homepage_welcome_url.additional", "");
 // Interval: Time between checks for a new version (in seconds)
 pref("app.update.interval", 43200); // 12 hours
+// The time interval between the downloading of mar file chunks in the
+// background (in seconds)
+// 0 means "download everything at once"
+pref("app.update.download.backgroundInterval", 0);
 // Give the user x seconds to react before showing the big UI. default=192 hours
 pref("app.update.promptWaitTime", 691200);
 // URL user can browse to manually if for some reason all update installation
 // attempts fail.
 pref("app.update.url.manual", "https://www.mozilla.org/firefox/");
 // A default value for the "More information about this update" link
 // supplied in the "An update is available" page of the update wizard.
 pref("app.update.url.details", "https://www.mozilla.org/%LOCALE%/firefox/notes");
--- a/browser/branding/unofficial/pref/firefox-branding.js
+++ b/browser/branding/unofficial/pref/firefox-branding.js
@@ -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/. */
 
 pref("startup.homepage_override_url", "");
 pref("startup.homepage_welcome_url", "");
 pref("startup.homepage_welcome_url.additional", "");
 // The time interval between checks for a new version (in seconds)
 pref("app.update.interval", 86400); // 24 hours
+// The time interval between the downloading of mar file chunks in the
+// background (in seconds)
+pref("app.update.download.backgroundInterval", 60);
 // Give the user x seconds to react before showing the big UI. default=24 hours
 pref("app.update.promptWaitTime", 86400);
 // URL user can browse to manually if for some reason all update installation
 // attempts fail.
 pref("app.update.url.manual", "https://nightly.mozilla.org");
 // A default value for the "More information about this update" link
 // supplied in the "An update is available" page of the update wizard.
 pref("app.update.url.details", "https://nightly.mozilla.org");
--- a/js/xpconnect/src/xpc.msg
+++ b/js/xpconnect/src/xpc.msg
@@ -229,12 +229,9 @@ XPC_MSG_DEF(NS_ERROR_DOM_NOT_FOUND_ERR  
 XPC_MSG_DEF(NS_ERROR_DOM_NOT_ALLOWED_ERR              , "The request is not allowed.")
 
 /* Codes related to the URIClassifier service */
 XPC_MSG_DEF(NS_ERROR_MALWARE_URI                      , "The URI is malware")
 XPC_MSG_DEF(NS_ERROR_PHISHING_URI                     , "The URI is phishing")
 XPC_MSG_DEF(NS_ERROR_TRACKING_URI                     , "The URI is tracking")
 XPC_MSG_DEF(NS_ERROR_UNWANTED_URI                     , "The URI is unwanted")
 XPC_MSG_DEF(NS_ERROR_BLOCKED_URI                      , "The URI is blocked")
-XPC_MSG_DEF(NS_ERROR_HARMFUL_URI                      , "The URI is harmful")
-
-/* nsISocketTransport status codes */
-XPC_MSG_DEF(NS_NET_STATUS_WAITING_FOR                 , "Waiting for response")
+XPC_MSG_DEF(NS_ERROR_HARMFUL_URI                      , "The URI is harmful")
\ No newline at end of file
--- a/toolkit/mozapps/update/UpdateTelemetry.jsm
+++ b/toolkit/mozapps/update/UpdateTelemetry.jsm
@@ -171,17 +171,16 @@ this.AUSTLMY = {
   DWNLD_ERR_NO_UPDATE_PATCH: 6,
   DWNLD_ERR_PATCH_SIZE_LARGER: 8,
   DWNLD_ERR_PATCH_SIZE_NOT_EQUAL: 9,
   DWNLD_ERR_BINDING_ABORTED: 10,
   DWNLD_ERR_ABORT: 11,
   DWNLD_ERR_DOCUMENT_NOT_CACHED: 12,
   DWNLD_ERR_VERIFY_NO_REQUEST: 13,
   DWNLD_ERR_VERIFY_PATCH_SIZE_NOT_EQUAL: 14,
-  DWNLD_RESUME_FAILURE: 15,
 
   /**
    * Submit a telemetry ping for the update download result code.
    *
    * @param  aIsComplete
    *         If true the histogram is for a patch type complete, if false the
    *         histogram is for a patch type partial, and when undefined the
    *         histogram is for an unknown patch type. This is used to determine
--- a/toolkit/mozapps/update/nsUpdateService.js
+++ b/toolkit/mozapps/update/nsUpdateService.js
@@ -16,16 +16,17 @@ Cu.import("resource://gre/modules/Update
 Cu.import("resource://gre/modules/AppConstants.jsm", this);
 Cu.importGlobalProperties(["XMLHttpRequest"]);
 
 const UPDATESERVICE_CID = Components.ID("{B3C290A6-3943-4B89-8BBE-C01EB7B3B311}");
 const UPDATESERVICE_CONTRACTID = "@mozilla.org/updates/update-service;1";
 
 const PREF_APP_UPDATE_ALTWINDOWTYPE        = "app.update.altwindowtype";
 const PREF_APP_UPDATE_AUTO                 = "app.update.auto";
+const PREF_APP_UPDATE_BACKGROUNDINTERVAL   = "app.update.download.backgroundInterval";
 const PREF_APP_UPDATE_BACKGROUNDERRORS     = "app.update.backgroundErrors";
 const PREF_APP_UPDATE_BACKGROUNDMAXERRORS  = "app.update.backgroundMaxErrors";
 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";
@@ -151,16 +152,20 @@ 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;
 
+const DOWNLOAD_CHUNK_SIZE           = 300000; // bytes
+const DOWNLOAD_BACKGROUND_INTERVAL  = 600;    // seconds
+const DOWNLOAD_FOREGROUND_INTERVAL  = 0;
+
 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;
 
 // The number of consecutive socket errors to allow before falling back to
 // downloading a different MAR file or failing if already downloading the full.
@@ -181,36 +186,30 @@ const APPID_TO_TOPIC = {
   // SeaMonkey
   "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": "sessionstore-windows-restored",
   // Thunderbird
   "{3550f703-e582-4d05-9a08-453d09bdfdc6}": "mail-startup-done",
   // Instantbird
   "{33cb9019-c295-46dd-be21-8c4936574bee}": "xul-window-visible",
 };
 
-// Download progress notifications are throttled to fire at least this many
-// milliseconds apart, to keep the UI from updating too fast to read.
-const DOWNLOAD_PROGRESS_INTERVAL = 500; // ms
-
 // A var is used for the delay so tests can set a smaller value.
 var gSaveUpdateXMLDelay = 2000;
 var gUpdateMutexHandle = null;
 
 XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
                                   "resource://gre/modules/UpdateUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
                                   "resource://gre/modules/WindowsRegistry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
                                   "resource://gre/modules/AsyncShutdown.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
                                   "resource://gre/modules/DeferredTask.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                  "resource://gre/modules/NetUtil.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function aus_gLogEnabled() {
   return getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
 });
 
 XPCOMUtils.defineLazyGetter(this, "gUpdateBundle", function aus_gUpdateBundle() {
   return Services.strings.createBundle(URI_UPDATES_PROPERTIES);
 });
@@ -1141,19 +1140,16 @@ function UpdatePatch(patch) {
   this._properties = {};
   for (var i = 0; i < patch.attributes.length; ++i) {
     var attr = patch.attributes.item(i);
     attr.QueryInterface(Ci.nsIDOMAttr);
     switch (attr.name) {
       case "selected":
         this.selected = attr.value == "true";
         break;
-      case "entityID":
-        this.setProperty("entityID", attr.value);
-        break;
       case "size":
         if (0 == parseInt(attr.value)) {
           LOG("UpdatePatch:init - 0-sized patch!");
           throw Cr.NS_ERROR_ILLEGAL_VALUE;
         }
         // fall through
       default:
         this[attr.name] = attr.value;
@@ -1273,16 +1269,18 @@ UpdatePatch.prototype = {
  */
 function Update(update) {
   this._properties = {};
   this._patches = [];
   this.isCompleteUpdate = false;
   this.unsupported = false;
   this.channel = "default";
   this.promptWaitTime = getPref("getIntPref", PREF_APP_UPDATE_PROMPTWAITTIME, 43200);
+  this.backgroundInterval = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDINTERVAL,
+                                    DOWNLOAD_BACKGROUND_INTERVAL);
 
   // Null <update>, assume this is a message container and do no
   // further initialization
   if (!update) {
     return;
   }
 
   const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
@@ -1330,16 +1328,20 @@ function Update(update) {
         this.errorCode = val;
       }
     } else if (attr.name == "isCompleteUpdate") {
       this.isCompleteUpdate = attr.value == "true";
     } else if (attr.name == "promptWaitTime") {
       if (!isNaN(attr.value)) {
         this.promptWaitTime = parseInt(attr.value);
       }
+    } else if (attr.name == "backgroundInterval") {
+      if (!isNaN(attr.value)) {
+        this.backgroundInterval = parseInt(attr.value);
+      }
     } else if (attr.name == "unsupported") {
       this.unsupported = attr.value == "true";
     } else {
       this[attr.name] = attr.value;
 
       switch (attr.name) {
         case "appVersion":
         case "buildID":
@@ -1360,16 +1362,19 @@ function Update(update) {
       }
     }
   }
 
   if (!this.displayVersion) {
     this.displayVersion = this.appVersion;
   }
 
+  // Don't allow the background download interval to be greater than 10 minutes.
+  this.backgroundInterval = Math.min(this.backgroundInterval, 600);
+
   // The Update Name is either the string provided by the <update> element, or
   // the string: "<App Name> <Update App Version>"
   var name = "";
   if (update.hasAttribute("name")) {
     name = update.getAttribute("name");
   } else {
     var brandBundle = Services.strings.createBundle(URI_BRAND_PROPERTIES);
     var appName = brandBundle.GetStringFromName("brandShortName");
@@ -1465,16 +1470,17 @@ Update.prototype = {
   serialize: function Update_serialize(updates) {
     // If appVersion isn't defined just return null. This happens when cleaning
     // up invalid updates (e.g. incorrect channel).
     if (!this.appVersion) {
       return null;
     }
     var update = updates.createElementNS(URI_UPDATE_NS, "update");
     update.setAttribute("appVersion", this.appVersion);
+    update.setAttribute("backgroundInterval", this.backgroundInterval);
     update.setAttribute("buildID", this.buildID);
     update.setAttribute("channel", this.channel);
     update.setAttribute("displayVersion", this.displayVersion);
     update.setAttribute("installDate", this.installDate);
     update.setAttribute("isCompleteUpdate", this.isCompleteUpdate);
     update.setAttribute("name", this.name);
     update.setAttribute("promptWaitTime", this.promptWaitTime);
     update.setAttribute("serviceURL", this.serviceURL);
@@ -2459,17 +2465,17 @@ UpdateService.prototype = {
         LOG("UpdateService:downloadUpdate - no support for downloading more " +
             "than one update at a time");
         return readStatusFile(getUpdatesDir());
       }
       this._downloader.cancel();
     }
     // Set the previous application version prior to downloading the update.
     update.previousAppVersion = Services.appinfo.version;
-    this._downloader = getDownloader(background, this);
+    this._downloader = new Downloader(background, this);
     return this._downloader.downloadUpdate(update);
   },
 
   /**
    * See nsIUpdateService.idl
    */
   pauseDownload: function AUS_pauseDownload() {
     if (this.isDownloading) {
@@ -3196,91 +3202,108 @@ Checker.prototype = {
 
 /**
  * Manages the download of updates
  * @param   background
  *          Whether or not this downloader is operating in background
  *          update mode.
  * @param   updateService
  *          The update service that created this downloader.
+ * @constructor
  */
-class CommonDownloader {
-  constructor(background, updateService) {
-    this.background = background;
-    this.updateService = updateService;
-
-    /**
-     * The nsIUpdatePatch that we are downloading
-     */
-    this._patch = null;
-
-    /**
-     * The nsIUpdate that we are downloading
-     */
-    this._update = 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.
-     */
-    this.isCompleteUpdate = null;
-
-    /**
-     * An array of download listeners to notify when we receive
-     * nsIRequestObserver or nsIProgressEventSink method calls.
-     */
-    this._listeners = [];
-  }
+function Downloader(background, updateService) {
+  LOG("Creating Downloader");
+  this.background = background;
+  this.updateService = updateService;
+}
+Downloader.prototype = {
+  /**
+   * The nsIUpdatePatch that we are downloading
+   */
+  _patch: null,
+
+  /**
+   * The nsIUpdate that we are downloading
+   */
+  _update: null,
+
+  /**
+   * The nsIIncrementalDownload 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.
+   */
+  cancel: function Downloader_cancel(cancelError) {
+    LOG("Downloader: cancel");
+    if (cancelError === undefined) {
+      cancelError = Cr.NS_BINDING_ABORTED;
+    }
+    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() {
     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_SERVICE ||
            readState == STATE_PENDING_ELEVATE ||
            readState == STATE_APPLIED || readState == STATE_APPLIED_SERVICE;
-  }
+  },
 
   /**
    * Verify the downloaded file.  We assume that the download is complete at
    * this point.
-   *
-   * @param patchFile
-   *        nsIFile representing the fully downloaded file
    */
-  _verifyDownload(patchFile) {
-    LOG("CommonDownloader:_verifyDownload called");
+  _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;
 
     // Ensure that the file size matches the expected file size.
-    if (patchFile.fileSize != this._patch.size) {
-      LOG("CommonDownloader:_verifyDownload downloaded size != expected 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;
     }
 
-    LOG("CommonDownloader:_verifyDownload downloaded size == expected size.");
+    LOG("Downloader:_verifyDownload downloaded size == expected size.");
     return true;
-  }
+  },
 
   /**
    * Select the patch to use given the current state of updateDir and the given
    * set of update patches.
    * @param   update
    *          A nsIUpdate object to select a patch from
    * @param   updateDir
    *          A nsIFile representing the update directory
    * @return  A nsIUpdatePatch object to download
    */
-  _selectPatch(update, updateDir) {
+  _selectPatch: function Downloader__selectPatch(update, updateDir) {
     // Given an update to download, we will always try to download the patch
     // for a partial update over the patch for a full update.
 
     /**
      * Return the first UpdatePatch with the given type.
      * @param   type
      *          The type of the patch ("complete" or "partial")
      * @return  A nsIUpdatePatch object matching the type specified
@@ -3300,32 +3323,32 @@ class CommonDownloader {
     var selectedPatch = update.selectedPatch;
 
     var state = readStatusFile(updateDir);
 
     // If this is a patch that we know about, then select it.  If it is a patch
     // that we do not know about, then remove it and use our default logic.
     var useComplete = false;
     if (selectedPatch) {
-      LOG("CommonDownloader:_selectPatch - found existing patch with state: " +
+      LOG("Downloader:_selectPatch - found existing patch with state: " +
           state);
       if (state == STATE_DOWNLOADING) {
-        LOG("CommonDownloader:_selectPatch - resuming download");
+        LOG("Downloader:_selectPatch - resuming download");
         return selectedPatch;
       }
       if (state == STATE_PENDING || state == STATE_PENDING_SERVICE ||
           state == STATE_PENDING_ELEVATE || state == STATE_APPLIED ||
           state == STATE_APPLIED_SERVICE) {
-        LOG("CommonDownloader:_selectPatch - already downloaded");
+        LOG("Downloader:_selectPatch - already downloaded");
         return null;
       }
 
       if (update && selectedPatch.type == "complete") {
         // This is a pretty fatal error.  Just bail.
-        LOG("CommonDownloader:_selectPatch - failed to apply complete patch!");
+        LOG("Downloader:_selectPatch - failed to apply complete patch!");
         writeStatusFile(updateDir, STATE_NONE);
         writeVersionFile(getUpdatesDir(), null);
         return null;
       }
 
       // Something went wrong when we tried to apply the previous patch.
       // Try the complete patch next time.
       useComplete = true;
@@ -3356,375 +3379,229 @@ class CommonDownloader {
 
     // 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;
+  },
+
+  /**
+   * Download and stage the given update.
+   * @param   update
+   *          A nsIUpdate object to download a patch for. Cannot be null.
+   */
+  downloadUpdate: function Downloader_downloadUpdate(update) {
+    LOG("UpdateService:_downloadUpdate");
+    if (!update) {
+      AUSTLMY.pingDownloadCode(undefined, AUSTLMY.DWNLD_ERR_NO_UPDATE);
+      throw Cr.NS_ERROR_NULL_POINTER;
+    }
+
+    var updateDir = getUpdatesDir();
+
+    this._update = update;
+
+    // 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.isCompleteUpdate = this._patch.type == "complete";
+
+    let patchFile = getUpdatesDir().clone();
+    patchFile.append(FILE_UPDATE_MAR);
+    update.QueryInterface(Ci.nsIPropertyBag);
+    let interval = this.background ? update.getProperty("backgroundInterval")
+                                   : DOWNLOAD_FOREGROUND_INTERVAL;
+
+    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);
+
+    writeStatusFile(updateDir, STATE_DOWNLOADING);
+    this._patch.QueryInterface(Ci.nsIWritablePropertyBag);
+    this._patch.state = STATE_DOWNLOADING;
+    var um = Cc["@mozilla.org/updates/update-manager;1"].
+             getService(Ci.nsIUpdateManager);
+    um.saveUpdates();
+    return STATE_DOWNLOADING;
+  },
+
+  /**
+   * An array of download listeners to notify when we receive
+   * nsIRequestObserver or nsIProgressEventSink method calls.
+   */
+  _listeners: [],
 
   /**
    * Adds a listener to the download process
    * @param   listener
    *          A download listener, implementing nsIRequestObserver and
    *          nsIProgressEventSink
    */
-  addDownloadListener(listener) {
-    for (let i = 0; i < this._listeners.length; ++i) {
-      if (this._listeners[i] == listener) {
+  addDownloadListener: function Downloader_addDownloadListener(listener) {
+    for (var i = 0; i < this._listeners.length; ++i) {
+      if (this._listeners[i] == listener)
         return;
-      }
     }
     this._listeners.push(listener);
-  }
+  },
 
   /**
    * Removes a download listener
    * @param   listener
    *          The listener to remove.
    */
-  removeDownloadListener(listener) {
-    for (let i = 0; i < this._listeners.length; ++i) {
+  removeDownloadListener: function Downloader_removeDownloadListener(listener) {
+    for (var i = 0; i < this._listeners.length; ++i) {
       if (this._listeners[i] == listener) {
         this._listeners.splice(i, 1);
         return;
       }
     }
-  }
-}
-
-/**
- * Update downloader which uses an nsHttpChannel
- */
-class ChannelDownloader extends CommonDownloader {
-  constructor(background, updateService) {
-    LOG("Creating ChannelDownloader");
-
-    super(background, updateService);
-
-    /**
-     * Background file saver, for writing the downloaded file on another thread
-     */
-    this._bkgFileSaver = null;
-
-    /**
-     * The channel object handling the download
-     */
-    this._channel = null;
-
-    /**
-     * Timestamp of the last time we notified listeners for OnProgress.
-     * Used to throttle how frequently those notifications can be sent,
-     * to avoid updating user interfaces faster than they can be read.
-     */
-    this._lastProgressTimeMs = 0;
-
-    /**
-     * If a previous download is being resumed, this is set to the number of
-     * bytes that had already been downloaded.
-     */
-    this._resumedFrom = 0;
-
-    this.QueryInterface = XPCOMUtils.generateQI([Ci.nsIStreamListener,
-                                                 Ci.nsIChannelEventSink,
-                                                 Ci.nsIProgressEventSink,
-                                                 Ci.nsIRequestObserver,
-                                                 Ci.nsIInterfaceRequestor]);
-  }
-
-  /**
-   * Verify the downloaded file.  We assume that the download is complete at
-   * this point.
-   */
-  _verifyDownload() {
-    if (!this._channel) {
-      AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
-                               AUSTLMY.DWNLD_ERR_VERIFY_NO_REQUEST);
-      return false;
-    }
-    let patchFile = getUpdatesDir().clone();
-    patchFile.append(FILE_UPDATE_MAR);
-    return super._verifyDownload(patchFile);
-  }
-
-  /**
-   * Cancels the active download.
-   */
-  cancel(cancelError) {
-    LOG("ChannelDownloader: cancel");
-    if (cancelError === undefined) {
-      cancelError = Cr.NS_BINDING_ABORTED;
-    }
-    if (this._bkgFileSaver) {
-      this._bkgFileSaver.finish(cancelError);
-      this._bkgFileSaver.observer = null;
-      this._bkgFileSaver = null;
-    }
-    if (this._channel) {
-      this._channel.cancel(cancelError);
-      this._channel = null;
-    }
-  }
-
-  /**
-   * Whether or not we are currently downloading something.
-   */
-  get isBusy() {
-    return this._channel != null;
-  }
-
-  /**
-   * Download and stage the given update.
-   * @param   update
-   *          A nsIUpdate object to download a patch for. Cannot be null.
-   */
-  downloadUpdate(update) {
-    LOG("ChannelDownloader:downloadUpdate");
-    if (!update) {
-      AUSTLMY.pingDownloadCode(undefined, AUSTLMY.DWNLD_ERR_NO_UPDATE);
-      throw Cr.NS_ERROR_NULL_POINTER;
-    }
-
-    let updateDir = getUpdatesDir();
-
-    this._update = update;
-
-    // This function may return null, which indicates that there are no patches
-    // to download.
-    this._patch = this._selectPatch(update, updateDir);
-    if (!this._patch) {
-      LOG("ChannelDownloader:downloadUpdate - no patch to download");
-      AUSTLMY.pingDownloadCode(undefined, AUSTLMY.DWNLD_ERR_NO_UPDATE_PATCH);
-      return readStatusFile(updateDir);
-    }
-    this.isCompleteUpdate = this._patch.type == "complete";
-    this._patch.QueryInterface(Ci.nsIWritablePropertyBag);
-
-    let patchFile = getUpdatesDir().clone();
-    patchFile.append(FILE_UPDATE_MAR);
-
-    LOG("ChannelDownloader:downloadUpdate - url: " + this._patch.URL +
-        ", path: " + patchFile.path);
-    let uri = Services.io.newURI(this._patch.URL);
-
-    let BackgroundFileSaver = Components.Constructor(
-      "@mozilla.org/network/background-file-saver;1?mode=streamlistener",
-      "nsIBackgroundFileSaver");
-    this._bkgFileSaver = new BackgroundFileSaver();
-    this._bkgFileSaver.QueryInterface(Ci.nsIStreamListener);
-
-    this._channel = NetUtil.newChannel({uri, loadUsingSystemPrincipal: true});
-    this._channel.notificationCallbacks = this;
-    this._channel.asyncOpen2(this.QueryInterface(Ci.nsIStreamListener));
-
-    if (this._channel instanceof Ci.nsIResumableChannel &&
-        patchFile.exists()) {
-      let resumeFrom;
-      let entityID = this._patch.getProperty("entityID");
-      if (!entityID) {
-        LOG("ChannelDownloader:downloadUpdate - failed to resume download, " +
-            "couldn't get entityID for the selected patch");
-      } else {
-        try {
-          resumeFrom = patchFile.fileSize;
-        } catch (e) {
-          LOG("ChannelDownloader:downloadUpdate - failed to resume download, " +
-              "couldn't open partially downloaded file, exception: " + e);
-        }
-      }
-
-      if (entityID && resumeFrom !== undefined) {
-        this._channel.resumeAt(resumeFrom, entityID);
-        this._bkgFileSaver.enableAppend();
-        this._resumedFrom = resumeFrom;
-        LOG("ChannelDownloader:downloadUpdate - resuming previous download " +
-            "starting after " + resumeFrom + " bytes");
-      } else {
-        AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
-                                 AUSTLMY.DWNLD_RESUME_FAILURE);
-      }
-    }
-
-    this._bkgFileSaver.setTarget(patchFile, true);
-
-    writeStatusFile(updateDir, STATE_DOWNLOADING);
-    this._patch.state = STATE_DOWNLOADING;
-    let um = Cc["@mozilla.org/updates/update-manager;1"].
-             getService(Ci.nsIUpdateManager);
-    um.saveUpdates();
-    return STATE_DOWNLOADING;
-  }
-
-  /**
-   * See nsIChannelEventSink.idl
-   */
-  asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
-    LOG("ChannelDownloader: redirected from " + oldChannel.URI +
-        " to " + newChannel.URI);
-    this._patch.finalURL = newChannel.URI;
-    callback.onRedirectVerifyCallback(Cr.NS_OK);
-    this._channel = newChannel;
-  }
-
-  /**
-   * See nsIProgressEventSink.idl
-   */
-  onStatus(request, context, status, statusText) {
-    LOG("ChannelDownloader:onStatus - status: " + status +
-        ", statusText: " + statusText);
-
-    for (let listener of this._listeners) {
-      if (listener instanceof Ci.nsIProgressEventSink) {
-        listener.onStatus(request, context, status, statusText);
-      }
-    }
-  }
+  },
 
   /**
    * When the async request begins
    * @param   request
    *          The nsIRequest object for the transfer
    * @param   context
    *          Additional data
    */
-  onStartRequest(request, context) {
-    if (!this._channel || !this._bkgFileSaver) {
-      // Sometimes the channel calls onStartRequest after being canceled.
-      return;
-    }
-
-    LOG("ChannelDownloader:onStartRequest");
-
-    this._bkgFileSaver.onStartRequest(request, context);
-
-    if (request instanceof Ci.nsIResumableChannel) {
-      // Reading the entityID can throw if the server doesn't allow resuming.
-      try {
-        this._patch.setProperty("entityID", request.entityID);
-      } catch (ex) {
-        if (!(ex instanceof Components.Exception) ||
-            ex.result != Cr.NS_ERROR_NOT_RESUMABLE) {
-          throw ex;
-        }
-      }
-    }
-
+  onStartRequest: function Downloader_onStartRequest(request, context) {
+    if (request instanceof Ci.nsIIncrementalDownload)
+      LOG("Downloader:onStartRequest - original URI spec: " + request.URI.spec +
+          ", final URI spec: " + request.finalURI.spec);
+    // Always set finalURL in onStartRequest since it can change.
+    this._patch.finalURL = request.finalURI.spec;
     var um = Cc["@mozilla.org/updates/update-manager;1"].
              getService(Ci.nsIUpdateManager);
     um.saveUpdates();
 
     var listeners = this._listeners.concat();
     var listenerCount = listeners.length;
-    for (var i = 0; i < listenerCount; ++i) {
+    for (var i = 0; i < listenerCount; ++i)
       listeners[i].onStartRequest(request, context);
-    }
-  }
+  },
 
   /**
-   * See nsIProgressEventSink.idl
+   * When new data has been downloaded
+   * @param   request
+   *          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(request, context, progress, maxProgress) {
-    LOG("ChannelDownloader:onProgress - progress: " + progress +
-        "/" + maxProgress);
+  onProgress: function Downloader_onProgress(request, context, progress,
+                                             maxProgress) {
+    LOG("Downloader:onProgress - progress: " + progress + "/" + maxProgress);
 
     if (progress > this._patch.size) {
-      LOG("ChannelDownloader:onProgress - progress: " + progress +
+      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._resumedFrom) != this._patch.size) {
-      LOG("ChannelDownloader:onProgress - maxProgress: " +
-          (maxProgress + this._resumedFrom) +
+    if (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;
     }
 
-    let currentTime = Date.now();
-    if ((currentTime - this._lastProgressTimeMs) > DOWNLOAD_PROGRESS_INTERVAL) {
-      this._lastProgressTimeMs = currentTime;
-      let listeners = this._listeners.concat();
-      let listenerCount = listeners.length;
-      for (let i = 0; i < listenerCount; ++i) {
-        let listener = listeners[i];
-        if (listener instanceof Ci.nsIProgressEventSink) {
-          listener.onProgress(request, context, progress + this._resumedFrom,
-                              this._patch.size);
-        }
-      }
+    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;
-  }
-
-  /**
-   * See nsIStreamListener.idl
-   */
-  onDataAvailable(request, context, stream, offset, count) {
-    // this._bkgFileSaver may not exist anymore if we've been canceled.
-    if (this._bkgFileSaver) {
-      this._bkgFileSaver.onDataAvailable(request, context, stream, offset, count);
-    }
-  }
+  },
 
   /**
-   * See nsIRequestObserver.idl
+   * When we have new status text
+   * @param   request
+   *          The nsIRequest object for the transfer
+   * @param   context
+   *          Additional data
+   * @param   status
+   *          A status code
+   * @param   statusText
+   *          Human readable version of |status|
    */
-  onStopRequest(request, context, status) {
-    // this._bkgFileSaver may not exist anymore if we've been canceled.
-    if (this._bkgFileSaver) {
-      this._bkgFileSaver.onStopRequest(request, context, status);
+  onStatus: function Downloader_onStatus(request, context, status, statusText) {
+    LOG("Downloader:onStatus - status: " + status + ", statusText: " +
+        statusText);
+
+    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);
     }
-    if (Components.isSuccessCode(status)) {
-      this._bkgFileSaver.observer = {
-        onTargetChange() { },
-        onSaveComplete: (aSaver, aStatus) => {
-          this._bkgFileSaver.observer = null;
-          this._finishDownload(request, context, aStatus);
-        }
-      };
-      this._bkgFileSaver.finish(status);
-    } else {
-      this._finishDownload(request, context, status);
-    }
-  }
+  },
 
   /**
-   * Handler for when the download attempt is completely finished;
-   * either the file must be ready to use or the download must have
-   * conclusively failed.
-   *
-   * The interface to this function is the same as onStopRequest,
-   * so see nsIRequestObserver.idl for the details.
+   * When data transfer ceases
+   * @param   request
+   *          The nsIRequest object for the transfer
+   * @param   context
+   *          Additional data
+   * @param   status
+   *          Status code containing the reason for the cessation.
    */
-  _finishDownload(request, context, status) {
+  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 shouldRegisterOnlineObserver = false;
     var shouldRetrySoon = false;
     var deleteActiveUpdate = false;
     var retryTimeout = getPref("getIntPref", PREF_APP_UPDATE_SOCKET_RETRYTIMEOUT,
                                DEFAULT_SOCKET_RETRYTIMEOUT);
     // Prevent the preference from setting a value greater than 10000.
     retryTimeout = Math.min(retryTimeout, 10000);
     var maxFail = getPref("getIntPref", PREF_APP_UPDATE_SOCKET_MAXERRORS,
                           DEFAULT_SOCKET_MAX_ERRORS);
     // Prevent the preference from setting a value greater than 20.
     maxFail = Math.min(maxFail, 20);
-    LOG("ChannelDownloader:finishDownload - status: " + status + ", " +
+    LOG("Downloader:onStopRequest - status: " + status + ", " +
         "current fail: " + this.updateService._consecutiveSocketErrors + ", " +
         "max fail: " + maxFail + ", " +
         "retryTimeout: " + retryTimeout);
     if (Components.isSuccessCode(status)) {
       if (this._verifyDownload()) {
         if (shouldUseService()) {
           state = STATE_PENDING_SERVICE;
         } else if (getElevationRequired()) {
@@ -3739,17 +3616,17 @@ class ChannelDownloader extends CommonDo
 
         // 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");
         Services.prefs.setIntPref(PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS, 0);
       } else {
-        LOG("ChannelDownloader:finishDownload - download verification failed");
+        LOG("Downloader:onStopRequest - download verification failed");
         state = STATE_DOWNLOAD_FAILED;
         status = Cr.NS_ERROR_CORRUPTED_CONTENT;
 
         // Yes, this code is a string.
         const vfCode = "verification_failed";
         var message = getStatusTextFromCode(vfCode, vfCode);
         this._update.statusText = message;
 
@@ -3759,46 +3636,46 @@ class ChannelDownloader extends CommonDo
         // Destroy the updates directory, since we're done with it.
         cleanUpUpdatesDir();
       }
     } else if (status == Cr.NS_ERROR_OFFLINE) {
       // Register an online observer to try again.
       // 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("ChannelDownloader:finishDownload - offline, register online observer: true");
+      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 ||
                 status == Cr.NS_ERROR_DOCUMENT_NOT_CACHED) &&
                this.updateService._consecutiveSocketErrors < maxFail) {
-      LOG("ChannelDownloader:finishDownload - socket error, shouldRetrySoon: true");
+      LOG("Downloader:onStopRequest - socket error, shouldRetrySoon: true");
       let dwnldCode = AUSTLMY.DWNLD_RETRY_CONNECTION_REFUSED;
       if (status == Cr.NS_ERROR_NET_TIMEOUT) {
         dwnldCode = AUSTLMY.DWNLD_RETRY_NET_TIMEOUT;
       } else if (status == Cr.NS_ERROR_NET_RESET) {
         dwnldCode = AUSTLMY.DWNLD_RETRY_NET_RESET;
       } else if (status == Cr.NS_ERROR_DOCUMENT_NOT_CACHED) {
         dwnldCode = AUSTLMY.DWNLD_ERR_DOCUMENT_NOT_CACHED;
       }
       AUSTLMY.pingDownloadCode(this.isCompleteUpdate, dwnldCode);
       shouldRetrySoon = true;
       deleteActiveUpdate = false;
     } else if (status != Cr.NS_BINDING_ABORTED &&
                status != Cr.NS_ERROR_ABORT) {
-      LOG("ChannelDownloader:finishDownload - non-verification failure");
+      LOG("Downloader:onStopRequest - non-verification failure");
       let dwnldCode = AUSTLMY.DWNLD_ERR_BINDING_ABORTED;
       if (status == Cr.NS_ERROR_ABORT) {
         dwnldCode = AUSTLMY.DWNLD_ERR_ABORT;
       }
       AUSTLMY.pingDownloadCode(this.isCompleteUpdate, dwnldCode);
 
       // Some sort of other failure, log this in the |statusText| property
       state = STATE_DOWNLOAD_FAILED;
@@ -3809,17 +3686,17 @@ class ChannelDownloader extends CommonDo
       this._update.statusText = getStatusTextFromCode(status,
                                                       Cr.NS_BINDING_FAILED);
 
       // Destroy the updates directory, since we're done with it.
       cleanUpUpdatesDir();
 
       deleteActiveUpdate = true;
     }
-    LOG("ChannelDownloader:finishDownload - setting state to: " + state);
+    LOG("Downloader:onStopRequest - setting state to: " + state);
     this._patch.state = state;
     var um = Cc["@mozilla.org/updates/update-manager;1"].
              getService(Ci.nsIUpdateManager);
     if (deleteActiveUpdate) {
       this._update.installDate = (new Date()).getTime();
       // Setting |activeUpdate| to null will move the active update to the
       // update history.
       um.activeUpdate = null;
@@ -3833,24 +3710,23 @@ class ChannelDownloader extends CommonDo
     if (!shouldRetrySoon && !shouldRegisterOnlineObserver) {
       var listeners = this._listeners.concat();
       var listenerCount = listeners.length;
       for (var i = 0; i < listenerCount; ++i) {
         listeners[i].onStopRequest(request, context, status);
       }
     }
 
-    this._channel = null;
-    this._bkgFileSaver = null;
+    this._request = null;
 
     if (state == STATE_DOWNLOAD_FAILED) {
       var allFailed = true;
       // Check if there is a complete update patch that can be downloaded.
       if (!this._update.isCompleteUpdate && this._update.patchCount == 2) {
-        LOG("ChannelDownloader:finishDownload - verification of patch failed, " +
+        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 {
           allFailed = false;
@@ -3860,34 +3736,33 @@ class ChannelDownloader extends CommonDo
       if (allFailed) {
         if (getPref("getBoolPref", PREF_APP_UPDATE_DOORHANGER, false)) {
           let downloadAttempts = getPref("getIntPref", PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS, 0);
           downloadAttempts++;
           Services.prefs.setIntPref(PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS, downloadAttempts);
           let maxAttempts = Math.min(getPref("getIntPref", PREF_APP_UPDATE_DOWNLOAD_MAXATTEMPTS, 2), 10);
 
           if (downloadAttempts > maxAttempts) {
-            LOG("ChannelDownloader:finishDownload - notifying observers of error. " +
+            LOG("Downloader:onStopRequest - notifying observers of error. " +
                 "topic: update-error, status: download-attempts-exceeded, " +
                 "downloadAttempts: " + downloadAttempts + " " +
                 "maxAttempts: " + maxAttempts);
             Services.obs.notifyObservers(this._update, "update-error", "download-attempts-exceeded");
           } else {
             this._update.selectedPatch.selected = false;
-            LOG("ChannelDownloader:finishDownload - notifying observers of error. " +
+            LOG("Downloader:onStopRequest - notifying observers of error. " +
                 "topic: update-error, status: download-attempt-failed");
             Services.obs.notifyObservers(this._update, "update-error", "download-attempt-failed");
           }
         }
 
         // If the update UI is not open (e.g. the user closed the window while
         // downloading) and if at any point this was a foreground download
         // notify the user about the error. If the update was a background
         // update there is no notification since the user won't be expecting it.
-        this._update.QueryInterface(Ci.nsIPropertyBag);
         if (!Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME) &&
             this._update.getProperty("foregroundDownload") == "true") {
           let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
                          createInstance(Ci.nsIUpdatePrompt);
           prompter.showUpdateError(this._update);
         }
 
         // Prevent leaking the update object (bug 454964).
@@ -3895,28 +3770,28 @@ class ChannelDownloader extends CommonDo
       }
       // A complete download has been initiated or the failure was handled.
       return;
     }
 
     if (state == STATE_PENDING || state == STATE_PENDING_SERVICE ||
         state == STATE_PENDING_ELEVATE) {
       if (getCanStageUpdates()) {
-        LOG("ChannelDownloader:finishDownload - attempting to stage update: " +
+        LOG("Downloader:onStopRequest - attempting to stage update: " +
             this._update.name);
 
         // Initiate the update in the background
         try {
           Cc["@mozilla.org/updates/update-processor;1"].
             createInstance(Ci.nsIUpdateProcessor).
             processUpdate(this._update);
         } catch (e) {
           // Fail gracefully in case the application does not support the update
           // processor service.
-          LOG("ChannelDownloader:finishDownload - failed to stage update. Exception: " +
+          LOG("Downloader:onStopRequest - failed to stage update. Exception: " +
               e);
           if (this.background) {
             shouldShowPrompt = true;
           }
         }
       }
     }
 
@@ -3927,57 +3802,52 @@ class ChannelDownloader extends CommonDo
       // installation (i.e. that they should restart the application). We do
       // not notify on failed update attempts.
       let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
                      createInstance(Ci.nsIUpdatePrompt);
       prompter.showUpdateDownloaded(this._update, true);
     }
 
     if (shouldRegisterOnlineObserver) {
-      LOG("ChannelDownloader:finishDownload - Registering online observer");
+      LOG("Downloader:onStopRequest - Registering online observer");
       this.updateService._registerOnlineObserver();
     } else if (shouldRetrySoon) {
-      LOG("ChannelDownloader:finishDownload - Retrying soon");
+      LOG("Downloader:onStopRequest - Retrying soon");
       this.updateService._consecutiveSocketErrors++;
       if (this.updateService._retryTimer) {
         this.updateService._retryTimer.cancel();
       }
       this.updateService._retryTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
       this.updateService._retryTimer.initWithCallback(function() {
         this._attemptResume();
       }.bind(this.updateService), retryTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
     } else {
       // Prevent leaking the update object (bug 454964)
       this._update = null;
     }
-  }
+  },
 
   /**
    * See nsIInterfaceRequestor.idl
    */
-  getInterface(iid) {
+  getInterface: function Downloader_getInterface(iid) {
     // The network request may require proxy authentication, so provide the
     // default nsIAuthPrompt if requested.
     if (iid.equals(Ci.nsIAuthPrompt)) {
       var prompt = Cc["@mozilla.org/network/default-auth-prompt;1"].
                    createInstance();
       return prompt.QueryInterface(iid);
-    } else if (iid.equals(Ci.nsIProgressEventSink)) {
-      return this.QueryInterface(iid);
     }
     throw Cr.NS_NOINTERFACE;
-  }
-}
-
-/**
- * Construct the correct type of Downloader object.
- */
-function getDownloader(background, updateService) {
-  return new ChannelDownloader(background, updateService);
-}
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
+                                         Ci.nsIProgressEventSink,
+                                         Ci.nsIInterfaceRequestor])
+};
 
 /**
  * UpdatePrompt
  * An object which can prompt the user with information about updates, request
  * action, etc. Embedding clients can override this component with one that
  * invokes a native front end.
  * @constructor
  */
--- a/toolkit/mozapps/update/tests/chrome/utils.js
+++ b/toolkit/mozapps/update/tests/chrome/utils.js
@@ -807,16 +807,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.setIntPref(PREF_APP_UPDATE_DOWNLOADBACKGROUNDINTERVAL, 0);
 }
 
 /**
  * Restores files that were backed up for the tests and general file cleanup.
  */
 function resetFiles() {
   // Restore the backed up updater-settings.ini if it exists.
   let baseAppDir = getGREDir();
@@ -907,16 +908,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_DOWNLOADBACKGROUNDINTERVAL)) {
+    Services.prefs.clearUserPref(PREF_APP_UPDATE_DOWNLOADBACKGROUNDINTERVAL);
+  }
 }
 
 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
@@ -11,16 +11,17 @@ Cu.import("resource://gre/modules/XPCOMU
 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_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_DOWNLOADPROMPTMAXATTEMPTS  = "app.update.download.maxAttempts";
+const PREF_APP_UPDATE_DOWNLOADBACKGROUNDINTERVAL = "app.update.download.backgroundInterval";
 const PREF_APP_UPDATE_ENABLED                    = "app.update.enabled";
 const PREF_APP_UPDATE_IDLETIME                   = "app.update.idletime";
 const PREF_APP_UPDATE_LOG                        = "app.update.log";
 const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED        = "app.update.notifiedUnsupported";
 const PREF_APP_UPDATE_PROMPTWAITTIME             = "app.update.promptWaitTime";
 const PREF_APP_UPDATE_RETRYTIMEOUT               = "app.update.socket.retryTimeout";
 const PREF_APP_UPDATE_SERVICE_ENABLED            = "app.update.service.enabled";
 const PREF_APP_UPDATE_SILENT                     = "app.update.silent";
--- a/toolkit/mozapps/update/tests/data/sharedUpdateXML.js
+++ b/toolkit/mozapps/update/tests/data/sharedUpdateXML.js
@@ -113,16 +113,17 @@ function getRemoteUpdatesXMLString(aUpda
  *         See updateProps names below for possible object names.
  * @param  aPatches
  *         String representing the application update patches.
  * @return The string representing an update element for an update xml file.
  */
 function getRemoteUpdateString(aUpdateProps, aPatches) {
   const updateProps = {
     appVersion: DEFAULT_UPDATE_VERSION,
+    backgroundInterval: null,
     buildID: "20080811053724",
     custom1: null,
     custom2: null,
     detailsURL: URL_HTTP_UPDATE_SJS + "?uiURL=DETAILS",
     displayVersion: null,
     name: "App Update Test",
     promptWaitTime: null,
     type: "major"
@@ -209,16 +210,17 @@ function getLocalUpdateString(aUpdatePro
       if (Services && Services.appinfo && Services.appinfo.version) {
         return Services.appinfo.version;
       }
       return DEFAULT_UPDATE_VERSION;
     },
     set appVersion(val) {
       this._appVersion = val;
     },
+    backgroundInterval: null,
     buildID: "20080811053724",
     channel: gDefaultPrefBranch.getCharPref(PREF_APP_UPDATE_CHANNEL),
     custom1: null,
     custom2: null,
     detailsURL: URL_HTTP_UPDATE_SJS + "?uiURL=DETAILS",
     displayVersion: null,
     foregroundDownload: "true",
     installDate: "1238441400314",
@@ -278,23 +280,19 @@ function getLocalPatchString(aPatchProps
     state: STATE_SUCCEEDED
   };
 
   for (let name in aPatchProps) {
     patchProps[name] = aPatchProps[name];
   }
 
   let selected = "selected=\"" + patchProps.selected + "\" ";
-  let entityID = aPatchProps.entityID ?
-                   "entityID=\"" + aPatchProps.entityID + "\" " :
-                   "";
   let state = "state=\"" + patchProps.state + "\"/>";
   return getPatchString(patchProps) + " " +
          selected +
-         entityID +
          state;
 }
 
 /**
  * Constructs a string representing an update element for a remote update xml
  * file.
  *
  * @param  aUpdateProps (optional)
@@ -307,26 +305,29 @@ function getUpdateString(aUpdateProps) {
   let name = "name=\"" + aUpdateProps.name + "\" ";
   let displayVersion = aUpdateProps.displayVersion ?
     "displayVersion=\"" + aUpdateProps.displayVersion + "\" " : "";
   let appVersion = "appVersion=\"" + aUpdateProps.appVersion + "\" ";
   // Not specifying a detailsURL will cause a leak due to bug 470244
   let detailsURL = "detailsURL=\"" + aUpdateProps.detailsURL + "\" ";
   let promptWaitTime = aUpdateProps.promptWaitTime ?
     "promptWaitTime=\"" + aUpdateProps.promptWaitTime + "\" " : "";
+  let backgroundInterval = aUpdateProps.backgroundInterval ?
+    "backgroundInterval=\"" + aUpdateProps.backgroundInterval + "\" " : "";
   let custom1 = aUpdateProps.custom1 ? aUpdateProps.custom1 + " " : "";
   let custom2 = aUpdateProps.custom2 ? aUpdateProps.custom2 + " " : "";
   let buildID = "buildID=\"" + aUpdateProps.buildID + "\"";
 
   return "<update " + type +
                       name +
                       displayVersion +
                       appVersion +
                       detailsURL +
                       promptWaitTime +
+                      backgroundInterval +
                       custom1 +
                       custom2 +
                       buildID;
 }
 
 /**
  * Constructs a string representing a patch element for an update xml file.
  *
--- a/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
+++ b/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
@@ -143,17 +143,16 @@ var gCheckFunc;
 var gResponseBody;
 var gResponseStatusCode = 200;
 var gRequestURL;
 var gUpdateCount;
 var gUpdates;
 var gStatusCode;
 var gStatusText;
 var gStatusResult;
-var gSlowDownloadContinue = false;
 
 var gProcess;
 var gAppTimer;
 var gHandle;
 
 var gGREDirOrig;
 var gGREBinDirOrig;
 var gAppDirOrig;
@@ -3675,40 +3674,30 @@ const downloadListener = {
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
                                          Ci.nsIProgressEventSink])
 };
 
 /**
  * Helper for starting the http server used by the tests
- *
- * @param options.slowDownload
- *        When serving FILE_SIMPLE_MAR, hang until gSlowDownloadContinue is true
- * @param options.errorDownload
- *        When serving FILE_SIMPLE_MAR, return a 500 error instead of the file
  */
-function start_httpserver(options) {
+function start_httpserver() {
   let dir = getTestDirFile();
   debugDump("http server directory path: " + dir.path);
 
   if (!dir.isDirectory()) {
     do_throw("A file instead of a directory was specified for HttpServer " +
              "registerDirectory! Path: " + dir.path);
   }
 
   let { HttpServer } = Cu.import("resource://testing-common/httpd.js", {});
   gTestserver = new HttpServer();
   gTestserver.registerDirectory("/", dir);
   gTestserver.registerPathHandler("/" + gHTTPHandlerPath, pathHandler);
-  if (options && options.slowDownload) {
-    gTestserver.registerPathHandler("/" + FILE_SIMPLE_MAR, marPathHandler);
-  } else if (options && options.errorDownload) {
-    gTestserver.registerPathHandler("/" + FILE_SIMPLE_MAR, marErrorPathHandler);
-  }
   gTestserver.start(-1);
   let testserverPort = gTestserver.identity.primaryPort;
   gURLData = URL_HOST + ":" + testserverPort + "/";
   debugDump("http server port = " + testserverPort);
 }
 
 /**
  * Custom path handler for the http server
@@ -3720,44 +3709,16 @@ function start_httpserver(options) {
  */
 function pathHandler(aMetadata, aResponse) {
   aResponse.setHeader("Content-Type", "text/xml", false);
   aResponse.setStatusLine(aMetadata.httpVersion, gResponseStatusCode, "OK");
   aResponse.bodyOutputStream.write(gResponseBody, gResponseBody.length);
 }
 
 /**
- * Custom path handlers for MAR files served by the HTTP server.
- * Only used if start_httpserver was called with the appropriate options.
- *
- * @param   aRequest
- *          The http metadata for the request.
- * @param   aResponse
- *          The http response for the request.
- */
-function marPathHandler(aRequest, aResponse) {
-  aResponse.processAsync();
-  aResponse.setHeader("Content-Type", "binary/octet-stream");
-  aResponse.setHeader("Content-Length", SIZE_SIMPLE_MAR);
-
-  (function marPathHandlerTimeout() {
-    if (gSlowDownloadContinue) {
-      let content = readFileBytes(getTestDirFile(FILE_SIMPLE_MAR));
-      aResponse.bodyOutputStream.write(content, SIZE_SIMPLE_MAR);
-      aResponse.finish();
-    } else {
-      do_timeout(100, marPathHandlerTimeout);
-    }
-  })();
-}
-function marErrorPathHandler(aRequest, aResponse) {
-  aResponse.setStatusLine(aMetadata.httpVersion, 500, "Internal server error");
-}
-
-/**
  * Helper for stopping the http server used by the tests
  *
  * @param   aCallback
  *          The callback to call after stopping the http server.
  */
 function stop_httpserver(aCallback) {
   Assert.ok(!!aCallback, "the aCallback parameter should be defined");
   gTestserver.stop(aCallback);
@@ -4095,16 +4056,142 @@ function runUpdateUsingApp(aExpectedStat
 
   setEnvironment();
   debugDump("launching application");
   gProcess.runAsync(args, args.length, processObserver);
 
   debugDump("finish - launching application to apply update");
 }
 
+/* This Mock incremental downloader is used to verify that connection interrupts
+ * work correctly in updater code. The implementation of the mock incremental
+ * downloader is very simple, it simply copies the file to the destination
+ * location.
+ */
+function initMockIncrementalDownload() {
+  const INC_CONTRACT_ID = "@mozilla.org/network/incremental-download;1";
+  let incrementalDownloadCID =
+    MockRegistrar.register(INC_CONTRACT_ID, IncrementalDownload);
+  do_register_cleanup(() => {
+    MockRegistrar.unregister(incrementalDownloadCID);
+  });
+}
+
+function IncrementalDownload() {
+  this.wrappedJSObject = this;
+}
+
+IncrementalDownload.prototype = {
+  /* nsIIncrementalDownload */
+  init(uri, file, chunkSize, intervalInSeconds) {
+    this._destination = file;
+    this._URI = uri;
+    this._finalURI = uri;
+  },
+
+  start(observer, ctxt) {
+    let tm = Cc["@mozilla.org/thread-manager;1"].
+             getService(Ci.nsIThreadManager);
+    // Do the actual operation async to give a chance for observers to add
+    // themselves.
+    tm.dispatchToMainThread(() => {
+      this._observer = observer.QueryInterface(Ci.nsIRequestObserver);
+      this._ctxt = ctxt;
+      this._observer.onStartRequest(this, this._ctxt);
+      let mar = getTestDirFile(FILE_SIMPLE_MAR);
+      mar.copyTo(this._destination.parent, this._destination.leafName);
+      let status = Cr.NS_OK;
+      switch (gIncrementalDownloadErrorType++) {
+        case 0:
+          status = Cr.NS_ERROR_NET_RESET;
+          break;
+        case 1:
+          status = Cr.NS_ERROR_CONNECTION_REFUSED;
+          break;
+        case 2:
+          status = Cr.NS_ERROR_NET_RESET;
+          break;
+        case 3:
+          status = Cr.NS_OK;
+          break;
+        case 4:
+          status = Cr.NS_ERROR_OFFLINE;
+          // After we report offline, we want to eventually show offline
+          // status being changed to online.
+          let tm2 = Cc["@mozilla.org/thread-manager;1"].
+                    getService(Ci.nsIThreadManager);
+          tm2.dispatchToMainThread(function() {
+            Services.obs.notifyObservers(gAUS,
+                                         "network:offline-status-changed",
+                                         "online");
+          });
+          break;
+      }
+      this._observer.onStopRequest(this, this._ctxt, status);
+    });
+  },
+
+  get URI() {
+    return this._URI;
+  },
+
+  get currentSize() {
+    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+  },
+
+  get destination() {
+    return this._destination;
+  },
+
+  get finalURI() {
+    return this._finalURI;
+  },
+
+  get totalSize() {
+    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+  },
+
+  /* nsIRequest */
+  cancel(aStatus) {
+    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+  },
+  suspend() {
+    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+  },
+  isPending() {
+    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+  },
+  _loadFlags: 0,
+  get loadFlags() {
+    return this._loadFlags;
+  },
+  set loadFlags(val) {
+    this._loadFlags = val;
+  },
+
+  _loadGroup: null,
+  get loadGroup() {
+    return this._loadGroup;
+  },
+  set loadGroup(val) {
+    this._loadGroup = val;
+  },
+
+  _name: "",
+  get name() {
+    return this._name;
+  },
+
+  _status: 0,
+  get status() {
+    return this._status;
+  },
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIIncrementalDownload])
+};
+
 /**
  * Sets the environment that will be used by the application process when it is
  * launched.
  */
 function setEnvironment() {
   if (IS_WIN) {
     // The tests use nsIProcess to launch the updater and it is simpler to just
     // set an environment variable and have the test updater set the current
--- a/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedNoRecovery.js
+++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedNoRecovery.js
@@ -4,18 +4,20 @@
  */
 
 function run_test() {
   setupTestCommon();
 
   debugDump("testing mar download with interrupted recovery count exceeded");
 
   Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
-  start_httpserver({errorDownload: true});
+  start_httpserver();
   setUpdateURL(gURLData + gHTTPHandlerPath);
+  initMockIncrementalDownload();
+  gIncrementalDownloadErrorType = 0;
   Services.prefs.setIntPref(PREF_APP_UPDATE_SOCKET_MAXERRORS, 2);
   Services.prefs.setIntPref(PREF_APP_UPDATE_RETRYTIMEOUT, 0);
   let patches = getRemotePatchString({});
   let updates = getRemoteUpdateString({}, patches);
   gResponseBody = getRemoteUpdatesXMLString(updates);
   gCheckFunc = updateCheckCompleted;
   gUpdateChecker.checkForUpdates(updateCheckListener, true);
 }
@@ -34,17 +36,17 @@ function updateCheckCompleted() {
   }
   gAUS.addDownloadListener(downloadListener);
 }
 
 /**
  * Called after the download listener onStopRequest is called.
  */
 function downloadListenerStop() {
-  Assert.equal(gStatusResult, Cr.NS_ERROR_UNEXPECTED,
+  Assert.equal(gStatusResult, Cr.NS_ERROR_NET_RESET,
                "the download status result" + MSG_SHOULD_EQUAL);
   gAUS.removeDownloadListener(downloadListener);
   do_execute_soon(waitForUpdateXMLFiles);
 }
 
 /**
  * Called after the call to waitForUpdateXMLFiles finishes.
  */
--- a/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedOffline.js
+++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedOffline.js
@@ -6,32 +6,33 @@
 function run_test() {
   setupTestCommon();
 
   debugDump("testing mar download when offline");
 
   Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
   start_httpserver();
   setUpdateURL(gURLData + gHTTPHandlerPath);
+  initMockIncrementalDownload();
+  gIncrementalDownloadErrorType = 4;
   let patches = getRemotePatchString({});
   let updates = getRemoteUpdateString({}, patches);
   gResponseBody = getRemoteUpdatesXMLString(updates);
   gCheckFunc = updateCheckCompleted;
   gUpdateChecker.checkForUpdates(updateCheckListener, true);
 }
 
 /**
  * Since gCheckFunc value is the updateCheckCompleted function at this stage
  * this is called after the update check completes in updateCheckListener.
  */
 function updateCheckCompleted() {
   Assert.equal(gUpdateCount, 1,
                "the update count" + MSG_SHOULD_EQUAL);
   let bestUpdate = gAUS.selectUpdate(gUpdates, gUpdateCount);
-  Services.io.offline = true;
   let state = gAUS.downloadUpdate(bestUpdate, false);
   if (state == STATE_NONE || state == STATE_FAILED) {
     do_throw("nsIApplicationUpdateService:downloadUpdate returned " + state);
   }
   gAUS.addDownloadListener(downloadListener);
 }
 
 /**
rename from toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecoveryChannel.js
rename to toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js
--- a/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecoveryChannel.js
+++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js
@@ -1,102 +1,59 @@
 /* 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 MAR File Download Tests for the ChannelDownloader */
-
 function run_test() {
   setupTestCommon();
 
-  debugDump("testing recovery of mar download after pause and resume");
+  debugDump("testing mar mar download interrupted recovery");
 
   Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
-  start_httpserver({slowDownload: true});
+  start_httpserver();
   setUpdateURL(gURLData + gHTTPHandlerPath);
-  standardInit();
 
+  initMockIncrementalDownload();
+  gIncrementalDownloadErrorType = 0;
   let patches = getRemotePatchString({});
   let updates = getRemoteUpdateString({}, patches);
   gResponseBody = getRemoteUpdatesXMLString(updates);
-
   gUpdates = null;
   gUpdateCount = null;
   gStatusResult = null;
-  gCheckFunc = downloadUpdate;
+  gCheckFunc = updateCheckCompleted;
   gUpdateChecker.checkForUpdates(updateCheckListener, true);
 }
 
-// The HttpServer must be stopped before calling do_test_finished
-function finish_test() {
-  stop_httpserver(doTestFinish);
-}
-
-class TestDownloadListener {
-  constructor() {
-    this.stoppedOnce = false;
-  }
-
-  onStartRequest(aRequest, aContext) { }
-
-  onStatus(aRequest, aContext, aStatus, aStatusText) {
-    if (!this.stoppedOnce) {
-      Assert.equal(aStatus, Cr.NS_NET_STATUS_WAITING_FOR,
-                   "the download status" + MSG_SHOULD_EQUAL);
-      do_execute_soon(() => gAUS.pauseDownload());
-    }
-  }
-
-  onProgress(aRequest, aContext, aProgress, aMaxProgress) { }
-
-  onStopRequest(aRequest, aContext, aStatus) {
-    if (!this.stoppedOnce) {
-      // The first time we stop, it should be because we paused the download.
-      this.stoppedOnce = true;
-      Assert.equal(gBestUpdate.state, STATE_DOWNLOADING,
-                   "the update state" + MSG_SHOULD_EQUAL);
-      Assert.equal(aStatus, Cr.NS_BINDING_ABORTED,
-                   "the download status" + MSG_SHOULD_EQUAL);
-      do_execute_soon(resumeDownload);
-    } else {
-      // The second time we stop, it should be because the download is done.
-      Assert.equal(gBestUpdate.state, STATE_PENDING,
-                   "the update state" + MSG_SHOULD_EQUAL);
-      Assert.equal(aStatus, Cr.NS_OK,
-                   "the download status" + MSG_SHOULD_EQUAL);
-      do_execute_soon(finish_test);
-    }
-  }
-
-  QueryInterface(iid) {
-    if (iid.equals(Ci.nsIRequestObserver) ||
-        iid.equals(Ci.nsIProgressEventSink)) {
-      return this;
-    }
-    throw Cr.NS_ERROR_NO_INTERFACE;
-  }
-}
-
-let gBestUpdate;
-let gListener = new TestDownloadListener();
-
-function downloadUpdate() {
-  Assert.equal(gUpdateCount, 1, "the update count" + MSG_SHOULD_EQUAL);
-  gBestUpdate = gAUS.selectUpdate(gUpdates, gUpdateCount);
-  let state = gAUS.downloadUpdate(gBestUpdate, false);
+/**
+ * Since gCheckFunc value is the updateCheckCompleted function at this stage
+ * this is called after the update check completes in updateCheckListener.
+ */
+function updateCheckCompleted() {
+  Assert.equal(gUpdateCount, 1,
+               "the update count" + MSG_SHOULD_EQUAL);
+  let bestUpdate = gAUS.selectUpdate(gUpdates, gUpdateCount);
+  let state = gAUS.downloadUpdate(bestUpdate, false);
   if (state == STATE_NONE || state == STATE_FAILED) {
     do_throw("nsIApplicationUpdateService:downloadUpdate returned " + state);
   }
-  gAUS.addDownloadListener(gListener);
+  gAUS.addDownloadListener(downloadListener);
 }
 
-function resumeDownload() {
-  gSlowDownloadContinue = true;
+/**
+ * Called after the download listener onStopRequest is called.
+ */
+function downloadListenerStop() {
+  Assert.equal(gStatusResult, Cr.NS_OK,
+               "the download status result" + MSG_SHOULD_EQUAL);
+  gAUS.removeDownloadListener(downloadListener);
+  gUpdateManager.cleanupActiveUpdate();
+  do_execute_soon(waitForUpdateXMLFiles);
+}
 
-  let state = gAUS.downloadUpdate(gBestUpdate, false);
-  if (state == STATE_NONE || state == STATE_FAILED) {
-    do_throw("nsIApplicationUpdateService:downloadUpdate returned " + state);
-  }
-
-  // Resuming creates a new Downloader, and thus drops registered listeners.
-  gAUS.addDownloadListener(gListener);
+/**
+ * Called after the call to waitForUpdateXMLFiles finishes.
+ */
+function waitForUpdateXMLFilesFinished() {
+  // The HttpServer must be stopped before calling do_test_finished
+  stop_httpserver(doTestFinish);
 }
deleted file mode 100644
--- a/toolkit/mozapps/update/tests/unit_aus_update/downloadResumeAfterRestart.js
+++ /dev/null
@@ -1,71 +0,0 @@
-/* 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/.
- */
-
-function run_test() {
-  setupTestCommon();
-  start_httpserver();
-
-  debugDump("testing resuming a download on startup that was in progress " +
-            "when the application quit");
-
-  // Simulate a download that was partially completed in an earlier session
-  // by writing part of the final MAR into the download path.
-  const marBytesToPreCopy = 200;
-  let marFile = getTestDirFile(FILE_SIMPLE_MAR);
-  let marBytes = readFileBytes(marFile);
-  marBytes = marBytes.substring(0, marBytesToPreCopy);
-  let updateFile = getUpdatesPatchDir();
-  updateFile.append(FILE_UPDATE_MAR);
-  writeFile(updateFile, marBytes);
-
-  // Also need to spoof the entityID that the HttpBaseChannel would have created
-  // had those bytes been transferred by a real channel.
-  let marModTime = new Date(marFile.lastModifiedTime).toUTCString();
-  let patchProps = {state: STATE_DOWNLOADING,
-                    url: gURLData + FILE_SIMPLE_MAR,
-                    entityID: "/" + SIZE_SIMPLE_MAR + "/" + marModTime};
-  let patches = getLocalPatchString(patchProps);
-  let updateProps = {appVersion: "1.0"};
-  let updates = getLocalUpdateString(updateProps, patches);
-  writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
-  writeStatusFile(STATE_DOWNLOADING);
-
-  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);
-
-  gAUS.addDownloadListener({
-    onStartRequest(aRequest, aContext) { },
-
-    onStatus(aRequest, aContext, aStatus, aStatusText) { },
-
-    onProgress(aRequest, aContext, aProgress, aMaxProgress) {
-      aRequest.QueryInterface(Ci.nsIHttpChannel);
-      Assert.equal(aRequest.getResponseHeader("Content-Length"),
-                   SIZE_SIMPLE_MAR - marBytesToPreCopy,
-                   "the downloader should only try to transfer the bytes " +
-                   "that were not already present");
-    },
-
-    onStopRequest(aRequest, aContext, aStatus) {
-      Assert.equal(aStatus, Cr.NS_OK, "the download should succeed");
-
-      gUpdateManager.cleanupActiveUpdate();
-      stop_httpserver(doTestFinish);
-    },
-
-    QueryInterface(iid) {
-      if (iid.equals(Ci.nsIRequestObserver) ||
-          iid.equals(Ci.nsIProgressEventSink)) {
-        return this;
-      }
-      throw Cr.NS_ERROR_NO_INTERFACE;
-    }
-  });
-}
deleted file mode 100644
--- a/toolkit/mozapps/update/tests/unit_aus_update/downloadResumeErrorNoEntityID.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/* 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/.
- */
-
-function run_test() {
-  setupTestCommon();
-  start_httpserver();
-
-  debugDump("testing fallback to restarting a download when a partial file " +
-            "is present but the entityID isn't set in the patch XML");
-
-
-  // Simulate a download that was partially completed in an earlier session
-  // by writing part of the final MAR into the download path.
-  const marBytesToPreCopy = 200;
-  let marFile = getTestDirFile(FILE_SIMPLE_MAR);
-  let marBytes = readFileBytes(marFile);
-  marBytes = marBytes.substring(0, marBytesToPreCopy);
-  let updateFile = getUpdatesPatchDir();
-  updateFile.append(FILE_UPDATE_MAR);
-  writeFile(updateFile, marBytes);
-
-  let patchProps = {state: STATE_DOWNLOADING, url: gURLData + FILE_SIMPLE_MAR};
-  let patches = getLocalPatchString(patchProps);
-  let updateProps = {appVersion: "1.0"};
-  let updates = getLocalUpdateString(updateProps, patches);
-  writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
-  writeStatusFile(STATE_DOWNLOADING);
-
-  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);
-
-  gAUS.addDownloadListener({
-    onStartRequest(aRequest, aContext) { },
-
-    onStatus(aRequest, aContext, aStatus, aStatusText) { },
-
-    onProgress(aRequest, aContext, aProgress, aMaxProgress) {
-      aRequest.QueryInterface(Ci.nsIHttpChannel);
-      Assert.equal(aRequest.getResponseHeader("Content-Length"),
-                   SIZE_SIMPLE_MAR,
-                   "should be downloading the entire MAR file");
-    },
-
-    onStopRequest(aRequest, aContext, aStatus) {
-      Assert.equal(aStatus, Cr.NS_OK, "the download should succeed");
-
-      gUpdateManager.cleanupActiveUpdate();
-      stop_httpserver(doTestFinish);
-    },
-
-    QueryInterface(iid) {
-      if (iid.equals(Ci.nsIRequestObserver) ||
-          iid.equals(Ci.nsIProgressEventSink)) {
-        return this;
-      }
-      throw Cr.NS_ERROR_NO_INTERFACE;
-    }
-  });
-}
--- a/toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js
+++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js
@@ -1,16 +1,15 @@
 /* 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/.
  */
 
 function run_test() {
   setupTestCommon();
-  start_httpserver();
 
   debugDump("testing resuming an update download in progress for the same " +
             "version of the application on startup (Bug 485624)");
 
   let patchProps = {state: STATE_DOWNLOADING};
   let patches = getLocalPatchString(patchProps);
   let updateProps = {appVersion: "1.0"};
   let updates = getLocalUpdateString(updateProps, patches);
@@ -31,10 +30,10 @@ function run_test() {
   gUpdateManager.cleanupActiveUpdate();
   do_execute_soon(waitForUpdateXMLFiles);
 }
 
 /**
  * Called after the call to waitForUpdateXMLFiles finishes.
  */
 function waitForUpdateXMLFilesFinished() {
-  stop_httpserver(doTestFinish);
+  do_execute_soon(doTestFinish);
 }
--- a/toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js
+++ b/toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js
@@ -10,16 +10,20 @@ function run_test() {
   setupTestCommon();
 
   debugDump("testing remote update xml attributes");
 
   start_httpserver();
   setUpdateURL(gURLData + gHTTPHandlerPath);
   setUpdateChannel("test_channel");
 
+  // This test expects that the app.update.download.backgroundInterval
+  // preference doesn't already exist.
+  Services.prefs.deleteBranch("app.update.download.backgroundInterval");
+
   standardInit();
   do_execute_soon(run_test_pt01);
 }
 
 // Helper function for testing update counts returned from an update xml
 function run_test_helper_pt1(aMsg, aExpectedCount, aNextRunFunc) {
   gUpdates = null;
   gUpdateCount = null;
@@ -58,16 +62,17 @@ function run_test_pt02() {
   patches += getRemotePatchString(patchProps);
   let updateProps = {type: "minor",
                      name: "Minor Test",
                      displayVersion: "version 2.1a1pre",
                      appVersion: "2.1a1pre",
                      buildID: "20080811053724",
                      detailsURL: "http://details/",
                      promptWaitTime: "345600",
+                     backgroundInterval: "1200",
                      custom1: "custom1_attr=\"custom1 value\"",
                      custom2: "custom2_attr=\"custom2 value\""};
   let updates = getRemoteUpdateString(updateProps, patches);
   gResponseBody = getRemoteUpdatesXMLString(updates);
   gUpdateChecker.checkForUpdates(updateCheckListener, true);
 }
 
 function check_test_pt02() {
@@ -97,16 +102,19 @@ function check_test_pt02() {
   Assert.equal(bestUpdate.appVersion, "2.1a1pre",
                "the update appVersion attribute" + MSG_SHOULD_EQUAL);
   Assert.equal(bestUpdate.buildID, "20080811053724",
                "the update buildID attribute" + MSG_SHOULD_EQUAL);
   Assert.equal(bestUpdate.detailsURL, "http://details/",
                "the update detailsURL attribute" + MSG_SHOULD_EQUAL);
   Assert.equal(bestUpdate.promptWaitTime, "345600",
                "the update promptWaitTime attribute" + MSG_SHOULD_EQUAL);
+  // The default and maximum value for backgroundInterval is 600
+  Assert.equal(bestUpdate.getProperty("backgroundInterval"), "600",
+               "the update backgroundInterval attribute" + MSG_SHOULD_EQUAL);
   Assert.equal(bestUpdate.serviceURL, gURLData + gHTTPHandlerPath + "?force=1",
                "the update serviceURL attribute" + MSG_SHOULD_EQUAL);
   Assert.equal(bestUpdate.channel, "test_channel",
                "the update channel attribute" + MSG_SHOULD_EQUAL);
   Assert.ok(!bestUpdate.isCompleteUpdate,
             "the update isCompleteUpdate attribute" + MSG_SHOULD_EQUAL);
   Assert.ok(!bestUpdate.isSecurityUpdate,
             "the update isSecurityUpdate attribute" + MSG_SHOULD_EQUAL);
--- a/toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js
+++ b/toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js
@@ -7,16 +7,20 @@ function run_test() {
   setupTestCommon();
 
   debugDump("testing addition of a successful update to " + FILE_UPDATES_XML +
             " and verification of update properties including the format " +
             "prior to bug 530872");
 
   setUpdateChannel("test_channel");
 
+  // This test expects that the app.update.download.backgroundInterval
+  // preference doesn't already exist.
+  Services.prefs.deleteBranch("app.update.download.backgroundInterval");
+
   let patchProps = {type: "partial",
                     url: "http://partial/",
                     size: "86",
                     selected: "true",
                     state: STATE_PENDING};
   let patches = getLocalPatchString(patchProps);
   let updateProps = {type: "major",
                      name: "New",
@@ -26,16 +30,17 @@ function run_test() {
                      detailsURL: "http://details1/",
                      serviceURL: "http://service1/",
                      installDate: "1238441300314",
                      statusText: "test status text",
                      isCompleteUpdate: "false",
                      channel: "test_channel",
                      foregroundDownload: "true",
                      promptWaitTime: "345600",
+                     backgroundInterval: "300",
                      previousAppVersion: "3.0",
                      custom1: "custom1_attr=\"custom1 value\"",
                      custom2: "custom2_attr=\"custom2 value\""};
   let updates = getLocalUpdateString(updateProps, patches);
   writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
   writeStatusFile(STATE_SUCCEEDED);
 
   patchProps = {type: "complete",
@@ -90,16 +95,18 @@ function run_test() {
   Assert.equal(update.statusText, getString("installSuccess"),
                "the update statusText attribute" + MSG_SHOULD_EQUAL);
   Assert.ok(!update.isCompleteUpdate,
             "the update isCompleteUpdate attribute" + MSG_SHOULD_EQUAL);
   Assert.equal(update.channel, "test_channel",
                "the update channel attribute" + MSG_SHOULD_EQUAL);
   Assert.equal(update.promptWaitTime, "345600",
                "the update promptWaitTime attribute" + MSG_SHOULD_EQUAL);
+  Assert.equal(update.getProperty("backgroundInterval"), "300",
+               "the update backgroundInterval attribute" + MSG_SHOULD_EQUAL);
   Assert.equal(update.previousAppVersion, "3.0",
                "the update previousAppVersion attribute" + MSG_SHOULD_EQUAL);
   // Custom attributes
   Assert.equal(update.getProperty("custom1_attr"), "custom1 value",
                "the update custom1_attr property" + MSG_SHOULD_EQUAL);
   Assert.equal(update.getProperty("custom2_attr"), "custom2 value",
                "the update custom2_attr property" + MSG_SHOULD_EQUAL);
 
@@ -139,16 +146,19 @@ function run_test() {
   Assert.equal(update.buildID, "20080811053724",
                "the update buildID attribute" + MSG_SHOULD_EQUAL);
   Assert.ok(!!update.isCompleteUpdate,
             "the update isCompleteUpdate attribute" + MSG_SHOULD_EQUAL);
   Assert.equal(update.channel, "test_channel",
                "the update channel attribute" + MSG_SHOULD_EQUAL);
   Assert.equal(update.promptWaitTime, "691200",
                "the update promptWaitTime attribute" + MSG_SHOULD_EQUAL);
+  // The default and maximum value for backgroundInterval is 600
+  Assert.equal(update.getProperty("backgroundInterval"), "600",
+               "the update backgroundInterval attribute" + MSG_SHOULD_EQUAL);
   Assert.equal(update.previousAppVersion, null,
                "the update previousAppVersion attribute" + MSG_SHOULD_EQUAL);
   // Custom attributes
   Assert.equal(update.getProperty("custom3_attr"), "custom3 value",
                "the update custom3_attr property" + MSG_SHOULD_EQUAL);
   Assert.equal(update.getProperty("custom4_attr"), "custom4 value",
                "the update custom4_attr property" + MSG_SHOULD_EQUAL);
 
--- a/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini
+++ b/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini
@@ -16,17 +16,15 @@ head = head_update.js
 [cleanupDownloadingForOlderAppVersion.js]
 [cleanupDownloadingForDifferentChannel.js]
 [cleanupDownloadingForSameVersionAndBuildID.js]
 [cleanupDownloadingIncorrectStatus.js]
 [cleanupPendingVersionFileIncorrectStatus.js]
 [cleanupSuccessLogMove.js]
 [cleanupSuccessLogsFIFO.js]
 [downloadInterruptedOffline.js]
-[downloadInterruptedRecoveryChannel.js]
 [downloadInterruptedNoRecovery.js]
-[downloadResumeAfterRestart.js]
-[downloadResumeErrorNoEntityID.js]
+[downloadInterruptedRecovery.js]
 [downloadResumeForSameAppVersion.js]
 [downloadCompleteAfterPartialFailure.js]
 [uiSilentPref.js]
 [uiUnsupportedAlreadyNotified.js]
 [uiAutoPref.js]