Bug 726444 - Implement the Downloads Panel. r=mak ui-r=limi
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Tue, 17 Apr 2012 13:35:09 +0200
changeset 95163 913f7811c0681b7485a61044502a1877f3c96006
parent 95162 9caaf32e08fc012fb993b4bed2296fbb9086a75a
child 95164 fdc8a3e8c956d48b0e7ed5812aa1122d464da46c
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak, limi
bugs726444, 697679
milestone14.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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.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/indicatorOverlay.xul
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/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
@@ -344,16 +344,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.js
+++ b/browser/base/content/browser.js
@@ -1386,16 +1386,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();
@@ -1635,16 +1637,21 @@ function delayedStartup(isLoadingBlank, 
       let tempScope = {};
       Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm",
                 tempScope);
       tempScope.DownloadTaskbarProgress.onBrowserWindowLoad(window);
     }
 #endif
   }, 10000);
 
+  // The object handling the downloads indicator is also initialized here in the
+  // delayed startup function, but the actual indicator element is not loaded
+  // unless there are downloads to be displayed.
+  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);
@@ -3717,16 +3724,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");
@@ -3783,16 +3791,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
@@ -850,16 +850,19 @@
 
 # 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;"/>
--- a/browser/base/content/global-scripts.inc
+++ b/browser/base/content/global-scripts.inc
@@ -36,10 +36,12 @@
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 
 <script type="application/javascript" src="chrome://global/content/printUtils.js"/>
 <script type="application/javascript" src="chrome://global/content/viewZoomOverlay.js"/>
 <script type="application/javascript" src="chrome://browser/content/places/browserPlacesViews.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser.js"/>
+<script type="application/javascript" src="chrome://browser/content/downloads/downloads.js"/>
+<script type="application/javascript" src="chrome://browser/content/downloads/indicator.js"/>
 <script type="application/javascript" src="chrome://global/content/inlineSpellCheckUI.js"/>
 <script type="application/javascript" src="chrome://global/content/viewSourceUtils.js"/>
--- 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,1201 @@
+/* -*- 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.
+ *
+ * DownloadsOverlayLoader
+ * Allows loading the downloads panel and the status indicator interfaces on
+ * demand, to improve startup performance.
+ *
+ * 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,
+
+  /**
+   * Location of the panel overlay.
+   */
+  get kDownloadsOverlay()
+      "chrome://browser/content/downloads/downloadsOverlay.xul",
+
+  /**
+   * 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) {
+      DownloadsOverlayLoader.ensureOverlayLoaded(this.kDownloadsOverlay,
+                                                 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.
+    DownloadsOverlayLoader.ensureOverlayLoaded(this.kDownloadsOverlay,
+                                               function DP_I_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.
+    DownloadsButton.getAnchor(function DP_OPIDR_callback(aAnchor) {
+      if (aAnchor) {
+        this.panel.openPopup(aAnchor, "bottomcenter topright", 0, 0, false,
+                             null);
+      } else {
+        this.panel.openPopup(document.getElementById("TabsToolbar"),
+                             "after_end", 0, 0, false, null);
+      }
+    }.bind(this));
+  }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsOverlayLoader
+
+/**
+ * Allows loading the downloads panel and the status indicator interfaces on
+ * demand, to improve startup performance.
+ */
+const DownloadsOverlayLoader = {
+  /**
+   * We cannot load two overlays at the same time, thus we use a queue of
+   * pending load requests.
+   */
+  _loadRequests: [],
+
+  /**
+   * True while we are waiting for an overlay to be loaded.
+   */
+  _overlayLoading: false,
+
+  /**
+   * This object has a key for each overlay URI that is already loaded.
+   */
+  _loadedOverlays: {},
+
+  /**
+   * Loads the specified overlay and invokes the given callback when finished.
+   *
+   * @param aOverlay
+   *        String containing the URI of the overlay to load in the current
+   *        window.  If this overlay has already been loaded using this
+   *        function, then the overlay is not loaded again. 
+   * @param aCallback
+   *        Invoked when loading is completed.  If the overlay is already
+   *        loaded, the function is called immediately.
+   */
+  ensureOverlayLoaded: function DOL_ensureOverlayLoaded(aOverlay, aCallback)
+  {
+    // The overlay is already loaded, invoke the callback immediately.
+    if (aOverlay in this._loadedOverlays) {
+      aCallback();
+      return;
+    }
+
+    // The callback will be invoked when loading is finished.
+    this._loadRequests.push({ overlay: aOverlay, callback: aCallback });
+    if (this._overlayLoading) {
+      return;
+    }
+
+    function DOL_EOL_loadCallback() {
+      this._overlayLoading = false;
+      this._loadedOverlays[aOverlay] = 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();
+
+      this.processPendingRequests();
+    }
+
+    this._overlayLoading = true;
+    document.loadOverlay(aOverlay, DOL_EOL_loadCallback.bind(this));
+  },
+
+  /**
+   * Re-processes all the currently pending requests, invoking the callbacks
+   * and/or loading more overlays as needed.  In most cases, there will be a
+   * single request for one overlay, that will be processed immediately.
+   */
+  processPendingRequests: function DOL_processPendingRequests()
+  {
+    // Re-process all the currently pending requests, yet allow more requests
+    // to be appended at the end of the array if we're not ready for them.
+    let currentLength = this._loadRequests.length;
+    for (let i = 0; i < currentLength; i++) {
+      let request = this._loadRequests.shift();
+
+      // We must call ensureOverlayLoaded again for each request, to check if
+      // the associated callback can be invoked now, or if we must still wait
+      // for the associated overlay to load.
+      this.ensureOverlayLoaded(request.overlay, request.callback);
+    }
+  }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// 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;
+    }
+
+    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;
+        }
+        return null;
+      }.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";
+        }
+        return null;
+      }.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,114 @@
+<?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/.
+
+<?xml-stylesheet href="chrome://browser/content/downloads/downloads.css"?>
+<?xml-stylesheet href="chrome://browser/skin/downloads/downloads.css"?>
+
+<!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,591 @@
+/* -*- 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 = {
+  /**
+   * Location of the indicator overlay.
+   */
+  get kIndicatorOverlay()
+      "chrome://browser/content/downloads/indicatorOverlay.xul",
+
+  /**
+   * 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()
+  {
+    // Exit now if the feature is disabled.  To improve startup time, we don't
+    // load the DownloadsCommon module yet, but check the preference directly.
+    if (gPrefService.getBoolPref("browser.download.useToolkitUI")) {
+      return;
+    }
+
+    // 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;
+
+    let indicator = DownloadsIndicatorView.indicator;
+    if (indicator) {
+      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.  This does not happen if the
+   * indicator is currently being used as the anchor for the panel, to ensure
+   * that the panel doesn't flicker because we move the DOM element to which
+   * it's anchored.
+   */
+  updatePosition: function DB_updatePosition()
+  {
+    if (!this._anchorRequested) {
+      this._updatePositionInternal();
+    }
+  },
+
+  /**
+   * 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.
+   */
+  _updatePositionInternal: function DB_updatePositionInternal()
+  {
+    let indicator = DownloadsIndicatorView.indicator;
+    if (!indicator) {
+      // Exit now if the indicator overlay isn't loaded yet.
+      return null;
+    }
+
+    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.
+   *
+   * @param aCallback
+   *        Called when the anchor is available, passing the 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(aCallback)
+  {
+    // Do not allow anchoring the panel to the element while customizing.
+    if (this._customizing) {
+      aCallback(null);
+      return;
+    }
+
+    function DB_GA_callback() {
+      this._anchorRequested = true;
+      aCallback(this._updatePositionInternal());
+    }
+
+    DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay,
+                                               DB_GA_callback.bind(this));
+  },
+
+  /**
+   * 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 = {
+  /**
+   * True when the view is connected with the underlying downloads data.
+   */
+  _initialized: false,
+
+  /**
+   * True when the user interface elements required to display the indicator
+   * have finished loading in the browser window, and can be referenced.
+   */
+  _operational: 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;
+  },
+
+  /**
+   * Ensures that the user interface elements required to display the indicator
+   * are loaded, then invokes the given callback.
+   */
+  _ensureOperational: function DIV_ensureOperational(aCallback)
+  {
+    if (this._operational) {
+      aCallback();
+      return;
+    }
+
+    function DIV_EO_callback() {
+      this._operational = true;
+
+      // If the view is initialized, we need to update the elements now that
+      // they are finally available in the document.
+      if (this._initialized) {
+        DownloadsCommon.indicatorData.refreshView(this);
+      }
+
+      aCallback();
+    }
+
+    DownloadsOverlayLoader.ensureOverlayLoaded(
+                                 DownloadsButton.kIndicatorOverlay,
+                                 DIV_EO_callback.bind(this));
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// 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;
+    }
+
+    function DIV_SEN_callback() {
+      if (this._notificationTimeout) {
+        clearTimeout(this._notificationTimeout);
+      }
+
+      let indicator = this.indicator;
+      indicator.setAttribute("notification", "true");
+      this._notificationTimeout = setTimeout(
+        function () indicator.removeAttribute("notification"), 1000);
+    }
+
+    this._ensureOperational(DIV_SEN_callback.bind(this));
+  },
+
+  //////////////////////////////////////////////////////////////////////////////
+  //// 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;
+
+      // If there is at least one download, ensure that the view elements are
+      // loaded before determining the position of the downloads button.
+      if (aValue) {
+        this._ensureOperational(function() DownloadsButton.updatePosition());
+      } else {
+        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._operational) {
+      return this._counter;
+    }
+
+    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._operational) {
+      return this._percentComplete;
+    }
+
+    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._operational) {
+      return this._paused;
+    }
+
+    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._operational) {
+      return this._attention;
+    }
+
+    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();
+    }
+  },
+
+  /**
+   * Returns a reference to the main indicator element, or null if the element
+   * is not present in the browser window yet.
+   */
+  get indicator()
+  {
+    let indicator = document.getElementById("downloads-indicator");
+    if (!indicator) {
+      return null;
+    }
+
+    // Once the element is loaded, it will never be unloaded.
+    delete this.indicator;
+    return this.indicator = 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/indicatorOverlay.xul
@@ -0,0 +1,51 @@
+<?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/.
+
+<?xml-stylesheet href="chrome://browser/content/downloads/downloads.css"?>
+<?xml-stylesheet href="chrome://browser/skin/downloads/downloads.css"?>
+
+<!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="indicatorOverlay">
+
+  <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"
+             class="toolbarbutton-icon">
+        <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>
+</overlay>
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/jar.mn
@@ -0,0 +1,7 @@
+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)
+*       content/browser/downloads/indicatorOverlay.xul   (content/indicatorOverlay.xul)
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,1230 @@
+/* -*- 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);
+    this.refreshView(aView);
+  },
+
+  /**
+   * Updates the properties of an object previously added using addView.
+   *
+   * @param aView
+   *        DownloadsIndicatorView object to be updated.
+   */
+  refreshView: function DID_refreshView(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,242 @@
+/* -*- 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);
+  }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// 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,20 @@
+# 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 \
+  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/head.js
@@ -0,0 +1,229 @@
+/* -*- 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;
+
+  // Wait for focus on the main window.
+  waitForFocus(testRunner.continueTest);
+  yield;
+}
+
+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);
+
+  // Wait for focus on the main window.
+  waitForFocus(testRunner.continueTest);
+  yield;
+
+  // Open the downloads panel, waiting until loading is completed.
+  DownloadsPanel.showPanel();
+  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
@@ -303,16 +303,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
@@ -126,16 +128,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
zc$@(o3*+>OP)<h;3K|Lk000e1NJLTq001fg001}$1^@s6i`{C=000W(Nkl<Zc-qyO
zdrVYU9><r_fnr51O2t=LaIL#kcQ<XU4@7(-z6C)Pe1Iq_prU{eR8)L}uWEdt8}R|Y
z1t0C&WVh)iX3d(Anyz$f$NI-M8k?}KR$0?=)w7@9?3u|-=H40TA7xAO$vOAjIo$7@
z$M5&OXMUlS`dQ#{F@F5`CZj*!Y_49t>LdVKL!d5OY24caphbmRpe=NRw;&S2p|jL=
zTQ~g~i=oh2vu0&L7<K1$C}cH3Xa>0YK`3OI2yLx}#4TL7@DE3h9Lb}e1c{ZEl{pI*
zEI3MiD0GL`UP9;0nUg(t?p&3Xm351H7=%)<lQ!ykW<n=Tnv@L+Zb2A?LY;2QGY}dC
zZ_S@S|Ja~GgVeZj;~rO6S6@1P`gARyc_eM~=FO|(Grxu)BcWY$a&qbyFJ7z`En0L7
z>ZDGaje9-JgeD~=bxlrAu1`rxQIxl!PS<VR>tP_&3Q>$?{)7n=p2Wn&sEHFNKIKa}
z>SM=_Z9H`7P$8e`309-24?s{vVPRqYs#U9$K;6cuM#()NQy(y8%9M!d)2G*`r>83+
zEiKK4P~Fc<Xe$W6a^=dDs;a6F5ZE|m$PgtU{MpHqC$DjDDxc{Btqj-bvSrIUu359@
z(~TQ9DuMcEw7uzXjn0@cqhm%!#;1HKB~bs2Hr>xyC?uUZbEbCc)TvL04<D`sW}@-b
zsZ&?^OEwkC2!^g*yS8rg=FK9~2K7>>?H2dK%!FoUW;)h#c6PR`EgRHJUDi7H!b~<(
z)bZoTb5m1OAB`S8T9(N(1UE{Z^6}B5M;FUy45xnGx^+1sY}2Mqw*373s3l95L{N8#
z<zBv-P=v`&RbV4RM9rKzGlGp$&$S58H`z?RdB#cBqY{BEfop82YtyDpdm?S@jnk%^
ziGO58PytizSX;u<rAs5Eje4Muskb7iVD{|Uu6_uPpiTEP9MSF<FJ2tQV4SO{s8}g$
z5>nV;MYNrjdnVz@_259y4M3eXKWo&ypLxI!5h1M6zLzdt`jxClfqGAA(;`E_($Xo^
zqDM9etk6nhl{S6P;+{f(7Pyb*Pk^a834{*N6(Xe6*Ma(eiwg~aHd<UPOozF;jFonM
zKfqKdyEcJce1kY>mr|~qC?WxFfL)gfK7<BoG2@9W)wDN|=U+-G*R)U2_k#?DB5oW;
zL}H+w@`f%I?a97GlCQ^s0z2H$rGjJ=p&cNWgmMajiah@+F){IbDY=#ueqJ0FykRJm
zta2Os)j;6x&2<|ox&BVteF^0uw=p3N<ZeelDY^bm+D(LZhv@|PN2Jf^WU5atU%q^X
z@-M@N4f~I{D(<I2cSE6hd3pBS+}w{`agmbi52f9gP%@02T<y`1>kp;fL}*XQ89H?6
zD@@F{)K5b>RAX%ZMf*$Y^Wc|;Ldi_^xTXfuSU0(rvY?=#fs|M8OQ^Uf9@sz{bo7%_
zuBF{Ts1<$(YZ-CL@y~HhH$*Ju#|a4u&zTYP5k*kw>eZ|3S<H&k&N{hXTwE-rLTI{~
zNSsnAo35U9tSIelzU#VF>HHap><kkz%hg!K#t|b%sQCE!mwc(mQjQ!sQk+|@;Tm1J
za;3vPVMNr14IA!JzgbdJq7ZtM5l-VXt&K!t=|b_bb!<Atrn^V|CMZPTM5sqUoozZo
z3?`&pObEghYxx->zn3~~-}2m=+S=MgnRbs|kNVK{>(|$9-@aXymX_L>?kL$15F(|S
zNUU5a0_)gpip^$YWPSG2sWI}{v19W^5K}DfNzAec+l(10gv^?nn&BeSD~=*EXUmo?
z3QK3hF}a+%31$}0Uq26v$F3HUPk5o*k8?_*{rAulx<LmBa%LuDi|1dYvBmS>&&%TR
zZd5=>8miq>polWUx5e{MbNd-tJQ=}`zC^ZEt~a!J2I2yo7zu3&!9matI%(QNh%Q6n
zb+9%|C?O;AM~ZUq=WgvfLNpAABu%`&jw4K_=*`vMODMZP7_x|?m4eiDTO<kKz7InZ
z3|UaA$<l3+;L%Tu>;kc8&z{XA$$UgkkkEFwsHo_Z3l}aNIehqVCHen}2=~fNydL+3
zI|O(|X9v_JWZj|tJ|`y1NaW<nllzhe?|^bgx7`QjIo(V#B6scD_5PSKV;rX=?$Gvc
zGSd%I=X3L+mrE#Of)R6<7-}bQmlAGz3z(4#zNG>==J66r`U~cr{4Ut3mm;dSz;y+N
zMB=2P?k@B-c$<-2f}p3Ij=bWTU*VWOK5^p2-+1-|v3LUq4pf5&4;G=TAlfB#+qP{*
z%a<=#IHYyXaD(_LV(SCR^UhH=a7;yH7!l;^M?H#k_`rF*L)~3y2Z$$zUdJ8D|GYyS
zEhpfwCGcM(Q~eMd8>@u4xHu7-553$o)5R4lR>)bC!};voxigY<dS6810UVGFyo4g;
zB5O-=;^F9rTZ)AH(uR)}M6A2eH(@GS?4NRk!@<J|u4nDuy*nFU`b65CU{pkCipLsd
z<Z8(Gs=U11d0u3Jwh-cu!7ED*rbHpkPd`tgEUT|ajT)seCdar=CC|s4J9jRPj8#m%
zoU(-Fk)fkPJNFSS#*C=Fd-twtPUt~oF)37>lAnH_Q_t-DRnD21#eZ<U9?~^)U<M3>
zULq1&yM)rQy;!?_`}Wz%zsn9DJQ%K#dWqSCndbHgy~kOT9VfL6!UbLT>gU`{_|O75
zxufkB>;E?H=_s3NA3Cn_zr=^W3vU=^COW()2V@5h98jF5IoQ8{zhb?9$@x$>GqHHj
z_ryI(_;R9c-<P2Kd55Pxd-m)X_arXL9im4A_Y&oEW+~LdKkh^Z%4Hc;2~w9fKc>UN
z&hNy?<}w;pg4Ahq^)m|i7?L>FXaRfU&wR13__B8ytUofkbBP4el1lyMdMmu9vHB3$
zjDF@Rl#LS)sT#>dztQ)sBJ%&!lu*}g7DJl{Kea%s-dI8IKYn@A@<M~53%sp~fDkR%
zztEEZ{YC$XhnGNfGlYi0AcFn}WTYL`Gvyk(LmO!2Q>f%}GSmy=X$6Eg^}*OU`QhsY
zWd(#cL1-k*7V`nGh_?@TD?O|O{<{#amqmb~P>Fw-1xH>do<7A*t%kP#9?Uzr#6Abq
zpW>cYgK0ngBf@eK+P{B)HDJI1F(5B+QTIrj<=Q~^aohO;9zqc*cFqN9RbfmXZr!>y
z%g>pU92&U5peWiNV($$5>HNJ;91>z(i2o%2+cD^M+|Pk`paWPugo-$VuLqo#C@i4b
zv17;Q7?x<0G%7C115ApzCK3KQL>mben};9uLhPNKJMr8_&R<46gd$9)+ygNxY%n>i
z{DO{sOpJ+Qoq3>-bVT$Q2<~Gf6dNaY&I@U0={#mO7aIx{QH+{{va&M8$%$;PZy4#N
zOl3Psl}1A4keBr5fb^;KuXf+CtC#YcqWgIZ<(x^T+J#)p;V%RB_w(n^7h?DN8BRTG
zQDFvTnnh$IdAE!QclMV?5h!m4iq{qq+sJ5^G2ObK`DWr(MB(TK`LXr|ru}PN)%yrc
z<$hn&&4f4^P1!`pIUDN9KMiu43V%XHBu3;oQ|-5(4o?}{Ey<UBNO-D5U?ygypH2qB
zU@;6&2rR?M_7hXloTDA4x{USM<bK-a$KaU$w3k0l?iL_0hPEE?y5E4WN5ay~tkmp&
uI{S3t=k9kH#ozi)p)GGp0wK7mrsTiPD0l}?0`CL>0000<MNUMnLSTY<V0(f9
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
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
XW9$$UKIlc7{VnitVLF#Nh9&<8<U0OL
new file mode 100644
--- /dev/null
+++ b/browser/themes/gnomestripe/downloads/downloads.css
@@ -0,0 +1,279 @@
+/* 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;
+  color: inherit;
+}
+
+#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: inherit;
+}
+
+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
zc$|G!d00|e9|pw~XPktz-Mk?gS#0i*xWL7<LP^ck%8}q2h;R`w(4xW8)ETpA@f+L5
z)~PJhEGu=?R&7R0%Uoz&Gp9vc)Cnug!OFhx`JTr)&vWlNzu)t|zw@5odH*<>+@K{^
zmNP982!vH&02pGNDPy0-B;$XT$=h_}G(*XbP=>;>N)=BIA^ZgJItU1q@YX{i5KoYl
za2xVMAWZS1un1)YXE~h@OGvyi3`s4KA;yN6k6Om#iy<Yj4q7jgGH@@;Dsg~Fz`(6?
z<4`y<79<n}B+H@D<e)HqvY1a3;C#FRFE!m*KmsXwfLanSRnXN8+^4#9<9aMj#sQxo
zN-+cXSyTjv3$S201h|nXL_Wox3b?zHsIC;6D{T?rOrcW9RA(~9gGi;&-8|?ND)43C
zjJwGNvGfqo|I1#+h=CI-l`=Y+tWv2+Di;zgUr(meXf!g#ne6OLG$M$KB&m|8CQ20!
z;}t+i!Iz6<N)apt#$w(&I8n*K;l__3k$o*IReWiav0-F2Pe!JaDCGYEayb7El}Ns#
z70M7O_TSNpup}8o4uKSKqMUEMxLAiVvW(7>Lp&uc4};<O@h)<Ouo6}XVHv;*bt6#$
z4v#OAj%7YEI2?MQRH5Wa`A{Ipz!@_nkw`%I13?$ikLkyvxU#5JHpP?0@&wsTf2uRh
z-I?X*G0p{Hexd}DD#y8k|8kiVa>vZ@nGMNB8z6zd9F_o|4ow$Lti^L;EuXo9iM7xs
z<dThI$p3fM<0+$m#+F}=YYe_xACemVE;kxG7F%eBK$uMq1pUI)`o5?-@mjWxkBgxq
zSi{L*z{xsmd)luJ-IUi^ZJ#Gxxc3bB<QJgSDeZ;LsD<j;?`$g!1~n}t%AO>Cn8uwo
zYnP4a7tTQY!O1VnYcI^7?_wCdVli~JE2y{qiovj7(YvWORdkl;GwRtl*ikXx`hK>m
zqToYNj-oQBE_zMvTrLKm?-|}aZ(1|rj^&%Th`6EIFZfXA4cn?m;^>D<e0RG$Xf>LP
z=+vskU56?bnFNJR8fM~8$7!cs-cpUXLEfq9cs5#(xR~(my+H8eiG6v+FF<|1Lv?1j
zJaZ3qZk+RXp0-O(ygHBD#-7s#6go$rI)iSA+|l6X2yQ~*vAUw@#$(Gf%wp}gN_UF0
zTvN~l)SA4^0IzQj+CBgyrwpXPyTn_C$eO<S;c8%S!|nm*;oKb-!#R5$cX|3&)sECG
z?V0P_7GE&68+#~SUt!u&(yiwlGs}+G*Hs`ZITj|7JqN7O$;X^#X6R0N*4r(V9J8`V
znr;qwv7-6Mw8zJ%C7>Vv>evWI$xx-+ZXAazhfxu(caI9qe_LK8PLgz853anxpAp&8
z;=(Se!=TrC?1YFh>zQj89&9@NYwu(q6;^%pA`6&t?cMf221SF^yS0nY?P)Ps2G3q_
zF(cByv-=SHPyC5SNpPL7p#~ijZ=T;|sTX@(#iCGNMUR%?vjvaCOS302ql9y@#GAOC
z`;G=DP&995e7iZdDRg!~pySjUwI4Pwq?4(ue<y2?LTx&r+t#yh#Tu$*C^oyMC01bR
ztt<Q9@j(H--0#-CT!XHD_m4NSc2<G6d~}=RKv!VH58eYkL5G)bIC6y>O)W0|{m7j;
zRzG{6pT(>m&^9f^SXjMzGdl7hP2u!xdGTfB>y$=QyYE|AT86i2<sywD`p?y7L~Li=
z#{T{tcQoO$>)Mr>H`q(B6_I}!ZG7sJ^X^jgqx5V=d#*_v?Q(Uh5}p(_zkSQC-nHD(
zkyGuOzjO0db57LhLtmxGJ!Q^XP3LOgJ2@qEK3V2dVR$#Fq3J`;H<(_H;#<W~HNA(H
zmh_mz73$-Q-(L$RWCRWcb>JW9bH6cBoqAnYR(NmIK<nfCWS@p<&pmpC8>j4QgdK0q
z+Ronkbc(q7@u{I1jKui2=Qh5&WJj)lPtfUC|D^1lhyF16{2zOfX9~Ag=iDuKZfkzt
z46Q>fV@$ojZM(R;_V)D;L%H_hqiGM_9qjb4(YG^Cx;tU&Q{q?b3ZP$zDG7;Lc-k;@
zulw4NZArq)jI+q;)c%`k4*#iR3}z)Yv3v#E=0rE&pAtOYe^;*KIEW3Q+R!EB%_Fz^
z&h>BaDB1pV$+J3*PldUx!@G=a(W4{u`HPTUE{AJ2PdB$;l{oK`IpsiqoyPFCmB#<;
z)qAHkQFc{!VDgiOEr6qq9G})kXj5N9sSzVvFL=%?y5g!`eJl@C)3?pcjhlbcAkrQs
zVoD(#H?B62?O)7wN}5UF_-<@M7TcQy6Q7rd>Lh#d0M22~9$X&7-jKG@zL1cP*l;sU
z;J>JTCCB1r&f_Ekh7iCkh)4!G_fjJ!q5E4cyUYy!PkRgSA4-L@2}e?_(FSC;MIpBA
zzTULdX81^}Fax!rh%HY#m{5gT+UZ!b_^jz)ky!^$4rlKNtg{AugNHH}C`XQ(RtH<x
zBYF@-vx+#s-Kukav?9{*$4qMi3uQTHe-nK;cE`uoN0^l4%2CvGgc<*(ZrKI9lCcLv
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,300 @@
+/* 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;
+  color: inherit;
+}
+
+#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;
+  color: inherit;
+}
+
+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
zc$|G!c{r47AAZf)LQS1UrNT6-&pPvteI`?wA!MYnCdD-Kj&)|t!VIFJBuXVq4zd(c
zWJ~t4w^)iePK#0(+BifDiQ^k>*LQu_={(o<zR&afUHAQa?&tU1e>`!H4m+1CtXBX4
zV7aXgnJSsVrB7Z~@^^!Mj+IO*0xNfc6Nf1f(s&SH!QdQ(P_`_Z4@8A%j8N`f$P57B
zoBf>K1@07k0-eLc(3UV55i1B-8fH60K{R>*BtRX6eEir%^kjV_8s*0zqTP%rASH+d
z`TE&}@gS!#2WNU%03FXj@7Rtq6A>f@Sdf5*60ri=e1eFG{#utHSudrrXw+ATAb^Pe
zCd!@Sh$3-#5XuMx>d`@C9Lm@bgEIv2hIn0+K8ORcIDIT=qK5+sMkWLhhx$IylHGU=
zCV@(}{=SzaBBFf-f*=AGD-;SbLIVtk=Yz%J@pvq#kJZ=LlOXi?p=<$7q{rrK{HQ>N
z_;j9Mkid__MlHp(gPdRi5sm(F1Xj?`vTXkMHc1+W714sQI1GsWA0UPD?@$)&Cz>yy
zLd<_h^PNM3AS@N)bAowv$;B}>mdHT_5)YyYI6P+#C-6rX9ep_h4&Rp(gd#Z^VQ?r4
zjqb-@%6w%|C<I$JUqEBiAzLyLEy-Z~{1^laGTDG^VQEDI4M{kh6=+H#nUbw6t#SHz
zV||i^$qz1>Ll0&_Y{3sML&60u^>H{$0}B#pWc82SB{O_uLp;9_h+)m+uuxwQP4N4*
z7SgY^eB(0yLCbe8`IlU*L=5)-uKGtx;-97EPvc60pVo)i62J2###Y}rPz(UFbX&58
zv*_`-$I}2+D`fWXPrv-0!8zhf;ZgRr8G0mDA+~GIIwIS}R}s+zyOD=_4>t)Hf##XB
z&e1stW#_jf<zA}2yu5a|ThhZkqwZIfiS8T1nF#{lASo-e>)H7mxi(EPtr)(K$t&T#
z$wOWCg_B=KK(CV<r^kBWWmAA^AfZRj>*k@Tfw_f@=3F>;pfU4F5%!eSYGC~qzou)h
zv0@*MAUT*ASzzv;KpTx%ew;|ZF7^hBSNJ=ARzvQ3uxgim{DjG__p1Jut#^Pk)5xf@
zGZkE|II)j>hh;}!A!=NH3ce`LpAy#<4bjXr?VLxh)r3EvxLcc&Gh6rszJ6@q`c54k
zMO&_I$2O!<DxlV!Rdyk)`IK*jFIOJmJ%cG&AT&=Xa%*aMr!FoNjILUYgq1}pZ>m;C
zsRC}=ft}$hkZxMyNdSb;^;5U6DC}+8(we^~{?6@gYI>8%`kqTAb*NW1cwIsuX^b>V
zxB0voNKBr0Js?(d9Kdq5{CzLIk5+#v!}N`a=Mk&SDn*LtTrn)q$N80a%h!yo8eitF
zQ5d&2jn-RXxOeA#Z)M(5>P;8zd2?}PCtiVU9jS5ou3f4v_uBIJ+h0b)q#V`m4Bj-(
z&iWF0c2L#pDE@r#haH&{kuOAZxdrve_T(w=kQP|*yRo>aocN;Y2#*Ijws+@Kv?g0N
zU~g1?;^M|;*&j7<{z(HVBjfMZ4xI`D^rpi>gIbiecRa*%*l$t%-dsr;lNKQ@PR%cm
z8GWK&!YF0WJ9r*`#d(sP_$ocSYkJ5`1tG2Jl^V7G0kZ0~T8}jLq?BSi@FChRDIips
zVfu_Hi;s*sj{6*`(P8t%NZWwa+)&zHrm+UM|4WW}p{Ok8MZ}0`R0&_8lzjX0TAjl=
zc3qbGdpuVos_rL@<OUyWq?_)$r{%Bf)j5|a^;#SJ0F>N{rv~1Jd+tX_y$g{7ouh^|
zgFVvOCg6&e=rrHAQhmA}$>=}wG+aZ~WXpyV)6$QaasrSh9+w^;n~yEYJLBQxwUC&;
zNPBP>%f=LPO7Jo(yeDJZ(q<E|Ykk^CFB6%MgFm@Xzt*{7-)HBvdM?fJxO8!-u(Wn^
zD^)#W`sL^Re%IkkZs^Y)#wtTK<)<yL<=2n2-<KO=l~MB&x7h5=-8A#4pw){H(6UOG
zZS9CxGWN)-6J`-TZt71DyWg(WxPS5-^J=%<9+~k22vI06I5g9c3&7(7p%0z4y_ml-
z?unsBA$qV30<$a}NI3>IdJs=H&b7_?Ukmr)GCrM4Dxa`BwzWvhsSQBlwnm0)5Bzm+
z#qNd7fSGfr&X4VrOR0`dO_Lk5HqzN#Jnw+<Vbo6^F(}%Hs2FX;cFz-5cGgjneOykT
zeHzx3-Tv_M!;2Gx`ELT+@6BEj?QGB)i#j3}6~??9lM=syDZ9lK+Gy!Ho)~p#ILyrN
zSN6(K$!d6(eWxzrWO&PeTvpv**!b|nyS8B5tG4h0x3+EbPaBGtT{rI^m9o!{ADG+L
zZlhUlrSkN7yk702wzu86alL)zYSjtQ@s(3%M8R;i-EN*s*D8Mdw(0$&F%`$`auqi)
zZPaVl#TH$Oc4j1X%DUNW);wn>3AbYY#(!#f>V`yT-N@?Sabq&m<2m28%@czMa^%fT
z*RO02Jy3mJa|6M%^0)M=O>kE?|En3FbPmjwo)|t~9a*Qf20L=++M_?7Rm-utkCB^S
zH%MP4hh7AL6pM`gMi(CP+V-8OqA3*){CQUW))w`uTUIT;e@|7*8mombFs@JQKjF>l
z+SEr|)9f`e6VPr4GcA>4*1i4nMN8fl6yly~Hf?VzD@wJt1Je}VJ!o7}1J`6E+fj%)
zRSW&r$?R4I4^vt5-xxrRSPM3G&Tt;_5m3Y*?ddIaMJYy??AlAZaEmXVk)Z(fgwZ2a
zgMZ0ih2Qk%9?{Vq>>GPoC!Pcs<y=Nyr>sMLGJMl7Bhb9iG-#<7wI=q_rQ<c~&TxM<
z5!m`(R|y8wgcDvU!DX1L?hPI33Jqf)%jg9?2ktbjUMJlJ%H$bVhT(JO!Y*}e$nHU{
z%wigJSg2`T9FI;3alG~Dc4%Lp{jfoWxa%k#M(wmvIM<t5XUgffBbzbvrDn?Lu7?^I
zZx;t|4D2h5-2S#%s3~p@Sy;TUw)pX5DF6p@V^72=@(@i+k1$&+2Xcv}cl3V&BOE??
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,310 @@
+/* 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;
+  color: inherit;
+}
+
+#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;
+  color: inherit;
+}
+
+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
@@ -45,16 +45,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)
@@ -219,16 +223,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
@@ -69,16 +69,17 @@ skip-if = os == "android"
 [include:toolkit/components/url-classifier/tests/unit/xpcshell.ini]
 [include:services/common/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
@@ -321,17 +321,17 @@ nsDownloadManager::GetMemoryDBConnection
 
 void
 nsDownloadManager::CloseDB()
 {
   DebugOnly<nsresult> rv = mGetIdsForURIStatement->Finalize();
   MOZ_ASSERT(NS_SUCCEEDED(rv));
   rv = mUpdateDownloadStatement->Finalize();
   MOZ_ASSERT(NS_SUCCEEDED(rv));
-  rv = mDBConn->Close();
+  rv = mDBConn->AsyncClose(nsnull);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 }
 
 nsresult
 nsDownloadManager::InitMemoryDB()
 {
   bool ready = false;
   if (mDBConn && NS_SUCCEEDED(mDBConn->GetConnectionReady(&ready)) && ready)
@@ -894,16 +894,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);
@@ -928,16 +933,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;
@@ -1836,16 +1854,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
--- 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)
@@ -81,16 +83,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"].
@@ -139,16 +144,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