Bug 1117141 - Part 2 of 2 - Refactor notifications and remove the DownloadsDataItem object. r=mak
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Mon, 16 Feb 2015 18:49:51 +0000
changeset 229503 448f00fe77e1474b1576eb06141a1ffaaa9aeb8a
parent 229502 2b8d8208e2c59c1da8bc885b5f3b3453d8336769
child 229504 acb012724bf0b36cb9be64c91b7dd00b94dfd36f
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
bugs1117141
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 1117141 - Part 2 of 2 - Refactor notifications and remove the DownloadsDataItem object. r=mak
browser/components/downloads/DownloadsCommon.jsm
browser/components/downloads/content/allDownloadsViewOverlay.js
browser/components/downloads/content/downloads.js
browser/components/downloads/content/downloadsViewCommon.js
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -3,38 +3,32 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = [
   "DownloadsCommon",
-  "DownloadsDataItem",
 ];
 
 /**
  * Handles the Downloads panel shared methods and data access.
  *
  * This file includes the following constructors and global objects:
  *
  * DownloadsCommon
  * This object is exposed directly to the consumers of this JavaScript module,
  * and provides shared methods for all the instances of the user interface.
  *
  * DownloadsData
  * Retrieves the list of past and completed downloads from the underlying
- * Download Manager data, and provides asynchronous notifications allowing
+ * Downloads API data, and provides asynchronous notifications allowing
  * to build a consistent view of the available data.
  *
- * DownloadsDataItem
- * Represents a single item in the list of downloads.  This object wraps the
- * Download object from the JavaScript API for downloads.  A specialized version
- * of this object is implemented in the Places front-end view.
- *
  * DownloadsIndicatorData
  * This object registers itself with DownloadsData as a view, and transforms the
  * notifications it receives into overall status data, that is then broadcast to
  * the registered download status indicators.
  */
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Globals
@@ -345,55 +339,54 @@ this.DownloadsCommon = {
       return download.totalBytes;
     } else {
       // The download final size and progress percentage is unknown.
       return -1;
     }
   },
 
   /**
-   * Given an iterable collection of DownloadDataItems, generates and returns
+   * Given an iterable collection of Download objects, generates and returns
    * statistics about that collection.
    *
-   * @param aDataItems An iterable collection of DownloadDataItems.
+   * @param downloads An iterable collection of Download objects.
    *
    * @return Object whose properties are the generated statistics. Currently,
    *         we return the following properties:
    *
    *         numActive       : The total number of downloads.
    *         numPaused       : The total number of paused downloads.
    *         numScanning     : The total number of downloads being scanned.
    *         numDownloading  : The total number of downloads being downloaded.
    *         totalSize       : The total size of all downloads once completed.
    *         totalTransferred: The total amount of transferred data for these
    *                           downloads.
    *         slowestSpeed    : The slowest download rate.
    *         rawTimeLeft     : The estimated time left for the downloads to
    *                           complete.
    *         percentComplete : The percentage of bytes successfully downloaded.
    */
-  summarizeDownloads(aDataItems) {
+  summarizeDownloads(downloads) {
     let summary = {
       numActive: 0,
       numPaused: 0,
       numScanning: 0,
       numDownloading: 0,
       totalSize: 0,
       totalTransferred: 0,
       // slowestSpeed is Infinity so that we can use Math.min to
       // find the slowest speed. We'll set this to 0 afterwards if
       // it's still at Infinity by the time we're done iterating all
-      // dataItems.
+      // download.
       slowestSpeed: Infinity,
       rawTimeLeft: -1,
       percentComplete: -1
     }
 
-    for (let dataItem of aDataItems) {
-      let download = dataItem.download;
+    for (let download of downloads) {
       let state = DownloadsCommon.stateOfDownload(download);
       let maxBytes = DownloadsCommon.maxBytesOfDownload(download);
 
       summary.numActive++;
       switch (state) {
         case nsIDM.DOWNLOAD_PAUSED:
           summary.numPaused++;
           break;
@@ -654,26 +647,22 @@ XPCOMUtils.defineLazyGetter(DownloadsCom
  *
  * Note that DownloadsData and PrivateDownloadsData are two equivalent singleton
  * objects, one accessing non-private downloads, and the other accessing private
  * ones.
  */
 function DownloadsDataCtor(aPrivate) {
   this._isPrivate = aPrivate;
 
-  // Contains all the available DownloadsDataItem objects.
-  this.dataItems = new Set();
+  // Contains all the available Download objects and their integer state.
   this.oldDownloadStates = new Map();
 
   // Array of view objects that should be notified when the available download
   // data changes.
   this._views = [];
-
-  // Maps Download objects to DownloadDataItem objects.
-  this._downloadToDataItemMap = new Map();
 }
 
 DownloadsDataCtor.prototype = {
   /**
    * Starts receiving events for current downloads.
    */
   initializeDataLink() {
     if (!this._dataLinkInitialized) {
@@ -681,21 +670,26 @@ DownloadsDataCtor.prototype = {
                                                           : Downloads.PUBLIC);
       promiseList.then(list => list.addView(this)).then(null, Cu.reportError);
       this._dataLinkInitialized = true;
     }
   },
   _dataLinkInitialized: false,
 
   /**
+   * Iterator for all the available Download objects. This is empty until the
+   * data has been loaded using the JavaScript API for downloads.
+   */
+  get downloads() this.oldDownloadStates.keys(),
+
+  /**
    * True if there are finished downloads that can be removed from the list.
    */
   get canRemoveFinished() {
-    for (let dataItem of this.dataItems) {
-      let download = dataItem.download;
+    for (let download of this.oldDownloadStates.keys()) {
       // Stopped, paused, and failed downloads with partial data are removed.
       if (download.stopped && !(download.canceled && download.hasPartialData)) {
         return true;
       }
     }
     return false;
   },
 
@@ -707,109 +701,99 @@ DownloadsDataCtor.prototype = {
                                                         : Downloads.PUBLIC);
     promiseList.then(list => list.removeFinished())
                .then(null, Cu.reportError);
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// Integration with the asynchronous Downloads back-end
 
-  onDownloadAdded(aDownload) {
-    let dataItem = new DownloadsDataItem(aDownload);
-    this._downloadToDataItemMap.set(aDownload, dataItem);
-    this.dataItems.add(dataItem);
-    this.oldDownloadStates.set(aDownload,
-                               DownloadsCommon.stateOfDownload(aDownload));
+  onDownloadAdded(download) {
+    // Download objects do not store the end time of downloads, as the Downloads
+    // API does not need to persist this information for all platforms. Once a
+    // download terminates on a Desktop browser, it becomes a history download,
+    // for which the end time is stored differently, as a Places annotation.
+    download.endTime = Date.now();
+
+    this.oldDownloadStates.set(download,
+                               DownloadsCommon.stateOfDownload(download));
 
     for (let view of this._views) {
-      view.onDataItemAdded(dataItem, true);
+      view.onDownloadAdded(download, true);
     }
   },
 
-  onDownloadChanged(aDownload) {
-    let aDataItem = this._downloadToDataItemMap.get(aDownload);
-    if (!aDataItem) {
-      Cu.reportError("Download doesn't exist.");
-      return;
-    }
-
-    let oldState = this.oldDownloadStates.get(aDownload);
-    let newState = DownloadsCommon.stateOfDownload(aDownload);
-    this.oldDownloadStates.set(aDownload, newState);
+  onDownloadChanged(download) {
+    let oldState = this.oldDownloadStates.get(download);
+    let newState = DownloadsCommon.stateOfDownload(download);
+    this.oldDownloadStates.set(download, newState);
 
     if (oldState != newState) {
-      if (aDownload.succeeded ||
-          (aDownload.canceled && !aDownload.hasPartialData) ||
-          aDownload.error) {
+      if (download.succeeded ||
+          (download.canceled && !download.hasPartialData) ||
+          download.error) {
         // Store the end time that may be displayed by the views.
-        aDownload.endTime = Date.now();
+        download.endTime = Date.now();
 
         // This state transition code should actually be located in a Downloads
         // API module (bug 941009).  Moreover, the fact that state is stored as
         // annotations should be ideally hidden behind methods of
         // nsIDownloadHistory (bug 830415).
         if (!this._isPrivate) {
           try {
             let downloadMetaData = {
-              state: DownloadsCommon.stateOfDownload(aDownload),
-              endTime: aDownload.endTime,
+              state: DownloadsCommon.stateOfDownload(download),
+              endTime: download.endTime,
             };
-            if (aDownload.succeeded ||
-                (aDownload.error && aDownload.error.becauseBlocked)) {
+            if (download.succeeded ||
+                (download.error && download.error.becauseBlocked)) {
               downloadMetaData.fileSize =
-                DownloadsCommon.maxBytesOfDownload(aDataItem.download);
+                DownloadsCommon.maxBytesOfDownload(download);
             }
   
             PlacesUtils.annotations.setPageAnnotation(
-                          NetUtil.newURI(aDownload.source.url),
+                          NetUtil.newURI(download.source.url),
                           "downloads/metaData",
                           JSON.stringify(downloadMetaData), 0,
                           PlacesUtils.annotations.EXPIRE_WITH_HISTORY);
           } catch (ex) {
             Cu.reportError(ex);
           }
         }
       }
 
       for (let view of this._views) {
         try {
-          view.onDataItemStateChanged(aDataItem);
+          view.onDownloadStateChanged(download);
         } catch (ex) {
           Cu.reportError(ex);
         }
       }
 
-      if (aDownload.succeeded ||
-          (aDownload.error && aDownload.error.becauseBlocked)) {
+      if (download.succeeded ||
+          (download.error && download.error.becauseBlocked)) {
         this._notifyDownloadEvent("finish");
       }
     }
 
-    if (!aDownload.newDownloadNotified) {
-      aDownload.newDownloadNotified = true;
+    if (!download.newDownloadNotified) {
+      download.newDownloadNotified = true;
       this._notifyDownloadEvent("start");
     }
 
     for (let view of this._views) {
-      view.onDataItemChanged(aDataItem);
+      view.onDownloadChanged(download);
     }
   },
 
-  onDownloadRemoved(aDownload) {
-    let dataItem = this._downloadToDataItemMap.get(aDownload);
-    if (!dataItem) {
-      Cu.reportError("Download doesn't exist.");
-      return;
-    }
+  onDownloadRemoved(download) {
+    this.oldDownloadStates.delete(download);
 
-    this._downloadToDataItemMap.delete(aDownload);
-    this.dataItems.delete(dataItem);
-    this.oldDownloadStates.delete(aDownload);
     for (let view of this._views) {
-      view.onDataItemRemoved(dataItem);
+      view.onDownloadRemoved(download);
     }
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// Registration of views
 
   /**
    * Adds an object to be notified when the available download data changes.
@@ -844,19 +828,19 @@ DownloadsDataCtor.prototype = {
    *        DownloadsView object to be initialized.
    */
   _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.download.startTime - a.download.startTime);
-    loadedItemsArray.forEach(dataItem => aView.onDataItemAdded(dataItem, false));
+    let downloadsArray = [...this.oldDownloadStates.keys()];
+    downloadsArray.sort((a, b) => b.startTime - a.startTime);
+    downloadsArray.forEach(download => aView.onDownloadAdded(download, false));
 
     // Notify the view that all data is available.
     aView.onDataLoadCompleted();
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// Notifications sent to the most recent browser window only
 
@@ -909,116 +893,16 @@ XPCOMUtils.defineLazyGetter(this, "Priva
   return new DownloadsDataCtor(true);
 });
 
 XPCOMUtils.defineLazyGetter(this, "DownloadsData", function() {
   return new DownloadsDataCtor(false);
 });
 
 ////////////////////////////////////////////////////////////////////////////////
-//// DownloadsDataItem
-
-/**
- * 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.download.endTime = Date.now();
-}
-
-DownloadsDataItem.prototype = {
-  get state() DownloadsCommon.stateOfDownload(this.download),
-
-  /**
-   * Indicates whether the download is proceeding normally, and not finished
-   * yet.  This includes paused downloads.  When this property is true, the
-   * "progress" property represents the current progress of the download.
-   */
-  get inProgress() {
-    return [
-      nsIDM.DOWNLOAD_NOTSTARTED,
-      nsIDM.DOWNLOAD_QUEUED,
-      nsIDM.DOWNLOAD_DOWNLOADING,
-      nsIDM.DOWNLOAD_PAUSED,
-      nsIDM.DOWNLOAD_SCANNING,
-    ].indexOf(this.state) != -1;
-  },
-
-  /**
-   * This is true during the initial phases of a download, before the actual
-   * download of data bytes starts.
-   */
-  get starting() {
-    return this.state == nsIDM.DOWNLOAD_NOTSTARTED ||
-           this.state == nsIDM.DOWNLOAD_QUEUED;
-  },
-
-  /**
-   * Indicates whether the download is paused.
-   */
-  get paused() {
-    return this.state == nsIDM.DOWNLOAD_PAUSED;
-  },
-
-  /**
-   * Indicates whether the download is in a final state, either because it
-   * completed successfully or because it was blocked.
-   */
-  get done() {
-    return [
-      nsIDM.DOWNLOAD_FINISHED,
-      nsIDM.DOWNLOAD_BLOCKED_PARENTAL,
-      nsIDM.DOWNLOAD_BLOCKED_POLICY,
-      nsIDM.DOWNLOAD_DIRTY,
-    ].indexOf(this.state) != -1;
-  },
-
-  /**
-   * 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() {
-    // 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 new FileUtils.File(this.download.target.path + kPartialDownloadSuffix);
-  },
-};
-
-////////////////////////////////////////////////////////////////////////////////
 //// DownloadsViewPrototype
 
 /**
  * A prototype for an object that registers itself with DownloadsData as soon
  * as a view is registered with it.
  */
 const DownloadsViewPrototype = {
   //////////////////////////////////////////////////////////////////////////////
@@ -1118,65 +1002,67 @@ const DownloadsViewPrototype = {
   onDataLoadCompleted() {
     this._loading = false;
   },
 
   /**
    * Called when a new download data item is available, either during the
    * asynchronous data load or when a new download is started.
    *
-   * @param aDataItem
-   *        DownloadsDataItem object that was just added.
-   * @param aNewest
+   * @param download
+   *        Download object that was just added.
+   * @param newest
    *        When true, indicates that this item is the most recent and should be
    *        added in the topmost position.  This happens when a new download is
    *        started.  When false, indicates that the item is the least recent
    *        with regard to the items that have been already added. The latter
    *        generally happens during the asynchronous data load.
    *
    * @note Subclasses should override this.
    */
-  onDataItemAdded(aDataItem, aNewest) {
+  onDownloadAdded(download, newest) {
+    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+  },
+
+  /**
+   * Called when the overall state of a Download has changed. In particular,
+   * this is called only once when the download succeeds or is blocked
+   * permanently, and is never called if only the current progress changed.
+   *
+   * The onDownloadChanged notification will always be sent afterwards.
+   *
+   * @note Subclasses should override this.
+   */
+  onDownloadStateChanged(download) {
+    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+  },
+
+  /**
+   * Called every time any state property of a Download may have changed,
+   * including progress properties.
+   *
+   * Note that progress notification changes are throttled at the Downloads.jsm
+   * API level, and there is no throttling mechanism in the front-end.
+   *
+   * @note Subclasses should override this.
+   */
+  onDownloadChanged(download) {
     throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
   },
 
   /**
    * Called when a data item is removed, ensures that the widget associated with
    * the view item is removed from the user interface.
    *
-   * @param aDataItem
-   *        DownloadsDataItem object that is being removed.
+   * @param download
+   *        Download object that is being removed.
    *
    * @note Subclasses should override this.
    */
-  onDataItemRemoved(aDataItem) {
-    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
-  },
-
-  /**
-   * Called when the "state" property of a DownloadsDataItem has changed.
-   *
-   * The onDataItemChanged notification will be sent afterwards.
-   *
-   * @note Subclasses should override this.
-   */
-  onDataItemStateChanged(aDataItem) {
-    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
-  },
-
-  /**
-   * Called every time any state property of a DownloadsDataItem may have
-   * changed, including progress properties and the "state" property.
-   *
-   * Note that progress notification changes are throttled at the Downloads.jsm
-   * API level, and there is no throttling mechanism in the front-end.
-   *
-   * @note Subclasses should override this.
-   */
-  onDataItemChanged(aDataItem) {
+  onDownloadRemoved(download) {
     throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
   },
 
   /**
    * Private function used to refresh the internal properties being sent to
    * each registered view.
    *
    * @note Subclasses should override this.
@@ -1227,69 +1113,42 @@ DownloadsIndicatorDataCtor.prototype = {
     if (this._views.length == 0) {
       this._itemCount = 0;
     }
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// Callback functions from DownloadsData
 
-  /**
-   * Called after data loading finished.
-   */
   onDataLoadCompleted() {
     DownloadsViewPrototype.onDataLoadCompleted.call(this);
     this._updateViews();
   },
 
-  /**
-   * Called when a new download data item is available, either during the
-   * asynchronous data load or when a new download is started.
-   *
-   * @param aDataItem
-   *        DownloadsDataItem object that was just added.
-   * @param aNewest
-   *        When true, indicates that this item is the most recent and should be
-   *        added in the topmost position.  This happens when a new download is
-   *        started.  When false, indicates that the item is the least recent
-   *        with regard to the items that have been already added. The latter
-   *        generally happens during the asynchronous data load.
-   */
-  onDataItemAdded(aDataItem, aNewest) {
+  onDownloadAdded(download, newest) {
     this._itemCount++;
     this._updateViews();
   },
 
-  /**
-   * Called when a data item is removed, ensures that the widget associated with
-   * the view item is removed from the user interface.
-   *
-   * @param aDataItem
-   *        DownloadsDataItem object that is being removed.
-   */
-  onDataItemRemoved(aDataItem) {
-    this._itemCount--;
-    this._updateViews();
-  },
-
-  // DownloadsView
-  onDataItemStateChanged(aDataItem) {
-    let download = aDataItem.download;
-
+  onDownloadStateChanged(download) {
     if (download.succeeded || download.error) {
       this.attention = true;
     }
 
     // Since the state of a download changed, reset the estimated time left.
     this._lastRawTimeLeft = -1;
     this._lastTimeLeft = -1;
   },
 
-  // DownloadsView
-  onDataItemChanged() {
+  onDownloadChanged(download) {
+    this._updateViews();
+  },
+
+  onDownloadRemoved(download) {
+    this._itemCount--;
     this._updateViews();
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// Propagation of properties to our views
 
   // The following properties are updated by _refreshProperties and are then
   // propagated to the views.  See _refreshProperties for details.
@@ -1367,41 +1226,37 @@ DownloadsIndicatorDataCtor.prototype = {
    * Last number of seconds estimated until all in-progress downloads with a
    * known size and speed will finish.  This value is stored to allow smoothing
    * in case of small variations.  This is set to -1 if the previous value is
    * unknown.
    */
   _lastTimeLeft: -1,
 
   /**
-   * A generator function for the dataItems that this summary is currently
+   * A generator function for the Download objects this summary is currently
    * interested in. This generator is passed off to summarizeDownloads in order
-   * to generate statistics about the dataItems we care about - in this case,
-   * it's all dataItems for active downloads.
+   * to generate statistics about the downloads we care about - in this case,
+   * it's all active downloads.
    */
-  _activeDataItems() {
-    let dataItems = this._isPrivate ? PrivateDownloadsData.dataItems
-                                    : DownloadsData.dataItems;
-    for (let dataItem of dataItems) {
-      if (!dataItem) {
-        continue;
-      }
-      let download = dataItem.download;
+  _activeDownloads() {
+    let downloads = this._isPrivate ? PrivateDownloadsData.downloads
+                                    : DownloadsData.downloads;
+    for (let download of downloads) {
       if (!download.stopped || (download.canceled && download.hasPartialData)) {
-        yield dataItem;
+        yield download;
       }
     }
   },
 
   /**
    * Computes aggregate values based on the current state of downloads.
    */
   _refreshProperties() {
     let summary =
-      DownloadsCommon.summarizeDownloads(this._activeDataItems());
+      DownloadsCommon.summarizeDownloads(this._activeDownloads());
 
     // Determine if the indicator should be shown or get attention.
     this._hasDownloads = (this._itemCount > 0);
 
     // If all downloads are paused, show the progress indicator as paused.
     this._paused = summary.numActive > 0 &&
                    summary.numActive == summary.numPaused;
 
@@ -1452,17 +1307,17 @@ XPCOMUtils.defineLazyGetter(this, "Downl
  */
 function DownloadsSummaryData(aIsPrivate, aNumToExclude) {
   this._numToExclude = aNumToExclude;
   // Since we can have multiple instances of DownloadsSummaryData, we
   // override these values from the prototype so that each instance can be
   // completely separated from one another.
   this._loading = false;
 
-  this._dataItems = [];
+  this._downloads = [];
 
   // Floating point value indicating the last number of seconds estimated until
   // the longest download will finish.  We need to store this value so that we
   // don't continuously apply smoothing if the actual download state has not
   // changed.  This is set to -1 if the previous value is unknown.
   this._lastRawTimeLeft = -1;
 
   // Last number of seconds estimated until all in-progress downloads with a
@@ -1491,57 +1346,55 @@ DownloadsSummaryData.prototype = {
    *
    * @param aView
    *        DownloadsSummary view to be removed.
    */
   removeView(aView) {
     DownloadsViewPrototype.removeView.call(this, aView);
 
     if (this._views.length == 0) {
-      // Clear out our collection of DownloadDataItems. If we ever have
+      // Clear out our collection of Download objects. If we ever have
       // another view registered with us, this will get re-populated.
-      this._dataItems = [];
+      this._downloads = [];
     }
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// Callback functions from DownloadsData - see the documentation in
   //// DownloadsViewPrototype for more information on what these functions
   //// are used for.
 
   onDataLoadCompleted() {
     DownloadsViewPrototype.onDataLoadCompleted.call(this);
     this._updateViews();
   },
 
-  onDataItemAdded(aDataItem, aNewest) {
-    if (aNewest) {
-      this._dataItems.unshift(aDataItem);
+  onDownloadAdded(download, newest) {
+    if (newest) {
+      this._downloads.unshift(download);
     } else {
-      this._dataItems.push(aDataItem);
+      this._downloads.push(download);
     }
 
     this._updateViews();
   },
 
-  onDataItemRemoved(aDataItem) {
-    let itemIndex = this._dataItems.indexOf(aDataItem);
-    this._dataItems.splice(itemIndex, 1);
-    this._updateViews();
-  },
-
-  // DownloadsView
-  onDataItemStateChanged() {
+  onDownloadStateChanged() {
     // Since the state of a download changed, reset the estimated time left.
     this._lastRawTimeLeft = -1;
     this._lastTimeLeft = -1;
   },
 
-  // DownloadsView
-  onDataItemChanged() {
+  onDownloadChanged() {
+    this._updateViews();
+  },
+
+  onDownloadRemoved(download) {
+    let itemIndex = this._downloads.indexOf(download);
+    this._downloads.splice(itemIndex, 1);
     this._updateViews();
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// Propagation of properties to our views
 
   /**
    * Computes aggregate values and propagates the changes to our views.
@@ -1568,37 +1421,37 @@ DownloadsSummaryData.prototype = {
     aView.description = this._description;
     aView.details = this._details;
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// Property updating based on current download status
 
   /**
-   * A generator function for the dataItems that this summary is currently
+   * A generator function for the Download objects this summary is currently
    * interested in. This generator is passed off to summarizeDownloads in order
-   * to generate statistics about the dataItems we care about - in this case,
-   * it's the dataItems in this._dataItems after the first few to exclude,
+   * to generate statistics about the downloads we care about - in this case,
+   * it's the downloads in this._downloads after the first few to exclude,
    * which was set when constructing this DownloadsSummaryData instance.
    */
-  _dataItemsForSummary() {
-    if (this._dataItems.length > 0) {
-      for (let i = this._numToExclude; i < this._dataItems.length; ++i) {
-        yield this._dataItems[i];
+  _downloadsForSummary() {
+    if (this._downloads.length > 0) {
+      for (let i = this._numToExclude; i < this._downloads.length; ++i) {
+        yield this._downloads[i];
       }
     }
   },
 
   /**
    * Computes aggregate values based on the current state of downloads.
    */
   _refreshProperties() {
     // Pre-load summary with default values.
     let summary =
-      DownloadsCommon.summarizeDownloads(this._dataItemsForSummary());
+      DownloadsCommon.summarizeDownloads(this._downloadsForSummary());
 
     this._description = DownloadsCommon.strings
                                        .otherDownloads2(summary.numActive);
     this._percentComplete = summary.percentComplete;
 
     // If all downloads are paused, show the progress indicator as paused.
     this._showingProgress = summary.numDownloading > 0 ||
                             summary.numPaused > 0;
--- a/browser/components/downloads/content/allDownloadsViewOverlay.js
+++ b/browser/components/downloads/content/allDownloadsViewOverlay.js
@@ -1,14 +1,12 @@
 /* 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/. */
 
-XPCOMUtils.defineLazyModuleGetter(this, "DownloadsDataItem",
-                                  "resource:///modules/DownloadsCommon.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
                                   "resource:///modules/RecentWindow.jsm");
 
 const nsIDM = Ci.nsIDownloadManager;
 
 const DESTINATION_FILE_URI_ANNO  = "downloads/destinationFileURI";
 const DOWNLOAD_META_DATA_ANNO    = "downloads/metaData";
 
@@ -123,62 +121,47 @@ HistoryDownload.prototype = {
     let leafName = this.target.path ? OS.Path.basename(this.target.path) : null;
     DownloadURL(this.source.url, leafName, initiatingDoc);
 
     return Promise.resolve();
   },
 };
 
 /**
- * Represents a download from the browser history. It uses the same interface as
- * the DownloadsDataItem object.
- *
- * @param aPlacesNode
- *        The Places node for the history download.
- */
-function DownloadsHistoryDataItem(aPlacesNode) {
-  this.download = new HistoryDownload(aPlacesNode);
-}
-
-DownloadsHistoryDataItem.prototype = {
-  __proto__: DownloadsDataItem.prototype,
-};
-
-/**
  * A download element shell is responsible for handling the commands and the
  * displayed data for a single download view element.
  *
  * The shell may contain a session download, a history download, or both.  When
  * both a history and a current download are present, the current download gets
  * priority and its information is displayed.
  *
  * On construction, a new richlistitem is created, and can be accessed through
  * the |element| getter. The shell doesn't insert the item in a richlistbox, the
  * caller must do it and remove the element when it's no longer needed.
  *
  * The caller is also responsible for forwarding status notifications for
  * session downloads, calling the onStateChanged and onChanged methods.
  *
- * @param [optional] aSessionDataItem
- *        The session download, required if aHistoryDataItem is not set.
- * @param [optional] aHistoryDataItem
- *        The history download, required if aSessionDataItem is not set.
+ * @param [optional] aSessionDownload
+ *        The session download, required if aHistoryDownload is not set.
+ * @param [optional] aHistoryDownload
+ *        The history download, required if aSessionDownload is not set.
  */
-function HistoryDownloadElementShell(aSessionDataItem, aHistoryDataItem) {
+function HistoryDownloadElementShell(aSessionDownload, aHistoryDownload) {
   this.element = document.createElement("richlistitem");
   this.element._shell = this;
 
   this.element.classList.add("download");
   this.element.classList.add("download-state");
 
-  if (aSessionDataItem) {
-    this.sessionDataItem = aSessionDataItem;
+  if (aSessionDownload) {
+    this.sessionDownload = aSessionDownload;
   }
-  if (aHistoryDataItem) {
-    this.historyDataItem = aHistoryDataItem;
+  if (aHistoryDownload) {
+    this.historyDownload = aHistoryDownload;
   }
 }
 
 HistoryDownloadElementShell.prototype = {
   __proto__: DownloadElementShell.prototype,
 
   /**
    * Manages the "active" state of the shell.  By default all the shells without
@@ -191,50 +174,50 @@ HistoryDownloadElementShell.prototype = 
       this._active = true;
       this.element.setAttribute("active", true);
       this._updateUI();
     }
   },
   get active() !!this._active,
 
   /**
-   * DownloadsDataItem or DownloadsHistoryDataItem object to use for displaying
-   * information and for executing commands in the user interface.
+   * Overrides the base getter to return the Download or HistoryDownload object
+   * for displaying information and executing commands in the user interface.
    */
-  get dataItem() this._sessionDataItem || this._historyDataItem,
+  get download() this._sessionDownload || this._historyDownload,
 
-  _sessionDataItem: null,
-  get sessionDataItem() this._sessionDataItem,
-  set sessionDataItem(aValue) {
-    if (this._sessionDataItem != aValue) {
-      if (!aValue && !this._historyDataItem) {
-        throw new Error("Should always have either a dataItem or a historyDataItem");
+  _sessionDownload: null,
+  get sessionDownload() this._sessionDownload,
+  set sessionDownload(aValue) {
+    if (this._sessionDownload != aValue) {
+      if (!aValue && !this._historyDownload) {
+        throw new Error("Should always have either a Download or a HistoryDownload");
       }
 
-      this._sessionDataItem = aValue;
+      this._sessionDownload = aValue;
 
       this.ensureActive();
       this._updateUI();
     }
     return aValue;
   },
 
-  _historyDataItem: null,
-  get historyDataItem() this._historyDataItem,
-  set historyDataItem(aValue) {
-    if (this._historyDataItem != aValue) {
-      if (!aValue && !this._sessionDataItem) {
-        throw new Error("Should always have either a dataItem or a historyDataItem");
+  _historyDownload: null,
+  get historyDownload() this._historyDownload,
+  set historyDownload(aValue) {
+    if (this._historyDownload != aValue) {
+      if (!aValue && !this._sessionDownload) {
+        throw new Error("Should always have either a Download or a HistoryDownload");
       }
 
-      this._historyDataItem = aValue;
+      this._historyDownload = aValue;
 
       // We don't need to update the UI if we had a session data item, because
       // the places information isn't used in this case.
-      if (!this._sessionDataItem) {
+      if (!this._sessionDownload) {
         this._updateUI();
       }
     }
     return aValue;
   },
 
   _updateUI() {
     // There is nothing to do if the item has always been invisible.
@@ -283,30 +266,30 @@ HistoryDownloadElementShell.prototype = 
     if (!this.active && aCommand != "cmd_delete") {
       return false;
     }
     switch (aCommand) {
       case "downloadsCmd_open":
         // 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._sessionDataItem && !this.download.succeeded) {
+        if (this._sessionDownload && !this.download.succeeded) {
           return false;
         }
 
         if (this._targetFileChecked) {
           return this._targetFileExists;
         }
 
         // If the target file information is not yet fetched,
         // temporarily assume that the file is in place.
         return this.download.succeeded;
       case "downloadsCmd_show":
         // TODO: Bug 827010 - Handle part-file asynchronously.
-        if (this._sessionDataItem && this.download.target.partFilePath) {
+        if (this._sessionDownload && this.download.target.partFilePath) {
           let partFile = new FileUtils.File(this.download.target.partFilePath);
           if (partFile.exists()) {
             return true;
           }
         }
 
         if (this._targetFileChecked) {
           return this._targetFileExists;
@@ -320,17 +303,17 @@ HistoryDownloadElementShell.prototype = 
       case "downloadsCmd_retry":
         return this.download.canceled || this.download.error;
       case "downloadsCmd_openReferrer":
         return !!this.download.source.referrer;
       case "cmd_delete":
         // We don't want in-progress downloads to be removed accidentally.
         return this.download.stopped;
       case "downloadsCmd_cancel":
-        return !!this._sessionDataItem;
+        return !!this._sessionDownload;
     }
     return false;
   },
 
   /* nsIController */
   doCommand(aCommand) {
     switch (aCommand) {
       case "downloadsCmd_open": {
@@ -348,23 +331,23 @@ HistoryDownloadElementShell.prototype = 
         break;
       }
       case "downloadsCmd_cancel": {
         this.download.cancel().catch(() => {});
         this.download.removePartialData().catch(Cu.reportError);
         break;
       }
       case "cmd_delete": {
-        if (this._sessionDataItem) {
+        if (this._sessionDownload) {
           Downloads.getList(Downloads.ALL)
                    .then(list => list.remove(this.download))
                    .then(() => this.download.finalize(true))
                    .catch(Cu.reportError);
         }
-        if (this._historyDataItem) {
+        if (this._historyDownload) {
           let uri = NetUtil.newURI(this.download.source.url);
           PlacesUtils.bhistory.removePage(uri);
         }
         break;
       }
       case "downloadsCmd_retry": {
         // Errors when retrying are already reported as download failures.
         this.download.start().catch(() => {});
@@ -480,17 +463,17 @@ function DownloadsPlacesView(aRichListBo
   this._richlistbox = aRichListBox;
   this._richlistbox._placesView = this;
   window.controllers.insertControllerAt(0, this);
 
   // Map download URLs to download element shells regardless of their type
   this._downloadElementsShellsForURI = new Map();
 
   // Map download data items to their element shells.
-  this._viewItemsForDataItems = new WeakMap();
+  this._viewItemsForDownloads = new WeakMap();
 
   // Points to the last session download element. We keep track of this
   // in order to keep all session downloads above past downloads.
   this._lastSessionDownloadElement = null;
 
   this._searchTerm = "";
 
   this._active = aActive;
@@ -626,34 +609,31 @@ DownloadsPlacesView.prototype = {
    *     elements for the same download url. If there are, then we just reset
    *     their places node. Otherwise we add a new download element.
    *  2. If the given data is a data item, we first check if there's a history
    *     download in the list that is not associated with a data item. If we
    *     found one, we use it for the data item as well and reposition it
    *     alongside the other session downloads. If we don't, then we go ahead
    *     and create a new element for the download.
    *
-   * @param aDataItem
-   *        The data item of a session download. Set to null for history
-   *        downloads data.
+   * @param [optional] sessionDownload
+   *        A Download object, or null for history downloads.
    * @param [optional] aPlacesNode
-   *        The places node for a history download. Required if there's no data
-   *        item.
+   *        The Places node for a history download, or null for session downloads.
    * @param [optional] aNewest
-   *        @see onDataItemAdded. Ignored for history downloads.
+   *        @see onDownloadAdded. Ignored for history downloads.
    * @param [optional] aDocumentFragment
    *        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,
+  _addDownloadData(sessionDownload, aPlacesNode, aNewest = false,
                    aDocumentFragment = null) {
-    let sessionDownload = aDataItem && aDataItem.download;
     let downloadURI = aPlacesNode ? aPlacesNode.uri
                                   : sessionDownload.source.url;
     let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI);
     if (!shellsForURI) {
       shellsForURI = new Set();
       this._downloadElementsShellsForURI.set(downloadURI, shellsForURI);
     }
 
@@ -669,86 +649,87 @@ DownloadsPlacesView.prototype = {
     //    no data item). In this case, we update this shell and move it
     //    if necessary
     // 2) There are multiple shells, indicating multiple downloads for
     //    the same download uri are running. In this case we create
     //    another shell for the download (so we have one shell for each data
     //    item).
     //
     // Note: If a cancelled session download is already in the list, and the
-    // download is retired, onDataItemAdded is called again for the same
+    // download is retried, onDownloadAdded is called again for the same
     // data item. Thus, we also check that we make sure we don't have a view item
     // already.
     if (!shouldCreateShell &&
-        aDataItem && !this._viewItemsForDataItems.has(aDataItem)) {
+        sessionDownload && !this._viewItemsForDownloads.has(sessionDownload)) {
       // If there's a past-download-only shell for this download-uri with no
       // associated data item, use it for the new data item. Otherwise, go ahead
       // and create another shell.
       shouldCreateShell = true;
       for (let shell of shellsForURI) {
-        if (!shell.sessionDataItem) {
+        if (!shell.sessionDownload) {
           shouldCreateShell = false;
-          shell.sessionDataItem = aDataItem;
+          shell.sessionDownload = sessionDownload;
           newOrUpdatedShell = shell;
-          this._viewItemsForDataItems.set(aDataItem, shell);
+          this._viewItemsForDownloads.set(sessionDownload, shell);
           break;
         }
       }
     }
 
     if (shouldCreateShell) {
       // If we are adding a new history download here, it means there is no
       // associated session download, thus we must read the Places metadata,
       // because it will not be obscured by the session download.
-      let historyDataItem = null;
+      let historyDownload = null;
       if (aPlacesNode) {
         let metaData = this._getCachedPlacesMetaDataFor(aPlacesNode.uri);
-        historyDataItem = new DownloadsHistoryDataItem(aPlacesNode);
-        historyDataItem.download.updateFromMetaData(metaData);
+        historyDownload = new HistoryDownload(aPlacesNode);
+        historyDownload.updateFromMetaData(metaData);
       }
-      let shell = new HistoryDownloadElementShell(aDataItem, historyDataItem);
+      let shell = new HistoryDownloadElementShell(sessionDownload,
+                                                  historyDownload);
       shell.element._placesNode = aPlacesNode;
       newOrUpdatedShell = shell;
       shellsForURI.add(shell);
-      if (aDataItem) {
-        this._viewItemsForDataItems.set(aDataItem, shell);
+      if (sessionDownload) {
+        this._viewItemsForDownloads.set(sessionDownload, shell);
       }
     } else if (aPlacesNode) {
       // We are updating information for a history download for which we have
       // at least one download element shell already. There are two cases:
       // 1) There are one or more download element shells for this source URI,
       //    each with an associated session download. We update the Places node
       //    because we may need it later, but we don't need to read the Places
       //    metadata until the last session download is removed.
       // 2) Occasionally, we may receive a duplicate notification for a history
       //    download with no associated session download. We have exactly one
       //    download element shell in this case, but the metdata cannot have
       //    changed, just the reference to the Places node object is different.
       // So, we update all the node references and keep the metadata intact.
       for (let shell of shellsForURI) {
-        if (!shell.historyDataItem) {
+        if (!shell.historyDownload) {
           // Create the element to host the metadata when needed.
-          shell.historyDataItem = new DownloadsHistoryDataItem(aPlacesNode);
+          shell.historyDownload = new HistoryDownload(aPlacesNode);
         }
         shell.element._placesNode = aPlacesNode;
       }
     }
 
     if (newOrUpdatedShell) {
       if (aNewest) {
         this._richlistbox.insertBefore(newOrUpdatedShell.element,
                                        this._richlistbox.firstChild);
         if (!this._lastSessionDownloadElement) {
           this._lastSessionDownloadElement = newOrUpdatedShell.element;
         }
         // Some operations like retrying an history download move an element to
         // the top of the richlistbox, along with other session downloads.
         // More generally, if a new download is added, should be made visible.
         this._richlistbox.ensureElementIsVisible(newOrUpdatedShell.element);
-      } else if (aDataItem) {
+      } else if (sessionDownload) {
         let before = this._lastSessionDownloadElement ?
           this._lastSessionDownloadElement.nextSibling : this._richlistbox.firstChild;
         this._richlistbox.insertBefore(newOrUpdatedShell.element, before);
         this._lastSessionDownloadElement = newOrUpdatedShell.element;
       } else {
         let appendTo = aDocumentFragment || this._richlistbox;
         appendTo.appendChild(newOrUpdatedShell.element);
       }
@@ -788,61 +769,60 @@ DownloadsPlacesView.prototype = {
     goUpdateCommand("downloadsCmd_clearDownloads");
   },
 
   _removeHistoryDownloadFromView(aPlacesNode) {
     let downloadURI = aPlacesNode.uri;
     let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI);
     if (shellsForURI) {
       for (let shell of shellsForURI) {
-        if (shell.sessionDataItem) {
-          shell.historyDataItem = null;
+        if (shell.sessionDownload) {
+          shell.historyDownload = null;
         } else {
           this._removeElement(shell.element);
           shellsForURI.delete(shell);
           if (shellsForURI.size == 0)
             this._downloadElementsShellsForURI.delete(downloadURI);
         }
       }
     }
   },
 
-  _removeSessionDownloadFromView(aDataItem) {
-    let download = aDataItem.download;
+  _removeSessionDownloadFromView(download) {
     let shells = this._downloadElementsShellsForURI
                      .get(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);
+    let shell = this._viewItemsForDownloads.get(download);
     if (!shells.has(shell)) {
       throw new Error("Missing download element shell in shells list for url");
     }
 
     // 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.historyDataItem) {
+    if (shells.size > 1 || !shell.historyDownload) {
       this._removeElement(shell.element);
       shells.delete(shell);
       if (shells.size == 0) {
         this._downloadElementsShellsForURI.delete(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.
-      let url = shell.historyDataItem.download.source.url;
+      let url = shell.historyDownload.source.url;
       let metaData = this._getPlacesMetaDataFor(url);
-      shell.historyDataItem.download.updateFromMetaData(metaData);
-      shell.sessionDataItem = null;
+      shell.historyDownload.updateFromMetaData(metaData);
+      shell.sessionDownload = null;
       // Move it below the session-download items;
       if (this._lastSessionDownloadElement == shell.element) {
         this._lastSessionDownloadElement = shell.element.previousSibling;
       } else {
         let before = this._lastSessionDownloadElement ?
           this._lastSessionDownloadElement.nextSibling : this._richlistbox.firstChild;
         this._richlistbox.insertBefore(shell.element, before);
       }
@@ -1108,32 +1088,30 @@ DownloadsPlacesView.prototype = {
     }
   },
 
   onDataLoadStarting() {},
   onDataLoadCompleted() {
     this._ensureInitialSelection();
   },
 
-  onDataItemAdded(aDataItem, aNewest) {
-    this._addDownloadData(aDataItem, null, aNewest);
-  },
-
-  onDataItemRemoved(aDataItem) {
-    this._removeSessionDownloadFromView(aDataItem);
+  onDownloadAdded(download, newest) {
+    this._addDownloadData(download, null, newest);
   },
 
-  // DownloadsView
-  onDataItemStateChanged(aDataItem) {
-    this._viewItemsForDataItems.get(aDataItem).onStateChanged();
+  onDownloadStateChanged(download) {
+    this._viewItemsForDownloads.get(download).onStateChanged();
   },
 
-  // DownloadsView
-  onDataItemChanged(aDataItem) {
-    this._viewItemsForDataItems.get(aDataItem).onChanged();
+  onDownloadChanged(download) {
+    this._viewItemsForDownloads.get(download).onChanged();
+  },
+
+  onDownloadRemoved(download) {
+    this._removeSessionDownloadFromView(download);
   },
 
   supportsCommand(aCommand) {
     if (DOWNLOAD_VIEW_SUPPORTED_COMMANDS.indexOf(aCommand) != -1) {
       // The clear-downloads command may be performed by the toolbar-button,
       // which can be focused on OS X.  Thus enable this command even if the
       // richlistbox is not focused.
       // For other commands, be prudent and disable them unless the richlistview
--- a/browser/components/downloads/content/downloads.js
+++ b/browser/components/downloads/content/downloads.js
@@ -644,37 +644,37 @@ const DownloadsView = {
   kItemCountLimit: 3,
 
   /**
    * Indicates whether we are still loading downloads data asynchronously.
    */
   loading: false,
 
   /**
-   * Ordered array of all DownloadsDataItem objects.  We need to keep this array
-   * because only a limited number of items are shown at once, and if an item
-   * that is currently visible is removed from the list, we might need to take
-   * another item from the array and make it appear at the bottom.
+   * Ordered array of all Download objects.  We need to keep this array because
+   * only a limited number of items are shown at once, and if an item that is
+   * currently visible is removed from the list, we might need to take another
+   * item from the array and make it appear at the bottom.
    */
-  _dataItems: [],
+  _downloads: [],
 
   /**
-   * Associates the visible DownloadsDataItem objects with their corresponding
+   * Associates the visible Download objects with their corresponding
    * DownloadsViewItem object.  There is a limited number of view items in the
    * panel at any given time.
    */
   _visibleViewItems: new Map(),
 
   /**
    * Called when the number of items in the list changes.
    */
   _itemCountChanged() {
     DownloadsCommon.log("The downloads item count has changed - we are tracking",
-                        this._dataItems.length, "downloads in total.");
-    let count = this._dataItems.length;
+                        this._downloads.length, "downloads in total.");
+    let count = this._downloads.length;
     let hiddenCount = count - this.kItemCountLimit;
 
     if (count > 0) {
       DownloadsCommon.log("Setting the panel's hasdownloads attribute to true.");
       DownloadsPanel.panel.setAttribute("hasdownloads", "true");
     } else {
       DownloadsCommon.log("Removing the panel's hasdownloads attribute.");
       DownloadsPanel.panel.removeAttribute("hasdownloads");
@@ -729,140 +729,138 @@ const DownloadsView = {
     // loaded.  This ensures that the interface is visible, if still required.
     DownloadsPanel.onViewLoadCompleted();
   },
 
   /**
    * Called when a new download data item is available, either during the
    * asynchronous data load or when a new download is started.
    *
-   * @param aDataItem
-   *        DownloadsDataItem object that was just added.
+   * @param aDownload
+   *        Download object that was just added.
    * @param aNewest
    *        When true, indicates that this item is the most recent and should be
    *        added in the topmost position.  This happens when a new download is
    *        started.  When false, indicates that the item is the least recent
    *        and should be appended.  The latter generally happens during the
    *        asynchronous data load.
    */
-  onDataItemAdded(aDataItem, aNewest) {
+  onDownloadAdded(download, aNewest) {
     DownloadsCommon.log("A new download data item was added - aNewest =",
                         aNewest);
 
     if (aNewest) {
-      this._dataItems.unshift(aDataItem);
+      this._downloads.unshift(download);
     } else {
-      this._dataItems.push(aDataItem);
+      this._downloads.push(download);
     }
 
-    let itemsNowOverflow = this._dataItems.length > this.kItemCountLimit;
+    let itemsNowOverflow = this._downloads.length > this.kItemCountLimit;
     if (aNewest || !itemsNowOverflow) {
       // The newly added item is visible in the panel and we must add the
       // corresponding element.  This is either because it is the first item, or
       // because it was added at the bottom but the list still doesn't overflow.
-      this._addViewItem(aDataItem, aNewest);
+      this._addViewItem(download, aNewest);
     }
     if (aNewest && itemsNowOverflow) {
       // If the list overflows, remove the last item from the panel to make room
       // for the new one that we just added at the top.
-      this._removeViewItem(this._dataItems[this.kItemCountLimit]);
+      this._removeViewItem(this._downloads[this.kItemCountLimit]);
     }
 
     // For better performance during batch loads, don't update the count for
     // every item, because the interface won't be visible until load finishes.
     if (!this.loading) {
       this._itemCountChanged();
     }
   },
 
-  /**
-   * Called when a data item is removed.  Ensures that the widget associated
-   * with the view item is removed from the user interface.
-   *
-   * @param aDataItem
-   *        DownloadsDataItem object that is being removed.
-   */
-  onDataItemRemoved(aDataItem) {
-    DownloadsCommon.log("A download data item was removed.");
-
-    let itemIndex = this._dataItems.indexOf(aDataItem);
-    this._dataItems.splice(itemIndex, 1);
-
-    if (itemIndex < this.kItemCountLimit) {
-      // The item to remove is visible in the panel.
-      this._removeViewItem(aDataItem);
-      if (this._dataItems.length >= this.kItemCountLimit) {
-        // Reinsert the next item into the panel.
-        this._addViewItem(this._dataItems[this.kItemCountLimit - 1], false);
-      }
-    }
-
-    this._itemCountChanged();
-  },
-
-  // DownloadsView
-  onDataItemStateChanged(aDataItem) {
-    let viewItem = this._visibleViewItems.get(aDataItem);
+  onDownloadStateChanged(download) {
+    let viewItem = this._visibleViewItems.get(download);
     if (viewItem) {
       viewItem.onStateChanged();
     }
   },
 
-  // DownloadsView
-  onDataItemChanged(aDataItem) {
-    let viewItem = this._visibleViewItems.get(aDataItem);
+  onDownloadChanged(download) {
+    let viewItem = this._visibleViewItems.get(download);
     if (viewItem) {
       viewItem.onChanged();
     }
   },
 
   /**
+   * Called when a data item is removed.  Ensures that the widget associated
+   * with the view item is removed from the user interface.
+   *
+   * @param download
+   *        Download object that is being removed.
+   */
+  onDownloadRemoved(download) {
+    DownloadsCommon.log("A download data item was removed.");
+
+    let itemIndex = this._downloads.indexOf(download);
+    this._downloads.splice(itemIndex, 1);
+
+    if (itemIndex < this.kItemCountLimit) {
+      // The item to remove is visible in the panel.
+      this._removeViewItem(download);
+      if (this._downloads.length >= this.kItemCountLimit) {
+        // Reinsert the next item into the panel.
+        this._addViewItem(this._downloads[this.kItemCountLimit - 1], false);
+      }
+    }
+
+    this._itemCountChanged();
+  },
+
+  /**
    * Associates each richlistitem for a download with its corresponding
    * DownloadsViewItemController object.
    */
   _controllersForElements: new Map(),
 
   controllerForElement(element) {
     return this._controllersForElements.get(element);
   },
 
   /**
    * Creates a new view item associated with the specified data item, and adds
    * it to the top or the bottom of the list.
    */
-  _addViewItem(aDataItem, aNewest)
+  _addViewItem(download, aNewest)
   {
     DownloadsCommon.log("Adding a new DownloadsViewItem to the downloads list.",
                         "aNewest =", aNewest);
 
     let element = document.createElement("richlistitem");
-    let viewItem = new DownloadsViewItem(aDataItem, element);
-    this._visibleViewItems.set(aDataItem, viewItem);
-    let viewItemController = new DownloadsViewItemController(aDataItem);
+    let viewItem = new DownloadsViewItem(download, element);
+    this._visibleViewItems.set(download, viewItem);
+    let viewItemController = new DownloadsViewItemController(download);
     this._controllersForElements.set(element, viewItemController);
     if (aNewest) {
       this.richListBox.insertBefore(element, this.richListBox.firstChild);
     } else {
       this.richListBox.appendChild(element);
     }
   },
 
   /**
    * Removes the view item associated with the specified data item.
    */
-  _removeViewItem(aDataItem) {
+  _removeViewItem(download) {
     DownloadsCommon.log("Removing a DownloadsViewItem from the downloads list.");
-    let element = this._visibleViewItems.get(aDataItem).element;
+    let element = this._visibleViewItems.get(download).element;
     let previousSelectedIndex = this.richListBox.selectedIndex;
     this.richListBox.removeChild(element);
     if (previousSelectedIndex != -1) {
       this.richListBox.selectedIndex = Math.min(previousSelectedIndex,
                                                 this.richListBox.itemCount - 1);
     }
-    this._visibleViewItems.delete(aDataItem);
+    this._visibleViewItems.delete(download);
     this._controllersForElements.delete(element);
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// User interface event functions
 
   /**
    * Helper function to do commands on a specific download item.
@@ -974,42 +972,37 @@ const DownloadsView = {
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadsViewItem
 
 /**
  * Builds and updates a single item in the downloads list widget, responding to
  * changes in the download state and real-time data.
  *
- * @param aDataItem
- *        DownloadsDataItem to be associated with the view item.
+ * @param download
+ *        Download object to be associated with the view item.
  * @param aElement
  *        XUL element corresponding to the single download item in the view.
  */
-function DownloadsViewItem(aDataItem, aElement) {
-  this.dataItem = aDataItem;
+function DownloadsViewItem(download, aElement) {
+  this.download = download;
   this.element = aElement;
   this.element._shell = this;
 
   this.element.setAttribute("type", "download");
   this.element.classList.add("download-state");
 
   this._updateState();
   this.verifyTargetExists();
 }
 
 DownloadsViewItem.prototype = {
   __proto__: DownloadElementShell.prototype,
 
   /**
-   * The DownloadDataItem associated with this view item.
-   */
-  dataItem: null,
-
-  /**
    * The XUL element corresponding to the associated richlistbox item.
    */
   _element: null,
 
   onStateChanged() {
     this.element.setAttribute("image", this.image);
     this.element.setAttribute("state",
                               DownloadsCommon.stateOfDownload(this.download));
@@ -1143,31 +1136,21 @@ const DownloadsViewController = {
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadsViewItemController
 
 /**
  * Handles all the user interaction events, in particular the "commands",
  * related to a single item in the downloads list widgets.
  */
-function DownloadsViewItemController(aDataItem) {
-  this.dataItem = aDataItem;
+function DownloadsViewItemController(download) {
+  this.download = download;
 }
 
 DownloadsViewItemController.prototype = {
-  //////////////////////////////////////////////////////////////////////////////
-  //// Command dispatching
-
-  /**
-   * The DownloadDataItem controlled by this object.
-   */
-  dataItem: null,
-
-  get download() this.dataItem.download,
-
   isCommandEnabled(aCommand) {
     switch (aCommand) {
       case "downloadsCmd_open": {
         if (!this.download.succeeded) {
           return false;
         }
 
         let file = new FileUtils.File(this.download.target.path);
--- a/browser/components/downloads/content/downloadsViewCommon.js
+++ b/browser/components/downloads/content/downloadsViewCommon.js
@@ -40,40 +40,30 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 /**
  * A download element shell is responsible for handling the commands and the
  * displayed data for a single element that uses the "download.xml" binding.
  *
  * The information to display is obtained through the associated Download object
  * from the JavaScript API for downloads, and commands are executed using a
  * combination of Download methods and DownloadsCommon.jsm helper functions.
  *
- * Specialized versions of this shell must be defined, currently they are the
+ * Specialized versions of this shell must be defined, and they are required to
+ * implement the "download" property or getter. Currently these objects are the
  * HistoryDownloadElementShell and the DownloadsViewItem for the panel. The
  * history view may use a HistoryDownload object in place of a Download object.
  */
 function DownloadElementShell() {}
 
 DownloadElementShell.prototype = {
   /**
    * The richlistitem for the download, initialized by the derived object.
    */
   element: null,
 
   /**
-   * The DownloadsDataItem for the download, overridden by the derived object.
-   */
-  dataItem: null,
-
-  /**
-   * Download or HistoryDownload object to use for displaying information and
-   * for executing commands in the user interface.
-   */
-  get download() this.dataItem.download,
-
-  /**
    * URI string for the file type icon displayed in the download element.
    */
   get image() {
     if (!this.download.target.path) {
       // Old history downloads may not have a target path.
       return "moz-icon://.unknown?size=32";
     }