Bug 1129896 - Part 2 of 2 - Convert the shared front-end code to a JavaScript code module. r=mak
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Mon, 16 Feb 2015 18:49:56 +0000
changeset 229506 79fcb3f8f12ec0d25fe343007e040b0d6f6b4619
parent 229505 c41b520cb4a83e166afb6104ca090e95ddfb236b
child 229507 33847f98f601592826dc8cf36364c1440514ccc1
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
bugs1129896
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 1129896 - Part 2 of 2 - Convert the shared front-end code to a JavaScript code module. r=mak
browser/base/content/global-scripts.inc
browser/components/downloads/DownloadsCommon.jsm
browser/components/downloads/DownloadsViewUI.jsm
browser/components/downloads/content/allDownloadsViewOverlay.js
browser/components/downloads/content/allDownloadsViewOverlay.xul
browser/components/downloads/content/downloads.js
browser/components/downloads/content/downloadsViewCommon.js
browser/components/downloads/jar.mn
browser/components/downloads/moz.build
--- a/browser/base/content/global-scripts.inc
+++ b/browser/base/content/global-scripts.inc
@@ -2,14 +2,13 @@
 # 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/.
 
 <script type="application/javascript" src="chrome://global/content/printUtils.js"/>
 <script type="application/javascript" src="chrome://global/content/viewZoomOverlay.js"/>
 <script type="application/javascript" src="chrome://browser/content/places/browserPlacesViews.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser.js"/>
-<script type="application/javascript" src="chrome://browser/content/downloads/downloadsViewCommon.js"/>
 <script type="application/javascript" src="chrome://browser/content/downloads/downloads.js"/>
 <script type="application/javascript" src="chrome://browser/content/downloads/indicator.js"/>
 <script type="application/javascript" src="chrome://browser/content/customizableui/panelUI.js"/>
 <script type="application/javascript" src="chrome://global/content/inlineSpellCheckUI.js"/>
 <script type="application/javascript" src="chrome://global/content/viewSourceUtils.js"/>
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -319,41 +319,50 @@ this.DownloadsCommon = {
         return nsIDM.DOWNLOAD_PAUSED;
       }
       return nsIDM.DOWNLOAD_CANCELED;
     }
     return nsIDM.DOWNLOAD_NOTSTARTED;
   },
 
   /**
+   * Helper function required because the Downloads Panel and the Downloads View
+   * don't share the controller yet.
+   */
+  removeAndFinalizeDownload(download) {
+    Downloads.getList(Downloads.ALL)
+             .then(list => list.remove(download))
+             .then(() => download.finalize(true))
+             .catch(Cu.reportError);
+  },
+
+  /**
    * Given an iterable collection of Download objects, generates and returns
    * statistics about that collection.
    *
    * @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(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
       // download.
       slowestSpeed: Infinity,
@@ -653,17 +662,17 @@ DownloadsDataCtor.prototype = {
    * 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 download of this.oldDownloadStates.keys()) {
+    for (let download of this.downloads) {
       // Stopped, paused, and failed downloads with partial data are removed.
       if (download.stopped && !(download.canceled && download.hasPartialData)) {
         return true;
       }
     }
     return false;
   },
 
@@ -800,17 +809,17 @@ 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 downloadsArray = [...this.oldDownloadStates.keys()];
+    let downloadsArray = [...this.downloads];
     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();
   },
 
   //////////////////////////////////////////////////////////////////////////////
@@ -1203,17 +1212,17 @@ DownloadsIndicatorDataCtor.prototype = {
   _lastTimeLeft: -1,
 
   /**
    * 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 downloads we care about - in this case,
    * it's all active downloads.
    */
-  _activeDownloads() {
+  * _activeDownloads() {
     let downloads = this._isPrivate ? PrivateDownloadsData.downloads
                                     : DownloadsData.downloads;
     for (let download of downloads) {
       if (!download.stopped || (download.canceled && download.hasPartialData)) {
         yield download;
       }
     }
   },
@@ -1399,17 +1408,17 @@ DownloadsSummaryData.prototype = {
 
   /**
    * 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 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.
    */
-  _downloadsForSummary() {
+  * _downloadsForSummary() {
     if (this._downloads.length > 0) {
       for (let i = this._numToExclude; i < this._downloads.length; ++i) {
         yield this._downloads[i];
       }
     }
   },
 
   /**
rename from browser/components/downloads/content/downloadsViewCommon.js
rename to browser/components/downloads/DownloadsViewUI.jsm
--- a/browser/components/downloads/content/downloadsViewCommon.js
+++ b/browser/components/downloads/DownloadsViewUI.jsm
@@ -1,63 +1,52 @@
 /* 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/. */
 
 /*
- * This file is loaded in every window that uses the "download.xml" binding, and
+ * This module is imported by code that uses the "download.xml" binding, and
  * provides prototypes for objects that handle input and display information.
- *
- * This file lazily imports common JavaScript modules in the window scope. Most
- * of these modules are generally already declared before this file is included.
  */
 
 "use strict";
 
-let { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+this.EXPORTED_SYMBOLS = [
+  "DownloadsViewUI",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
                                   "resource://gre/modules/DownloadUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
                                   "resource:///modules/DownloadsCommon.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
-                                  "resource://gre/modules/FileUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                  "resource://gre/modules/NetUtil.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, "Promise",
-                                  "resource://gre/modules/Promise.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Services",
-                                  "resource://gre/modules/Services.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
-                                  "resource://gre/modules/Task.jsm");
+
+this.DownloadsViewUI = {};
 
 /**
  * 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, 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() {}
+this.DownloadsViewUI.DownloadElementShell = function () {}
 
-DownloadElementShell.prototype = {
+this.DownloadsViewUI.DownloadElementShell.prototype = {
   /**
    * The richlistitem for the download, initialized by the derived object.
    */
   element: null,
 
   /**
    * URI string for the file type icon displayed in the download element.
    */
@@ -91,18 +80,20 @@ DownloadElementShell.prototype = {
 
   /**
    * The progress element for the download, or undefined in case the XBL binding
    * has not been applied yet.
    */
   get _progressElement() {
     if (!this.__progressElement) {
       // If the element is not available now, we will try again the next time.
-      this.__progressElement = document.getAnonymousElementByAttribute(
-                               this.element, "anonid", "progressmeter");
+      this.__progressElement =
+           this.element.ownerDocument.getAnonymousElementByAttribute(
+                                         this.element, "anonid",
+                                         "progressmeter");
     }
     return this.__progressElement;
   },
 
   /**
    * Processes a major state change in the user interface, then proceeds with
    * the normal progress update. This function is not called for every progress
    * update in order to improve performance.
@@ -138,17 +129,17 @@ DownloadElementShell.prototype = {
       this.element.setAttribute("progressmode", "normal");
       this.element.setAttribute("progress", this.download.progress);
     } else {
       this.element.setAttribute("progressmode", "undetermined");
     }
 
     // Dispatch the ValueChange event for accessibility, if possible.
     if (this._progressElement) {
-      let event = document.createEvent("Events");
+      let event = this.element.ownerDocument.createEvent("Events");
       event.initEvent("ValueChange", true, true);
       this._progressElement.dispatchEvent(event);
     }
 
     let status = this.statusTextAndTip;
     this.element.setAttribute("status", status.text);
     this.element.setAttribute("statusTip", status.tip);
   },
@@ -168,51 +159,53 @@ DownloadElementShell.prototype = {
   get rawStatusTextAndTip() {
     const nsIDM = Ci.nsIDownloadManager;
     let s = DownloadsCommon.strings;
 
     let text = "";
     let tip = "";
 
     if (!this.download.stopped) {
-      let total = this.download.hasProgress ? this.download.totalBytes : -1;
+      let totalBytes = this.download.hasProgress ? this.download.totalBytes
+                                                 : -1;
       // By default, extended status information including the individual
       // download rate is displayed in the tooltip. The history view overrides
-      // the getter and displays the detials in the main area instead.
+      // the getter and displays the datails in the main area instead.
       [text] = DownloadUtils.getDownloadStatusNoRate(
                                           this.download.currentBytes,
-                                          total,
+                                          totalBytes,
                                           this.download.speed,
                                           this.lastEstimatedSecondsLeft);
       let newEstimatedSecondsLeft;
       [tip, newEstimatedSecondsLeft] = DownloadUtils.getDownloadStatus(
                                           this.download.currentBytes,
-                                          total,
+                                          totalBytes,
                                           this.download.speed,
                                           this.lastEstimatedSecondsLeft);
       this.lastEstimatedSecondsLeft = newEstimatedSecondsLeft;
     } else if (this.download.canceled && this.download.hasPartialData) {
-      let total = this.download.hasProgress ? this.download.totalBytes : -1;
+      let totalBytes = this.download.hasProgress ? this.download.totalBytes
+                                                 : -1;
       let transfer = DownloadUtils.getTransferTotal(this.download.currentBytes,
-                                                    total);
+                                                    totalBytes);
 
       // We use the same XUL label to display both the state and the amount
       // transferred, for example "Paused -  1.1 MB".
       text = s.statusSeparatorBeforeNumber(s.statePaused, transfer);
     } else if (!this.download.succeeded && !this.download.canceled &&
                !this.download.error) {
       text = s.stateStarting;
     } else {
       let stateLabel;
 
       if (this.download.succeeded) {
         // For completed downloads, show the file size (e.g. "1.5 MB").
         if (this.download.target.size !== undefined) {
-          let [size, unit] = DownloadUtils.convertByteUnits(
-                                                  this.download.target.size);
+          let [size, unit] =
+            DownloadUtils.convertByteUnits(this.download.target.size);
           stateLabel = s.sizeWithUnits(size, unit);
         } else {
           // History downloads may not have a size defined.
           stateLabel = s.sizeUnknown;
         }
       } else if (this.download.canceled) {
         stateLabel = s.stateCanceled;
       } else if (this.download.error.becauseBlockedByParentalControls) {
--- a/browser/components/downloads/content/allDownloadsViewOverlay.js
+++ b/browser/components/downloads/content/allDownloadsViewOverlay.js
@@ -1,35 +1,54 @@
 /* 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/. */
 
+let { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
+                                  "resource://gre/modules/DownloadUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
+                                  "resource:///modules/DownloadsCommon.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsViewUI",
+                                  "resource:///modules/DownloadsViewUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+                                  "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+                                  "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+                                  "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+                                  "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
                                   "resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+                                  "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
 
 const nsIDM = Ci.nsIDownloadManager;
 
 const DESTINATION_FILE_URI_ANNO  = "downloads/destinationFileURI";
 const DOWNLOAD_META_DATA_ANNO    = "downloads/metaData";
 
 const DOWNLOAD_VIEW_SUPPORTED_COMMANDS =
  ["cmd_delete", "cmd_copy", "cmd_paste", "cmd_selectAll",
   "downloadsCmd_pauseResume", "downloadsCmd_cancel",
   "downloadsCmd_open", "downloadsCmd_show", "downloadsCmd_retry",
   "downloadsCmd_openReferrer", "downloadsCmd_clearDownloads"];
 
 /**
  * Represents a download from the browser history. It implements part of the
  * interface of the Download object.
  *
- * @param url
- *        URI string for the download source.
- * @param endTime
- *        Timestamp with the end time for the download, used if there is no
- *        additional metadata available.
+ * @param aPlacesNode
+ *        The Places node from which the history download should be initialized.
  */
 function HistoryDownload(aPlacesNode) {
   // TODO (bug 829201): history downloads should get the referrer from Places.
   this.source = {
     url: aPlacesNode.uri,
   };
   this.target = {
     path: undefined,
@@ -110,21 +129,24 @@ HistoryDownload.prototype = {
    * instead of the history download. In case this session download is not
    * available, we show the history download as canceled, not paused.
    */
   hasPartialData: false,
 
   /**
    * This method mimicks the "start" method of session downloads, and is called
    * when the user retries a history download.
+   *
+   * At present, we always ask the user for a new target path when retrying a
+   * history download. In the future we may consider reusing the known target
+   * path if the folder still exists and the file name is not already used,
+   * except when the user preferences indicate that the target path should be
+   * requested every time a new download is started.
    */
   start() {
-    // In future we may try to download into the same original target uri, when
-    // we have it.  Though that requires verifying the path is still valid and
-    // may surprise the user if he wants to be requested every time.
     let browserWin = RecentWindow.getMostRecentBrowserWindow();
     let initiatingDoc = browserWin ? browserWin.document : document;
 
     // Do not suggest a file name if we don't know the original target.
     let leafName = this.target.path ? OS.Path.basename(this.target.path) : null;
     DownloadURL(this.source.url, leafName, initiatingDoc);
 
     return Promise.resolve();
@@ -145,17 +167,17 @@ HistoryDownload.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
+ * both a history and a session download are present, the session 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.
@@ -176,17 +198,17 @@ function HistoryDownloadElementShell(aSe
     this.sessionDownload = aSessionDownload;
   }
   if (aHistoryDownload) {
     this.historyDownload = aHistoryDownload;
   }
 }
 
 HistoryDownloadElementShell.prototype = {
-  __proto__: DownloadElementShell.prototype,
+  __proto__: DownloadsViewUI.DownloadElementShell.prototype,
 
   /**
    * Manages the "active" state of the shell.  By default all the shells without
    * a session download are inactive, thus their UI is not updated.  They must
    * be activated when entering the visible area.  Session downloads are always
    * active.
    */
   ensureActive() {
@@ -335,20 +357,17 @@ HistoryDownloadElementShell.prototype = 
       }
       case "downloadsCmd_cancel": {
         this.download.cancel().catch(() => {});
         this.download.removePartialData().catch(Cu.reportError);
         break;
       }
       case "cmd_delete": {
         if (this._sessionDownload) {
-          Downloads.getList(Downloads.ALL)
-                   .then(list => list.remove(this.download))
-                   .then(() => this.download.finalize(true))
-                   .catch(Cu.reportError);
+          DownloadsCommon.removeAndFinalizeDownload(this.download);
         }
         if (this._historyDownload) {
           let uri = NetUtil.newURI(this.download.source.url);
           PlacesUtils.bhistory.removePage(uri);
         }
         break;
       }
       case "downloadsCmd_retry": {
@@ -399,18 +418,18 @@ HistoryDownloadElementShell.prototype = 
           return "downloadsCmd_show";
         case nsIDM.DOWNLOAD_BLOCKED_PARENTAL:
         case nsIDM.DOWNLOAD_DIRTY:
         case nsIDM.DOWNLOAD_BLOCKED_POLICY:
           return "downloadsCmd_openReferrer";
       }
       return "";
     }
-    let command = getDefaultCommandForState(
-                            DownloadsCommon.stateOfDownload(this.download));
+    let state = DownloadsCommon.stateOfDownload(this.download);
+    let command = getDefaultCommandForState(state);
     if (command && this.isCommandEnabled(command)) {
       this.doCommand(command);
     }
   },
 
   /**
    * This method is called by the outer download view, after the controller
    * commands have already been updated. In case we did not check for the
@@ -451,17 +470,17 @@ HistoryDownloadElementShell.prototype = 
     // Ensure the interface has been updated based on the new values. We need to
     // do this because history downloads can't trigger update notifications.
     this._updateProgress();
   }),
 };
 
 /**
  * A Downloads Places View is a places view designed to show a places query
- * for history downloads alongside the current "session"-downloads.
+ * for history downloads alongside the session downloads.
  *
  * As we don't use the places controller, some methods implemented by other
  * places views are not implemented by this view.
  *
  * A richlistitem in this view can represent either a past download or a session
  * download, or both. Session downloads are shown first in the view, and as long
  * as they exist they "collapses" their history "counterpart" (So we don't show two
  * items for every download).
--- a/browser/components/downloads/content/allDownloadsViewOverlay.xul
+++ b/browser/components/downloads/content/allDownloadsViewOverlay.xul
@@ -30,18 +30,16 @@
         // transition-type is set correctly
         view.place = "place:transition=7&sort=4";
 -->
 <overlay id="downloadsViewOverlay"
          xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
          xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <script type="application/javascript"
-          src="chrome://browser/content/downloads/downloadsViewCommon.js"/>
-  <script type="application/javascript"
           src="chrome://browser/content/downloads/allDownloadsViewOverlay.js"/>
   <script type="application/javascript"
           src="chrome://global/content/contentAreaUtils.js"/>
 
   <richlistbox flex="1"
                seltype="multiple"
                id="downloadsRichListBox" context="downloadsContextMenu"
                onscroll="return this._placesView.onScroll();"
--- a/browser/components/downloads/content/downloads.js
+++ b/browser/components/downloads/content/downloads.js
@@ -59,16 +59,31 @@
  * We end up capturing the tab/down key events, and preventing their default
  * behaviour. We then set a "keyfocus" attribute on the panel, which allows
  * us to draw a ring around the currently focused element. If the panel is
  * closed or the mouse moves over the panel, we remove the attribute.
  */
 
 "use strict";
 
+let { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
+                                  "resource:///modules/DownloadsCommon.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsViewUI",
+                                  "resource:///modules/DownloadsViewUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+                                  "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+                                  "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+                                  "resource://gre/modules/Services.jsm");
+
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadsPanel
 
 /**
  * Main entry point for the downloads panel interface.
  */
 const DownloadsPanel = {
   //////////////////////////////////////////////////////////////////////////////
@@ -989,17 +1004,17 @@ function DownloadsViewItem(download, aEl
 
   this.element.setAttribute("type", "download");
   this.element.classList.add("download-state");
 
   this._updateState();
 }
 
 DownloadsViewItem.prototype = {
-  __proto__: DownloadElementShell.prototype,
+  __proto__: DownloadsViewUI.DownloadElementShell.prototype,
 
   /**
    * The XUL element corresponding to the associated richlistbox item.
    */
   _element: null,
 
   onStateChanged() {
     this.element.setAttribute("image", this.image);
@@ -1164,20 +1179,17 @@ 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() {
-      Downloads.getList(Downloads.ALL)
-               .then(list => list.remove(this.download))
-               .then(() => this.download.finalize(true))
-               .catch(Cu.reportError);
+      DownloadsCommon.removeAndFinalizeDownload(this.download);
       PlacesUtils.bhistory.removePage(
                              NetUtil.newURI(this.download.source.url));
     },
 
     downloadsCmd_cancel() {
       this.download.cancel().catch(() => {});
       this.download.removePartialData().catch(Cu.reportError);
     },
--- a/browser/components/downloads/jar.mn
+++ b/browser/components/downloads/jar.mn
@@ -2,17 +2,16 @@
 # 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/.
 
 browser.jar:
 *       content/browser/downloads/download.xml           (content/download.xml)
         content/browser/downloads/download.css           (content/download.css)
         content/browser/downloads/downloads.css          (content/downloads.css)
 *       content/browser/downloads/downloads.js           (content/downloads.js)
-        content/browser/downloads/downloadsViewCommon.js (content/downloadsViewCommon.js)
 *       content/browser/downloads/downloadsOverlay.xul   (content/downloadsOverlay.xul)
         content/browser/downloads/indicator.js           (content/indicator.js)
         content/browser/downloads/indicatorOverlay.xul   (content/indicatorOverlay.xul)
 *       content/browser/downloads/allDownloadsViewOverlay.xul (content/allDownloadsViewOverlay.xul)
         content/browser/downloads/allDownloadsViewOverlay.js  (content/allDownloadsViewOverlay.js)
         content/browser/downloads/allDownloadsViewOverlay.css (content/allDownloadsViewOverlay.css)
 *       content/browser/downloads/contentAreaDownloadsView.xul (content/contentAreaDownloadsView.xul)
         content/browser/downloads/contentAreaDownloadsView.js  (content/contentAreaDownloadsView.js)
--- a/browser/components/downloads/moz.build
+++ b/browser/components/downloads/moz.build
@@ -8,9 +8,10 @@ XPCSHELL_TESTS_MANIFESTS += ['test/unit/
 BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
 
 JAR_MANIFESTS += ['jar.mn']
 
 EXTRA_JS_MODULES += [
     'DownloadsCommon.jsm',
     'DownloadsLogger.jsm',
     'DownloadsTaskbar.jsm',
+    'DownloadsViewUI.jsm',
 ]