/* 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 Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); XPCOMUtils.defineLazyModuleGetter(this, "Downloads", "resource://gre/modules/Downloads.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils", "resource://gre/modules/DownloadUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Messaging", "resource://gre/modules/Messaging.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm"); var gStrings = Services.strings.createBundle("chrome://browser/locale/aboutDownloads.properties"); XPCOMUtils.defineLazyGetter(this, "strings", () => Services.strings.createBundle("chrome://browser/locale/aboutDownloads.properties")); function deleteDownload(download) { download.finalize(true).then(null, Cu.reportError); OS.File.remove(download.target.path).then(null, ex => { if (!(ex instanceof OS.File.Error && ex.becauseNoSuchFile)) { Cu.reportError(ex); } }); } var contextMenu = { _items: [], _targetDownload: null, init: function () { let element = document.getElementById("downloadmenu"); element.addEventListener("click", event => event.download = this._targetDownload, true); this._items = [ new ContextMenuItem("open", download => download.succeeded, download => download.launch().then(null, Cu.reportError)), new ContextMenuItem("retry", download => download.error || (download.canceled && !download.hasPartialData), download => download.start().then(null, Cu.reportError)), new ContextMenuItem("remove", download => download.stopped, download => { Downloads.getList(Downloads.ALL) .then(list => list.remove(download)) .then(null, Cu.reportError); deleteDownload(download); }), new ContextMenuItem("pause", download => !download.stopped && download.hasPartialData, download => download.cancel().then(null, Cu.reportError)), new ContextMenuItem("resume", download => download.canceled && download.hasPartialData, download => download.start().then(null, Cu.reportError)), new ContextMenuItem("cancel", download => !download.stopped || (download.canceled && download.hasPartialData), download => { download.cancel().then(null, Cu.reportError); download.removePartialData().then(null, Cu.reportError); }), // following menu item is a global action new ContextMenuItem("removeall", () => downloadLists.finished.length > 0, () => downloadLists.removeFinished()) ]; }, addContextMenuEventListener: function (element) { element.addEventListener("contextmenu", this.onContextMenu.bind(this)); }, onContextMenu: function (event) { let target = event.target; while (target && !target.download) { target = target.parentNode; } if (!target) { Cu.reportError("No download found for context menu target"); event.preventDefault(); return; } // capture the target download for menu items to use in a click event this._targetDownload = target.download; for (let item of this._items) { item.updateVisibility(target.download); } } }; function ContextMenuItem(name, isVisible, action) { this.element = document.getElementById("contextmenu-" + name); this.isVisible = isVisible; this.element.addEventListener("click", event => action(event.download)); } ContextMenuItem.prototype = { updateVisibility: function (download) { this.element.hidden = !this.isVisible(download); } }; function DownloadListView(type, listElementId) { this.listElement = document.getElementById(listElementId); contextMenu.addContextMenuEventListener(this.listElement); this.items = new Map(); Downloads.getList(type) .then(list => list.addView(this)) .then(null, Cu.reportError); window.addEventListener("unload", event => { Downloads.getList(type) .then(list => list.removeView(this)) .then(null, Cu.reportError); }); } DownloadListView.prototype = { get finished() { let finished = []; for (let download of this.items.keys()) { if (download.stopped && (!download.hasPartialData || download.error)) { finished.push(download); } } return finished; }, insertOrMoveItem: function (item) { var 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 insertLocation = this.listElement.firstChild; while (insertLocation && compare(item.download, insertLocation.download) > 0) { insertLocation = insertLocation.nextElementSibling; } this.listElement.insertBefore(item.element, insertLocation); }, onDownloadAdded: function (download) { let item = new DownloadItem(download); this.items.set(download, item); this.insertOrMoveItem(item); }, onDownloadChanged: function (download) { let item = this.items.get(download); if (!item) { Cu.reportError("No DownloadItem found for download"); return; } if (item.stateChanged) { this.insertOrMoveItem(item); } item.onDownloadChanged(); }, onDownloadRemoved: function (download) { let item = this.items.get(download); if (!item) { Cu.reportError("No DownloadItem found for download"); return; } this.items.delete(download); this.listElement.removeChild(item.element); Messaging.sendRequest({ type: "Download:Remove", path: download.target.path }); } }; var downloadLists = { init: function () { this.publicDownloads = new DownloadListView(Downloads.PUBLIC, "public-downloads-list"); this.privateDownloads = new DownloadListView(Downloads.PRIVATE, "private-downloads-list"); }, get finished() { return this.publicDownloads.finished.concat(this.privateDownloads.finished); }, removeFinished: function () { let finished = this.finished; if (finished.length == 0) { return; } let title = strings.GetStringFromName("downloadAction.deleteAll"); let messageForm = strings.GetStringFromName("downloadMessage.deleteAll"); let message = PluralForm.get(finished.length, messageForm).replace("#1", finished.length); if (Services.prompt.confirm(null, title, message)) { Downloads.getList(Downloads.ALL) .then(list => { for (let download of finished) { list.remove(download).then(null, Cu.reportError); deleteDownload(download); } }, Cu.reportError); } } }; function DownloadItem(download) { this._download = download; this._updateFromDownload(); this._domain = DownloadUtils.getURIHost(download.source.url)[0]; this._fileName = this._htmlEscape(OS.Path.basename(download.target.path)); this._iconUrl = "moz-icon://" + this._fileName + "?size=64"; this._startDate = this._htmlEscape(DownloadUtils.getReadableDates(download.startTime)[0]); this._element = this.createElement(); } const kDownloadStatePropertyNames = [ "stopped", "succeeded", "canceled", "error", "startTime" ]; DownloadItem.prototype = { _htmlEscape : function (s) { s = s.replace(/&/g, "&"); s = s.replace(/>/g, ">"); s = s.replace(/ this._state[name] = this._download[name], this); }, get stateChanged() { return kDownloadStatePropertyNames.some( name => this._state[name] != this._download[name], this); }, get download() { return this._download; }, get element() { return this._element; }, createElement: function() { let template = document.getElementById("download-item"); // TODO: use this once