Bug 726444 - Implement the Downloads Panel. r=mak ui-r=limi
☠☠ backed out by f0a006794b94 ☠ ☠
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Sat, 10 Mar 2012 15:00:45 +0100
changeset 88911 ba7da6256b234effb34a471e9df5995e804d5146
parent 88910 b74bbbfafde30ea5b203c7e8e374e4076425fdb1
child 88912 c29f828d1ce456d8764ad305d4a80636723d67db
push id165
push userMs2ger@gmail.com
push dateWed, 14 Mar 2012 20:23:47 +0000
reviewersmak, limi
bugs726444, 697679
milestone13.0a1
Bug 726444 - Implement the Downloads Panel. r=mak ui-r=limi Includes: Bug 697679 - By Javi Rueda <leofigueres@yahoo.com>.
browser/app/profile/firefox.js
browser/base/content/browser-doctype.inc
browser/base/content/browser.js
browser/base/content/browser.xul
browser/base/content/global-scripts.inc
browser/components/Makefile.in
browser/components/downloads/Makefile.in
browser/components/downloads/content/download.xml
browser/components/downloads/content/downloads.css
browser/components/downloads/content/downloads.js
browser/components/downloads/content/downloadsOverlay.xul
browser/components/downloads/content/indicator.js
browser/components/downloads/content/indicatorXul.inc
browser/components/downloads/jar.mn
browser/components/downloads/src/BrowserDownloads.manifest
browser/components/downloads/src/DownloadsCommon.jsm
browser/components/downloads/src/DownloadsStartup.js
browser/components/downloads/src/DownloadsUI.js
browser/components/downloads/src/Makefile.in
browser/components/downloads/test/Makefile.in
browser/components/downloads/test/browser/Makefile.in
browser/components/downloads/test/browser/browser_basic_functionality.js
browser/components/downloads/test/browser/browser_delete_key_removes.js
browser/components/downloads/test/browser/head.js
browser/components/downloads/test/unit/head.js
browser/components/downloads/test/unit/test_DownloadsCommon.js
browser/components/downloads/test/unit/xpcshell.ini
browser/components/preferences/main.js
browser/installer/package-manifest.in
browser/locales/en-US/chrome/browser/downloads/downloads.dtd
browser/locales/en-US/chrome/browser/downloads/downloads.properties
browser/locales/jar.mn
browser/makefiles.sh
browser/themes/gnomestripe/downloads/buttons.png
browser/themes/gnomestripe/downloads/download-glow.png
browser/themes/gnomestripe/downloads/download-notification.png
browser/themes/gnomestripe/downloads/downloads.css
browser/themes/gnomestripe/jar.mn
browser/themes/pinstripe/downloads/buttons.png
browser/themes/pinstripe/downloads/download-glow.png
browser/themes/pinstripe/downloads/download-notification.png
browser/themes/pinstripe/downloads/downloads.css
browser/themes/pinstripe/jar.mn
browser/themes/winstripe/downloads/buttons-aero.png
browser/themes/winstripe/downloads/buttons.png
browser/themes/winstripe/downloads/download-glow-aero.png
browser/themes/winstripe/downloads/download-glow.png
browser/themes/winstripe/downloads/download-notification.png
browser/themes/winstripe/downloads/downloads-aero.css
browser/themes/winstripe/downloads/downloads.css
browser/themes/winstripe/jar.mn
testing/xpcshell/xpcshell.ini
toolkit/components/downloads/nsDownloadManager.cpp
toolkit/components/downloads/test/browser/browser_nsIDownloadManagerUI.js
toolkit/content/Services.jsm
toolkit/mozapps/downloads/tests/chrome/test_destinationURI_annotation.xul
toolkit/mozapps/downloads/tests/chrome/test_taskbarprogress_service.xul
toolkit/mozapps/downloads/tests/chrome/test_ui_stays_open_on_alert_clickback.xul
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -339,16 +339,22 @@ pref("browser.download.manager.showWhenS
 pref("browser.download.manager.closeWhenDone", false);
 pref("browser.download.manager.focusWhenStarting", false);
 pref("browser.download.manager.flashCount", 2);
 pref("browser.download.manager.addToRecentDocs", true);
 pref("browser.download.manager.quitBehavior", 0);
 pref("browser.download.manager.scanWhenDone", true);
 pref("browser.download.manager.resumeOnWakeDelay", 10000);
 
+// This allows disabling the Downloads Panel in favor of the old interface.
+pref("browser.download.useToolkitUI", false);
+
+// This controls retention behavior in the Downloads Panel only.
+pref("browser.download.panel.removeFinishedDownloads", false);
+
 // search engines URL
 pref("browser.search.searchEnginesURL",      "https://addons.mozilla.org/%LOCALE%/firefox/search-engines/");
 
 // pointer to the default engine name
 pref("browser.search.defaultenginename",      "chrome://browser-region/locale/region.properties");
 
 // disable logging for the search service by default
 pref("browser.search.log", false);
--- a/browser/base/content/browser-doctype.inc
+++ b/browser/base/content/browser-doctype.inc
@@ -6,16 +6,18 @@
 <!ENTITY % baseMenuDTD SYSTEM "chrome://browser/locale/baseMenuOverlay.dtd" >
 %baseMenuDTD;
 <!ENTITY % charsetDTD SYSTEM "chrome://global/locale/charsetOverlay.dtd" >
 %charsetDTD;
 <!ENTITY % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd" >
 %textcontextDTD;
 <!ENTITY % customizeToolbarDTD SYSTEM "chrome://global/locale/customizeToolbar.dtd">
   %customizeToolbarDTD;
+<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
+%downloadsDTD;
 <!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
 %placesDTD;
 #ifdef MOZ_SAFE_BROWSING
 <!ENTITY % safebrowsingDTD SYSTEM "chrome://browser/locale/safebrowsing/phishing-afterload-warning-message.dtd">
 %safebrowsingDTD;
 #endif
 <!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
 %aboutHomeDTD;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1376,16 +1376,18 @@ function BrowserStartup() {
   TabsOnTop.init();
 
   BookmarksMenuButton.init();
 
   TabsInTitlebar.init();
 
   gPrivateBrowsingUI.init();
 
+  DownloadsButton.initializePlaceholder();
+
   retrieveToolbarIconsizesFromTheme();
 
   gDelayedStartupTimeoutId = setTimeout(delayedStartup, 0, isLoadingBlank, mustLoadSidebar);
   gStartupRan = true;
 }
 
 function HandleAppCommandEvent(evt) {
   evt.stopPropagation();
@@ -1624,16 +1626,21 @@ function delayedStartup(isLoadingBlank, 
       let tempScope = {};
       Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm",
                 tempScope);
       tempScope.DownloadTaskbarProgress.onBrowserWindowLoad(window);
     }
 #endif
   }, 10000);
 
+  // We can intialize the downloads indicator here in delayedStartup() because
+  // it is initially invisible, at least until the download manager is started,
+  // thus no flickering occurs when we set its position.
+  DownloadsButton.initializeIndicator();
+
 #ifndef XP_MACOSX
   updateEditUIVisibility();
   let placesContext = document.getElementById("placesContext");
   placesContext.addEventListener("popupshowing", updateEditUIVisibility, false);
   placesContext.addEventListener("popuphiding", updateEditUIVisibility, false);
 #endif
 
   gBrowser.mPanelContainer.addEventListener("InstallBrowserTheme", LightWeightThemeWebInstaller, false, true);
@@ -3246,39 +3253,16 @@ var newWindowButtonObserver = {
     url = getShortcutOrURI(url, postData);
     if (url) {
       // allow third-party services to fixup this URL
       openNewWindowWith(url, null, postData.value, true);
     }
   }
 }
 
-var DownloadsButtonDNDObserver = {
-  onDragOver: function (aEvent)
-  {
-    var types = aEvent.dataTransfer.types;
-    if (types.contains("text/x-moz-url") ||
-        types.contains("text/uri-list") ||
-        types.contains("text/plain"))
-      aEvent.preventDefault();
-  },
-
-  onDragExit: function (aEvent)
-  {
-  },
-
-  onDrop: function (aEvent)
-  {
-    let name = { };
-    let url = browserDragAndDrop.drop(aEvent, name);
-    if (url)
-      saveURL(url, name, null, true, true);
-  }
-}
-
 const DOMLinkHandler = {
   handleEvent: function (event) {
     switch (event.type) {
       case "DOMLinkAdded":
         this.onLinkAdded(event);
         break;
     }
   },
@@ -3683,16 +3667,17 @@ function BrowserCustomizeToolbar()
   var splitter = document.getElementById("urlbar-search-splitter");
   if (splitter)
     splitter.parentNode.removeChild(splitter);
 
   CombinedStopReload.uninit();
 
   PlacesToolbarHelper.customizeStart();
   BookmarksMenuButton.customizeStart();
+  DownloadsButton.customizeStart();
 
   TabsInTitlebar.allowedBy("customizing-toolbars", false);
 
   var customizeURL = "chrome://global/content/customizeToolbar.xul";
   gCustomizeSheet = getBoolPref("toolbar.customization.usesheet", false);
 
   if (gCustomizeSheet) {
     var sheetFrame = document.getElementById("customizeToolbarSheetIFrame");
@@ -3749,16 +3734,17 @@ function BrowserToolboxCustomizeDone(aTo
     // if it already exists, since it may have changed if the URL bar was
     // added/removed.
     if (!__lookupGetter__("PopupNotifications"))
       PopupNotifications.iconBox = document.getElementById("notification-popup-box");
   }
 
   PlacesToolbarHelper.customizeDone();
   BookmarksMenuButton.customizeDone();
+  DownloadsButton.customizeDone();
 
   // The url bar splitter state is dependent on whether stop/reload
   // and the location bar are combined, so we need this ordering
   CombinedStopReload.init();
   UpdateUrlbarSearchSplitterState();
   setUrlAndSearchBarWidthForConditionalForwardButton();
 
   // Update the urlbar
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -50,19 +50,21 @@
 # decision by deleting the provisions above and replace them with the notice
 # and other provisions required by the GPL or the LGPL. If you do not delete
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 
 <?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/downloads/downloads.css"?>
 <?xml-stylesheet href="chrome://browser/content/places/places.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/webconsole.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/downloads/downloads.css"?>
 <?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
 
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 <?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
 <?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
 
 # All DTD information is stored in a separate file so that it can be shared by
 # hiddenWindow.xul.
@@ -414,16 +416,20 @@
 #ifdef XP_MACOSX
       <label class="tooltip-label" value="&backForwardButtonMenuMac.tooltip;"/>
 #else
       <label class="tooltip-label" value="&backForwardButtonMenu.tooltip;"/>
 #endif
     </tooltip>
   </popupset>
 
+# To improve startup performance, we include the XUL elements for the downloads
+# status indicator at build time instead of using an overlay.
+#include ../../components/downloads/content/indicatorXul.inc
+
 #ifdef CAN_DRAW_IN_TITLEBAR
 <vbox id="titlebar">
   <hbox id="titlebar-content">
     <hbox id="appmenu-button-container">
       <button id="appmenu-button"
               type="menu"
               label="&brandShortName;"
               style="-moz-user-focus: ignore;">
@@ -848,22 +854,20 @@
 
 # Update primaryToolbarButtons in browser/themes/browserShared.inc when adding
 # or removing default items with the toolbarbutton-1 class.
 
       <toolbarbutton id="print-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
                      label="&printButton.label;" command="cmd_print"
                      tooltiptext="&printButton.tooltip;"/>
 
+      <!-- This is a placeholder for the Downloads Indicator.  It is visible
+           only during the customization of the toolbar or in the palette, and
+           is replaced when customization is done. -->
       <toolbarbutton id="downloads-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
-                     observes="Tools:Downloads"
-                     ondrop="DownloadsButtonDNDObserver.onDrop(event)"
-                     ondragover="DownloadsButtonDNDObserver.onDragOver(event)"
-                     ondragenter="DownloadsButtonDNDObserver.onDragOver(event)"
-                     ondragexit="DownloadsButtonDNDObserver.onDragExit(event)"
                      label="&downloads.label;"
                      tooltiptext="&downloads.tooltip;"/>
 
       <toolbarbutton id="history-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
                      observes="viewHistorySidebar" label="&historyButton.label;"
                      tooltiptext="&historyButton.tooltip;"/>
 
       <toolbarbutton id="bookmarks-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
old mode 100644
new mode 100755
--- a/browser/components/Makefile.in
+++ b/browser/components/Makefile.in
@@ -57,16 +57,17 @@ EXTRA_PP_COMPONENTS = \
   $(NULL)
 
 EXTRA_JS_MODULES = distribution.js
 
 PARALLEL_DIRS = \
   about \
   certerror \
   dirprovider \
+  downloads \
   feeds \
   places \
   preferences \
   privatebrowsing \
   search \
   sessionstore \
   shell \
   sidebar \
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/Makefile.in
@@ -0,0 +1,18 @@
+# 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/.
+
+DEPTH     = ../../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+DIRS = src
+
+ifdef ENABLE_TESTS
+DIRS += test
+endif
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/content/download.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+# -*- Mode: HTML; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+# vim: set ts=2 et sw=2 tw=80:
+# 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 SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
+
+<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"
+           extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+    <resources>
+      <stylesheet src="chrome://browser/skin/downloads/downloads.css"/>
+    </resources>
+    <content orient="horizontal">
+      <xul:hbox class="downloadInfo"
+                align="center"
+                flex="1"
+                onclick="DownloadsView.onDownloadClick(event);">
+        <xul:vbox pack="center">
+          <xul:image class="downloadTypeIcon"
+                     validate="always"
+                     xbl:inherits="src=image"/>
+          <xul:image class="downloadTypeIcon blockedIcon"/>
+        </xul:vbox>
+        <xul:vbox pack="center"
+                  flex="1">
+          <xul:description class="downloadTarget"
+                           crop="center"
+                           xbl:inherits="value=target,tooltiptext=target"/>
+          <xul:progressmeter anonid="progressmeter"
+                             class="downloadProgress"
+                             min="0"
+                             max="100"
+                             xbl:inherits="mode=progressmode,value=progress"/>
+          <xul:description class="downloadDetails"
+                           crop="end"
+                           xbl:inherits="value=status,tooltiptext=statusTip"/>
+        </xul:vbox>
+      </xul:hbox>
+      <xul:hbox class="downloadButtonContainer"
+                align="center">
+        <xul:button class="downloadButton downloadCancel"
+                    tooltiptext="&cmd.cancel.label;"
+                    oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_cancel');"/>
+        <xul:button class="downloadButton downloadRetry"
+                    tooltiptext="&cmd.retry.label;"
+                    oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_retry');"/>
+        <xul:button class="downloadButton downloadShow"
+                    tooltiptext="&cmd.show.label;"
+                    oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_show');"/>
+      </xul:hbox>
+    </content>
+  </binding>
+</bindings>
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/content/downloads.css
@@ -0,0 +1,90 @@
+/* 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/. */
+
+/*** Download items ***/
+
+richlistitem[type="download"] {
+  -moz-binding: url('chrome://browser/content/downloads/download.xml#download');
+}
+
+richlistitem[type="download"]:not([selected]) button {
+  /* Only focus buttons in the selected item. */
+  -moz-user-focus: none;
+}
+
+/*** Visibility of controls inside download items ***/
+
+.download-state:-moz-any(     [state="6"], /* Blocked (parental) */
+                              [state="8"], /* Blocked (dirty)    */
+                              [state="9"]) /* Blocked (policy)   */
+                                           .downloadTypeIcon:not(.blockedIcon),
+
+.download-state:not(:-moz-any([state="6"], /* Blocked (parental) */
+                              [state="8"], /* Blocked (dirty)    */
+                              [state="9"]) /* Blocked (policy)   */)
+                                           .downloadTypeIcon.blockedIcon,
+
+.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
+                              [state="5"], /* Starting (queued)  */
+                              [state="0"], /* Downloading        */
+                              [state="4"], /* Paused             */
+                              [state="7"]) /* Scanning           */)
+                                           .downloadProgress,
+
+.download-state:not(          [state="0"]  /* Downloading        */)
+                                           .downloadPauseMenuItem,
+
+.download-state:not(          [state="4"]  /* Paused             */)
+                                           .downloadResumeMenuItem,
+
+.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
+                              [state="5"], /* Starting (queued)  */
+                              [state="0"], /* Downloading        */
+                              [state="4"]) /* Paused             */)
+                                           .downloadCancel,
+
+.download-state:not(:-moz-any([state="2"], /* Failed             */
+                              [state="4"]) /* Paused             */)
+                                           .downloadCancelMenuItem,
+
+.download-state:not(:-moz-any([state="1"], /* Finished           */
+                              [state="3"], /* Canceled           */
+                              [state="6"], /* Blocked (parental) */
+                              [state="8"], /* Blocked (dirty)    */
+                              [state="9"]) /* Blocked (policy)   */)
+                                           .downloadRemoveFromListMenuItem,
+
+.download-state:not(:-moz-any([state="2"], /* Failed             */
+                              [state="3"]) /* Canceled           */)
+                                           .downloadRetry,
+
+.download-state:not(          [state="1"]  /* Finished           */)
+                                           .downloadShow,
+
+.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
+                              [state="5"], /* Starting (queued)  */
+                              [state="0"], /* Downloading        */
+                              [state="4"]) /* Paused             */)
+                                           .downloadShowMenuItem,
+
+.download-state[state="7"]                 .downloadCommandsSeparator
+
+{
+  display: none;
+}
+
+/*** Visibility of controls inside the downloads indicator ***/
+
+#downloads-indicator:-moz-any([progress],
+                              [counter],
+                              [paused])    #downloads-indicator-icon,
+
+#downloads-indicator:not(:-moz-any([progress],
+                                   [counter],
+                                   [paused]))
+                                           #downloads-indicator-progress-area
+
+{
+  visibility: hidden;
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/content/downloads.js
@@ -0,0 +1,1139 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/**
+ * Handles the Downloads panel user interface for each browser window.
+ *
+ * This file includes the following constructors and global objects:
+ *
+ * DownloadsPanel
+ * Main entry point for the downloads panel interface.
+ *
+ * DownloadsView
+ * Builds and updates the downloads list widget, responding to changes in the
+ * download state and real-time data.  In addition, handles part of the user
+ * interaction events raised by the downloads list widget.
+ *
+ * DownloadsViewItem
+ * Builds and updates a single item in the downloads list widget, responding to
+ * changes in the download state and real-time data.
+ *
+ * DownloadsViewController
+ * Handles part of the user interaction events raised by the downloads list
+ * widget, in particular the "commands" that apply to multiple items, and
+ * dispatches the commands that apply to individual items.
+ *
+ * DownloadsViewItemController
+ * Handles all the user interaction events, in particular the "commands",
+ * related to a single item in the downloads list widgets.
+ */
+
+"use strict";
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
+                                  "resource://gre/modules/DownloadUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
+                                  "resource:///modules/DownloadsCommon.jsm");
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsPanel
+
+/**
+ * Main entry point for the downloads panel interface.
+ */
+const DownloadsPanel = {
+  //////////////////////////////////////////////////////////////////////////////
+  //// Initialization and termination
+
+  /**
+   * State of the downloads panel, based on one of the kPanel constants.
+   */
+  _panelState: 0,
+
+  /** Download data has not been loaded. */
+  get kPanelUninitialized() 0,
+  /** Download data is loading, but the user interface is invisible. */
+  get kPanelHidden() 1,
+  /** The panel will be shown as soon as possible. */
+  get kPanelShowing() 2,
+  /** The panel is open, though download data might still be loading. */
+  get kPanelShown() 3,
+
+  /**
+   * Loads the overlay containing the XUL elements required to render and
+   * control the panel.
+   *
+   * @param aCallback
+   *        Invoked when loading is completed.  If the overlay is already
+   *        loaded, the function is called immediately.  If the overlay is still
+   *        loading, the function is not called.
+   */
+  _ensureOverlayLoaded: function DP_ensureOverlayLoaded(aCallback)
+  {
+    if (this._overlayLoading) {
+      // We are already waiting to invoke the callback.
+      return;
+    }
+
+    if (this._overlayLoaded) {
+      aCallback();
+      return;
+    }
+
+    function loadCallback() {
+      this._overlayLoading = false;
+      this._overlayLoaded = true;
+
+      // Loading the overlay causes all the persisted XUL attributes to be
+      // reapplied, including "iconsize" on the toolbars.  Until bug 640158 is
+      // fixed, we must recalculate the correct "iconsize" attributes manually.
+      retrieveToolbarIconsizesFromTheme();
+      aCallback();
+    }
+
+    this._overlayLoading = true;
+    document.loadOverlay(
+                 "chrome://browser/content/downloads/downloadsOverlay.xul",
+                 loadCallback.bind(this));
+  },
+  _overlayLoading: false,
+  _overlayLoaded: false,
+
+  /**
+   * Starts loading the download data in background, without opening the panel.
+   * Use showPanel instead to load the data and open the panel at the same time.
+   *
+   * @param aCallback
+   *        Called when initialization is complete.
+   */
+  initialize: function DP_initialize(aCallback)
+  {
+    if (this._panelState != this.kPanelUninitialized) {
+      this._ensureOverlayLoaded(aCallback);
+      return;
+    }
+    this._panelState = this.kPanelHidden;
+
+    window.addEventListener("unload", this.onWindowUnload, false);
+
+    // Ensure that the Download Manager service is running.  This resumes
+    // active downloads if required.  If there are downloads to be shown in the
+    // panel, starting the service will make us load their data asynchronously.
+    Services.downloads;
+
+    // Now that data loading has eventually started, load the required XUL
+    // elements and initialize our views.
+    this._ensureOverlayLoaded(function DP_OPIDR_callback() {
+      DownloadsViewController.initialize();
+      DownloadsCommon.data.addView(DownloadsView);
+      aCallback();
+    });
+  },
+
+  /**
+   * Closes the downloads panel and frees the internal resources related to the
+   * downloads.  The downloads panel can be reopened later, even after this
+   * function has been called.
+   */
+  terminate: function DP_terminate()
+  {
+    if (this._panelState == this.kPanelUninitialized) {
+      return;
+    }
+
+    window.removeEventListener("unload", this.onWindowUnload, false);
+
+    // Ensure that the panel is closed before shutting down.
+    this.hidePanel();
+
+    DownloadsViewController.terminate();
+    DownloadsCommon.data.removeView(DownloadsView);
+
+    this._panelState = this.kPanelUninitialized;
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Panel interface
+
+  /**
+   * Main panel element in the browser window.
+   */
+  get panel()
+  {
+    delete this.panel;
+    return this.panel = document.getElementById("downloadsPanel");
+  },
+
+  /**
+   * Starts opening the downloads panel interface, anchored to the downloads
+   * button of the browser window.  The list of downloads to display is
+   * initialized the first time this method is called, and the panel is shown
+   * only when data is ready.
+   */
+  showPanel: function DP_showPanel()
+  {
+    if (this.isPanelShowing) {
+      this._focusPanel();
+      return;
+    }
+
+    this.initialize(function DP_SP_callback() {
+      // Delay displaying the panel because this function will sometimes be
+      // called while another window is closing (like the window for selecting
+      // whether to save or open the file), and that would cause the panel to
+      // close immediately.
+      setTimeout(function () DownloadsPanel._openPopupIfDataReady(), 0);
+    }.bind(this));
+
+    this._panelState = this.kPanelShowing;
+  },
+
+  /**
+   * Hides the downloads panel, if visible, but keeps the internal state so that
+   * the panel can be reopened quickly if required.
+   */
+  hidePanel: function DP_hidePanel()
+  {
+    if (!this.isPanelShowing) {
+      return;
+    }
+
+    this.panel.hidePopup();
+
+    // Ensure that we allow the panel to be reopened.  Note that, if the popup
+    // was open, then the onPopupHidden event handler has already updated the
+    // current state, otherwise we must update the state ourselves.
+    this._panelState = this.kPanelHidden;
+  },
+
+  /**
+   * Indicates whether the panel is shown or will be shown.
+   */
+  get isPanelShowing()
+  {
+    return this._panelState == this.kPanelShowing ||
+           this._panelState == this.kPanelShown;
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Callback functions from DownloadsView
+
+  /**
+   * Called after data loading finished.
+   */
+  onViewLoadCompleted: function DP_onViewLoadCompleted()
+  {
+    this._openPopupIfDataReady();
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// User interface event functions
+
+  onWindowUnload: function DP_onWindowUnload()
+  {
+    // This function is registered as an event listener, we can't use "this".
+    DownloadsPanel.terminate();
+  },
+
+  onPopupShown: function DP_onPopupShown(aEvent)
+  {
+    // Ignore events raised by nested popups.
+    if (aEvent.target != aEvent.currentTarget) {
+      return;
+    }
+
+    // Since at most one popup is open at any given time, we can set globally.
+    DownloadsCommon.indicatorData.attentionSuppressed = true;
+
+    // Ensure that an item is selected when the panel is focused.
+    if (DownloadsView.richListBox.itemCount > 0 &&
+        !DownloadsView.richListBox.selectedItem) {
+      DownloadsView.richListBox.selectedIndex = 0;
+    }
+
+    this._focusPanel();
+  },
+
+  onPopupHidden: function DP_onPopupHidden(aEvent)
+  {
+    // Ignore events raised by nested popups.
+    if (aEvent.target != aEvent.currentTarget) {
+      return;
+    }
+
+    // Since at most one popup is open at any given time, we can set globally.
+    DownloadsCommon.indicatorData.attentionSuppressed = false;
+
+    // Allow the anchor to be hidden.
+    DownloadsButton.releaseAnchor();
+
+    // Allow the panel to be reopened.
+    this._panelState = this.kPanelHidden;
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Related operations
+
+  /**
+   * Shows or focuses the user interface dedicated to downloads history.
+   */
+  showDownloadsHistory: function DP_showDownloadsHistory()
+  {
+    // Hide the panel before invoking the Library window, otherwise focus will
+    // return to the browser window when the panel closes automatically.
+    this.hidePanel();
+
+    // Open the Library window and select the Downloads query.
+    PlacesCommandHook.showPlacesOrganizer("Downloads");
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Internal functions
+
+  /**
+   * Move focus to the main element in the downloads panel, unless another
+   * element in the panel is already focused.
+   */
+  _focusPanel: function DP_focusPanel()
+  {
+    // We may be invoked while the panel is still waiting to be shown.
+    if (this._panelState != this.kPanelShown) {
+      return;
+    }
+
+    let element = document.commandDispatcher.focusedElement;
+    while (element && element != this.panel) {
+      element = element.parentNode;
+    }
+    if (!element) {
+      DownloadsView.richListBox.focus();
+    }
+  },
+
+  /**
+   * Opens the downloads panel when data is ready to be displayed.
+   */
+  _openPopupIfDataReady: function DP_openPopupIfDataReady()
+  {
+    // We don't want to open the popup if we already displayed it, or if we are
+    // still loading data.
+    if (this._panelState != this.kPanelShowing || DownloadsView.loading) {
+      return;
+    }
+
+    this._panelState = this.kPanelShown;
+
+    // Make sure that clicking outside the popup cannot reopen it accidentally.
+    this.panel.popupBoxObject.setConsumeRollupEvent(Ci.nsIPopupBoxObject
+                                                      .ROLLUP_CONSUME);
+
+    // Ensure the anchor is visible.  If that is not possible, show the panel
+    // anchored to the top area of the window, near the default anchor position.
+    let anchor = DownloadsButton.getAnchor();
+    if (anchor) {
+      this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, null);
+    } else {
+      this.panel.openPopup(document.getElementById("TabsToolbar"),
+                           "after_end", 0, 0, false, null);
+    }
+  }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsView
+
+/**
+ * Builds and updates the downloads list widget, responding to changes in the
+ * download state and real-time data.  In addition, handles part of the user
+ * interaction events raised by the downloads list widget.
+ */
+const DownloadsView = {
+  //////////////////////////////////////////////////////////////////////////////
+  //// Functions handling download items in the list
+
+  /**
+   * Indicates whether we are still loading downloads data asynchronously.
+   */
+  loading: false,
+
+  /**
+   * Object containing all the available DownloadsViewItem objects, indexed by
+   * their numeric download identifier.
+   */
+  _viewItems: {},
+
+  /**
+   * Called when the number of items in the list changes.
+   */
+  _itemCountChanged: function DV_itemCountChanged()
+  {
+    if (Object.keys(this._viewItems).length > 0) {
+      DownloadsPanel.panel.setAttribute("hasdownloads", "true");
+    } else {
+      DownloadsPanel.panel.removeAttribute("hasdownloads");
+    }
+  },
+
+  /**
+   * Element corresponding to the list of downloads.
+   */
+  get richListBox()
+  {
+    delete this.richListBox;
+    return this.richListBox = document.getElementById("downloadsListBox");
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Callback functions from DownloadsData
+
+  /**
+   * Called before multiple downloads are about to be loaded.
+   */
+  onDataLoadStarting: function DV_onDataLoadStarting()
+  {
+    this.loading = true;
+  },
+
+  /**
+   * Called after data loading finished.
+   */
+  onDataLoadCompleted: function DV_onDataLoadCompleted()
+  {
+    this.loading = false;
+
+    // Notify the panel that all the initially available downloads have been
+    // loaded.  This ensures that the interface is visible, if still required.
+    DownloadsPanel.onViewLoadCompleted();
+  },
+
+  /**
+   * Called when the downloads database becomes unavailable (for example,
+   * entering Private Browsing Mode).  References to existing data should be
+   * discarded.
+   */
+  onDataInvalidated: function DV_onDataInvalidated()
+  {
+    DownloadsPanel.terminate();
+
+    // Clear the list by replacing with a shallow copy.
+    let emptyView = this.richListBox.cloneNode(false);
+    this.richListBox.parentNode.replaceChild(emptyView, this.richListBox);
+    this.richListBox = emptyView;
+    this._viewItems = {};
+  },
+
+  /**
+   * Called when a new download data item is available, either during the
+   * asynchronous data load or when a new download is started.
+   *
+   * @param aDataItem
+   *        DownloadsDataItem object that was just added.
+   * @param aNewest
+   *        When true, indicates that this item is the most recent and should be
+   *        added in the topmost position.  This happens when a new download is
+   *        started.  When false, indicates that the item is the least recent
+   *        and should be appended.  The latter generally happens during the
+   *        asynchronous data load.
+   */
+  onDataItemAdded: function DV_onDataItemAdded(aDataItem, aNewest)
+  {
+    // Make the item and add it in the appropriate place in the list.
+    let element = document.createElement("richlistitem");
+    let viewItem = new DownloadsViewItem(aDataItem, element);
+    this._viewItems[aDataItem.downloadId] = viewItem;
+    if (aNewest) {
+      this.richListBox.insertBefore(element, this.richListBox.firstChild);
+    } else {
+      this.richListBox.appendChild(element);
+    }
+
+    this._itemCountChanged();
+  },
+
+  /**
+   * Called when a data item is removed.  Ensures that the widget associated
+   * with the view item is removed from the user interface.
+   *
+   * @param aDataItem
+   *        DownloadsDataItem object that is being removed.
+   */
+  onDataItemRemoved: function DV_onDataItemRemoved(aDataItem)
+  {
+    let element = this.getViewItem(aDataItem)._element;
+    let previousSelectedIndex = this.richListBox.selectedIndex;
+    this.richListBox.removeChild(element);
+    this.richListBox.selectedIndex = Math.min(previousSelectedIndex,
+                                              this.richListBox.itemCount - 1);
+    delete this._viewItems[aDataItem.downloadId];
+
+    this._itemCountChanged();
+  },
+
+  /**
+   * Returns the view item associated with the provided data item for this view.
+   *
+   * @param aDataItem
+   *        DownloadsDataItem object for which the view item is requested.
+   *
+   * @return Object that can be used to notify item status events.
+   */
+  getViewItem: function DV_getViewItem(aDataItem)
+  {
+    return this._viewItems[aDataItem.downloadId];
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// User interface event functions
+
+  /**
+   * Helper function to do commands on a specific download item.
+   *
+   * @param aEvent
+   *        Event object for the event being handled.  If the event target is
+   *        not a richlistitem that represents a download, this function will
+   *        walk up the parent nodes until it finds a DOM node that is.
+   * @param aCommand
+   *        The command to be performed.
+   */
+  onDownloadCommand: function DV_onDownloadCommand(aEvent, aCommand)
+  {
+    let target = aEvent.target;
+    while (target.nodeName != "richlistitem") {
+      target = target.parentNode;
+    }
+    new DownloadsViewItemController(target).doCommand(aCommand);
+  },
+
+  onDownloadClick: function DV_onDownloadClick(aEvent)
+  {
+    // Handle primary clicks only.
+    if (aEvent.button == 0) {
+      goDoCommand("downloadsCmd_open");
+    }
+  },
+
+  onDownloadKeyPress: function DV_onDownloadKeyPress(aEvent)
+  {
+    // Handle unmodified keys only.
+    if (aEvent.altKey || aEvent.ctrlKey || aEvent.shiftKey || aEvent.metaKey) {
+      return;
+    }
+
+    // Pressing the key on buttons should not invoke the action because the
+    // event has already been handled by the button itself.
+    if (aEvent.originalTarget.hasAttribute("command") ||
+        aEvent.originalTarget.hasAttribute("oncommand")) {
+      return;
+    }
+
+    if (aEvent.charCode == " ".charCodeAt(0)) {
+      goDoCommand("downloadsCmd_pauseResume");
+      return;
+    }
+
+    switch (aEvent.keyCode) {
+      case KeyEvent.DOM_VK_ENTER:
+      case KeyEvent.DOM_VK_RETURN:
+        goDoCommand("downloadsCmd_doDefault");
+        break;
+    }
+  },
+
+  onDownloadContextMenu: function DV_onDownloadContextMenu(aEvent)
+  {
+    let element = this.richListBox.selectedItem;
+    if (!element) {
+      return false;
+    }
+
+    DownloadsViewController.updateCommands();
+
+    // Set the state attribute so that only the appropriate items are displayed.
+    let contextMenu = document.getElementById("downloadsContextMenu");
+    contextMenu.setAttribute("state", element.getAttribute("state"));
+  },
+
+  onDownloadDragStart: function DV_onDownloadDragStart(aEvent)
+  {
+    let element = this.richListBox.selectedItem;
+    if (!element) {
+      return;
+    }
+
+    let controller = new DownloadsViewItemController(element);
+    let localFile = controller.dataItem.localFile;
+    if (!localFile.exists()) {
+      return;
+    }
+
+    let dataTransfer = aEvent.dataTransfer;
+    dataTransfer.mozSetDataAt("application/x-moz-file", localFile, 0);
+    dataTransfer.effectAllowed = "copyMove";
+    dataTransfer.addElement(element);
+
+    aEvent.stopPropagation();
+  }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsViewItem
+
+/**
+ * Builds and updates a single item in the downloads list widget, responding to
+ * changes in the download state and real-time data.
+ *
+ * @param aDataItem
+ *        DownloadsDataItem to be associated with the view item.
+ * @param aElement
+ *        XUL element corresponding to the single download item in the view.
+ */
+function DownloadsViewItem(aDataItem, aElement)
+{
+  this._element = aElement;
+  this.dataItem = aDataItem;
+
+  this.wasDone = this.dataItem.done;
+  this.wasInProgress = this.dataItem.inProgress;
+  this.lastEstimatedSecondsLeft = Infinity;
+
+  // Set the URI that represents the correct icon for the target file.  As soon
+  // as bug 239948 comment 12 is handled, the "file" property will be always a
+  // file URL rather than a file name.  At that point we should remove the "//"
+  // (double slash) from the icon URI specification (see test_moz_icon_uri.js).
+  this.image = "moz-icon://" + this.dataItem.file + "?size=32";
+
+  let attributes = {
+    "type": "download",
+    "class": "download-state",
+    "id": "downloadsItem_" + this.dataItem.downloadId,
+    "downloadId": this.dataItem.downloadId,
+    "state": this.dataItem.state,
+    "progress": this.dataItem.inProgress ? this.dataItem.percentComplete : 100,
+    "target": this.dataItem.target,
+    "image": this.image
+  };
+
+  for (let attributeName in attributes) {
+    this._element.setAttribute(attributeName, attributes[attributeName]);
+  }
+
+  // Initialize more complex attributes.
+  this._updateProgress();
+  this._updateStatusLine();
+}
+
+DownloadsViewItem.prototype = {
+  /**
+   * The DownloadDataItem associated with this view item.
+   */
+  dataItem: null,
+
+  /**
+   * 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,
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Callback functions from DownloadsData
+
+  /**
+   * Called when the download state might have changed.  Sometimes the state of
+   * the download might be the same as before, if the data layer received
+   * multiple events for the same download.
+   */
+  onStateChange: function DVI_onStateChange()
+  {
+    // If a download just finished successfully, it means that the target file
+    // now exists and we can extract its specific icon.  To ensure that the icon
+    // is reloaded, we must change the URI used by the XUL image element, for
+    // example by adding a query parameter.  Since this URI has a "moz-icon"
+    // scheme, this only works if we add one of the parameters explicitly
+    // supported by the nsIMozIconURI interface.
+    if (!this.wasDone && this.dataItem.openable) {
+      this._element.setAttribute("image", this.image + "&state=normal");
+    }
+
+    // Update the end time using the current time if required.
+    if (this.wasInProgress && !this.dataItem.inProgress) {
+      this.endTime = Date.now();
+    }
+
+    this.wasDone = this.dataItem.done;
+    this.wasInProgress = this.dataItem.inProgress;
+
+    // Update the user interface after switching states.
+    this._element.setAttribute("state", this.dataItem.state);
+    this._updateProgress();
+    this._updateStatusLine();
+  },
+
+  /**
+   * Called when the download progress has changed.
+   */
+  onProgressChange: function DVI_onProgressChange() {
+    this._updateProgress();
+    this._updateStatusLine();
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Functions for updating the user interface
+
+  /**
+   * Updates the progress bar.
+   */
+  _updateProgress: function DVI_updateProgress() {
+    if (this.dataItem.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.dataItem.state == Ci.nsIDownloadManager.DOWNLOAD_SCANNING ||
+               this.dataItem.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.dataItem.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);
+    }
+  },
+
+  /**
+   * Updates the main status line, including bytes transferred, bytes total,
+   * download rate, and time remaining.
+   */
+  _updateStatusLine: function DVI_updateStatusLine() {
+    const nsIDM = Ci.nsIDownloadManager;
+
+    let status = "";
+    let statusTip = "";
+
+    if (this.dataItem.paused) {
+      let transfer = DownloadUtils.getTransferTotal(this.dataItem.currBytes,
+                                                    this.dataItem.maxBytes);
+
+      // We use the same XUL label to display both the state and the amount
+      // transferred, for example "Paused -  1.1 MB".
+      status = DownloadsCommon.strings.statusSeparatorBeforeNumber(
+                                            DownloadsCommon.strings.statePaused,
+                                            transfer);
+    } else if (this.dataItem.state == nsIDM.DOWNLOAD_DOWNLOADING) {
+      let newEstimatedSecondsLeft;
+      [status, newEstimatedSecondsLeft] =
+        DownloadUtils.getDownloadStatus(this.dataItem.currBytes,
+                                        this.dataItem.maxBytes,
+                                        this.dataItem.speed,
+                                        this.lastEstimatedSecondsLeft);
+      this.lastEstimatedSecondsLeft = newEstimatedSecondsLeft;
+    } else if (this.dataItem.starting) {
+      status = DownloadsCommon.strings.stateStarting;
+    } else if (this.dataItem.state == nsIDM.DOWNLOAD_SCANNING) {
+      status = DownloadsCommon.strings.stateScanning;
+    } else if (!this.dataItem.inProgress) {
+      let stateLabel = function () {
+        let s = DownloadsCommon.strings;
+        switch (this.dataItem.state) {
+          case nsIDM.DOWNLOAD_FAILED:           return s.stateFailed;
+          case nsIDM.DOWNLOAD_CANCELED:         return s.stateCanceled;
+          case nsIDM.DOWNLOAD_BLOCKED_PARENTAL: return s.stateBlockedParentalControls;
+          case nsIDM.DOWNLOAD_BLOCKED_POLICY:   return s.stateBlockedPolicy;
+          case nsIDM.DOWNLOAD_DIRTY:            return s.stateDirty;
+          case nsIDM.DOWNLOAD_FINISHED:         return this._fileSizeText;
+        }
+      }.apply(this);
+
+      let [displayHost, fullHost] =
+        DownloadUtils.getURIHost(this.dataItem.referrer || this.dataItem.uri);
+
+      let end = new Date(this.dataItem.endTime);
+      let [displayDate, fullDate] = DownloadUtils.getReadableDates(end);
+
+      // We use the same XUL label to display the state, the host name, and the
+      // end time, for example "Canceled - 222.net - 11:15" or "1.1 MB -
+      // website2.com - Yesterday".  We show the full host and the complete date
+      // in the tooltip.
+      let firstPart = DownloadsCommon.strings.statusSeparator(stateLabel,
+                                                              displayHost);
+      status = DownloadsCommon.strings.statusSeparator(firstPart, displayDate);
+      statusTip = DownloadsCommon.strings.statusSeparator(fullHost, fullDate);
+    }
+
+    this._element.setAttribute("status", status);
+    this._element.setAttribute("statusTip", statusTip || status);
+  },
+
+  /**
+   * Localized string representing the total size of completed downloads, for
+   * example "1.5 MB" or "Unknown size".
+   */
+  get _fileSizeText()
+  {
+    // Display the file size, but show "Unknown" for negative sizes.
+    let fileSize = this.dataItem.maxBytes;
+    if (fileSize < 0) {
+      return DownloadsCommon.strings.sizeUnknown;
+    }
+    let [size, unit] = DownloadUtils.convertByteUnits(fileSize);
+    return DownloadsCommon.strings.sizeWithUnits(size, unit);
+  }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsViewController
+
+/**
+ * Handles part of the user interaction events raised by the downloads list
+ * widget, in particular the "commands" that apply to multiple items, and
+ * dispatches the commands that apply to individual items.
+ */
+const DownloadsViewController = {
+  //////////////////////////////////////////////////////////////////////////////
+  //// Initialization and termination
+
+  initialize: function DVC_initialize()
+  {
+    window.controllers.insertControllerAt(0, this);
+  },
+
+  terminate: function DVC_terminate()
+  {
+    window.controllers.removeController(this);
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsIController
+
+  supportsCommand: function DVC_supportsCommand(aCommand)
+  {
+    // Firstly, determine if this is a command that we can handle.
+    if (!(aCommand in this.commands) &&
+        !(aCommand in DownloadsViewItemController.prototype.commands)) {
+      return false;
+    }
+    // Secondly, determine if focus is on a control in the downloads list.
+    let element = document.commandDispatcher.focusedElement;
+    while (element && element != DownloadsView.richListBox) {
+      element = element.parentNode;
+    }
+    // We should handle the command only if the downloads list is among the
+    // ancestors of the focused element.
+    return !!element;
+  },
+
+  isCommandEnabled: function DVC_isCommandEnabled(aCommand)
+  {
+    // Handle commands that are not selection-specific.
+    if (aCommand == "downloadsCmd_clearList") {
+      return Services.downloads.canCleanUp;
+    }
+
+    // Other commands are selection-specific.
+    let element = DownloadsView.richListBox.selectedItem;
+    return element &&
+           new DownloadsViewItemController(element).isCommandEnabled(aCommand);
+  },
+
+  doCommand: function DVC_doCommand(aCommand)
+  {
+    // If this command is not selection-specific, execute it.
+    if (aCommand in this.commands) {
+      this.commands[aCommand].apply(this);
+      return;
+    }
+
+    // Other commands are selection-specific.
+    let element = DownloadsView.richListBox.selectedItem;
+    if (element) {
+      // The doCommand function also checks if the command is enabled.
+      new DownloadsViewItemController(element).doCommand(aCommand);
+    }
+  },
+
+  onEvent: function () { },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Other functions
+
+  updateCommands: function DVC_updateCommands()
+  {
+    Object.keys(this.commands).forEach(goUpdateCommand);
+    Object.keys(DownloadsViewItemController.prototype.commands)
+          .forEach(goUpdateCommand);
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Selection-independent commands
+
+  /**
+   * This object contains one key for each command that operates regardless of
+   * the currently selected item in the list.
+   */
+  commands: {
+    downloadsCmd_clearList: function DVC_downloadsCmd_clearList()
+    {
+      Services.downloads.cleanUp();
+    }
+  }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsViewItemController
+
+/**
+ * Handles all the user interaction events, in particular the "commands",
+ * related to a single item in the downloads list widgets.
+ */
+function DownloadsViewItemController(aElement) {
+  let downloadId = aElement.getAttribute("downloadId");
+  this.dataItem = DownloadsCommon.data.dataItems[downloadId];
+}
+
+DownloadsViewItemController.prototype = {
+  //////////////////////////////////////////////////////////////////////////////
+  //// Constants
+
+  get kPrefBdmAlertOnExeOpen() "browser.download.manager.alertOnEXEOpen",
+  get kPrefBdmScanWhenDone() "browser.download.manager.scanWhenDone",
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Command dispatching
+
+  /**
+   * The DownloadDataItem controlled by this object.
+   */
+  dataItem: null,
+
+  isCommandEnabled: function DVIC_isCommandEnabled(aCommand)
+  {
+    switch (aCommand) {
+      case "downloadsCmd_open": {
+        return this.dataItem.openable && this.dataItem.localFile.exists();
+      }
+      case "downloadsCmd_show": {
+        return this.dataItem.localFile.exists();
+      }
+      case "downloadsCmd_pauseResume":
+        return this.dataItem.inProgress && this.dataItem.resumable;
+      case "downloadsCmd_retry":
+        return this.dataItem.canRetry;
+      case "downloadsCmd_openReferrer":
+        return !!this.dataItem.referrer;
+      case "cmd_delete":
+      case "downloadsCmd_cancel":
+      case "downloadsCmd_copyLocation":
+      case "downloadsCmd_doDefault":
+        return true;
+    }
+    return false;
+  },
+
+  doCommand: function DVIC_doCommand(aCommand)
+  {
+    if (this.isCommandEnabled(aCommand)) {
+      this.commands[aCommand].apply(this);
+    }
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Item commands
+
+  /**
+   * This object contains one key for each command that operates on this item.
+   *
+   * In commands, the "this" identifier points to the controller item.
+   */
+  commands: {
+    cmd_delete: function DVIC_cmd_delete()
+    {
+      this.commands.downloadsCmd_cancel.apply(this);
+
+      Services.downloads.removeDownload(this.dataItem.downloadId);
+    },
+
+    downloadsCmd_cancel: function DVIC_downloadsCmd_cancel()
+    {
+      if (this.dataItem.inProgress) {
+        Services.downloads.cancelDownload(this.dataItem.downloadId);
+
+        // It is possible that in some cases the Download Manager service
+        // doesn't delete the file from disk when canceling.  See bug 732924.
+        try {
+          let localFile = this.dataItem.localFile;
+          if (localFile.exists()) {
+            localFile.remove(false);
+          }
+        } catch (ex) { }
+      }
+    },
+
+    downloadsCmd_open: function DVIC_downloadsCmd_open()
+    {
+      // Confirm opening executable files if required.
+      let localFile = this.dataItem.localFile;
+      if (localFile.isExecutable()) {
+        let showAlert = true;
+        try {
+          showAlert = Services.prefs.getBoolPref(this.kPrefBdmAlertOnExeOpen);
+        } catch (ex) { }
+
+        // On Vista and above, we rely on native security prompting for
+        // downloaded content unless it's disabled.
+        if (DownloadsCommon.isWinVistaOrHigher) {
+          try {
+            if (Services.prefs.getBoolPref(this.kPrefBdmScanWhenDone)) {
+              showAlert = false;
+            }
+          } catch (ex) { }
+        }
+
+        if (showAlert) {
+          let name = this.dataItem.target;
+          let message =
+              DownloadsCommon.strings.fileExecutableSecurityWarning(name, name);
+          let title =
+              DownloadsCommon.strings.fileExecutableSecurityWarningTitle;
+          let dontAsk =
+              DownloadsCommon.strings.fileExecutableSecurityWarningDontAsk;
+
+          let checkbox = { value: false };
+          let open = Services.prompt.confirmCheck(window, title, message,
+                                                  dontAsk, checkbox);
+          if (!open) {
+            return;
+          }
+
+          Services.prefs.setBoolPref(this.kPrefBdmAlertOnExeOpen,
+                                     !checkbox.value);
+        }
+      }
+
+      // Actually open the file.
+      try {
+        let launched = false;
+        try {
+          let mimeInfo = this.dataItem.download.MIMEInfo;
+          if (mimeInfo.preferredAction == mimeInfo.useHelperApp) {
+            mimeInfo.launchWithFile(localFile);
+            launched = true;
+          }
+        } catch (ex) { }
+        if (!launched) {
+          localFile.launch();
+        }
+      } catch (ex) {
+        // If launch fails, try sending it through the system's external "file:"
+        // URL handler.
+        this._openExternal(localFile);
+      }
+    },
+
+    downloadsCmd_show: function DVIC_downloadsCmd_show()
+    {
+      let localFile = this.dataItem.localFile;
+
+      try {
+        // Show the directory containing the file and select the file.
+        localFile.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 = localFile.parent.QueryInterface(Ci.nsILocalFile);
+        if (parent) {
+          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.
+            this._openExternal(parent);
+          }
+        }
+      }
+    },
+
+    downloadsCmd_pauseResume: function DVIC_downloadsCmd_pauseResume()
+    {
+      if (this.dataItem.paused) {
+        Services.downloads.resumeDownload(this.dataItem.downloadId);
+      } else {
+        Services.downloads.pauseDownload(this.dataItem.downloadId);
+      }
+    },
+
+    downloadsCmd_retry: function DVIC_downloadsCmd_retry()
+    {
+      Services.downloads.retryDownload(this.dataItem.downloadId);
+    },
+
+    downloadsCmd_openReferrer: function DVIC_downloadsCmd_openReferrer()
+    {
+      openURL(this.dataItem.referrer);
+    },
+
+    downloadsCmd_copyLocation: function DVIC_downloadsCmd_copyLocation()
+    {
+      let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]
+                      .getService(Ci.nsIClipboardHelper);
+      clipboard.copyString(this.dataItem.uri);
+    },
+
+    downloadsCmd_doDefault: function DVIC_downloadsCmd_doDefault()
+    {
+      const nsIDM = Ci.nsIDownloadManager;
+
+      // Determine the default command for the current item.
+      let defaultCommand = function () {
+        switch (this.dataItem.state) {
+          case nsIDM.DOWNLOAD_NOTSTARTED:       return "downloadsCmd_cancel";
+          case nsIDM.DOWNLOAD_DOWNLOADING:      return "downloadsCmd_show";
+          case nsIDM.DOWNLOAD_FINISHED:         return "downloadsCmd_open";
+          case nsIDM.DOWNLOAD_FAILED:           return "downloadsCmd_retry";
+          case nsIDM.DOWNLOAD_CANCELED:         return "downloadsCmd_retry";
+          case nsIDM.DOWNLOAD_PAUSED:           return "downloadsCmd_pauseResume";
+          case nsIDM.DOWNLOAD_QUEUED:           return "downloadsCmd_cancel";
+          case nsIDM.DOWNLOAD_BLOCKED_PARENTAL: return "downloadsCmd_openReferrer";
+          case nsIDM.DOWNLOAD_SCANNING:         return "downloadsCmd_show";
+          case nsIDM.DOWNLOAD_DIRTY:            return "downloadsCmd_openReferrer";
+          case nsIDM.DOWNLOAD_BLOCKED_POLICY:   return "downloadsCmd_openReferrer";
+        }
+      }.apply(this);
+      // Invoke the command.
+      this.doCommand(defaultCommand);
+    }
+  },
+
+  /**
+   * Support function to open the specified nsIFile.
+   */
+  _openExternal: function DVIC_openExternal(aFile)
+  {
+    let protocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+                      .getService(Ci.nsIExternalProtocolService);
+    protocolSvc.loadUrl(makeFileURI(aFile));
+  }
+};
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/content/downloadsOverlay.xul
@@ -0,0 +1,111 @@
+<?xml version="1.0"?>
+# -*- Mode: HTML; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+# vim: set ts=2 et sw=2 tw=80:
+# 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 overlay SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
+
+<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+         id="downloadsOverlay">
+
+  <commandset>
+    <command id="downloadsCmd_doDefault"
+             oncommand="goDoCommand('downloadsCmd_doDefault')"/>
+    <command id="downloadsCmd_pauseResume"
+             oncommand="goDoCommand('downloadsCmd_pauseResume')"/>
+    <command id="downloadsCmd_cancel"
+             oncommand="goDoCommand('downloadsCmd_cancel')"/>
+    <command id="downloadsCmd_open"
+             oncommand="goDoCommand('downloadsCmd_open')"/>
+    <command id="downloadsCmd_show"
+             oncommand="goDoCommand('downloadsCmd_show')"/>
+    <command id="downloadsCmd_retry"
+             oncommand="goDoCommand('downloadsCmd_retry')"/>
+    <command id="downloadsCmd_openReferrer"
+             oncommand="goDoCommand('downloadsCmd_openReferrer')"/>
+    <command id="downloadsCmd_copyLocation"
+             oncommand="goDoCommand('downloadsCmd_copyLocation')"/>
+    <command id="downloadsCmd_clearList"
+             oncommand="goDoCommand('downloadsCmd_clearList')"/>
+  </commandset>
+
+  <popupset>
+    <!-- The panel has level="top" to ensure that it is never hidden by the
+         taskbar on Windows.  See bug 672365.  For accessibility to screen
+         readers, we use a label on the panel instead of the anchor because the
+         panel can also be displayed without an anchor. -->
+    <panel id="downloadsPanel"
+           aria-label="&downloads.title;"
+           role="group"
+           type="arrow"
+           orient="vertical"
+           level="top"
+           onpopupshown="DownloadsPanel.onPopupShown(event);"
+           onpopuphidden="DownloadsPanel.onPopupHidden(event);">
+      <!-- The following popup menu should be a child of the panel element,
+           otherwise flickering may occur when the cursor is moved over the area
+           of a disabled menu item that overlaps the panel.  See bug 492960. -->
+      <menupopup id="downloadsContextMenu"
+                 class="download-state">
+        <menuitem command="downloadsCmd_pauseResume"
+                  class="downloadPauseMenuItem"
+                  label="&cmd.pause.label;"
+                  accesskey="&cmd.pause.accesskey;"/>
+        <menuitem command="downloadsCmd_pauseResume"
+                  class="downloadResumeMenuItem"
+                  label="&cmd.resume.label;"
+                  accesskey="&cmd.resume.accesskey;"/>
+        <menuitem command="downloadsCmd_cancel"
+                  class="downloadCancelMenuItem"
+                  label="&cmd.cancel.label;"
+                  accesskey="&cmd.cancel.accesskey;"/>
+        <menuitem command="cmd_delete"
+                  class="downloadRemoveFromListMenuItem"
+                  label="&cmd.removeFromList.label;"
+                  accesskey="&cmd.removeFromList.accesskey;"/>
+        <menuitem command="downloadsCmd_show"
+                  class="downloadShowMenuItem"
+#ifdef XP_MACOSX
+                  label="&cmd.showMac.label;"
+                  accesskey="&cmd.showMac.accesskey;"
+#else
+                  label="&cmd.show.label;"
+                  accesskey="&cmd.show.accesskey;"
+#endif
+                  />
+
+        <menuseparator class="downloadCommandsSeparator"/>
+
+        <menuitem command="downloadsCmd_openReferrer"
+                  label="&cmd.goToDownloadPage.label;"
+                  accesskey="&cmd.goToDownloadPage.accesskey;"/>
+        <menuitem command="downloadsCmd_copyLocation"
+                  label="&cmd.copyDownloadLink.label;"
+                  accesskey="&cmd.copyDownloadLink.accesskey;"/>
+
+        <menuseparator/>
+
+        <menuitem command="downloadsCmd_clearList"
+                  label="&cmd.clearList.label;"
+                  accesskey="&cmd.clearList.accesskey;"/>
+      </menupopup>
+
+      <richlistbox id="downloadsListBox"
+                   class="plain"
+                   flex="1"
+                   context="downloadsContextMenu"
+                   onkeypress="DownloadsView.onDownloadKeyPress(event);"
+                   oncontextmenu="DownloadsView.onDownloadContextMenu(event);"
+                   ondragstart="DownloadsView.onDownloadDragStart(event);"/>
+
+      <button id="downloadsHistory"
+              class="plain"
+              label="&downloadshistory.label;"
+              accesskey="&downloadshistory.accesskey;"
+              oncommand="DownloadsPanel.showDownloadsHistory();"/>
+    </panel>
+  </popupset>
+</overlay>
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/content/indicator.js
@@ -0,0 +1,485 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/**
+ * Handles the indicator that displays the progress of ongoing downloads, which
+ * is also used as the anchor for the downloads panel.
+ *
+ * This module includes the following constructors and global objects:
+ *
+ * DownloadsButton
+ * Main entry point for the downloads indicator.  Depending on how the toolbars
+ * have been customized, this object determines if we should show a fully
+ * functional indicator, a placeholder used during customization and in the
+ * customization palette, or a neutral view as a temporary anchor for the
+ * downloads panel.
+ *
+ * DownloadsIndicatorView
+ * Builds and updates the actual downloads status widget, responding to changes
+ * in the global status data, or provides a neutral view if the indicator is
+ * removed from the toolbars and only used as a temporary anchor.  In addition,
+ * handles the user interaction events raised by the widget.
+ */
+
+"use strict";
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsButton
+
+/**
+ * Main entry point for the downloads indicator.  Depending on how the toolbars
+ * have been customized, this object determines if we should show a fully
+ * functional indicator, a placeholder used during customization and in the
+ * customization palette, or a neutral view as a temporary anchor for the
+ * downloads panel.
+ */
+const DownloadsButton = {
+  /**
+   * Returns a reference to the downloads button position placeholder, or null
+   * if not available because it has been removed from the toolbars.
+   */
+  get _placeholder()
+  {
+    return document.getElementById("downloads-button");
+  },
+
+  /**
+   * This function is called synchronously at window initialization.  It only
+   * sets the visibility of user interface elements to avoid flickering.
+   *
+   * NOTE: To keep startup time to a minimum, this function should not perform
+   *       any expensive operations or input/output, and should not cause the
+   *       Download Manager service to start.
+   */
+  initializePlaceholder: function DB_initializePlaceholder()
+  {
+    // We must hide the placeholder used for toolbar customization, unless it
+    // has been removed from the toolbars and is now located in the palette.
+    let placeholder = this._placeholder;
+    if (placeholder) {
+      placeholder.collapsed = true;
+    }
+  },
+
+  /**
+   * This function is called asynchronously just after window initialization.
+   *
+   * NOTE: This function should limit the input/output it performs to improve
+   *       startup time, and in particular should not cause the Download Manager
+   *       service to start.
+   */
+  initializeIndicator: function DB_initializeIndicator()
+  {
+    this._update();
+  },
+
+  /**
+   * Indicates whether toolbar customization is in progress.
+   */
+  _customizing: false,
+
+  /**
+   * This function is called when toolbar customization starts.
+   *
+   * During customization, we never show the actual download progress indication
+   * or the event notifications, but we show a neutral placeholder.  The neutral
+   * placeholder is an ordinary button defined in the browser window that can be
+   * moved freely between the toolbars and the customization palette.
+   */
+  customizeStart: function DB_customizeStart()
+  {
+    // Hide the indicator and prevent it to be displayed as a temporary anchor
+    // during customization, even if requested using the getAnchor method.
+    this._customizing = true;
+    this._anchorRequested = false;
+    DownloadsIndicatorView.indicator.collapsed = true;
+
+    let placeholder = this._placeholder;
+    if (placeholder) {
+      placeholder.collapsed = false;
+    }
+  },
+
+  /**
+   * This function is called when toolbar customization ends.
+   */
+  customizeDone: function DB_customizeDone()
+  {
+    this._customizing = false;
+    this._update();
+  },
+
+  /**
+   * This function is called during initialization or when toolbar customization
+   * ends.  It determines if we should enable or disable the object that keeps
+   * the indicator updated, and ensures that the placeholder is hidden unless it
+   * has been moved to the customization palette.
+   *
+   * NOTE: This function is also called on startup, thus it should limit the
+   *       input/output it performs, and in particular should not cause the
+   *       Download Manager service to start.
+   */
+  _update: function DB_update() {
+    this._updatePositionInternal();
+
+    let placeholder = this._placeholder;
+    if (!DownloadsCommon.useToolkitUI) {
+      DownloadsIndicatorView.ensureInitialized();
+      if (placeholder) {
+        placeholder.collapsed = true;
+      }
+    } else {
+      DownloadsIndicatorView.ensureTerminated();
+    }
+  },
+
+  /**
+   * Determines the position where the indicator should appear, and moves its
+   * associated element to the new position.
+   *
+   * @return Anchor element, or null if the indicator is not visible.
+   */
+  updatePosition: function DB_updatePosition()
+  {
+    // Don't update the position of the indicator if it's currently being used
+    // as the anchor for the panel.  This ensures that the panel doesn't flicker
+    // because we move the DOM element to which it's anchored.
+    if (!this._anchorRequested) {
+      return this._updatePositionInternal();
+    }
+  },
+
+  _updatePositionInternal: function DB_updatePositionInternal()
+  {
+    let indicator = DownloadsIndicatorView.indicator;
+    let placeholder = this._placeholder;
+
+    // Firstly, determine if we should always hide the indicator.
+    if (!placeholder && !this._anchorRequested &&
+        !DownloadsIndicatorView.hasDownloads) {
+      indicator.collapsed = true;
+      return null;
+    }
+    indicator.collapsed = false;
+
+    indicator.open = this._anchorRequested;
+
+    // Determine if we should display the indicator in a known position.
+    if (placeholder) {
+      placeholder.parentNode.insertBefore(indicator, placeholder);
+      // Determine if the placeholder is located on a visible toolbar.
+      if (isElementVisible(placeholder.parentNode)) {
+        return DownloadsIndicatorView.indicatorAnchor;
+      }
+    }
+
+    // If not customized, the indicator is normally in the navigation bar.
+    // Always place it in the default position, unless we need an anchor.
+    if (!this._anchorRequested) {
+      this._navBar.appendChild(indicator);
+      return null;
+    }
+
+    // Show the indicator temporarily in the navigation bar, if visible.
+    if (isElementVisible(this._navBar)) {
+      this._navBar.appendChild(indicator);
+      return DownloadsIndicatorView.indicatorAnchor;
+    }
+
+    // Show the indicator temporarily in the tab bar, if visible.
+    if (!this._tabsToolbar.collapsed) {
+      this._tabsToolbar.appendChild(indicator);
+      return DownloadsIndicatorView.indicatorAnchor;
+    }
+
+    // The temporary anchor cannot be shown.
+    return null;
+  },
+
+  /**
+   * Indicates whether we should try and show the indicator temporarily as an
+   * anchor for the panel, even if the indicator would be hidden by default.
+   */
+  _anchorRequested: false,
+
+  /**
+   * Ensures that there is an anchor available for the panel.
+   *
+   * @return Element where the panel should be anchored, or null if an anchor
+   *         is not available, for example because both the tab bar and the
+   *         navigation bar are hidden.
+   */
+  getAnchor: function DB_getAnchor()
+  {
+    // Do not allow anchoring the panel to the element while customizing.
+    if (this._customizing) {
+      return null;
+    }
+
+    this._anchorRequested = true;
+    return this._updatePositionInternal();
+  },
+
+  /**
+   * Allows the temporary anchor to be hidden.
+   */
+  releaseAnchor: function DB_releaseAnchor()
+  {
+    this._anchorRequested = false;
+    this._updatePositionInternal();
+  },
+
+  get _tabsToolbar()
+  {
+    delete this._tabsToolbar;
+    return this._tabsToolbar = document.getElementById("TabsToolbar");
+  },
+
+  get _navBar()
+  {
+    delete this._navBar;
+    return this._navBar = document.getElementById("nav-bar");
+  }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsIndicatorView
+
+/**
+ * Builds and updates the actual downloads status widget, responding to changes
+ * in the global status data, or provides a neutral view if the indicator is
+ * removed from the toolbars and only used as a temporary anchor.  In addition,
+ * handles the user interaction events raised by the widget.
+ */
+const DownloadsIndicatorView = {
+  _initialized: false,
+
+  /**
+   * Prepares the downloads indicator to be displayed.
+   */
+  ensureInitialized: function DIV_ensureInitialized()
+  {
+    if (this._initialized) {
+      return;
+    }
+    this._initialized = true;
+
+    window.addEventListener("unload", this.onWindowUnload, false);
+    DownloadsCommon.indicatorData.addView(this);
+  },
+
+  /**
+   * Frees the internal resources related to the indicator.
+   */
+  ensureTerminated: function DIV_ensureTerminated()
+  {
+    if (!this._initialized) {
+      return;
+    }
+    this._initialized = false;
+
+    window.removeEventListener("unload", this.onWindowUnload, false);
+    DownloadsCommon.indicatorData.removeView(this);
+
+    // Reset the view properties, so that a neutral indicator is displayed if we
+    // are visible only temporarily as an anchor.
+    this.counter = "";
+    this.percentComplete = 0;
+    this.paused = false;
+    this.attention = false;
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Direct control functions
+
+  /**
+   * Set while we are waiting for a notification to fade out.
+   */
+  _notificationTimeout: null,
+
+  /**
+   * If the status indicator is visible in its assigned position, shows for a
+   * brief time a visual notification of a relevant event, like a new download.
+   */
+  showEventNotification: function DIV_showEventNotification()
+  {
+    if (!this._initialized) {
+      return;
+    }
+
+    if (this._notificationTimeout) {
+      clearTimeout(this._notificationTimeout);
+    }
+
+    let indicator = this.indicator;
+    indicator.setAttribute("notification", "true");
+    this._notificationTimeout = setTimeout(
+      function () indicator.removeAttribute("notification"), 1000);
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Callback functions from DownloadsIndicatorData
+
+  /**
+   * Indicates whether the indicator should be shown because there are some
+   * downloads to be displayed.
+   */
+  set hasDownloads(aValue)
+  {
+    if (this._hasDownloads != aValue) {
+      this._hasDownloads = aValue;
+      DownloadsButton.updatePosition();
+    }
+    return aValue;
+  },
+  get hasDownloads()
+  {
+    return this._hasDownloads;
+  },
+  _hasDownloads: false,
+
+  /**
+   * Status text displayed in the indicator.  If this is set to an empty value,
+   * then the small downloads icon is displayed instead of the text.
+   */
+  set counter(aValue)
+  {
+    if (this._counter !== aValue) {
+      this._counter = aValue;
+      if (this._counter)
+        this.indicator.setAttribute("counter", "true");
+      else
+        this.indicator.removeAttribute("counter");
+      // We have to set the attribute instead of using the property because the
+      // XBL binding isn't applied if the element is invisible for any reason.
+      this._indicatorCounter.setAttribute("value", aValue);
+    }
+    return aValue;
+  },
+  _counter: null,
+
+  /**
+   * Progress indication to display, from 0 to 100, or -1 if unknown.  The
+   * progress bar is hidden if the current progress is unknown and no status
+   * text is set in the "counter" property.
+   */
+  set percentComplete(aValue)
+  {
+    if (this._percentComplete !== aValue) {
+      this._percentComplete = aValue;
+      if (this._percentComplete >= 0)
+        this.indicator.setAttribute("progress", "true");
+      else
+        this.indicator.removeAttribute("progress");
+      // We have to set the attribute instead of using the property because the
+      // XBL binding isn't applied if the element is invisible for any reason.
+      this._indicatorProgress.setAttribute("value", Math.max(aValue, 0));
+    }
+    return aValue;
+  },
+  _percentComplete: null,
+
+  /**
+   * Indicates whether the progress won't advance because of a paused state.
+   * Setting this property forces a paused progress bar to be displayed, even if
+   * the current progress information is unavailable.
+   */
+  set paused(aValue)
+  {
+    if (this._paused != aValue) {
+      this._paused = aValue;
+      if (this._paused) {
+        this.indicator.setAttribute("paused", "true")
+      } else {
+        this.indicator.removeAttribute("paused");
+      }
+    }
+    return aValue;
+  },
+  _paused: false,
+
+  /**
+   * Set when the indicator should draw user attention to itself.
+   */
+  set attention(aValue)
+  {
+    if (this._attention != aValue) {
+      this._attention = aValue;
+      if (aValue) {
+        this.indicator.setAttribute("attention", "true")
+      } else {
+        this.indicator.removeAttribute("attention");
+      }
+    }
+    return aValue;
+  },
+  _attention: false,
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// User interface event functions
+
+  onWindowUnload: function DIV_onWindowUnload()
+  {
+    // This function is registered as an event listener, we can't use "this".
+    DownloadsIndicatorView.ensureTerminated();
+  },
+
+  onCommand: function DIV_onCommand(aEvent)
+  {
+    if (DownloadsCommon.useToolkitUI) {
+      // The panel won't suppress attention for us, we need to clear now.
+      DownloadsCommon.indicatorData.attention = false;
+    }
+
+    BrowserDownloadsUI();
+
+    aEvent.stopPropagation();
+  },
+
+  onDragOver: function DIV_onDragOver(aEvent)
+  {
+    browserDragAndDrop.dragOver(aEvent);
+  },
+
+  onDragExit: function () { },
+
+  onDrop: function DIV_onDrop(aEvent)
+  {
+    let name = {};
+    let url = browserDragAndDrop.drop(aEvent, name);
+    if (url) {
+      saveURL(url, name.value, null, true, true);
+      aEvent.preventDefault();
+    }
+  },
+
+  get indicator()
+  {
+    delete this.indicator;
+    return this.indicator = document.getElementById("downloads-indicator");
+  },
+
+  get indicatorAnchor()
+  {
+    delete this.indicatorAnchor;
+    return this.indicatorAnchor =
+      document.getElementById("downloads-indicator-anchor");
+  },
+
+  get _indicatorCounter()
+  {
+    delete this._indicatorCounter;
+    return this._indicatorCounter =
+      document.getElementById("downloads-indicator-counter");
+  },
+
+  get _indicatorProgress()
+  {
+    delete this._indicatorProgress;
+    return this._indicatorProgress =
+      document.getElementById("downloads-indicator-progress");
+  }
+};
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/content/indicatorXul.inc
@@ -0,0 +1,44 @@
+# -*- Mode: HTML; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+# vim: set ts=2 et sw=2 tw=80:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<script type="application/javascript"
+        src="chrome://browser/content/downloads/downloads.js"/>
+<script type="application/javascript"
+        src="chrome://browser/content/downloads/indicator.js"/>
+
+<popupset>
+  <!-- The downloads indicator is placed in its final toolbar location
+       programmatically, and can be shown temporarily even when its
+       placeholder is removed from the toolbars.  Its initial location within
+       the document must not be a toolbar or the toolbar palette, otherwise the
+       toolbar handling code could remove it from the document. -->
+  <toolbarbutton id="downloads-indicator"
+                 class="toolbarbutton-1 chromeclass-toolbar-additional"
+                 tooltiptext="&indicator.tooltiptext;"
+                 collapsed="true"
+                 oncommand="DownloadsIndicatorView.onCommand(event);"
+                 ondrop="DownloadsIndicatorView.onDrop(event);"
+                 ondragover="DownloadsIndicatorView.onDragOver(event);"
+                 ondragenter="DownloadsIndicatorView.onDragOver(event);"
+                 ondragleave="DownloadsIndicatorView.onDragLeave(event);">
+    <!-- The panel's anchor area is smaller than the outer button, but must
+         always be visible and must not move or resize when the indicator
+         state changes, otherwise the panel could change its position or lose
+         its arrow unexpectedly. -->
+    <stack id="downloads-indicator-anchor">
+      <vbox id="downloads-indicator-progress-area"
+            pack="center">
+        <description id="downloads-indicator-counter"/>
+        <progressmeter id="downloads-indicator-progress"
+                       class="plain"
+                       min="0"
+                       max="100"/>
+      </vbox>
+      <vbox id="downloads-indicator-icon"/>
+      <vbox id="downloads-indicator-notification"/>
+    </stack>
+  </toolbarbutton>
+</popupset>
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/jar.mn
@@ -0,0 +1,6 @@
+browser.jar:
+*       content/browser/downloads/download.xml           (content/download.xml)
+        content/browser/downloads/downloads.css          (content/downloads.css)
+        content/browser/downloads/downloads.js           (content/downloads.js)
+*       content/browser/downloads/downloadsOverlay.xul   (content/downloadsOverlay.xul)
+        content/browser/downloads/indicator.js           (content/indicator.js)
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/src/BrowserDownloads.manifest
@@ -0,0 +1,4 @@
+component {49507fe5-2cee-4824-b6a3-e999150ce9b8} DownloadsStartup.js
+contract @mozilla.org/browser/downloadsstartup;1 {49507fe5-2cee-4824-b6a3-e999150ce9b8}
+category app-startup DownloadsStartup service,@mozilla.org/browser/downloadsstartup;1
+component {4d99321e-d156-455b-81f7-e7aa2308134f} DownloadsUI.js
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/src/DownloadsCommon.jsm
@@ -0,0 +1,1220 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
+/* 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";
+
+var EXPORTED_SYMBOLS = [
+  "DownloadsCommon",
+];
+
+/**
+ * Handles the Downloads panel shared methods and data access.
+ *
+ * This file includes the following constructors and global objects:
+ *
+ * DownloadsCommon
+ * This object is exposed directly to the consumers of this JavaScript module,
+ * and provides shared methods for all the instances of the user interface.
+ *
+ * DownloadsData
+ * Retrieves the list of past and completed downloads from the underlying
+ * Download Manager data, and provides asynchronous notifications allowing
+ * to build a consistent view of the available data.
+ *
+ * DownloadsDataItem
+ * Represents a single item in the list of downloads.  This object either wraps
+ * an existing nsIDownload from the Download Manager, or provides the same
+ * information read directly from the downloads database, with the possibility
+ * of querying the nsIDownload lazily, for performance reasons.
+ *
+ * DownloadsIndicatorData
+ * This object registers itself with DownloadsData as a view, and transforms the
+ * notifications it receives into overall status data, that is then broadcast to
+ * the registered download status indicators.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gBrowserGlue",
+                                   "@mozilla.org/browser/browserglue;1",
+                                   "nsIBrowserGlue");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+                                  "resource://gre/modules/NetUtil.jsm");
+
+const nsIDM = Ci.nsIDownloadManager;
+
+const kDownloadsStringBundleUrl =
+  "chrome://browser/locale/downloads/downloads.properties";
+
+const kDownloadsStringsRequiringFormatting = {
+  sizeWithUnits: true,
+  shortTimeLeftSeconds: true,
+  shortTimeLeftMinutes: true,
+  shortTimeLeftHours: true,
+  shortTimeLeftDays: true,
+  statusSeparator: true,
+  statusSeparatorBeforeNumber: true,
+  fileExecutableSecurityWarning: true
+};
+
+XPCOMUtils.defineLazyGetter(this, "DownloadsLocalFileCtor", function () {
+  return Components.Constructor("@mozilla.org/file/local;1",
+                                "nsILocalFile", "initWithPath");
+});
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsCommon
+
+/**
+ * This object is exposed directly to the consumers of this JavaScript module,
+ * and provides shared methods for all the instances of the user interface.
+ */
+const DownloadsCommon = {
+  /**
+   * Returns an object whose keys are the string names from the downloads string
+   * bundle, and whose values are either the translated strings or functions
+   * returning formatted strings.
+   */
+  get strings()
+  {
+    let strings = {};
+    let sb = Services.strings.createBundle(kDownloadsStringBundleUrl);
+    let enumerator = sb.getSimpleEnumeration();
+    while (enumerator.hasMoreElements()) {
+      let string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
+      let stringName = string.key;
+      if (stringName in kDownloadsStringsRequiringFormatting) {
+        strings[stringName] = function () {
+          // Convert "arguments" to a real array before calling into XPCOM.
+          return sb.formatStringFromName(stringName,
+                                         Array.slice(arguments, 0),
+                                         arguments.length);
+        };
+      } else {
+        strings[stringName] = string.value;
+      }
+    }
+    delete this.strings;
+    return this.strings = strings;
+  },
+
+  /**
+   * Generates a very short string representing the given time left.
+   *
+   * @param aSeconds
+   *        Value to be formatted.  It represents the number of seconds, it must
+   *        be positive but does not need to be an integer.
+   *
+   * @return Formatted string, for example "30s" or "2h".  The returned value is
+   *         maximum three characters long, at least in English.
+   */
+  formatTimeLeft: function DC_formatTimeLeft(aSeconds)
+  {
+    // Decide what text to show for the time
+    let seconds = Math.round(aSeconds);
+    if (!seconds) {
+      return "";
+    } else if (seconds <= 30) {
+      return DownloadsCommon.strings["shortTimeLeftSeconds"](seconds);
+    }
+    let minutes = Math.round(aSeconds / 60);
+    if (minutes < 60) {
+      return DownloadsCommon.strings["shortTimeLeftMinutes"](minutes);
+    }
+    let hours = Math.round(minutes / 60);
+    if (hours < 48) { // two days
+      return DownloadsCommon.strings["shortTimeLeftHours"](hours);
+    }
+    let days = Math.round(hours / 24);
+    return DownloadsCommon.strings["shortTimeLeftDays"](Math.min(days, 99));
+  },
+
+  /**
+   * Indicates whether we should show the full Download Manager window interface
+   * instead of the simplified panel interface.  The behavior of downloads
+   * across browsing session is consistent with the selected interface.
+   */
+  get useToolkitUI()
+  {
+    try {
+      return Services.prefs.getBoolPref("browser.download.useToolkitUI");
+    } catch (ex) { }
+    return false;
+  },
+
+  /**
+   * Returns a reference to the DownloadsData singleton.
+   *
+   * This does not need to be a lazy getter, since no initialization is required
+   * at present.
+   */
+  get data() DownloadsData,
+
+  /**
+   * Returns a reference to the DownloadsData singleton.
+   *
+   * This does not need to be a lazy getter, since no initialization is required
+   * at present.
+   */
+  get indicatorData() DownloadsIndicatorData
+};
+
+/**
+ * Returns true if we are executing on Windows Vista or a later version.
+ */
+XPCOMUtils.defineLazyGetter(DownloadsCommon, "isWinVistaOrHigher", function () {
+  let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
+  if (os != "WINNT") {
+    return false;
+  }
+  let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
+  return parseFloat(sysInfo.getProperty("version")) >= 6;
+});
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsData
+
+/**
+ * Retrieves the list of past and completed downloads from the underlying
+ * Download Manager data, and provides asynchronous notifications allowing to
+ * build a consistent view of the available data.
+ *
+ * This object responds to real-time changes in the underlying Download Manager
+ * data.  For example, the deletion of one or more downloads is notified through
+ * the nsIObserver interface, while any state or progress change is notified
+ * through the nsIDownloadProgressListener interface.
+ *
+ * Note that using this object does not automatically start the Download Manager
+ * service.  Consumers will see an empty list of downloads until the service is
+ * actually started.  This is useful to display a neutral progress indicator in
+ * the main browser window until the autostart timeout elapses.
+ */
+const DownloadsData = {
+  /**
+   * Starts receiving events for current downloads.
+   *
+   * @param aDownloadManagerService
+   *        Reference to the service implementing nsIDownloadManager.  We need
+   *        this because getService isn't available for us when this method is
+   *        called, and we must ensure to register our listeners before the
+   *        getService call for the Download Manager returns.
+   */
+  initializeDataLink: function DD_initializeDataLink(aDownloadManagerService)
+  {
+    // Start receiving real-time events.
+    aDownloadManagerService.addListener(this);
+    Services.obs.addObserver(this, "download-manager-remove-download", false);
+    Services.obs.addObserver(this, "download-manager-database-type-changed",
+                             false);
+  },
+
+  /**
+   * Stops receiving events for current downloads and cancels any pending read.
+   */
+  terminateDataLink: function DD_terminateDataLink()
+  {
+    this._terminateDataAccess();
+
+    // Stop receiving real-time events.
+    Services.obs.removeObserver(this, "download-manager-database-type-changed");
+    Services.obs.removeObserver(this, "download-manager-remove-download");
+    Services.downloads.removeListener(this);
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Registration of views
+
+  /**
+   * Array of view objects that should be notified when the available download
+   * data changes.
+   */
+  _views: [],
+
+  /**
+   * Adds an object to be notified when the available download data changes.
+   * The specified object is initialized with the currently available downloads.
+   *
+   * @param aView
+   *        DownloadsView object to be added.  This reference must be passed to
+   *        removeView before termination.
+   */
+  addView: function DD_addView(aView)
+  {
+    this._views.push(aView);
+    this._updateView(aView);
+  },
+
+  /**
+   * Removes an object previously added using addView.
+   *
+   * @param aView
+   *        DownloadsView object to be removed.
+   */
+  removeView: function DD_removeView(aView)
+  {
+    let index = this._views.indexOf(aView);
+    if (index != -1) {
+      this._views.splice(index, 1);
+    }
+  },
+
+  /**
+   * Ensures that the currently loaded data is added to the specified view.
+   *
+   * @param aView
+   *        DownloadsView object to be initialized.
+   */
+  _updateView: function DD_updateView(aView)
+  {
+    // Indicate to the view that a batch loading operation is in progress.
+    aView.onDataLoadStarting();
+
+    // Sort backwards by download identifier, ensuring that the most recent
+    // downloads are added first regardless of their state.
+    let loadedItemsArray = [dataItem
+                            for each (dataItem in this.dataItems)
+                            if (dataItem)];
+    loadedItemsArray.sort(function(a, b) b.downloadId - a.downloadId);
+    loadedItemsArray.forEach(
+      function (dataItem) aView.onDataItemAdded(dataItem, false)
+    );
+
+    // Notify the view that all data is available unless loading is in progress.
+    if (!this._pendingStatement) {
+      aView.onDataLoadCompleted();
+    }
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// In-memory downloads data store
+
+  /**
+   * Object containing all the available DownloadsDataItem objects, indexed by
+   * their numeric download identifier.  The identifiers of downloads that have
+   * been removed from the Download Manager data are still present, however the
+   * associated objects are replaced with the value "null".  This is required to
+   * prevent race conditions when populating the list asynchronously.
+   */
+  dataItems: {},
+
+  /**
+   * While operating in Private Browsing Mode, persistent data items are parked
+   * here until we return to the normal mode.
+   */
+  _persistentDataItems: {},
+
+  /**
+   * Clears the loaded data.
+   */
+  clear: function DD_clear()
+  {
+    this._terminateDataAccess();
+    this.dataItems = {};
+  },
+
+  /**
+   * Returns the data item associated with the provided source object.  The
+   * source can be a download object that we received from the Download Manager
+   * because of a real-time notification, or a row from the downloads database,
+   * during the asynchronous data load.
+   *
+   * In case we receive download status notifications while we are still
+   * populating the list of downloads from the database, we want the real-time
+   * status to take precedence over the state that is read from the database,
+   * which might be older.  This is achieved by creating the download item if
+   * it's not already in the list, but never updating the returned object using
+   * the data from the database, if the object already exists.
+   *
+   * @param aSource
+   *        Object containing the data with which the item should be initialized
+   *        if it doesn't already exist in the list.  This should implement
+   *        either nsIDownload or mozIStorageRow.  If the item exists, this
+   *        argument is only used to retrieve the download identifier.
+   * @param aMayReuseId
+   *        If false, indicates that the download should not be added if a
+   *        download with the same identifier was removed in the meantime.  This
+   *        ensures that, while loading the list asynchronously, downloads that
+   *        have been removed in the meantime do no reappear inadvertently.
+   *
+   * @return New or existing data item, or null if the item was deleted from the
+   *         list of available downloads.
+   */
+  _getOrAddDataItem: function DD_getOrAddDataItem(aSource, aMayReuseId)
+  {
+    let downloadId = (aSource instanceof Ci.nsIDownload)
+                     ? aSource.id
+                     : aSource.getResultByName("id");
+    if (downloadId in this.dataItems) {
+      let existingItem = this.dataItems[downloadId];
+      if (existingItem || !aMayReuseId) {
+        // Returns null if the download was removed and we can't reuse the item.
+        return existingItem;
+      }
+    }
+
+    let dataItem = new DownloadsDataItem(aSource);
+    this.dataItems[downloadId] = dataItem;
+
+    // Create the view items before returning.
+    let addToStartOfList = aSource instanceof Ci.nsIDownload;
+    this._views.forEach(
+      function (view) view.onDataItemAdded(dataItem, addToStartOfList)
+    );
+    return dataItem;
+  },
+
+  /**
+   * Removes the data item with the specified identifier.
+   *
+   * This method can be called at most once per download identifier.
+   */
+  _removeDataItem: function DD_removeDataItem(aDownloadId)
+  {
+    if (aDownloadId in this.dataItems) {
+      let dataItem = this.dataItems[aDownloadId];
+      this._views.forEach(
+        function (view) view.onDataItemRemoved(dataItem)
+      );
+    }
+    this.dataItems[aDownloadId] = null;
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Persistent data loading
+
+  /**
+   * Asynchronous database statement used to read the list of downloads.
+   */
+  _statement: null,
+
+  /**
+   * Represents an executing statement, allowing its cancellation.
+   */
+  _pendingStatement: null,
+
+  /**
+   * Indicates which kind of items from the persistent downloads database have
+   * been fully loaded in memory and are available to the views.  This can
+   * assume the value of one of the kLoad constants.
+   */
+  _loadState: 0,
+
+  /** No downloads have been fully loaded yet. */
+  get kLoadNone() 0,
+  /** All the active downloads in the database are loaded in memory. */
+  get kLoadActive() 1,
+  /** All the downloads in the database are loaded in memory. */
+  get kLoadAll() 2,
+
+  /**
+   * Reloads the specified kind of downloads from the persistent database.  This
+   * method must only be called when Private Browsing Mode is disabled.
+   *
+   * @param aActiveOnly
+   *        True to load only active downloads from the database.
+   */
+  ensurePersistentDataLoaded:
+  function DD_ensurePersistentDataLoaded(aActiveOnly)
+  {
+    if (this._pendingStatement) {
+      // We are already in the process of reloading all downloads.
+      return;
+    }
+
+    if (aActiveOnly) {
+      if (this._loadState == this.kLoadNone) {
+        // Indicate to the views that a batch loading operation is in progress.
+        this._views.forEach(
+          function (view) view.onDataLoadStarting()
+        );
+
+        // Reload the list using the Download Manager service.
+        let downloads = Services.downloads.activeDownloads;
+        while (downloads.hasMoreElements()) {
+          let download = downloads.getNext().QueryInterface(Ci.nsIDownload);
+          this._getOrAddDataItem(download, true);
+        }
+        this._loadState = this.kLoadActive;
+
+        // Indicate to the views that the batch loading operation is complete.
+        this._views.forEach(
+          function (view) view.onDataLoadCompleted()
+        );
+      }
+    } else {
+      if (this._loadState != this.kLoadAll) {
+        // Reload the list from the database asynchronously.
+        this._statement = Services.downloads.DBConnection.createAsyncStatement(
+                                "SELECT * FROM moz_downloads ORDER BY id DESC");
+        this._pendingStatement = this._statement.executeAsync(this);
+      }
+    }
+  },
+
+  /**
+   * Cancels any pending data access and ensures views are notified.
+   */
+  _terminateDataAccess: function DD_terminateDataAccess()
+  {
+    if (this._pendingStatement) {
+      this._pendingStatement.cancel();
+      this._pendingStatement = null;
+    }
+    if (this._statement) {
+      this._statement.finalize();
+      this._statement = null;
+    }
+
+    // Close all the views on the current data.  Create a copy of the array
+    // because some views might unregister while processing this event.
+    Array.slice(this._views, 0).forEach(
+      function (view) view.onDataInvalidated()
+    );
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// mozIStorageStatementCallback
+
+  handleResult: function DD_handleResult(aResultSet)
+  {
+    for (let row = aResultSet.getNextRow();
+         row;
+         row = aResultSet.getNextRow()) {
+      // Add the download to the list and initialize it with the data we read,
+      // unless we already received a notification providing more reliable
+      // information for this download.
+      this._getOrAddDataItem(row, false);
+    }
+  },
+
+  handleError: function DD_handleError(aError)
+  {
+    Cu.reportError("Database statement execution error (" + aError.result +
+                   "): " + aError.message);
+  },
+
+  handleCompletion: function DD_handleCompletion(aReason)
+  {
+    this._pendingStatement = null;
+
+    // To ensure that we don't inadvertently delete more downloads from the
+    // database than needed on shutdown, we should update the load state only if
+    // the operation completed successfully.
+    if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
+      this._loadState = this.kLoadAll;
+    }
+
+    // Indicate to the views that the batch loading operation is complete, even
+    // if the lookup failed or was canceled.  The only possible glitch happens
+    // in case the database backend changes while loading data, when the views
+    // would open and immediately close.  This case is rare enough not to need a
+    // special treatment.
+    this._views.forEach(
+      function (view) view.onDataLoadCompleted()
+    );
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsIObserver
+
+  observe: function DD_observe(aSubject, aTopic, aData)
+  {
+    switch (aTopic) {
+      case "download-manager-remove-download":
+        // If a single download was removed, remove the corresponding data item.
+        if (aSubject) {
+          this._removeDataItem(aSubject.QueryInterface(Ci.nsISupportsPRUint32));
+          break;
+        }
+
+        // Multiple downloads have been removed.  Iterate over known downloads
+        // and remove those that don't exist anymore.
+        for each (let dataItem in this.dataItems) {
+          if (dataItem) {
+            try {
+              Services.downloads.getDownload(dataItem.downloadId);
+            } catch (ex) {
+              this._removeDataItem(dataItem.downloadId);
+            }
+          }
+        }
+        break;
+
+      case "download-manager-database-type-changed":
+        let pbs = Cc["@mozilla.org/privatebrowsing;1"]
+                  .getService(Ci.nsIPrivateBrowsingService);
+        if (pbs.privateBrowsingEnabled) {
+          // Save a reference to the persistent store before terminating access.
+          this._persistentDataItems = this.dataItems;
+          this.clear();
+        } else {
+          // Terminate data access, then restore the persistent store.
+          this.clear();
+          this.dataItems = this._persistentDataItems;
+          this._persistentDataItems = null;
+        }
+        // Reinitialize the views with the current items.  View data has been
+        // already invalidated by the previous calls.
+        this._views.forEach(this._updateView, this);
+        break;
+    }
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsIDownloadProgressListener
+
+  onDownloadStateChange: function DD_onDownloadStateChange(aState, aDownload)
+  {
+    // When a new download is added, it may have the same identifier of a
+    // download that we previously deleted during this session, and we also
+    // want to provide a visible indication that the download started.
+    let isNew = aState == nsIDM.DOWNLOAD_NOTSTARTED ||
+                aState == nsIDM.DOWNLOAD_QUEUED;
+
+    let dataItem = this._getOrAddDataItem(aDownload, isNew);
+    if (!dataItem) {
+      return;
+    }
+
+    dataItem.state = aDownload.state;
+    dataItem.referrer = aDownload.referrer && aDownload.referrer.spec;
+    dataItem.resumable = aDownload.resumable;
+    dataItem.startTime = Math.round(aDownload.startTime / 1000);
+    dataItem.currBytes = aDownload.amountTransferred;
+    dataItem.maxBytes = aDownload.size;
+
+    this._views.forEach(
+      function (view) view.getViewItem(dataItem).onStateChange()
+    );
+
+    if (isNew && !dataItem.newDownloadNotified) {
+      dataItem.newDownloadNotified = true;
+      this._notifyNewDownload();
+    }
+  },
+
+  onProgressChange: function DD_onProgressChange(aWebProgress, aRequest,
+                                                  aCurSelfProgress,
+                                                  aMaxSelfProgress,
+                                                  aCurTotalProgress,
+                                                  aMaxTotalProgress, aDownload)
+  {
+    let dataItem = this._getOrAddDataItem(aDownload, false);
+    if (!dataItem) {
+      return;
+    }
+
+    dataItem.currBytes = aDownload.amountTransferred;
+    dataItem.maxBytes = aDownload.size;
+    dataItem.speed = aDownload.speed;
+    dataItem.percentComplete = aDownload.percentComplete;
+
+    this._views.forEach(
+      function (view) view.getViewItem(dataItem).onProgressChange()
+    );
+  },
+
+  onStateChange: function () { },
+
+  onSecurityChange: function () { },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Notifications sent to the most recent browser window only
+
+  /**
+   * Set to true after the first download in the session caused the downloads
+   * panel to be displayed.
+   */
+  firstDownloadShown: false,
+
+  /**
+   * Displays a new download notification in the most recent browser window, if
+   * one is currently available.
+   */
+  _notifyNewDownload: function DD_notifyNewDownload()
+  {
+    if (DownloadsCommon.useToolkitUI) {
+      return;
+    }
+
+    // Show the panel in the most recent browser window, if present.
+    let browserWin = gBrowserGlue.getMostRecentBrowserWindow();
+    if (!browserWin) {
+      return;
+    }
+
+    browserWin.focus();
+    if (this.firstDownloadShown) {
+      // For new downloads after the first one in the session, don't show the
+      // panel automatically, but provide a visible notification in the topmost
+      // browser window, if the status indicator is already visible.
+      browserWin.DownloadsIndicatorView.showEventNotification();
+      return;
+    }
+    this.firstDownloadShown = true;
+    browserWin.DownloadsPanel.showPanel();
+  }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsDataItem
+
+/**
+ * Represents a single item in the list of downloads.  This object either wraps
+ * an existing nsIDownload from the Download Manager, or provides the same
+ * information read directly from the downloads database, with the possibility
+ * of querying the nsIDownload lazily, for performance reasons.
+ *
+ * @param aSource
+ *        Object containing the data with which the item should be initialized.
+ *        This should implement either nsIDownload or mozIStorageRow.
+ */
+function DownloadsDataItem(aSource)
+{
+  if (aSource instanceof Ci.nsIDownload) {
+    this._initFromDownload(aSource);
+  } else {
+    this._initFromDataRow(aSource);
+  }
+}
+
+DownloadsDataItem.prototype = {
+  /**
+   * Initializes this object from a download object of the Download Manager.
+   *
+   * The endTime property is initialized to the current date and time.
+   *
+   * @param aDownload
+   *        The nsIDownload with the current state.
+   */
+  _initFromDownload: function DDI_initFromDownload(aDownload)
+  {
+    this.download = aDownload;
+
+    // Fetch all the download properties eagerly.
+    this.downloadId = aDownload.id;
+    this.file = aDownload.target.spec;
+    this.target = aDownload.displayName;
+    this.uri = aDownload.source.spec;
+    this.referrer = aDownload.referrer && aDownload.referrer.spec;
+    this.state = aDownload.state;
+    this.startTime = Math.round(aDownload.startTime / 1000);
+    this.endTime = Date.now();
+    this.currBytes = aDownload.amountTransferred;
+    this.maxBytes = aDownload.size;
+    this.resumable = aDownload.resumable;
+    this.speed = aDownload.speed;
+    this.percentComplete = aDownload.percentComplete;
+  },
+
+  /**
+   * Initializes this object from a data row in the downloads database, without
+   * querying the associated nsIDownload object, to improve performance when
+   * loading the list of downloads asynchronously.
+   *
+   * When this object is initialized in this way, accessing the "download"
+   * property loads the underlying nsIDownload object synchronously, and should
+   * be avoided unless the object is really required.
+   *
+   * @param aStorageRow
+   *        The mozIStorageRow from the downloads database.
+   */
+  _initFromDataRow: function DDI_initFromDataRow(aStorageRow)
+  {
+    // Get the download properties from the data row.
+    this.downloadId = aStorageRow.getResultByName("id");
+    this.file = aStorageRow.getResultByName("target");
+    this.target = aStorageRow.getResultByName("name");
+    this.uri = aStorageRow.getResultByName("source");
+    this.referrer = aStorageRow.getResultByName("referrer");
+    this.state = aStorageRow.getResultByName("state");
+    this.startTime = Math.round(aStorageRow.getResultByName("startTime") / 1000);
+    this.endTime = Math.round(aStorageRow.getResultByName("endTime") / 1000);
+    this.currBytes = aStorageRow.getResultByName("currBytes");
+    this.maxBytes = aStorageRow.getResultByName("maxBytes");
+
+    // Allows accessing the underlying download object lazily.
+    XPCOMUtils.defineLazyGetter(this, "download", function ()
+                                Services.downloads.getDownload(this.downloadId));
+
+    // Now we have to determine if the download is resumable, but don't want to
+    // access the underlying download object unnecessarily.  The only case where
+    // the property is relevant is when we are currently downloading data, and
+    // in this case the download object is already loaded in memory or will be
+    // loaded very soon in any case.  In all the other cases, including a paused
+    // download, we assume that the download is resumable.  The property will be
+    // updated as soon as the underlying download state changes.
+    if (this.state == nsIDM.DOWNLOAD_DOWNLOADING) {
+      this.resumable = this.download.resumable;
+    } else {
+      this.resumable = true;
+    }
+
+    // Compute the other properties without accessing the download object.
+    this.speed = 0;
+    this.percentComplete = this.maxBytes <= 0
+                           ? -1
+                           : Math.round(this.currBytes / this.maxBytes * 100);
+  },
+
+  /**
+   * 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 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()
+  {
+    // 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 (/^file:/.test(this.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(this.file).QueryInterface(Ci.nsIFileURL);
+      return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile);
+    } else {
+      // The downloads database contains a native path.  Try to create a local
+      // file, though this may throw an exception if the path is invalid.
+      return new DownloadsLocalFileCtor(this.file);
+    }
+  }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsIndicatorData
+
+/**
+ * This object registers itself with DownloadsData as a view, and transforms the
+ * notifications it receives into overall status data, that is then broadcast to
+ * the registered download status indicators.
+ *
+ * Note that using this object does not automatically start the Download Manager
+ * service.  Consumers will see an empty list of downloads until the service is
+ * actually started.  This is useful to display a neutral progress indicator in
+ * the main browser window until the autostart timeout elapses.
+ */
+const DownloadsIndicatorData = {
+  //////////////////////////////////////////////////////////////////////////////
+  //// Registration of views
+
+  /**
+   * Array of view objects that should be notified when the available status
+   * data changes.
+   */
+  _views: [],
+
+  /**
+   * Adds an object to be notified when the available status data changes.
+   * The specified object is initialized with the currently available status.
+   *
+   * @param aView
+   *        DownloadsIndicatorView object to be added.  This reference must be
+   *        passed to removeView before termination.
+   */
+  addView: function DID_addView(aView)
+  {
+    // Start receiving events when the first of our views is registered.
+    if (this._views.length == 0) {
+      DownloadsCommon.data.addView(this);
+    }
+
+    this._views.push(aView);
+
+    // Update immediately even if we are still loading data asynchronously.
+    this._refreshProperties();
+    this._updateView(aView);
+  },
+
+  /**
+   * Removes an object previously added using addView.
+   *
+   * @param aView
+   *        DownloadsIndicatorView object to be removed.
+   */
+  removeView: function DID_removeView(aView)
+  {
+    let index = this._views.indexOf(aView);
+    if (index != -1) {
+      this._views.splice(index, 1);
+    }
+
+    // Stop receiving events when the last of our views is unregistered.
+    if (this._views.length == 0) {
+      DownloadsCommon.data.removeView(this);
+      this._itemCount = 0;
+    }
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Callback functions from DownloadsData
+
+  /**
+   * Indicates whether we are still loading downloads data asynchronously.
+   */
+  _loading: false,
+
+  /**
+   * Called before multiple downloads are about to be loaded.
+   */
+  onDataLoadStarting: function DID_onDataLoadStarting()
+  {
+    this._loading = true;
+  },
+
+  /**
+   * Called after data loading finished.
+   */
+  onDataLoadCompleted: function DID_onDataLoadCompleted()
+  {
+    this._loading = false;
+    this._updateViews();
+  },
+
+  /**
+   * Called when the downloads database becomes unavailable (for example, we
+   * entered Private Browsing Mode and the database backend changed).
+   * References to existing data should be discarded.
+   */
+  onDataInvalidated: function DID_onDataInvalidated()
+  {
+    this._itemCount = 0;
+  },
+
+  /**
+   * Called when a new download data item is available, either during the
+   * asynchronous data load or when a new download is started.
+   *
+   * @param aDataItem
+   *        DownloadsDataItem object that was just added.
+   * @param aNewest
+   *        When true, indicates that this item is the most recent and should be
+   *        added in the topmost position.  This happens when a new download is
+   *        started.  When false, indicates that the item is the least recent
+   *        with regard to the items that have been already added. The latter
+   *        generally happens during the asynchronous data load.
+   */
+  onDataItemAdded: function DID_onDataItemAdded(aDataItem, aNewest)
+  {
+    this._itemCount++;
+    this._updateViews();
+  },
+
+  /**
+   * Called when a data item is removed, ensures that the widget associated with
+   * the view item is removed from the user interface.
+   *
+   * @param aDataItem
+   *        DownloadsDataItem object that is being removed.
+   */
+  onDataItemRemoved: function DID_onDataItemRemoved(aDataItem)
+  {
+    this._itemCount--;
+    this._updateViews();
+  },
+
+  /**
+   * Returns the view item associated with the provided data item for this view.
+   *
+   * @param aDataItem
+   *        DownloadsDataItem object for which the view item is requested.
+   *
+   * @return Object that can be used to notify item status events.
+   */
+  getViewItem: function DID_getViewItem(aDataItem)
+  {
+    return Object.freeze({
+      onStateChange: function DIVI_onStateChange()
+      {
+        if (aDataItem.state == nsIDM.DOWNLOAD_FINISHED ||
+            aDataItem.state == nsIDM.DOWNLOAD_FAILED) {
+          DownloadsIndicatorData.attention = true;
+        }
+
+        // Since the state of a download changed, reset the estimated time left.
+        DownloadsIndicatorData._lastRawTimeLeft = -1;
+        DownloadsIndicatorData._lastTimeLeft = -1;
+
+        DownloadsIndicatorData._updateViews();
+      },
+      onProgressChange: function DIVI_onProgressChange()
+      {
+        DownloadsIndicatorData._updateViews();
+      }
+    });
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Propagation of properties to our views
+
+  // The following properties are updated by _refreshProperties and are then
+  // propagated to the views.  See _refreshProperties for details.
+  _hasDownloads: false,
+  _counter: "",
+  _percentComplete: -1,
+  _paused: false,
+
+  /**
+   * Indicates whether the download indicators should be highlighted.
+   */
+  set attention(aValue)
+  {
+    this._attention = aValue;
+    this._updateViews();
+    return aValue;
+  },
+  _attention: false,
+
+  /**
+   * Indicates whether the user is interacting with downloads, thus the
+   * attention indication should not be shown even if requested.
+   */
+  set attentionSuppressed(aValue)
+  {
+    this._attentionSuppressed = aValue;
+    this._attention = false;
+    this._updateViews();
+    return aValue;
+  },
+  _attentionSuppressed: false,
+
+  /**
+   * Computes aggregate values and propagates the changes to our views.
+   */
+  _updateViews: function DID_updateViews()
+  {
+    // Do not update the status indicators during batch loads of download items.
+    if (this._loading) {
+      return;
+    }
+
+    this._refreshProperties();
+    this._views.forEach(this._updateView, this);
+  },
+
+  /**
+   * Updates the specified view with the current aggregate values.
+   *
+   * @param aView
+   *        DownloadsIndicatorView object to be updated.
+   */
+  _updateView: function DID_updateView(aView)
+  {
+    aView.hasDownloads = this._hasDownloads;
+    aView.counter = this._counter;
+    aView.percentComplete = this._percentComplete;
+    aView.paused = this._paused;
+    aView.attention = this._attention && !this._attentionSuppressed;
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Property updating based on current download status
+
+  /**
+   * Number of download items that are available to be displayed.
+   */
+  _itemCount: 0,
+
+  /**
+   * Floating point value indicating the last number of seconds estimated until
+   * the longest download will finish.  We need to store this value so that we
+   * don't continuously apply smoothing if the actual download state has not
+   * changed.  This is set to -1 if the previous value is unknown.
+   */
+  _lastRawTimeLeft: -1,
+
+  /**
+   * Last number of seconds estimated until all in-progress downloads with a
+   * known size and speed will finish.  This value is stored to allow smoothing
+   * in case of small variations.  This is set to -1 if the previous value is
+   * unknown.
+   */
+  _lastTimeLeft: -1,
+
+  /**
+   * Update the estimated time until all in-progress downloads will finish.
+   *
+   * @param aSeconds
+   *        Current raw estimate on number of seconds left for all downloads.
+   *        This is a floating point value to help get sub-second accuracy for
+   *        current and future estimates.
+   */
+  _updateTimeLeft: function DID_updateTimeLeft(aSeconds)
+  {
+    // We apply an algorithm similar to the DownloadUtils.getTimeLeft function,
+    // though tailored to a single time estimation for all downloads.  We never
+    // apply sommothing if the new value is less than half the previous value.
+    let shouldApplySmoothing = this._lastTimeLeft >= 0 &&
+                               aSeconds > this._lastTimeLeft / 2;
+    if (shouldApplySmoothing) {
+      // Apply hysteresis to favor downward over upward swings.  Trust only 30%
+      // of the new value if lower, and 10% if higher (exponential smoothing).
+      let (diff = aSeconds - this._lastTimeLeft) {
+        aSeconds = this._lastTimeLeft + (diff < 0 ? .3 : .1) * diff;
+      }
+
+      // If the new time is similar, reuse something close to the last time
+      // left, but subtract a little to provide forward progress.
+      let diff = aSeconds - this._lastTimeLeft;
+      let diffPercent = diff / this._lastTimeLeft * 100;
+      if (Math.abs(diff) < 5 || Math.abs(diffPercent) < 5) {
+        aSeconds = this._lastTimeLeft - (diff < 0 ? .4 : .2);
+      }
+    }
+
+    // In the last few seconds of downloading, we are always subtracting and
+    // never adding to the time left.  Ensure that we never fall below one
+    // second left until all downloads are actually finished.
+    this._lastTimeLeft = Math.max(aSeconds, 1);
+  },
+
+  /**
+   * Computes aggregate values based on the current state of downloads.
+   */
+  _refreshProperties: function DID_refreshProperties()
+  {
+    let numActive = 0;
+    let numPaused = 0;
+    let numScanning = 0;
+    let totalSize = 0;
+    let totalTransferred = 0;
+    let rawTimeLeft = -1;
+
+    // If no download has been loaded, don't use the methods of the Download
+    // Manager service, so that it is not initialized unnecessarily.
+    if (this._itemCount > 0) {
+      let downloads = Services.downloads.activeDownloads;
+      while (downloads.hasMoreElements()) {
+        let download = downloads.getNext().QueryInterface(Ci.nsIDownload);
+        numActive++;
+        switch (download.state) {
+          case nsIDM.DOWNLOAD_PAUSED:
+            numPaused++;
+            break;
+          case nsIDM.DOWNLOAD_SCANNING:
+            numScanning++;
+            break;
+          case nsIDM.DOWNLOAD_DOWNLOADING:
+            if (download.size > 0 && download.speed > 0) {
+              let sizeLeft = download.size - download.amountTransferred;
+              rawTimeLeft = Math.max(rawTimeLeft, sizeLeft / download.speed);
+            }
+            break;
+        }
+        // Only add to total values if we actually know the download size.
+        if (download.size > 0) {
+          totalSize += download.size;
+          totalTransferred += download.amountTransferred;
+        }
+      }
+    }
+
+    // Determine if the indicator should be shown or get attention.
+    this._hasDownloads = (this._itemCount > 0);
+
+    if (numActive == 0 || totalSize == 0 || numActive == numScanning) {
+      // Don't display the current progress.
+      this._percentComplete = -1;
+    } else {
+      // Display the current progress.
+      this._percentComplete = (totalTransferred / totalSize) * 100;
+    }
+
+    // If all downloads are paused, show the progress indicator as paused.
+    this._paused = numActive > 0 && numActive == numPaused;
+
+    // Display the estimated time left, if present.
+    if (rawTimeLeft == -1) {
+      // There are no downloads with a known time left.
+      this._lastRawTimeLeft = -1;
+      this._lastTimeLeft = -1;
+      this._counter = "";
+    } else {
+      // Compute the new time left only if state actually changed.
+      if (this._lastRawTimeLeft != rawTimeLeft) {
+        this._lastRawTimeLeft = rawTimeLeft;
+        this._updateTimeLeft(rawTimeLeft);
+      }
+      this._counter = DownloadsCommon.formatTimeLeft(this._lastTimeLeft);
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/src/DownloadsStartup.js
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This component listens to notifications for startup, shutdown and session
+ * restore, controlling which downloads should be loaded from the database.
+ *
+ * To avoid affecting startup performance, this component monitors the current
+ * session restore state, but defers the actual downloads data manipulation
+ * until the Download Manager service is loaded.
+ */
+
+"use strict";
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
+                                  "resource:///modules/DownloadsCommon.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
+                                   "@mozilla.org/browser/sessionstartup;1",
+                                   "nsISessionStartup");
+XPCOMUtils.defineLazyServiceGetter(this, "gPrivateBrowsingService",
+                                   "@mozilla.org/privatebrowsing;1",
+                                   "nsIPrivateBrowsingService");
+
+const kObservedTopics = [
+  "sessionstore-windows-restored",
+  "sessionstore-browser-state-restored",
+  "download-manager-initialized",
+  "download-manager-change-retention",
+  "private-browsing-transition-complete",
+  "browser-lastwindow-close-granted",
+  "quit-application",
+  "profile-change-teardown",
+];
+
+/**
+ * CID of our implementation of nsIDownloadManagerUI.
+ */
+const kDownloadsUICid = Components.ID("{4d99321e-d156-455b-81f7-e7aa2308134f}");
+
+/**
+ * Contract ID of the service implementing nsIDownloadManagerUI.
+ */
+const kDownloadsUIContractId = "@mozilla.org/download-manager-ui;1";
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsStartup
+
+function DownloadsStartup() { }
+
+DownloadsStartup.prototype = {
+  classID: Components.ID("{49507fe5-2cee-4824-b6a3-e999150ce9b8}"),
+
+  _xpcom_factory: XPCOMUtils.generateSingletonFactory(DownloadsStartup),
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsISupports
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+                                         Ci.nsISupportsWeakReference]),
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsIObserver
+
+  observe: function DS_observe(aSubject, aTopic, aData)
+  {
+    switch (aTopic) {
+      case "app-startup":
+        kObservedTopics.forEach(
+          function (topic) Services.obs.addObserver(this, topic, true),
+          this);
+
+        // Override Toolkit's nsIDownloadManagerUI implementation with our own.
+        // This must be done at application startup and not in the manifest to
+        // ensure that our implementation overrides the original one.
+        Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
+                          .registerFactory(kDownloadsUICid, "",
+                                           kDownloadsUIContractId, null);
+        break;
+
+      case "sessionstore-windows-restored":
+      case "sessionstore-browser-state-restored":
+        // Unless there is no saved session, there is a chance that we are
+        // starting up after a restart or a crash.  We should check the disk
+        // database to see if there are completed downloads to recover and show
+        // in the panel, in addition to in-progress downloads.
+        if (gSessionStartup.sessionType != Ci.nsISessionStartup.NO_SESSION) {
+          this._recoverAllDownloads = true;
+        }
+        this._ensureDataLoaded();
+        break;
+
+      case "download-manager-initialized":
+        // Don't initialize the JavaScript data and user interface layer if we
+        // are initializing the Download Manager service during shutdown.
+        if (this._shuttingDown) {
+          break;
+        }
+
+        // Start receiving events for active and new downloads before we return
+        // from this observer function.  We can't defer the execution of this
+        // step, to ensure that we don't lose events raised in the meantime.
+        DownloadsCommon.data.initializeDataLink(
+                             aSubject.QueryInterface(Ci.nsIDownloadManager));
+
+        this._downloadsServiceInitialized = true;
+
+        // Since this notification is generated during the getService call and
+        // we need to get the Download Manager service ourselves, we must post
+        // the handler on the event queue to be executed later.
+        Services.tm.mainThread.dispatch(this._ensureDataLoaded.bind(this),
+                                        Ci.nsIThread.DISPATCH_NORMAL);
+        break;
+
+      case "download-manager-change-retention":
+        // When the panel interface is enabled, we use a different preference to
+        // determine whether downloads should be removed from view as soon as
+        // they are finished.  We do this to allow proper migration to the new
+        // feature when using the same profile on multiple versions of the
+        // product (bug 697678).
+        if (!DownloadsCommon.useToolkitUI) {
+          let removeFinishedDownloads = Services.prefs.getBoolPref(
+                            "browser.download.panel.removeFinishedDownloads");
+          aSubject.QueryInterface(Ci.nsISupportsPRInt32)
+                  .data = removeFinishedDownloads ? 0 : 2;
+        }
+        break;
+
+      case "private-browsing-transition-complete":
+        // Ensure that persistent data is reloaded only when the database
+        // connection is available again.
+        this._ensureDataLoaded();
+        break;
+
+      case "browser-lastwindow-close-granted":
+        // When using the panel interface, downloads that are already completed
+        // should be removed when the last full browser window is closed.  This
+        // event is invoked only if the application is not shutting down yet.
+        // If the Download Manager service is not initialized, we don't want to
+        // initialize it just to clean up completed downloads, because they can
+        // be present only in case there was a browser crash or restart.
+        if (this._downloadsServiceInitialized &&
+            !DownloadsCommon.useToolkitUI) {
+          Services.downloads.cleanUp();
+        }
+        break;
+
+      case "quit-application":
+        // When the application is shutting down, we must free all resources in
+        // addition to cleaning up completed downloads.  If the Download Manager
+        // service is not initialized, we don't want to initialize it just to
+        // clean up completed downloads, because they can be present only in
+        // case there was a browser crash or restart.
+        this._shuttingDown = true;
+        if (!this._downloadsServiceInitialized) {
+          break;
+        }
+
+        DownloadsCommon.data.terminateDataLink();
+
+        // When using the panel interface, downloads that are already completed
+        // should be removed when quitting the application.
+        if (!DownloadsCommon.useToolkitUI && aData != "restart") {
+          this._cleanupOnShutdown = true;
+        }
+        break;
+
+      case "profile-change-teardown":
+        // If we need to clean up, we must do it synchronously after all the
+        // "quit-application" listeners are invoked, so that the Download
+        // Manager service has a chance to pause or cancel in-progress downloads
+        // before we remove completed downloads from the list.  Note that, since
+        // "quit-application" was invoked, we've already exited Private Browsing
+        // Mode, thus we are always working on the disk database.
+        if (this._cleanupOnShutdown) {
+          Services.downloads.cleanUp();
+        }
+        break;
+    }
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// Private
+
+  /**
+   * Indicates whether we should load all downloads from the previous session,
+   * including completed items as well as active downloads.
+   */
+  _recoverAllDownloads: false,
+
+  /**
+   * Indicates whether the Download Manager service has been initialized.  This
+   * flag is required because we want to avoid accessing the service immediately
+   * at browser startup.  The service will start when the user first requests a
+   * download, or some time after browser startup.
+   */
+  _downloadsServiceInitialized: false,
+
+  /**
+   * True while we are processing the "quit-application" event, and later.
+   */
+  _shuttingDown: false,
+
+  /**
+   * True during shutdown if we need to remove completed downloads.
+   */
+  _cleanupOnShutdown: false,
+
+  /**
+   * Ensures that persistent download data is reloaded at the appropriate time.
+   */
+  _ensureDataLoaded: function DS_ensureDataLoaded()
+  {
+    if (!this._downloadsServiceInitialized ||
+        gPrivateBrowsingService.privateBrowsingEnabled) {
+      return;
+    }
+
+    // If the previous session has been already restored, then we ensure that
+    // all the downloads are loaded.  Otherwise, we only ensure that the active
+    // downloads from the previous session are loaded.
+    DownloadsCommon.data.ensurePersistentDataLoaded(!this._recoverAllDownloads);
+  },
+
+  /**
+   * Enqueues the given function to be executed on the main thread.
+   */
+  _executeSoon: function DS_executeSoon(aCallbackFn)
+  {
+    let self = this;
+    Services.tm.mainThread.dispatch(
+                           { run: function () aCallbackFn.apply(self) },
+                           Ci.nsIThread.DISPATCH_NORMAL);
+  }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Module
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([DownloadsStartup]);
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/src/DownloadsUI.js
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This component implements the nsIDownloadManagerUI interface and opens the
+ * downloads panel in the most recent browser window when requested.
+ *
+ * If a specific preference is set, this component transparently forwards all
+ * calls to the original implementation in Toolkit, that shows the window UI.
+ */
+
+"use strict";
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
+                                  "resource:///modules/DownloadsCommon.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "gBrowserGlue",
+                                   "@mozilla.org/browser/browserglue;1",
+                                   "nsIBrowserGlue");
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsUI
+
+function DownloadsUI()
+{
+  XPCOMUtils.defineLazyGetter(this, "_toolkitUI", function () {
+    // Create Toolkit's nsIDownloadManagerUI implementation.
+    return Components.classesByID["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"]
+                     .getService(Ci.nsIDownloadManagerUI);
+  });
+}
+
+DownloadsUI.prototype = {
+  classID: Components.ID("{4d99321e-d156-455b-81f7-e7aa2308134f}"),
+
+  _xpcom_factory: XPCOMUtils.generateSingletonFactory(DownloadsUI),
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsISupports
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadManagerUI]),
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// nsIDownloadManagerUI
+
+  show: function DUI_show(aWindowContext, aID, aReason)
+  {
+    if (DownloadsCommon.useToolkitUI) {
+      this._toolkitUI.show(aWindowContext, aID, aReason);
+      return;
+    }
+
+    if (!aReason) {
+      aReason = Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED;
+    }
+
+    if (aReason == Ci.nsIDownloadManagerUI.REASON_NEW_DOWNLOAD) {
+      // New download notifications are already handled by the panel service.
+      // We don't handle them here because we don't want them to depend on the
+      // "browser.download.manager.showWhenStarting" and
+      // "browser.download.manager.focusWhenStarting" preferences.
+      return;
+    }
+
+    // Show the panel in the most recent browser window, if present.
+    let browserWin = gBrowserGlue.getMostRecentBrowserWindow();
+    if (browserWin) {
+      browserWin.focus();
+      browserWin.DownloadsPanel.showPanel();
+      return;
+    }
+
+    // If no browser window is visible and the user requested to show the
+    // current downloads, try and open a new window.  We'll open the panel when
+    // delayed loading is finished.
+    Services.obs.addObserver(function DUIO_observe(aSubject, aTopic, aData) {
+      Services.obs.removeObserver(DUIO_observe, aTopic);
+      aSubject.DownloadsPanel.showPanel();
+    }, "browser-delayed-startup-finished", false);
+
+    // We must really build an empty arguments list for the new window.
+    let windowFirstArg = Cc["@mozilla.org/supports-string;1"]
+                         .createInstance(Ci.nsISupportsString);
+    let windowArgs = Cc["@mozilla.org/supports-array;1"]
+                     .createInstance(Ci.nsISupportsArray);
+    windowArgs.AppendElement(windowFirstArg);
+    Services.ww.openWindow(null, "chrome://browser/content/browser.xul",
+                           null, "chrome,dialog=no,all", windowArgs);
+  },
+
+  get visible()
+  {
+    if (DownloadsCommon.useToolkitUI) {
+      return this._toolkitUI.visible;
+    }
+
+    let browserWin = gBrowserGlue.getMostRecentBrowserWindow();
+    return browserWin ? browserWin.DownloadsPanel.isPanelShowing : false;
+  },
+
+  getAttention: function DUI_getAttention()
+  {
+    if (DownloadsCommon.useToolkitUI) {
+      this._toolkitUI.getAttention();
+    }
+  }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Module
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([DownloadsUI]);
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/src/Makefile.in
@@ -0,0 +1,22 @@
+# 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/.
+
+DEPTH     = ../../../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+EXTRA_COMPONENTS = \
+  BrowserDownloads.manifest \
+  DownloadsStartup.js \
+  DownloadsUI.js \
+  $(NULL)
+
+EXTRA_JS_MODULES = \
+  DownloadsCommon.jsm \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/test/Makefile.in
@@ -0,0 +1,17 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DEPTH     = ../../../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+relativesrcdir = browser/components/downloads/test
+
+include $(DEPTH)/config/autoconf.mk
+
+XPCSHELL_TESTS = unit
+
+DIRS = browser
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/test/browser/Makefile.in
@@ -0,0 +1,21 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+DEPTH     = ../../../../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+relativesrcdir = browser/components/downloads/test/browser
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+_BROWSER_FILES = \
+  browser_basic_functionality.js \
+  browser_delete_key_removes.js \
+  head.js \
+  $(NULL)
+
+libs:: $(_BROWSER_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/test/browser/browser_basic_functionality.js
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure the downloads panel can display items in the right order and
+ * contains the expected data.
+ */
+function gen_test()
+{
+  // Display one of each download state.
+  const DownloadData = [
+    { endTime: 1180493839859239, state: nsIDM.DOWNLOAD_NOTSTARTED },
+    { endTime: 1180493839859238, state: nsIDM.DOWNLOAD_DOWNLOADING },
+    { endTime: 1180493839859237, state: nsIDM.DOWNLOAD_PAUSED },
+    { endTime: 1180493839859236, state: nsIDM.DOWNLOAD_SCANNING },
+    { endTime: 1180493839859235, state: nsIDM.DOWNLOAD_QUEUED },
+    { endTime: 1180493839859234, state: nsIDM.DOWNLOAD_FINISHED },
+    { endTime: 1180493839859233, state: nsIDM.DOWNLOAD_FAILED },
+    { endTime: 1180493839859232, state: nsIDM.DOWNLOAD_CANCELED },
+    { endTime: 1180493839859231, state: nsIDM.DOWNLOAD_BLOCKED_PARENTAL },
+    { endTime: 1180493839859230, state: nsIDM.DOWNLOAD_DIRTY },
+    { endTime: 1180493839859229, state: nsIDM.DOWNLOAD_BLOCKED_POLICY },
+  ];
+
+  try {
+    // Ensure that state is reset in case previous tests didn't finish.
+    for (let yy in gen_resetState()) yield;
+
+    // Populate the downloads database with the data required by this test.
+    for (let yy in gen_addDownloadRows(DownloadData)) yield;
+
+    // Open the user interface and wait for data to be fully loaded.
+    for (let yy in gen_openPanel()) yield;
+
+    // Test item data and count.  This also tests the ordering of the display.
+    let richlistbox = document.getElementById("downloadsListBox");
+    is(richlistbox.children.length, DownloadData.length,
+       "There is the correct number of richlistitems");
+    for (let i = 0; i < richlistbox.children.length; i++) {
+      let element = richlistbox.children[i];
+      let dataItem = new DownloadsViewItemController(element).dataItem;
+      is(dataItem.target, DownloadData[i].name, "Download names match up");
+      is(dataItem.state, DownloadData[i].state, "Download states match up");
+      is(dataItem.file, DownloadData[i].target, "Download targets match up");
+      is(dataItem.uri, DownloadData[i].source, "Download sources match up");
+    }
+  } finally {
+    // Clean up when the test finishes.
+    for (let yy in gen_resetState()) yield;
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/test/browser/browser_delete_key_removes.js
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This test ensures that the delete key removes a download.
+ */
+function gen_test()
+{
+  const DownloadData = [
+    { state: nsIDM.DOWNLOAD_FINISHED },
+    { state: nsIDM.DOWNLOAD_FAILED },
+    { state: nsIDM.DOWNLOAD_CANCELED },
+    { state: nsIDM.DOWNLOAD_BLOCKED_PARENTAL },
+    { state: nsIDM.DOWNLOAD_DIRTY },
+    { state: nsIDM.DOWNLOAD_BLOCKED_POLICY },
+  ];
+
+  try {
+    // Ensure that state is reset in case previous tests didn't finish.
+    for (let yy in gen_resetState()) yield;
+
+    // Populate the downloads database with the data required by this test.
+    for (let yy in gen_addDownloadRows(DownloadData)) yield;
+
+    // Open the user interface and wait for data to be fully loaded.
+    for (let yy in gen_openPanel()) yield;
+
+    // Test if items are deleted properly.
+    let richlistbox = document.getElementById("downloadsListBox");
+    let statement = Services.downloads.DBConnection.createAsyncStatement(
+                    "SELECT COUNT(*) FROM moz_downloads");
+    try {
+      // Check the number of downloads initially present.
+      statement.executeAsync({
+        handleResult: function(aResultSet)
+        {
+          is(aResultSet.getNextRow().getResultByIndex(0),
+             richlistbox.children.length,
+             "The database and the number of downloads display matches");
+        },
+        handleError: function(aError)
+        {
+          Cu.reportError(aError);
+        },
+        handleCompletion: function(aReason)
+        {
+          testRunner.continueTest();
+        }
+      });
+      yield;
+
+      // Check that the delete key removes each download.
+      let len = DownloadData.length;
+      for (let i = 0; i < len; i++) {
+        // Wait for focus on the main window.
+        waitForFocus(testRunner.continueTest);
+        yield;
+
+        // This removes the download synchronously.
+        EventUtils.synthesizeKey("VK_DELETE", {});
+
+        // Run the statement asynchronously and wait.
+        statement.executeAsync({
+          handleResult: function(aResultSet)
+          {
+            is(aResultSet.getNextRow().getResultByIndex(0),
+               len - (i + 1),
+               "The download was properly removed");
+          },
+          handleError: function(aError)
+          {
+            Cu.reportError(aError);
+          },
+          handleCompletion: function(aReason)
+          {
+            testRunner.continueTest();
+          }
+        });
+        yield;
+      }
+    } finally {
+      statement.finalize();
+    }
+  } finally {
+    // Clean up when the test finishes.
+    for (let yy in gen_resetState()) yield;
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/test/browser/head.js
@@ -0,0 +1,225 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Provides infrastructure for automated download components tests.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
+
+const nsIDM = Ci.nsIDownloadManager;
+
+let gTestTargetFile = FileUtils.getFile("TmpD", ["dm-ui-test.file"]);
+gTestTargetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+registerCleanupFunction(function () {
+  gTestTargetFile.remove(false);
+});
+
+/**
+ * This objects contains a property for each column in the downloads table.
+ */
+let gDownloadRowTemplate = {
+  name: "test-download.txt",
+  source: "http://www.example.com/test-download.txt",
+  target: NetUtil.newURI(gTestTargetFile).spec,
+  startTime: 1180493839859230,
+  endTime: 1180493839859234,
+  state: nsIDM.DOWNLOAD_FINISHED,
+  currBytes: 0,
+  maxBytes: -1,
+  preferredAction: 0,
+  autoResume: 0
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Infrastructure
+
+// All test are run through the test runner.
+function test()
+{
+  testRunner.runTest(this.gen_test);
+}
+
+/**
+ * Runs a browser-chrome test defined through a generator function.
+ *
+ * This object is a singleton, initialized automatically when this script is
+ * included.  Every browser-chrome test file includes a new copy of this object.
+ */
+var testRunner = {
+  _testIterator: null,
+  _lastEventResult: undefined,
+  _testRunning: false,
+  _eventRaised: false,
+
+  // --- Main test runner ---
+
+  /**
+   * Runs the test described by the provided generator function asynchronously.
+   *
+   * Calling yield in the generator will cause it to wait until continueTest is
+   * called.  The parameter provided to continueTest will be the return value of
+   * the yield operator.
+   *
+   * @param aGenerator
+   *        Test generator function.  The function will be called with no
+   *        arguments to retrieve its iterator.
+   */
+  runTest: function TR_runTest(aGenerator) {
+    waitForExplicitFinish();
+    testRunner._testIterator = aGenerator();
+    testRunner.continueTest();
+  },
+
+  /**
+   * Continues the currently running test.
+   *
+   * @param aEventResult
+   *        This will be the return value of the yield operator in the test.
+   */
+  continueTest: function TR_continueTest(aEventResult) {
+    // Store the last event result, or set it to undefined.
+    testRunner._lastEventResult = aEventResult;
+
+    // Never reenter the main loop, but notify that the event has been raised.
+    if (testRunner._testRunning) {
+      testRunner._eventRaised = true;
+      return;
+    }
+
+    // Enter the main iteration loop.
+    testRunner._testRunning = true;
+    try {
+      do {
+        // Call the iterator, but don't leave the loop if the expected event is
+        // raised during the execution of the generator.
+        testRunner._eventRaised = false;
+        testRunner._testIterator.send(testRunner._lastEventResult);
+      } while (testRunner._eventRaised);
+    }
+    catch (e) {
+      // This block catches exceptions raised by the generator, including the
+      // normal StopIteration exception.  Unexpected exceptions are reported as
+      // test failures.
+      if (!(e instanceof StopIteration))
+        ok(false, e);
+      // In any case, stop the tests in this file.
+      finish();
+    }
+
+    // Wait for the next event or finish.
+    testRunner._testRunning = false;
+  }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Asynchronous generator-based support subroutines
+
+//
+// The following functions are all generators that can be used inside the main
+// test generator to perform specific tasks asynchronously.  To invoke these
+// subroutines correctly, an iteration syntax should be used:
+//
+//   for (let yy in gen_example("Parameter")) yield;
+//
+
+function gen_resetState()
+{
+  let statement = Services.downloads.DBConnection.createAsyncStatement(
+                  "DELETE FROM moz_downloads");
+  try {
+    statement.executeAsync({
+      handleResult: function(aResultSet) { },
+      handleError: function(aError)
+      {
+        Cu.reportError(aError);
+      },
+      handleCompletion: function(aReason)
+      {
+        testRunner.continueTest();
+      }
+    });
+    yield;
+  } finally {
+    statement.finalize();
+  }
+
+  // Ensure that the panel is closed and data is unloaded.
+  DownloadsCommon.data.clear();
+  DownloadsCommon.data._loadState = DownloadsCommon.data.kLoadNone;
+}
+
+function gen_addDownloadRows(aDataRows)
+{
+  let columnNames = Object.keys(gDownloadRowTemplate).join(", ");
+  let parameterNames = Object.keys(gDownloadRowTemplate)
+                             .map(function(n) ":" + n)
+                             .join(", ");
+  let statement = Services.downloads.DBConnection.createAsyncStatement(
+                  "INSERT INTO moz_downloads (" + columnNames +
+                                    ") VALUES(" + parameterNames + ")");
+  try {
+    // Execute the statement for each of the provided downloads in reverse.
+    for (let i = aDataRows.length - 1; i >= 0; i--) {
+      let dataRow = aDataRows[i];
+
+      // Populate insert parameters from the provided data.
+      for (let columnName in gDownloadRowTemplate) {
+        if (!(columnName in dataRow)) {
+          // Update the provided row object with data from the global template,
+          // for columns whose value is not provided explicitly.
+          dataRow[columnName] = gDownloadRowTemplate[columnName];
+        }
+        statement.params[columnName] = dataRow[columnName];
+      }
+
+      // Run the statement asynchronously and wait.
+      statement.executeAsync({
+        handleResult: function(aResultSet) { },
+        handleError: function(aError)
+        {
+          Cu.reportError(aError);
+        },
+        handleCompletion: function(aReason)
+        {
+          testRunner.continueTest();
+        }
+      });
+      yield;
+
+      // At each iteration, ensure that the end time in the global template is
+      // distinct, as this column is used to sort each download in its category.
+      gDownloadRowTemplate.endTime++;
+    }
+  } finally {
+    statement.finalize();
+  }
+}
+
+function gen_openPanel(aData)
+{
+  // Hook to wait until the test data has been loaded.
+  let originalOnViewLoadCompleted = DownloadsPanel.onViewLoadCompleted;
+  DownloadsPanel.onViewLoadCompleted = function () {
+    DownloadsPanel.onViewLoadCompleted = originalOnViewLoadCompleted;
+    originalOnViewLoadCompleted.apply(this);
+    testRunner.continueTest();
+  };
+
+  // Start loading all the downloads from the database asynchronously.
+  DownloadsCommon.data.ensurePersistentDataLoaded(false);
+
+  // Open the downloads panel, waiting until loading is completed.
+  DownloadsPanel.showPanel();
+  yield;
+
+  // Wait for focus on the main window.
+  waitForFocus(testRunner.continueTest);
+  yield;
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/test/unit/head.js
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Provides infrastructure for automated download components tests.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource:///modules/DownloadsCommon.jsm");
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/test/unit/test_DownloadsCommon.js
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests for the functions located directly in the "DownloadsCommon" object.
+ */
+
+function testFormatTimeLeft(aSeconds, aExpectedValue, aExpectedUnitString)
+{
+  let expected = "";
+  if (aExpectedValue) {
+    // Format the expected result based on the current language.
+    expected = DownloadsCommon.strings[aExpectedUnitString](aExpectedValue);
+  }
+  do_check_eq(DownloadsCommon.formatTimeLeft(aSeconds), expected);
+}
+
+function run_test()
+{
+  testFormatTimeLeft(      0,   "", "");
+  testFormatTimeLeft(      1,  "1", "shortTimeLeftSeconds");
+  testFormatTimeLeft(     29, "29", "shortTimeLeftSeconds");
+  testFormatTimeLeft(     30, "30", "shortTimeLeftSeconds");
+  testFormatTimeLeft(     31,  "1", "shortTimeLeftMinutes");
+  testFormatTimeLeft(     60,  "1", "shortTimeLeftMinutes");
+  testFormatTimeLeft(     89,  "1", "shortTimeLeftMinutes");
+  testFormatTimeLeft(     90,  "2", "shortTimeLeftMinutes");
+  testFormatTimeLeft(     91,  "2", "shortTimeLeftMinutes");
+  testFormatTimeLeft(   3600,  "1", "shortTimeLeftHours");
+  testFormatTimeLeft(  86400, "24", "shortTimeLeftHours");
+  testFormatTimeLeft( 169200, "47", "shortTimeLeftHours");
+  testFormatTimeLeft( 172800,  "2", "shortTimeLeftDays");
+  testFormatTimeLeft(8553600, "99", "shortTimeLeftDays");
+  testFormatTimeLeft(8640000, "99", "shortTimeLeftDays");
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/test/unit/xpcshell.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+head = head.js
+tail =
+
+[test_DownloadsCommon.js]
--- a/browser/components/preferences/main.js
+++ b/browser/components/preferences/main.js
@@ -33,16 +33,20 @@
 # use your version of this file under the terms of the MPL, indicate your
 # decision by deleting the provisions above and replace them with the notice
 # and other provisions required by the GPL or the LGPL. If you do not delete
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
+                                  "resource:///modules/DownloadsCommon.jsm");
+
 var gMainPane = {
   _pane: null,
 
   /**
    * Initialization of this.
    */
   init: function ()
   {
@@ -50,22 +54,35 @@ var gMainPane = {
 
     // set up the "use current page" label-changing listener
     this._updateUseCurrentButton();
     window.addEventListener("focus", this._updateUseCurrentButton.bind(this), false);
 
     this.updateBrowserStartupLastSession();
     this.startupPagePrefChanged();
 
+    this.setupDownloadsWindowOptions();
+
     // Notify observers that the UI is now ready
     Components.classes["@mozilla.org/observer-service;1"]
               .getService(Components.interfaces.nsIObserverService)
               .notifyObservers(window, "main-pane-loaded", null);
   },
 
+  setupDownloadsWindowOptions: function ()
+  {
+    var showWhenDownloading = document.getElementById("showWhenDownloading");
+    var closeWhenDone = document.getElementById("closeWhenDone");
+
+    // These radio-buttons should not be visible if we have enabled the Downloads Panel.
+    let shouldHide = !DownloadsCommon.useToolkitUI;
+    showWhenDownloading.hidden = shouldHide;
+    closeWhenDone.hidden = shouldHide;
+  },
+
   // HOME PAGE
 
   /*
    * Preferences:
    *
    * browser.startup.homepage
    * - the user's home page, as a string; if the home page is a set of tabs,
    *   this will be those URLs separated by the pipe character "|"
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -289,16 +289,19 @@
 @BINPATH@/components/fuelApplication.manifest
 @BINPATH@/components/fuelApplication.js
 @BINPATH@/components/WebContentConverter.js
 @BINPATH@/components/BrowserComponents.manifest
 @BINPATH@/components/nsBrowserContentHandler.js
 @BINPATH@/components/nsBrowserGlue.js
 @BINPATH@/components/nsSetDefaultBrowser.manifest
 @BINPATH@/components/nsSetDefaultBrowser.js
+@BINPATH@/components/BrowserDownloads.manifest
+@BINPATH@/components/DownloadsStartup.js
+@BINPATH@/components/DownloadsUI.js
 @BINPATH@/components/BrowserPlaces.manifest
 @BINPATH@/components/BrowserPageThumbs.manifest
 @BINPATH@/components/nsPrivateBrowsingService.manifest
 @BINPATH@/components/nsPrivateBrowsingService.js
 @BINPATH@/components/toolkitsearch.manifest
 @BINPATH@/components/nsSearchService.js
 @BINPATH@/components/nsSearchSuggestions.js
 @BINPATH@/components/passwordmgr.manifest
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/downloads/downloads.dtd
@@ -0,0 +1,41 @@
+<!-- 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/.  -->
+
+<!-- LOCALIZATION NOTE (indicator.tooltiptext):
+     Tooltip for the indicator that displays the progress of ongoing downloads.
+     -->
+<!ENTITY indicator.tooltiptext            "Downloads">
+
+<!-- LOCALIZATION NOTE (downloads.title):
+     Used by screen readers to describe the Downloads Panel.
+     -->
+<!ENTITY downloads.title                  "Downloads">
+
+<!ENTITY cmd.pause.label                  "Pause">
+<!ENTITY cmd.pause.accesskey              "P">
+<!ENTITY cmd.resume.label                 "Resume">
+<!ENTITY cmd.resume.accesskey             "R">
+<!ENTITY cmd.cancel.label                 "Cancel">
+<!ENTITY cmd.cancel.accesskey             "C">
+<!-- LOCALIZATION NOTE (cmd.show.label, cmd.show.accesskey, cmd.showMac.label,
+     cmd.showMac.accesskey):
+     The show and showMac commands are never shown together, thus they can share
+     the same access key (though the two access keys can also be different).
+     -->
+<!ENTITY cmd.show.label                   "Open Containing Folder">
+<!ENTITY cmd.show.accesskey               "F">
+<!ENTITY cmd.showMac.label                "Show In Finder">
+<!ENTITY cmd.showMac.accesskey            "F">
+<!ENTITY cmd.retry.label                  "Retry">
+<!ENTITY cmd.goToDownloadPage.label       "Go To Download Page">
+<!ENTITY cmd.goToDownloadPage.accesskey   "G">
+<!ENTITY cmd.copyDownloadLink.label       "Copy Download Link">
+<!ENTITY cmd.copyDownloadLink.accesskey   "L">
+<!ENTITY cmd.removeFromList.label         "Remove From List">
+<!ENTITY cmd.removeFromList.accesskey     "e">
+<!ENTITY cmd.clearList.label              "Clear List">
+<!ENTITY cmd.clearList.accesskey          "a">
+
+<!ENTITY downloadshistory.label           "Show All Downloads">
+<!ENTITY downloadshistory.accesskey       "S">
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/downloads/downloads.properties
@@ -0,0 +1,72 @@
+# 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/.
+
+# LOCALIZATION NOTE (stateStarting):
+# Indicates that the download is starting.
+stateStarting=Starting…
+# LOCALIZATION NOTE (stateScanning):
+# Indicates that an external program is scanning the download for viruses.
+stateScanning=Scanning for viruses…
+# LOCALIZATION NOTE (stateFailed):
+# Indicates that the download failed because of an error.
+stateFailed=Failed
+# LOCALIZATION NOTE (statePaused):
+# Indicates that the download was paused by the user.
+statePaused=Paused
+# LOCALIZATION NOTE (stateCanceled):
+# Indicates that the download was canceled by the user.
+stateCanceled=Canceled
+# LOCALIZATION NOTE (stateBlockedParentalControls):
+# Indicates that the download was blocked by the Parental Controls feature of
+# Windows.  "Parental Controls" should be consistently named and capitalized
+# with the display of this feature in Windows.  The following article can
+# provide a reference for the translation of "Parental Controls" in various
+# languages:
+# http://windows.microsoft.com/en-US/windows-vista/Set-up-Parental-Controls
+stateBlockedParentalControls=Blocked by Parental Controls
+# LOCALIZATION NOTE (stateBlockedPolicy):
+# Indicates that the download was blocked on Windows because of the "Launching
+# applications and unsafe files" setting of the "security zone" associated with
+# the target site.  "Security zone" should be consistently named and capitalized
+# with the display of this feature in Windows.  The following article can
+# provide a reference for the translation of "security zone" in various
+# languages:
+# http://support.microsoft.com/kb/174360
+stateBlockedPolicy=Blocked by your security zone policy
+# LOCALIZATION NOTE (stateDirty):
+# Indicates that the download was blocked after scanning.
+stateDirty=Blocked: May contain a virus or spyware
+
+# LOCALIZATION NOTE (sizeWithUnits):
+# %1$S is replaced with the size number, and %2$S with the measurement unit.
+sizeWithUnits=%1$S %2$S
+sizeUnknown=Unknown size
+
+# LOCALIZATION NOTE (shortTimeLeftSeconds, shortTimeLeftMinutes,
+# shortTimeLeftHours, shortTimeLeftDays):
+# These values are displayed in the downloads indicator in the main browser
+# window, where space is available for three characters maximum.  %1$S is
+# replaced with the time left for the given measurement unit.  Even for days,
+# the value is never longer than two digits.
+shortTimeLeftSeconds=%1$Ss
+shortTimeLeftMinutes=%1$Sm
+shortTimeLeftHours=%1$Sh
+shortTimeLeftDays=%1$Sd
+
+# LOCALIZATION NOTE (statusSeparator, statusSeparatorBeforeNumber):
+# These strings define templates for the separation of different elements in the
+# status line of a download item.  As a separator, by default we use the Unicode
+# character U+2014 'EM DASH' (long dash).  Examples of status lines include
+# "Canceled - 222.net", "1.1 MB - website2.com", or "Paused -  1.1 MB".  Note
+# that we use a wider space after the separator when it is followed by a number,
+# just to avoid visually confusing it with with a minus sign with some fonts.
+# If you use a different separator, this might not be necessary.  However, there
+# is usually no need to change the separator or the order of the substitutions,
+# even for right-to-left languages, unless the defaults are not suitable.
+statusSeparator=%1$S \u2014 %2$S
+statusSeparatorBeforeNumber=%1$S \u2014  %2$S
+
+fileExecutableSecurityWarning="%S" is an executable file. Executable files may contain viruses or other malicious code that could harm your computer. Use caution when opening this file. Are you sure you want to launch "%S"?
+fileExecutableSecurityWarningTitle=Open Executable File?
+fileExecutableSecurityWarningDontAsk=Don't ask me this again
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -45,16 +45,18 @@
     locale/browser/engineManager.dtd               (%chrome/browser/engineManager.dtd)
     locale/browser/engineManager.properties        (%chrome/browser/engineManager.properties)
     locale/browser/setDesktopBackground.dtd        (%chrome/browser/setDesktopBackground.dtd)
     locale/browser/shellservice.properties         (%chrome/browser/shellservice.properties)
     locale/browser/tabbrowser.dtd                  (%chrome/browser/tabbrowser.dtd)
     locale/browser/tabbrowser.properties           (%chrome/browser/tabbrowser.properties)
     locale/browser/tabview.properties              (%chrome/browser/tabview.properties)
     locale/browser/taskbar.properties              (%chrome/browser/taskbar.properties)
+    locale/browser/downloads/downloads.dtd         (%chrome/browser/downloads/downloads.dtd)
+    locale/browser/downloads/downloads.properties  (%chrome/browser/downloads/downloads.properties)
     locale/browser/places/places.dtd               (%chrome/browser/places/places.dtd)
     locale/browser/places/places.properties        (%chrome/browser/places/places.properties)
     locale/browser/places/editBookmarkOverlay.dtd  (%chrome/browser/places/editBookmarkOverlay.dtd)
     locale/browser/places/bookmarkProperties.properties (%chrome/browser/places/bookmarkProperties.properties)
     locale/browser/preferences/selectBookmark.dtd  (%chrome/browser/preferences/selectBookmark.dtd)
     locale/browser/places/moveBookmarks.dtd        (%chrome/browser/places/moveBookmarks.dtd)
 #ifdef MOZ_SAFE_BROWSING
     locale/browser/safebrowsing/phishing-afterload-warning-message.dtd (%chrome/browser/safebrowsing/phishing-afterload-warning-message.dtd)
--- a/browser/makefiles.sh
+++ b/browser/makefiles.sh
@@ -43,16 +43,18 @@ browser/Makefile
 browser/app/Makefile
 browser/app/profile/extensions/Makefile
 browser/base/Makefile
 browser/components/Makefile
 browser/components/about/Makefile
 browser/components/build/Makefile
 browser/components/certerror/Makefile
 browser/components/dirprovider/Makefile
+browser/components/downloads/Makefile
+browser/components/downloads/src/Makefile
 browser/components/feeds/Makefile
 browser/components/feeds/public/Makefile
 browser/components/feeds/src/Makefile
 browser/components/migration/Makefile
 browser/components/migration/public/Makefile
 browser/components/migration/src/Makefile
 browser/components/places/Makefile
 browser/components/places/src/Makefile
@@ -128,16 +130,18 @@ else
 fi
 
 if [ "$ENABLE_TESTS" ]; then
   add_makefiles "
     browser/base/content/test/Makefile
     browser/base/content/test/newtab/Makefile
     browser/components/certerror/test/Makefile
     browser/components/dirprovider/tests/Makefile
+    browser/components/downloads/test/Makefile
+    browser/components/downloads/test/browser/Makefile
     browser/components/preferences/tests/Makefile
     browser/components/search/test/Makefile
     browser/components/sessionstore/test/Makefile
     browser/components/shell/test/Makefile
     browser/components/feeds/test/Makefile
     browser/components/feeds/test/chrome/Makefile
     browser/components/migration/tests/Makefile
     browser/components/places/tests/Makefile
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..abff1bc4e26a27dae77a48b861179a1024829ab0
GIT binary patch
literal 2844
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..507c627f765990037170bbb5244c7a3b686a59cb
GIT binary patch
literal 2102
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
K(krQ<Y5xTY9A?P?
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bd548b18382388cfbe7672d1d0725bff0d21432c
GIT binary patch
literal 3166
zc$|G#3piA1A3rn17`7%F6_sOLw$$91p&2IQerv|HD2bUd24gNe7bc5lscaTq6cL5m
zl3Gnm)DpH_%C)VntS+|8A}Y5ek-j78>-(OkeV^x?^Zx(u@AtpG|MQ;b?Db)~eS<VW
z0s!!hhr6>M^i<AX2o30dZ3C+n0MtyQ0)k{gjEz)|NPuI7i`ZP8LLdfahn>Ab%;NC5
zGLX&XMG0w`q3Sve7!^*#1d<s9hS-T48RZ@?;rhoj132+~4ka98zX7yUP$2^Xu8ajL
z1TjJ>RYAjiv`dBNvuZpB{0NcpX&8su1u%%=13HN$T#$?-U^xV9B4|y*5lI9JiLwr~
zBoGOBq9vYSgC!EEWE(1h2!0wE$eScQg6ik&`pFk0(J+xRnV5>l$Hm3r;;e8Y2@g-C
zP$+nUCEn5!3n8%59YPsPffY(k<}EmLr5s6=SQaG`g0nJ<Et1P<7|eVM0`X_FLg}Y6
zK?TDrSYkX8N5FppWHA06DiC}|OJ#oCh<`^*19phHct5UGB$sfw5Zq*zET%e1xGb4S
z5+D-A%vaGTQX~^eBSm7+$)AiPf(#ZXN;s?e$Y3z29zv;%CFF2DoM{+HgNup^r&3&;
zoSg||lA|ldib!;UN=|g3thaP=a3Z@}xj0(RbDc#TxqvH_&2z*5%O!u6JKGF%Y_23K
zmK*LW5edMLNmHY~9*gDIW0~WIe?1nfuX6EFF!=x5>UkB^KeN-%jSC5%Tc0b0`dtDw
zHnM|f54{x2WiCN7e^G=ijwRs&bCvfHuz1kku)=qIcY|vO0En9&&JF>J?w19L8xL<<
z(Hw%!IeSNPaT~+tbZh&a(cP*uq*e;a6@&W@lZFRHfjG>ld0N_e<=vpXOEME_M&qr2
zqN!5fKV_r`LAVtgd-*@@KBDw7@UA;wP+g?w)OjVR&#rS~-;ndGYgg*Vrjj~#bWl1e
zeG7kG$4C9Mov5X*^Z<dx$mzEuv3GoY(^vmtp5{yZ%fg$f9#&C;F*R+-vlir$6W_^#
z5f?!7hEr@)0Cuy@Bg<Ugc8qr4)7@RAtEVS0YuMF=GGUtmGn*oVg1UOVve#WG7@0s4
zJ<o;wm<zmhOF7Lf=&q4H_v1IFp<NLuFrtA8kkAGOd4!!a37obGEuu**yztER>(`C4
zvB425fFGA%&;uClxmJDNGSx;kfavDtR<#&lG&VFW<JEeP^|nu?0+ANh)^|NUJuUsT
z_1CEo%Aa8L7wSp?1;FV*1>nwPTKjz#E5wf-*P59%x*NwrA_9Pc0JLUD1<sr~<C-=H
zLMXGJJb7}iqJm_yyG!d6Bog-bh$4N-oPYwb*sT@Uw_cX~e8BNbU*6s}bG|~b+iSW;
z<FB|Pp#<hU-GqZCCfD2-&x$g%k&)<^truIqh+``d9uZ-%=3@Y5;@(FCcg|)kVShX4
z4_>%{zIk)@0nDt}k4$wwzooqCf5|+a;XL_O5|f!ZA6W<>kxDbZh&;6RK}&0^mEVsA
z9}R2MlagxZ4TsiM#=hJN<z1j&|8zk6fL_X)m~v}0(6H)RLgU`1cZN#i;$kg5^NL-&
zcW)_=Xh-H@Fql_uCAmm|?Y2Dm@cH3kF@N;g4P}|blA)!5qoZSKadGjhbyC+NBoevW
zP<z$m{VmI_B7j|_5MWPxf$an{b<Yj5m%?DpFIUa5095#<6DBD(+p2^sx%&x=78@Du
z4AEAu4J@l!4Qy+!Fwct4R;_zDIDYI8IQ?~yhsTYa^`knX-;&<Pw*W;(`wt!Z(|^mB
z+!GJAUc7jb|6)Xe3(K`KR@I5Cn}(%Nn=;5x9=CP_h3LrWXwRj^TQA%^fa<!R5aX4z
zIM^vLFz`~3dGfQq(*_0xNI!r74}khx)zzzxEv}A@0e!lp%F0TdYvcIf#=_|jxlK(?
zc0UyEMs?jzc>b1LQB}o(_wQBOY47i$J$&#WzaTsNjV)N8D(X`GU_dXzTum5x8c4Kf
z+Qr)yE=m#Gu7^54n?7u4tjd_=HBNUu>eYN51%j6pBkWV@3(MFtIzppkIzK-@^tbBj
zz8-#3y^&_?pi|lQD;6f1ue~C*P=|DAB_IOL%nil&b~>es=y%nwlf5e|Do!HQmm(a*
z?VX*S_9ne`JvoyO!6?kVZ}brdzjsna-8Q!XMsttn<Y<naFbYrd2~>wALz$gSPfs7~
z*Fz=OAKCYmr;S7=Whc13KR)RYs=Mbx-$njCT}vw~LoJ7}ww%n+U!GBJRr295m^p@E
z3`2-JEi5dGNxRZcw%KDRGgR-3eRSLi#tw*SUhVW$*0b>t&gKPEV7>YRkZx7EV=Q98
zcvqU*m|2{?=Z0$;!TJjk1cw7hj|T5+)LM<oczN7sY$U@ZxH4vP`{?>>U5T*%GwgF|
z#I^c|b(3d~)(P%c4w>#8T2@DNre&vJ?LuQ=LQO|&xlvJa{WR1-A~T-IHkyY5>zC<c
zVaEOUr*GZ5rFZab)r!kx9ZrxAk8sol@ygn$^-}E1ZuvF|>CPYDs}b`ZI`vUuCFSzL
z&4JozSZ~hZ!-*!BbDyNO7xn*DoH`hbNa!7UbY+Tv<Hpgl?v?4Qgd)VrdxI85ZG^g-
zn(4JSTuRnfrK-VSD~OLDn<IS2F5F}H)F!%_YU+g~ZUEwI`lkyuUuYl#%6rOpewexD
z_eRSOd{Re0{r3h0g0TG8#Hm;}Px2H^bK#1TgO>ZAY8l6A8V-ab!fHuVG&C9=>b@nk
zZ!s~Eu4FEzy15_mZws85KIW9VFcHU1$l$5d*7xtdWbf&9GbKB$Idi3}7kR4c@FZu4
zd%y>{QR*q(39l?;pZ1xTEiEmb$%5LE)4Ph!*eQm?%s@JZL9Qz;9q?b%x@?u+MC1Ym
zn(=L(PZsDiF&&(`$f#_;WzAJ7d>l#CXRMCa16Sj4l@|lr>*UX_A5hbsid?ni`SIj*
z2Z1B|r4Ecerm(BZTt$1gx52JOV~11&C?%ZmJ^KRrL+RwsmG((B+t@3kvhpdKz@|dq
zk`N_uBIV#g-QerInG2leH=gMxF^BLxSRs<Edr?Qr?TLV$G_hCj+YOajlbH+X=ood~
z%!Wm#>I27m5}{LOpXsT`)@@l<FrVFq+e1P^G}JWUaGmjI<sytmeiQfh3d&MoQ+>`7
zb(=)gq3mppfGg26?Rj~5ix#J3j6ID35>WMs%PVEYn>BRdq3zAQHDjrN+1Yex($TW6
z=eJLLH5_?EcevR)9h;E-usse9l&bK5-n}banj(B_nEa^sI8|P{>^Rq*`9Mu+F+3)h
z%Yz|{H3hbD(ON@Ad*W??BWrrAb%9O&MdRb+F6=IO2x7RZw6_Nc0@_sDE-eB!4fk~%
zqgDIH4+y$q_L@(#+M9cz-K%Mq0}E6-Z7H@;W;-jPZxcwPy|>KtZsv5)<n7~+*la&P
zF?w0rhlCv}qKNT8?FoS`m3mNHTict(Sk=+}yTO&Ihx)^|HGB9f#Deda-S6)96TANU
z`#*6uKy8YJv(?YbKhUG)E1QP9iqg*qj3ibsO=`L;hu~uD8^XJVA?82_z$)pd`EF(~
z+P9zE@Ti>E>5H(3j>w^F{DHipg=?%}{roqmv-?~`s~Hu82{TGKof5hc4hz$LaBe58
XW9$$UKIlc7{VnitVLF#Nh9&<8<U0OL
new file mode 100644
--- /dev/null
+++ b/browser/themes/gnomestripe/downloads/downloads.css
@@ -0,0 +1,278 @@
+/* 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/. */
+
+/*** Panel and outer controls ***/
+
+#downloadsPanel > .panel-arrowcontainer > .panel-arrowcontent {
+  padding: 0;
+}
+
+#downloadsListBox {
+  width: 60ch;
+  background: transparent;
+  padding: 4px;
+}
+
+#downloadsPanel:not([hasdownloads]) > #downloadsListBox {
+  display: none;
+}
+
+#downloadsHistory {
+  background: inherit;
+  color: -moz-nativehyperlinktext;
+  cursor: pointer;
+}
+
+#downloadsPanel[hasdownloads] > #downloadsHistory {
+  border-top: 1px solid ThreeDShadow;
+  background-image: -moz-linear-gradient(hsla(0,0%,0%,.15), hsla(0,0%,0%,.08) 6px);
+}
+
+#downloadsHistory > .button-box {
+  margin: 1em;
+}
+
+/*** List items ***/
+
+richlistitem[type="download"] {
+  height: 6em;
+  margin: 0;
+  border-top: 1px solid hsla(0,0%,100%,.2);
+  border-bottom: 1px solid hsla(0,0%,0%,.15);
+  background: transparent;
+  padding: 0;
+  color: -moz-DialogText;
+}
+
+richlistitem[type="download"]:first-child {
+  border-top: 1px solid transparent;
+}
+
+richlistitem[type="download"]:last-child {
+  border-bottom: 1px solid transparent;
+}
+
+#downloadsListBox:-moz-focusring > richlistitem[type="download"][selected] {
+  outline: 1px #999 dotted;
+  outline-offset: -1px;
+  -moz-outline-radius: 3px;
+}
+
+.downloadInfo {
+  padding: 8px;
+  -moz-padding-end: 0;
+}
+
+.downloadTypeIcon {
+  -moz-margin-end: 8px;
+  /* Prevent flickering when changing states. */
+  min-height: 32px;
+  min-width: 32px;
+}
+
+.blockedIcon {
+  list-style-image: url("chrome://global/skin/icons/Error.png");
+}
+
+.downloadTarget {
+  margin-bottom: 7px;
+  cursor: inherit;
+}
+
+.downloadDetails {
+  margin-top: 1px;
+  opacity: 0.6;
+  font-size: 90%;
+  cursor: inherit;
+}
+
+.downloadButton {
+  -moz-appearance: none;
+  min-width: 0;
+  min-height: 0;
+  margin: 6px;
+  border: none;
+  background: transparent;
+  padding: 5px;
+  list-style-image: url("chrome://browser/skin/downloads/buttons.png");
+}
+
+.downloadButton > .button-box {
+  padding: 0;
+}
+
+/*** Highlighted list items ***/
+
+richlistitem[type="download"][state="1"] > .downloadInfo {
+  border-top: 1px solid transparent;
+  border-bottom: 1px solid transparent;
+  -moz-padding-end: 8px;
+}
+
+richlistitem[type="download"][state="1"] > .downloadInfo:hover {
+  border-radius: 3px;
+  border-top: 1px solid hsla(0,0%,100%,.3);
+  border-bottom: 1px solid hsla(0,0%,0%,.2);
+  background-color: Highlight;
+  background-image: -moz-linear-gradient(hsla(0,0%,100%,.1), hsla(0,0%,100%,0));
+  color: HighlightText;
+  cursor: pointer;
+}
+
+/*** Button icons ***/
+
+.downloadButton.downloadCancel {
+  -moz-image-region: rect(0px, 14px, 14px, 0px);
+}
+.downloadButton.downloadCancel:hover {
+  -moz-image-region: rect(0px, 28px, 14px, 14px);
+}
+.downloadButton.downloadCancel:active {
+  -moz-image-region: rect(0px, 42px, 14px, 28px);
+}
+
+.downloadButton.downloadShow {
+  -moz-image-region: rect(14px, 14px, 28px, 0px);
+}
+.downloadButton.downloadShow:hover {
+  -moz-image-region: rect(14px, 28px, 28px, 14px);
+}
+.downloadButton.downloadShow:active {
+  -moz-image-region: rect(14px, 42px, 28px, 28px);
+}
+
+.downloadButton.downloadRetry {
+  -moz-image-region: rect(28px, 14px, 42px, 0px);
+}
+.downloadButton.downloadRetry:hover {
+  -moz-image-region: rect(28px, 28px, 42px, 14px);
+}
+.downloadButton.downloadRetry:active {
+  -moz-image-region: rect(28px, 42px, 42px, 28px);
+}
+
+/*** Status and progress indicator ***/
+
+#downloads-indicator {
+  width: 35px;
+}
+
+#downloads-indicator-anchor {
+  min-width: 18px;
+  min-height: 18px;
+  /* Makes the outermost stack element positioned, so that its contents are
+     rendered over the main browser window in the Z order.  This is required by
+     the animated event notification. */
+  position: relative;
+}
+
+/*** Main indicator icon ***/
+
+#downloads-indicator-icon {
+  background: -moz-image-rect(url("chrome://browser/skin/Toolbar-small.png"),
+                              0, 16, 16, 0) center no-repeat;
+}
+
+#downloads-indicator-icon:-moz-lwtheme-brighttext {
+  background: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"),
+                              0, 16, 16, 0) center no-repeat;
+}
+
+#downloads-indicator[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+  background: -moz-image-rect(url("chrome://browser/skin/downloads/download-glow.png"),
+                              16, 32, 32, 16) center no-repeat;
+}
+
+#downloads-indicator:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+  background: -moz-image-rect(url("chrome://browser/skin/Toolbar-small.png"),
+                              0, 16, 16, 0) center no-repeat;
+  background-size: 12px;
+}
+
+#downloads-indicator:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+  background-image: -moz-image-rect(url("chrome://browser/skin/downloads/download-glow.png"),
+                                    16, 32, 32, 16);
+}
+
+/*** Event notification ***/
+
+#downloads-indicator-notification {
+  opacity: 0;
+  background: url("chrome://browser/skin/downloads/download-notification.png")
+              center no-repeat;
+  background-size: 16px;
+}
+
+@-moz-keyframes downloadsIndicatorNotificationRight {
+  from { opacity: 0; -moz-transform: translate(-128px, 128px) scale(8); }
+  20%  { opacity: .85; -moz-animation-timing-function: ease-out; }
+  to   { opacity: 0; -moz-transform: translate(0) scale(1); }
+}
+
+@-moz-keyframes downloadsIndicatorNotificationLeft {
+  from { opacity: 0; -moz-transform: translate(128px, 128px) scale(8); }
+  20%  { opacity: .85; -moz-animation-timing-function: ease-out; }
+  to   { opacity: 0; -moz-transform: translate(0) scale(1); }
+}
+
+#downloads-indicator[notification] > #downloads-indicator-anchor > #downloads-indicator-notification {
+  -moz-animation-name: downloadsIndicatorNotificationRight;
+  -moz-animation-duration: 1s;
+}
+
+#downloads-indicator[notification]:-moz-locale-dir(rtl) > #downloads-indicator-anchor > #downloads-indicator-notification {
+  -moz-animation-name: downloadsIndicatorNotificationLeft;
+}
+
+/*** Progress bar and text ***/
+
+#downloads-indicator-counter {
+  height: 12px;
+  margin: 0;
+  color: hsl(0,0%,30%);
+  text-shadow: 0 1px 0 hsla(0,0%,100%,.5);
+  font-size: 10px;
+  line-height: 10px;
+  text-align: center;
+}
+
+#downloads-indicator-progress {
+  width: 24px;
+  height: 4px;
+  min-width: 0;
+  min-height: 0;
+  margin-top: 1px;
+  margin-bottom: 2px;
+  border-radius: 2px;
+  box-shadow: 0 1px 0 hsla(0,0%,100%,.4);
+}
+
+#downloads-indicator-progress > .progress-bar {
+  -moz-appearance: none;
+  min-width: 0;
+  min-height: 0;
+  background-image: -moz-linear-gradient(#41a0ff, #2090ff);
+  border: 1px solid;
+  border-color: hsla(0,0%,0%,.6) hsla(0,0%,0%,.2) hsla(0,0%,0%,.2);
+  border-radius: 2px 0 0 2px;
+}
+
+#downloads-indicator-progress > .progress-remainder {
+  -moz-appearance: none;
+  min-width: 0;
+  min-height: 0;
+  background-image: -moz-linear-gradient(#9a9a9a, #a1a1a1);
+  border: 1px solid;
+  border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.3) hsla(0,0%,0%,.2);
+  -moz-border-start: none;
+  border-radius: 0 2px 2px 0;
+}
+
+#downloads-indicator[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-bar {
+  background-image: -moz-linear-gradient(#a0a000, #909000);
+}
+
+#downloads-indicator[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-remainder {
+  background-image: -moz-linear-gradient(#9a9a00, #a1a100);
+}
--- a/browser/themes/gnomestripe/jar.mn
+++ b/browser/themes/gnomestripe/jar.mn
@@ -30,16 +30,20 @@ browser.jar:
   skin/classic/browser/Privacy-48.png
   skin/classic/browser/searchbar.css                  (searchbar.css)
   skin/classic/browser/Secure.png
   skin/classic/browser/Security-broken.png
   skin/classic/browser/setDesktopBackground.css
   skin/classic/browser/Toolbar.png
   skin/classic/browser/Toolbar-small.png
   skin/classic/browser/urlbar-arrow.png
+  skin/classic/browser/downloads/buttons.png          (downloads/buttons.png)
+  skin/classic/browser/downloads/download-glow.png    (downloads/download-glow.png)
+  skin/classic/browser/downloads/download-notification.png (downloads/download-notification.png)
+  skin/classic/browser/downloads/downloads.css        (downloads/downloads.css)
   skin/classic/browser/feeds/feedIcon.png             (feeds/feedIcon.png)
   skin/classic/browser/feeds/feedIcon16.png           (feeds/feedIcon16.png)
   skin/classic/browser/feeds/videoFeedIcon.png        (feeds/feedIcon.png)
   skin/classic/browser/feeds/videoFeedIcon16.png      (feeds/feedIcon16.png)
   skin/classic/browser/feeds/audioFeedIcon.png        (feeds/feedIcon.png)
   skin/classic/browser/feeds/audioFeedIcon16.png      (feeds/feedIcon16.png)
   skin/classic/browser/feeds/subscribe.css            (feeds/subscribe.css)
   skin/classic/browser/feeds/subscribe-ui.css         (feeds/subscribe-ui.css)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0fdb1b4c138eee18daf481dc503d24fb631bef57
GIT binary patch
literal 1673
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
T&}r*z00000NkvXXu0mjfnfx4l
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8babe61f450c5a27c8a37ab04687b6aa3eb4ab61
GIT binary patch
literal 2205
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
MAUg;wV8(3u2Ro09^8f$<
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bd548b18382388cfbe7672d1d0725bff0d21432c
GIT binary patch
literal 3166
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
XW9$$UKIlc7{VnitVLF#Nh9&<8<U0OL
new file mode 100644
--- /dev/null
+++ b/browser/themes/pinstripe/downloads/downloads.css
@@ -0,0 +1,298 @@
+/* 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/. */
+
+/*** Panel and outer controls ***/
+
+#downloadsPanel > .panel-arrowcontainer > .panel-arrowcontent {
+  padding: 0;
+}
+
+#downloadsListBox {
+  width: 60ch;
+  background: transparent;
+  padding: 4px;
+}
+
+#downloadsPanel:not([hasdownloads]) > #downloadsListBox {
+  display: none;
+}
+
+#downloadsHistory {
+  background: inherit;
+  border-bottom-left-radius: 6px;
+  border-bottom-right-radius: 6px;
+  color: hsl(210,100%,75%);
+  cursor: pointer;
+}
+
+#downloadsPanel:not([hasdownloads]) > #downloadsHistory {
+  border-top-left-radius: 6px;
+  border-top-right-radius: 6px;
+}
+
+#downloadsPanel[hasdownloads] > #downloadsHistory {
+  border-top: 1px solid hsla(0,0%,100%,.1);
+  background-color: hsla(0,0%,7%,.3);
+  box-shadow: 0 1px 1px hsla(0,0%,0%,.25) inset;
+}
+
+#downloadsHistory > .button-box {
+  margin: 1em;
+}
+
+#downloadsHistory:-moz-focusring > .button-box {
+  border-top-left-radius: 6px;
+  border-top-right-radius: 6px;
+}
+
+#downloadsPanel:not([hasdownloads]) > #downloadsHistory:-moz-focusring > .button-box {
+  border-bottom-left-radius: 6px;
+  border-bottom-right-radius: 6px;
+}
+
+/*** List items ***/
+
+richlistitem[type="download"] {
+  height: 7em;
+  margin: 0;
+  border-top: 1px solid hsla(0,0%,100%,.07);
+  border-bottom: 1px solid hsla(0,0%,0%,.2);
+  background: transparent;
+  padding: 0;
+}
+
+richlistitem[type="download"]:first-child {
+  border-top: 1px solid transparent;
+}
+
+richlistitem[type="download"]:last-child {
+  border-bottom: 1px solid transparent;
+}
+
+#downloadsListBox:-moz-focusring > richlistitem[type="download"][selected] {
+  outline: 1px #999 dotted;
+  outline-offset: -1px;
+  -moz-outline-radius: 3px;
+}
+
+.downloadInfo {
+  color: white;
+  padding: 8px;
+  -moz-padding-end: 0;
+}
+
+.downloadTypeIcon {
+  -moz-margin-end: 8px;
+  /* Prevent flickering when changing states. */
+  min-height: 32px;
+  min-width: 32px;
+}
+
+.blockedIcon {
+  list-style-image: url("chrome://global/skin/icons/Error.png");
+}
+
+.downloadTarget {
+  margin-bottom: 6px;
+  cursor: inherit;
+}
+
+.downloadDetails {
+  opacity: 0.7;
+  font-size: 95%;
+  cursor: inherit;
+}
+
+.downloadButton {
+  -moz-appearance: none;
+  min-width: 0;
+  min-height: 0;
+  margin: 6px;
+  border: none;
+  background: transparent;
+  padding: 5px;
+  list-style-image: url("chrome://browser/skin/downloads/buttons.png");
+}
+
+.downloadButton > .button-box {
+  padding: 0;
+}
+
+/*** Highlighted list items ***/
+
+richlistitem[type="download"][state="1"] .downloadInfo {
+  border-top: 1px solid transparent;
+  border-bottom: 1px solid transparent;
+  -moz-padding-end: 8px;
+}
+
+richlistitem[type="download"][state="1"] .downloadInfo:hover {
+  border-radius: 3px;
+  border-top: 1px solid hsla(0,0%,100%,.2);
+  border-bottom: 1px solid hsla(0,0%,0%,.4);
+  background-color: Highlight;
+  background-image: -moz-linear-gradient(hsl(210,100%,50%), hsl(210,96%,41%));
+  color: HighlightText;
+  cursor: pointer;
+}
+
+/*** Button icons ***/
+
+.downloadButton.downloadCancel {
+  -moz-image-region: rect(0px, 14px, 14px, 0px);
+}
+.downloadButton.downloadCancel:hover {
+  -moz-image-region: rect(0px, 28px, 14px, 14px);
+}
+.downloadButton.downloadCancel:active {
+  -moz-image-region: rect(0px, 42px, 14px, 28px);
+}
+
+.downloadButton.downloadShow {
+  -moz-image-region: rect(14px, 14px, 28px, 0px);
+}
+.downloadButton.downloadShow:hover {
+  -moz-image-region: rect(14px, 28px, 28px, 14px);
+}
+.downloadButton.downloadShow:active {
+  -moz-image-region: rect(14px, 42px, 28px, 28px);
+}
+
+.downloadButton.downloadRetry {
+  -moz-image-region: rect(28px, 14px, 42px, 0px);
+}
+.downloadButton.downloadRetry:hover {
+  -moz-image-region: rect(28px, 28px, 42px, 14px);
+}
+.downloadButton.downloadRetry:active {
+  -moz-image-region: rect(28px, 42px, 42px, 28px);
+}
+
+/*** Status and progress indicator ***/
+
+#downloads-indicator {
+  width: 35px;
+}
+
+#downloads-indicator-anchor {
+  min-width: 20px;
+  min-height: 20px;
+  /* Makes the outermost stack element positioned, so that its contents are
+     rendered over the main browser window in the Z order.  This is required by
+     the animated event notification. */
+  position: relative;
+}
+
+/*** Main indicator icon ***/
+
+#downloads-indicator-icon {
+  background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
+                              0, 140, 20, 120) center no-repeat;
+}
+
+#downloads-indicator-icon:-moz-lwtheme-brighttext {
+  background: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"),
+                              0, 140, 20, 120) center no-repeat;
+}
+
+#downloads-indicator[attention]
+#downloads-indicator-icon {
+  background: -moz-image-rect(url("chrome://browser/skin/downloads/download-glow.png"),
+                              14, 34, 34, 14) center no-repeat;
+}
+
+#downloads-indicator:not([counter])
+#downloads-indicator-counter {
+  background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
+                              0, 140, 20, 120) center no-repeat;
+  background-size: 12px;
+}
+
+#downloads-indicator:not([counter])[attention]
+#downloads-indicator-counter {
+  background-image: -moz-image-rect(url("chrome://browser/skin/downloads/download-glow.png"),
+                                    14, 34, 34, 14);
+}
+
+/*** Event notification ***/
+
+#downloads-indicator-notification {
+  opacity: 0;
+  background: url("chrome://browser/skin/downloads/download-notification.png")
+              center no-repeat;
+  background-size: 16px;
+}
+
+@-moz-keyframes downloadsIndicatorNotificationRight {
+  from { opacity: 0; -moz-transform: translate(-128px, 128px) scale(8); }
+  20%  { opacity: .85; -moz-animation-timing-function: ease-out; }
+  to   { opacity: 0; -moz-transform: translate(0) scale(1); }
+}
+
+@-moz-keyframes downloadsIndicatorNotificationLeft {
+  from { opacity: 0; -moz-transform: translate(128px, 128px) scale(8); }
+  20%  { opacity: .85; -moz-animation-timing-function: ease-out; }
+  to   { opacity: 0; -moz-transform: translate(0) scale(1); }
+}
+
+#downloads-indicator[notification] > #downloads-indicator-anchor > #downloads-indicator-notification {
+  -moz-animation-name: downloadsIndicatorNotificationRight;
+  -moz-animation-duration: 1s;
+}
+
+#downloads-indicator[notification]:-moz-locale-dir(rtl) > #downloads-indicator-anchor > #downloads-indicator-notification {
+  -moz-animation-name: downloadsIndicatorNotificationLeft;
+}
+
+/*** Progress bar and text ***/
+
+#downloads-indicator-counter {
+  height: 12px;
+  margin: 0;
+  color: hsl(0,0%,30%);
+  text-shadow: 0 1px 0 hsla(0,0%,100%,.5);
+  font-size: 10px;
+  line-height: 10px;
+  text-align: center;
+}
+
+#downloads-indicator-progress {
+  width: 24px;
+  height: 4px;
+  min-width: 0;
+  min-height: 0;
+  margin-top: 1px;
+  margin-bottom: 2px;
+  border-radius: 2px;
+  box-shadow: 0 1px 0 hsla(0,0%,100%,.4);
+}
+
+#downloads-indicator-progress > .progress-bar {
+  -moz-appearance: none;
+  min-width: 0;
+  min-height: 0;
+  background-image: -moz-linear-gradient(#41a0ff, #2090ff);
+  border: 1px solid;
+  border-color: hsla(0,0%,0%,.6) hsla(0,0%,0%,.2) hsla(0,0%,0%,.2);
+  border-radius: 2px 0 0 2px;
+}
+
+#downloads-indicator-progress > .progress-remainder {
+  -moz-appearance: none;
+  min-width: 0;
+  min-height: 0;
+  background-image: -moz-linear-gradient(#9a9a9a, #a1a1a1);
+  border: 1px solid;
+  border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.3) hsla(0,0%,0%,.2);
+  -moz-border-start: none;
+  border-radius: 0 2px 2px 0;
+}
+
+#downloads-indicator[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-bar {
+  background-image: -moz-linear-gradient(#a0a000, #909000);
+}
+
+#downloads-indicator[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-remainder {
+  background-image: -moz-linear-gradient(#9a9a00, #a1a100);
+}
--- a/browser/themes/pinstripe/jar.mn
+++ b/browser/themes/pinstripe/jar.mn
@@ -40,16 +40,20 @@ browser.jar:
   skin/classic/browser/Search.png
   skin/classic/browser/Secure-Glyph-White.png
   skin/classic/browser/keyhole-circle.png
   skin/classic/browser/Toolbar.png
   skin/classic/browser/toolbarbutton-dropmarker.png
   skin/classic/browser/urlbar-history-dropmarker.png
   skin/classic/browser/urlbar-arrow.png
   skin/classic/browser/urlbar-popup-blocked.png
+  skin/classic/browser/downloads/buttons.png                (downloads/buttons.png)
+  skin/classic/browser/downloads/download-glow.png          (downloads/download-glow.png)
+  skin/classic/browser/downloads/download-notification.png  (downloads/download-notification.png)
+  skin/classic/browser/downloads/downloads.css              (downloads/downloads.css)
   skin/classic/browser/feeds/subscribe.css                  (feeds/subscribe.css)
   skin/classic/browser/feeds/subscribe-ui.css               (feeds/subscribe-ui.css)
   skin/classic/browser/feeds/feedIcon.png                   (feeds/feedIcon.png)
   skin/classic/browser/feeds/feedIcon16.png                 (feeds/feedIcon16.png)
   skin/classic/browser/feeds/videoFeedIcon.png              (feeds/feedIcon.png)
   skin/classic/browser/feeds/videoFeedIcon16.png            (feeds/feedIcon16.png)
   skin/classic/browser/feeds/audioFeedIcon.png              (feeds/feedIcon.png)
   skin/classic/browser/feeds/audioFeedIcon16.png            (feeds/feedIcon16.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..22592cd6c991067145b4571f1b056ee553f92438
GIT binary patch
literal 3143
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..586e59bdc4d5e21f5ac56c258d9a14c31a09d946
GIT binary patch
literal 3223
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
JPDHLkV1kiMAf^BS
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c70b5b0370796782dc0cf99b0a3f87ee9256e490
GIT binary patch
literal 2545
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c8b3dc02ff1a1bb3c053996039fae83ac3eed6a6
GIT binary patch
literal 2183
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bd548b18382388cfbe7672d1d0725bff0d21432c
GIT binary patch
literal 3166
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
XW9$$UKIlc7{VnitVLF#Nh9&<8<U0OL
new file mode 100644
--- /dev/null
+++ b/browser/themes/winstripe/downloads/downloads-aero.css
@@ -0,0 +1,49 @@
+%define WINSTRIPE_AERO
+%include downloads.css
+%undef WINSTRIPE_AERO
+
+#downloadsPanel > .panel-arrowcontainer > .panel-arrowcontent > .panel-inner-arrowcontent {
+  padding: 0;
+}
+
+@media (-moz-windows-default-theme) {
+  #downloadsPanel[hasdownloads] > #downloadsHistory {
+    background-color: #f1f5fb;
+  }
+
+  richlistitem[type="download"] {
+    border: 1px solid transparent;
+    border-bottom: 1px solid hsl(213,40%,90%);
+  }
+
+  richlistitem[type="download"][state="1"] > .downloadInfo {
+    border: 1px solid transparent;
+    -moz-padding-end: 8px;
+  }
+
+  richlistitem[type="download"][state="1"] > .downloadInfo:hover {
+    border: 1px solid hsl(213,45%,65%);
+    box-shadow: 0 0 0 1px hsla(0,0%,100%,.5) inset,
+                0 1px 0 hsla(0,0%,100%,.3) inset;
+    background-image: -moz-linear-gradient(hsl(212,86%,92%), hsl(212,91%,86%));
+    color: black;
+  }
+}
+
+@media (-moz-windows-compositor) {
+  #toolbar-menubar #downloads-indicator-icon:not(:-moz-lwtheme),
+  #TabsToolbar[tabsontop=true] #downloads-indicator-icon:not(:-moz-lwtheme),
+  #navigator-toolbox[tabsontop=false] > #nav-bar #downloads-indicator-icon:not(:-moz-lwtheme),
+  #nav-bar + #customToolbars + #PersonalToolbar[collapsed=true] + #TabsToolbar[tabsontop=false]:last-child #downloads-indicator-icon:not(:-moz-lwtheme) {
+    list-style-image: url("chrome://browser/skin/Toolbar-inverted.png");
+  }
+
+  #toolbar-menubar #downloads-indicator-counter:not(:-moz-lwtheme),
+  #TabsToolbar[tabsontop=true] #downloads-indicator-counter:not(:-moz-lwtheme),
+  #navigator-toolbox[tabsontop=false] > #nav-bar #downloads-indicator-counter:not(:-moz-lwtheme),
+  #nav-bar + #customToolbars + #PersonalToolbar[collapsed=true] + #TabsToolbar[tabsontop=false]:last-child #downloads-indicator-counter:not(:-moz-lwtheme) {
+    color: white;
+    text-shadow: 0 0 1px rgba(0,0,0,.7),
+                 0 1px 1.5px rgba(0,0,0,.5);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/themes/winstripe/downloads/downloads.css
@@ -0,0 +1,308 @@
+/* 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/. */
+
+/*** Panel and outer controls ***/
+
+%ifndef WINSTRIPE_AERO
+#downloadsHistory,
+#downloadsHistory:-moz-focusring > .button-box {
+  border-bottom-left-radius: 6px;
+  border-bottom-right-radius: 6px;
+}
+
+#downloadsPanel:not([hasdownloads]) > #downloadsHistory,
+#downloadsPanel:not([hasdownloads]) > #downloadsHistory:-moz-focusring > .button-box  {
+  border-top-left-radius: 6px;
+  border-top-right-radius: 6px;
+}
+
+#downloadsPanel > .panel-arrowcontainer > .panel-arrowcontent {
+  /* Avoid that the arrow overlaps the selection on first item */
+  padding-top: 5px;
+}
+%endif
+
+#downloadsPanel > .panel-arrowcontainer > .panel-arrowcontent {
+  padding: 0;
+}
+
+#downloadsListBox {
+  width: 60ch;
+  background-color: transparent;
+  padding: 4px;
+}
+
+#downloadsHistory {
+  background: inherit;
+  color: -moz-nativehyperlinktext;
+  cursor: pointer;
+}
+
+#downloadsHistory > .button-box {
+  margin: 1em;
+}
+
+@media (-moz-windows-default-theme) {
+  #downloadsPanel[hasdownloads] > #downloadsHistory {
+    background-color: hsla(216,45%,88%,.98);
+    box-shadow: 0px 1px 2px rgb(204,214,234) inset;  
+  }
+}
+
+/*** List items ***/
+
+richlistitem[type="download"] {
+  height: 7em;
+  margin: 0;
+  border-top: 1px solid hsla(0,0%,100%,.3);
+  border-bottom: 1px solid hsla(220,18%,51%,.25);
+  background: transparent;
+  padding: 0;
+}
+
+richlistitem[type="download"]:first-child {
+  border-top: 1px solid transparent;
+}
+
+@media (-moz-windows-default-theme) {
+  richlistitem[type="download"]:last-child {
+    border-bottom: 1px solid transparent;
+  }
+}
+
+#downloadsListBox:-moz-focusring > richlistitem[type="download"][selected] {
+  outline: 1px #999 dotted;
+  outline-offset: -1px;
+  -moz-outline-radius: 3px;
+}
+
+.downloadInfo {
+  padding: 8px;
+  -moz-padding-end: 0;
+}
+
+.downloadTypeIcon {
+  -moz-margin-end: 8px;
+  /* Prevent flickering when changing states. */
+  min-height: 32px;
+  min-width: 32px;
+}
+
+.blockedIcon {
+  list-style-image: url("chrome://global/skin/icons/Error.png");
+}
+
+.downloadTarget {
+  margin-bottom: 6px;
+  cursor: inherit;
+}
+
+.downloadDetails {
+  opacity: 0.6;
+  font-size: 90%;
+  cursor: inherit;
+}
+
+.downloadButton {
+  -moz-appearance: none;
+  min-width: 0;
+  min-height: 0;
+  margin: 6px;
+  border: none;
+  background: transparent;
+  padding: 5px;
+  list-style-image: url("chrome://browser/skin/downloads/buttons.png");
+}
+
+.downloadButton > .button-box {
+  padding: 0;
+}
+
+/*** Highlighted list items ***/
+
+%ifdef WINSTRIPE_AERO
+@media not all and (-moz-windows-default-theme) {
+%endif
+
+richlistitem[type="download"][state="1"] > .downloadInfo {
+  border-top: 1px solid transparent;
+  border-bottom: 1px solid transparent;
+  -moz-padding-end: 8px;
+}
+
+%ifdef WINSTRIPE_AERO
+}
+%endif
+
+richlistitem[type="download"][state="1"] > .downloadInfo:hover {
+  border-radius: 3px;
+  border-top: 1px solid hsla(0,0%,100%,.2);
+  border-bottom: 1px solid hsla(0,0%,0%,.2);
+  background-color: Highlight;
+  color: HighlightText;
+  cursor: pointer;
+}
+
+/*** Button icons ***/
+
+.downloadButton.downloadCancel {
+  -moz-image-region: rect(0px, 14px, 14px, 0px);
+}
+.downloadButton.downloadCancel:hover {
+  -moz-image-region: rect(0px, 28px, 14px, 14px);
+}
+.downloadButton.downloadCancel:active {
+  -moz-image-region: rect(0px, 42px, 14px, 28px);
+}
+
+.downloadButton.downloadShow {
+  -moz-image-region: rect(14px, 14px, 28px, 0px);
+}
+.downloadButton.downloadShow:hover {
+  -moz-image-region: rect(14px, 28px, 28px, 14px);
+}
+.downloadButton.downloadShow:active {
+  -moz-image-region: rect(14px, 42px, 28px, 28px);
+}
+
+.downloadButton.downloadRetry {
+  -moz-image-region: rect(28px, 14px, 42px, 0px);
+}
+.downloadButton.downloadRetry:hover {
+  -moz-image-region: rect(28px, 28px, 42px, 14px);
+}
+.downloadButton.downloadRetry:active {
+  -moz-image-region: rect(28px, 42px, 42px, 28px);
+}
+
+/*** Status and progress indicator ***/
+
+#downloads-indicator {
+  width: 35px;
+}
+
+#downloads-indicator-anchor {
+  min-width: 18px;
+  min-height: 18px;
+  /* Makes the outermost stack element positioned, so that its contents are
+     rendered over the main browser window in the Z order.  This is required by
+     the animated event notification. */
+  position: relative;
+}
+
+/*** Main indicator icon ***/
+
+#downloads-indicator-icon {
+  background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
+                              0, 108, 18, 90) center no-repeat;
+}
+
+#downloads-indicator-icon:-moz-lwtheme-brighttext {
+  background: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"),
+                              0, 108, 18, 90) center no-repeat;
+}
+
+#downloads-indicator[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+  background: -moz-image-rect(url("chrome://browser/skin/downloads/download-glow.png"),
+                              15, 33, 33, 15) center no-repeat;
+}
+
+#downloads-indicator:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+  background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
+                              0, 108, 18, 90) center no-repeat;
+  background-size: 12px;
+}
+
+#downloads-indicator:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+  background-image: -moz-image-rect(url("chrome://browser/skin/downloads/download-glow.png"),
+                                    16, 32, 32, 16);
+}
+
+/*** Event notification ***/
+
+#downloads-indicator-notification {
+  opacity: 0;
+  background: url("chrome://browser/skin/downloads/download-notification.png")
+              center no-repeat;
+  background-size: 16px;
+}
+
+@-moz-keyframes downloadsIndicatorNotificationRight {
+  from { opacity: 0; -moz-transform: translate(-128px, 128px) scale(8); }
+  20%  { opacity: .85; -moz-animation-timing-function: ease-out; }
+  to   { opacity: 0; -moz-transform: translate(0) scale(1); }
+}
+
+@-moz-keyframes downloadsIndicatorNotificationLeft {
+  from { opacity: 0; -moz-transform: translate(128px, 128px) scale(8); }
+  20%  { opacity: .85; -moz-animation-timing-function: ease-out; }
+  to   { opacity: 0; -moz-transform: translate(0) scale(1); }
+}
+
+#downloads-indicator[notification] > #downloads-indicator-anchor > #downloads-indicator-notification {
+  -moz-animation-name: downloadsIndicatorNotificationRight;
+  -moz-animation-duration: 1s;
+}
+
+#downloads-indicator[notification]:-moz-locale-dir(rtl) > #downloads-indicator-anchor > #downloads-indicator-notification {
+  -moz-animation-name: downloadsIndicatorNotificationLeft;
+}
+
+/*** Progress bar and text ***/
+
+#downloads-indicator-counter {
+  height: 12px;
+  margin: 0;
+  color: hsl(0,0%,30%);
+  text-shadow: hsla(0,0%,100%,.5) 0 1px;
+  font-size: 10px;
+  line-height: 10px;
+  text-align: center;
+}
+
+#downloads-indicator-counter:-moz-lwtheme-brighttext {
+  color: white;
+  text-shadow: 0 0 1px rgba(0,0,0,.7),
+               0 1px 1.5px rgba(0,0,0,.5);
+}
+
+#downloads-indicator-progress {
+  width: 24px;
+  height: 4px;
+  min-width: 0;
+  min-height: 0;
+  margin-top: 1px;
+  margin-bottom: 2px;
+  border-radius: 2px;
+  box-shadow: 0 1px 0 hsla(0,0%,100%,.4);
+}
+
+#downloads-indicator-progress > .progress-bar {
+  -moz-appearance: none;
+  min-width: 0;
+  min-height: 0;
+  background-image: -moz-linear-gradient(#92DDA0, #6FC483 49%, #5EB272 51%, #80CE91);
+  border: 1px solid;
+  border-color: hsla(0,0%,0%,.6) hsla(0,0%,0%,.2) hsla(0,0%,0%,.2);
+  border-radius: 2px 0 0 2px;
+}
+
+#downloads-indicator-progress > .progress-remainder {
+  -moz-appearance: none;
+  min-width: 0;
+  min-height: 0;
+  background-image: -moz-linear-gradient(#9a9a9a, #a1a1a1);
+  border: 1px solid;
+  border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.3) hsla(0,0%,0%,.2);
+  -moz-border-start: none;
+  border-radius: 0 2px 2px 0;
+}
+
+#downloads-indicator[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-bar {
+  background-image: -moz-linear-gradient(#DDDD00, #C4C400 49%, #B2B200 51%, #CECE00);
+}
+
+#downloads-indicator[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-remainder {
+  background-image: -moz-linear-gradient(#9a9a00, #a1a100);
+}
--- a/browser/themes/winstripe/jar.mn
+++ b/browser/themes/winstripe/jar.mn
@@ -43,16 +43,20 @@ browser.jar:
         skin/classic/browser/setDesktopBackground.css
         skin/classic/browser/menu-back.png                           (menu-back.png)
         skin/classic/browser/menu-forward.png                        (menu-forward.png)
         skin/classic/browser/monitor.png
         skin/classic/browser/monitor_16-10.png
         skin/classic/browser/urlbar-arrow.png
         skin/classic/browser/urlbar-popup-blocked.png
         skin/classic/browser/urlbar-history-dropmarker.png
+        skin/classic/browser/downloads/buttons.png                   (downloads/buttons.png)
+        skin/classic/browser/downloads/download-glow.png             (downloads/download-glow.png)
+        skin/classic/browser/downloads/download-notification.png     (downloads/download-notification.png)
+*       skin/classic/browser/downloads/downloads.css                 (downloads/downloads.css)
         skin/classic/browser/feeds/feedIcon.png                      (feeds/feedIcon.png)
         skin/classic/browser/feeds/feedIcon16.png                    (feeds/feedIcon16.png)
         skin/classic/browser/feeds/audioFeedIcon.png                 (feeds/feedIcon.png)
         skin/classic/browser/feeds/audioFeedIcon16.png               (feeds/feedIcon16.png)
         skin/classic/browser/feeds/videoFeedIcon.png                 (feeds/feedIcon.png)
         skin/classic/browser/feeds/videoFeedIcon16.png               (feeds/feedIcon16.png)
         skin/classic/browser/feeds/subscribe.css                     (feeds/subscribe.css)
         skin/classic/browser/feeds/subscribe-ui.css                  (feeds/subscribe-ui.css)
@@ -212,16 +216,20 @@ browser.jar:
         skin/classic/aero/browser/setDesktopBackground.css
         skin/classic/aero/browser/menu-back.png                      (menu-back-aero.png)
         skin/classic/aero/browser/menu-forward.png                   (menu-forward-aero.png)
         skin/classic/aero/browser/monitor.png
         skin/classic/aero/browser/monitor_16-10.png
         skin/classic/aero/browser/urlbar-arrow.png
         skin/classic/aero/browser/urlbar-popup-blocked.png
         skin/classic/aero/browser/urlbar-history-dropmarker.png
+        skin/classic/aero/browser/downloads/buttons.png              (downloads/buttons-aero.png)
+        skin/classic/aero/browser/downloads/download-glow.png        (downloads/download-glow-aero.png)
+        skin/classic/aero/browser/downloads/download-notification.png (downloads/download-notification.png)
+*       skin/classic/aero/browser/downloads/downloads.css            (downloads/downloads-aero.css)
         skin/classic/aero/browser/feeds/feedIcon.png                 (feeds/feedIcon-aero.png)
         skin/classic/aero/browser/feeds/feedIcon16.png               (feeds/feedIcon16-aero.png)
         skin/classic/aero/browser/feeds/audioFeedIcon.png            (feeds/feedIcon-aero.png)
         skin/classic/aero/browser/feeds/audioFeedIcon16.png          (feeds/feedIcon16-aero.png)
         skin/classic/aero/browser/feeds/videoFeedIcon.png            (feeds/feedIcon-aero.png)
         skin/classic/aero/browser/feeds/videoFeedIcon16.png          (feeds/feedIcon16-aero.png)
         skin/classic/aero/browser/feeds/subscribe.css                (feeds/subscribe.css)
         skin/classic/aero/browser/feeds/subscribe-ui.css             (feeds/subscribe-ui.css)
--- a/testing/xpcshell/xpcshell.ini
+++ b/testing/xpcshell/xpcshell.ini
@@ -66,16 +66,17 @@ skip-if = os == "android"
 [include:content/test/unit/xpcshell.ini]
 [include:toolkit/components/url-classifier/tests/unit/xpcshell.ini]
 [include:services/crypto/tests/unit/xpcshell.ini]
 [include:services/crypto/components/tests/unit/xpcshell.ini]
 [include:services/sync/tests/unit/xpcshell.ini]
 # Bug 676978: tests hang on Android 
 skip-if = os == "android"
 [include:browser/components/dirprovider/tests/unit/xpcshell.ini]
+[include:browser/components/downloads/test/unit/xpcshell.ini]
 [include:browser/components/feeds/test/unit/xpcshell.ini]
 [include:browser/components/migration/tests/unit/xpcshell.ini]
 [include:browser/components/places/tests/unit/xpcshell.ini]
 [include:browser/components/privatebrowsing/test/unit/xpcshell.ini]
 [include:browser/components/shell/test/unit/xpcshell.ini]
 [include:extensions/spellcheck/hunspell/tests/unit/xpcshell.ini]
 [include:toolkit/components/search/tests/xpcshell/xpcshell.ini]
 [include:toolkit/mozapps/shared/test/unit/xpcshell.ini]
--- a/toolkit/components/downloads/nsDownloadManager.cpp
+++ b/toolkit/components/downloads/nsDownloadManager.cpp
@@ -877,16 +877,21 @@ nsDownloadManager::Init()
     (void)pbs->GetPrivateBrowsingEnabled(&mInPrivateBrowsing);
     if (mInPrivateBrowsing)
       OnEnterPrivateBrowsingMode();
   }
 
   nsCOMPtr<nsINavHistoryService> history =
     do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
 
+  (void)mObserverService->NotifyObservers(
+                                static_cast<nsIDownloadManager *>(this),
+                                "download-manager-initialized",
+                                nsnull);
+
   // The following AddObserver calls must be the last lines in this function,
   // because otherwise, this function may fail (and thus, this object would be not
   // completely initialized), but the observerservice would still keep a reference
   // to us and notify us about shutdown, which may cause crashes.
   // failure to add an observer is not critical
   (void)mObserverService->AddObserver(this, "quit-application", true);
   (void)mObserverService->AddObserver(this, "quit-application-requested", true);
   (void)mObserverService->AddObserver(this, "offline-requested", true);
@@ -911,16 +916,29 @@ nsDownloadManager::GetRetentionBehavior(
   nsresult rv;
   nsCOMPtr<nsIPrefBranch> pref = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, 0);
 
   PRInt32 val;
   rv = pref->GetIntPref(PREF_BDM_RETENTION, &val);
   NS_ENSURE_SUCCESS(rv, 0);
 
+  // Allow the Downloads Panel to change the retention behavior.  We do this to
+  // allow proper migration to the new feature when using the same profile on
+  // multiple versions of the product (bug 697678).  Implementation note: in
+  // order to allow observers to change the retention value, we have to pass an
+  // object in the aSubject parameter, we cannot use aData for that.
+  nsCOMPtr<nsISupportsPRInt32> retentionBehavior =
+    do_CreateInstance(NS_SUPPORTS_PRINT32_CONTRACTID);
+  retentionBehavior->SetData(val);
+  (void)mObserverService->NotifyObservers(retentionBehavior,
+                                          "download-manager-change-retention",
+                                          nsnull);
+  retentionBehavior->GetData(&val);
+
   return val;
 }
 
 enum nsDownloadManager::QuitBehavior
 nsDownloadManager::GetQuitBehavior()
 {
   // We use 0 as the default, which is "remember and resume the download"
   nsresult rv;
@@ -1819,16 +1837,22 @@ nsDownloadManager::SwitchDatabaseTypeTo(
   nsresult rv = InitDB();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Do things *after* initializing various download manager properties such as
   // restoring downloads to a consistent state
   rv = RestoreDatabaseState();
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // Notify that the database type changed before resuming current downloads
+  (void)mObserverService->NotifyObservers(
+                                static_cast<nsIDownloadManager *>(this),
+                                "download-manager-database-type-changed",
+                                nsnull);
+
   rv = RestoreActiveDownloads();
   NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to restore all active downloads");
 
   return rv;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// nsINavHistoryObserver
@@ -1937,17 +1961,17 @@ nsDownloadManager::Observe(nsISupports *
     PRUint32 id;
     dl->GetId(&id);
     nsDownload *dl2 = FindDownload(id);
     if (dl2)
       return CancelDownload(id);
   } else if (strcmp(aTopic, "profile-before-change") == 0) {
     mGetIdsForURIStatement->Finalize();
     mUpdateDownloadStatement->Finalize();
-    mozilla::DebugOnly<nsresult> rv = mDBConn->Close();
+    mozilla::DebugOnly<nsresult> rv = mDBConn->AsyncClose(nsnull);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
   } else if (strcmp(aTopic, "quit-application") == 0) {
     // Try to pause all downloads and, if appropriate, mark them as auto-resume
     // unless user has specified that downloads should be canceled
     enum QuitBehavior behavior = GetQuitBehavior();
     if (behavior != QUIT_AND_CANCEL)
       (void)PauseAllDownloads(bool(behavior != QUIT_AND_PAUSE));
 
--- a/toolkit/components/downloads/test/browser/browser_nsIDownloadManagerUI.js
+++ b/toolkit/components/downloads/test/browser/browser_nsIDownloadManagerUI.js
@@ -101,16 +101,23 @@ function test()
   // First, we populate the database with some fake data
   db.executeSimpleSQL("DELETE FROM moz_downloads");
 
   // See if the DM is already open, and if it is, close it!
   var win = Services.wm.getMostRecentWindow("Download:Manager");
   if (win)
     win.close();
 
+  // Ensure that the download manager callbacks and nsIDownloadManagerUI always
+  // use the window UI instead of the panel in the browser's window.
+  Services.prefs.setBoolPref("browser.download.useToolkitUI", true);
+  registerCleanupFunction(function() {
+    Services.prefs.clearUserPref("browser.download.useToolkitUI");
+  });
+
   // OK, now that all the data is in, let's pull up the UI
   Cc["@mozilla.org/download-manager-ui;1"].
   getService(Ci.nsIDownloadManagerUI).show();
 
   // The window doesn't open once we call show, so we need to wait a little bit
   function finishUp() {
     var win = Services.wm.getMostRecentWindow("Download:Manager");
 
--- a/toolkit/content/Services.jsm
+++ b/toolkit/content/Services.jsm
@@ -63,16 +63,17 @@ XPCOMUtils.defineLazyGetter(Services, "d
 });
 
 let initTable = [
   ["appShell", "@mozilla.org/appshell/appShellService;1", "nsIAppShellService"],
   ["cache", "@mozilla.org/network/cache-service;1", "nsICacheService"],
   ["console", "@mozilla.org/consoleservice;1", "nsIConsoleService"],
   ["contentPrefs", "@mozilla.org/content-pref/service;1", "nsIContentPrefService"],
   ["cookies", "@mozilla.org/cookiemanager;1", "nsICookieManager2"],
+  ["downloads", "@mozilla.org/download-manager;1", "nsIDownloadManager"],
   ["droppedLinkHandler", "@mozilla.org/content/dropped-link-handler;1", "nsIDroppedLinkHandler"],
   ["eTLD", "@mozilla.org/network/effective-tld-service;1", "nsIEffectiveTLDService"],
   ["io", "@mozilla.org/network/io-service;1", "nsIIOService2"],
   ["locale", "@mozilla.org/intl/nslocaleservice;1", "nsILocaleService"],
   ["logins", "@mozilla.org/login-manager;1", "nsILoginManager"],
   ["obs", "@mozilla.org/observer-service;1", "nsIObserverService"],
   ["perms", "@mozilla.org/permissionmanager;1", "nsIPermissionManager"],
   ["prompt", "@mozilla.org/embedcomp/prompt-service;1", "nsIPromptService"],
--- a/toolkit/mozapps/downloads/tests/chrome/test_destinationURI_annotation.xul
+++ b/toolkit/mozapps/downloads/tests/chrome/test_destinationURI_annotation.xul
@@ -32,16 +32,17 @@ let ww = Cc["@mozilla.org/embedcomp/wind
            .getService(Ci.nsIWindowWatcher);
 
 let dm = Cc["@mozilla.org/download-manager;1"]
            .getService(Ci.nsIDownloadManager);
 
 let os = Cc["@mozilla.org/observer-service;1"]
            .getService(Ci.nsIObserverService);
 
+Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
 
 let checkDestination = false,
     checkFileName = false;
 
 SimpleTest.waitForExplicitFinish();
 
 let annoObserver = {
@@ -109,16 +110,20 @@ let downloadListener = {
   },
 
   onStateChange: function() {},
   onProgressChange: function() {},
   onSecurityChange: function() {}
 };
 
 function init() {
+  // Ensure that the download manager callbacks always use the window UI instead
+  // of the panel in the browser's window.
+  Services.prefs.setBoolPref("browser.download.useToolkitUI", true);
+
   ww.registerNotification(windowObserver);
   PlacesUtils.annotations.addObserver(annoObserver, false);
   dm.addListener(downloadListener);
 
   let frame = document.getElementById("testframe");
   frame.setAttribute("src", DOWNLOAD_URI);
 }
 
@@ -157,16 +162,18 @@ function endTest() {
   dm = null;
   os = null;
 
   Cc["@mozilla.org/appshell/window-mediator;1"]
     .getService(Ci.nsIWindowMediator)
     .getMostRecentWindow("Download:Manager")
     .close();
 
+  Services.prefs.clearUserPref("browser.download.useToolkitUI");
+
   waitForClearHistory(SimpleTest.finish);
 }
 
   ]]>
   </script>
 
   <body xmlns="http://www.w3.org/1999/xhtml">
     <p id="display"></p>
--- a/toolkit/mozapps/downloads/tests/chrome/test_taskbarprogress_service.xul
+++ b/toolkit/mozapps/downloads/tests/chrome/test_taskbarprogress_service.xul
@@ -54,16 +54,18 @@
 
   <script type="application/javascript">
   <![CDATA[
 
 const kTaskbarID = "@mozilla.org/windows-taskbar;1";
 const DOWNLOAD_MANAGER_URL = "chrome://mozapps/content/downloads/downloads.xul";
 const DLMGR_UI_DONE = "download-manager-ui-done";
 
+Components.utils.import("resource://gre/modules/Services.jsm");
+
 let DownloadTaskbarProgress, TaskbarService, observerService, wwatch, chromeWindow;
 let gGen = null;
 
 function test() {
   let isWin7OrHigher = false;
   try {
     let version = Cc["@mozilla.org/system-info;1"]
                     .getService(Ci.nsIPropertyBag2)
@@ -80,16 +82,19 @@ function test() {
   gGen.next();
 }
 
 function continueTest() {
   gGen.next();
 }
 
 function doTest() {
+  // Ensure that the download manager callbacks always use the window UI instead
+  // of the panel in the browser's window.
+  Services.prefs.setBoolPref("browser.download.useToolkitUI", true);
 
   let tempScope = {};
   Components.utils.import("resource://gre/modules/DownloadTaskbarProgress.jsm", tempScope);
 
   DownloadTaskbarProgress = tempScope.DownloadTaskbarProgress;
   TaskbarService =  Cc[kTaskbarID].getService(Ci.nsIWinTaskbar);
 
   observerService = Cc["@mozilla.org/observer-service;1"].
@@ -138,16 +143,18 @@ function doTest() {
 
   ok(browserWindow, "Browser window was opened");
   DownloadTaskbarProgress.onBrowserWindowLoad(browserWindow);
 
   // The owner window should not have changed, since our
   // original window still exists
   checkActiveTaskbar(false, window);
 
+  Services.prefs.clearUserPref("browser.download.useToolkitUI");
+
   browserWindow.close();
   SimpleTest.finish();
   yield;
 }
 
 function checkActiveTaskbar(isDownloadManager, ownerWindow) {
 
   isnot(DownloadTaskbarProgress.activeTaskbarProgress, null, "DownloadTaskbarProgress has an active taskbar");
--- a/toolkit/mozapps/downloads/tests/chrome/test_ui_stays_open_on_alert_clickback.xul
+++ b/toolkit/mozapps/downloads/tests/chrome/test_ui_stays_open_on_alert_clickback.xul
@@ -50,40 +50,46 @@
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
   <script type="application/javascript"
           src="utils.js"/>
 
   <script type="application/javascript">
   <![CDATA[
 
+Components.utils.import("resource://gre/modules/Services.jsm");
+
 function test()
 {
   var dmui = getDMUI();
   if (!dmui) {
     todo(false, "skip test for toolkit download manager UI");
     return;
   }
 
   let dm = Cc["@mozilla.org/download-manager;1"].
            getService(Ci.nsIDownloadManager);
 
   // Empty any old downloads
   dm.DBConnection.executeSimpleSQL("DELETE FROM moz_downloads");
 
-  let setClose = function(aVal) Cc["@mozilla.org/preferences-service;1"].
-    getService(Ci.nsIPrefBranch).
-    setBoolPref("browser.download.manager.closeWhenDone", aVal);
+  function setClose(aVal) {
+    Services.prefs.setBoolPref("browser.download.manager.closeWhenDone", aVal);
+  }
 
   // Close the UI if necessary
   let wm = Cc["@mozilla.org/appshell/window-mediator;1"].
            getService(Ci.nsIWindowMediator);
   let win = wm.getMostRecentWindow("Download:Manager");
   if (win) win.close();
 
+  // Ensure that the download manager callbacks always use the window UI instead
+  // of the panel in the browser's window.
+  Services.prefs.setBoolPref("browser.download.useToolkitUI", true);
+
   let os = Cc["@mozilla.org/observer-service;1"].
            getService(Ci.nsIObserverService);
   const DLMGR_UI_DONE = "download-manager-ui-done";
 
   let testObs = {
     observe: function(aSubject, aTopic, aData)
     {
       if (aTopic != DLMGR_UI_DONE)
@@ -96,21 +102,19 @@ function test()
       //        synchronously in Startup in downloads.js
       // Make sure the window stays open
       ok(dmui.visible, "Download Manager stays open on alert click");
 
       win.close();
       setClose(false);
       os.removeObserver(testObs, DLMGR_UI_DONE);
 
-      try {
-        Cc["@mozilla.org/preferences-service;1"].
-        getService(Ci.nsIPrefBranch).
-        clearUserPref("browser.download.manager.closeWhenDone");
-      } catch (err) { }
+      Services.prefs.clearUserPref("browser.download.manager.closeWhenDone");
+      Services.prefs.clearUserPref("browser.download.useToolkitUI");
+
       SimpleTest.finish();
     }
   };
 
   // Register with the observer service
   os.addObserver(testObs, DLMGR_UI_DONE, false);
 
   // Simulate an alert click with pref set to true