tenfourfox/mobile/android/chrome/content/aboutDownloads.js
Cameron Kaiser c9b2922b70 hello FPR
2017-04-19 00:56:45 -07:00

371 lines
11 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
var 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, "&lt;");
s = s.replace(/"/g, "&quot;");
s = s.replace(/'/g, "&apos;");
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.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()
});