diff options
Diffstat (limited to 'mobile/android/chrome/content/aboutDownloads.js')
-rw-r--r-- | mobile/android/chrome/content/aboutDownloads.js | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/mobile/android/chrome/content/aboutDownloads.js b/mobile/android/chrome/content/aboutDownloads.js new file mode 100644 index 000000000..add0a48e6 --- /dev/null +++ b/mobile/android/chrome/content/aboutDownloads.js @@ -0,0 +1,373 @@ +/* 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(/</g, "<"); + s = s.replace(/"/g, """); + s = s.replace(/'/g, "'"); + return s; + }, + + _updateFromDownload: function () { + this._state = {}; + kDownloadStatePropertyNames.forEach( + name => 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 <template> is working + // let element = document.importNode(template.content, true); + + // simulate a <template> node... + let element = template.cloneNode(true); + element.removeAttribute("id"); + element.removeAttribute("style"); + + // launch the download if clicked + element.addEventListener("click", this.onClick.bind(this)); + + // set download as an expando property for the context menu + element.download = this.download; + + // fill in template placeholders + this.updateElement(element); + + return element; + }, + + updateElement: function (element) { + element.querySelector(".date").textContent = this.startDate; + element.querySelector(".domain").textContent = this.domain; + element.querySelector(".icon").src = this.iconUrl; + element.querySelector(".size").textContent = this.size; + element.querySelector(".state").textContent = this.stateDescription; + element.querySelector(".title").setAttribute("value", this.fileName); + }, + + onClick: function (event) { + if (this.download.succeeded) { + this.download.launch().then(null, Cu.reportError); + } + }, + + onDownloadChanged: function () { + this._updateFromDownload(); + this.updateElement(this.element); + }, + + // template properties below + get domain() { + return this._domain; + }, + get fileName() { + return this._fileName; + }, + get id() { + return this._id; + }, + get iconUrl() { + return this._iconUrl; + }, + + get size() { + if (this.download.succeeded && this.download.target.exists) { + return DownloadUtils.convertByteUnits(this.download.target.size).join(""); + } else if (this.download.hasProgress) { + return DownloadUtils.convertByteUnits(this.download.totalBytes).join(""); + } + return strings.GetStringFromName("downloadState.unknownSize"); + }, + + get startDate() { + return this._startDate; + }, + + get stateDescription() { + let name; + if (this.download.error) { + name = "downloadState.failed"; + } else if (this.download.canceled) { + if (this.download.hasPartialData) { + name = "downloadState.paused"; + } else { + name = "downloadState.canceled"; + } + } else if (!this.download.stopped) { + if (this.download.currentBytes > 0) { + name = "downloadState.downloading"; + } else { + name = "downloadState.starting"; + } + } + + if (name) { + return strings.GetStringFromName(name); + } + return ""; + } +}; + +window.addEventListener("DOMContentLoaded", event => { + contextMenu.init(); + downloadLists.init() +});
\ No newline at end of file |