Bug 911636 - Webapp Runtime migration to Downloads.jsm. r=myk, r=paolo
authorMarco Castelluccio <mar.castelluccio@studenti.unina.it>
Tue, 19 Aug 2014 08:50:00 -0400
changeset 214773 9c96b4e6c9ee211b98cb0b7a5b25a5c8dd0c1b5d
parent 214772 261d832d497bcc416a6dbdbfa6964e96f2fddf0d
child 214774 a2a4617a6b4ef18ad05de307379edfd77cf7ef30
push idunknown
push userunknown
push dateunknown
reviewersmyk, paolo
bugs911636
milestone34.0a1
Bug 911636 - Webapp Runtime migration to Downloads.jsm. r=myk, r=paolo
b2g/chrome/content/shell.js
browser/installer/package-manifest.in
toolkit/components/downloads/nsDownloadManager.cpp
webapprt/DownloadView.jsm
webapprt/Startup.jsm
webapprt/content/downloads/download.xml
webapprt/content/downloads/downloads.css
webapprt/content/downloads/downloads.js
webapprt/content/downloads/downloads.xul
webapprt/jar.mn
webapprt/locales/en-US/webapprt/downloads/downloads.dtd
webapprt/locales/jar.mn
webapprt/moz.build
webapprt/test/chrome/browser_download.js
webapprt/test/chrome/download.html
webapprt/test/chrome/download.test
webapprt/test/chrome/downloads/browser_add_download.js
webapprt/test/chrome/downloads/browser_remove_download.js
webapprt/test/chrome/downloads/webapprt.ini
webapprt/test/chrome/head.js
webapprt/test/chrome/webapprt.ini
webapprt/themes/LICENSE
webapprt/themes/linux/downloads/downloadIcon.png
webapprt/themes/linux/downloads/downloads.css
webapprt/themes/linux/jar.mn
webapprt/themes/linux/moz.build
webapprt/themes/moz.build
webapprt/themes/osx/downloads/buttons.png
webapprt/themes/osx/downloads/downloadIcon.png
webapprt/themes/osx/downloads/downloads.css
webapprt/themes/osx/jar.mn
webapprt/themes/osx/moz.build
webapprt/themes/windows/downloads/downloadButtons-aero.png
webapprt/themes/windows/downloads/downloadButtons.png
webapprt/themes/windows/downloads/downloadIcon-aero.png
webapprt/themes/windows/downloads/downloadIcon.png
webapprt/themes/windows/downloads/downloads-aero.css
webapprt/themes/windows/downloads/downloads.css
webapprt/themes/windows/jar.mn
webapprt/themes/windows/moz.build
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -1380,13 +1380,12 @@ const kTransferCid = Components.ID("{1b4
 
 /**
   * Contract ID of the service implementing nsITransfer.
   */
 const kTransferContractId = "@mozilla.org/transfer;1";
 
 // Override Toolkit's nsITransfer implementation with the one from the
 // JavaScript API for downloads.  This will eventually be removed when
-// nsIDownloadManager will not be available anymore (bug 851471).  The
-// old code in this module will be removed in bug 899110.
+// nsIDownloadManager will not be available anymore (bug 851471).
 Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
                   .registerFactory(kTransferCid, "",
                                    kTransferContractId, null);
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -811,16 +811,17 @@ bin/libfreebl_32int64_3.so
 @BINPATH@/webapprt/chrome/@AB_CD@@JAREXT@
 @BINPATH@/webapprt/chrome/@AB_CD@.manifest
 @BINPATH@/webapprt/components/CommandLineHandler.js
 @BINPATH@/webapprt/components/ContentPermission.js
 @BINPATH@/webapprt/components/DirectoryProvider.js
 @BINPATH@/webapprt/components/PaymentUIGlue.js
 @BINPATH@/webapprt/components/components.manifest
 @BINPATH@/webapprt/defaults/preferences/prefs.js
+@BINPATH@/webapprt/modules/DownloadView.jsm
 @BINPATH@/webapprt/modules/Startup.jsm
 @BINPATH@/webapprt/modules/WebappRT.jsm
 @BINPATH@/webapprt/modules/WebappManager.jsm
 @BINPATH@/webapprt/modules/RemoteDebugger.jsm
 @BINPATH@/webapprt/modules/WebRTCHandler.jsm
 #endif
 
 #ifdef MOZ_METRO
--- a/toolkit/components/downloads/nsDownloadManager.cpp
+++ b/toolkit/components/downloads/nsDownloadManager.cpp
@@ -940,41 +940,23 @@ nsDownloadManager::Init()
   rv = bundleService->CreateBundle(DOWNLOAD_MANAGER_BUNDLE,
                                    getter_AddRefs(mBundle));
   NS_ENSURE_SUCCESS(rv, rv);
 
 #if !defined(MOZ_JSDOWNLOADS)
   // When MOZ_JSDOWNLOADS is undefined, we still check the preference that can
   // be used to enable the JavaScript API during the migration process.
   mUseJSTransfer = Preferences::GetBool(PREF_BD_USEJSTRANSFER, false);
-#else
-
-  nsAutoCString appID;
-  nsCOMPtr<nsIXULAppInfo> info = do_GetService("@mozilla.org/xre/app-info;1");
-  if (info) {
-    rv = info->GetID(appID);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  // The webapp runtime doesn't use the new JS downloads API yet.
-  // The conversion of the webapp runtime to use the JavaScript API for
-  // downloads is tracked in bug 911636.
-  if (appID.EqualsLiteral("webapprt@mozilla.org")) {
-    mUseJSTransfer = false;
-  } else {
-#if !defined(XP_WIN)
-    mUseJSTransfer = true;
-#else
+#elif defined(XP_WIN)
     // When MOZ_JSDOWNLOADS is defined on Windows, this component is disabled
     // unless we are running in Windows Metro.  The conversion of Windows Metro
     // to use the JavaScript API for downloads is tracked in bug 906042.
     mUseJSTransfer = !IsRunningInWindowsMetro();
-#endif
-  }
-
+#else
+    mUseJSTransfer = true;
 #endif
 
   if (mUseJSTransfer)
     return NS_OK;
 
   // Clean up any old downloads.rdf files from before Firefox 3
   {
     nsCOMPtr<nsIFile> oldDownloadsFile;
new file mode 100644
--- /dev/null
+++ b/webapprt/DownloadView.jsm
@@ -0,0 +1,35 @@
+/* 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 = ["DownloadView"];
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Downloads.jsm");
+
+this.DownloadView = {
+  init: function() {
+    Downloads.getList(Downloads.ALL)
+             .then(list => list.addView(this))
+             .catch(Cu.reportError);
+  },
+
+  onDownloadAdded: function(aDownload) {
+    let dmWindow = Services.wm.getMostRecentWindow("Download:Manager");
+    if (dmWindow) {
+      dmWindow.focus();
+    } else {
+      Services.ww.openWindow(null,
+                             "chrome://webapprt/content/downloads/downloads.xul",
+                             "Download:Manager",
+                             "chrome,dialog=no,resizable",
+                             null);
+    }
+  },
+};
+
+DownloadView.init();
--- a/webapprt/Startup.jsm
+++ b/webapprt/Startup.jsm
@@ -137,21 +137,29 @@ this.startup = function(window) {
 
     Services.io.getProtocolHandler("resource")
                .QueryInterface(Ci.nsIResProtocolHandler)
                .setSubstitution("webappbranding", aliasURI);
 
     // Wait for XUL window loading
     yield deferredWindowLoad.promise;
 
+    // Override Toolkit's nsITransfer implementation with the one from the
+    // JavaScript API for downloads. This will eventually be removed when
+    // nsIDownloadManager will not be available anymore (bug 851471).
+    Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
+              .registerFactory(Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}"),
+                               "", "@mozilla.org/transfer;1", null);
+
     // Load these modules here because they aren't needed right at startup,
     // but they need to be loaded to perform some initialization steps.
     Cu.import("resource://gre/modules/Payment.jsm");
     Cu.import("resource://gre/modules/AlarmService.jsm");
     Cu.import("resource://webapprt/modules/WebRTCHandler.jsm");
+    Cu.import("resource://webapprt/modules/DownloadView.jsm");
 
     // Get the <browser> element in the webapp.xul window.
     let appBrowser = window.document.getElementById("content");
 
     // Set the principal to the correct appID and launch the application.
     appBrowser.docShell.setIsApp(WebappRT.appID);
     appBrowser.setAttribute("src", WebappRT.launchURI);
 
copy from toolkit/mozapps/downloads/content/download.xml
copy to webapprt/content/downloads/download.xml
--- a/toolkit/mozapps/downloads/content/download.xml
+++ b/webapprt/content/downloads/download.xml
@@ -1,27 +1,27 @@
 <?xml version="1.0"?>
 
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!DOCTYPE bindings [
-  <!ENTITY % downloadDTD SYSTEM "chrome://mozapps/locale/downloads/downloads.dtd" >
+  <!ENTITY % downloadDTD SYSTEM "chrome://webapprt/locale/downloads/downloads.dtd" >
   %downloadDTD;
 ]>
 
 <bindings id="downloadBindings"
           xmlns="http://www.mozilla.org/xbl"
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           xmlns:xbl="http://www.mozilla.org/xbl">
 
   <binding id="download-base" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
     <resources>
-      <stylesheet src="chrome://mozapps/skin/downloads/downloads.css"/>
+      <stylesheet src="chrome://webapprt/skin/downloads/downloads.css"/>
     </resources>
     <implementation>
       <property name="paused">
         <getter>
         <![CDATA[
           return parseInt(this.getAttribute("state")) == Components.interfaces.nsIDownloadManager.DOWNLOAD_PAUSED;
         ]]>
         </getter>
@@ -59,28 +59,29 @@
                  state == dl.DOWNLOAD_FAILED;
         ]]>
         </getter>
       </property>
       <property name="buttons">
         <getter>
         <![CDATA[
           var startEl = document.getAnonymousNodes(this);
-          if (!startEl.length)
+          if (!startEl.length) {
             startEl = [this];
+          }
 
           const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
           return startEl[0].getElementsByTagNameNS(XULNS, "button");
         ]]>
         </getter>
       </property>
     </implementation>
   </binding>
-  
-  <binding id="download-starting" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+
+  <binding id="download-starting" extends="chrome://webapprt/content/downloads/download.xml#download-base">
     <content>
       <xul:hbox flex="1">
         <xul:vbox pack="center">
           <xul:image class="downloadTypeIcon" validate="always"
                      xbl:inherits="src=image"/>
         </xul:vbox>
         <xul:vbox pack="start" flex="1">
           <xul:label xbl:inherits="value=target,tooltiptext=target"
@@ -88,23 +89,23 @@
           <xul:progressmeter mode="normal" value="0" flex="1"
                              anonid="progressmeter"/>
           <xul:label value="&starting.label;" class="status"/>
           <xul:spacer flex="1"/>
         </xul:vbox>
         <xul:vbox pack="center">
           <xul:button class="cancel mini-button" tooltiptext="&cmd.cancel.label;"
                       cmd="cmd_cancel" ondblclick="event.stopPropagation();"
-                      oncommand="performCommand('cmd_cancel', this);"/>
+                      oncommand="gDownloadList.performCommand('cmd_cancel', this);"/>
         </xul:vbox>
       </xul:hbox>
-    </content>  
+    </content>
   </binding>
-  
-  <binding id="download-downloading" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+
+  <binding id="download-downloading" extends="chrome://webapprt/content/downloads/download.xml#download-base">
     <content>
       <xul:hbox flex="1" class="downloadContentBox">
         <xul:vbox pack="center">
           <xul:image class="downloadTypeIcon" validate="always"
                      xbl:inherits="src=image"/>
         </xul:vbox>
         <xul:vbox flex="1">
           <xul:label xbl:inherits="value=target,tooltiptext=target"
@@ -112,30 +113,30 @@
           <xul:hbox>
             <xul:vbox flex="1">
               <xul:progressmeter mode="normal" value="0" flex="1"
                                  anonid="progressmeter"
                                  xbl:inherits="value=progress,mode=progressmode"/>
             </xul:vbox>
             <xul:button class="pause mini-button" tooltiptext="&cmd.pause.label;"
                         cmd="cmd_pause" ondblclick="event.stopPropagation();"
-                        oncommand="performCommand('cmd_pause', this);"/>
+                        oncommand="gDownloadList.performCommand('cmd_pause', this);"/>
             <xul:button class="cancel mini-button" tooltiptext="&cmd.cancel.label;"
                         cmd="cmd_cancel" ondblclick="event.stopPropagation();"
-                        oncommand="performCommand('cmd_cancel', this);"/>
+                        oncommand="gDownloadList.performCommand('cmd_cancel', this);"/>
           </xul:hbox>
           <xul:label xbl:inherits="value=status,tooltiptext=statusTip" flex="1"
                      crop="right" class="status"/>
           <xul:spacer flex="1"/>
         </xul:vbox>
       </xul:hbox>
     </content>
   </binding>
-  
-  <binding id="download-paused" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+
+  <binding id="download-paused" extends="chrome://webapprt/content/downloads/download.xml#download-base">
     <content>
       <xul:hbox flex="1">
         <xul:vbox pack="center">
           <xul:image class="downloadTypeIcon" validate="always"
                      xbl:inherits="src=image"/>
         </xul:vbox>
         <xul:vbox flex="1">
           <xul:label xbl:inherits="value=target,tooltiptext=target"
@@ -143,30 +144,30 @@
           <xul:hbox>
             <xul:vbox flex="1">
               <xul:progressmeter mode="normal" value="0" flex="1"
                                  anonid="progressmeter"
                                  xbl:inherits="value=progress,mode=progressmode"/>
             </xul:vbox>
             <xul:button class="resume mini-button" tooltiptext="&cmd.resume.label;"
                         cmd="cmd_resume" ondblclick="event.stopPropagation();"
-                        oncommand="performCommand('cmd_resume', this);"/>
+                        oncommand="gDownloadList.performCommand('cmd_resume', this);"/>
             <xul:button class="cancel mini-button" tooltiptext="&cmd.cancel.label;"
                         cmd="cmd_cancel" ondblclick="event.stopPropagation();"
-                        oncommand="performCommand('cmd_cancel', this);"/>
+                        oncommand="gDownloadList.performCommand('cmd_cancel', this);"/>
           </xul:hbox>
           <xul:label xbl:inherits="value=status,tooltiptext=statusTip" flex="1"
                      crop="right" class="status"/>
           <xul:spacer flex="1"/>
         </xul:vbox>
       </xul:hbox>
     </content>
   </binding>
-  
-  <binding id="download-done" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+
+  <binding id="download-done" extends="chrome://webapprt/content/downloads/download.xml#download-base">
     <content>
       <xul:hbox flex="1">
         <xul:vbox pack="center">
           <xul:image class="downloadTypeIcon" validate="always"
                      xbl:inherits="src=image"/>
         </xul:vbox>
         <xul:vbox pack="start" flex="1">
           <xul:hbox align="center" flex="1">
@@ -176,20 +177,20 @@
                        class="dateTime"/>
           </xul:hbox>
           <xul:hbox align="center" flex="1">
             <xul:label xbl:inherits="value=status,tooltiptext=statusTip"
                        crop="end" flex="1" class="status"/>
           </xul:hbox>
         </xul:vbox>
       </xul:hbox>
-    </content>  
+    </content>
   </binding>
-  
-  <binding id="download-canceled" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+
+  <binding id="download-canceled" extends="chrome://webapprt/content/downloads/download.xml#download-base">
     <content>
       <xul:hbox flex="1">
         <xul:vbox pack="center">
           <xul:image class="downloadTypeIcon" validate="always"
                      xbl:inherits="src=image"/>
         </xul:vbox>
         <xul:vbox pack="start" flex="1">
           <xul:hbox align="center" flex="1">
@@ -198,24 +199,24 @@
             <xul:label xbl:inherits="value=dateTime,tooltiptext=dateTimeTip"
                        class="dateTime"/>
           </xul:hbox>
           <xul:hbox align="center" flex="1">
             <xul:label xbl:inherits="value=status,tooltiptext=statusTip"
                        crop="end" flex="1" class="status"/>
             <xul:button class="retry mini-button" tooltiptext="&cmd.retry.label;"
                         cmd="cmd_retry" ondblclick="event.stopPropagation();"
-                        oncommand="performCommand('cmd_retry', this);"/>
+                        oncommand="gDownloadList.performCommand('cmd_retry', this);"/>
           </xul:hbox>
         </xul:vbox>
       </xul:hbox>
-    </content>  
+    </content>
   </binding>
-  
-  <binding id="download-failed" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+
+  <binding id="download-failed" extends="chrome://webapprt/content/downloads/download.xml#download-base">
     <content>
       <xul:hbox flex="1">
         <xul:vbox pack="center">
           <xul:image class="downloadTypeIcon" validate="always"
                      xbl:inherits="src=image"/>
         </xul:vbox>
         <xul:vbox pack="start" flex="1">
           <xul:hbox align="center" flex="1">
@@ -224,24 +225,24 @@
             <xul:label xbl:inherits="value=dateTime,tooltiptext=dateTimeTip"
                        class="dateTime"/>
           </xul:hbox>
           <xul:hbox align="center" flex="1">
             <xul:label xbl:inherits="value=status,tooltiptext=statusTip"
                        crop="end" flex="1" class="status"/>
             <xul:button class="retry mini-button" tooltiptext="&cmd.retry.label;"
                         cmd="cmd_retry" ondblclick="event.stopPropagation();"
-                        oncommand="performCommand('cmd_retry', this);"/>
+                        oncommand="gDownloadList.performCommand('cmd_retry', this);"/>
           </xul:hbox>
         </xul:vbox>
       </xul:hbox>
-    </content>  
+    </content>
   </binding>
 
-  <binding id="download-blocked-parental" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+  <binding id="download-blocked-parental" extends="chrome://webapprt/content/downloads/download.xml#download-base">
     <content>
       <xul:hbox flex="1">
         <xul:vbox pack="center">
           <xul:image class="downloadTypeIcon blockedIcon"/>
         </xul:vbox>
         <xul:vbox pack="start" flex="1">
           <xul:hbox align="center" flex="1">
             <xul:label xbl:inherits="value=target,tooltiptext=target"
@@ -253,17 +254,17 @@
             <xul:label xbl:inherits="value=status,tooltiptext=statusTip"
                        crop="end" flex="1" class="status"/>
           </xul:hbox>
         </xul:vbox>
       </xul:hbox>
     </content>
   </binding>
 
-  <binding id="download-blocked-policy" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+  <binding id="download-blocked-policy" extends="chrome://webapprt/content/downloads/download.xml#download-base">
     <content>
       <xul:hbox flex="1">
         <xul:vbox pack="center">
           <xul:image class="downloadTypeIcon blockedIcon"/>
         </xul:vbox>
         <xul:vbox pack="start" flex="1">
           <xul:hbox align="center" flex="1">
             <xul:label xbl:inherits="value=target,tooltiptext=target"
@@ -275,17 +276,17 @@
             <xul:label xbl:inherits="value=status,tooltiptext=statusTip"
                        crop="end" flex="1" class="status"/>
           </xul:hbox>
         </xul:vbox>
       </xul:hbox>
     </content>
   </binding>
 
-  <binding id="download-scanning" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+  <binding id="download-scanning" extends="chrome://webapprt/content/downloads/download.xml#download-base">
     <content>
       <xul:hbox flex="1">
         <xul:vbox pack="center">
           <xul:image class="downloadTypeIcon" validate="always"
                      xbl:inherits="src=image"/>
         </xul:vbox>
         <xul:vbox pack="start" flex="1">
           <xul:label xbl:inherits="value=target,tooltiptext=target"
@@ -294,20 +295,20 @@
             <xul:vbox flex="1">
               <xul:progressmeter mode="undetermined" flex="1" />
             </xul:vbox>
           </xul:hbox>
           <xul:label value="&scanning.label;" class="status"/>
           <xul:spacer flex="1"/>
         </xul:vbox>
       </xul:hbox>
-    </content>  
+    </content>
   </binding>
 
-  <binding id="download-dirty" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+  <binding id="download-dirty" extends="chrome://webapprt/content/downloads/download.xml#download-base">
     <content>
       <xul:hbox flex="1">
         <xul:vbox pack="center">
           <xul:image class="downloadTypeIcon blockedIcon"/>
         </xul:vbox>
         <xul:vbox pack="start" flex="1">
           <xul:hbox align="center" flex="1">
             <xul:label xbl:inherits="value=target,tooltiptext=target"
copy from toolkit/mozapps/downloads/content/downloads.css
copy to webapprt/content/downloads/downloads.css
--- a/toolkit/mozapps/downloads/content/downloads.css
+++ b/webapprt/content/downloads/downloads.css
@@ -1,50 +1,50 @@
 /* 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/. */
 
 richlistitem[type="download"] {
-  -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-starting');
+  -moz-binding: url('chrome://webapprt/content/downloads/download.xml#download-starting');
   -moz-box-orient: vertical;
 }
 
 richlistitem[type="download"][state="0"] {
-  -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-downloading');
+  -moz-binding: url('chrome://webapprt/content/downloads/download.xml#download-downloading');
 }
 
 richlistitem[type="download"][state="1"] {
-  -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-done');
+  -moz-binding: url('chrome://webapprt/content/downloads/download.xml#download-done');
 }
 
 richlistitem[type="download"][state="2"] {
-  -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-failed');
+  -moz-binding: url('chrome://webapprt/content/downloads/download.xml#download-failed');
 }
 
 richlistitem[type="download"][state="3"] {
-  -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-canceled');
+  -moz-binding: url('chrome://webapprt/content/downloads/download.xml#download-canceled');
 }
 
 richlistitem[type="download"][state="4"] {
-  -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-paused');
+  -moz-binding: url('chrome://webapprt/content/downloads/download.xml#download-paused');
 }
 
 richlistitem[type="download"][state="6"] {
-  -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-blocked-parental');
+  -moz-binding: url('chrome://webapprt/content/downloads/download.xml#download-blocked-parental');
 }
 
 richlistitem[type="download"][state="7"] {
-  -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-scanning');
+  -moz-binding: url('chrome://webapprt/content/downloads/download.xml#download-scanning');
 }
 
 richlistitem[type="download"][state="8"] {
-  -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-dirty');
+  -moz-binding: url('chrome://webapprt/content/downloads/download.xml#download-dirty');
 }
 
 richlistitem[type="download"][state="9"] {
-  -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-blocked-policy');
+  -moz-binding: url('chrome://webapprt/content/downloads/download.xml#download-blocked-policy');
 }
 
 /* Only focus buttons in the selected item*/
 richlistitem[type="download"]:not([selected="true"]) button {
   -moz-user-focus: none;
 }
 
new file mode 100644
--- /dev/null
+++ b/webapprt/content/downloads/downloads.js
@@ -0,0 +1,1134 @@
+/* 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/. */
+
+const { interfaces: Ci, utils: Cu, classes: Cc } = Components;
+
+const nsIDM = Ci.nsIDownloadManager;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+  "resource://gre/modules/NetUtil.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
+  "resource://gre/modules/DownloadUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+  "resource://gre/modules/PluralForm.jsm");
+
+let gStr = {};
+
+let DownloadItem = function(aID, aDownload) {
+  this.id = aID;
+  this._download = aDownload;
+
+  this.file = aDownload.target.path;
+  this.target = OS.Path.basename(aDownload.target.path);
+  this.uri = aDownload.source.url;
+  this.endTime = new Date();
+
+  this.updateState();
+
+  this.updateData();
+
+  this.createItem();
+};
+
+DownloadItem.prototype = {
+  /**
+   * The XUL element corresponding to the associated richlistbox item.
+   */
+  element: null,
+
+  /**
+   * The inner XUL element for the progress bar, or null if not available.
+   */
+  _progressElement: null,
+
+  _lastEstimatedSecondsLeft: Infinity,
+
+  updateState: function() {
+    // Collapse state using the correct priority.
+    if (this._download.succeeded) {
+      this.state = nsIDM.DOWNLOAD_FINISHED;
+    } else if (this._download.error &&
+               this._download.error.becauseBlockedByParentalControls) {
+      this.state = nsIDM.DOWNLOAD_BLOCKED_PARENTAL;
+    } else if (this._download.error &&
+               this._download.error.becauseBlockedByReputationCheck) {
+      this.state = nsIDM.DOWNLOAD_DIRTY;
+    } else if (this._download.error) {
+      this.state = nsIDM.DOWNLOAD_FAILED;
+    } else if (this._download.canceled && this._download.hasPartialData) {
+      this.state = nsIDM.DOWNLOAD_PAUSED;
+    } else if (this._download.canceled) {
+      this.state = nsIDM.DOWNLOAD_CANCELED;
+    } else if (this._download.stopped) {
+      this.state = nsIDM.DOWNLOAD_NOTSTARTED;
+    } else {
+      this.state = nsIDM.DOWNLOAD_DOWNLOADING;
+    }
+  },
+
+  updateData: function() {
+    let wasInProgress = this.inProgress;
+
+    this.updateState();
+
+    if (wasInProgress && !this.inProgress) {
+      this.endTime = new Date();
+    }
+
+    this.referrer = this._download.source.referrer;
+    this.startTime = this._download.startTime;
+    this.currBytes = this._download.currentBytes;
+    this.resumable = this._download.hasPartialData;
+    this.speed = this._download.speed;
+
+    if (this._download.succeeded) {
+      // If the download succeeded, show the final size if available, otherwise
+      // use the last known number of bytes transferred.  The final size on disk
+      // will be available when bug 941063 is resolved.
+      this.maxBytes = this._download.hasProgress ? this._download.totalBytes
+                                                 : this._download.currentBytes;
+      this.percentComplete = 100;
+    } else if (this._download.hasProgress) {
+      // If the final size and progress are known, use them.
+      this.maxBytes = this._download.totalBytes;
+      this.percentComplete = this._download.progress;
+    } else {
+      // The download final size and progress percentage is unknown.
+      this.maxBytes = -1;
+      this.percentComplete = -1;
+    }
+  },
+
+  /**
+   * 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 can be removed.
+   */
+  get removable() {
+    return [
+      nsIDM.DOWNLOAD_FINISHED,
+      nsIDM.DOWNLOAD_CANCELED,
+      nsIDM.DOWNLOAD_BLOCKED_PARENTAL,
+      nsIDM.DOWNLOAD_BLOCKED_POLICY,
+      nsIDM.DOWNLOAD_DIRTY,
+      nsIDM.DOWNLOAD_FAILED,
+    ].indexOf(this.state) != -1;
+  },
+
+  /**
+   * Indicates whether the download is finished and can be opened.
+   */
+  get openable() {
+    return this.state == nsIDM.DOWNLOAD_FINISHED;
+  },
+
+  /**
+   * Indicates whether the download stopped because of an error, and can be
+   * resumed manually.
+   */
+  get canRetry() {
+    return this.state == nsIDM.DOWNLOAD_CANCELED ||
+           this.state == nsIDM.DOWNLOAD_FAILED;
+  },
+
+  /**
+   * Returns the nsILocalFile for the download target.
+   *
+   * @throws if the native path is not valid.  This can happen if the same
+   *         profile is used on different platforms, for example if a native
+   *         Windows path is stored and then the item is accessed on a Mac.
+   */
+  get localFile() {
+    return this._getFile(this.file);
+  },
+
+  /**
+   * 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.
+   */
+  get partFile() {
+    return this._getFile(this.file + ".part");
+  },
+
+  /**
+   * Return a nsILocalFile for aFilename. aFilename might be a file URL or
+   * a native path.
+   *
+   * @param aFilename the filename of the file to retrieve.
+   * @return an nsILocalFile for the file.
+   * @throws if the native path is not valid.  This can happen if the same
+   *         profile is used on different platforms, for example if a native
+   *         Windows path is stored and then the item is accessed on a Mac.
+   * @note This function makes no guarantees about the file's existence -
+   *       callers should check that the returned file exists.
+   */
+  _getFile: function(aFilename) {
+    // The download database may contain targets stored as file URLs or native
+    // paths.  This can still be true for previously stored items, even if new
+    // items are stored using their file URL.  See also bug 239948 comment 12.
+    if (aFilename.startsWith("file:")) {
+      // Assume the file URL we obtained from the downloads database or from the
+      // "spec" property of the target has the UTF-8 charset.
+      let fileUrl = NetUtil.newURI(aFilename).QueryInterface(Ci.nsIFileURL);
+      return fileUrl.file;
+    }
+
+    // The downloads database contains a native path.  Try to create a local
+    // file, though this may throw an exception if the path is invalid.
+    let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+    file.initWithPath(aFilename);
+    return file;
+  },
+
+  /**
+   * Show a downloaded file in the system file manager.
+   */
+  showLocalFile: function() {
+    let file = this.localFile;
+
+    try {
+      // Show the directory containing the file and select the file.
+      file.reveal();
+    } catch (ex) {
+      // If reveal fails for some reason (e.g., it's not implemented on unix
+      // or the file doesn't exist), try using the parent if we have it.
+      let parent = file.parent;
+      if (!parent) {
+        return;
+      }
+
+      try {
+        // Open the parent directory to show where the file should be.
+        parent.launch();
+      } catch (ex) {
+        // If launch also fails (probably because it's not implemented), let
+        // the OS handler try to open the parent.
+        Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+        getService(Ci.nsIExternalProtocolService).
+        loadUrl(NetUtil.newURI(parent));
+      }
+    }
+  },
+
+  /**
+   * Open the target file for this download.
+   */
+  openLocalFile: function() {
+    return this._download.launch();
+  },
+
+  /**
+   * Pause the download, if it is active.
+   */
+  pause: function() {
+    return this._download.cancel();
+  },
+
+  /**
+   * Resume the download, if it is paused.
+   */
+  resume: function() {
+    return this._download.start();
+  },
+
+  /**
+   * Attempt to retry the download.
+   */
+  retry: function() {
+    return this._download.start();
+  },
+
+  /**
+   * Cancel the download.
+   */
+  cancel: function() {
+    this._download.cancel();
+    return this._download.removePartialData();
+  },
+
+  /**
+   * Remove the download.
+   */
+  remove: function() {
+    return Downloads.getList(Downloads.ALL)
+                    .then(list => list.remove(this._download))
+                    .then(() => this._download.finalize(true));
+  },
+
+  /**
+   * Create a download richlistitem with the provided attributes.
+   */
+  createItem: function() {
+    this.element = document.createElement("richlistitem");
+
+    this.element.setAttribute("id", this.id);
+    this.element.setAttribute("target", this.target);
+    this.element.setAttribute("state", this.state);
+    this.element.setAttribute("progress", this.percentComplete);
+    this.element.setAttribute("type", "download");
+    this.element.setAttribute("image", "moz-icon://" + this.file + "?size=32");
+  },
+
+  /**
+   * Update the status for a download item depending on its state.
+   */
+  updateStatusText: function() {
+    let status = "";
+    let statusTip = "";
+
+    switch (this.state) {
+      case nsIDM.DOWNLOAD_PAUSED:
+        let transfer = DownloadUtils.getTransferTotal(this.currBytes,
+                                                      this.maxBytes);
+        status = gStr.paused.replace("#1", transfer);
+
+        break;
+
+      case nsIDM.DOWNLOAD_DOWNLOADING:
+        let newLast;
+        [status, newLast] =
+          DownloadUtils.getDownloadStatus(this.currBytes, this.maxBytes,
+                                          this.speed,
+                                          this._lastEstimatedSecondsLeft);
+
+        this._lastEstimatedSecondsLeft = newLast;
+
+        break;
+
+      case nsIDM.DOWNLOAD_FINISHED:
+      case nsIDM.DOWNLOAD_FAILED:
+      case nsIDM.DOWNLOAD_CANCELED:
+      case nsIDM.DOWNLOAD_BLOCKED_PARENTAL:
+      case nsIDM.DOWNLOAD_BLOCKED_POLICY:
+      case nsIDM.DOWNLOAD_DIRTY:
+        let (stateSize = {}) {
+          stateSize[nsIDM.DOWNLOAD_FINISHED] = () => {
+            // Display the file size, but show "Unknown" for negative sizes.
+            let sizeText = gStr.doneSizeUnknown;
+            if (this.maxBytes >= 0) {
+              let [size, unit] = DownloadUtils.convertByteUnits(this.maxBytes);
+              sizeText = gStr.doneSize.replace("#1", size);
+              sizeText = sizeText.replace("#2", unit);
+            }
+            return sizeText;
+          };
+          stateSize[nsIDM.DOWNLOAD_FAILED] = () => gStr.stateFailed;
+          stateSize[nsIDM.DOWNLOAD_CANCELED] = () => gStr.stateCanceled;
+          stateSize[nsIDM.DOWNLOAD_BLOCKED_PARENTAL] = () => gStr.stateBlocked;
+          stateSize[nsIDM.DOWNLOAD_BLOCKED_POLICY] = () => gStr.stateBlockedPolicy;
+          stateSize[nsIDM.DOWNLOAD_DIRTY] = () => gStr.stateDirty;
+
+          // Insert 1 is the download size or download state.
+          status = gStr.doneStatus.replace("#1", stateSize[this.state]());
+        }
+
+        let [displayHost, fullHost] =
+          DownloadUtils.getURIHost(this.referrer || this.uri);
+        // Insert 2 is the eTLD + 1 or other variations of the host.
+        status = status.replace("#2", displayHost);
+        // Set the tooltip to be the full host.
+        statusTip = fullHost;
+
+        break;
+    }
+
+    this.element.setAttribute("status", status);
+    this.element.setAttribute("statusTip", statusTip || status);
+  },
+
+  updateView: function() {
+    this.updateData();
+
+    // Update this download's progressmeter.
+    if (this.starting) {
+      // Before the download starts, the progress meter has its initial value.
+      this.element.setAttribute("progressmode", "normal");
+      this.element.setAttribute("progress", "0");
+    } else if (this.state == Ci.nsIDownloadManager.DOWNLOAD_SCANNING ||
+               this.percentComplete == -1) {
+      // We might not know the progress of a running download, and we don't know
+      // the remaining time during the malware scanning phase.
+      this.element.setAttribute("progressmode", "undetermined");
+    } else {
+      // This is a running download of which we know the progress.
+      this.element.setAttribute("progressmode", "normal");
+      this.element.setAttribute("progress", this.percentComplete);
+    }
+
+    // Find the progress element as soon as the download binding is accessible.
+    if (!this._progressElement) {
+      this._progressElement =
+           document.getAnonymousElementByAttribute(this.element, "anonid",
+                                                   "progressmeter");
+    }
+
+    // Dispatch the ValueChange event for accessibility, if possible.
+    if (this._progressElement) {
+      let event = document.createEvent("Events");
+      event.initEvent("ValueChange", true, true);
+      this._progressElement.dispatchEvent(event);
+    }
+
+    // Update the rest of the UI (bytes transferred, bytes total, download rate,
+    // time remaining).
+
+    // Update the time that gets shown for completed download items
+    // Don't bother updating it for things that aren't finished
+    if (!this.inProgress) {
+      let [dateCompact, dateComplete] = DownloadUtils.getReadableDates(this.endTime);
+      this.element.setAttribute("dateTime", dateCompact);
+      this.element.setAttribute("dateTimeTip", dateComplete);
+    }
+
+    this.element.setAttribute("state", this.state);
+
+    this.updateStatusText();
+
+    // Update the disabled state of the buttons of a download
+    let buttons = this.element.buttons;
+    for (let i = 0; i < buttons.length; ++i) {
+      let cmd = buttons[i].getAttribute("cmd");
+      let enabled = this.isCommandEnabled(cmd);
+      buttons[i].disabled = !enabled;
+
+      if ("cmd_pause" == cmd && !enabled) {
+        // We need to add the tooltip indicating that the download cannot be
+        // paused now.
+        buttons[i].setAttribute("tooltiptext", gStr.cannotPause);
+      }
+    }
+
+    if (this.done) {
+      // getTypeFromFile fails if it can't find a type for this file.
+      try {
+        // Refresh the icon, so that executable icons are shown.
+        let mimeService = Cc["@mozilla.org/mime;1"].
+                          getService(Ci.nsIMIMEService);
+        let contentType = mimeService.getTypeFromFile(this.localFile);
+
+        let oldImage = this.element.getAttribute("image");
+        // Tacking on contentType bypasses cache
+        this.element.setAttribute("image", oldImage + "&contentType=" + contentType);
+      } catch (e) {}
+    }
+  },
+
+  /**
+   * Check if the download matches the provided search term based on the texts
+   * shown to the user. All search terms are checked to see if each matches any
+   * of the displayed texts.
+   *
+   * @return Boolean true if it matches the search; false otherwise
+   */
+  matchesSearch: function(aTerms, aAttributes) {
+    return aTerms.some(term => aAttributes.some(attr => this.element.getAttribute(attr).contains(term)));
+  },
+
+  isCommandEnabled: function(aCommand) {
+    switch (aCommand) {
+      case "cmd_cancel":
+        return this.inProgress;
+
+      case "cmd_open":
+        return this.openable && this.localFile.exists();
+
+      case "cmd_show":
+        return this.localFile.exists();
+
+      case "cmd_pause":
+        return this.inProgress && !this.paused && this.resumable;
+
+      case "cmd_pauseResume":
+        return (this.inProgress || this.paused) && this.resumable;
+
+      case "cmd_resume":
+        return this.paused && this.resumable;
+
+      case "cmd_openReferrer":
+        return !!this.referrer;
+
+      case "cmd_removeFromList":
+        return this.removable;
+
+      case "cmd_retry":
+        return this.canRetry;
+
+      case "cmd_copyLocation":
+        return true;
+    }
+
+    return false;
+  },
+
+  doCommand: function(aCommand) {
+    if (!this.isCommandEnabled(aCommand)) {
+      return;
+    }
+
+    switch (aCommand) {
+      case "cmd_cancel":
+        this.cancel().catch(Cu.reportError);
+        break;
+
+      case "cmd_open":
+        this.openLocalFile().catch(Cu.reportError);
+        break;
+
+      case "cmd_show":
+        this.showLocalFile();
+        break;
+
+      case "cmd_pause":
+        this.pause().catch(Cu.reportError);
+        break;
+
+      case "cmd_pauseResume":
+        if (this.paused) {
+          this.resume().catch(Cu.reportError);
+        } else {
+          this.pause().catch(Cu.reportError);
+        }
+        break;
+
+      case "cmd_resume":
+        this.resume().catch(Cu.reportError);
+        break;
+
+      case "cmd_openReferrer":
+        let uri = Services.io.newURI(this.referrer || this.uri, null, null);
+
+        // Direct the URL to the default browser.
+        Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+        getService(Ci.nsIExternalProtocolService).
+        getProtocolHandlerInfo(uri.scheme).
+        launchWithURI(uri);
+        break;
+
+      case "cmd_removeFromList":
+        this.remove().catch(Cu.reportError);
+        break;
+
+      case "cmd_retry":
+        this.retry().catch(Cu.reportError);
+        break;
+
+      case "cmd_copyLocation":
+        let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+                        getService(Ci.nsIClipboardHelper);
+        clipboard.copyString(this.uri, document);
+        break;
+    }
+  },
+};
+
+let gDownloadList = {
+  downloadItemsMap: new Map(),
+  downloadItems: {},
+  _autoIncrementID: 0,
+  downloadView: null,
+  searchBox: null,
+  searchTerms: [],
+  searchAttributes: [ "target", "status", "dateTime", ],
+  contextMenus: [
+    // DOWNLOAD_DOWNLOADING
+    [
+      "menuitem_pause",
+      "menuitem_cancel",
+      "menuseparator",
+      "menuitem_show",
+      "menuseparator",
+      "menuitem_openReferrer",
+      "menuitem_copyLocation",
+      "menuseparator",
+      "menuitem_selectAll",
+    ],
+    // DOWNLOAD_FINISHED
+    [
+      "menuitem_open",
+      "menuitem_show",
+      "menuseparator",
+      "menuitem_openReferrer",
+      "menuitem_copyLocation",
+      "menuseparator",
+      "menuitem_selectAll",
+      "menuseparator",
+      "menuitem_removeFromList"
+    ],
+    // DOWNLOAD_FAILED
+    [
+      "menuitem_retry",
+      "menuseparator",
+      "menuitem_openReferrer",
+      "menuitem_copyLocation",
+      "menuseparator",
+      "menuitem_selectAll",
+      "menuseparator",
+      "menuitem_removeFromList",
+    ],
+    // DOWNLOAD_CANCELED
+    [
+      "menuitem_retry",
+      "menuseparator",
+      "menuitem_openReferrer",
+      "menuitem_copyLocation",
+      "menuseparator",
+      "menuitem_selectAll",
+      "menuseparator",
+      "menuitem_removeFromList",
+    ],
+    // DOWNLOAD_PAUSED
+    [
+      "menuitem_resume",
+      "menuitem_cancel",
+      "menuseparator",
+      "menuitem_show",
+      "menuseparator",
+      "menuitem_openReferrer",
+      "menuitem_copyLocation",
+      "menuseparator",
+      "menuitem_selectAll",
+    ],
+    // DOWNLOAD_QUEUED
+    [
+      "menuitem_cancel",
+      "menuseparator",
+      "menuitem_show",
+      "menuseparator",
+      "menuitem_openReferrer",
+      "menuitem_copyLocation",
+      "menuseparator",
+      "menuitem_selectAll",
+    ],
+    // DOWNLOAD_BLOCKED_PARENTAL
+    [
+      "menuitem_openReferrer",
+      "menuitem_copyLocation",
+      "menuseparator",
+      "menuitem_selectAll",
+      "menuseparator",
+      "menuitem_removeFromList",
+    ],
+    // DOWNLOAD_SCANNING
+    [
+      "menuitem_show",
+      "menuseparator",
+      "menuitem_openReferrer",
+      "menuitem_copyLocation",
+      "menuseparator",
+      "menuitem_selectAll",
+    ],
+    // DOWNLOAD_DIRTY
+    [
+      "menuitem_openReferrer",
+      "menuitem_copyLocation",
+      "menuseparator",
+      "menuitem_selectAll",
+      "menuseparator",
+      "menuitem_removeFromList",
+    ],
+    // DOWNLOAD_BLOCKED_POLICY
+    [
+      "menuitem_openReferrer",
+      "menuitem_copyLocation",
+      "menuseparator",
+      "menuitem_selectAll",
+      "menuseparator",
+      "menuitem_removeFromList",
+    ]
+  ],
+
+  init: function() {
+    this.downloadView = document.getElementById("downloadView");
+    this.searchBox = document.getElementById("searchbox");
+
+    this.buildList(true);
+
+    this.downloadView.focus();
+
+    // Clear the search box and move focus to the list on escape from the box.
+    this.searchBox.addEventListener("keypress", (e) => {
+      this.searchBoxKeyPressHandler(e);
+    }, false);
+
+    Downloads.getList(Downloads.ALL)
+             .then(list => list.addView(this))
+             .catch(Cu.reportError);
+  },
+
+  buildList: function(aForceBuild) {
+    // Stringify the previous search.
+    let prevSearch = this.searchTerms.join(" ");
+
+    // Array of space-separated lower-case search terms.
+    this.searchTerms = this.searchBox.value.trim().toLowerCase().split(/\s+/);
+
+    // Unless forced, don't rebuild the download list if the search didn't change.
+    if (!aForceBuild && this.searchTerms.join(" ") == prevSearch) {
+      return;
+    }
+
+    // Clear the list before adding items by replacing with a shallow copy.
+    let (empty = this.downloadView.cloneNode(false)) {
+      this.downloadView.parentNode.replaceChild(empty, this.downloadView);
+      this.downloadView = empty;
+    }
+
+    for each (let downloadItem in this.downloadItems) {
+      if (downloadItem.inProgress ||
+          downloadItem.matchesSearch(this.searchTerms, this.searchAttributes)) {
+        this.downloadView.appendChild(downloadItem.element);
+
+        // Because of the joys of XBL, we can't update the buttons until the
+        // download object is in the document.
+        downloadItem.updateView();
+      }
+    }
+
+    this.updateClearListButton();
+  },
+
+  /**
+   * Remove the finished downloads from the shown download list.
+   */
+  clearList: Task.async(function*() {
+    let searchTerms = this.searchTerms;
+
+    let list = yield Downloads.getList(Downloads.ALL);
+
+    yield list.removeFinished((aDownload) => {
+      let downloadItem = this.downloadItemsMap.get(aDownload);
+      if (!downloadItem) {
+        Cu.reportError("Download doesn't exist.");
+        return;
+      }
+
+      return downloadItem.matchesSearch(searchTerms, this.searchAttributes);
+    });
+
+    // Clear the input as if the user did it and move focus to the list.
+    this.searchBox.value = "";
+    this.searchBox.doCommand();
+    this.downloadView.focus();
+  }),
+
+  /**
+   * Update the disabled state of the clear list button based on whether or not
+   * there are items in the list that can potentially be removed.
+   */
+  updateClearListButton: function() {
+    let button = document.getElementById("clearListButton");
+
+    // The button is enabled if we have items in the list that we can clean up.
+    for each (let downloadItem in this.downloadItems) {
+      if (!downloadItem.inProgress &&
+          downloadItem.matchesSearch(this.searchTerms, this.searchAttributes)) {
+        button.disabled = false;
+        return;
+      }
+    }
+
+    button.disabled = true;
+  },
+
+  setSearchboxFocus: function() {
+    this.searchBox.focus();
+    this.searchBox.select();
+  },
+
+  searchBoxKeyPressHandler: function(aEvent) {
+    if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
+      // Move focus to the list instead of closing the window.
+      this.downloadView.focus();
+      aEvent.preventDefault();
+    }
+  },
+
+  buildContextMenu: function(aEvent) {
+    if (aEvent.target.id != "downloadContextMenu") {
+      return false;
+    }
+
+    let popup = document.getElementById("downloadContextMenu");
+    while (popup.hasChildNodes()) {
+      popup.removeChild(popup.firstChild);
+    }
+
+    if (this.downloadView.selectedItem) {
+      let dl = this.downloadView.selectedItem;
+      let downloadItem = this.downloadItems[dl.getAttribute("id")];
+
+      let idx = downloadItem.state;
+      if (idx < 0) {
+        idx = 0;
+      }
+
+      let menus = this.contextMenus[idx];
+      for (let i = 0; i < menus.length; ++i) {
+        let menuitem = document.getElementById(menus[i]).cloneNode(true);
+        let cmd = menuitem.getAttribute("cmd");
+        if (cmd) {
+          menuitem.disabled = !downloadItem.isCommandEnabled(cmd);
+        }
+
+        popup.appendChild(menuitem);
+      }
+
+      return true;
+    }
+
+    return false;
+  },
+
+  /**
+   * Perform the default action for the currently selected download item.
+   */
+  doDefaultForSelected: function() {
+    // Make sure we have something selected.
+    let item = this.downloadView.selectedItem;
+    if (!item) {
+      return;
+    }
+
+    let download = this.downloadItems[item.getAttribute("id")];
+
+    // Get the default action (first item in the menu).
+    let menuitem = document.getElementById(this.contextMenus[download.state][0]);
+
+    // Try to do the action if the command is enabled.
+    download.doCommand(menuitem.getAttribute("cmd"));
+  },
+
+  onDownloadDblClick: function(aEvent) {
+    // Only do the default action for double primary clicks.
+    if (aEvent.button == 0 && aEvent.target.selected) {
+      this.doDefaultForSelected();
+    }
+  },
+
+  /**
+   * Helper function to do commands.
+   *
+   * @param aCmd
+   *        The command to be performed.
+   * @param aItem
+   *        The richlistitem that represents the download that will have the
+   *        command performed on it. If this is null, the command is performed on
+   *        all downloads. If the item passed in is not a richlistitem that
+   *        represents a download, it will walk up the parent nodes until it finds
+   *        a DOM node that is.
+   */
+  performCommand: function(aCmd, aItem) {
+    if (!aItem) {
+      // Convert the nodelist into an array to keep a copy of the download items.
+      let items = Array.from(this.downloadView.selectedItems);
+
+      // Do the command for each download item.
+      for each (let item in items) {
+        this.performCommand(aCmd, item);
+      }
+
+      return;
+    }
+
+    let elm = aItem;
+
+    while (elm.nodeName != "richlistitem" ||
+           elm.getAttribute("type") != "download") {
+      elm = elm.parentNode;
+    }
+
+    let downloadItem = this.downloadItems[elm.getAttribute("id")];
+    downloadItem.doCommand(aCmd);
+  },
+
+  onDragStart: function(aEvent) {
+    if (!this.downloadView.selectedItem) {
+      return;
+    }
+
+    let dl = this.downloadView.selectedItem;
+    let downloadItem = this.downloadItems[dl.getAttribute("id")];
+    let f = downloadItem.localFile;
+    if (!f.exists()) {
+      return;
+    }
+
+    let dt = aEvent.dataTransfer;
+    dt.mozSetDataAt("application/x-moz-file", f, 0);
+    let url = Services.io.newFileURI(f).spec;
+    dt.setData("text/uri-list", url);
+    dt.setData("text/plain", url);
+    dt.effectAllowed = "copyMove";
+    dt.addElement(dl);
+  },
+
+  onDragOver: function(aEvent) {
+    let types = aEvent.dataTransfer.types;
+    if (types.contains("text/uri-list") ||
+        types.contains("text/x-moz-url") ||
+        types.contains("text/plain")) {
+      aEvent.preventDefault();
+    }
+
+    aEvent.stopPropagation();
+  },
+
+  onDrop: function(aEvent) {
+    let dt = aEvent.dataTransfer;
+    // If dragged item is from our source, do not try to
+    // redownload already downloaded file.
+    if (dt.mozGetDataAt("application/x-moz-file", 0)) {
+      return;
+    }
+
+    let name;
+    let url = dt.getData("URL");
+
+    if (!url) {
+      url = dt.getData("text/x-moz-url") || dt.getData("text/plain");
+      [url, name] = url.split("\n");
+    }
+
+    if (url) {
+      let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document;
+      saveURL(url, name ? name : url, null, true, true, null, sourceDoc);
+    }
+  },
+
+  pasteHandler: function() {
+    let trans = Cc["@mozilla.org/widget/transferable;1"].
+                createInstance(Ci.nsITransferable);
+    trans.init(null);
+    let flavors = ["text/x-moz-url", "text/unicode"];
+    flavors.forEach(trans.addDataFlavor);
+
+    Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard);
+
+    // Getting the data or creating the nsIURI might fail
+    try {
+      let data = {};
+      trans.getAnyTransferData({}, data, {});
+      let [url, name] = data.value.QueryInterface(Ci.nsISupportsString).data.split("\n");
+
+      if (!url) {
+        return;
+      }
+
+      let uri = Services.io.newURI(url, null, null);
+
+      saveURL(uri.spec, name || uri.spec, null, true, true, null, document);
+    } catch (ex) {}
+  },
+
+  selectAll: function() {
+    this.downloadView.selectAll();
+  },
+
+  onUpdateProgress: function() {
+    let numActive = 0;
+    let totalSize = 0;
+    let totalTransferred = 0;
+
+    for each (let downloadItem in this.downloadItems) {
+      if (!downloadItem.inProgress) {
+        continue;
+      }
+
+      numActive++;
+
+      // Only add to total values if we actually know the download size.
+      if (downloadItem.maxBytes > 0 &&
+          downloadItem.state != nsIDM.DOWNLOAD_CANCELED &&
+          downloadItem.state != nsIDM.DOWNLOAD_FAILED) {
+        totalSize += downloadItem.maxBytes;
+        totalTransferred += downloadItem.currBytes;
+      }
+    }
+
+    // Use the default title and reset "last" values if there are no downloads.
+    if (numActive == 0) {
+      document.title = document.documentElement.getAttribute("statictitle");
+      return;
+    }
+
+    // Establish the mean transfer speed and amount downloaded.
+    let mean = totalTransferred;
+
+    // Calculate the percent transferred, unless we don't have a total file size.
+    let title = gStr.downloadsTitlePercent;
+    if (totalSize == 0) {
+      title = gStr.downloadsTitleFiles;
+    } else {
+      mean = Math.floor((totalTransferred / totalSize) * 100);
+    }
+
+    // Get the correct plural form and insert number of downloads and percent.
+    title = PluralForm.get(numActive, title);
+    title = title.replace("#1", numActive);
+    title = title.replace("#2", mean);
+
+    // Update title of window.
+    document.title = title;
+  },
+
+  onDownloadAdded: function(aDownload) {
+    let newID = this._autoIncrementID++;
+
+    let downloadItem = new DownloadItem(newID, aDownload);
+    this.downloadItemsMap.set(aDownload, downloadItem);
+    this.downloadItems[newID] = downloadItem;
+
+    if (downloadItem.inProgress ||
+        downloadItem.matchesSearch(this.searchTerms, this.searchAttributes)) {
+      this.downloadView.appendChild(downloadItem.element);
+
+      // Because of the joys of XBL, we can't update the buttons until the
+      // download object is in the document.
+      downloadItem.updateView();
+
+      // We might have added an item to an empty list, so update button.
+      this.updateClearListButton();
+    }
+  },
+
+  onDownloadChanged: function(aDownload) {
+    let downloadItem = this.downloadItemsMap.get(aDownload);
+    if (!downloadItem) {
+      Cu.reportError("Download doesn't exist.");
+      return;
+    }
+
+    downloadItem.updateView();
+
+    this.onUpdateProgress();
+
+    // The download may have changed state, so update the clear list button.
+    this.updateClearListButton();
+
+    if (downloadItem.done) {
+      // Move the download below active if it should stay in the list.
+      if (downloadItem.matchesSearch(this.searchTerms, this.searchAttributes)) {
+        // Iterate down until we find a non-active download.
+        let next = this.element.nextSibling;
+        while (next && next.inProgress) {
+          next = next.nextSibling;
+        }
+
+        // Move the item.
+        this.downloadView.insertBefore(this.element, next);
+      } else {
+        this.removeFromView(downloadItem);
+      }
+
+      return true;
+    }
+
+    return false;
+  },
+
+  onDownloadRemoved: function(aDownload) {
+    let downloadItem = this.downloadItemsMap.get(aDownload);
+    if (!downloadItem) {
+      Cu.reportError("Download doesn't exist.");
+      return;
+    }
+
+    this.downloadItemsMap.delete(aDownload);
+    delete this.downloadItems[downloadItem.id];
+
+    this.removeFromView(downloadItem);
+  },
+
+  removeFromView: function(aDownloadItem) {
+    // Make sure we have an item to remove.
+    if (!aDownloadItem.element) {
+      return;
+    }
+
+    let index = this.downloadView.selectedIndex;
+    this.downloadView.removeChild(aDownloadItem.element);
+    this.downloadView.selectedIndex = Math.min(index, this.downloadView.itemCount - 1);
+
+    // We might have removed the last item, so update the clear list button.
+    this.updateClearListButton();
+  },
+};
+
+function Startup() {
+  // Convert strings to those in the string bundle.
+  let (sb = document.getElementById("downloadStrings")) {
+    let strings = ["paused", "cannotPause", "doneStatus", "doneSize",
+                   "doneSizeUnknown", "stateFailed", "stateCanceled",
+                   "stateBlocked", "stateBlockedPolicy", "stateDirty",
+                   "downloadsTitleFiles", "downloadsTitlePercent",];
+
+    for each (let name in strings) {
+      gStr[name] = sb.getString(name);
+    }
+  }
+
+  gDownloadList.init();
+
+  let DownloadTaskbarProgress =
+    Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress;
+  DownloadTaskbarProgress.onDownloadWindowLoad(window);
+}
+
+function Shutdown() {
+  Downloads.getList(Downloads.ALL)
+           .then(list => list.removeView(gDownloadList))
+           .catch(Cu.reportError);
+}
copy from toolkit/mozapps/downloads/content/downloads.xul
copy to webapprt/content/downloads/downloads.xul
--- a/toolkit/mozapps/downloads/content/downloads.xul
+++ b/webapprt/content/downloads/downloads.xul
@@ -7,58 +7,57 @@
 
 #ifdef XP_UNIX
 #ifndef XP_MACOSX
 #define XP_GNOME 1
 #endif
 #endif
 
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
-<?xml-stylesheet href="chrome://mozapps/content/downloads/downloads.css"?>
-<?xml-stylesheet href="chrome://mozapps/skin/downloads/downloads.css"?>
+<?xml-stylesheet href="chrome://webapprt/content/downloads/downloads.css"?>
+<?xml-stylesheet href="chrome://webapprt/skin/downloads/downloads.css"?>
 
 <!DOCTYPE window [
-<!ENTITY % downloadManagerDTD SYSTEM "chrome://mozapps/locale/downloads/downloads.dtd">
+<!ENTITY % downloadManagerDTD SYSTEM "chrome://webapprt/locale/downloads/downloads.dtd">
 %downloadManagerDTD;
 <!ENTITY % editMenuOverlayDTD SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
 %editMenuOverlayDTD;
 ]>
 
 <window xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         id="downloadManager" windowtype="Download:Manager"
         orient="vertical" title="&downloads.title;" statictitle="&downloads.title;"
         width="&window.width2;" height="&window.height;" screenX="10" screenY="10"
         persist="width height screenX screenY sizemode"
         onload="Startup();" onunload="Shutdown();"
         onclose="return closeWindow(false);">
 
-  <script type="application/javascript" src="chrome://mozapps/content/downloads/downloads.js"/>
-  <script type="application/javascript" src="chrome://mozapps/content/downloads/DownloadProgressListener.js"/>
+  <script type="application/javascript" src="chrome://webapprt/content/downloads/downloads.js"/>
   <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
   <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
 
   <stringbundleset id="downloadSet">
     <stringbundle id="brandStrings" src="chrome://branding/locale/brand.properties"/>
     <stringbundle id="downloadStrings" src="chrome://mozapps/locale/downloads/downloads.properties"/>
   </stringbundleset>
 
   <!-- Use this commandset for command which do not depened on focus or selection -->
   <commandset id="generalCommands">
-    <command id="cmd_findDownload" oncommand="setSearchboxFocus();"/>
-    <command id="cmd_selectAllDownloads" oncommand="gDownloadsView.selectAll();"/>
-    <command id="cmd_clearList" oncommand="clearDownloadList();"/>
+    <command id="cmd_findDownload" oncommand="gDownloadList.setSearchboxFocus();"/>
+    <command id="cmd_selectAllDownloads" oncommand="gDownloadList.selectAll();"/>
+    <command id="cmd_clearList" oncommand="gDownloadList.clearList();"/>
   </commandset>
 
   <keyset id="downloadKeys">
-    <key keycode="VK_RETURN" oncommand="doDefaultForSelected();"/>
-    <key id="key_pauseResume" key=" " oncommand="performCommand('cmd_pauseResume');"/>
-    <key id="key_removeFromList"  keycode="VK_DELETE" oncommand="performCommand('cmd_removeFromList');"/>
+    <key keycode="VK_RETURN" oncommand="gDownloadList.doDefaultForSelected();"/>
+    <key id="key_pauseResume" key=" " oncommand="gDownloadList.performCommand('cmd_pauseResume');"/>
+    <key id="key_removeFromList"  keycode="VK_DELETE" oncommand="gDownloadList.performCommand('cmd_removeFromList');"/>
 #ifdef XP_MACOSX
-    <key id="key_removeFromList2" keycode="VK_BACK" oncommand="performCommand('cmd_removeFromList');"/>
+    <key id="key_removeFromList2" keycode="VK_BACK" oncommand="gDownloadList.performCommand('cmd_removeFromList');"/>
 #endif
     <key id="key_close"   key="&cmd.close.commandKey;"  oncommand="closeWindow(true);"    modifiers="accel"/>
 #ifdef XP_GNOME
     <key id="key_close2"  key="&cmd.close2Unix.commandKey;" oncommand="closeWindow(true);" modifiers="accel,shift"/>
 #else
     <key id="key_close2"  key="&cmd.close2.commandKey;" oncommand="closeWindow(true);" modifiers="accel"/>
 #endif
     <key                  keycode="VK_ESCAPE"           oncommand="closeWindow(true);"/>
@@ -73,92 +72,92 @@
          command="cmd_findDownload"/>
     <key id="key_selectAllDownloads"
          key="&selectAllCmd.key;"
          modifiers="accel"
          command="cmd_selectAllDownloads"/>
     <key id="pasteKey"
          key="V"
          modifiers="accel"
-         oncommand="pasteHandler();"/>
+         oncommand="gDownloadList.pasteHandler();"/>
   </keyset>
 
   <vbox id="contextMenuPalette" hidden="true">
     <menuitem id="menuitem_pause"
               label="&cmd.pause.label;" accesskey="&cmd.pause.accesskey;"
-              oncommand="performCommand('cmd_pause');"
+              oncommand="gDownloadList.performCommand('cmd_pause');"
               cmd="cmd_pause"/>
     <menuitem id="menuitem_resume"
               label="&cmd.resume.label;" accesskey="&cmd.resume.accesskey;"
-              oncommand="performCommand('cmd_resume');"
+              oncommand="gDownloadList.performCommand('cmd_resume');"
               cmd="cmd_resume"/>
     <menuitem id="menuitem_cancel"
               label="&cmd.cancel.label;" accesskey="&cmd.cancel.accesskey;"
-              oncommand="performCommand('cmd_cancel');"
+              oncommand="gDownloadList.performCommand('cmd_cancel');"
               cmd="cmd_cancel"/>
 
     <menuitem id="menuitem_open" default="true"
               label="&cmd.open.label;" accesskey="&cmd.open.accesskey;"
-              oncommand="performCommand('cmd_open');"
+              oncommand="gDownloadList.performCommand('cmd_open');"
               cmd="cmd_open"/>
     <menuitem id="menuitem_show"
 #ifdef XP_MACOSX
               label="&cmd.showMac.label;"
               accesskey="&cmd.showMac.accesskey;"
 #else
               label="&cmd.show.label;"
               accesskey="&cmd.show.accesskey;"
 #endif
-              oncommand="performCommand('cmd_show');"
+              oncommand="gDownloadList.performCommand('cmd_show');"
               cmd="cmd_show"/>
 
     <menuitem id="menuitem_retry" default="true"
               label="&cmd.retry.label;" accesskey="&cmd.retry.accesskey;"
-              oncommand="performCommand('cmd_retry');"
+              oncommand="gDownloadList.performCommand('cmd_retry');"
               cmd="cmd_retry"/>
 
     <menuitem id="menuitem_removeFromList"
               label="&cmd.removeFromList.label;" accesskey="&cmd.removeFromList.accesskey;"
-              oncommand="performCommand('cmd_removeFromList');"
+              oncommand="gDownloadList.performCommand('cmd_removeFromList');"
               cmd="cmd_removeFromList"/>
 
     <menuseparator id="menuseparator"/>
 
     <menuitem id="menuitem_openReferrer"
               label="&cmd.goToDownloadPage.label;"
               accesskey="&cmd.goToDownloadPage.accesskey;"
-              oncommand="performCommand('cmd_openReferrer');"
+              oncommand="gDownloadList.performCommand('cmd_openReferrer');"
               cmd="cmd_openReferrer"/>
 
     <menuitem id="menuitem_copyLocation"
               label="&cmd.copyDownloadLink.label;"
               accesskey="&cmd.copyDownloadLink.accesskey;"
-              oncommand="performCommand('cmd_copyLocation');"
+              oncommand="gDownloadList.performCommand('cmd_copyLocation');"
               cmd="cmd_copyLocation"/>
 
     <menuitem id="menuitem_selectAll"
               label="&selectAllCmd.label;"
               accesskey="&selectAllCmd.accesskey;"
               command="cmd_selectAllDownloads"/>
   </vbox>
-  
-  <menupopup id="downloadContextMenu" onpopupshowing="return buildContextMenu(event);"/>
+
+  <menupopup id="downloadContextMenu" onpopupshowing="return gDownloadList.buildContextMenu(event);"/>
 
   <richlistbox id="downloadView" seltype="multiple" flex="1"
                context="downloadContextMenu"
-               ondblclick="onDownloadDblClick(event);"
-               ondragstart="gDownloadDNDObserver.onDragStart(event);"
-               ondragover="gDownloadDNDObserver.onDragOver(event);event.stopPropagation();"
-               ondrop="gDownloadDNDObserver.onDrop(event)">
+               ondblclick="gDownloadList.onDownloadDblClick(event);"
+               ondragstart="gDownloadList.onDragStart(event);"
+               ondragover="gDownloadList.onDragOver(event);"
+               ondrop="gDownloadList.onDrop(event)">
   </richlistbox>
 
   <windowdragbox id="search" align="center">
     <button id="clearListButton" command="cmd_clearList"
             label="&cmd.clearList.label;"
             accesskey="&cmd.clearList.accesskey;"
             tooltiptext="&cmd.clearList.tooltip;"/>
     <spacer flex="1"/>
     <textbox type="search" id="searchbox" class="compact"
              aria-controls="downloadView"
-             oncommand="buildDownloadList();" placeholder="&searchBox.label;"/>
+             oncommand="gDownloadList.buildList();" placeholder="&searchBox.label;"/>
   </windowdragbox>
 
 </window>
--- a/webapprt/jar.mn
+++ b/webapprt/jar.mn
@@ -7,8 +7,12 @@ webapprt.jar:
 * content/webapp.js                     (content/webapp.js)
 * content/webapp.xul                    (content/webapp.xul)
   content/getUserMediaDialog.xul        (content/getUserMediaDialog.xul)
   content/getUserMediaDialog.js         (content/getUserMediaDialog.js)
   content/mochitest-shared.js           (content/mochitest-shared.js)
   content/mochitest.js                  (content/mochitest.js)
   content/mochitest.xul                 (content/mochitest.xul)
   content/dbg-webapp-actors.js          (content/dbg-webapp-actors.js)
+* content/downloads/downloads.xul       (content/downloads/downloads.xul)
+  content/downloads/downloads.js        (content/downloads/downloads.js)
+  content/downloads/downloads.css       (content/downloads/downloads.css)
+  content/downloads/download.xml        (content/downloads/download.xml)
copy from toolkit/locales/en-US/chrome/mozapps/downloads/downloads.dtd
copy to webapprt/locales/en-US/webapprt/downloads/downloads.dtd
--- a/webapprt/locales/jar.mn
+++ b/webapprt/locales/jar.mn
@@ -3,15 +3,17 @@
 # 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/.
 
 @AB_CD@.jar:
 % locale webapprt @AB_CD@ %locale/webapprt/
     locale/webapprt/webapp.dtd                     (%webapprt/webapp.dtd)
     locale/webapprt/webapp.properties              (%webapprt/webapp.properties)
     locale/webapprt/getUserMediaDialog.dtd         (%webapprt/getUserMediaDialog.dtd)
-    locale/webapprt/appstrings.properties         (%webapprt/overrides/appstrings.properties)
-    locale/webapprt/dom.properties         (%webapprt/overrides/dom.properties)
+    locale/webapprt/appstrings.properties          (%webapprt/overrides/appstrings.properties)
+    locale/webapprt/dom.properties                 (%webapprt/overrides/dom.properties)
+    locale/webapprt/downloads/downloads.dtd        (%webapprt/downloads/downloads.dtd)
+
 
 % locale branding @AB_CD@ resource://webappbranding/
 
 % override chrome://global/locale/appstrings.properties  chrome://webapprt/locale/appstrings.properties
 % override chrome://global/locale/dom/dom.properties  chrome://webapprt/locale/dom.properties
--- a/webapprt/moz.build
+++ b/webapprt/moz.build
@@ -6,35 +6,42 @@
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     DIRS += ['win']
 elif CONFIG['OS_ARCH'] == 'Darwin':
     DIRS += ['mac']
 elif CONFIG['MOZ_ENABLE_GTK']:
     DIRS += ['gtk']
 
-DIRS += ['locales']
+DIRS += [
+    'locales',
+    'themes',
+]
 
 EXTRA_COMPONENTS += [
     'CommandLineHandler.js',
     'components.manifest',
     'ContentPermission.js',
     'DirectoryProvider.js',
     'PaymentUIGlue.js',
 ]
 
 EXTRA_JS_MODULES += [
+    'DownloadView.jsm',
     'RemoteDebugger.jsm',
     'Startup.jsm',
     'WebappManager.jsm',
     'WebappRT.jsm',
     'WebRTCHandler.jsm',
 ]
 
-MOCHITEST_WEBAPPRT_CHROME_MANIFESTS += ['test/chrome/webapprt.ini']
+MOCHITEST_WEBAPPRT_CHROME_MANIFESTS += [
+    'test/chrome/downloads/webapprt.ini',
+    'test/chrome/webapprt.ini',
+]
 MOCHITEST_MANIFESTS += ['test/content/mochitest.ini']
 
 # Place webapprt resources in a separate app dir
 DIST_SUBDIR = 'webapprt'
 export('DIST_SUBDIR')
 
 if CONFIG['MOZ_DEBUG']:
     DEFINES['MOZ_DEBUG'] = 1
--- a/webapprt/test/chrome/browser_download.js
+++ b/webapprt/test/chrome/browser_download.js
@@ -1,58 +1,125 @@
 Cu.import("resource://gre/modules/Services.jsm");
+let { Downloads } = Cu.import("resource://gre/modules/Downloads.jsm", {});
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function getFile(aFilename) {
+  // The download database may contain targets stored as file URLs or native
+  // paths.  This can still be true for previously stored items, even if new
+  // items are stored using their file URL.  See also bug 239948 comment 12.
+  if (aFilename.startsWith("file:")) {
+    // Assume the file URL we obtained from the downloads database or from the
+    // "spec" property of the target has the UTF-8 charset.
+    let fileUrl = NetUtil.newURI(aFilename).QueryInterface(Ci.nsIFileURL);
+    return fileUrl.file.clone();
+  }
+
+  // The downloads database contains a native path.  Try to create a local
+  // file, though this may throw an exception if the path is invalid.
+  let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+  file.initWithPath(aFilename);
+  return file;
+}
+
+let fileDownloaded = false;
+
+let download = new DummyDownload("download.test");
+download.state = 1;
+download.percentComplete = 100;
 
 let winObserver = function(win, topic) {
   if (topic == "domwindowopened") {
     win.addEventListener("load", function onLoadWindow() {
       win.removeEventListener("load", onLoadWindow, false);
 
       if (win.document.documentURI ==
           "chrome://mozapps/content/downloads/unknownContentType.xul") {
         ok(true, "Download dialog shown");
 
-        setTimeout(() => {
+        executeSoon(() => {
           let button = win.document.documentElement.getButton("accept");
           button.disabled = false;
           win.document.documentElement.acceptDialog();
-        }, 0);
+        });
+      } else if (win.document.documentURI ==
+          "chrome://webapprt/content/downloads/downloads.xul") {
+        ok(true, "Download manager window shown");
+        ok(fileDownloaded, "File downloaded");
+
+        waitDownloadListPopulation(win).then(() => {
+          test_downloadList(win, [download]);
+          finish();
+        });
       }
     }, false);
   }
 }
 
 Services.ww.registerNotification(winObserver);
 
 let MockFilePicker = SpecialPowers.MockFilePicker;
 MockFilePicker.init(window);
-MockFilePicker.useAnyFile();
+MockFilePicker.returnFiles = [download.file];
 MockFilePicker.showCallback = function() {
   ok(true, "File picker shown");
   return MockFilePicker.returnOK;
 }
 
 let downloadListener = {
-  onDownloadStateChange: function(aState, aDownload) {
-    if (aDownload.state == Services.downloads.DOWNLOAD_FINISHED) {
-      ok(aDownload.targetFile.exists(), "Download completed");
-      is(aDownload.targetFile.fileSize, 154, "Downloaded file has correct size");
+  onDownloadAdded: function(aDownload) {
+    if (aDownload.succeeded) {
+      let downloadedFile = getFile(aDownload.target.path);
+      ok(downloadedFile.exists(), "Download completed");
+      is(downloadedFile.fileSize, 10, "Downloaded file has correct size");
+      fileDownloaded = true;
+      try {
+        downloadedFile.remove(true);
+      } catch (ex) {
+      }
+    }
+  },
 
-      finish();
+  onDownloadChanged: function(aDownload) {
+    if (aDownload.succeeded) {
+      let downloadedFile = getFile(aDownload.target.path);
+      ok(downloadedFile.exists(), "Download completed");
+      is(downloadedFile.fileSize, 10, "Downloaded file has correct size");
+      fileDownloaded = true;
+      try {
+        downloadedFile.remove(true);
+      } catch (ex) {
+      }
     }
   },
 };
 
-Services.downloads.addListener(downloadListener);
+let downloadList;
 
 registerCleanupFunction(function() {
   Services.wm.getMostRecentWindow("Download:Manager").close();
+
   Services.ww.unregisterNotification(winObserver);
+
   MockFilePicker.cleanup();
-  Services.downloads.removeListener(downloadListener);
+
+  if (downloadList) {
+    return downloadList.removeView(downloadListener);
+  }
 });
 
 function test() {
   waitForExplicitFinish();
 
   loadWebapp("download.webapp", undefined, function onLoad() {
-    gAppBrowser.contentDocument.getElementById("download").click();
+    Task.spawn(function*() {
+      downloadList = yield Downloads.getList(Downloads.ALL);
+
+      yield downloadList.addView(downloadListener);
+
+      gAppBrowser.contentDocument.getElementById("download").click();
+    }).catch(function(e) {
+      ok(false, "Error during test: " + e);
+      SimpleTest.finish();
+    });
   });
 }
--- a/webapprt/test/chrome/download.html
+++ b/webapprt/test/chrome/download.html
@@ -1,11 +1,11 @@
 <!DOCTYPE HTML>
 <html>
   <head>
     <title>Download Test App</title>
     <meta charset="utf-8">
   </head>
   <body>
     <h1>Download Test App</h1>
-    <a id="download" href="http://mochi.test:8888//webapprtChrome/webapprt/test/chrome/download.webapp">Download</a>
+    <a id="download" href="http://mochi.test:8888//webapprtChrome/webapprt/test/chrome/download.test">Download</a>
   </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/download.test
@@ -0,0 +1,1 @@
+TEST FILE
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/downloads/browser_add_download.js
@@ -0,0 +1,44 @@
+Cu.import("resource://gre/modules/Services.jsm");
+
+let list = MockDownloadsModule();
+
+let downloadsInList = [
+  new DummyDownload("test1"),
+  new DummyDownload("test2"),
+];
+
+let winObserver = function(win, topic) {
+  if (topic == "domwindowopened") {
+    win.addEventListener("load", function onLoadWindow() {
+      win.removeEventListener("load", onLoadWindow, false);
+
+      if (win.document.documentURI ==
+          "chrome://webapprt/content/downloads/downloads.xul") {
+        ok(true, "Download manager window shown");
+
+        waitDownloadListPopulation(win).then(() => {
+          test_downloadList(win, downloadsInList);
+          finish();
+        });
+      }
+    }, false);
+  }
+}
+
+Services.ww.registerNotification(winObserver);
+
+registerCleanupFunction(function() {
+  Services.ww.unregisterNotification(winObserver);
+
+  Services.wm.getMostRecentWindow("Download:Manager").close();
+});
+
+function test() {
+  waitForExplicitFinish();
+
+  loadWebapp("download.webapp", undefined, function onLoad() {
+    for each (let download in downloadsInList) {
+      list.addDownload(download);
+    }
+  });
+}
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/downloads/browser_remove_download.js
@@ -0,0 +1,45 @@
+Cu.import("resource://gre/modules/Services.jsm");
+
+let list = MockDownloadsModule();
+
+let download = new DummyDownload("test");
+let removedDownload = new DummyDownload("removed");
+
+let winObserver = function(win, topic) {
+  if (topic == "domwindowopened") {
+    win.addEventListener("load", function onLoadWindow() {
+      win.removeEventListener("load", onLoadWindow, false);
+
+      if (win.document.documentURI ==
+          "chrome://webapprt/content/downloads/downloads.xul") {
+        ok(true, "Download manager window shown");
+
+        waitDownloadListPopulation(win).then(() => {
+          test_downloadList(win, [download, removedDownload]);
+          list.removeDownload(removedDownload);
+          executeSoon(() => {
+            test_downloadList(win, [download]);
+            finish();
+          });
+        });
+      }
+    }, false);
+  }
+}
+
+Services.ww.registerNotification(winObserver);
+
+registerCleanupFunction(function() {
+  Services.ww.unregisterNotification(winObserver);
+
+  Services.wm.getMostRecentWindow("Download:Manager").close();
+});
+
+function test() {
+  waitForExplicitFinish();
+
+  loadWebapp("download.webapp", undefined, function onLoad() {
+    list.addDownload(download);
+    list.addDownload(removedDownload);
+  });
+}
new file mode 100644
--- /dev/null
+++ b/webapprt/test/chrome/downloads/webapprt.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+  ../head.js
+
+[browser_add_download.js]
+[browser_remove_download.js]
--- a/webapprt/test/chrome/head.js
+++ b/webapprt/test/chrome/head.js
@@ -35,12 +35,200 @@ function loadWebapp(manifest, parameters
   });
 
   registerCleanupFunction(function() {
     // We load DOMApplicationRegistry into a local scope to avoid appearing
     // to leak it.
     let { DOMApplicationRegistry } = Cu.import("resource://gre/modules/Webapps.jsm", {});
 
     return new Promise(function(resolve, reject) {
-      DOMApplicationRegistry.uninstall(url.spec, resolve, reject);
+      DOMApplicationRegistry.uninstall(url.spec, () => {
+        // Load another page in the browser element, this is needed for tests
+        // that use the same app (that have the same URL).
+        gAppBrowser.setAttribute("src", "about:blank");
+
+        resolve();
+      }, reject);
     });
   });
 }
+
+// Utilities for the downloads tests
+
+let MockDownloadList = function() {
+};
+
+MockDownloadList.prototype = {
+  downloads: new Set(),
+  views: new Set(),
+
+  addView: function(aView) {
+    this.views.add(aView);
+
+    for (let download of this.downloads) {
+      for (let view of this.views) {
+        view.onDownloadAdded(download);
+      }
+    }
+  },
+
+  removeView: function(aView) {
+    this.views.delete(aView);
+  },
+
+  addDownload: function(aDownload) {
+    this.downloads.add(aDownload);
+
+    for (let view of this.views) {
+      view.onDownloadAdded(aDownload);
+    }
+  },
+
+  changeDownload: function(aDownload) {
+    for (let view of this.views) {
+      if (view.onDownloadChanged) {
+        view.onDownloadChanged(aDownload);
+      }
+    }
+  },
+
+  removeDownload: function(aDownload) {
+    this.downloads.delete(aDownload);
+
+    for (let view of this.views) {
+      if (view.onDownloadRemoved) {
+        view.onDownloadRemoved(aDownload);
+      }
+    }
+  },
+}
+
+function MockDownloadsModule() {
+  let { DownloadView } = Cu.import("resource://webapprt/modules/DownloadView.jsm", {});
+
+  let list = new MockDownloadList();
+
+  let { Downloads } = Cu.import("resource://gre/modules/Downloads.jsm", {});
+  let oldDownloadsGetList = Downloads.getList;
+
+  Downloads.getList = function(aKind) {
+    return new Promise(function(resolve, reject) {
+      resolve(list);
+    });
+  };
+
+  registerCleanupFunction(function() {
+    list.removeView(DownloadView);
+
+    Downloads.getList = oldDownloadsGetList;
+  });
+
+  // Reinitialize DownloadView because it's already initialized
+  // when the webapprt test starts.
+  // We need to reinitialize it so that it gets associated to the
+  // MockDownloadList object that we've created.
+  DownloadView.init();
+
+  return list;
+}
+
+function DummyDownload(aFileName) {
+  this.file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+  this.file.append(aFileName);
+  this.file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+
+  this.startTime = Date.now();
+  this.source = {
+    url: "http://mochi.test:8888//webapprtChrome/webapprt/test/chrome/download.webapp",
+    isPrivate: false,
+    referrer: null,
+  };
+  this.target = {
+    path: this.file.path,
+    partFilePath: this.file.path + ".part",
+  }
+};
+
+DummyDownload.prototype = {
+  succeeded: false,
+  canceled: false,
+  stopped: false,
+  hasPartialData: false,
+  hasProgress: false,
+  progress: 0,
+  currentBytes: 0,
+  totalBytes: 0,
+  error: null,
+
+  // Attributes needed to test the download item in the richlistbox
+  state: 0,
+  percentComplete: -1,
+};
+
+function waitDownloadListPopulation(aWin) {
+  return new Promise(function(resolve, reject) {
+    let disconnected = false;
+
+    var observer = new MutationObserver(function(aMutations) {
+      for each (let mutation in aMutations) {
+        if (mutation.addedNodes) {
+          for each (let node in mutation.addedNodes) {
+            if (node.id == "downloadView") {
+              observer.disconnect();
+              disconnected = true;
+
+              // Wait for the resolution of the Downloads.getList promise.
+              executeSoon(() => {
+                resolve();
+              });
+            }
+          }
+        }
+      }
+    });
+
+    observer.observe(aWin.document, {
+      childList: true,
+      subtree: true,
+      attributes: false,
+      characterData: false
+    });
+
+    registerCleanupFunction(function() {
+      if (!disconnected) {
+        observer.disconnect();
+      }
+    });
+  });
+}
+
+function test_downloadList(aWin, aDownloadList) {
+  let richlistbox = aWin.document.getElementById("downloadView");
+
+  is(richlistbox.children.length, aDownloadList.length,
+     "There is the correct number of richlistitems");
+
+  for (let i = 0; i < richlistbox.children.length; i++) {
+    let elm = richlistbox.children[i];
+
+    let name = elm.getAttribute("target");
+
+    let download = null;
+    for (let d = 0; d < aDownloadList.length; d++) {
+      if (aDownloadList[d].file.leafName == name) {
+        download = aDownloadList[d];
+        aDownloadList.splice(d, 1);
+      }
+    }
+
+    if (!download) {
+      ok(false, "Download item unexpected");
+    } else {
+      ok(true, "Download item expected");
+      is(elm.getAttribute("state"), download.state, "Download state correct");
+      is(elm.getAttribute("progress"), download.percentComplete,
+         "Download progress correct");
+    }
+  }
+
+  is(aDownloadList.length, 0,
+     "All the downloads expected to be in the list were in the list");
+}
--- a/webapprt/test/chrome/webapprt.ini
+++ b/webapprt/test/chrome/webapprt.ini
@@ -40,17 +40,17 @@ support-files =
   window-open-blank.webapp^headers^
   window-open-blank.html
   alarm.html
   alarm.webapp
   alarm.webapp^headers^
   download.html
   download.webapp
   download.webapp^headers^
-
+  download.test
 
 [browser_sample.js]
 [browser_window-title.js]
 [browser_webperm.js]
 [browser_noperm.js]
 [browser_geolocation-prompt-perm.js]
 [browser_geolocation-prompt-noperm.js]
 [browser_debugger.js]
new file mode 100644
--- /dev/null
+++ b/webapprt/themes/LICENSE
@@ -0,0 +1,2 @@
+All files in this directory are assumed to be licensed under the MPL 2 license
+which is used throughout this codebase.
copy from toolkit/themes/linux/mozapps/downloads/downloadIcon.png
copy to webapprt/themes/linux/downloads/downloadIcon.png
copy from toolkit/themes/linux/mozapps/downloads/downloads.css
copy to webapprt/themes/linux/downloads/downloads.css
new file mode 100644
--- /dev/null
+++ b/webapprt/themes/linux/jar.mn
@@ -0,0 +1,7 @@
+# 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/.
+
+webapprt.jar:
++ skin/classic/webapprt/downloads/downloadIcon.png          (downloads/downloadIcon.png)
++ skin/classic/webapprt/downloads/downloads.css             (downloads/downloads.css)
new file mode 100644
--- /dev/null
+++ b/webapprt/themes/linux/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/webapprt/themes/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+# Theme Selection
+#
+# MacOS X                 osx (toolkit/themes/osx)
+# Windows                 windows (toolkit/themes/windows)
+# GNOME/Linux             windows (toolkit/themes/windows) +
+#                         linux overrides (toolkit/themes/linux)
+
+toolkit = CONFIG['MOZ_WIDGET_TOOLKIT']
+
+if toolkit == 'cocoa':
+    DIRS += ['osx']
+else:
+    DIRS += ['windows']
+
+    if toolkit in ('gtk2', 'gtk3', 'qt'):
+        DIRS += ['linux']
copy from toolkit/themes/osx/mozapps/downloads/buttons.png
copy to webapprt/themes/osx/downloads/buttons.png
copy from toolkit/themes/osx/mozapps/downloads/downloadIcon.png
copy to webapprt/themes/osx/downloads/downloadIcon.png
copy from toolkit/themes/osx/mozapps/downloads/downloads.css
copy to webapprt/themes/osx/downloads/downloads.css
--- a/toolkit/themes/osx/mozapps/downloads/downloads.css
+++ b/webapprt/themes/osx/downloads/downloads.css
@@ -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/. */
 
-%include ../../global/shared.inc
-
 #downloadView {
   -moz-appearance: none;
   margin: 0;
   padding: 0;
   border-width: 0;
 }
 
 /* Download View Items */
@@ -47,17 +45,17 @@ richlistitem[type="download"] button {
   padding: 0;
   margin: 0 1px 0 1px;
 }
 
 /**
  * Images for buttons in the interface
  */
 richlistitem[type="download"] button {
-  list-style-image: url(chrome://mozapps/skin/downloads/buttons.png);
+  list-style-image: url(chrome://webapprt/skin/downloads/buttons.png);
 }
 .cancel {
   -moz-image-region: rect(0px, 16px, 16px, 0px);
 }
 .cancel:hover {
   -moz-image-region: rect(0px, 32px, 16px, 16px);
 }
 .cancel:hover:active {
@@ -114,10 +112,10 @@ richlistitem[type="download"] button {
   -moz-appearance: statusbar;
 }
 
 #clearListButton {
   -moz-appearance: toolbarbutton;
   min-height: 18px;
   min-width: 0;
   margin: 0 6px;
-  text-shadow: @loweredShadow@;
+  text-shadow: 0 1px rgba(255, 255, 255, .4);
 }
new file mode 100644
--- /dev/null
+++ b/webapprt/themes/osx/jar.mn
@@ -0,0 +1,9 @@
+# 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/.
+
+webapprt.jar:
+% skin webapprt classic/1.0 %skin/classic/webapprt/
+  skin/classic/webapprt/downloads/buttons.png                      (downloads/buttons.png)
+  skin/classic/webapprt/downloads/downloadIcon.png                 (downloads/downloadIcon.png)
+  skin/classic/webapprt/downloads/downloads.css                    (downloads/downloads.css)
new file mode 100644
--- /dev/null
+++ b/webapprt/themes/osx/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file
copy from toolkit/themes/windows/mozapps/downloads/downloadButtons-aero.png
copy to webapprt/themes/windows/downloads/downloadButtons-aero.png
copy from toolkit/themes/windows/mozapps/downloads/downloadButtons.png
copy to webapprt/themes/windows/downloads/downloadButtons.png
copy from toolkit/themes/windows/mozapps/downloads/downloadIcon-aero.png
copy to webapprt/themes/windows/downloads/downloadIcon-aero.png
copy from toolkit/themes/windows/mozapps/downloads/downloadIcon.png
copy to webapprt/themes/windows/downloads/downloadIcon.png
copy from toolkit/themes/windows/mozapps/downloads/downloads-aero.css
copy to webapprt/themes/windows/downloads/downloads-aero.css
copy from toolkit/themes/windows/mozapps/downloads/downloads.css
copy to webapprt/themes/windows/downloads/downloads.css
--- a/toolkit/themes/windows/mozapps/downloads/downloads.css
+++ b/webapprt/themes/windows/downloads/downloads.css
@@ -28,17 +28,17 @@ richlistitem[type="download"] .name {
 }
 
 richlistitem[type="download"] .dateTime {
   font-size: smaller;
 }
 
 .mini-button {
   -moz-appearance: none;
-  list-style-image: url(chrome://mozapps/skin/downloads/downloadButtons.png);
+  list-style-image: url(chrome://webapprt/skin/downloads/downloadButtons.png);
   background-color: transparent;
   border: none;
   padding: 0;
   margin: 0;
   min-width: 0;
   min-height: 0;
 }
 
new file mode 100644
--- /dev/null
+++ b/webapprt/themes/windows/jar.mn
@@ -0,0 +1,20 @@
+# 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/.
+
+webapprt.jar:
+% skin webapprt classic/1.0 %skin/classic/webapprt/ os=WINNT osversion<6
+% skin webapprt classic/1.0 %skin/classic/webapprt/ os!=WINNT
+# NOTE: If you add a new file here, you'll need to add it to the aero
+# section at the bottom of this file
+        skin/classic/webapprt/downloads/downloadButtons.png         (downloads/downloadButtons.png)
+        skin/classic/webapprt/downloads/downloadIcon.png            (downloads/downloadIcon.png)
+        skin/classic/webapprt/downloads/downloads.css               (downloads/downloads.css)
+
+#ifdef XP_WIN
+webapprt.jar:
+% skin webapprt classic/1.0 %skin/classic/aero/webapprt/ os=WINNT osversion>=6
+        skin/classic/aero/webapprt/downloads/downloadButtons.png            (downloads/downloadButtons-aero.png)
+        skin/classic/aero/webapprt/downloads/downloadIcon.png               (downloads/downloadIcon-aero.png)
+*       skin/classic/aero/webapprt/downloads/downloads.css                  (downloads/downloads-aero.css)
+#endif
new file mode 100644
--- /dev/null
+++ b/webapprt/themes/windows/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file