summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/downloads/content
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/downloads/content')
-rw-r--r--toolkit/mozapps/downloads/content/DownloadProgressListener.js117
-rw-r--r--toolkit/mozapps/downloads/content/download.xml327
-rw-r--r--toolkit/mozapps/downloads/content/downloads.css50
-rw-r--r--toolkit/mozapps/downloads/content/downloads.js1320
-rw-r--r--toolkit/mozapps/downloads/content/downloads.xul164
-rw-r--r--toolkit/mozapps/downloads/content/unknownContentType.xul107
6 files changed, 2085 insertions, 0 deletions
diff --git a/toolkit/mozapps/downloads/content/DownloadProgressListener.js b/toolkit/mozapps/downloads/content/DownloadProgressListener.js
new file mode 100644
index 000000000..ab349baf2
--- /dev/null
+++ b/toolkit/mozapps/downloads/content/DownloadProgressListener.js
@@ -0,0 +1,117 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+
+/* 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/. */
+
+/**
+ * DownloadProgressListener "class" is used to help update download items shown
+ * in the Download Manager UI such as displaying amount transferred, transfer
+ * rate, and time left for each download.
+ *
+ * This class implements the nsIDownloadProgressListener interface.
+ */
+function DownloadProgressListener() {}
+
+DownloadProgressListener.prototype = {
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadProgressListener]),
+
+ // nsIDownloadProgressListener
+
+ onDownloadStateChange: function dlPL_onDownloadStateChange(aState, aDownload)
+ {
+ // Update window title in-case we don't get all progress notifications
+ onUpdateProgress();
+
+ let dl;
+ let state = aDownload.state;
+ switch (state) {
+ case nsIDM.DOWNLOAD_QUEUED:
+ prependList(aDownload);
+ break;
+
+ case nsIDM.DOWNLOAD_BLOCKED_POLICY:
+ prependList(aDownload);
+ // Should fall through, this is a final state but DOWNLOAD_QUEUED
+ // is skipped. See nsDownloadManager::AddDownload.
+ case nsIDM.DOWNLOAD_FAILED:
+ case nsIDM.DOWNLOAD_CANCELED:
+ case nsIDM.DOWNLOAD_BLOCKED_PARENTAL:
+ case nsIDM.DOWNLOAD_DIRTY:
+ case nsIDM.DOWNLOAD_FINISHED:
+ downloadCompleted(aDownload);
+ if (state == nsIDM.DOWNLOAD_FINISHED)
+ autoRemoveAndClose(aDownload);
+ break;
+ case nsIDM.DOWNLOAD_DOWNLOADING: {
+ dl = getDownload(aDownload.id);
+
+ // At this point, we know if we are an indeterminate download or not
+ dl.setAttribute("progressmode", aDownload.percentComplete == -1 ?
+ "undetermined" : "normal");
+
+ // As well as knowing the referrer
+ let referrer = aDownload.referrer;
+ if (referrer)
+ dl.setAttribute("referrer", referrer.spec);
+
+ break;
+ }
+ }
+
+ // autoRemoveAndClose could have already closed our window...
+ try {
+ if (!dl)
+ dl = getDownload(aDownload.id);
+
+ // Update to the new state
+ dl.setAttribute("state", state);
+
+ // Update ui text values after switching states
+ updateTime(dl);
+ updateStatus(dl);
+ updateButtons(dl);
+ } catch (e) { }
+ },
+
+ onProgressChange: function dlPL_onProgressChange(aWebProgress, aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress, aDownload)
+ {
+ var download = getDownload(aDownload.id);
+
+ // Update this download's progressmeter
+ if (aDownload.percentComplete != -1) {
+ download.setAttribute("progress", aDownload.percentComplete);
+
+ // Dispatch ValueChange for a11y
+ let event = document.createEvent("Events");
+ event.initEvent("ValueChange", true, true);
+ document.getAnonymousElementByAttribute(download, "anonid", "progressmeter")
+ .dispatchEvent(event);
+ }
+
+ // Update the progress so the status can be correctly updated
+ download.setAttribute("currBytes", aDownload.amountTransferred);
+ download.setAttribute("maxBytes", aDownload.size);
+
+ // Update the rest of the UI (bytes transferred, bytes total, download rate,
+ // time remaining).
+ updateStatus(download, aDownload);
+
+ // Update window title
+ onUpdateProgress();
+ },
+
+ onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload)
+ {
+ },
+
+ onSecurityChange: function(aWebProgress, aRequest, aState, aDownload)
+ {
+ }
+};
diff --git a/toolkit/mozapps/downloads/content/download.xml b/toolkit/mozapps/downloads/content/download.xml
new file mode 100644
index 000000000..1d4b87270
--- /dev/null
+++ b/toolkit/mozapps/downloads/content/download.xml
@@ -0,0 +1,327 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE bindings [
+ <!ENTITY % downloadDTD SYSTEM "chrome://mozapps/locale/downloads/downloads.dtd" >
+ %downloadDTD;
+]>
+
+<bindings id="downloadBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="download-base" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <resources>
+ <stylesheet src="chrome://mozapps/skin/downloads/downloads.css"/>
+ </resources>
+ <implementation>
+ <property name="paused">
+ <getter>
+ <![CDATA[
+ return parseInt(this.getAttribute("state")) == Components.interfaces.nsIDownloadManager.DOWNLOAD_PAUSED;
+ ]]>
+ </getter>
+ </property>
+ <property name="openable">
+ <getter>
+ <![CDATA[
+ return parseInt(this.getAttribute("state")) == Components.interfaces.nsIDownloadManager.DOWNLOAD_FINISHED;
+ ]]>
+ </getter>
+ </property>
+ <property name="inProgress">
+ <getter>
+ <![CDATA[
+ var state = parseInt(this.getAttribute("state"));
+ const dl = Components.interfaces.nsIDownloadManager;
+ return state == dl.DOWNLOAD_NOTSTARTED ||
+ state == dl.DOWNLOAD_QUEUED ||
+ state == dl.DOWNLOAD_DOWNLOADING ||
+ state == dl.DOWNLOAD_PAUSED ||
+ state == dl.DOWNLOAD_SCANNING;
+ ]]>
+ </getter>
+ </property>
+ <property name="removable">
+ <getter>
+ <![CDATA[
+ var state = parseInt(this.getAttribute("state"));
+ const dl = Components.interfaces.nsIDownloadManager;
+ return state == dl.DOWNLOAD_FINISHED ||
+ state == dl.DOWNLOAD_CANCELED ||
+ state == dl.DOWNLOAD_BLOCKED_PARENTAL ||
+ state == dl.DOWNLOAD_BLOCKED_POLICY ||
+ state == dl.DOWNLOAD_DIRTY ||
+ state == dl.DOWNLOAD_FAILED;
+ ]]>
+ </getter>
+ </property>
+ <property name="buttons">
+ <getter>
+ <![CDATA[
+ var startEl = document.getAnonymousNodes(this);
+ if (!startEl.length)
+ startEl = [this];
+
+ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ return startEl[0].getElementsByTagNameNS(XULNS, "button");
+ ]]>
+ </getter>
+ </property>
+ </implementation>
+ </binding>
+
+ <binding id="download-starting" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon" validate="always"
+ xbl:inherits="src=image"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" class="name"/>
+ <xul:progressmeter mode="normal" value="0" flex="1"
+ anonid="progressmeter"/>
+ <xul:label value="&starting.label;" class="status"/>
+ <xul:spacer flex="1"/>
+ </xul:vbox>
+ <xul:vbox pack="center">
+ <xul:button class="cancel mini-button" tooltiptext="&cmd.cancel.label;"
+ cmd="cmd_cancel" ondblclick="event.stopPropagation();"
+ oncommand="performCommand('cmd_cancel', this);"/>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="download-downloading" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1" class="downloadContentBox">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon" validate="always"
+ xbl:inherits="src=image"/>
+ </xul:vbox>
+ <xul:vbox flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" flex="2" class="name"/>
+ <xul:hbox>
+ <xul:vbox flex="1">
+ <xul:progressmeter mode="normal" value="0" flex="1"
+ anonid="progressmeter"
+ xbl:inherits="value=progress,mode=progressmode"/>
+ </xul:vbox>
+ <xul:button class="pause mini-button" tooltiptext="&cmd.pause.label;"
+ cmd="cmd_pause" ondblclick="event.stopPropagation();"
+ oncommand="performCommand('cmd_pause', this);"/>
+ <xul:button class="cancel mini-button" tooltiptext="&cmd.cancel.label;"
+ cmd="cmd_cancel" ondblclick="event.stopPropagation();"
+ oncommand="performCommand('cmd_cancel', this);"/>
+ </xul:hbox>
+ <xul:label xbl:inherits="value=status,tooltiptext=statusTip" flex="1"
+ crop="right" class="status"/>
+ <xul:spacer flex="1"/>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="download-paused" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon" validate="always"
+ xbl:inherits="src=image"/>
+ </xul:vbox>
+ <xul:vbox flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" flex="2" class="name"/>
+ <xul:hbox>
+ <xul:vbox flex="1">
+ <xul:progressmeter mode="normal" value="0" flex="1"
+ anonid="progressmeter"
+ xbl:inherits="value=progress,mode=progressmode"/>
+ </xul:vbox>
+ <xul:button class="resume mini-button" tooltiptext="&cmd.resume.label;"
+ cmd="cmd_resume" ondblclick="event.stopPropagation();"
+ oncommand="performCommand('cmd_resume', this);"/>
+ <xul:button class="cancel mini-button" tooltiptext="&cmd.cancel.label;"
+ cmd="cmd_cancel" ondblclick="event.stopPropagation();"
+ oncommand="performCommand('cmd_cancel', this);"/>
+ </xul:hbox>
+ <xul:label xbl:inherits="value=status,tooltiptext=statusTip" flex="1"
+ crop="right" class="status"/>
+ <xul:spacer flex="1"/>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="download-done" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon" validate="always"
+ xbl:inherits="src=image"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" flex="1" class="name"/>
+ <xul:label xbl:inherits="value=dateTime,tooltiptext=dateTimeTip"
+ class="dateTime"/>
+ </xul:hbox>
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=status,tooltiptext=statusTip"
+ crop="end" flex="1" class="status"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="download-canceled" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon" validate="always"
+ xbl:inherits="src=image"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" flex="1" class="name"/>
+ <xul:label xbl:inherits="value=dateTime,tooltiptext=dateTimeTip"
+ class="dateTime"/>
+ </xul:hbox>
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=status,tooltiptext=statusTip"
+ crop="end" flex="1" class="status"/>
+ <xul:button class="retry mini-button" tooltiptext="&cmd.retry.label;"
+ cmd="cmd_retry" ondblclick="event.stopPropagation();"
+ oncommand="performCommand('cmd_retry', this);"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="download-failed" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon" validate="always"
+ xbl:inherits="src=image"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" flex="1" class="name"/>
+ <xul:label xbl:inherits="value=dateTime,tooltiptext=dateTimeTip"
+ class="dateTime"/>
+ </xul:hbox>
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=status,tooltiptext=statusTip"
+ crop="end" flex="1" class="status"/>
+ <xul:button class="retry mini-button" tooltiptext="&cmd.retry.label;"
+ cmd="cmd_retry" ondblclick="event.stopPropagation();"
+ oncommand="performCommand('cmd_retry', this);"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="download-blocked-parental" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon blockedIcon"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" flex="1" class="name"/>
+ <xul:label xbl:inherits="value=dateTime,tooltiptext=dateTimeTip"
+ class="dateTime"/>
+ </xul:hbox>
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=status,tooltiptext=statusTip"
+ crop="end" flex="1" class="status"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="download-blocked-policy" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon blockedIcon"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" flex="1" class="name"/>
+ <xul:label xbl:inherits="value=dateTime,tooltiptext=dateTimeTip"
+ class="dateTime"/>
+ </xul:hbox>
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=status,tooltiptext=statusTip"
+ crop="end" flex="1" class="status"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="download-scanning" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon" validate="always"
+ xbl:inherits="src=image"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" flex="2" class="name"/>
+ <xul:hbox>
+ <xul:vbox flex="1">
+ <xul:progressmeter mode="undetermined" flex="1" />
+ </xul:vbox>
+ </xul:hbox>
+ <xul:label value="&scanning.label;" class="status"/>
+ <xul:spacer flex="1"/>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="download-dirty" extends="chrome://mozapps/content/downloads/download.xml#download-base">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="center">
+ <xul:image class="downloadTypeIcon blockedIcon"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=target,tooltiptext=target"
+ crop="center" flex="1" class="name"/>
+ <xul:label xbl:inherits="value=dateTime,tooltiptext=dateTimeTip"
+ class="dateTime"/>
+ </xul:hbox>
+ <xul:hbox align="center" flex="1">
+ <xul:label xbl:inherits="value=status,tooltiptext=statusTip"
+ crop="end" flex="1" class="status"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+</bindings>
diff --git a/toolkit/mozapps/downloads/content/downloads.css b/toolkit/mozapps/downloads/content/downloads.css
new file mode 100644
index 000000000..dcb648d62
--- /dev/null
+++ b/toolkit/mozapps/downloads/content/downloads.css
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+richlistitem[type="download"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-starting');
+ -moz-box-orient: vertical;
+}
+
+richlistitem[type="download"][state="0"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-downloading');
+}
+
+richlistitem[type="download"][state="1"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-done');
+}
+
+richlistitem[type="download"][state="2"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-failed');
+}
+
+richlistitem[type="download"][state="3"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-canceled');
+}
+
+richlistitem[type="download"][state="4"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-paused');
+}
+
+richlistitem[type="download"][state="6"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-blocked-parental');
+}
+
+richlistitem[type="download"][state="7"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-scanning');
+}
+
+richlistitem[type="download"][state="8"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-dirty');
+}
+
+richlistitem[type="download"][state="9"] {
+ -moz-binding: url('chrome://mozapps/content/downloads/download.xml#download-blocked-policy');
+}
+
+/* Only focus buttons in the selected item*/
+richlistitem[type="download"]:not([selected="true"]) button {
+ -moz-user-focus: none;
+}
+
diff --git a/toolkit/mozapps/downloads/content/downloads.js b/toolkit/mozapps/downloads/content/downloads.js
new file mode 100644
index 000000000..92a9f7593
--- /dev/null
+++ b/toolkit/mozapps/downloads/content/downloads.js
@@ -0,0 +1,1320 @@
+/* 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";
+
+// Globals
+
+const PREF_BDM_CLOSEWHENDONE = "browser.download.manager.closeWhenDone";
+const PREF_BDM_ALERTONEXEOPEN = "browser.download.manager.alertOnEXEOpen";
+const PREF_BDM_SCANWHENDONE = "browser.download.manager.scanWhenDone";
+
+const nsLocalFile = Components.Constructor("@mozilla.org/file/local;1",
+ "nsILocalFile", "initWithPath");
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/DownloadUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+
+const nsIDM = Ci.nsIDownloadManager;
+
+var gDownloadManager = Cc["@mozilla.org/download-manager;1"].getService(nsIDM);
+var gDownloadManagerUI = Cc["@mozilla.org/download-manager-ui;1"].
+ getService(Ci.nsIDownloadManagerUI);
+
+var gDownloadListener = null;
+var gDownloadsView = null;
+var gSearchBox = null;
+var gSearchTerms = [];
+var gBuilder = 0;
+
+// This variable is used when performing commands on download items and gives
+// the command the ability to do something after all items have been operated
+// on. The following convention is used to handle the value of the variable:
+// whenever we aren't performing a command, the value is |undefined|; just
+// before executing commands, the value will be set to |null|; and when
+// commands want to create a callback, they set the value to be a callback
+// function to be executed after all download items have been visited.
+var gPerformAllCallback;
+
+// Control the performance of the incremental list building by setting how many
+// milliseconds to wait before building more of the list and how many items to
+// add between each delay.
+const gListBuildDelay = 300;
+const gListBuildChunk = 3;
+
+// Array of download richlistitem attributes to check when searching
+const gSearchAttributes = [
+ "target",
+ "status",
+ "dateTime",
+];
+
+// If the user has interacted with the window in a significant way, we should
+// not auto-close the window. Tough UI decisions about what is "significant."
+var gUserInteracted = false;
+
+// These strings will be converted to the corresponding ones from the string
+// bundle on startup.
+var gStr = {
+ paused: "paused",
+ cannotPause: "cannotPause",
+ doneStatus: "doneStatus",
+ doneSize: "doneSize",
+ doneSizeUnknown: "doneSizeUnknown",
+ stateFailed: "stateFailed",
+ stateCanceled: "stateCanceled",
+ stateBlockedParentalControls: "stateBlocked",
+ stateBlockedPolicy: "stateBlockedPolicy",
+ stateDirty: "stateDirty",
+ downloadsTitleFiles: "downloadsTitleFiles",
+ downloadsTitlePercent: "downloadsTitlePercent",
+ fileExecutableSecurityWarningTitle: "fileExecutableSecurityWarningTitle",
+ fileExecutableSecurityWarningDontAsk: "fileExecutableSecurityWarningDontAsk"
+};
+
+// The statement to query for downloads that are active or match the search
+var gStmt = null;
+
+// Start/Stop Observers
+
+function downloadCompleted(aDownload)
+{
+ // The download is changing state, so update the clear list button
+ updateClearListButton();
+
+ // Wrap this in try...catch since this can be called while shutting down...
+ // it doesn't really matter if it fails then since well.. we're shutting down
+ // and there's no UI to update!
+ try {
+ let dl = getDownload(aDownload.id);
+
+ // Update attributes now that we've finished
+ dl.setAttribute("startTime", Math.round(aDownload.startTime / 1000));
+ dl.setAttribute("endTime", Date.now());
+ dl.setAttribute("currBytes", aDownload.amountTransferred);
+ dl.setAttribute("maxBytes", aDownload.size);
+
+ // Move the download below active if it should stay in the list
+ if (downloadMatchesSearch(dl)) {
+ // Iterate down until we find a non-active download
+ let next = dl.nextSibling;
+ while (next && next.inProgress)
+ next = next.nextSibling;
+
+ // Move the item
+ gDownloadsView.insertBefore(dl, next);
+ } else {
+ removeFromView(dl);
+ }
+
+ // getTypeFromFile fails if it can't find a type for this file.
+ try {
+ // Refresh the icon, so that executable icons are shown.
+ var mimeService = Cc["@mozilla.org/mime;1"].
+ getService(Ci.nsIMIMEService);
+ var contentType = mimeService.getTypeFromFile(aDownload.targetFile);
+
+ var listItem = getDownload(aDownload.id)
+ var oldImage = listItem.getAttribute("image");
+ // Tacking on contentType bypasses cache
+ listItem.setAttribute("image", oldImage + "&contentType=" + contentType);
+ } catch (e) { }
+
+ if (gDownloadManager.activeDownloadCount == 0)
+ document.title = document.documentElement.getAttribute("statictitle");
+
+ gDownloadManagerUI.getAttention();
+ }
+ catch (e) { }
+}
+
+function autoRemoveAndClose(aDownload)
+{
+ var pref = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+
+ if (gDownloadManager.activeDownloadCount == 0) {
+ // For the moment, just use the simple heuristic that if this window was
+ // opened by the download process, rather than by the user, it should
+ // auto-close if the pref is set that way. If the user opened it themselves,
+ // it should not close until they explicitly close it. Additionally, the
+ // preference to control the feature may not be set, so defaulting to
+ // keeping the window open.
+ let autoClose = false;
+ try {
+ autoClose = pref.getBoolPref(PREF_BDM_CLOSEWHENDONE);
+ } catch (e) { }
+ var autoOpened =
+ !window.opener || window.opener.location.href == window.location.href;
+ if (autoClose && autoOpened && !gUserInteracted) {
+ gCloseDownloadManager();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// This function can be overwritten by extensions that wish to place the
+// Download Window in another part of the UI.
+function gCloseDownloadManager()
+{
+ window.close();
+}
+
+// Download Event Handlers
+
+function cancelDownload(aDownload)
+{
+ gDownloadManager.cancelDownload(aDownload.getAttribute("dlid"));
+
+ // XXXben -
+ // If we got here because we resumed the download, we weren't using a temp file
+ // because we used saveURL instead. (this is because the proper download mechanism
+ // employed by the helper app service isn't fully accessible yet... should be fixed...
+ // talk to bz...)
+ // the upshot is we have to delete the file if it exists.
+ var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file"));
+
+ if (f.exists())
+ f.remove(false);
+}
+
+function pauseDownload(aDownload)
+{
+ var id = aDownload.getAttribute("dlid");
+ gDownloadManager.pauseDownload(id);
+}
+
+function resumeDownload(aDownload)
+{
+ gDownloadManager.resumeDownload(aDownload.getAttribute("dlid"));
+}
+
+function removeDownload(aDownload)
+{
+ gDownloadManager.removeDownload(aDownload.getAttribute("dlid"));
+}
+
+function retryDownload(aDownload)
+{
+ removeFromView(aDownload);
+ gDownloadManager.retryDownload(aDownload.getAttribute("dlid"));
+}
+
+function showDownload(aDownload)
+{
+ var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file"));
+
+ try {
+ // Show the directory containing the file and select the file
+ f.reveal();
+ } catch (e) {
+ // 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 = f.parent.QueryInterface(Ci.nsILocalFile);
+ if (!parent)
+ return;
+
+ try {
+ // "Double click" the parent directory to show where the file should be
+ parent.launch();
+ } catch (e) {
+ // If launch also fails (probably because it's not implemented), let the
+ // OS handler try to open the parent
+ openExternal(parent);
+ }
+ }
+}
+
+function onDownloadDblClick(aEvent)
+{
+ // Only do the default action for double primary clicks
+ if (aEvent.button == 0 && aEvent.target.selected)
+ doDefaultForSelected();
+}
+
+function openDownload(aDownload)
+{
+ var f = getLocalFileFromNativePathOrUrl(aDownload.getAttribute("file"));
+ if (f.isExecutable()) {
+ var dontAsk = false;
+ var pref = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ try {
+ dontAsk = !pref.getBoolPref(PREF_BDM_ALERTONEXEOPEN);
+ } catch (e) { }
+
+ if (AppConstants.platform == "win") {
+ // On Vista and above, we rely on native security prompting for
+ // downloaded content unless it's disabled.
+ try {
+ var sysInfo = Cc["@mozilla.org/system-info;1"].
+ getService(Ci.nsIPropertyBag2);
+ if (parseFloat(sysInfo.getProperty("version")) >= 6 &&
+ pref.getBoolPref(PREF_BDM_SCANWHENDONE)) {
+ dontAsk = true;
+ }
+ } catch (ex) { }
+ }
+
+ if (!dontAsk) {
+ var strings = document.getElementById("downloadStrings");
+ var name = aDownload.getAttribute("target");
+ var message = strings.getFormattedString("fileExecutableSecurityWarning", [name, name]);
+
+ let title = gStr.fileExecutableSecurityWarningTitle;
+ let dontAsk = gStr.fileExecutableSecurityWarningDontAsk;
+
+ var promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Ci.nsIPromptService);
+ var checkbox = { value: false };
+ var open = promptSvc.confirmCheck(window, title, message, dontAsk, checkbox);
+
+ if (!open)
+ return;
+ pref.setBoolPref(PREF_BDM_ALERTONEXEOPEN, !checkbox.value);
+ }
+ }
+ try {
+ try {
+ let download = gDownloadManager.getDownload(aDownload.getAttribute("dlid"));
+ let mimeInfo = download.MIMEInfo;
+ if (mimeInfo.preferredAction == mimeInfo.useHelperApp) {
+ mimeInfo.launchWithFile(f);
+ return;
+ }
+ } catch (ex) {
+ }
+ f.launch();
+ } catch (ex) {
+ // if launch fails, try sending it through the system's external
+ // file: URL handler
+ openExternal(f);
+ }
+}
+
+function openReferrer(aDownload)
+{
+ openURL(getReferrerOrSource(aDownload));
+}
+
+function copySourceLocation(aDownload)
+{
+ var uri = aDownload.getAttribute("uri");
+ var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+
+ // Check if we should initialize a callback
+ if (gPerformAllCallback === null) {
+ let uris = [];
+ gPerformAllCallback = aURI => aURI ? uris.push(aURI) :
+ clipboard.copyString(uris.join("\n"));
+ }
+
+ // We have a callback to use, so use it to add a uri
+ if (typeof gPerformAllCallback == "function")
+ gPerformAllCallback(uri);
+ else {
+ // It's a plain copy source, so copy it
+ clipboard.copyString(uri);
+ }
+}
+
+/**
+ * Remove the currently shown downloads from the download list.
+ */
+function clearDownloadList() {
+ // Clear the whole list if there's no search
+ if (gSearchTerms == "") {
+ gDownloadManager.cleanUp();
+ return;
+ }
+
+ // Remove each download starting from the end until we hit a download
+ // that is in progress
+ let item;
+ while ((item = gDownloadsView.lastChild) && !item.inProgress)
+ removeDownload(item);
+
+ // Clear the input as if the user did it and move focus to the list
+ gSearchBox.value = "";
+ gSearchBox.doCommand();
+ gDownloadsView.focus();
+}
+
+// This is called by the progress listener.
+var gLastComputedMean = -1;
+var gLastActiveDownloads = 0;
+function onUpdateProgress()
+{
+ let numActiveDownloads = gDownloadManager.activeDownloadCount;
+
+ // Use the default title and reset "last" values if there's no downloads
+ if (numActiveDownloads == 0) {
+ document.title = document.documentElement.getAttribute("statictitle");
+ gLastComputedMean = -1;
+ gLastActiveDownloads = 0;
+
+ return;
+ }
+
+ // Establish the mean transfer speed and amount downloaded.
+ var mean = 0;
+ var base = 0;
+ var dls = gDownloadManager.activeDownloads;
+ while (dls.hasMoreElements()) {
+ let dl = dls.getNext();
+ if (dl.percentComplete < 100 && dl.size > 0) {
+ mean += dl.amountTransferred;
+ base += dl.size;
+ }
+ }
+
+ // Calculate the percent transferred, unless we don't have a total file size
+ let title = gStr.downloadsTitlePercent;
+ if (base == 0)
+ title = gStr.downloadsTitleFiles;
+ else
+ mean = Math.floor((mean / base) * 100);
+
+ // Update title of window
+ if (mean != gLastComputedMean || gLastActiveDownloads != numActiveDownloads) {
+ gLastComputedMean = mean;
+ gLastActiveDownloads = numActiveDownloads;
+
+ // Get the correct plural form and insert number of downloads and percent
+ title = PluralForm.get(numActiveDownloads, title);
+ title = replaceInsert(title, 1, numActiveDownloads);
+ title = replaceInsert(title, 2, mean);
+
+ document.title = title;
+ }
+}
+
+// Startup, Shutdown
+
+function Startup()
+{
+ gDownloadsView = document.getElementById("downloadView");
+ gSearchBox = document.getElementById("searchbox");
+
+ // convert strings to those in the string bundle
+ let sb = document.getElementById("downloadStrings");
+ let getStr = string => sb.getString(string);
+ for (let [name, value] of Object.entries(gStr))
+ gStr[name] = typeof value == "string" ? getStr(value) : value.map(getStr);
+
+ initStatement();
+ buildDownloadList(true);
+
+ // The DownloadProgressListener (DownloadProgressListener.js) handles progress
+ // notifications.
+ gDownloadListener = new DownloadProgressListener();
+ gDownloadManager.addListener(gDownloadListener);
+
+ // If the UI was displayed because the user interacted, we need to make sure
+ // we update gUserInteracted accordingly.
+ if (window.arguments[1] == Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED)
+ gUserInteracted = true;
+
+ // downloads can finish before Startup() does, so check if the window should
+ // close and act accordingly
+ if (!autoRemoveAndClose())
+ gDownloadsView.focus();
+
+ let obs = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ obs.addObserver(gDownloadObserver, "download-manager-remove-download", false);
+ obs.addObserver(gDownloadObserver, "browser-lastwindow-close-granted", false);
+
+ // Clear the search box and move focus to the list on escape from the box
+ gSearchBox.addEventListener("keypress", function(e) {
+ if (e.keyCode == e.DOM_VK_ESCAPE) {
+ // Move focus to the list instead of closing the window
+ gDownloadsView.focus();
+ e.preventDefault();
+ }
+ }, false);
+
+ let DownloadTaskbarProgress =
+ Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress;
+ DownloadTaskbarProgress.onDownloadWindowLoad(window);
+}
+
+function Shutdown()
+{
+ gDownloadManager.removeListener(gDownloadListener);
+
+ let obs = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ obs.removeObserver(gDownloadObserver, "download-manager-remove-download");
+ obs.removeObserver(gDownloadObserver, "browser-lastwindow-close-granted");
+
+ clearTimeout(gBuilder);
+ gStmt.reset();
+ gStmt.finalize();
+}
+
+var gDownloadObserver = {
+ observe: function gdo_observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "download-manager-remove-download":
+ // A null subject here indicates "remove multiple", so we just rebuild.
+ if (!aSubject) {
+ // Rebuild the default view
+ buildDownloadList(true);
+ break;
+ }
+
+ // Otherwise, remove a single download
+ let id = aSubject.QueryInterface(Ci.nsISupportsPRUint32);
+ let dl = getDownload(id.data);
+ removeFromView(dl);
+ break;
+ case "browser-lastwindow-close-granted":
+ if (AppConstants.platform != "macosx" &&
+ gDownloadManager.activeDownloadCount == 0) {
+ setTimeout(gCloseDownloadManager, 0);
+ }
+ break;
+ }
+ }
+};
+
+// View Context Menus
+
+var gContextMenus = [
+ // DOWNLOAD_DOWNLOADING
+ [
+ "menuitem_pause"
+ , "menuitem_cancel"
+ , "menuseparator"
+ , "menuitem_show"
+ , "menuseparator"
+ , "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ ],
+ // DOWNLOAD_FINISHED
+ [
+ "menuitem_open"
+ , "menuitem_show"
+ , "menuseparator"
+ , "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ , "menuseparator"
+ , "menuitem_removeFromList"
+ ],
+ // DOWNLOAD_FAILED
+ [
+ "menuitem_retry"
+ , "menuseparator"
+ , "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ , "menuseparator"
+ , "menuitem_removeFromList"
+ ],
+ // DOWNLOAD_CANCELED
+ [
+ "menuitem_retry"
+ , "menuseparator"
+ , "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ , "menuseparator"
+ , "menuitem_removeFromList"
+ ],
+ // DOWNLOAD_PAUSED
+ [
+ "menuitem_resume"
+ , "menuitem_cancel"
+ , "menuseparator"
+ , "menuitem_show"
+ , "menuseparator"
+ , "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ ],
+ // DOWNLOAD_QUEUED
+ [
+ "menuitem_cancel"
+ , "menuseparator"
+ , "menuitem_show"
+ , "menuseparator"
+ , "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ ],
+ // DOWNLOAD_BLOCKED_PARENTAL
+ [
+ "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ , "menuseparator"
+ , "menuitem_removeFromList"
+ ],
+ // DOWNLOAD_SCANNING
+ [
+ "menuitem_show"
+ , "menuseparator"
+ , "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ ],
+ // DOWNLOAD_DIRTY
+ [
+ "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ , "menuseparator"
+ , "menuitem_removeFromList"
+ ],
+ // DOWNLOAD_BLOCKED_POLICY
+ [
+ "menuitem_openReferrer"
+ , "menuitem_copyLocation"
+ , "menuseparator"
+ , "menuitem_selectAll"
+ , "menuseparator"
+ , "menuitem_removeFromList"
+ ]
+];
+
+function buildContextMenu(aEvent)
+{
+ if (aEvent.target.id != "downloadContextMenu")
+ return false;
+
+ var popup = document.getElementById("downloadContextMenu");
+ while (popup.hasChildNodes())
+ popup.removeChild(popup.firstChild);
+
+ if (gDownloadsView.selectedItem) {
+ let dl = gDownloadsView.selectedItem;
+ let idx = parseInt(dl.getAttribute("state"));
+ if (idx < 0)
+ idx = 0;
+
+ var menus = gContextMenus[idx];
+ for (let i = 0; i < menus.length; ++i) {
+ let menuitem = document.getElementById(menus[i]).cloneNode(true);
+ let cmd = menuitem.getAttribute("cmd");
+ if (cmd)
+ menuitem.disabled = !gDownloadViewController.isCommandEnabled(cmd, dl);
+
+ popup.appendChild(menuitem);
+ }
+
+ return true;
+ }
+
+ return false;
+}
+// Drag and Drop
+var gDownloadDNDObserver =
+{
+ onDragStart: function (aEvent)
+ {
+ if (!gDownloadsView.selectedItem)
+ return;
+ var dl = gDownloadsView.selectedItem;
+ var f = getLocalFileFromNativePathOrUrl(dl.getAttribute("file"));
+ if (!f.exists())
+ return;
+
+ var dt = aEvent.dataTransfer;
+ dt.mozSetDataAt("application/x-moz-file", f, 0);
+ var url = Services.io.newFileURI(f).spec;
+ dt.setData("text/uri-list", url);
+ dt.setData("text/plain", url);
+ dt.effectAllowed = "copyMove";
+ dt.addElement(dl);
+ },
+
+ onDragOver: function (aEvent)
+ {
+ var types = aEvent.dataTransfer.types;
+ if (types.includes("text/uri-list") ||
+ types.includes("text/x-moz-url") ||
+ types.includes("text/plain"))
+ aEvent.preventDefault();
+ },
+
+ onDrop: function(aEvent)
+ {
+ var dt = aEvent.dataTransfer;
+ // If dragged item is from our source, do not try to
+ // redownload already downloaded file.
+ if (dt.mozGetDataAt("application/x-moz-file", 0))
+ return;
+
+ var url = dt.getData("URL");
+ var name;
+ if (!url) {
+ url = dt.getData("text/x-moz-url") || dt.getData("text/plain");
+ [url, name] = url.split("\n");
+ }
+ if (url) {
+ let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document;
+ saveURL(url, name ? name : url, null, true, true, null, sourceDoc);
+ }
+ }
+}
+
+function pasteHandler() {
+ let trans = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ trans.init(null);
+ let flavors = ["text/x-moz-url", "text/unicode"];
+ flavors.forEach(trans.addDataFlavor);
+
+ Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard);
+
+ // Getting the data or creating the nsIURI might fail
+ try {
+ let data = {};
+ trans.getAnyTransferData({}, data, {});
+ let [url, name] = data.value.QueryInterface(Ci.nsISupportsString).data.split("\n");
+
+ if (!url)
+ return;
+
+ let uri = Services.io.newURI(url, null, null);
+
+ saveURL(uri.spec, name || uri.spec, null, true, true, null, document);
+ } catch (ex) {}
+}
+
+// Command Updating and Command Handlers
+
+var gDownloadViewController = {
+ isCommandEnabled: function(aCommand, aItem)
+ {
+ let dl = aItem;
+ let download = null; // used for getting an nsIDownload object
+
+ switch (aCommand) {
+ case "cmd_cancel":
+ return dl.inProgress;
+ case "cmd_open": {
+ let file = getLocalFileFromNativePathOrUrl(dl.getAttribute("file"));
+ return dl.openable && file.exists();
+ }
+ case "cmd_show": {
+ let file = getLocalFileFromNativePathOrUrl(dl.getAttribute("file"));
+ return file.exists();
+ }
+ case "cmd_pause":
+ download = gDownloadManager.getDownload(dl.getAttribute("dlid"));
+ return dl.inProgress && !dl.paused && download.resumable;
+ case "cmd_pauseResume":
+ download = gDownloadManager.getDownload(dl.getAttribute("dlid"));
+ return (dl.inProgress || dl.paused) && download.resumable;
+ case "cmd_resume":
+ download = gDownloadManager.getDownload(dl.getAttribute("dlid"));
+ return dl.paused && download.resumable;
+ case "cmd_openReferrer":
+ return dl.hasAttribute("referrer");
+ case "cmd_removeFromList":
+ case "cmd_retry":
+ return dl.removable;
+ case "cmd_copyLocation":
+ return true;
+ }
+ return false;
+ },
+
+ doCommand: function(aCommand, aItem)
+ {
+ if (this.isCommandEnabled(aCommand, aItem))
+ this.commands[aCommand](aItem);
+ },
+
+ commands: {
+ cmd_cancel: function(aSelectedItem) {
+ cancelDownload(aSelectedItem);
+ },
+ cmd_open: function(aSelectedItem) {
+ openDownload(aSelectedItem);
+ },
+ cmd_openReferrer: function(aSelectedItem) {
+ openReferrer(aSelectedItem);
+ },
+ cmd_pause: function(aSelectedItem) {
+ pauseDownload(aSelectedItem);
+ },
+ cmd_pauseResume: function(aSelectedItem) {
+ if (aSelectedItem.paused)
+ this.cmd_resume(aSelectedItem);
+ else
+ this.cmd_pause(aSelectedItem);
+ },
+ cmd_removeFromList: function(aSelectedItem) {
+ removeDownload(aSelectedItem);
+ },
+ cmd_resume: function(aSelectedItem) {
+ resumeDownload(aSelectedItem);
+ },
+ cmd_retry: function(aSelectedItem) {
+ retryDownload(aSelectedItem);
+ },
+ cmd_show: function(aSelectedItem) {
+ showDownload(aSelectedItem);
+ },
+ cmd_copyLocation: function(aSelectedItem) {
+ copySourceLocation(aSelectedItem);
+ },
+ }
+};
+
+/**
+ * Helper function to do commands.
+ *
+ * @param aCmd
+ * The command to be performed.
+ * @param aItem
+ * The richlistitem that represents the download that will have the
+ * command performed on it. If this is null, the command is performed on
+ * all downloads. If the item passed in is not a richlistitem that
+ * represents a download, it will walk up the parent nodes until it finds
+ * a DOM node that is.
+ */
+function performCommand(aCmd, aItem)
+{
+ let elm = aItem;
+ if (!elm) {
+ // If we don't have a desired download item, do the command for all
+ // selected items. Initialize the callback to null so commands know to add
+ // a callback if they want. We will call the callback with empty arguments
+ // after performing the command on each selected download item.
+ gPerformAllCallback = null;
+
+ // Convert the nodelist into an array to keep a copy of the download items
+ let items = [];
+ for (let i = gDownloadsView.selectedItems.length; --i >= 0; )
+ items.unshift(gDownloadsView.selectedItems[i]);
+
+ // Do the command for each download item
+ for (let item of items)
+ performCommand(aCmd, item);
+
+ // Call the callback with no arguments and reset because we're done
+ if (typeof gPerformAllCallback == "function")
+ gPerformAllCallback();
+ gPerformAllCallback = undefined;
+
+ return;
+ }
+ while (elm.nodeName != "richlistitem" ||
+ elm.getAttribute("type") != "download") {
+ elm = elm.parentNode;
+ }
+
+ gDownloadViewController.doCommand(aCmd, elm);
+}
+
+function setSearchboxFocus()
+{
+ gSearchBox.focus();
+ gSearchBox.select();
+}
+
+function openExternal(aFile)
+{
+ var uri = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService).newFileURI(aFile);
+
+ var protocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+ protocolSvc.loadUrl(uri);
+
+ return;
+}
+
+// Utility Functions
+
+/**
+ * Create a download richlistitem with the provided attributes. Some attributes
+ * are *required* while optional ones will only be set on the item if provided.
+ *
+ * @param aAttrs
+ * An object that must have the following properties: dlid, file,
+ * target, uri, state, progress, startTime, endTime, currBytes,
+ * maxBytes; optional properties: referrer
+ * @return An initialized download richlistitem
+ */
+function createDownloadItem(aAttrs)
+{
+ let dl = document.createElement("richlistitem");
+
+ // Copy the attributes from the argument into the item
+ for (let attr in aAttrs)
+ dl.setAttribute(attr, aAttrs[attr]);
+
+ // Initialize other attributes
+ dl.setAttribute("type", "download");
+ dl.setAttribute("id", "dl" + aAttrs.dlid);
+ dl.setAttribute("image", "moz-icon://" + aAttrs.file + "?size=32");
+ dl.setAttribute("lastSeconds", Infinity);
+
+ // Initialize more complex attributes
+ updateTime(dl);
+ updateStatus(dl);
+
+ try {
+ let file = getLocalFileFromNativePathOrUrl(aAttrs.file);
+ dl.setAttribute("path", file.nativePath || file.path);
+ return dl;
+ } catch (e) {
+ // aFile might not be a file: url or a valid native path
+ // see bug #392386 for details
+ }
+ return null;
+}
+
+/**
+ * Updates the disabled state of the buttons of a downlaod.
+ *
+ * @param aItem
+ * The richlistitem representing the download.
+ */
+function updateButtons(aItem)
+{
+ let buttons = aItem.buttons;
+
+ for (let i = 0; i < buttons.length; ++i) {
+ let cmd = buttons[i].getAttribute("cmd");
+ let enabled = gDownloadViewController.isCommandEnabled(cmd, aItem);
+ buttons[i].disabled = !enabled;
+
+ if ("cmd_pause" == cmd && !enabled) {
+ // We need to add the tooltip indicating that the download cannot be
+ // paused now.
+ buttons[i].setAttribute("tooltiptext", gStr.cannotPause);
+ }
+ }
+}
+
+/**
+ * Updates the status for a download item depending on its state
+ *
+ * @param aItem
+ * The richlistitem that has various download attributes.
+ * @param aDownload
+ * The nsDownload from the backend. This is an optional parameter, but
+ * is useful for certain states such as DOWNLOADING.
+ */
+function updateStatus(aItem, aDownload) {
+ let status = "";
+ let statusTip = "";
+
+ let state = Number(aItem.getAttribute("state"));
+ switch (state) {
+ case nsIDM.DOWNLOAD_PAUSED:
+ {
+ let currBytes = Number(aItem.getAttribute("currBytes"));
+ let maxBytes = Number(aItem.getAttribute("maxBytes"));
+
+ let transfer = DownloadUtils.getTransferTotal(currBytes, maxBytes);
+ status = replaceInsert(gStr.paused, 1, transfer);
+
+ break;
+ }
+ case nsIDM.DOWNLOAD_DOWNLOADING:
+ {
+ let currBytes = Number(aItem.getAttribute("currBytes"));
+ let maxBytes = Number(aItem.getAttribute("maxBytes"));
+ // If we don't have an active download, assume 0 bytes/sec
+ let speed = aDownload ? aDownload.speed : 0;
+ let lastSec = Number(aItem.getAttribute("lastSeconds"));
+
+ let newLast;
+ [status, newLast] =
+ DownloadUtils.getDownloadStatus(currBytes, maxBytes, speed, lastSec);
+
+ // Update lastSeconds to be the new value
+ aItem.setAttribute("lastSeconds", newLast);
+
+ break;
+ }
+ case nsIDM.DOWNLOAD_FINISHED:
+ case nsIDM.DOWNLOAD_FAILED:
+ case nsIDM.DOWNLOAD_CANCELED:
+ case nsIDM.DOWNLOAD_BLOCKED_PARENTAL:
+ case nsIDM.DOWNLOAD_BLOCKED_POLICY:
+ case nsIDM.DOWNLOAD_DIRTY:
+ {
+ let stateSize = {};
+ stateSize[nsIDM.DOWNLOAD_FINISHED] = function() {
+ // Display the file size, but show "Unknown" for negative sizes
+ let fileSize = Number(aItem.getAttribute("maxBytes"));
+ let sizeText = gStr.doneSizeUnknown;
+ if (fileSize >= 0) {
+ let [size, unit] = DownloadUtils.convertByteUnits(fileSize);
+ sizeText = replaceInsert(gStr.doneSize, 1, size);
+ sizeText = replaceInsert(sizeText, 2, unit);
+ }
+ return sizeText;
+ };
+ stateSize[nsIDM.DOWNLOAD_FAILED] = () => gStr.stateFailed;
+ stateSize[nsIDM.DOWNLOAD_CANCELED] = () => gStr.stateCanceled;
+ stateSize[nsIDM.DOWNLOAD_BLOCKED_PARENTAL] = () => gStr.stateBlockedParentalControls;
+ stateSize[nsIDM.DOWNLOAD_BLOCKED_POLICY] = () => gStr.stateBlockedPolicy;
+ stateSize[nsIDM.DOWNLOAD_DIRTY] = () => gStr.stateDirty;
+
+ // Insert 1 is the download size or download state
+ status = replaceInsert(gStr.doneStatus, 1, stateSize[state]());
+
+ let [displayHost, fullHost] =
+ DownloadUtils.getURIHost(getReferrerOrSource(aItem));
+
+ // Insert 2 is the eTLD + 1 or other variations of the host
+ status = replaceInsert(status, 2, displayHost);
+ // Set the tooltip to be the full host
+ statusTip = fullHost;
+
+ break;
+ }
+ }
+
+ aItem.setAttribute("status", status);
+ aItem.setAttribute("statusTip", statusTip != "" ? statusTip : status);
+}
+
+/**
+ * Updates the time that gets shown for completed download items
+ *
+ * @param aItem
+ * The richlistitem representing a download in the UI
+ */
+function updateTime(aItem)
+{
+ // Don't bother updating for things that aren't finished
+ if (aItem.inProgress)
+ return;
+
+ let end = new Date(parseInt(aItem.getAttribute("endTime")));
+ let [dateCompact, dateComplete] = DownloadUtils.getReadableDates(end);
+ aItem.setAttribute("dateTime", dateCompact);
+ aItem.setAttribute("dateTimeTip", dateComplete);
+}
+
+/**
+ * Helper function to replace a placeholder string with a real string
+ *
+ * @param aText
+ * Source text containing placeholder (e.g., #1)
+ * @param aIndex
+ * Index number of placeholder to replace
+ * @param aValue
+ * New string to put in place of placeholder
+ * @return The string with placeholder replaced with the new string
+ */
+function replaceInsert(aText, aIndex, aValue)
+{
+ return aText.replace("#" + aIndex, aValue);
+}
+
+/**
+ * Perform the default action for the currently selected download item
+ */
+function doDefaultForSelected()
+{
+ // Make sure we have something selected
+ let item = gDownloadsView.selectedItem;
+ if (!item)
+ return;
+
+ // Get the default action (first item in the menu)
+ let state = Number(item.getAttribute("state"));
+ let menuitem = document.getElementById(gContextMenus[state][0]);
+
+ // Try to do the action if the command is enabled
+ gDownloadViewController.doCommand(menuitem.getAttribute("cmd"), item);
+}
+
+function removeFromView(aDownload)
+{
+ // Make sure we have an item to remove
+ if (!aDownload) return;
+
+ let index = gDownloadsView.selectedIndex;
+ gDownloadsView.removeChild(aDownload);
+ gDownloadsView.selectedIndex = Math.min(index, gDownloadsView.itemCount - 1);
+
+ // We might have removed the last item, so update the clear list button
+ updateClearListButton();
+}
+
+function getReferrerOrSource(aDownload)
+{
+ // Give the referrer if we have it set
+ if (aDownload.hasAttribute("referrer"))
+ return aDownload.getAttribute("referrer");
+
+ // Otherwise, provide the source
+ return aDownload.getAttribute("uri");
+}
+
+/**
+ * Initiate building the download list to have the active downloads followed by
+ * completed ones filtered by the search term if necessary.
+ *
+ * @param aForceBuild
+ * Force the list to be built even if the search terms don't change
+ */
+function buildDownloadList(aForceBuild)
+{
+ // Stringify the previous search
+ let prevSearch = gSearchTerms.join(" ");
+
+ // Array of space-separated lower-case search terms
+ gSearchTerms = gSearchBox.value.replace(/^\s+|\s+$/g, "").
+ toLowerCase().split(/\s+/);
+
+ // Unless forced, don't rebuild the download list if the search didn't change
+ if (!aForceBuild && gSearchTerms.join(" ") == prevSearch)
+ return;
+
+ // Clear out values before using them
+ clearTimeout(gBuilder);
+ gStmt.reset();
+
+ // Clear the list before adding items by replacing with a shallow copy
+ let empty = gDownloadsView.cloneNode(false);
+ gDownloadsView.parentNode.replaceChild(empty, gDownloadsView);
+ gDownloadsView = empty;
+
+ try {
+ gStmt.bindByIndex(0, nsIDM.DOWNLOAD_NOTSTARTED);
+ gStmt.bindByIndex(1, nsIDM.DOWNLOAD_DOWNLOADING);
+ gStmt.bindByIndex(2, nsIDM.DOWNLOAD_PAUSED);
+ gStmt.bindByIndex(3, nsIDM.DOWNLOAD_QUEUED);
+ gStmt.bindByIndex(4, nsIDM.DOWNLOAD_SCANNING);
+ } catch (e) {
+ // Something must have gone wrong when binding, so clear and quit
+ gStmt.reset();
+ return;
+ }
+
+ // Take a quick break before we actually start building the list
+ gBuilder = setTimeout(function() {
+ // Start building the list
+ stepListBuilder(1);
+
+ // We just tried to add a single item, so we probably need to enable
+ updateClearListButton();
+ }, 0);
+}
+
+/**
+ * Incrementally build the download list by adding at most the requested number
+ * of items if there are items to add. After doing that, it will schedule
+ * another chunk of items specified by gListBuildDelay and gListBuildChunk.
+ *
+ * @param aNumItems
+ * Number of items to add to the list before taking a break
+ */
+function stepListBuilder(aNumItems) {
+ try {
+ // If we're done adding all items, we can quit
+ if (!gStmt.executeStep()) {
+ // Send a notification that we finished, but wait for clear list to update
+ updateClearListButton();
+ setTimeout(() => Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService).
+ notifyObservers(window, "download-manager-ui-done", null), 0);
+
+ return;
+ }
+
+ // Try to get the attribute values from the statement
+ let attrs = {
+ dlid: gStmt.getInt64(0),
+ file: gStmt.getString(1),
+ target: gStmt.getString(2),
+ uri: gStmt.getString(3),
+ state: gStmt.getInt32(4),
+ startTime: Math.round(gStmt.getInt64(5) / 1000),
+ endTime: Math.round(gStmt.getInt64(6) / 1000),
+ currBytes: gStmt.getInt64(8),
+ maxBytes: gStmt.getInt64(9)
+ };
+
+ // Only add the referrer if it's not null
+ let referrer = gStmt.getString(7);
+ if (referrer)
+ attrs.referrer = referrer;
+
+ // If the download is active, grab the real progress, otherwise default 100
+ let isActive = gStmt.getInt32(10);
+ attrs.progress = isActive ? gDownloadManager.getDownload(attrs.dlid).
+ percentComplete : 100;
+
+ // Make the item and add it to the end if it's active or matches the search
+ let item = createDownloadItem(attrs);
+ if (item && (isActive || downloadMatchesSearch(item))) {
+ // Add item to the end
+ gDownloadsView.appendChild(item);
+
+ // Because of the joys of XBL, we can't update the buttons until the
+ // download object is in the document.
+ updateButtons(item);
+ } else {
+ // We didn't add an item, so bump up the number of items to process, but
+ // not a whole number so that we eventually do pause for a chunk break
+ aNumItems += .9;
+ }
+ } catch (e) {
+ // Something went wrong when stepping or getting values, so clear and quit
+ gStmt.reset();
+ return;
+ }
+
+ // Add another item to the list if we should; otherwise, let the UI update
+ // and continue later
+ if (aNumItems > 1) {
+ stepListBuilder(aNumItems - 1);
+ } else {
+ // Use a shorter delay for earlier downloads to display them faster
+ let delay = Math.min(gDownloadsView.itemCount * 10, gListBuildDelay);
+ gBuilder = setTimeout(stepListBuilder, delay, gListBuildChunk);
+ }
+}
+
+/**
+ * Add a download to the front of the download list
+ *
+ * @param aDownload
+ * The nsIDownload to make into a richlistitem
+ */
+function prependList(aDownload)
+{
+ let attrs = {
+ dlid: aDownload.id,
+ file: aDownload.target.spec,
+ target: aDownload.displayName,
+ uri: aDownload.source.spec,
+ state: aDownload.state,
+ progress: aDownload.percentComplete,
+ startTime: Math.round(aDownload.startTime / 1000),
+ endTime: Date.now(),
+ currBytes: aDownload.amountTransferred,
+ maxBytes: aDownload.size
+ };
+
+ // Make the item and add it to the beginning
+ let item = createDownloadItem(attrs);
+ if (item) {
+ // Add item to the beginning
+ gDownloadsView.insertBefore(item, gDownloadsView.firstChild);
+
+ // Because of the joys of XBL, we can't update the buttons until the
+ // download object is in the document.
+ updateButtons(item);
+
+ // We might have added an item to an empty list, so update button
+ updateClearListButton();
+ }
+}
+
+/**
+ * Check if the download matches the current search term based on the texts
+ * shown to the user. All search terms are checked to see if each matches any
+ * of the displayed texts.
+ *
+ * @param aItem
+ * Download richlistitem to check if it matches the current search
+ * @return Boolean true if it matches the search; false otherwise
+ */
+function downloadMatchesSearch(aItem)
+{
+ // Search through the download attributes that are shown to the user and
+ // make it into one big string for easy combined searching
+ let combinedSearch = "";
+ for (let attr of gSearchAttributes)
+ combinedSearch += aItem.getAttribute(attr).toLowerCase() + " ";
+
+ // Make sure each of the terms are found
+ for (let term of gSearchTerms)
+ if (combinedSearch.indexOf(term) == -1)
+ return false;
+
+ return true;
+}
+
+// we should be using real URLs all the time, but until
+// bug 239948 is fully fixed, this will do...
+//
+// note, this will thrown an exception if the native path
+// is not valid (for example a native Windows path on a Mac)
+// see bug #392386 for details
+function getLocalFileFromNativePathOrUrl(aPathOrUrl)
+{
+ if (aPathOrUrl.substring(0, 7) == "file://") {
+ // if this is a URL, get the file from that
+ let ioSvc = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ // XXX it's possible that using a null char-set here is bad
+ const fileUrl = ioSvc.newURI(aPathOrUrl, null, null).
+ QueryInterface(Ci.nsIFileURL);
+ return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile);
+ }
+ // if it's a pathname, create the nsILocalFile directly
+ var f = new nsLocalFile(aPathOrUrl);
+
+ return f;
+}
+
+/**
+ * Update the disabled state of the clear list button based on whether or not
+ * there are items in the list that can potentially be removed.
+ */
+function updateClearListButton()
+{
+ let button = document.getElementById("clearListButton");
+ // The button is enabled if we have items in the list and we can clean up
+ button.disabled = !(gDownloadsView.itemCount && gDownloadManager.canCleanUp);
+}
+
+function getDownload(aID)
+{
+ return document.getElementById("dl" + aID);
+}
+
+/**
+ * Initialize the statement which is used to retrieve the list of downloads.
+ */
+function initStatement()
+{
+ if (gStmt)
+ gStmt.finalize();
+
+ gStmt = gDownloadManager.DBConnection.createStatement(
+ "SELECT id, target, name, source, state, startTime, endTime, referrer, " +
+ "currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) isActive " +
+ "FROM moz_downloads " +
+ "ORDER BY isActive DESC, endTime DESC, startTime DESC");
+}
diff --git a/toolkit/mozapps/downloads/content/downloads.xul b/toolkit/mozapps/downloads/content/downloads.xul
new file mode 100644
index 000000000..5ca9eec2d
--- /dev/null
+++ b/toolkit/mozapps/downloads/content/downloads.xul
@@ -0,0 +1,164 @@
+<?xml version="1.0"?>
+
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# 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/.
+
+#ifdef XP_UNIX
+#ifndef XP_MACOSX
+#define XP_GNOME 1
+#endif
+#endif
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://mozapps/content/downloads/downloads.css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/downloads/downloads.css"?>
+
+<!DOCTYPE window [
+<!ENTITY % downloadManagerDTD SYSTEM "chrome://mozapps/locale/downloads/downloads.dtd">
+%downloadManagerDTD;
+<!ENTITY % editMenuOverlayDTD SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
+%editMenuOverlayDTD;
+]>
+
+<window xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="downloadManager" windowtype="Download:Manager"
+ orient="vertical" title="&downloads.title;" statictitle="&downloads.title;"
+ width="&window.width2;" height="&window.height;" screenX="10" screenY="10"
+ persist="width height screenX screenY sizemode"
+ onload="Startup();" onunload="Shutdown();"
+ onclose="return closeWindow(false);">
+
+ <script type="application/javascript" src="chrome://mozapps/content/downloads/downloads.js"/>
+ <script type="application/javascript" src="chrome://mozapps/content/downloads/DownloadProgressListener.js"/>
+ <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+
+ <stringbundleset id="downloadSet">
+ <stringbundle id="brandStrings" src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="downloadStrings" src="chrome://mozapps/locale/downloads/downloads.properties"/>
+ </stringbundleset>
+
+ <!-- Use this commandset for command which do not depened on focus or selection -->
+ <commandset id="generalCommands">
+ <command id="cmd_findDownload" oncommand="setSearchboxFocus();"/>
+ <command id="cmd_selectAllDownloads" oncommand="gDownloadsView.selectAll();"/>
+ <command id="cmd_clearList" oncommand="clearDownloadList();"/>
+ </commandset>
+
+ <keyset id="downloadKeys">
+ <key keycode="VK_RETURN" oncommand="doDefaultForSelected();"/>
+ <key id="key_pauseResume" key=" " oncommand="performCommand('cmd_pauseResume');"/>
+ <key id="key_removeFromList" keycode="VK_DELETE" oncommand="performCommand('cmd_removeFromList');"/>
+#ifdef XP_MACOSX
+ <key id="key_removeFromList2" keycode="VK_BACK" oncommand="performCommand('cmd_removeFromList');"/>
+#endif
+ <key id="key_close" key="&cmd.close.commandKey;" oncommand="closeWindow(true);" modifiers="accel"/>
+#ifdef XP_GNOME
+ <key id="key_close2" key="&cmd.close2Unix.commandKey;" oncommand="closeWindow(true);" modifiers="accel,shift"/>
+#else
+ <key id="key_close2" key="&cmd.close2.commandKey;" oncommand="closeWindow(true);" modifiers="accel"/>
+#endif
+ <key keycode="VK_ESCAPE" oncommand="closeWindow(true);"/>
+
+ <key id="key_findDownload"
+ key="&cmd.find.commandKey;"
+ modifiers="accel"
+ command="cmd_findDownload"/>
+ <key id="key_findDownload2"
+ key="&cmd.search.commandKey;"
+ modifiers="accel"
+ command="cmd_findDownload"/>
+ <key id="key_selectAllDownloads"
+ key="&selectAllCmd.key;"
+ modifiers="accel"
+ command="cmd_selectAllDownloads"/>
+ <key id="pasteKey"
+ key="V"
+ modifiers="accel"
+ oncommand="pasteHandler();"/>
+ </keyset>
+
+ <vbox id="contextMenuPalette" hidden="true">
+ <menuitem id="menuitem_pause"
+ label="&cmd.pause.label;" accesskey="&cmd.pause.accesskey;"
+ oncommand="performCommand('cmd_pause');"
+ cmd="cmd_pause"/>
+ <menuitem id="menuitem_resume"
+ label="&cmd.resume.label;" accesskey="&cmd.resume.accesskey;"
+ oncommand="performCommand('cmd_resume');"
+ cmd="cmd_resume"/>
+ <menuitem id="menuitem_cancel"
+ label="&cmd.cancel.label;" accesskey="&cmd.cancel.accesskey;"
+ oncommand="performCommand('cmd_cancel');"
+ cmd="cmd_cancel"/>
+
+ <menuitem id="menuitem_open" default="true"
+ label="&cmd.open.label;" accesskey="&cmd.open.accesskey;"
+ oncommand="performCommand('cmd_open');"
+ cmd="cmd_open"/>
+ <menuitem id="menuitem_show"
+#ifdef XP_MACOSX
+ label="&cmd.showMac.label;"
+ accesskey="&cmd.showMac.accesskey;"
+#else
+ label="&cmd.show.label;"
+ accesskey="&cmd.show.accesskey;"
+#endif
+ oncommand="performCommand('cmd_show');"
+ cmd="cmd_show"/>
+
+ <menuitem id="menuitem_retry" default="true"
+ label="&cmd.retry.label;" accesskey="&cmd.retry.accesskey;"
+ oncommand="performCommand('cmd_retry');"
+ cmd="cmd_retry"/>
+
+ <menuitem id="menuitem_removeFromList"
+ label="&cmd.removeFromList.label;" accesskey="&cmd.removeFromList.accesskey;"
+ oncommand="performCommand('cmd_removeFromList');"
+ cmd="cmd_removeFromList"/>
+
+ <menuseparator id="menuseparator"/>
+
+ <menuitem id="menuitem_openReferrer"
+ label="&cmd.goToDownloadPage.label;"
+ accesskey="&cmd.goToDownloadPage.accesskey;"
+ oncommand="performCommand('cmd_openReferrer');"
+ cmd="cmd_openReferrer"/>
+
+ <menuitem id="menuitem_copyLocation"
+ label="&cmd.copyDownloadLink.label;"
+ accesskey="&cmd.copyDownloadLink.accesskey;"
+ oncommand="performCommand('cmd_copyLocation');"
+ cmd="cmd_copyLocation"/>
+
+ <menuitem id="menuitem_selectAll"
+ label="&selectAllCmd.label;"
+ accesskey="&selectAllCmd.accesskey;"
+ command="cmd_selectAllDownloads"/>
+ </vbox>
+
+ <menupopup id="downloadContextMenu" onpopupshowing="return buildContextMenu(event);"/>
+
+ <richlistbox id="downloadView" seltype="multiple" flex="1"
+ context="downloadContextMenu"
+ ondblclick="onDownloadDblClick(event);"
+ ondragstart="gDownloadDNDObserver.onDragStart(event);"
+ ondragover="gDownloadDNDObserver.onDragOver(event);event.stopPropagation();"
+ ondrop="gDownloadDNDObserver.onDrop(event)">
+ </richlistbox>
+
+ <windowdragbox id="search" align="center">
+ <button id="clearListButton" command="cmd_clearList"
+ label="&cmd.clearList.label;"
+ accesskey="&cmd.clearList.accesskey;"
+ tooltiptext="&cmd.clearList.tooltip;"/>
+ <spacer flex="1"/>
+ <textbox type="search" id="searchbox" class="compact"
+ aria-controls="downloadView"
+ oncommand="buildDownloadList();" placeholder="&searchBox.label;"/>
+ </windowdragbox>
+
+</window>
diff --git a/toolkit/mozapps/downloads/content/unknownContentType.xul b/toolkit/mozapps/downloads/content/unknownContentType.xul
new file mode 100644
index 000000000..af8b7b016
--- /dev/null
+++ b/toolkit/mozapps/downloads/content/unknownContentType.xul
@@ -0,0 +1,107 @@
+<?xml version="1.0"?>
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# 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://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://mozapps/skin/downloads/unknownContentType.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+ %brandDTD;
+ <!ENTITY % uctDTD SYSTEM "chrome://mozapps/locale/downloads/unknownContentType.dtd" >
+ %uctDTD;
+ <!ENTITY % scDTD SYSTEM "chrome://mozapps/locale/downloads/settingsChange.dtd" >
+ %scDTD;
+]>
+
+<dialog id="unknownContentType"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="dialog.initDialog();" onunload="if (dialog) dialog.onCancel();"
+#ifdef XP_WIN
+ style="width: 36em;"
+#else
+ style="width: 34em;"
+#endif
+ screenX="" screenY=""
+ persist="screenX screenY"
+ aria-describedby="intro location whichIs type from source unknownPrompt"
+ ondialogaccept="return dialog.onOK()"
+ ondialogcancel="return dialog.onCancel()">
+
+
+ <stringbundle id="strings" src="chrome://mozapps/locale/downloads/unknownContentType.properties"/>
+
+ <vbox flex="1" id="container">
+ <description id="intro">&intro2.label;</description>
+ <separator class="thin"/>
+ <hbox align="start" class="small-indent">
+ <image id="contentTypeImage"/>
+ <vbox flex="1">
+ <description id="location" class="plain" crop="start" flex="1"/>
+ <separator class="thin"/>
+ <hbox align="center">
+ <label id="whichIs" value="&whichIs.label;"/>
+ <textbox id="type" class="plain" readonly="true" flex="1" noinitialfocus="true"/>
+ </hbox>
+ <hbox align="center">
+ <label value="&from.label;" id="from"/>
+ <description id="source" class="plain" crop="start" flex="1"/>
+ </hbox>
+ </vbox>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <hbox align="center" id="basicBox" collapsed="true">
+ <label id="unknownPrompt" value="&unknownPromptText.label;" flex="1"/>
+ </hbox>
+
+ <groupbox flex="1" id="normalBox">
+ <caption label="&actionQuestion.label;"/>
+ <separator class="thin"/>
+ <radiogroup id="mode" class="small-indent">
+ <hbox>
+ <radio id="open" label="&openWith.label;" accesskey="&openWith.accesskey;"/>
+ <deck id="modeDeck" flex="1">
+ <hbox id="openHandlerBox" flex="1" align="center"/>
+ <hbox flex="1" align="center">
+ <button id="chooseButton" oncommand="dialog.chooseApp();"
+#ifdef XP_MACOSX
+ label="&chooseHandlerMac.label;" accesskey="&chooseHandlerMac.accesskey;"/>
+#else
+ label="&chooseHandler.label;" accesskey="&chooseHandler.accesskey;"/>
+#endif
+ </hbox>
+ </deck>
+ </hbox>
+
+ <radio id="save" label="&saveFile.label;" accesskey="&saveFile.accesskey;"/>
+ </radiogroup>
+ <separator class="thin"/>
+ <hbox class="small-indent">
+ <checkbox id="rememberChoice" label="&rememberChoice.label;"
+ accesskey="&rememberChoice.accesskey;"
+ oncommand="dialog.toggleRememberChoice(event.target);"/>
+ </hbox>
+
+ <separator/>
+#ifdef XP_UNIX
+ <description id="settingsChange" hidden="true">&settingsChangePreferences.label;</description>
+#else
+ <description id="settingsChange" hidden="true">&settingsChangeOptions.label;</description>
+#endif
+ <separator class="thin"/>
+ </groupbox>
+ </vbox>
+
+ <menulist id="openHandler" flex="1">
+ <menupopup id="openHandlerPopup" oncommand="dialog.openHandlerCommand();">
+ <menuitem id="defaultHandler" default="true" crop="right"/>
+ <menuitem id="otherHandler" hidden="true" crop="left"/>
+ <menuseparator/>
+ <menuitem id="choose" label="&other.label;"/>
+ </menupopup>
+ </menulist>
+</dialog>