Bug 1115983 - Keep only minimal state information in the DataItem. r=mak
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Mon, 16 Feb 2015 18:49:44 +0000
changeset 229499 8427ddc63056f38fb4bbe5f6e52f62f1e40cdb69
parent 229498 9a9285ff83879890a3db27240bc4ce79bf325a05
child 229500 44c72cf73a9789a1d357cc875d018b8312f65182
push id55709
push userryanvm@gmail.com
push dateTue, 17 Feb 2015 19:27:34 +0000
treeherdermozilla-inbound@ebd50d4250b2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1115983
milestone38.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 1115983 - Keep only minimal state information in the DataItem. r=mak
browser/components/downloads/DownloadsCommon.jsm
browser/components/downloads/content/allDownloadsViewOverlay.js
browser/components/downloads/content/downloads.js
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -52,16 +52,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper",
                                   "resource://gre/modules/DownloadUIHelper.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
                                   "resource://gre/modules/DownloadUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm")
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
                                   "resource:///modules/RecentWindow.jsm");
@@ -87,21 +89,16 @@ const kDownloadsStringsRequiringFormatti
   statusSeparatorBeforeNumber: true,
   fileExecutableSecurityWarning: true
 };
 
 const kDownloadsStringsRequiringPluralForm = {
   otherDownloads2: true
 };
 
-XPCOMUtils.defineLazyGetter(this, "DownloadsLocalFileCtor", function () {
-  return Components.Constructor("@mozilla.org/file/local;1",
-                                "nsILocalFile", "initWithPath");
-});
-
 const kPartialDownloadSuffix = ".part";
 
 const kPrefBranch = Services.prefs.getBranch("browser.download.");
 
 let PrefObserver = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
   getPref(name) {
@@ -347,31 +344,31 @@ this.DownloadsCommon = {
         case nsIDM.DOWNLOAD_PAUSED:
           summary.numPaused++;
           break;
         case nsIDM.DOWNLOAD_SCANNING:
           summary.numScanning++;
           break;
         case nsIDM.DOWNLOAD_DOWNLOADING:
           summary.numDownloading++;
-          if (dataItem.maxBytes > 0 && dataItem.speed > 0) {
-            let sizeLeft = dataItem.maxBytes - dataItem.currBytes;
+          if (dataItem.maxBytes > 0 && dataItem.download.speed > 0) {
+            let sizeLeft = dataItem.maxBytes - dataItem.download.currentBytes;
             summary.rawTimeLeft = Math.max(summary.rawTimeLeft,
-                                           sizeLeft / dataItem.speed);
+                                           sizeLeft / dataItem.download.speed);
             summary.slowestSpeed = Math.min(summary.slowestSpeed,
-                                            dataItem.speed);
+                                            dataItem.download.speed);
           }
           break;
       }
       // Only add to total values if we actually know the download size.
       if (dataItem.maxBytes > 0 &&
           dataItem.state != nsIDM.DOWNLOAD_CANCELED &&
           dataItem.state != nsIDM.DOWNLOAD_FAILED) {
         summary.totalSize += dataItem.maxBytes;
-        summary.totalTransferred += dataItem.currBytes;
+        summary.totalTransferred += dataItem.download.currentBytes;
       }
     }
 
     if (summary.numActive != 0 && summary.totalSize != 0 &&
         summary.numActive != summary.numScanning) {
       summary.percentComplete = (summary.totalTransferred /
                                  summary.totalSize) * 100;
     }
@@ -475,17 +472,16 @@ this.DownloadsCommon = {
           .getService(Ci.nsIExternalProtocolService)
           .loadUrl(NetUtil.newURI(aFile));
       }
     }).then(null, Cu.reportError);
   },
 
   /**
    * Show a downloaded file in the system file manager.
-   * If you have a dataItem, use dataItem.showLocalFile.
    *
    * @param aFile
    *        a downloaded file.
    */
   showDownloadedFile(aFile) {
     if (!(aFile instanceof Ci.nsIFile)) {
       throw new Error("aFile must be a nsIFile object");
     }
@@ -728,17 +724,18 @@ DownloadsDataCtor.prototype = {
         try {
           let downloadMetaData = { state: aDataItem.state,
                                    endTime: aDataItem.endTime };
           if (aDataItem.done) {
             downloadMetaData.fileSize = aDataItem.maxBytes;
           }
 
           PlacesUtils.annotations.setPageAnnotation(
-                        NetUtil.newURI(aDataItem.uri), "downloads/metaData",
+                        NetUtil.newURI(aDataItem.download.source.url),
+                        "downloads/metaData",
                         JSON.stringify(downloadMetaData), 0,
                         PlacesUtils.annotations.EXPIRE_WITH_HISTORY);
         } catch (ex) {
           Cu.reportError(ex);
         }
       }
     }
 
@@ -793,17 +790,17 @@ DownloadsDataCtor.prototype = {
    */
   _updateView(aView) {
     // Indicate to the view that a batch loading operation is in progress.
     aView.onDataLoadStarting();
 
     // Sort backwards by start time, ensuring that the most recent
     // downloads are added first regardless of their state.
     let loadedItemsArray = [...this.dataItems];
-    loadedItemsArray.sort((a, b) => b.startTime - a.startTime);
+    loadedItemsArray.sort((a, b) => b.download.startTime - a.download.startTime);
     loadedItemsArray.forEach(dataItem => aView.onDataItemAdded(dataItem, false));
 
     // Notify the view that all data is available.
     aView.onDataLoadCompleted();
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// Notifications sent to the most recent browser window only
@@ -868,70 +865,59 @@ XPCOMUtils.defineLazyGetter(this, "Downl
  * Represents a single item in the list of downloads.
  *
  * The endTime property is initialized to the current date and time.
  *
  * @param aDownload
  *        The Download object with the current state.
  */
 function DownloadsDataItem(aDownload) {
-  this._download = aDownload;
-
-  this.file = aDownload.target.path;
-  this.target = OS.Path.basename(aDownload.target.path);
-  this.uri = aDownload.source.url;
+  this.download = aDownload;
   this.endTime = Date.now();
-
   this.updateFromDownload();
 }
 
 DownloadsDataItem.prototype = {
   /**
    * Updates this object from the underlying Download object.
    */
   updateFromDownload() {
     // Collapse state using the correct priority.
-    if (this._download.succeeded) {
+    if (this.download.succeeded) {
       this.state = nsIDM.DOWNLOAD_FINISHED;
-    } else if (this._download.error &&
-               this._download.error.becauseBlockedByParentalControls) {
+    } else if (this.download.error &&
+               this.download.error.becauseBlockedByParentalControls) {
       this.state = nsIDM.DOWNLOAD_BLOCKED_PARENTAL;
-    } else if (this._download.error &&
-               this._download.error.becauseBlockedByReputationCheck) {
+    } else if (this.download.error &&
+               this.download.error.becauseBlockedByReputationCheck) {
       this.state = nsIDM.DOWNLOAD_DIRTY;
-    } else if (this._download.error) {
+    } else if (this.download.error) {
       this.state = nsIDM.DOWNLOAD_FAILED;
-    } else if (this._download.canceled && this._download.hasPartialData) {
+    } else if (this.download.canceled && this.download.hasPartialData) {
       this.state = nsIDM.DOWNLOAD_PAUSED;
-    } else if (this._download.canceled) {
+    } else if (this.download.canceled) {
       this.state = nsIDM.DOWNLOAD_CANCELED;
-    } else if (this._download.stopped) {
+    } else if (this.download.stopped) {
       this.state = nsIDM.DOWNLOAD_NOTSTARTED;
     } else {
       this.state = nsIDM.DOWNLOAD_DOWNLOADING;
     }
 
-    this.referrer = this._download.source.referrer;
-    this.startTime = this._download.startTime;
-    this.currBytes = this._download.currentBytes;
-    this.resumable = this._download.hasPartialData;
-    this.speed = this._download.speed;
-
-    if (this._download.succeeded) {
+    if (this.download.succeeded) {
       // If the download succeeded, show the final size if available, otherwise
       // use the last known number of bytes transferred.  The final size on disk
       // will be available when bug 941063 is resolved.
-      this.maxBytes = this._download.hasProgress ?
-                             this._download.totalBytes :
-                             this._download.currentBytes;
+      this.maxBytes = this.download.hasProgress ?
+                             this.download.totalBytes :
+                             this.download.currentBytes;
       this.percentComplete = 100;
-    } else if (this._download.hasProgress) {
+    } else if (this.download.hasProgress) {
       // If the final size and progress are known, use them.
-      this.maxBytes = this._download.totalBytes;
-      this.percentComplete = this._download.progress;
+      this.maxBytes = this.download.totalBytes;
+      this.percentComplete = this.download.progress;
     } else {
       // The download final size and progress percentage is unknown.
       this.maxBytes = -1;
       this.percentComplete = -1;
     }
   },
 
   /**
@@ -974,131 +960,49 @@ DownloadsDataItem.prototype = {
       nsIDM.DOWNLOAD_FINISHED,
       nsIDM.DOWNLOAD_BLOCKED_PARENTAL,
       nsIDM.DOWNLOAD_BLOCKED_POLICY,
       nsIDM.DOWNLOAD_DIRTY,
     ].indexOf(this.state) != -1;
   },
 
   /**
-   * Indicates whether the download is finished and can be opened.
-   */
-  get openable() {
-    return this.state == nsIDM.DOWNLOAD_FINISHED;
-  },
-
-  /**
    * Indicates whether the download stopped because of an error, and can be
    * resumed manually.
    */
   get canRetry() {
     return this.state == nsIDM.DOWNLOAD_CANCELED ||
            this.state == nsIDM.DOWNLOAD_FAILED;
   },
 
   /**
    * Returns the nsILocalFile for the download target.
    *
    * @throws if the native path is not valid.  This can happen if the same
    *         profile is used on different platforms, for example if a native
    *         Windows path is stored and then the item is accessed on a Mac.
+   *
+   * @deprecated Callers should use OS.File and "download.target.path".
    */
   get localFile() {
-    return this._getFile(this.file);
+    // We should remove  should use this.download.target.partFilePath and check asyncrhonously.
+    return new FileUtils.File(this.download.target.path);
   },
 
   /**
    * Returns the nsILocalFile for the partially downloaded target.
    *
    * @throws if the native path is not valid.  This can happen if the same
    *         profile is used on different platforms, for example if a native
    *         Windows path is stored and then the item is accessed on a Mac.
+   *
+   * @deprecated Callers should use OS.File and "download.target.partFilePath".
    */
   get partFile() {
-    return this._getFile(this.file + kPartialDownloadSuffix);
-  },
-
-  /**
-   * Returns an nsILocalFile for aFilename. aFilename might be a file URL or
-   * a native path.
-   *
-   * @param aFilename the filename of the file to retrieve.
-   * @return an nsILocalFile for the file.
-   * @throws if the native path is not valid.  This can happen if the same
-   *         profile is used on different platforms, for example if a native
-   *         Windows path is stored and then the item is accessed on a Mac.
-   * @note This function makes no guarantees about the file's existence -
-   *       callers should check that the returned file exists.
-   */
-  _getFile(aFilename) {
-    // The download database may contain targets stored as file URLs or native
-    // paths.  This can still be true for previously stored items, even if new
-    // items are stored using their file URL.  See also bug 239948 comment 12.
-    if (aFilename.startsWith("file:")) {
-      // Assume the file URL we obtained from the downloads database or from the
-      // "spec" property of the target has the UTF-8 charset.
-      let fileUrl = NetUtil.newURI(aFilename).QueryInterface(Ci.nsIFileURL);
-      return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile);
-    } else {
-      // The downloads database contains a native path.  Try to create a local
-      // file, though this may throw an exception if the path is invalid.
-      return new DownloadsLocalFileCtor(aFilename);
-    }
-  },
-
-  /**
-   * Open the target file for this download.
-   */
-  openLocalFile() {
-    this._download.launch().then(null, Cu.reportError);
-  },
-
-  /**
-   * Show the downloaded file in the system file manager.
-   */
-  showLocalFile() {
-    DownloadsCommon.showDownloadedFile(this.localFile);
-  },
-
-  /**
-   * Resumes the download if paused, pauses it if active.
-   * @throws if the download is not resumable or if has already done.
-   */
-  togglePauseResume() {
-    if (this._download.stopped) {
-      this._download.start();
-    } else {
-      this._download.cancel();
-    }
-  },
-
-  /**
-   * Attempts to retry the download.
-   * @throws if we cannot.
-   */
-  retry() {
-    this._download.start();
-  },
-
-  /**
-   * Cancels the download.
-   */
-  cancel() {
-    this._download.cancel();
-    this._download.removePartialData().then(null, Cu.reportError);
-  },
-
-  /**
-   * Remove the download.
-   */
-  remove() {
-    Downloads.getList(Downloads.ALL)
-             .then(list => list.remove(this._download))
-             .then(() => this._download.finalize(true))
-             .then(null, Cu.reportError);
+    return new FileUtils.File(this.download.target.path + kPartialDownloadSuffix);
   },
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadsViewPrototype
 
 /**
  * A prototype for an object that registers itself with DownloadsData as soon
--- a/browser/components/downloads/content/allDownloadsViewOverlay.js
+++ b/browser/components/downloads/content/allDownloadsViewOverlay.js
@@ -136,17 +136,17 @@ DownloadElementShell.prototype = {
       }
     }
     return aValue;
   },
 
   // The download uri (as a string)
   get downloadURI() {
     if (this._dataItem) {
-      return this._dataItem.uri;
+      return this._dataItem.download.source.url;
     }
     if (this._placesNode) {
       return this._placesNode.uri;
     }
     throw new Error("Unexpected download element state");
   },
 
   get _downloadURIObj() {
@@ -253,28 +253,27 @@ DownloadElementShell.prototype = {
    * - fileSize (only set for downloads which completed successfully):
    *   the downloaded file size.  For downloads done after the landing of
    *   bug 826991, this value is "static" - that is, it does not necessarily
    *   mean that the file is in place and has this size.
    */
   getDownloadMetaData() {
     if (!this._metaData) {
       if (this._dataItem) {
+        let leafName = OS.Path.basename(this._dataItem.download.target.path);
         this._metaData = {
           state:       this._dataItem.state,
           endTime:     this._dataItem.endTime,
-          fileName:    this._dataItem.target,
-          displayName: this._dataItem.target
+          fileName:    leafName,
+          displayName: leafName,
         };
         if (this._dataItem.done) {
           this._metaData.fileSize = this._dataItem.maxBytes;
         }
-        if (this._dataItem.localFile) {
-          this._metaData.filePath = this._dataItem.localFile.path;
-        }
+        this._metaData.filePath = this._dataItem.download.target.path;
       } else {
         try {
           this._metaData = JSON.parse(this.placesMetaData.jsonDetails);
         } catch (ex) {
           this._metaData = {};
           if (this._targetFileInfoFetched && this._targetFileExists) {
             // For very old downloads without metadata, we assume that a zero
             // byte file is a placeholder, and allow the download to restart.
@@ -304,28 +303,28 @@ DownloadElementShell.prototype = {
     return this._metaData;
   },
 
   _getStatusText() {
     let s = DownloadsCommon.strings;
     if (this._dataItem && this._dataItem.inProgress) {
       if (this._dataItem.paused) {
         let transfer =
-          DownloadUtils.getTransferTotal(this._dataItem.currBytes,
+          DownloadUtils.getTransferTotal(this._dataItem.download.currentBytes,
                                          this._dataItem.maxBytes);
 
          // We use the same XUL label to display both the state and the amount
          // transferred, for example "Paused -  1.1 MB".
          return s.statusSeparatorBeforeNumber(s.statePaused, transfer);
       }
       if (this._dataItem.state == nsIDM.DOWNLOAD_DOWNLOADING) {
         let [status, newEstimatedSecondsLeft] =
-          DownloadUtils.getDownloadStatus(this.dataItem.currBytes,
+          DownloadUtils.getDownloadStatus(this.dataItem.download.currentBytes,
                                           this.dataItem.maxBytes,
-                                          this.dataItem.speed,
+                                          this.dataItem.download.speed,
                                           this._lastEstimatedSecondsLeft || Infinity);
         this._lastEstimatedSecondsLeft = newEstimatedSecondsLeft;
         return status;
       }
       if (this._dataItem.starting) {
         return s.stateStarting;
       }
       if (this._dataItem.state == nsIDM.DOWNLOAD_SCANNING) {
@@ -365,17 +364,17 @@ DownloadElementShell.prototype = {
         // Fallback to default unknown state.
       }
       default:
         stateLabel = s.sizeUnknown;
         break;
     }
 
     // TODO (bug 829201): history downloads should get the referrer from Places.
-    let referrer = this._dataItem && this._dataItem.referrer ||
+    let referrer = this._dataItem && this._dataItem.download.source.referrer ||
                    this.downloadURI;
     let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer);
 
     let date = new Date(this.getDownloadMetaData().endTime);
     let [displayDate, fullDate] = DownloadUtils.getReadableDates(date);
 
     // We use the same XUL label to display the state, the host name, and the
     // end time.
@@ -489,20 +488,20 @@ DownloadElementShell.prototype = {
   /* nsIController */
   isCommandEnabled(aCommand) {
     // The only valid command for inactive elements is cmd_delete.
     if (!this.active && aCommand != "cmd_delete") {
       return false;
     }
     switch (aCommand) {
       case "downloadsCmd_open":
-        // We cannot open a session download file unless it's done ("openable").
-        // If it's finished, we need to make sure the file was not removed,
+        // We cannot open a session download file unless it's succeeded.
+        // If it's succeeded, we need to make sure the file was not removed,
         // as we do for past downloads.
-        if (this._dataItem && !this._dataItem.openable) {
+        if (this._dataItem && !this._dataItem.download.succeeded) {
           return false;
         }
 
         if (this._targetFileInfoFetched) {
           return this._targetFileExists;
         }
 
         // If the target file information is not yet fetched,
@@ -519,22 +518,22 @@ DownloadElementShell.prototype = {
           return this._targetFileExists;
         }
 
         // If the target file information is not yet fetched,
         // temporarily assume that the file is in place.
         return this.getDownloadMetaData().state == nsIDM.DOWNLOAD_FINISHED;
       case "downloadsCmd_pauseResume":
         return this._dataItem && this._dataItem.inProgress &&
-               this._dataItem.resumable;
+               this._dataItem.download.hasPartialData;
       case "downloadsCmd_retry":
         // An history download can always be retried.
         return !this._dataItem || this._dataItem.canRetry;
       case "downloadsCmd_openReferrer":
-        return this._dataItem && !!this._dataItem.referrer;
+        return this._dataItem && !!this._dataItem.download.source.referrer;
       case "cmd_delete":
         // The behavior in this case is somewhat unexpected, so we disallow that.
         if (this._placesNode && this._dataItem && this._dataItem.inProgress) {
           return false;
         }
         return true;
       case "downloadsCmd_cancel":
         return this._dataItem != null;
@@ -551,59 +550,66 @@ DownloadElementShell.prototype = {
     DownloadURL(this.downloadURI, this.getDownloadMetaData().fileName,
                 initiatingDoc);
   },
 
   /* nsIController */
   doCommand(aCommand) {
     switch (aCommand) {
       case "downloadsCmd_open": {
-        let file = this._dataItem ?
-          this.dataItem.localFile :
-          new FileUtils.File(this.getDownloadMetaData().filePath);
+        let file = new FileUtils.File(this._dataItem
+                                      ? this._dataItem.download.target.path
+                                      : this.getDownloadMetaData().filePath);
 
         DownloadsCommon.openDownloadedFile(file, null, window);
         break;
       }
       case "downloadsCmd_show": {
-        if (this._dataItem) {
-          this._dataItem.showLocalFile();
-        } else {
-          let file = new FileUtils.File(this.getDownloadMetaData().filePath);
-          DownloadsCommon.showDownloadedFile(file);
-        }
+        let file = new FileUtils.File(this._dataItem
+                                      ? this._dataItem.download.target.path
+                                      : this.getDownloadMetaData().filePath);
+
+        DownloadsCommon.showDownloadedFile(file);
         break;
       }
       case "downloadsCmd_openReferrer": {
-        openURL(this._dataItem.referrer);
+        openURL(this._dataItem.download.source.referrer);
         break;
       }
       case "downloadsCmd_cancel": {
-        this._dataItem.cancel();
+        this._dataItem.download.cancel().catch(() => {});
+        this._dataItem.download.removePartialData().catch(Cu.reportError);
         break;
       }
       case "cmd_delete": {
         if (this._dataItem) {
-          this._dataItem.remove();
+          Downloads.getList(Downloads.ALL)
+                   .then(list => list.remove(this._dataItem.download))
+                   .then(() => this._dataItem.download.finalize(true))
+                   .catch(Cu.reportError);
         }
         if (this._placesNode) {
           PlacesUtils.bhistory.removePage(this._downloadURIObj);
         }
         break;
       }
       case "downloadsCmd_retry": {
         if (this._dataItem) {
-          this._dataItem.retry();
+          this._dataItem.download.start().catch(() => {});
         } else {
           this._retryAsHistoryDownload();
         }
         break;
       }
       case "downloadsCmd_pauseResume": {
-        this._dataItem.togglePauseResume();
+        if (this._dataItem.download.stopped) {
+          this._dataItem.download.start();
+        } else {
+          this._dataItem.download.cancel();
+        }
         break;
       }
     }
   },
 
   // Returns whether or not the download handled by this shell should
   // show up in the search results for the given term.  Both the display
   // name for the download and the url are searched.
@@ -843,17 +849,18 @@ DownloadsPlacesView.prototype = {
    *        To speed up the appending of multiple elements to the end of the
    *        list which are coming in a single batch (i.e. invalidateContainer),
    *        a document fragment may be passed to which the new elements would
    *        be appended. It's the caller's job to ensure the fragment is merged
    *        to the richlistbox at the end.
    */
   _addDownloadData(aDataItem, aPlacesNode, aNewest = false,
                    aDocumentFragment = null) {
-    let downloadURI = aPlacesNode ? aPlacesNode.uri : aDataItem.uri;
+    let downloadURI = aPlacesNode ? aPlacesNode.uri
+                                  : aDataItem.download.source.url;
     let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI);
     if (!shellsForURI) {
       shellsForURI = new Set();
       this._downloadElementsShellsForURI.set(downloadURI, shellsForURI);
     }
 
     let newOrUpdatedShell = null;
 
@@ -993,17 +1000,18 @@ DownloadsPlacesView.prototype = {
           if (shellsForURI.size == 0)
             this._downloadElementsShellsForURI.delete(downloadURI);
         }
       }
     }
   },
 
   _removeSessionDownloadFromView(aDataItem) {
-    let shells = this._downloadElementsShellsForURI.get(aDataItem.uri);
+    let shells = this._downloadElementsShellsForURI
+                     .get(aDataItem.download.source.url);
     if (shells.size == 0) {
       throw new Error("Should have had at leaat one shell for this uri");
     }
 
     let shell = this._viewItemsForDataItems.get(aDataItem);
     if (!shells.has(shell)) {
       throw new Error("Missing download element shell in shells list for url");
     }
@@ -1011,17 +1019,17 @@ DownloadsPlacesView.prototype = {
     // If there's more than one item for this download uri, we can let the
     // view item for this this particular data item go away.
     // If there's only one item for this download uri, we should only
     // keep it if it is associated with a history download.
     if (shells.size > 1 || !shell.placesNode) {
       this._removeElement(shell.element);
       shells.delete(shell);
       if (shells.size == 0) {
-        this._downloadElementsShellsForURI.delete(aDataItem.uri);
+        this._downloadElementsShellsForURI.delete(aDataItem.download.source.url);
       }
     } else {
       // We have one download element shell containing both a session download
       // and a history download, and we are now removing the session download.
       // Previously, we did not use the Places metadata because it was obscured
       // by the session download. Since this is no longer the case, we have to
       // read the latest metadata before removing the session download.
       shell.placesMetaData = this._getPlacesMetaDataFor(shell.placesNode.uri);
--- a/browser/components/downloads/content/downloads.js
+++ b/browser/components/downloads/content/downloads.js
@@ -1004,24 +1004,24 @@ function DownloadsViewItem(aDataItem, aE
   this.dataItem = aDataItem;
 
   this.lastEstimatedSecondsLeft = Infinity;
 
   // Set the URI that represents the correct icon for the target file.  As soon
   // as bug 239948 comment 12 is handled, the "file" property will be always a
   // file URL rather than a file name.  At that point we should remove the "//"
   // (double slash) from the icon URI specification (see test_moz_icon_uri.js).
-  this.image = "moz-icon://" + this.dataItem.file + "?size=32";
+  this.image = "moz-icon://" + this.dataItem.download.target.path + "?size=32";
 
   let attributes = {
     "type": "download",
     "class": "download-state",
     "state": this.dataItem.state,
     "progress": this.dataItem.inProgress ? this.dataItem.percentComplete : 100,
-    "target": this.dataItem.target,
+    "target": OS.Path.basename(this.dataItem.download.target.path),
     "image": this.image
   };
 
   for (let attributeName in attributes) {
     this._element.setAttribute(attributeName, attributes[attributeName]);
   }
 
   // Initialize more complex attributes.
@@ -1127,40 +1127,40 @@ DownloadsViewItem.prototype = {
    */
   _updateStatusLine() {
     const nsIDM = Ci.nsIDownloadManager;
 
     let status = "";
     let statusTip = "";
 
     if (this.dataItem.paused) {
-      let transfer = DownloadUtils.getTransferTotal(this.dataItem.currBytes,
+      let transfer = DownloadUtils.getTransferTotal(this.dataItem.download.currentBytes,
                                                     this.dataItem.maxBytes);
 
       // We use the same XUL label to display both the state and the amount
       // transferred, for example "Paused -  1.1 MB".
       status = DownloadsCommon.strings.statusSeparatorBeforeNumber(
                                             DownloadsCommon.strings.statePaused,
                                             transfer);
     } else if (this.dataItem.state == nsIDM.DOWNLOAD_DOWNLOADING) {
       // We don't show the rate for each download in order to reduce clutter.
       // The remaining time per download is likely enough information for the
       // panel.
       [status] =
-        DownloadUtils.getDownloadStatusNoRate(this.dataItem.currBytes,
+        DownloadUtils.getDownloadStatusNoRate(this.dataItem.download.currentBytes,
                                               this.dataItem.maxBytes,
-                                              this.dataItem.speed,
+                                              this.dataItem.download.speed,
                                               this.lastEstimatedSecondsLeft);
 
       // We are, however, OK with displaying the rate in the tooltip.
       let newEstimatedSecondsLeft;
       [statusTip, newEstimatedSecondsLeft] =
-        DownloadUtils.getDownloadStatus(this.dataItem.currBytes,
+        DownloadUtils.getDownloadStatus(this.dataItem.download.currentBytes,
                                         this.dataItem.maxBytes,
-                                        this.dataItem.speed,
+                                        this.dataItem.download.speed,
                                         this.lastEstimatedSecondsLeft);
       this.lastEstimatedSecondsLeft = newEstimatedSecondsLeft;
     } else if (this.dataItem.starting) {
       status = DownloadsCommon.strings.stateStarting;
     } else if (this.dataItem.state == nsIDM.DOWNLOAD_SCANNING) {
       status = DownloadsCommon.strings.stateScanning;
     } else if (!this.dataItem.inProgress) {
       let stateLabel = function () {
@@ -1172,17 +1172,18 @@ DownloadsViewItem.prototype = {
           case nsIDM.DOWNLOAD_BLOCKED_POLICY:   return s.stateBlockedPolicy;
           case nsIDM.DOWNLOAD_DIRTY:            return s.stateDirty;
           case nsIDM.DOWNLOAD_FINISHED:         return this._fileSizeText;
         }
         return null;
       }.apply(this);
 
       let [displayHost, fullHost] =
-        DownloadUtils.getURIHost(this.dataItem.referrer || this.dataItem.uri);
+        DownloadUtils.getURIHost(this.dataItem.download.source.referrer ||
+                                 this.dataItem.download.source.url);
 
       let end = new Date(this.dataItem.endTime);
       let [displayDate, fullDate] = DownloadUtils.getReadableDates(end);
 
       // We use the same XUL label to display the state, the host name, and the
       // end time, for example "Canceled - 222.net - 11:15" or "1.1 MB -
       // website2.com - Yesterday".  We show the full host and the complete date
       // in the tooltip.
@@ -1217,21 +1218,21 @@ DownloadsViewItem.prototype = {
    * Starts checking whether the target file of a finished download is still
    * available on disk, and sets an attribute that controls how the item is
    * presented visually.
    *
    * The existence check is executed on a background thread.
    */
   verifyTargetExists() {
     // We don't need to check if the download is not finished successfully.
-    if (!this.dataItem.openable) {
+    if (!this.dataItem.download.succeeded) {
       return;
     }
 
-    OS.File.exists(this.dataItem.localFile.path).then(aExists => {
+    OS.File.exists(this.dataItem.download.target.path).then(aExists => {
       if (aExists) {
         this._element.setAttribute("exists", "true");
       } else {
         this._element.removeAttribute("exists");
       }
     }).catch(Cu.reportError);
   },
 };
@@ -1345,28 +1346,30 @@ DownloadsViewItemController.prototype = 
   /**
    * The DownloadDataItem controlled by this object.
    */
   dataItem: null,
 
   isCommandEnabled(aCommand) {
     switch (aCommand) {
       case "downloadsCmd_open": {
-        return this.dataItem.openable && this.dataItem.localFile.exists();
+        return this.dataItem.download.succeeded &&
+               this.dataItem.localFile.exists();
       }
       case "downloadsCmd_show": {
         return this.dataItem.localFile.exists() ||
                this.dataItem.partFile.exists();
       }
       case "downloadsCmd_pauseResume":
-        return this.dataItem.inProgress && this.dataItem.resumable;
+        return this.dataItem.inProgress &&
+               this.dataItem.download.hasPartialData;
       case "downloadsCmd_retry":
         return this.dataItem.canRetry;
       case "downloadsCmd_openReferrer":
-        return !!this.dataItem.referrer;
+        return !!this.dataItem.download.source.referrer;
       case "cmd_delete":
       case "downloadsCmd_cancel":
       case "downloadsCmd_copyLocation":
       case "downloadsCmd_doDefault":
         return true;
     }
     return false;
   },
@@ -1382,62 +1385,71 @@ DownloadsViewItemController.prototype = 
 
   /**
    * This object contains one key for each command that operates on this item.
    *
    * In commands, the "this" identifier points to the controller item.
    */
   commands: {
     cmd_delete() {
-      this.dataItem.remove();
-      PlacesUtils.bhistory.removePage(NetUtil.newURI(this.dataItem.uri));
+      Downloads.getList(Downloads.ALL)
+               .then(list => list.remove(this.dataItem.download))
+               .then(() => this.dataItem.download.finalize(true))
+               .catch(Cu.reportError);
+      PlacesUtils.bhistory.removePage(
+                             NetUtil.newURI(this.dataItem.download.source.url));
     },
 
     downloadsCmd_cancel() {
-      this.dataItem.cancel();
+      this.dataItem.download.cancel().catch(() => {});
+      this.dataItem.download.removePartialData().catch(Cu.reportError);
     },
 
     downloadsCmd_open() {
-      this.dataItem.openLocalFile();
+      this.dataItem.download.launch().catch(Cu.reportError);
 
       // We explicitly close the panel here to give the user the feedback that
       // their click has been received, and we're handling the action.
       // Otherwise, we'd have to wait for the file-type handler to execute
       // before the panel would close. This also helps to prevent the user from
       // accidentally opening a file several times.
       DownloadsPanel.hidePanel();
     },
 
     downloadsCmd_show() {
-      this.dataItem.showLocalFile();
+      DownloadsCommon.showDownloadedFile(this.dataItem.localFile);
 
       // We explicitly close the panel here to give the user the feedback that
       // their click has been received, and we're handling the action.
       // Otherwise, we'd have to wait for the operating system file manager
       // window to open before the panel closed. This also helps to prevent the
       // user from opening the containing folder several times.
       DownloadsPanel.hidePanel();
     },
 
     downloadsCmd_pauseResume() {
-      this.dataItem.togglePauseResume();
+      if (this.dataItem.download.stopped) {
+        this.dataItem.download.start();
+      } else {
+        this.dataItem.download.cancel();
+      }
     },
 
     downloadsCmd_retry() {
-      this.dataItem.retry();
+      this.dataItem.download.start().catch(() => {});
     },
 
     downloadsCmd_openReferrer() {
-      openURL(this.dataItem.referrer);
+      openURL(this.dataItem.download.source.referrer);
     },
 
     downloadsCmd_copyLocation() {
       let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]
                       .getService(Ci.nsIClipboardHelper);
-      clipboard.copyString(this.dataItem.uri, document);
+      clipboard.copyString(this.dataItem.download.source.url, document);
     },
 
     downloadsCmd_doDefault() {
       const nsIDM = Ci.nsIDownloadManager;
 
       // Determine the default command for the current item.
       let defaultCommand = function () {
         switch (this.dataItem.state) {