diff options
Diffstat (limited to 'comm/mail/components/downloads')
-rw-r--r-- | comm/mail/components/downloads/content/aboutDownloads.js | 414 | ||||
-rw-r--r-- | comm/mail/components/downloads/content/aboutDownloads.xhtml | 98 | ||||
-rw-r--r-- | comm/mail/components/downloads/jar.mn | 7 | ||||
-rw-r--r-- | comm/mail/components/downloads/moz.build | 5 |
4 files changed, 524 insertions, 0 deletions
diff --git a/comm/mail/components/downloads/content/aboutDownloads.js b/comm/mail/components/downloads/content/aboutDownloads.js new file mode 100644 index 0000000000..6cd7e2973c --- /dev/null +++ b/comm/mail/components/downloads/content/aboutDownloads.js @@ -0,0 +1,414 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +/* globals goUpdateCommand */ + +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + Downloads: "resource://gre/modules/Downloads.sys.mjs", + DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs", +}); + +window.addEventListener("load", event => { + DownloadsView.init(); +}); + +var DownloadsView = { + init() { + window.controllers.insertControllerAt(0, this); + this.listElement = document.getElementById("msgDownloadsRichListBox"); + + this.items = new Map(); + + Downloads.getList(Downloads.ALL) + .then(list => list.addView(this)) + .catch(console.error); + + window.addEventListener("unload", aEvent => { + Downloads.getList(Downloads.ALL) + .then(list => list.removeView(this)) + .catch(console.error); + window.controllers.removeController(this); + }); + }, + + insertOrMoveItem(aItem) { + let compare = (a, b) => { + // active downloads always before stopped downloads + if (a.stopped != b.stopped) { + return b.stopped ? -1 : 1; + } + // most recent downloads first + return b.startTime - a.startTime; + }; + + let at = this.listElement.firstElementChild; + while (at && compare(aItem.download, at.download) > 0) { + at = at.nextElementSibling; + } + this.listElement.insertBefore(aItem.element, at); + }, + + onDownloadAdded(aDownload) { + let isPurgedFromDisk = download => { + if (!download.succeeded) { + return false; + } + let targetFile = Cc["@mozilla.org/file/local;1"].createInstance( + Ci.nsIFile + ); + targetFile.initWithPath(download.target.path); + return !targetFile.exists(); + }; + if (isPurgedFromDisk(aDownload)) { + Downloads.getList(Downloads.ALL).then(list => list.remove(aDownload)); + return; + } + + let item = new DownloadItem(aDownload); + this.items.set(aDownload, item); + this.insertOrMoveItem(item); + }, + + onDownloadChanged(aDownload) { + let item = this.items.get(aDownload); + if (!item) { + console.error("No DownloadItem found for download"); + return; + } + + if (item.stateChanged) { + this.insertOrMoveItem(item); + } + + item.onDownloadChanged(); + }, + + onDownloadRemoved(aDownload) { + let item = this.items.get(aDownload); + if (!item) { + console.error("No DownloadItem found for download"); + return; + } + + this.items.delete(aDownload); + this.listElement.removeChild(item.element); + }, + + onDownloadContextMenu() { + this.updateCommands(); + }, + + clearDownloads() { + Downloads.getList(Downloads.ALL) + .then(list => list.removeFinished()) + .catch(console.error); + }, + + searchDownloads() { + let searchString = document.getElementById("searchBox").value.toLowerCase(); + for (let i = 0; i < this.listElement.itemCount; i++) { + let downloadElem = this.listElement.getItemAtIndex(i); + downloadElem.collapsed = !downloadElem.downloadItem.fileName + .toLowerCase() + .includes(searchString); + } + this.listElement.clearSelection(); + }, + + supportsCommand(aCommand) { + return ( + this.commands.includes(aCommand) || + DownloadItem.prototype.supportsCommand(aCommand) + ); + }, + + isCommandEnabled(aCommand) { + switch (aCommand) { + case "msgDownloadsCmd_clearDownloads": + case "msgDownloadsCmd_searchDownloads": + // We could disable these if there are no downloads in the list, but + // updating the commands when new items become available is tricky. + return true; + } + + let element = this.listElement.selectedItem; + if (element) { + return element.downloadItem.isCommandEnabled(aCommand); + } + + return false; + }, + + doCommand(aCommand) { + switch (aCommand) { + case "msgDownloadsCmd_clearDownloads": + this.clearDownloads(); + return; + case "msgDownloadsCmd_searchDownloads": + this.searchDownloads(); + return; + } + + if (this.listElement.selectedCount == 0) { + return; + } + + for (let element of this.listElement.selectedItems) { + element.downloadItem.doCommand(aCommand); + } + }, + + onEvent() {}, + + updateCommands() { + this.commands.forEach(goUpdateCommand); + DownloadItem.prototype.commands.forEach(goUpdateCommand); + }, + + commands: [ + "msgDownloadsCmd_clearDownloads", + "msgDownloadsCmd_searchDownloads", + ], +}; + +function DownloadItem(aDownload) { + this._download = aDownload; + this._updateFromDownload(); + + if (aDownload._unknownProperties && aDownload._unknownProperties.sender) { + this._sender = aDownload._unknownProperties.sender; + } else { + this._sender = ""; + } + this._fileName = this._htmlEscape(PathUtils.filename(aDownload.target.path)); + this._iconUrl = "moz-icon://" + this._fileName + "?size=32"; + this._startDate = this._htmlEscape( + DownloadUtils.getReadableDates(aDownload.startTime)[0] + ); + this._filePath = aDownload.target.path; +} + +var kDownloadStatePropertyNames = [ + "stopped", + "succeeded", + "canceled", + "error", + "startTime", +]; + +DownloadItem.prototype = { + _htmlEscape(s) { + s = s.replace(/&/g, "&"); + s = s.replace(/>/g, ">"); + s = s.replace(/</g, "<"); + s = s.replace(/"/g, """); + s = s.replace(/'/g, "'"); + return s; + }, + + _updateFromDownload() { + this._state = {}; + for (let name of kDownloadStatePropertyNames) { + this._state[name] = this._download[name]; + } + }, + + get stateChanged() { + for (let name of kDownloadStatePropertyNames) { + if (this._state[name] != this._download[name]) { + return true; + } + } + return false; + }, + + get download() { + return this._download; + }, + + get element() { + if (!this._element) { + this._element = this.createXULElement(); + } + + return this._element; + }, + + createXULElement() { + let element = document.createXULElement("richlistitem"); + element.classList.add("download"); + element.setAttribute("align", "center"); + + let image = document.createElement("img"); + image.setAttribute("alt", ""); + // Allow the given src to be invalid. + image.classList.add("fileTypeIcon", "invisible-on-broken"); + + let vbox = document.createXULElement("vbox"); + vbox.setAttribute("pack", "center"); + vbox.setAttribute("flex", "1"); + + let hbox = document.createXULElement("hbox"); + let hbox2 = document.createXULElement("hbox"); + + let sender = document.createXULElement("description"); + sender.classList.add("sender"); + + let fileName = document.createXULElement("description"); + fileName.setAttribute("crop", "center"); + fileName.classList.add("fileName"); + + let size = document.createXULElement("description"); + size.classList.add("size"); + + let startDate = document.createXULElement("description"); + startDate.setAttribute("crop", "end"); + startDate.classList.add("startDate"); + + hbox.appendChild(fileName); + hbox.appendChild(size); + hbox2.appendChild(sender); + hbox2.appendChild(startDate); + + vbox.appendChild(hbox); + vbox.appendChild(hbox2); + + let vbox2 = document.createXULElement("vbox"); + + let downloadButton = document.createXULElement("button"); + downloadButton.classList.add("downloadButton", "downloadIconShow"); + + vbox2.appendChild(downloadButton); + + element.appendChild(image); + element.appendChild(vbox2); + element.appendChild(vbox); + + // launch the download if double clicked + vbox.addEventListener("dblclick", aEvent => this.launch()); + + // Show the downloaded file in folder if the folder icon is clicked. + downloadButton.addEventListener("click", aEvent => this.show()); + + // set download as an expando property for the context menu + element.download = this.download; + element.downloadItem = this; + + this.updateElement(element); + + return element; + }, + + updateElement(element) { + let fileTypeIcon = element.querySelector(".fileTypeIcon"); + fileTypeIcon.setAttribute("src", this.iconUrl); + + let size = element.querySelector(".size"); + size.setAttribute("value", this.size); + size.setAttribute("tooltiptext", this.size); + + let fileName = element.querySelector(".fileName"); + fileName.setAttribute("value", this.fileName); + fileName.setAttribute("tooltiptext", this.fileName); + + let sender = element.querySelector(".sender"); + sender.setAttribute("value", this.sender); + sender.setAttribute("tooltiptext", this.sender); + + let startDate = element.querySelector(".startDate"); + startDate.setAttribute("value", this.startDate); + startDate.setAttribute("tooltiptext", this.startDate); + }, + + launch() { + if (this.download.succeeded) { + this.download.launch().catch(console.error); + } + }, + + remove() { + Downloads.getList(Downloads.ALL) + .then(list => list.remove(this.download)) + .then(() => this.download.finalize(true)) + .catch(console.error); + }, + + show() { + if (this.download.succeeded) { + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.initWithPath(this._filePath); + file.reveal(); + } + }, + + onDownloadChanged() { + this._updateFromDownload(); + this.updateElement(this.element); + }, + + get fileName() { + return this._fileName; + }, + + get iconUrl() { + return this._iconUrl; + }, + + get sender() { + return this._sender; + }, + + get size() { + let bytes; + if (this.download.succeeded || this.download.hasProgress) { + bytes = this.download.target.size; + } else { + bytes = this.download.currentBytes; + } + return DownloadUtils.convertByteUnits(bytes).join(""); + }, + + get startDate() { + return this._startDate; + }, + + supportsCommand(aCommand) { + return this.commands.includes(aCommand); + }, + + isCommandEnabled(aCommand) { + switch (aCommand) { + case "msgDownloadsCmd_open": + case "msgDownloadsCmd_show": + return this.download.succeeded; + case "msgDownloadsCmd_remove": + return true; + } + return false; + }, + + doCommand(aCommand) { + switch (aCommand) { + case "msgDownloadsCmd_open": + this.launch(); + break; + case "msgDownloadsCmd_show": + this.show(); + break; + case "msgDownloadsCmd_remove": + this.remove(); + break; + } + }, + + commands: [ + "msgDownloadsCmd_remove", + "msgDownloadsCmd_open", + "msgDownloadsCmd_show", + ], +}; diff --git a/comm/mail/components/downloads/content/aboutDownloads.xhtml b/comm/mail/components/downloads/content/aboutDownloads.xhtml new file mode 100644 index 0000000000..fdc570c06f --- /dev/null +++ b/comm/mail/components/downloads/content/aboutDownloads.xhtml @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- 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/global.css"?> +<?xml-stylesheet href="chrome://messenger/skin/messenger.css"?> +<?xml-stylesheet href="chrome://global/skin/in-content/common.css"?> +<?xml-stylesheet href="chrome://messenger/skin/downloads/aboutDownloads.css"?> + +<!DOCTYPE html [ +<!ENTITY % aboutDownloadsDTD SYSTEM "chrome://messenger/locale/aboutDownloads.dtd"> +%aboutDownloadsDTD; +]> + +<html id="aboutDownloads" xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + scrolling="false" + lightweightthemes="true"> +<head> + <title>&aboutDownloads.title;</title> + <script defer="defer" src="chrome://messenger/content/globalOverlay.js"></script> + <script defer="defer" src="chrome://global/content/editMenuOverlay.js"></script> + <script defer="defer" src="chrome://messenger/content/downloads/aboutDownloads.js"></script> + <meta http-equiv="Content-Security-Policy" content="default-src chrome:; img-src chrome: moz-icon:; object-src 'none'; script-src chrome: 'unsafe-inline'" /> + <meta name="color-scheme" content="light dark" /> + <link rel="icon" href="chrome://messenger/skin/icons/new/compact/download.svg" /> +</head> +<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <commandset id="msgDownloadCommands" + commandupdater="true" + events="focus,select,contextmenu"> + <command id="msgDownloadsCmd_open" + oncommand="goDoCommand('msgDownloadsCmd_open')"/> + <command id="msgDownloadsCmd_show" + oncommand="goDoCommand('msgDownloadsCmd_show')"/> + <command id="msgDownloadsCmd_remove" + oncommand="goDoCommand('msgDownloadsCmd_remove')"/> + <command id="msgDownloadsCmd_clearDownloads" + oncommand="goDoCommand('msgDownloadsCmd_clearDownloads')"/> + <command id="msgDownloadsCmd_searchDownloads" + oncommand="goDoCommand('msgDownloadsCmd_searchDownloads')"/> + </commandset> + + <keyset id="downloadKeys"> + <key keycode="&cmd.searchDownloads.key;" modifiers="accel" + oncommand="document.getElementById('searchBox').focus();"/> + </keyset> + + <hbox id="downloadTopBox" + align="center"> + <button id="clearDownloads" + command="msgDownloadsCmd_clearDownloads" + label="&cmd.clearList.label;" + accesskey="&cmd.clearList.accesskey;" + tooltiptext="&cmd.clearList.tooltip;"/> + + <spacer flex="1"/> + <search-textbox id="searchBox" + class="themeableSearchBox" + command="msgDownloadsCmd_searchDownloads" + placeholder="&cmd.searchDownloads.label;"/> + </hbox> + + <hbox id="downloadBottomBox" flex="1"> + <richlistbox id="msgDownloadsRichListBox" + flex="1" + seltype="multiple" + context="msgDownloadsContextMenu" + oncontextmenu="DownloadsView.onDownloadContextMenu();"/> + </hbox> + + <menupopup id="msgDownloadsContextMenu"> + <menuitem command="msgDownloadsCmd_remove" + class="msgDownloadRemoveFromHistoryMenuItem" + label="&cmd.removeFromHistory.label;" + accesskey="&cmd.removeFromHistory.accesskey;"/> + <menuitem command="msgDownloadsCmd_open" + label="&cmd.open.label;" + accesskey="&cmd.open.accesskey;"/> + <menuitem command="msgDownloadsCmd_show" + class="msgDownloadShowMenuItem" +#ifdef XP_MACOSX + label="&cmd.showMac.label;" + accesskey="&cmd.showMac.accesskey;" +#else + label="&cmd.show.label;" + accesskey="&cmd.show.accesskey;" +#endif + /> + <menuitem command="msgDownloadsCmd_clearDownloads" + label="&cmd.clearList.label;" + accesskey="&cmd.clearList.accesskey;" + tooltiptext="&cmd.clearList.tooltip;"/> + </menupopup> +</html:body> +</html> diff --git a/comm/mail/components/downloads/jar.mn b/comm/mail/components/downloads/jar.mn new file mode 100644 index 0000000000..ff6628e82d --- /dev/null +++ b/comm/mail/components/downloads/jar.mn @@ -0,0 +1,7 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +messenger.jar: + content/messenger/downloads/aboutDownloads.js (content/aboutDownloads.js) +* content/messenger/downloads/aboutDownloads.xhtml (content/aboutDownloads.xhtml) diff --git a/comm/mail/components/downloads/moz.build b/comm/mail/components/downloads/moz.build new file mode 100644 index 0000000000..ea0b25aae8 --- /dev/null +++ b/comm/mail/components/downloads/moz.build @@ -0,0 +1,5 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +JAR_MANIFESTS += ["jar.mn"] |